]> git.proxmox.com Git - mirror_acme.sh.git/blobdiff - le.sh
add timeout for nc.
[mirror_acme.sh.git] / le.sh
diff --git a/le.sh b/le.sh
index c028e75fdc4aeae4e6639d3292512d9c88a2b69a..fd38622696388adc24a6e3366b5ab56f8debe595 100755 (executable)
--- a/le.sh
+++ b/le.sh
@@ -1,47 +1,66 @@
 #!/usr/bin/env bash
-VER=1.1.9
+VER=2.0.0
 PROJECT="https://github.com/Neilpang/le"
 
 DEFAULT_CA="https://acme-v01.api.letsencrypt.org"
 DEFAULT_AGREEMENT="https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"
 
+DEFAULT_USER_AGENT="le.sh client: $PROJECT"
+
 STAGE_CA="https://acme-staging.api.letsencrypt.org"
 
 VTYPE_HTTP="http-01"
 VTYPE_DNS="dns-01"
 
-if [ -z "$AGREEMENT" ] ; then
+BEGIN_CSR="-----BEGIN CERTIFICATE REQUEST-----"
+END_CSR="-----END CERTIFICATE REQUEST-----"
+
+BEGIN_CERT="-----BEGIN CERTIFICATE-----"
+END_CERT="-----END CERTIFICATE-----"
+
+if [[ -z "$AGREEMENT" ]] ; then
   AGREEMENT="$DEFAULT_AGREEMENT"
 fi
 
-_debug() {
 
-  if [ -z "$DEBUG" ] ; then
-    return
-  fi
-  
-  if [ -z "$2" ] ; then
-    echo $1
+_info() {
+  if [[ -z "$2" ]] ; then
+    echo "[$(date)] $1"
   else
-    echo "$1"="$2"
+    echo "[$(date)] $1"="'$2'"
   fi
 }
 
-_info() {
-  if [ -z "$2" ] ; then
-    echo "$1"
-  else
-    echo "$1"="$2"
+_err() {
+  _info "$@" >&2
+  return 1
+}
+
+_debug() {
+  if [[ -z "$DEBUG" ]] ; then
+    return
   fi
+  _err "$@"
+  return 0
 }
 
-_err() {
-  if [ -z "$2" ] ; then
-    echo "$1" >&2
-  else
-    echo "$1"="$2" >&2
+_debug2() {
+  if [[ "$DEBUG" -ge "2" ]] ; then
+    _debug "$@"
   fi
-  return 1
+  return
+}
+
+_exists() {
+  cmd="$1"
+  if [[ -z "$cmd" ]] ; then
+    _err "Usage: _exists cmd"
+    return 1
+  fi
+  command -v $cmd >/dev/null 2>&1
+  ret="$?"
+  _debug2 "$cmd exists=$ret"
+  return $ret
 }
 
 _h2b() {
@@ -50,7 +69,7 @@ _h2b() {
   j=2
   while [ '1' ] ; do
     h=$(printf $hex | cut -c $i-$j)
-    if [ -z "$h" ] ; then
+    if [[ -z "$h" ]] ; then
       break;
     fi
     printf "\x$h"
@@ -59,30 +78,164 @@ _h2b() {
   done
 }
 
+#options file
+_sed_i() {
+  options="$1"
+  filename="$2"
+  if [[ -z "$filename" ]] ; then
+    _err "Usage:_sed_i options filename"
+    return 1
+  fi
+  
+  if sed -h 2>&1 | grep "\-i[SUFFIX]" ; then
+    _debug "Using sed  -i"
+    sed -i ""
+  else
+    _debug "No -i support in sed"
+    text="$(cat $filename)"
+    echo "$text" | sed "$options" > "$filename"
+  fi
+}
+
+#Usage: file startline endline
+_getfile() {
+  filename="$1"
+  startline="$2"
+  endline="$3"
+  if [[ -z "$endline" ]] ; then
+    _err "Usage: file startline endline"
+    return 1
+  fi
+  
+  i="$(grep -n --  "$startline"  $filename | cut -d : -f 1)"
+  if [[ -z "$i" ]] ; then
+    _err "Can not find start line: $startline"
+    return 1
+  fi
+  let "i+=1"
+  _debug i $i
+  
+  j="$(grep -n --  "$endline"  $filename | cut -d : -f 1)"
+  if [[ -z "$j" ]] ; then
+    _err "Can not find end line: $endline"
+    return 1
+  fi
+  let "j-=1"
+  _debug j $j
+  
+  sed -n $i,${j}p  "$filename"
+
+}
+
+#Usage: multiline
 _base64() {
-  openssl base64 -e | tr -d '\n'
+  if [[ "$1" ]] ; then
+    openssl base64 -e
+  else
+    openssl base64 -e | tr -d '\r\n'
+  fi
+}
+
+#Usage: multiline
+_dbase64() {
+  if [[ "$1" ]] ; then
+    openssl base64 -d -A
+  else
+    openssl base64 -d
+  fi
+}
+
+#Usage: hashalg
+#Output Base64-encoded digest
+_digest() {
+  alg="$1"
+  if [[ -z "$alg" ]] ; then
+    _err "Usage: _digest hashalg"
+    return 1
+  fi
+  
+  if [[ "$alg" == "sha256" ]] ; then
+    openssl dgst -sha256 -binary | _base64
+  else
+    _err "$alg is not supported yet"
+    return 1
+  fi
+
+}
+
+#Usage: keyfile hashalg
+#Output: Base64-encoded signature value
+_sign() {
+  keyfile="$1"
+  alg="$2"
+  if [[ -z "$alg" ]] ; then
+    _err "Usage: _sign keyfile hashalg"
+    return 1
+  fi
+  
+  if [[ "$alg" == "sha256" ]] ; then
+    openssl   dgst   -sha256  -sign  "$keyfile" | _base64
+  else
+    _err "$alg is not supported yet"
+    return 1
+  fi  
+  
 }
 
 _ss() {
   _port="$1"
-  if command -v "netstat" >/dev/null 2>&1 ; then
-    _debug "Using: netstat"
-    netstat -ntpl | grep :$_port" "
-    return 0
-  fi
-  if command -v "ss" >/dev/null 2>&1 ; then
+  
+  if _exists "ss" ; then
     _debug "Using: ss"
     ss -ntpl | grep :$_port" "
     return 0
   fi
+
+  if _exists "netstat" ; then
+    _debug "Using: netstat"
+    if netstat -h 2>&1 | grep "\-p proto" >/dev/null ; then
+      #for windows version netstat tool
+      netstat -anb -p tcp | grep "LISTENING" | grep :$_port" "
+    else
+      if netstat -help 2>&1 | grep "\-p protocol" >/dev/null ; then
+        netstat -an -p tcp | grep LISTEN | grep :$_port" "
+      else
+        netstat -ntpl | grep :$_port" "
+      fi
+    fi
+    return 0
+  fi
+
   return 1
 }
 
+toPkcs() {
+  domain="$1"
+  pfxPassword="$2"
+  if [[ -z "$domain" ]] ; then
+    echo "Usage: le.sh --toPkcs -d domain [--password pfx-password]"
+    return 1
+  fi
+
+  _initpath "$domain"
+  
+  if [[ "$pfxPassword" ]] ; then
+    openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword"
+  else
+    openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH"
+  fi
+  
+  if [[ "$?" == "0" ]] ; then
+    _info "Success, Pfx is exported to: $CERT_PFX_PATH"
+  fi
+
+}
+
 #domain [2048]  
 createAccountKey() {
   _info "Creating account key"
-  if [ -z "$1" ] ; then
-    echo Usage: createAccountKey account-domain  [2048]
+  if [[ -z "$1" ]] ; then
+    echo Usage: le.sh --createAccountKey -d domain.com  [--accountkeylength 2048]
     return
   fi
   
@@ -93,13 +246,13 @@ createAccountKey() {
     length=2048
   fi
   
-  if [ -z "$2" ] ; then
+  if [[ -z "$2" ]] ; then
     _info "Use default length 2048"
     length=2048
   fi
   _initpath
   
-  if [ -f "$ACCOUNT_KEY_PATH" ] ; then
+  if [[ -f "$ACCOUNT_KEY_PATH" ]] ; then
     _info "Account key exists, skip"
     return
   else
@@ -112,8 +265,8 @@ createAccountKey() {
 #domain length
 createDomainKey() {
   _info "Creating domain key"
-  if [ -z "$1" ] ; then
-    echo Usage: createDomainKey domain  [2048]
+  if [[ -z "$1" ]] ; then
+    echo Usage: le.sh --createDomainKey -d domain.com  [ --keylength 2048 ]
     return
   fi
   
@@ -126,8 +279,8 @@ createDomainKey() {
     eccname="$length"
   fi
 
-  if [ -z "$length" ] ; then
-    if [ "$isec" ] ; then
+  if [[ -z "$length" ]] ; then
+    if [[ "$isec" ]] ; then
       length=256
     else
       length=2048
@@ -135,14 +288,14 @@ createDomainKey() {
   fi
   _info "Use length $length"
 
-  if [ "$isec" ] ; then
-    if [ "$length" == "256" ] ; then
+  if [[ "$isec" ]] ; then
+    if [[ "$length" == "256" ]] ; then
       eccname="prime256v1"
     fi
-    if [ "$length" == "384" ] ; then
+    if [[ "$length" == "384" ]] ; then
       eccname="secp384r1"
     fi
-    if [ "$length" == "521" ] ; then
+    if [[ "$length" == "521" ]] ; then
       eccname="secp521r1"
     fi
     _info "Using ec name: $eccname"
@@ -150,15 +303,15 @@ createDomainKey() {
   
   _initpath $domain
   
-  if [ ! -f "$CERT_KEY_PATH" ] || ( [ "$FORCE" ] && ! [ "$IS_RENEW" ] ); then 
+  if [[ ! -f "$CERT_KEY_PATH" ]] || ( [[ "$FORCE" ]] && ! [[ "$IS_RENEW" ]] ); then 
     #generate account key
-    if [ "$isec" ] ; then
+    if [[ "$isec" ]] ; then
       openssl ecparam  -name $eccname -genkey 2>/dev/null > "$CERT_KEY_PATH"
     else
       openssl genrsa $length 2>/dev/null > "$CERT_KEY_PATH"
     fi
   else
-    if [ "$IS_RENEW" ] ; then
+    if [[ "$IS_RENEW" ]] ; then
       _info "Domain key exists, skip"
       return 0
     else
@@ -173,8 +326,8 @@ createDomainKey() {
 # domain  domainlist
 createCSR() {
   _info "Creating csr"
-  if [ -z "$1" ] ; then
-    echo Usage: $0 domain  [domainlist]
+  if [[ -z "$1" ]] ; then
+    echo Usage: le.sh --createCSR -d domain1.com [-d domain2.com  -d domain3.com ... ]
     return
   fi
   domain=$1
@@ -182,12 +335,12 @@ createCSR() {
   
   domainlist=$2
   
-  if [ -f "$CSR_PATH" ]  && [ "$IS_RENEW" ] && ! [ "$FORCE" ]; then
+  if [[ -f "$CSR_PATH" ]]  && [[ "$IS_RENEW" ]] && [[ -z "$FORCE" ]]; then
     _info "CSR exists, skip"
     return
   fi
   
-  if [ -z "$domainlist" ] ; then
+  if [[ -z "$domainlist" ]] || [[ "$domainlist" == "no" ]]; then
     #single domain
     _info "Single domain" $domain
     printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\n" > "$DOMAIN_SSL_CONF"
@@ -235,7 +388,7 @@ _stat() {
 #keyfile
 _calcjwk() {
   keyfile="$1"
-  if [ -z "$keyfile" ] ; then
+  if [[ -z "$keyfile" ]] ; then
     _err "Usage: _calcjwk keyfile"
     return 1
   fi
@@ -243,18 +396,18 @@ _calcjwk() {
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" > /dev/null 2>&1 ; then
     _debug "RSA key"
     pub_exp=$(openssl rsa -in $keyfile  -noout -text | grep "^publicExponent:"| cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1)
-    if [ "${#pub_exp}" == "5" ] ; then
+    if [[ "${#pub_exp}" == "5" ]] ; then
       pub_exp=0$pub_exp
     fi
-    _debug pub_exp "$pub_exp"
+    _debug2 pub_exp "$pub_exp"
     
     e=$(echo $pub_exp | _h2b | _base64)
-    _debug e "$e"
+    _debug2 e "$e"
     
     modulus=$(openssl rsa -in $keyfile -modulus -noout | cut -d '=' -f 2 )
     n=$(echo $modulus| _h2b | _base64 | _urlencode )
     jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}'
-    _debug jwk "$jwk"
+    _debug2 jwk "$jwk"
     
     HEADER='{"alg": "RS256", "jwk": '$jwk'}'
     HEADERPLACE='{"nonce": "NONCE", "alg": "RS256", "jwk": '$jwk'}'
@@ -262,39 +415,39 @@ _calcjwk() {
     _debug "EC key"
     EC_SIGN="1"
     crv="$(openssl ec  -in $keyfile  -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
-    _debug crv $crv
+    _debug2 crv $crv
     
     pubi="$(openssl ec  -in $keyfile  -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)"
-    _debug pubi $pubi
+    _debug2 pubi $pubi
     let "pubi=pubi+1"
     
     pubj="$(openssl ec  -in $keyfile  -noout -text 2>/dev/null | grep -n "ASN1 OID:"  | cut -d : -f 1)"
-    _debug pubj $pubj
+    _debug2 pubj $pubj
     let "pubj=pubj-1"
     
     pubtext="$(openssl ec  -in $keyfile  -noout -text 2>/dev/null | sed  -n "$pubi,${pubj}p" | tr -d " \n\r")"
-    _debug pubtext "$pubtext"
+    _debug2 pubtext "$pubtext"
     
     xlen="$(printf "$pubtext" | tr -d ':' | wc -c)"
     let "xlen=xlen/4"
-    _debug xlen $xlen
+    _debug2 xlen $xlen
     
     let "xend=xlen+1"
     x="$(printf $pubtext | cut -d : -f 2-$xend)"
-    _debug x $x
+    _debug2 x $x
     
     x64="$(printf $x | tr -d : | _h2b | _base64 | _urlencode)"
-    _debug x64 $x64
+    _debug2 x64 $x64
     
     let "xend+=1"
     y="$(printf $pubtext | cut -d : -f $xend-10000)"
-    _debug y $y
+    _debug2 y $y
     
     y64="$(printf $y | tr -d : | _h2b | _base64 | _urlencode)"
-    _debug y64 $y64
+    _debug2 y64 $y64
    
     jwk='{"kty": "EC", "crv": "'$crv'", "x": "'$x64'", "y": "'$y64'"}'
-    _debug jwk "$jwk"
+    _debug2 jwk "$jwk"
     
     HEADER='{"alg": "ES256", "jwk": '$jwk'}'
     HEADERPLACE='{"nonce": "NONCE", "alg": "ES256", "jwk": '$jwk'}'
@@ -304,7 +457,54 @@ _calcjwk() {
     return 1
   fi
 
-  _debug HEADER "$HEADER"
+  _debug2 HEADER "$HEADER"
+}
+# body  url [needbase64]
+_post() {
+  body="$1"
+  url="$2"
+  needbase64="$3"
+
+  if _exists "curl" ; then
+    CURL="$CURL --dump-header $HTTP_HEADER "
+    if [[ "$needbase64" ]] ; then
+      response="$($CURL -A "User-Agent: $USER_AGENT" -X POST --data "$body" $url | _base64)"
+    else
+      response="$($CURL -A "User-Agent: $USER_AGENT" -X POST --data "$body" $url)"
+    fi
+  else
+    if [[ "$needbase64" ]] ; then
+      response="$($WGET -S -O - --user-agent="$USER_AGENT" --post-data="$body" $url 2>"$HTTP_HEADER" | _base64)"
+    else
+      response="$($WGET -S -O - --user-agent="$USER_AGENT" --post-data="$body" $url 2>"$HTTP_HEADER")"
+    fi
+    _sed_i "s/^ *//g" "$HTTP_HEADER"
+  fi
+  echo -n "$response"
+  
+}
+
+# url getheader
+_get() {
+  url="$1"
+  onlyheader="$2"
+  _debug url $url
+  if _exists "curl" ; then
+    if [[ "$onlyheader" ]] ; then
+      $CURL -I -A "User-Agent: $USER_AGENT" $url
+    else
+      $CURL -A "User-Agent: $USER_AGENT" $url
+    fi
+  else
+    _debug "WGET" "$WGET"
+    if [[ "$onlyheader" ]] ; then
+      eval $WGET --user-agent=\"$USER_AGENT\" -S -O /dev/null $url 2>&1 | sed 's/^[ ]*//g'
+    else
+      eval $WGET --user-agent=\"$USER_AGENT\" -O - $url
+    fi
+  fi
+  ret=$?
+  return $ret
 }
 
 # url  payload needbase64  keyfile
@@ -313,7 +513,7 @@ _send_signed_request() {
   payload=$2
   needbase64=$3
   keyfile=$4
-  if [ -z "$keyfile" ] ; then
+  if [[ -z "$keyfile" ]] ; then
     keyfile="$ACCOUNT_KEY_PATH"
   fi
   _debug url $url
@@ -322,58 +522,39 @@ _send_signed_request() {
   if ! _calcjwk "$keyfile" ; then
     return 1
   fi
-  
-  CURL_HEADER="$LE_WORKING_DIR/curl.header"
-  dp="$LE_WORKING_DIR/curl.dump"
-  CURL="curl --silent --dump-header $CURL_HEADER "
-  if [ "$DEBUG" ] ; then
-    CURL="$CURL --trace-ascii $dp "
-  fi
+
   payload64=$(echo -n $payload | _base64 | _urlencode)
-  _debug payload64 $payload64
+  _debug2 payload64 $payload64
   
   nonceurl="$API/directory"
-  nonce="$($CURL -I $nonceurl | grep -o "^Replay-Nonce:.*$" | tr -d "\r\n" | cut -d ' ' -f 2)"
+  nonce="$(_get $nonceurl "onlyheader" | grep -o "Replay-Nonce:.*$" | head -1 | tr -d "\r\n" | cut -d ' ' -f 2)"
 
   _debug nonce "$nonce"
   
   protected="$(printf "$HEADERPLACE" | sed "s/NONCE/$nonce/" )"
-  _debug protected "$protected"
+  _debug2 protected "$protected"
   
   protected64="$(printf "$protected" | _base64 | _urlencode)"
-  _debug protected64 "$protected64"
+  _debug2 protected64 "$protected64"
 
-  sig=$(echo -n "$protected64.$payload64" |  openssl   dgst   -sha256  -sign  "$keyfile" | _base64 | _urlencode)
-  _debug sig "$sig"
+  sig=$(echo -n "$protected64.$payload64" |  _sign  "$keyfile" "sha256" | _urlencode)
+  _debug2 sig "$sig"
   
   body="{\"header\": $HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}"
-  _debug body "$body"
+  _debug2 body "$body"
   
-  if [ "$needbase64" ] ; then
-    response="$($CURL -X POST --data "$body" $url | _base64)"
-  else
-    response="$($CURL -X POST --data "$body" $url)"
-  fi
 
-  responseHeaders="$(cat $CURL_HEADER)"
+  response="$(_post "$body" $url "$needbase64" )"
+
+  responseHeaders="$(cat $HTTP_HEADER)"
   
-  _debug responseHeaders "$responseHeaders"
-  _debug response  "$response"
-  code="$(grep ^HTTP $CURL_HEADER | tail -1 | cut -d " " -f 2 | tr -d "\r\n" )"
+  _debug2 responseHeaders "$responseHeaders"
+  _debug2 response  "$response"
+  code="$(grep "^HTTP" $HTTP_HEADER | tail -1 | cut -d " " -f 2 | tr -d "\r\n" )"
   _debug code $code
 
 }
 
-_get() {
-  url="$1"
-  _debug url $url
-  response="$(curl --silent $url)"
-  ret=$?
-  _debug response  "$response"
-  code="$(echo $response | grep -o '"status":[0-9]\+' | cut -d : -f 2)"
-  _debug code $code
-  return $ret
-}
 
 #setopt "file"  "opt"  "="  "value" [";"]
 _setopt() {
@@ -382,16 +563,16 @@ _setopt() {
   __sep="$3"
   __val="$4"
   __end="$5"
-  if [ -z "$__opt" ] ; then 
+  if [[ -z "$__opt" ]] ; then 
     echo usage: _setopt  '"file"  "opt"  "="  "value" [";"]'
     return
   fi
-  if [ ! -f "$__conf" ] ; then
+  if [[ ! -f "$__conf" ]] ; then
     touch "$__conf"
   fi
 
   if grep -H -n "^$__opt$__sep" "$__conf" > /dev/null ; then
-    _debug OK
+    _debug2 OK
     if [[ "$__val" == *"&"* ]] ; then
       __val="$(echo $__val | sed 's/&/\\&/g')"
     fi
@@ -406,7 +587,7 @@ _setopt() {
     echo "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" > "$__conf"
 
   else
-    _debug APP
+    _debug2 APP
     echo "$__opt$__sep$__val$__end" >> "$__conf"
   fi
   _debug "$(grep -H -n "^$__opt$__sep" $__conf)"
@@ -417,7 +598,7 @@ _setopt() {
 _savedomainconf() {
   key="$1"
   value="$2"
-  if [ "$DOMAIN_CONF" ] ; then
+  if [[ "$DOMAIN_CONF" ]] ; then
     _setopt $DOMAIN_CONF "$key" "=" "$value"
   else
     _err "DOMAIN_CONF is empty, can not save $key=$value"
@@ -428,8 +609,8 @@ _savedomainconf() {
 _saveaccountconf() {
   key="$1"
   value="$2"
-  if [ "$ACCOUNT_CONF_PATH" ] ; then
-    _setopt $ACCOUNT_CONF_PATH "$key" "=" "$value"
+  if [[ "$ACCOUNT_CONF_PATH" ]] ; then
+    _setopt $ACCOUNT_CONF_PATH "$key" "=" "\"$value\""
   else
     _err "ACCOUNT_CONF_PATH is empty, can not save $key=$value"
   fi
@@ -437,14 +618,16 @@ _saveaccountconf() {
 
 _startserver() {
   content="$1"
-
+  _debug "startserver: $$"
   nchelp="$(nc -h 2>&1)"
   
-  if echo "$nchelp" | grep "\-q " >/dev/null ; then
+  if echo "$nchelp" | grep "\-q[ ,]" >/dev/null ; then
     _NC="nc -q 1 -l"
   else
     if echo "$nchelp" | grep "GNU netcat" >/dev/null && echo "$nchelp" | grep "\-c, \-\-close" >/dev/null ; then
       _NC="nc -c -l"
+    elif echo "$nchelp" | grep "\-N" |grep "Shutdown the network socket after EOF on stdin"  >/dev/null ; then
+      _NC="nc -N -l"
     else
       _NC="nc -l"
     fi
@@ -452,7 +635,7 @@ _startserver() {
 
   _debug "_NC" "$_NC"
 #  while true ; do
-    if [ "$DEBUG" ] ; then
+    if [[ "$DEBUG" ]] ; then
       if ! echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC -p $Le_HTTPPort -vv ; then
         echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC $Le_HTTPPort -vv ;
       fi
@@ -461,34 +644,58 @@ _startserver() {
         echo -e -n "HTTP/1.1 200 OK\r\n\r\n$content" | $_NC $Le_HTTPPort > /dev/null 2>&1
       fi      
     fi
-    if [ "$?" != "0" ] ; then
+    if [[ "$?" != "0" ]] ; then
       _err "nc listen error."
-      return 1
+      exit 1
     fi
 #  done
 }
 
-_stopserver() {
+_stopserver(){
   pid="$1"
+  _debug "pid" "$pid"
+  if [[ -z "$pid" ]] ; then
+    return
+  fi
+  
+  if [[ "$(ps | grep "$pid" | grep "nc")" ]] ; then
+    _debug "Found nc process, kill it."
+    kill -s 9 $pid > /dev/null 2>&1
+  fi
+  
+  _get "http://localhost:$Le_HTTPPort" >/dev/null 2>$1
 
 }
 
 _initpath() {
 
-  if [ -z "$LE_WORKING_DIR" ]; then
+  if [[ -z "$LE_WORKING_DIR" ]] ; then
     LE_WORKING_DIR=$HOME/.le
   fi
   
-  if [ -z "$ACCOUNT_CONF_PATH" ] ; then
-    ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf"
+  _DEFAULT_ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf"
+
+  if [[ -f "$_DEFAULT_ACCOUNT_CONF_PATH" ]] ; then
+    source "$_DEFAULT_ACCOUNT_CONF_PATH"
+  fi
+  
+  if [[ -z "$ACCOUNT_CONF_PATH" ]] ; then
+    ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH"
   fi
   
-  if [ -f "$ACCOUNT_CONF_PATH" ] ; then
+  if [[ -f "$ACCOUNT_CONF_PATH" ]] ; then
     source "$ACCOUNT_CONF_PATH"
   fi
 
-  if [ -z "$API" ] ; then
-    if [ -z "$STAGE" ] ; then
+  if [[ "$IN_CRON" ]] ; then
+    if [[ ! "$_USER_PATH_EXPORTED" ]] ; then
+      _USER_PATH_EXPORTED=1
+      export PATH="$USER_PATH:$PATH"
+    fi
+  fi
+
+  if [[ -z "$API" ]] ; then
+    if [[ -z "$STAGE" ]] ; then
       API="$DEFAULT_CA"
     else
       API="$STAGE_CA"
@@ -496,66 +703,87 @@ _initpath() {
     fi  
   fi
   
-  if [ -z "$ACME_DIR" ] ; then
+  if [[ -z "$ACME_DIR" ]] ; then
     ACME_DIR="/home/.acme"
   fi
   
-  if [ -z "$APACHE_CONF_BACKUP_DIR" ] ; then
-    APACHE_CONF_BACKUP_DIR="$LE_WORKING_DIR/"
+  if [[ -z "$APACHE_CONF_BACKUP_DIR" ]] ; then
+    APACHE_CONF_BACKUP_DIR="$LE_WORKING_DIR"
   fi
   
-  domain="$1"
-  if ! mkdir -p "$LE_WORKING_DIR" ; then
-    _err "Can not craete working dir: $LE_WORKING_DIR"
-    return 1
+  if [[ -z "$USER_AGENT" ]] ; then
+    USER_AGENT="$DEFAULT_USER_AGENT"
+  fi
+  
+  HTTP_HEADER="$LE_WORKING_DIR/http.header"
+  
+  WGET="wget -q"
+  if [[ "$DEBUG" -ge "2" ]] ; then
+    WGET="$WGET -d "
+  fi
+
+  dp="$LE_WORKING_DIR/curl.dump"
+  CURL="curl -L --silent"
+  if [[ "$DEBUG" -ge "2" ]] ; then
+    CURL="$CURL -L --trace-ascii $dp "
   fi
   
-  if [ -z "$ACCOUNT_KEY_PATH" ] ; then
+  domain="$1"
+  
+  if [[ -z "$ACCOUNT_KEY_PATH" ]] ; then
     ACCOUNT_KEY_PATH="$LE_WORKING_DIR/account.key"
   fi
 
-  if [ -z "$domain" ] ; then
+  if [[ -z "$domain" ]] ; then
     return 0
   fi
   
   domainhome="$LE_WORKING_DIR/$domain"
   mkdir -p "$domainhome"
 
-  if [ -z "$DOMAIN_PATH" ] ; then
+  if [[ -z "$DOMAIN_PATH" ]] ; then
     DOMAIN_PATH="$domainhome"
   fi
-  if [ -z "$DOMAIN_CONF" ] ; then
+  if [[ -z "$DOMAIN_CONF" ]] ; then
     DOMAIN_CONF="$domainhome/$domain.conf"
   fi
   
-  if [ -z "$DOMAIN_SSL_CONF" ] ; then
+  if [[ -z "$DOMAIN_SSL_CONF" ]] ; then
     DOMAIN_SSL_CONF="$domainhome/$domain.ssl.conf"
   fi
   
-  if [ -z "$CSR_PATH" ] ; then
+  if [[ -z "$CSR_PATH" ]] ; then
     CSR_PATH="$domainhome/$domain.csr"
   fi
-  if [ -z "$CERT_KEY_PATH" ] ; then 
+  if [[ -z "$CERT_KEY_PATH" ]] ; then 
     CERT_KEY_PATH="$domainhome/$domain.key"
   fi
-  if [ -z "$CERT_PATH" ] ; then
+  if [[ -z "$CERT_PATH" ]] ; then
     CERT_PATH="$domainhome/$domain.cer"
   fi
-  if [ -z "$CA_CERT_PATH" ] ; then
+  if [[ -z "$CA_CERT_PATH" ]] ; then
     CA_CERT_PATH="$domainhome/ca.cer"
   fi
-  if [ -z "$CERT_FULLCHAIN_PATH" ] ; then
+  if [[ -z "$CERT_FULLCHAIN_PATH" ]] ; then
     CERT_FULLCHAIN_PATH="$domainhome/fullchain.cer"
   fi
-
+  if [[ -z "$CERT_PFX_PATH" ]] ; then
+    CERT_PFX_PATH="$domainhome/$domain.pfx"
+  fi
 }
 
 
 _apachePath() {
-  httpdroot="$(apachectl -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"' )"
   httpdconfname="$(apachectl -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"' )"
-  httpdconf="$httpdroot/$httpdconfname"
-  if [ ! -f $httpdconf ] ; then
+  if [[ "$httpdconfname" == '/'* ]] ; then
+    httpdconf="$httpdconfname"
+    httpdconfname="$(basename $httpdconfname)"
+  else
+    httpdroot="$(apachectl -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"' )"
+    httpdconf="$httpdroot/$httpdconfname"
+  fi
+
+  if [[ ! -f $httpdconf ]] ; then
     _err "Apache Config file not found" $httpdconf
     return 1
   fi
@@ -563,7 +791,7 @@ _apachePath() {
 }
 
 _restoreApache() {
-  if [ -z "$usingApache" ] ; then
+  if [[ -z "$usingApache" ]] ; then
     return 0
   fi
   _initpath
@@ -571,7 +799,7 @@ _restoreApache() {
     return 1
   fi
   
-  if [ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ] ; then
+  if [[ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ]] ; then
     _debug "No config file to restore."
     return 0
   fi
@@ -599,13 +827,31 @@ _setApache() {
   _info "The backup file will be deleted on sucess, just forget it."
   
   #add alias
-  echo "
+  
+  apacheVer="$(apachectl -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2 )"
+  _debug "apacheVer" "$apacheVer"
+  apacheMajer="$(echo "$apacheVer" | cut -d . -f 1)"
+  apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)"
+
+  if [[ "$apacheVer" ]] && [[ "$apacheMajer" -ge "2" ]] && [[ "$apacheMinor" -ge "4" ]] ; then
+    echo "
 Alias /.well-known/acme-challenge  $ACME_DIR
 
 <Directory $ACME_DIR >
 Require all granted
+</Directory>
+  " >> $httpdconf  
+  else
+    echo "
+Alias /.well-known/acme-challenge  $ACME_DIR
+
+<Directory $ACME_DIR >
+Order allow,deny
+Allow from all
 </Directory>
   " >> $httpdconf
+  fi
+
   
   if ! apachectl  -t ; then
     _err "Sorry, apache config error, please contact me."
@@ -613,7 +859,7 @@ Require all granted
     return 1;
   fi
   
-  if [ ! -d "$ACME_DIR" ] ; then
+  if [[ ! -d "$ACME_DIR" ]] ; then
     mkdir -p "$ACME_DIR"
     chmod 755 "$ACME_DIR"
   fi
@@ -636,18 +882,18 @@ _clearup () {
 # webroot  removelevel tokenfile
 _clearupwebbroot() {
   __webroot="$1"
-  if [ -z "$__webroot" ] ; then
+  if [[ -z "$__webroot" ]] ; then
     _debug "no webroot specified, skip"
     return 0
   fi
   
-  if [ "$2" == '1' ] ; then
+  if [[ "$2" == '1' ]] ; then
     _debug "remove $__webroot/.well-known"
     rm -rf "$__webroot/.well-known"
-  elif [ "$2" == '2' ] ; then
+  elif [[ "$2" == '2' ]] ; then
     _debug "remove $__webroot/.well-known/acme-challenge"
     rm -rf "$__webroot/.well-known/acme-challenge"
-  elif [ "$2" == '3' ] ; then
+  elif [[ "$2" == '3' ]] ; then
     _debug "remove $__webroot/.well-known/acme-challenge/$3"
     rm -rf "$__webroot/.well-known/acme-challenge/$3"
   else
@@ -659,8 +905,8 @@ _clearupwebbroot() {
 }
 
 issue() {
-  if [ -z "$2" ] ; then
-    _err "Usage: le  issue  webroot|no|apache|dns   a.com  [www.a.com,b.com,c.com]|no   [key-length]|no"
+  if [[ -z "$2" ]] ; then
+    echo "Usage: le  --issue  -d  a.com  -w /path/to/webroot/a.com/ "
     return 1
   fi
   Le_Webroot="$1"
@@ -671,60 +917,65 @@ issue() {
   Le_RealKeyPath="$6"
   Le_RealCACertPath="$7"
   Le_ReloadCmd="$8"
-
+  Le_RealFullChainPath="$9"
   
   _initpath $Le_Domain
   
-  if [ -f "$DOMAIN_CONF" ] ; then
+  if [[ -f "$DOMAIN_CONF" ]] ; then
     Le_NextRenewTime=$(grep "^Le_NextRenewTime=" "$DOMAIN_CONF" | cut -d '=' -f 2)
-    if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then 
+    if [[ -z "$FORCE" ]] && [[ "$Le_NextRenewTime" ]] && [[ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ]] ; then 
       _info "Skip, Next renewal time is: $(grep "^Le_NextRenewTimeStr" "$DOMAIN_CONF" | cut -d '=' -f 2)"
       return 2
     fi
   fi
+
+  _setopt "$DOMAIN_CONF"  "Le_Domain"             "="  "$Le_Domain"
+  _setopt "$DOMAIN_CONF"  "Le_Alt"                "="  "$Le_Alt"
+  _setopt "$DOMAIN_CONF"  "Le_Webroot"            "="  "$Le_Webroot"
+  _setopt "$DOMAIN_CONF"  "Le_Keylength"          "="  "$Le_Keylength"
+  _setopt "$DOMAIN_CONF"  "Le_RealCertPath"       "="  "\"$Le_RealCertPath\""
+  _setopt "$DOMAIN_CONF"  "Le_RealCACertPath"     "="  "\"$Le_RealCACertPath\""
+  _setopt "$DOMAIN_CONF"  "Le_RealKeyPath"        "="  "\"$Le_RealKeyPath\""
+  _setopt "$DOMAIN_CONF"  "Le_ReloadCmd"          "="  "\"$Le_ReloadCmd\""
+  _setopt "$DOMAIN_CONF"  "Le_RealFullChainPath"  "="  "\"$Le_RealFullChainPath\""
   
-  if [ "$Le_Alt" == "no" ] ; then
+  if [[ "$Le_Alt" == "no" ]] ; then
     Le_Alt=""
   fi
-  if [ "$Le_Keylength" == "no" ] ; then
+  if [[ "$Le_Keylength" == "no" ]] ; then
     Le_Keylength=""
   fi
-  if [ "$Le_RealCertPath" == "no" ] ; then
+  if [[ "$Le_RealCertPath" == "no" ]] ; then
     Le_RealCertPath=""
   fi
-  if [ "$Le_RealKeyPath" == "no" ] ; then
+  if [[ "$Le_RealKeyPath" == "no" ]] ; then
     Le_RealKeyPath=""
   fi
-  if [ "$Le_RealCACertPath" == "no" ] ; then
+  if [[ "$Le_RealCACertPath" == "no" ]] ; then
     Le_RealCACertPath=""
   fi
-  if [ "$Le_ReloadCmd" == "no" ] ; then
+  if [[ "$Le_ReloadCmd" == "no" ]] ; then
     Le_ReloadCmd=""
   fi
+  if [[ "$Le_RealFullChainPath" == "no" ]] ; then
+    Le_RealFullChainPath=""
+  fi
+
   
-  _setopt "$DOMAIN_CONF"  "Le_Domain"             "="  "$Le_Domain"
-  _setopt "$DOMAIN_CONF"  "Le_Alt"                "="  "$Le_Alt"
-  _setopt "$DOMAIN_CONF"  "Le_Webroot"            "="  "$Le_Webroot"
-  _setopt "$DOMAIN_CONF"  "Le_Keylength"          "="  "$Le_Keylength"
-  _setopt "$DOMAIN_CONF"  "Le_RealCertPath"       "="  "\"$Le_RealCertPath\""
-  _setopt "$DOMAIN_CONF"  "Le_RealCACertPath"     "="  "\"$Le_RealCACertPath\""
-  _setopt "$DOMAIN_CONF"  "Le_RealKeyPath"        "="  "\"$Le_RealKeyPath\""
-  _setopt "$DOMAIN_CONF"  "Le_ReloadCmd"          "="  "\"$Le_ReloadCmd\""
-  
-  if [ "$Le_Webroot" == "no" ] ; then
+  if [[ "$Le_Webroot" == *"no"* ]] ; then
     _info "Standalone mode."
     if ! command -v "nc" > /dev/null ; then
       _err "Please install netcat(nc) tools first."
       return 1
     fi
     
-    if [ -z "$Le_HTTPPort" ] ; then
+    if [[ -z "$Le_HTTPPort" ]] ; then
       Le_HTTPPort=80
     fi
     _setopt "$DOMAIN_CONF"  "Le_HTTPPort"             "="  "$Le_HTTPPort"
     
     netprc="$(_ss "$Le_HTTPPort" | grep "$Le_HTTPPort")"
-    if [ "$netprc" ] ; then
+    if [[ "$netprc" ]] ; then
       _err "$netprc"
       _err "tcp port $Le_HTTPPort is already used by $(echo "$netprc" | cut -d :  -f 4)"
       _err "Please stop it first"
@@ -732,7 +983,7 @@ issue() {
     fi
   fi
   
-  if [ "$Le_Webroot" == "apache" ] ; then
+  if [[ "$Le_Webroot" == *"apache"* ]] ; then
     if ! _setApache ; then
       _err "set up apache error. Report error to me."
       return 1
@@ -749,22 +1000,22 @@ issue() {
   fi
   
   accountkey_json=$(echo -n "$jwk" |  tr -d ' ' )
-  thumbprint=$(echo -n "$accountkey_json" | openssl dgst -sha256 -binary | _base64 | _urlencode)
+  thumbprint=$(echo -n "$accountkey_json" | _digest "sha256" | _urlencode)
   
-  accountkeyhash="$(cat "$ACCOUNT_KEY_PATH" | openssl dgst -sha256 -binary | _base64)"
-
-  if [ "$accountkeyhash" != "$ACCOUNT_KEY_HASH" ] ; then
+  accountkeyhash="$(cat "$ACCOUNT_KEY_PATH" | _digest "sha256" )"
+  accountkeyhash="$(echo $accountkeyhash$API | _digest "sha256" )"
+  if [[ "$accountkeyhash" != "$ACCOUNT_KEY_HASH" ]] ; then
     _info "Registering account"
     regjson='{"resource": "new-reg", "agreement": "'$AGREEMENT'"}'
-    if [ "$ACCOUNT_EMAIL" ] ; then
+    if [[ "$ACCOUNT_EMAIL" ]] ; then
       regjson='{"resource": "new-reg", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "agreement": "'$AGREEMENT'"}'
     fi  
     _send_signed_request   "$API/acme/new-reg"  "$regjson"
     
-    if [ "$code" == "" ] || [ "$code" == '201' ] ; then
+    if [[ "$code" == "" ]] || [[ "$code" == '201' ]] ; then
       _info "Registered"
       echo $response > $LE_WORKING_DIR/account.json
-    elif [ "$code" == '409' ] ; then
+    elif [[ "$code" == '409' ]] ; then
       _info "Already registered"
     else
       _err "Register account Error: $response"
@@ -786,23 +1037,33 @@ issue() {
     _err "Create CSR error."
     return 1
   fi
-  
-  vtype="$VTYPE_HTTP"
-  if [[ "$Le_Webroot" == "dns"* ]] ; then
-    vtype="$VTYPE_DNS"
-  fi
-  
+
   vlist="$Le_Vlist"
   # verify each domain
   _info "Verify each domain"
   sep='#'
-  if [ -z "$vlist" ] ; then
+  if [[ -z "$vlist" ]] ; then
     alldomains=$(echo "$Le_Domain,$Le_Alt" |  tr ',' ' ' )
+    _index=1
+    _currentRoot=""
     for d in $alldomains   
-    do  
+    do
+      _info "Getting webroot for domain" $d
+      _w="$(echo $Le_Webroot | cut -d , -f $_index)"
+      _debug _w "$_w"
+      if [[ "$_w" ]] ; then
+        _currentRoot="$_w"
+      fi
+      _debug "_currentRoot" "$_currentRoot"
+      let "_index+=1"
+      
+      vtype="$VTYPE_HTTP"
+      if [[ "$_currentRoot" == "dns"* ]] ; then
+        vtype="$VTYPE_DNS"
+      fi
       _info "Getting token for domain" $d
       _send_signed_request "$API/acme/new-authz" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$d\"}}"
-      if [ ! -z "$code" ] && [ ! "$code" == '201' ] ; then
+      if [[ ! -z "$code" ]] && [[ ! "$code" == '201' ]] ; then
         _err "new-authz error: $response"
         _clearup
         return 1
@@ -820,7 +1081,7 @@ issue() {
       keyauthorization="$token.$thumbprint"
       _debug keyauthorization "$keyauthorization"
 
-      dvlist="$d$sep$keyauthorization$sep$uri"
+      dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot"
       _debug dvlist "$dvlist"
       
       vlist="$vlist$dvlist,"
@@ -834,32 +1095,33 @@ issue() {
     do
       d=$(echo $ventry | cut -d $sep -f 1)
       keyauthorization=$(echo $ventry | cut -d $sep -f 2)
-
-      if [ "$vtype" == "$VTYPE_DNS" ] ; then
+      vtype=$(echo $ventry | cut -d $sep -f 4)
+      _currentRoot=$(echo $ventry | cut -d $sep -f 5)
+      if [[ "$vtype" == "$VTYPE_DNS" ]] ; then
         dnsadded='0'
         txtdomain="_acme-challenge.$d"
         _debug txtdomain "$txtdomain"
-        txt="$(echo -e -n $keyauthorization | openssl dgst -sha256 -binary | _base64 | _urlencode)"
+        txt="$(echo -e -n $keyauthorization | _digest "sha256" | _urlencode)"
         _debug txt "$txt"
         #dns
         #1. check use api
         d_api=""
-        if [ -f "$LE_WORKING_DIR/$d/$Le_Webroot" ] ; then
-          d_api="$LE_WORKING_DIR/$d/$Le_Webroot"
-        elif [ -f "$LE_WORKING_DIR/$d/$Le_Webroot.sh" ] ; then
-          d_api="$LE_WORKING_DIR/$d/$Le_Webroot.sh"
-        elif [ -f "$LE_WORKING_DIR/$Le_Webroot" ] ; then
-          d_api="$LE_WORKING_DIR/$Le_Webroot"
-        elif [ -f "$LE_WORKING_DIR/$Le_Webroot.sh" ] ; then
-          d_api="$LE_WORKING_DIR/$Le_Webroot.sh"
-        elif [ -f "$LE_WORKING_DIR/dnsapi/$Le_Webroot" ] ; then
-          d_api="$LE_WORKING_DIR/dnsapi/$Le_Webroot"
-        elif [ -f "$LE_WORKING_DIR/dnsapi/$Le_Webroot.sh" ] ; then
-          d_api="$LE_WORKING_DIR/dnsapi/$Le_Webroot.sh"
+        if [[ -f "$LE_WORKING_DIR/$d/$_currentRoot" ]] ; then
+          d_api="$LE_WORKING_DIR/$d/$_currentRoot"
+        elif [[ -f "$LE_WORKING_DIR/$d/$_currentRoot.sh" ]] ; then
+          d_api="$LE_WORKING_DIR/$d/$_currentRoot.sh"
+        elif [[ -f "$LE_WORKING_DIR/$_currentRoot" ]] ; then
+          d_api="$LE_WORKING_DIR/$_currentRoot"
+        elif [[ -f "$LE_WORKING_DIR/$_currentRoot.sh" ]] ; then
+          d_api="$LE_WORKING_DIR/$_currentRoot.sh"
+        elif [[ -f "$LE_WORKING_DIR/dnsapi/$_currentRoot" ]] ; then
+          d_api="$LE_WORKING_DIR/dnsapi/$_currentRoot"
+        elif [[ -f "$LE_WORKING_DIR/dnsapi/$_currentRoot.sh" ]] ; then
+          d_api="$LE_WORKING_DIR/dnsapi/$_currentRoot.sh"
         fi
         _debug d_api "$d_api"
         
-        if [ "$d_api" ]; then
+        if [[ "$d_api" ]] ; then
           _info "Found domain api file: $d_api"
         else
           _err "Add the following TXT record:"
@@ -869,27 +1131,33 @@ issue() {
           _err "so the resulting subdomain will be: $txtdomain"
           continue
         fi
-
-        if ! source $d_api ; then
-          _err "Load file $d_api error. Please check your api file and try again."
-          return 1
-        fi
         
-        addcommand="$Le_Webroot-add"
-        if ! command -v $addcommand ; then 
-          _err "It seems that your api file is not correct, it must have a function named: $addcommand"
-          return 1
-        fi
+        (
+          if ! source $d_api ; then
+            _err "Load file $d_api error. Please check your api file and try again."
+            return 1
+          fi
+          
+          addcommand="$_currentRoot-add"
+          if ! _exists $addcommand ; then 
+            _err "It seems that your api file is not correct, it must have a function named: $addcommand"
+            return 1
+          fi
+          
+          if ! $addcommand $txtdomain $txt ; then
+            _err "Error add txt for domain:$txtdomain"
+            return 1
+          fi
+        )
         
-        if ! $addcommand $txtdomain $txt ; then
-          _err "Error add txt for domain:$txtdomain"
+        if [[ "$?" != "0" ]] ; then
           return 1
         fi
         dnsadded='1'
       fi
     done
 
-    if [ "$dnsadded" == '0' ] ; then
+    if [[ "$dnsadded" == '0' ]] ; then
       _setopt "$DOMAIN_CONF"  "Le_Vlist" "=" "\"$vlist\""
       _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit."
       _err "Please add the TXT records to the domains, and retry again."
@@ -898,40 +1166,51 @@ issue() {
     
   fi
   
-  if [ "$dnsadded" == '1' ] ; then
+  if [[ "$dnsadded" == '1' ]] ; then
     _info "Sleep 60 seconds for the txt records to take effect"
     sleep 60
   fi
   
   _debug "ok, let's start to verify"
+
   ventries=$(echo "$vlist" |  tr ',' ' ' )
   for ventry in $ventries
   do
     d=$(echo $ventry | cut -d $sep -f 1)
     keyauthorization=$(echo $ventry | cut -d $sep -f 2)
     uri=$(echo $ventry | cut -d $sep -f 3)
+    vtype=$(echo $ventry | cut -d $sep -f 4)
+    _currentRoot=$(echo $ventry | cut -d $sep -f 5)
     _info "Verifying:$d"
     _debug "d" "$d"
     _debug "keyauthorization" "$keyauthorization"
     _debug "uri" "$uri"
     removelevel=""
     token=""
-    if [ "$vtype" == "$VTYPE_HTTP" ] ; then
-      if [ "$Le_Webroot" == "no" ] ; then
+
+    _debug "_currentRoot" "$_currentRoot"
+
+      
+    if [[ "$vtype" == "$VTYPE_HTTP" ]] ; then
+      if [[ "$_currentRoot" == "no" ]] ; then
         _info "Standalone mode server"
         _startserver "$keyauthorization" &
+        if [[ "$?" != "0" ]] ; then
+          return 1
+        fi
         serverproc="$!"
         sleep 2
         _debug serverproc $serverproc
+
       else
-        if [ -z "$wellknown_path" ] ; then
-          wellknown_path="$Le_Webroot/.well-known/acme-challenge"
+        if [[ -z "$wellknown_path" ]] ; then
+          wellknown_path="$_currentRoot/.well-known/acme-challenge"
         fi
         _debug wellknown_path "$wellknown_path"
         
-        if [ ! -d "$Le_Webroot/.well-known" ] ; then 
+        if [[ ! -d "$_currentRoot/.well-known" ]] ; then 
           removelevel='1'
-        elif [ ! -d "$Le_Webroot/.well-known/acme-challenge" ] ; then 
+        elif [[ ! -d "$_currentRoot/.well-known/acme-challenge" ]] ; then 
           removelevel='2'
         else
           removelevel='3'
@@ -942,57 +1221,71 @@ issue() {
 
         mkdir -p "$wellknown_path"
         echo -n "$keyauthorization" > "$wellknown_path/$token"
-
-        webroot_owner=$(_stat $Le_Webroot)
-        _debug "Changing owner/group of .well-known to $webroot_owner"
-        chown -R $webroot_owner "$Le_Webroot/.well-known"
+        if [[ ! "$usingApache" ]] ; then
+          webroot_owner=$(_stat $_currentRoot)
+          _debug "Changing owner/group of .well-known to $webroot_owner"
+          chown -R $webroot_owner "$_currentRoot/.well-known"
+        fi
         
       fi
     fi
     
     _send_signed_request $uri "{\"resource\": \"challenge\", \"keyAuthorization\": \"$keyauthorization\"}"
     
-    if [ ! -z "$code" ] && [ ! "$code" == '202' ] ; then
-      _err "$d:Challenge error: $resource"
-      _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+    if [[ ! -z "$code" ]] && [[ ! "$code" == '202' ]] ; then
+      _err "$d:Challenge error: $response"
+      _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
       _clearup
       return 1
     fi
     
-    while [ "1" ] ; do
+    waittimes=0
+    if [[ -z "$MAX_RETRY_TIMES" ]] ; then
+      MAX_RETRY_TIMES=30
+    fi
+    
+    while [[ "1" ]] ; do
+      let "waittimes+=1"
+      if [[ "$waittimes" -ge "$MAX_RETRY_TIMES" ]] ; then
+        _err "$d:Timeout"
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
+        _clearup
+        return 1
+      fi
+      
       _debug "sleep 5 secs to verify"
       sleep 5
       _debug "checking"
-      
-      if ! _get $uri ; then
-        _err "$d:Verify error:$resource"
-        _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+      response="$(_get $uri)"
+      if [[ "$?" != "0" ]] ; then
+        _err "$d:Verify error:$response"
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
         _clearup
         return 1
       fi
       
-      status=$(echo $response | egrep -o  '"status":"[^"]+"' | cut -d : -f 2 | tr -d '"')
-      if [ "$status" == "valid" ] ; then
+      status=$(echo $response | egrep -o  '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
+      if [[ "$status" == "valid" ]] ; then
         _info "Success"
         _stopserver $serverproc
         serverproc=""
-        _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
         break;
       fi
       
-      if [ "$status" == "invalid" ] ; then
+      if [[ "$status" == "invalid" ]] ; then
          error=$(echo $response | egrep -o '"error":{[^}]*}' | grep -o '"detail":"[^"]*"' | cut -d '"' -f 4)
         _err "$d:Verify error:$error"
-        _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
         _clearup
         return 1;
       fi
       
-      if [ "$status" == "pending" ] ; then
+      if [[ "$status" == "pending" ]] ; then
         _info "Pending"
       else
         _err "$d:Verify error:$response" 
-        _clearupwebbroot "$Le_Webroot" "$removelevel" "$token"
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
         _clearup
         return 1
       fi
@@ -1003,40 +1296,45 @@ issue() {
 
   _clearup
   _info "Verify finished, start to sign."
-  der="$(openssl req  -in $CSR_PATH -outform DER | _base64 | _urlencode)"
+  der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _urlencode)"
   _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64"
   
   
-  Le_LinkCert="$(grep -i -o '^Location.*$' $CURL_HEADER | tr -d "\r\n" | cut -d " " -f 2)"
+  Le_LinkCert="$(grep -i -o '^Location.*$' $HTTP_HEADER | head -1 | tr -d "\r\n" | cut -d " " -f 2)"
   _setopt "$DOMAIN_CONF"  "Le_LinkCert"           "="  "$Le_LinkCert"
 
-  if [ "$Le_LinkCert" ] ; then
-    echo -----BEGIN CERTIFICATE----- > "$CERT_PATH"
-    curl --silent "$Le_LinkCert" | openssl base64 -e  >> "$CERT_PATH"
-    echo -----END CERTIFICATE-----  >> "$CERT_PATH"
+  if [[ "$Le_LinkCert" ]] ; then
+    echo "$BEGIN_CERT" > "$CERT_PATH"
+    _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH"
+    echo "$END_CERT"  >> "$CERT_PATH"
     _info "Cert success."
     cat "$CERT_PATH"
     
     _info "Your cert is in $CERT_PATH"
     cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
+
+    if [[ ! "$USER_PATH" ]] || [[ ! "$IN_CRON" ]] ; then
+      USER_PATH="$PATH"
+      _saveaccountconf "USER_PATH" "$USER_PATH"
+    fi
   fi
   
 
-  if [ -z "$Le_LinkCert" ] ; then
-    response="$(echo $response | openssl base64 -d -A)"
+  if [[ -z "$Le_LinkCert" ]] ; then
+    response="$(echo $response | _dbase64 "multiline" )"
     _err "Sign failed: $(echo "$response" | grep -o  '"detail":"[^"]*"')"
     return 1
   fi
   
   _setopt "$DOMAIN_CONF"  'Le_Vlist' '=' "\"\""
   
-  Le_LinkIssuer=$(grep -i '^Link' $CURL_HEADER | cut -d " " -f 2| cut -d ';' -f 1 | tr -d '<>' )
+  Le_LinkIssuer=$(grep -i '^Link' $HTTP_HEADER | head -1 | cut -d " " -f 2| cut -d ';' -f 1 | tr -d '<>' )
   _setopt "$DOMAIN_CONF"  "Le_LinkIssuer"         "="  "$Le_LinkIssuer"
   
-  if [ "$Le_LinkIssuer" ] ; then
-    echo -----BEGIN CERTIFICATE----- > "$CA_CERT_PATH"
-    curl --silent "$Le_LinkIssuer" | openssl base64 -e  >> "$CA_CERT_PATH"
-    echo -----END CERTIFICATE-----  >> "$CA_CERT_PATH"
+  if [[ "$Le_LinkIssuer" ]] ; then
+    echo "$BEGIN_CERT" > "$CA_CERT_PATH"
+    _get "$Le_LinkIssuer" | _base64 "multiline"  >> "$CA_CERT_PATH"
+    echo "$END_CERT"  >> "$CA_CERT_PATH"
     _info "The intermediate CA cert is in $CA_CERT_PATH"
     cat "$CA_CERT_PATH" >> "$CERT_FULLCHAIN_PATH"
     _info "And the full chain certs is there: $CERT_FULLCHAIN_PATH"
@@ -1048,7 +1346,7 @@ issue() {
   Le_CertCreateTimeStr=$(date -u )
   _setopt "$DOMAIN_CONF"  "Le_CertCreateTimeStr"  "="  "\"$Le_CertCreateTimeStr\""
   
-  if [ ! "$Le_RenewalDays" ] ; then
+  if [[ ! "$Le_RenewalDays" ]] ; then
     Le_RenewalDays=80
   fi
   
@@ -1061,32 +1359,32 @@ issue() {
   _setopt "$DOMAIN_CONF"  "Le_NextRenewTimeStr"      "="  "\"$Le_NextRenewTimeStr\""
 
 
-  installcert $Le_Domain  "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd"
+  installcert $Le_Domain  "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath"
 
 }
 
 renew() {
   Le_Domain="$1"
-  if [ -z "$Le_Domain" ] ; then
-    _err "Usage: $0  domain.com"
+  if [[ -z "$Le_Domain" ]] ; then
+    _err "Usage: le.sh --renew  -d domain.com"
     return 1
   fi
 
   _initpath $Le_Domain
 
-  if [ ! -f "$DOMAIN_CONF" ] ; then
+  if [[ ! -f "$DOMAIN_CONF" ]] ; then
     _info "$Le_Domain is not a issued domain, skip."
     return 0;
   fi
   
   source "$DOMAIN_CONF"
-  if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ] ; then 
+  if [[ -z "$FORCE" ]] && [[ "$Le_NextRenewTime" ]] && [[ "$(date -u "+%s" )" -lt "$Le_NextRenewTime" ]] ; then 
     _info "Skip, Next renewal time is: $Le_NextRenewTimeStr"
     return 2
   fi
   
   IS_RENEW="1"
-  issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd"
+  issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath"
   local res=$?
   IS_RENEW=""
 
@@ -1097,13 +1395,13 @@ renewAll() {
   _initpath
   _info "renewAll"
   
-  for d in $(ls -F $LE_WORKING_DIR | grep [^.].*[.].*/$ ) ; do
+  for d in $(ls -F ${LE_WORKING_DIR}/ | grep [^.].*[.].*/$ ) ; do
     d=$(echo $d | cut -d '/' -f 1)
     _info "renew $d"
     
     Le_LinkCert=""
     Le_Domain=""
-    Le_Alt=""
+    Le_Alt="no"
     Le_Webroot=""
     Le_Keylength=""
     Le_LinkIssuer=""
@@ -1120,6 +1418,7 @@ renewAll() {
     Le_RealCACertPath=""
 
     Le_ReloadCmd=""
+    Le_RealFullChainPath=""
     
     DOMAIN_PATH=""
     DOMAIN_CONF=""
@@ -1128,6 +1427,7 @@ renewAll() {
     CERT_KEY_PATH=""
     CERT_PATH=""
     CA_CERT_PATH=""
+    CERT_PFX_PATH=""
     CERT_FULLCHAIN_PATH=""
     ACCOUNT_KEY_PATH=""
     
@@ -1140,8 +1440,8 @@ renewAll() {
 
 installcert() {
   Le_Domain="$1"
-  if [ -z "$Le_Domain" ] ; then
-    _err "Usage: $0  domain.com  [cert-file-path]|no  [key-file-path]|no  [ca-cert-file-path]|no   [reloadCmd]|no"
+  if [[ -z "$Le_Domain" ]] ; then
+    echo "Usage: le.sh --installcert -d domain.com  [--certpath cert-file-path]  [--keypath key-file-path]  [--capath ca-cert-file-path]   [ --reloadCmd reloadCmd] [--fullchainpath fullchain-path]"
     return 1
   fi
 
@@ -1149,6 +1449,7 @@ installcert() {
   Le_RealKeyPath="$3"
   Le_RealCACertPath="$4"
   Le_ReloadCmd="$5"
+  Le_RealFullChainPath="$6"
 
   _initpath $Le_Domain
 
@@ -1156,35 +1457,43 @@ installcert() {
   _setopt "$DOMAIN_CONF"  "Le_RealCACertPath"     "="  "\"$Le_RealCACertPath\""
   _setopt "$DOMAIN_CONF"  "Le_RealKeyPath"        "="  "\"$Le_RealKeyPath\""
   _setopt "$DOMAIN_CONF"  "Le_ReloadCmd"          "="  "\"$Le_ReloadCmd\""
+  _setopt "$DOMAIN_CONF"  "Le_RealFullChainPath"  "="  "\"$Le_RealFullChainPath\""
   
-  if [ "$Le_RealCertPath" ] ; then
-    if [ -f "$Le_RealCertPath" ] ; then
+  if [[ "$Le_RealCertPath" ]] ; then
+    if [[ -f "$Le_RealCertPath" ]] ; then
       cp -p "$Le_RealCertPath" "$Le_RealCertPath".bak
     fi
     cat "$CERT_PATH" > "$Le_RealCertPath"
   fi
   
-  if [ "$Le_RealCACertPath" ] ; then
-    if [ -f "$Le_RealCACertPath" ] ; then
-      cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak
-    fi
-    if [ "$Le_RealCACertPath" == "$Le_RealCertPath" ] ; then
+  if [[ "$Le_RealCACertPath" ]] ; then
+    if [[ "$Le_RealCACertPath" == "$Le_RealCertPath" ]] ; then
       echo "" >> "$Le_RealCACertPath"
       cat "$CA_CERT_PATH" >> "$Le_RealCACertPath"
     else
+      if [[ -f "$Le_RealCACertPath" ]] ; then
+        cp -p "$Le_RealCACertPath" "$Le_RealCACertPath".bak
+      fi
       cat "$CA_CERT_PATH" > "$Le_RealCACertPath"
     fi
   fi
 
 
-  if [ "$Le_RealKeyPath" ] ; then
-    if [ -f "$Le_RealKeyPath" ] ; then
+  if [[ "$Le_RealKeyPath" ]] ; then
+    if [[ -f "$Le_RealKeyPath" ]] ; then
       cp -p "$Le_RealKeyPath" "$Le_RealKeyPath".bak
     fi
     cat "$CERT_KEY_PATH" > "$Le_RealKeyPath"
   fi
+  
+  if [[ "$Le_RealFullChainPath" ]] ; then
+    if [[ -f "$Le_RealFullChainPath" ]] ; then
+      cp -p "$Le_RealFullChainPath" "$Le_RealFullChainPath".bak
+    fi
+    cat "$CERT_FULLCHAIN_PATH" > "$Le_RealFullChainPath"
+  fi  
 
-  if [ "$Le_ReloadCmd" ] ; then
+  if [[ "$Le_ReloadCmd" ]] ; then
     _info "Run Le_ReloadCmd: $Le_ReloadCmd"
     (cd "$DOMAIN_PATH" && eval "$Le_ReloadCmd")
   fi
@@ -1193,9 +1502,16 @@ installcert() {
 
 installcronjob() {
   _initpath
+  if ! _exists "crontab" ; then
+    _err "crontab doesn't exist, so, we can not install cron jobs."
+    _err "All your certs will not be renewed automatically."
+    _err "You must add your own cron job to call 'le.sh cron' everyday."
+    return 1
+  fi
+
   _info "Installing cron job"
   if ! crontab -l | grep 'le.sh cron' ; then 
-    if [ -f "$LE_WORKING_DIR/le.sh" ] ; then
+    if [[ -f "$LE_WORKING_DIR/le.sh" ]] ; then
       lesh="\"$LE_WORKING_DIR\"/le.sh"
     else
       _err "Can not install cronjob, le.sh not found."
@@ -1203,7 +1519,7 @@ installcronjob() {
     fi
     crontab -l | { cat; echo "0 0 * * * LE_WORKING_DIR=\"$LE_WORKING_DIR\" $lesh cron > /dev/null"; } | crontab -
   fi
-  if [ "$?" != "0" ] ; then
+  if [[ "$?" != "0" ]] ; then
     _err "Install cron job failed. You need to manually renew your certs."
     _err "Or you can add cronjob by yourself:"
     _err "LE_WORKING_DIR=\"$LE_WORKING_DIR\" $lesh cron > /dev/null"
@@ -1212,9 +1528,12 @@ installcronjob() {
 }
 
 uninstallcronjob() {
+  if ! _exists "crontab" ; then
+    return
+  fi
   _info "Removing cron job"
   cr="$(crontab -l | grep 'le.sh cron')"
-  if [ "$cr" ] ; then 
+  if [[ "$cr" ]] ; then 
     crontab -l | sed "/le.sh cron/d" | crontab -
     LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 6 | cut -d '=' -f 2 | tr -d '"')"
     _info LE_WORKING_DIR "$LE_WORKING_DIR"
@@ -1223,10 +1542,64 @@ uninstallcronjob() {
   
 }
 
+revoke() {
+  Le_Domain="$1"
+  if [[ -z "$Le_Domain" ]] ; then
+    echo "Usage: le.sh --revoke -d domain.com"
+    return 1
+  fi
+  
+  _initpath $Le_Domain
+  if [[ ! -f "$DOMAIN_CONF" ]] ; then
+    _err "$Le_Domain is not a issued domain, skip."
+    return 1;
+  fi
+  
+  if [[ ! -f "$CERT_PATH" ]] ; then
+    _err "Cert for $Le_Domain $CERT_PATH is not found, skip."
+    return 1
+  fi
+  
+  cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}"| tr -d "\r\n" | _urlencode)"
+
+  if [[ -z "$cert" ]] ; then
+    _err "Cert for $Le_Domain is empty found, skip."
+    return 1
+  fi
+  
+  data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
+  uri="$API/acme/revoke-cert"
+
+  _info "Try domain key first."
+  if _send_signed_request $uri "$data" "" "$CERT_KEY_PATH"; then
+    if [[ -z "$response" ]] ; then
+      _info "Revoke success."
+      rm -f $CERT_PATH
+      return 0
+    else 
+      _err "Revoke error by domain key."
+      _err "$resource"
+    fi
+  fi
+  
+  _info "Then try account key."
+
+  if _send_signed_request $uri "$data" "" "$ACCOUNT_KEY_PATH" ; then
+    if [[ -z "$response" ]] ; then
+      _info "Revoke success."
+      rm -f $CERT_PATH
+      return 0
+    else 
+      _err "Revoke error."
+      _debug "$resource"
+    fi
+  fi
+  return 1
+}
 
 # Detect profile file if not specified as environment variable
 _detect_profile() {
-  if [ -n "$PROFILE" -a -f "$PROFILE" ]; then
+  if [ -n "$PROFILE" -a -f "$PROFILE" ] ; then
     echo "$PROFILE"
     return
   fi
@@ -1236,38 +1609,41 @@ _detect_profile() {
   local SHELLTYPE
   SHELLTYPE="$(basename "/$SHELL")"
 
-  if [ "$SHELLTYPE" = "bash" ]; then
-    if [ -f "$HOME/.bashrc" ]; then
+  if [[ "$SHELLTYPE" = "bash" ]] ; then
+    if [[ -f "$HOME/.bashrc" ]] ; then
       DETECTED_PROFILE="$HOME/.bashrc"
-    elif [ -f "$HOME/.bash_profile" ]; then
+    elif [[ -f "$HOME/.bash_profile" ]] ; then
       DETECTED_PROFILE="$HOME/.bash_profile"
     fi
-  elif [ "$SHELLTYPE" = "zsh" ]; then
+  elif [[ "$SHELLTYPE" = "zsh" ]] ; then
     DETECTED_PROFILE="$HOME/.zshrc"
   fi
 
-  if [ -z "$DETECTED_PROFILE" ]; then
-    if [ -f "$HOME/.profile" ]; then
+  if [[ -z "$DETECTED_PROFILE" ]] ; then
+    if [[ -f "$HOME/.profile" ]] ; then
       DETECTED_PROFILE="$HOME/.profile"
-    elif [ -f "$HOME/.bashrc" ]; then
+    elif [[ -f "$HOME/.bashrc" ]] ; then
       DETECTED_PROFILE="$HOME/.bashrc"
-    elif [ -f "$HOME/.bash_profile" ]; then
+    elif [[ -f "$HOME/.bash_profile" ]] ; then
       DETECTED_PROFILE="$HOME/.bash_profile"
-    elif [ -f "$HOME/.zshrc" ]; then
+    elif [[ -f "$HOME/.zshrc" ]] ; then
       DETECTED_PROFILE="$HOME/.zshrc"
     fi
   fi
 
-  if [ ! -z "$DETECTED_PROFILE" ]; then
+  if [[ ! -z "$DETECTED_PROFILE" ]] ; then
     echo "$DETECTED_PROFILE"
   fi
 }
 
 _initconf() {
   _initpath
-  if [ ! -f "$ACCOUNT_CONF_PATH" ] ; then
-    echo "#Account configurations:
+  if [[ ! -f "$ACCOUNT_CONF_PATH" ]] ; then
+    echo "#ACCOUNT_CONF_PATH=xxxx
+
+#Account configurations:
 #Here are the supported macros, uncomment them to make them take effect.
+
 #ACCOUNT_EMAIL=aaa@aaa.com  # the account email used to register account.
 #ACCOUNT_KEY_PATH=\"/path/to/account.key\"
 
@@ -1277,6 +1653,10 @@ _initconf() {
 
 #ACCOUNT_KEY_HASH=account key hash
 
+USER_AGENT=\"le.sh client: $PROJECT\"
+
+#USER_PATH=""
+
 #dns api
 #######################
 #Cloudflare:
@@ -1302,53 +1682,59 @@ _initconf() {
   fi
 }
 
-install() {
-  if ! _initpath ; then
-    _err "Install failed."
+_precheck() {
+  if ! _exists "curl"  && ! _exists "wget"; then
+    _err "Please install curl or wget first, we need to access http resources."
     return 1
   fi
   
-  #check if there is sudo installed, AND if the current user is a sudoer.
-  if command -v sudo > /dev/null ; then
-    if [ "$(sudo -n uptime 2>&1|grep "load"|wc -l)" != "0" ] ; then
-      SUDO=sudo
+  if ! _exists "crontab" ; then
+    _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'."
+    _err "We need to set cron job to renew the certs automatically."
+    _err "Otherwise, your certs will not be able to be renewed automatically."
+    if [[ -z "$FORCE" ]] ; then
+      _err "Please define 'FORCE=1' and try install again to go without crontab."
+      _err "FORCE=1 ./le.sh install"
+      return 1
     fi
   fi
   
-  if command -v yum > /dev/null ; then
-   YUM="1"
-   INSTALL="$SUDO yum install -y "
-  elif command -v apt-get > /dev/null ; then
-   INSTALL="$SUDO apt-get install -y "
+  if ! _exists "openssl" ; then
+    _err "Please install openssl first."
+    _err "We need openssl to generate keys."
+    return 1
+  fi
+  
+  if ! _exists "nc" ; then
+    _err "It is recommended to install nc first, try to install 'nc' or 'netcat'."
+    _err "We use nc for standalone server if you use standalone mode."
+    _err "If you don't use standalone mode, just ignore this warning."
   fi
+  
+  return 0
+}
 
-  if ! command -v "curl" > /dev/null ; then
-    _err "Please install curl first."
-    _err "$INSTALL curl"
+install() {
+  if ! _initpath ; then
+    _err "Install failed."
     return 1
   fi
   
-  if ! command -v "crontab" > /dev/null ; then
-    _err "Please install crontab first."
-    if [ "$YUM" ] ; then
-      _err "$INSTALL crontabs"
-    else
-      _err "$INSTALL crontab"
-    fi
+  if ! _precheck ; then
+    _err "Pre-check failed, can not install."
     return 1
   fi
   
-  if ! command -v "openssl" > /dev/null ; then
-    _err "Please install openssl first."
-    _err "$INSTALL openssl"
+  _info "Installing to $LE_WORKING_DIR"
+  
+  if ! mkdir -p "$LE_WORKING_DIR" ; then
+    _err "Can not craete working dir: $LE_WORKING_DIR"
     return 1
   fi
-
-  _info "Installing to $LE_WORKING_DIR"
-
+  
   cp le.sh "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/le.sh"
 
-  if [ "$?" != "0" ] ; then
+  if [[ "$?" != "0" ]] ; then
     _err "Install failed, can not copy le.sh"
     return 1
   fi
@@ -1356,7 +1742,7 @@ install() {
   _info "Installed to $LE_WORKING_DIR/le.sh"
 
   _profile="$(_detect_profile)"
-  if [ "$_profile" ] ; then
+  if [[ "$_profile" ]] ; then
     _debug "Found profile: $_profile"
     
     echo "LE_WORKING_DIR=$LE_WORKING_DIR
@@ -1374,15 +1760,19 @@ alias le.sh=\"$LE_WORKING_DIR/le.sh\"
   cp  dnsapi/* $LE_WORKING_DIR/dnsapi/
   
   #to keep compatible mv the .acc file to .key file 
-  if [ -f "$LE_WORKING_DIR/account.acc" ] ; then
+  if [[ -f "$LE_WORKING_DIR/account.acc" ]] ; then
     mv "$LE_WORKING_DIR/account.acc" "$LE_WORKING_DIR/account.key"
   fi
+
+  if [[ ! -f "$ACCOUNT_CONF_PATH" ]] ; then
+    _initconf
+  fi
+  
+  _setopt "$_DEFAULT_ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
+  _setopt "$ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\""
   
   installcronjob
   
-  if [ ! -f "$ACCOUNT_CONF_PATH" ] ; then
-    _initconf
-  fi
   _info OK
 }
 
@@ -1391,7 +1781,7 @@ uninstall() {
   _initpath
 
   _profile="$(_detect_profile)"
-  if [ "$_profile" ] ; then
+  if [[ "$_profile" ]] ; then
     text="$(cat $_profile)"
     echo "$text" | sed "s|^source.*le.env.*$||" > "$_profile"
   fi
@@ -1402,49 +1792,341 @@ uninstall() {
 }
 
 cron() {
+  IN_CRON=1
   renewAll
+  IN_CRON=""
 }
 
 version() {
-  _info "$PROJECT"
-  _info "v$VER"
+  echo "$PROJECT"
+  echo "v$VER"
 }
 
 showhelp() {
   version
-  echo "Usage: le.sh  [command] ...[args]....
-Avalible commands:
-
-install:
-  Install le.sh to your system.
-issue:
-  Issue a cert.
-installcert:
-  Install the issued cert to apache/nginx or any other server.
-renew:
-  Renew a cert.
-renewAll:
-  Renew all the certs.
-uninstall:
-  Uninstall le.sh, and uninstall the cron job.
-version:
-  Show version info.
-installcronjob:
-  Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
-uninstallcronjob:
-  Uninstall the cron job. The 'uninstall' command can do this automatically.
-createAccountKey:
-  Create an account private key, professional use.
-createDomainKey:
-  Create an domain private key, professional use.
-createCSR:
-  Create CSR , professional use.
+  echo "Usage: le.sh  command ...[parameters]....
+Commands:
+  --help, -h               Show this help message.
+  --version, -v            Show version info.
+  --install                Install le.sh to your system.
+  --uninstall              Uninstall le.sh, and uninstall the cron job.
+  --issue                  Issue a cert.
+  --installcert            Install the issued cert to apache/nginx or any other server.
+  --renew, -r              Renew a cert.
+  --renewAll               Renew all the certs
+  --revoke                 Revoke a cert.
+  --installcronjob         Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job.
+  --uninstallcronjob       Uninstall the cron job. The 'uninstall' command can do this automatically.
+  --cron                   Run cron job to renew all the certs.
+  --toPkcs                 Export the certificate and key to a pfx file.
+  --createAccountKey, -cak Create an account private key, professional use.
+  --createDomainKey, -cdk  Create an domain private key, professional use.
+  --createCSR, -ccsr       Create CSR , professional use.
+  
+Parameters:
+  --domain, -d   domain.tld         Specifies a domain, used to issue, renew or revoke etc.
+  --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.
+    
+  --webroot, -w  /path/to/webroot   Specifies the web root folder for web root mode.
+  --standalone                      Use standalone mode.
+  --apache                          Use apache mode.
+  --dns [dns-cf|dns-dp|dns-cx|/path/to/api/file]   Use dns mode or dns api.
+  
+  --keylength, -k [2048]            Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384.
+  --accountkeylength, -ak [2048]    Specifies the account key length.
+  
+  These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert:
+  
+  --certpath /path/to/real/cert/file  After issue/renew, the cert will be copied to this path.
+  --keypath /path/to/real/key/file  After issue/renew, the key will be copied to this path.
+  --capath /path/to/real/ca/file    After issue/renew, the intermediate cert will be copied to this path.
+  --fullchainpath /path/to/fullchain/file After issue/renew, the fullchain cert will be copied to this path.
+  
+  --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
+
+  --accountconf                     Specifies a customized account config file.
+  --leworkingdir                    Specifies the home dir for le.sh
+  
   "
 }
 
+_installOnline() {
+  _info "Installing from online archive."
+  if [[ ! "$BRANCH" ]] ; then
+    BRANCH="master"
+  fi
+  _initpath
+  target="$PROJECT/archive/$BRANCH.tar.gz"
+  _info "Downloading $target"
+  localname="$BRANCH.tar.gz"
+  if ! _get "$target" > $localname ; then
+    _debug "Download error."
+    return 1
+  fi
+  _info "Extracting $localname"
+  tar xzf $localname
+  cd "le-$BRANCH"
+  chmod +x le.sh
+  if ./le.sh install ; then
+    _info "Install success!"
+  fi
+  
+  cd ..
+  rm -rf "le-$BRANCH"
+  rm -f "$localname"
+}
+
+
+_process() {
+  _CMD=""
+  _domain=""
+  _altdomains="no"
+  _webroot=""
+  _keylength="no"
+  _accountkeylength="no"
+  _certpath="no"
+  _keypath="no"
+  _capath="no"
+  _fullchainpath="no"
+  _reloadcmd="no"
+  _password=""
+  while (( ${#} )); do
+    case "${1}" in
+    
+    --help|-h)
+        showhelp
+        return
+        ;;
+    --version|-v)
+        version
+        return
+        ;;
+    --install)
+        _CMD="install"
+        ;;
+    --uninstall)
+        _CMD="uninstall"
+        ;;
+    --issue)
+        _CMD="issue"
+        ;;
+    --installcert|-i)
+        _CMD="installcert"
+        ;;
+    --renew|-r)
+        _CMD="renew"
+        ;;
+    --renewAll|-renewall)
+        _CMD="renewAll"
+        ;;
+    --revoke)
+        _CMD="revoke"
+        ;;
+    --installcronjob)
+        _CMD="installcronjob"
+        ;;
+    --uninstallcronjob)
+        _CMD="uninstallcronjob"
+        ;;
+    --cron)
+        _CMD="cron"
+        ;;
+    --toPkcs)
+        _CMD="toPkcs"
+        ;; 
+    --createAccountKey|--createaccountkey|-cak)
+        _CMD="createAccountKey"
+        ;;
+    --createDomainKey|--createdomainkey|-cdk)
+        _CMD="createDomainKey"
+        ;;
+    --createCSR|--createcsr|-ccr)
+        _CMD="createCSR"
+        ;;
+
+     
+    --domain|-d)
+        _dvalue="$2"
+        
+        if [[ -z "$_dvalue" ]] || [[ "$_dvalue" == "-"* ]] ; then
+          _err "'$_dvalue' is not a valid domain for parameter '$1'"
+          return 1
+        fi
+        
+        if [[ -z "$_domain" ]] ; then
+          _domain="$_dvalue"
+        else
+          if [[ "$_altdomains" == "no" ]] ; then
+            _altdomains="$_dvalue"
+          else
+            _altdomains="$_altdomains,$_dvalue"
+          fi
+        fi
+        shift
+        ;;
+
+    --force|-f)
+        FORCE="1"
+        ;;
+    --staging|--test)
+        STAGE="1"
+        ;;
+    --debug)
+        if [[ "$2" == "-"* ]] || [[ -z "$2" ]]; then
+          DEBUG="1"
+        else
+          DEBUG="$2"
+          shift
+        fi 
+        ;;
+    --webroot|-w)
+        wvalue="$2"
+        if [[ -z "$_webroot" ]] ; then
+          _webroot="$wvalue"
+        else
+          _webroot="$_webroot,$wvalue"
+        fi
+        shift
+        ;;        
+    --standalone)
+        wvalue="no"
+        if [[ -z "$_webroot" ]] ; then
+          _webroot="$wvalue"
+        else
+          _webroot="$_webroot,$wvalue"
+        fi
+        ;;
+    --apache)
+        wvalue="apache"
+        if [[ -z "$_webroot" ]] ; then
+          _webroot="$wvalue"
+        else
+          _webroot="$_webroot,$wvalue"
+        fi
+        ;;
+    --dns)
+        wvalue="dns"
+        if [[ "$2" != "-"* ]] ; then
+          wvalue="$2"
+          shift
+        fi
+        if [[ -z "$_webroot" ]] ; then
+          _webroot="$wvalue"
+        else
+          _webroot="$_webroot,$wvalue"
+        fi
+        ;;
+    --keylength|-k)
+        _keylength="$2"
+        accountkeylength="$2"
+        shift
+        ;;
+    --accountkeylength|-ak)
+        accountkeylength="$2"
+        shift
+        ;;
+
+    --certpath)
+        _certpath="$2"
+        shift
+        ;;
+    --keypath)
+        _keypath="$2"
+        shift
+        ;;
+    --capath)
+        _capath="$2"
+        shift
+        ;;
+    --fullchainpath)
+        _fullchainpath="$2"
+        shift
+        ;;
+    --reloadcmd)
+        _reloadcmd="$2"
+        shift
+        ;;
+    --password)
+        _password="$2"
+        shift
+        ;;
+    --accountconf)
+        ACCOUNT_CONF_PATH="$2"
+        ;;
+    --leworkingdir)
+        LE_WORKING_DIR="$2"
+        ;;
+        
+    *)
+        _err "Unknown parameter : $1"
+        return 1
+        ;;
+    esac
+
+    shift 1
+  done
 
-if [ -z "$1" ] ; then
+
+  case "${_CMD}" in
+    install) install ;;
+    uninstall) uninstall ;;
+    issue)
+      issue  "$_webroot"  "$_domain" "$_altdomains" "$_keylength" "$_certpath" "$_keylength" "$_capath" "$_reloadcmd" "$_fullchainpath"
+      ;;
+    installcert)
+      installcert "$_domain" "$_certpath" "$_keylength" "$_capath" "$_reloadcmd" "$_fullchainpath"
+      ;;
+    renew) 
+      renew "$_domain" 
+      ;;
+    renewAll) 
+      renewAll 
+      ;;
+    revoke) 
+      revoke "$_domain" 
+      ;;
+    installcronjob) installcronjob ;;
+    uninstallcronjob) uninstallcronjob ;;
+    cron) cron ;;
+    toPkcs) 
+      toPkcs "$_domain" "$_password"
+      ;;
+    createAccountKey) 
+      createAccountKey "$_domain" "$_accountkeylength"
+      ;;
+    createDomainKey) 
+      createDomainKey "$_domain" "$_keylength"
+      ;;
+    createCSR) 
+      createCSR "$_domain" "$_altdomains"
+      ;;
+
+    *)
+      _err "Invalid command: $_CMD"
+      showhelp;
+      return 1
+    ;;
+  esac
+  
+}
+
+
+if [[ "$INSTALLONLINE" ]] ; then
+  INSTALLONLINE=""
+  _installOnline $BRANCH
+  exit
+fi
+
+if [[ -z "$1" ]] ; then
   showhelp
 else
-  "$@"
+  if [[ "$1" == "-"* ]] ; then
+    _process "$@"
+  else
+    "$@"
+  fi
 fi
+
+