Browse Source

Merge branch 'dev' into master

justmwa 8 years ago
parent
commit
9201e0a5b9

+ 59 - 0
Dockerfile

@@ -0,0 +1,59 @@
+FROM alpine
+
+RUN apk update -f \
+  && apk --no-cache add -f \
+  openssl \
+  curl \
+  netcat-openbsd
+
+ENV LE_CONFIG_HOME /acme.sh
+
+ENV AUTO_UPGRADE 1
+
+#Install
+RUN mkdir -p /install_acme.sh/
+ADD ./ /install_acme.sh/
+RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh)
+RUN rm -rf /install_acme.sh/
+
+RUN ln -s  /root/.acme.sh/acme.sh  /usr/local/bin/acme.sh
+
+RUN for verb in help \ 
+  version \
+  install \
+  uninstall \
+  upgrade \
+  issue \
+  signcsr \
+  deploy \
+  install-cert \
+  renew \
+  renew-all \
+  revoke \
+  remove \
+  list \
+  showcsr \
+  install-cronjob \
+  uninstall-cronjob \
+  cron \
+  toPkcs \
+  toPkcs8 \
+  update-account \
+  register-account \
+  create-account-key \
+  create-domain-key \
+  createCSR \
+  deactivate \
+  ; do \
+    printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
+  ; done
+
+RUN printf "%b" '#!'"/usr/bin/env sh\n \
+if [ \"\$1\" = \"daemon\" ];  then \n \
+ crond; tail -f /dev/null;\n \
+else \n \
+ /root/.acme.sh/acme.sh --config-home /acme.sh \"\$@\"\n \
+fi" >/entry.sh && chmod +x /entry.sh
+
+ENTRYPOINT ["/entry.sh"]
+CMD ["--help"]

+ 31 - 9
README.md

@@ -7,11 +7,13 @@
 - Purely written in Shell with no dependencies on python or the official Let's Encrypt client.
 - Purely written in Shell with no dependencies on python or the official Let's Encrypt client.
 - Just one script to issue, renew and install your certificates automatically.
 - Just one script to issue, renew and install your certificates automatically.
 - DOES NOT require `root/sudoer` access.
 - DOES NOT require `root/sudoer` access.
+- Docker friendly
 
 
-It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
+It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
 
 
 Wiki: https://github.com/Neilpang/acme.sh/wiki
 Wiki: https://github.com/Neilpang/acme.sh/wiki
 
 
+For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/Neilpang/acme.sh/wiki/Run-acme.sh-in-docker)
 
 
 Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 
 
@@ -29,6 +31,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 - [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html)
 - [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html)
 - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
 - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
 - [archlinux](https://aur.archlinux.org/packages/acme.sh-git/)
 - [archlinux](https://aur.archlinux.org/packages/acme.sh-git/)
+- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
 - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials)
 - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials)
 
 
 # Tested OS
 # Tested OS
@@ -133,13 +136,25 @@ root@v1:~# acme.sh -h
 acme.sh --issue -d example.com -w /home/wwwroot/example.com
 acme.sh --issue -d example.com -w /home/wwwroot/example.com
 ```
 ```
 
 
+or:
+
+```bash
+acme.sh --issue -d example.com -w /home/username/public_html
+```
+
+or:
+
+```bash
+acme.sh --issue -d example.com -w /var/www/html
+```
+
 **Example 2:** Multiple domains in the same cert.
 **Example 2:** Multiple domains in the same cert.
 
 
 ```bash
 ```bash
 acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com
 acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com
 ```
 ```
 
 
-The parameter `/home/wwwroot/example.com` is the web root folder. You **MUST** have `write access` to this folder.
+The parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder.
 
 
 Second argument **"example.com"** is the main domain you want to issue the cert for.
 Second argument **"example.com"** is the main domain you want to issue the cert for.
 You must have at least one domain there.
 You must have at least one domain there.
@@ -161,17 +176,17 @@ You **MUST** use this command to copy the certs to the target files, **DO NOT**
 **Apache** example:
 **Apache** example:
 ```bash
 ```bash
 acme.sh --install-cert -d example.com \
 acme.sh --install-cert -d example.com \
---certpath      /path/to/certfile/in/apache/cert.pem  \
---keypath       /path/to/keyfile/in/apache/key.pem  \
---fullchainpath /path/to/fullchain/certfile/apache/fullchain.pem \
+--cert-file      /path/to/certfile/in/apache/cert.pem  \
+--key-file       /path/to/keyfile/in/apache/key.pem  \
+--fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \
 --reloadcmd     "service apache2 force-reload"
 --reloadcmd     "service apache2 force-reload"
 ```
 ```
 
 
 **Nginx** example:
 **Nginx** example:
 ```bash
 ```bash
 acme.sh --install-cert -d example.com \
 acme.sh --install-cert -d example.com \
---keypath       /path/to/keyfile/in/nginx/key.pem  \
---fullchainpath /path/to/fullchain/nginx/cert.pem \
+--key-file       /path/to/keyfile/in/nginx/key.pem  \
+--fullchain-file /path/to/fullchain/nginx/cert.pem \
 --reloadcmd     "service nginx force-reload"
 --reloadcmd     "service nginx force-reload"
 ```
 ```
 
 
@@ -289,6 +304,7 @@ You don't have to do anything manually!
 
 
 1. CloudFlare.com API
 1. CloudFlare.com API
 1. DNSPod.cn API
 1. DNSPod.cn API
+1. DNSimple API
 1. CloudXNS.com API
 1. CloudXNS.com API
 1. GoDaddy.com API
 1. GoDaddy.com API
 1. OVH, kimsufi, soyoustart and runabove API
 1. OVH, kimsufi, soyoustart and runabove API
@@ -308,7 +324,13 @@ You don't have to do anything manually!
 1. Domain-Offensive/Resellerinterface/Domainrobot API
 1. Domain-Offensive/Resellerinterface/Domainrobot API
 1. Gandi LiveDNS API
 1. Gandi LiveDNS API
 1. Knot DNS API
 1. Knot DNS API
-1. NS1. API
+1. NS1.com API
+1. DigitalOcean API (native)
+1. ClouDNS.net API
+1. Infoblox NIOS API (https://www.infoblox.com/)
+1. VSCALE (https://vscale.io/)
+1. Dynu API (https://www.dynu.com)
+
 
 
 **More APIs coming soon...**
 **More APIs coming soon...**
 
 
@@ -327,7 +349,7 @@ Just set the `length` parameter with a prefix `ec-`.
 
 
 For example:
 For example:
 
 
-### Single domain ECC cerfiticate
+### Single domain ECC certificate
 
 
 ```bash
 ```bash
 acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256
 acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256

+ 188 - 102
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-VER=2.6.7
+VER=2.6.9
 
 
 PROJECT_NAME="acme.sh"
 PROJECT_NAME="acme.sh"
 
 
@@ -107,7 +107,7 @@ __green() {
   if [ "$__INTERACTIVE" ]; then
   if [ "$__INTERACTIVE" ]; then
     printf '\033[1;31;32m'
     printf '\033[1;31;32m'
   fi
   fi
-  printf -- "$1"
+  printf -- "%b" "$1"
   if [ "$__INTERACTIVE" ]; then
   if [ "$__INTERACTIVE" ]; then
     printf '\033[0m'
     printf '\033[0m'
   fi
   fi
@@ -117,7 +117,7 @@ __red() {
   if [ "$__INTERACTIVE" ]; then
   if [ "$__INTERACTIVE" ]; then
     printf '\033[1;31;40m'
     printf '\033[1;31;40m'
   fi
   fi
-  printf -- "$1"
+  printf -- "%b" "$1"
   if [ "$__INTERACTIVE" ]; then
   if [ "$__INTERACTIVE" ]; then
     printf '\033[0m'
     printf '\033[0m'
   fi
   fi
@@ -138,8 +138,8 @@ _printargs() {
 _dlg_versions() {
 _dlg_versions() {
   echo "Diagnosis versions: "
   echo "Diagnosis versions: "
   echo "openssl:$ACME_OPENSSL_BIN"
   echo "openssl:$ACME_OPENSSL_BIN"
-  if _exists "$ACME_OPENSSL_BIN"; then
-    $ACME_OPENSSL_BIN version 2>&1
+  if _exists "${ACME_OPENSSL_BIN:-openssl}"; then
+    ${ACME_OPENSSL_BIN:-openssl} version 2>&1
   else
   else
     echo "$ACME_OPENSSL_BIN doesn't exists."
     echo "$ACME_OPENSSL_BIN doesn't exists."
   fi
   fi
@@ -166,7 +166,14 @@ _syslog() {
   fi
   fi
   _logclass="$1"
   _logclass="$1"
   shift
   shift
-  logger -i -t "$PROJECT_NAME" -p "$_logclass" "$(_printargs "$@")" >/dev/null 2>&1
+  if [ -z "$__logger_i" ]; then
+    if _contains "$(logger --help 2>&1)" "-i"; then
+      __logger_i="logger -i"
+    else
+      __logger_i="logger"
+    fi
+  fi
+  $__logger_i -t "$PROJECT_NAME" -p "$_logclass" "$(_printargs "$@")" >/dev/null 2>&1
 }
 }
 
 
 _log() {
 _log() {
@@ -299,6 +306,16 @@ _secure_debug3() {
   fi
   fi
 }
 }
 
 
+_upper_case() {
+  # shellcheck disable=SC2018,SC2019
+  tr 'a-z' 'A-Z'
+}
+
+_lower_case() {
+  # shellcheck disable=SC2018,SC2019
+  tr 'A-Z' 'a-z'
+}
+
 _startswith() {
 _startswith() {
   _str="$1"
   _str="$1"
   _sub="$2"
   _sub="$2"
@@ -330,14 +347,14 @@ _hasfield() {
     _sep=","
     _sep=","
   fi
   fi
 
 
-  for f in $(echo "$_str" | tr ',' ' '); do
+  for f in $(echo "$_str" | tr "$_sep" ' '); do
     if [ "$f" = "$_field" ]; then
     if [ "$f" = "$_field" ]; then
       _debug2 "'$_str' contains '$_field'"
       _debug2 "'$_str' contains '$_field'"
       return 0 #contains ok
       return 0 #contains ok
     fi
     fi
   done
   done
   _debug2 "'$_str' does not contain '$_field'"
   _debug2 "'$_str' does not contain '$_field'"
-  return 1 #not contains 
+  return 1 #not contains
 }
 }
 
 
 _getfield() {
 _getfield() {
@@ -712,7 +729,7 @@ _url_encode() {
       "7e")
       "7e")
         printf "%s" "~"
         printf "%s" "~"
         ;;
         ;;
-      #other hex  
+      #other hex
       *)
       *)
         printf '%%%s' "$_hex_code"
         printf '%%%s' "$_hex_code"
         ;;
         ;;
@@ -780,19 +797,19 @@ _base64() {
   [ "" ] #urgly
   [ "" ] #urgly
   if [ "$1" ]; then
   if [ "$1" ]; then
     _debug3 "base64 multiline:'$1'"
     _debug3 "base64 multiline:'$1'"
-    $ACME_OPENSSL_BIN base64 -e
+    ${ACME_OPENSSL_BIN:-openssl} base64 -e
   else
   else
     _debug3 "base64 single line."
     _debug3 "base64 single line."
-    $ACME_OPENSSL_BIN base64 -e | tr -d '\r\n'
+    ${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n'
   fi
   fi
 }
 }
 
 
 #Usage: multiline
 #Usage: multiline
 _dbase64() {
 _dbase64() {
   if [ "$1" ]; then
   if [ "$1" ]; then
-    $ACME_OPENSSL_BIN base64 -d -A
+    ${ACME_OPENSSL_BIN:-openssl} base64 -d -A
   else
   else
-    $ACME_OPENSSL_BIN base64 -d
+    ${ACME_OPENSSL_BIN:-openssl} base64 -d
   fi
   fi
 }
 }
 
 
@@ -809,9 +826,9 @@ _digest() {
 
 
   if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
   if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then
     if [ "$outputhex" ]; then
     if [ "$outputhex" ]; then
-      $ACME_OPENSSL_BIN dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
+      ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' '
     else
     else
-      $ACME_OPENSSL_BIN dgst -"$alg" -binary | _base64
+      ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64
     fi
     fi
   else
   else
     _err "$alg is not supported yet"
     _err "$alg is not supported yet"
@@ -834,9 +851,9 @@ _hmac() {
 
 
   if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
   if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then
     if [ "$outputhex" ]; then
     if [ "$outputhex" ]; then
-      ($ACME_OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || $ACME_OPENSSL_BIN dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
+      (${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' '
     else
     else
-      $ACME_OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || $ACME_OPENSSL_BIN dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
+      ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary
     fi
     fi
   else
   else
     _err "$alg is not supported yet"
     _err "$alg is not supported yet"
@@ -855,7 +872,7 @@ _sign() {
     return 1
     return 1
   fi
   fi
 
 
-  _sign_openssl="$ACME_OPENSSL_BIN   dgst -sign $keyfile "
+  _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
   if [ "$alg" = "sha256" ]; then
   if [ "$alg" = "sha256" ]; then
     _sign_openssl="$_sign_openssl -$alg"
     _sign_openssl="$_sign_openssl -$alg"
   else
   else
@@ -866,10 +883,10 @@ _sign() {
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
     $_sign_openssl | _base64
     $_sign_openssl | _base64
   elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
   elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
-    if ! _signedECText="$($_sign_openssl | $ACME_OPENSSL_BIN asn1parse -inform DER)"; then
+    if ! _signedECText="$($_sign_openssl | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
       _err "Sign failed: $_sign_openssl"
       _err "Sign failed: $_sign_openssl"
       _err "Key file: $keyfile"
       _err "Key file: $keyfile"
-      _err "Key content:$(wc -l <"$keyfile") lises"
+      _err "Key content:$(wc -l <"$keyfile") lines"
       return 1
       return 1
     fi
     fi
     _debug3 "_signedECText" "$_signedECText"
     _debug3 "_signedECText" "$_signedECText"
@@ -938,10 +955,10 @@ _createkey() {
 
 
   if _isEccKey "$length"; then
   if _isEccKey "$length"; then
     _debug "Using ec name: $eccname"
     _debug "Using ec name: $eccname"
-    $ACME_OPENSSL_BIN ecparam -name "$eccname" -genkey 2>/dev/null >"$f"
+    ${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -genkey 2>/dev/null >"$f"
   else
   else
     _debug "Using RSA: $length"
     _debug "Using RSA: $length"
-    $ACME_OPENSSL_BIN genrsa "$length" 2>/dev/null >"$f"
+    ${ACME_OPENSSL_BIN:-openssl} genrsa "$length" 2>/dev/null >"$f"
   fi
   fi
 
 
   if [ "$?" != "0" ]; then
   if [ "$?" != "0" ]; then
@@ -1015,7 +1032,7 @@ _createcsr() {
     else
     else
       alt="DNS:$domainlist"
       alt="DNS:$domainlist"
     fi
     fi
-    #multi 
+    #multi
     _info "Multi domain" "$alt"
     _info "Multi domain" "$alt"
     printf -- "\nsubjectAltName=$alt" >>"$csrconf"
     printf -- "\nsubjectAltName=$alt" >>"$csrconf"
   fi
   fi
@@ -1028,9 +1045,9 @@ _createcsr() {
   _csr_cn="$(_idn "$domain")"
   _csr_cn="$(_idn "$domain")"
   _debug2 _csr_cn "$_csr_cn"
   _debug2 _csr_cn "$_csr_cn"
   if _contains "$(uname -a)" "MINGW"; then
   if _contains "$(uname -a)" "MINGW"; then
-    $ACME_OPENSSL_BIN req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr"
+    ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr"
   else
   else
-    $ACME_OPENSSL_BIN req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr"
+    ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr"
   fi
   fi
 }
 }
 
 
@@ -1042,7 +1059,7 @@ _signcsr() {
   cert="$4"
   cert="$4"
   _debug "_signcsr"
   _debug "_signcsr"
 
 
-  _msg="$($ACME_OPENSSL_BIN x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)"
+  _msg="$(${ACME_OPENSSL_BIN:-openssl} x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)"
   _ret="$?"
   _ret="$?"
   _debug "$_msg"
   _debug "$_msg"
   return $_ret
   return $_ret
@@ -1055,7 +1072,7 @@ _readSubjectFromCSR() {
     _usage "_readSubjectFromCSR mycsr.csr"
     _usage "_readSubjectFromCSR mycsr.csr"
     return 1
     return 1
   fi
   fi
-  $ACME_OPENSSL_BIN req -noout -in "$_csrfile" -subject | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n'
+  ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n'
 }
 }
 
 
 #_csrfile
 #_csrfile
@@ -1070,7 +1087,7 @@ _readSubjectAltNamesFromCSR() {
   _csrsubj="$(_readSubjectFromCSR "$_csrfile")"
   _csrsubj="$(_readSubjectFromCSR "$_csrfile")"
   _debug _csrsubj "$_csrsubj"
   _debug _csrsubj "$_csrsubj"
 
 
-  _dnsAltnames="$($ACME_OPENSSL_BIN req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')"
+  _dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')"
   _debug _dnsAltnames "$_dnsAltnames"
   _debug _dnsAltnames "$_dnsAltnames"
 
 
   if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
   if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
@@ -1083,7 +1100,7 @@ _readSubjectAltNamesFromCSR() {
   printf "%s" "$_dnsAltnames" | sed "s/DNS://g"
   printf "%s" "$_dnsAltnames" | sed "s/DNS://g"
 }
 }
 
 
-#_csrfile 
+#_csrfile
 _readKeyLengthFromCSR() {
 _readKeyLengthFromCSR() {
   _csrfile="$1"
   _csrfile="$1"
   if [ -z "$_csrfile" ]; then
   if [ -z "$_csrfile" ]; then
@@ -1091,13 +1108,14 @@ _readKeyLengthFromCSR() {
     return 1
     return 1
   fi
   fi
 
 
-  _outcsr="$($ACME_OPENSSL_BIN req -noout -text -in "$_csrfile")"
+  _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")"
+  _debug2 _outcsr "$_outcsr"
   if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then
   if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then
     _debug "ECC CSR"
     _debug "ECC CSR"
-    echo "$_outcsr" | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
+    echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' '
   else
   else
     _debug "RSA CSR"
     _debug "RSA CSR"
-    echo "$_outcsr" | _egrep_o "(^ *|^RSA )Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1
+    echo "$_outcsr" | tr "\t" " " | _egrep_o "(^ *|RSA )Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1
   fi
   fi
 }
 }
 
 
@@ -1121,8 +1139,12 @@ _ss() {
       elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then
       elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then
         #for solaris
         #for solaris
         netstat -an -P tcp | grep "\.$_port " | grep "LISTEN"
         netstat -an -P tcp | grep "\.$_port " | grep "LISTEN"
-      else
+      elif netstat -help 2>&1 | grep "\-p" >/dev/null; then
+        #for full linux
         netstat -ntpl | grep ":$_port "
         netstat -ntpl | grep ":$_port "
+      else
+        #for busybox (embedded linux; no pid support)
+        netstat -ntl 2>/dev/null | grep ":$_port "
       fi
       fi
     fi
     fi
     return 0
     return 0
@@ -1145,9 +1167,9 @@ toPkcs() {
   _initpath "$domain" "$_isEcc"
   _initpath "$domain" "$_isEcc"
 
 
   if [ "$pfxPassword" ]; then
   if [ "$pfxPassword" ]; then
-    $ACME_OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword"
+    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword"
   else
   else
-    $ACME_OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH"
+    ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH"
   fi
   fi
 
 
   if [ "$?" = "0" ]; then
   if [ "$?" = "0" ]; then
@@ -1169,7 +1191,7 @@ toPkcs8() {
 
 
   _initpath "$domain" "$_isEcc"
   _initpath "$domain" "$_isEcc"
 
 
-  $ACME_OPENSSL_BIN pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH"
+  ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH"
 
 
   if [ "$?" = "0" ]; then
   if [ "$?" = "0" ]; then
     _info "Success, $CERT_PKCS8_PATH"
     _info "Success, $CERT_PKCS8_PATH"
@@ -1177,7 +1199,7 @@ toPkcs8() {
 
 
 }
 }
 
 
-#[2048]  
+#[2048]
 createAccountKey() {
 createAccountKey() {
   _info "Creating account key"
   _info "Creating account key"
   if [ -z "$1" ]; then
   if [ -z "$1" ]; then
@@ -1330,7 +1352,7 @@ _calcjwk() {
 
 
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
     _debug "RSA key"
     _debug "RSA key"
-    pub_exp=$($ACME_OPENSSL_BIN rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1)
+    pub_exp=$(${ACME_OPENSSL_BIN:-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
       pub_exp=0$pub_exp
     fi
     fi
@@ -1339,7 +1361,7 @@ _calcjwk() {
     e=$(echo "$pub_exp" | _h2b | _base64)
     e=$(echo "$pub_exp" | _h2b | _base64)
     _debug3 e "$e"
     _debug3 e "$e"
 
 
-    modulus=$($ACME_OPENSSL_BIN rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2)
+    modulus=$(${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2)
     _debug3 modulus "$modulus"
     _debug3 modulus "$modulus"
     n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)"
     n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)"
     _debug3 n "$n"
     _debug3 n "$n"
@@ -1352,12 +1374,12 @@ _calcjwk() {
     JWK_HEADERPLACE_PART2='", "alg": "RS256", "jwk": '$jwk'}'
     JWK_HEADERPLACE_PART2='", "alg": "RS256", "jwk": '$jwk'}'
   elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
   elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
     _debug "EC key"
     _debug "EC key"
-    crv="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
+    crv="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")"
     _debug3 crv "$crv"
     _debug3 crv "$crv"
 
 
     if [ -z "$crv" ]; then
     if [ -z "$crv" ]; then
       _debug "Let's try ASN1 OID"
       _debug "Let's try ASN1 OID"
-      crv_oid="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")"
+      crv_oid="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")"
       _debug3 crv_oid "$crv_oid"
       _debug3 crv_oid "$crv_oid"
       case "${crv_oid}" in
       case "${crv_oid}" in
         "prime256v1")
         "prime256v1")
@@ -1377,15 +1399,15 @@ _calcjwk() {
       _debug3 crv "$crv"
       _debug3 crv "$crv"
     fi
     fi
 
 
-    pubi="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)"
+    pubi="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)"
     pubi=$(_math "$pubi" + 1)
     pubi=$(_math "$pubi" + 1)
     _debug3 pubi "$pubi"
     _debug3 pubi "$pubi"
 
 
-    pubj="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)"
+    pubj="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)"
     pubj=$(_math "$pubj" - 1)
     pubj=$(_math "$pubj" - 1)
     _debug3 pubj "$pubj"
     _debug3 pubj "$pubj"
 
 
-    pubtext="$($ACME_OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")"
+    pubtext="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")"
     _debug3 pubtext "$pubtext"
     _debug3 pubtext "$pubtext"
 
 
     xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)"
     xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)"
@@ -1469,7 +1491,9 @@ _inithttp() {
       _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
       _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
     fi
     fi
 
 
-    if [ "$CA_BUNDLE" ]; then
+    if [ "$CA_PATH" ]; then
+      _ACME_CURL="$_ACME_CURL --capath $CA_PATH "
+    elif [ "$CA_BUNDLE" ]; then
       _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
       _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE "
     fi
     fi
 
 
@@ -1480,8 +1504,10 @@ _inithttp() {
     if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
     if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
       _ACME_WGET="$_ACME_WGET -d "
       _ACME_WGET="$_ACME_WGET -d "
     fi
     fi
-    if [ "$CA_BUNDLE" ]; then
-      _ACME_WGET="$_ACME_WGET --ca-certificate $CA_BUNDLE "
+    if [ "$CA_PATH" ]; then
+      _ACME_WGET="$_ACME_WGET --ca-directory=$CA_PATH "
+    elif [ "$CA_BUNDLE" ]; then
+      _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE "
     fi
     fi
   fi
   fi
 
 
@@ -1828,6 +1854,24 @@ _saveaccountconf() {
   _save_conf "$ACCOUNT_CONF_PATH" "$1" "$2"
   _save_conf "$ACCOUNT_CONF_PATH" "$1" "$2"
 }
 }
 
 
+#key  value
+_saveaccountconf_mutable() {
+  _save_conf "$ACCOUNT_CONF_PATH" "SAVED_$1" "$2"
+  #remove later
+  _clearaccountconf "$1"
+}
+
+#key
+_readaccountconf() {
+  _read_conf "$ACCOUNT_CONF_PATH" "$1"
+}
+
+#key
+_readaccountconf_mutable() {
+  _rac_key="$1"
+  _readaccountconf "SAVED_$_rac_key"
+}
+
 #_clearaccountconf   key
 #_clearaccountconf   key
 _clearaccountconf() {
 _clearaccountconf() {
   _clear_conf "$ACCOUNT_CONF_PATH" "$1"
   _clear_conf "$ACCOUNT_CONF_PATH" "$1"
@@ -1999,7 +2043,7 @@ _starttlsserver() {
     return 1
     return 1
   fi
   fi
 
 
-  __S_OPENSSL="$ACME_OPENSSL_BIN s_server -cert $TLS_CERT  -key $TLS_KEY "
+  __S_OPENSSL="${ACME_OPENSSL_BIN:-openssl} s_server -cert $TLS_CERT  -key $TLS_KEY "
   if [ "$opaddr" ]; then
   if [ "$opaddr" ]; then
     __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port"
     __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port"
   else
   else
@@ -2240,16 +2284,16 @@ _initpath() {
   fi
   fi
 
 
   if [ -z "$TLS_CONF" ]; then
   if [ -z "$TLS_CONF" ]; then
-    TLS_CONF="$DOMAIN_PATH/tls.valdation.conf"
+    TLS_CONF="$DOMAIN_PATH/tls.validation.conf"
   fi
   fi
   if [ -z "$TLS_CERT" ]; then
   if [ -z "$TLS_CERT" ]; then
-    TLS_CERT="$DOMAIN_PATH/tls.valdation.cert"
+    TLS_CERT="$DOMAIN_PATH/tls.validation.cert"
   fi
   fi
   if [ -z "$TLS_KEY" ]; then
   if [ -z "$TLS_KEY" ]; then
-    TLS_KEY="$DOMAIN_PATH/tls.valdation.key"
+    TLS_KEY="$DOMAIN_PATH/tls.validation.key"
   fi
   fi
   if [ -z "$TLS_CSR" ]; then
   if [ -z "$TLS_CSR" ]; then
-    TLS_CSR="$DOMAIN_PATH/tls.valdation.csr"
+    TLS_CSR="$DOMAIN_PATH/tls.validation.csr"
   fi
   fi
 
 
 }
 }
@@ -2367,7 +2411,7 @@ _setApache() {
   _debug "Backup apache config file" "$httpdconf"
   _debug "Backup apache config file" "$httpdconf"
   if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then
   if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then
     _err "Can not backup apache config file, so abort. Don't worry, the apache config is not changed."
     _err "Can not backup apache config file, so abort. Don't worry, the apache config is not changed."
-    _err "This might be a bug of $PROJECT_NAME , pleae report issue: $PROJECT"
+    _err "This might be a bug of $PROJECT_NAME , please report issue: $PROJECT"
     return 1
     return 1
   fi
   fi
   _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname"
   _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname"
@@ -2509,7 +2553,7 @@ _setNginx() {
 location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
 location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" {
   default_type text/plain;
   default_type text/plain;
   return 200 \"\$1.$_thumbpt\";
   return 200 \"\$1.$_thumbpt\";
-}  
+}
 #NGINX_START
 #NGINX_START
 " >>"$FOUND_REAL_NGINX_CONF"
 " >>"$FOUND_REAL_NGINX_CONF"
 
 
@@ -2546,7 +2590,7 @@ _checkConf() {
   if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then
   if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then
     _debug "wildcard"
     _debug "wildcard"
     for _w_f in $2; do
     for _w_f in $2; do
-      if [ -f "$_w_f"] && _checkConf "$1" "$_w_f"; then
+      if [ -f "$_w_f" ] && _checkConf "$1" "$_w_f"; then
         return 0
         return 0
       fi
       fi
     done
     done
@@ -2580,10 +2624,10 @@ _checkConf() {
 _isRealNginxConf() {
 _isRealNginxConf() {
   _debug "_isRealNginxConf $1 $2"
   _debug "_isRealNginxConf $1 $2"
   if [ -f "$2" ]; then
   if [ -f "$2" ]; then
-    for _fln in $(grep -n "^ *server_name.* $1" "$2" | cut -d : -f 1); do
+    for _fln in $(tr "\t" ' ' <"$2" | grep -n "^ *server_name.* $1" | cut -d : -f 1); do
       _debug _fln "$_fln"
       _debug _fln "$_fln"
       if [ "$_fln" ]; then
       if [ "$_fln" ]; then
-        _start=$(cat "$2" | _head_n "$_fln" | grep -n "^ *server *{" | _tail_n 1)
+        _start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *{" | _tail_n 1)
         _debug "_start" "$_start"
         _debug "_start" "$_start"
         _start_n=$(echo "$_start" | cut -d : -f 1)
         _start_n=$(echo "$_start" | cut -d : -f 1)
         _start_nn=$(_math $_start_n + 1)
         _start_nn=$(_math $_start_n + 1)
@@ -2592,8 +2636,8 @@ _isRealNginxConf() {
 
 
         _left="$(sed -n "${_start_nn},99999p" "$2")"
         _left="$(sed -n "${_start_nn},99999p" "$2")"
         _debug2 _left "$_left"
         _debug2 _left "$_left"
-        if echo "$_left" | grep -n "^ *server *{" >/dev/null; then
-          _end=$(echo "$_left" | grep -n "^ *server *{" | _head_n 1)
+        if echo "$_left" | tr "\t" ' ' | grep -n "^ *server *{" >/dev/null; then
+          _end=$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *{" | _head_n 1)
           _debug "_end" "$_end"
           _debug "_end" "$_end"
           _end_n=$(echo "$_end" | cut -d : -f 1)
           _end_n=$(echo "$_end" | cut -d : -f 1)
           _debug "_end_n" "$_end_n"
           _debug "_end_n" "$_end_n"
@@ -2865,7 +2909,7 @@ _on_issue_err() {
         uri=$(echo "$ventry" | cut -d "$sep" -f 3)
         uri=$(echo "$ventry" | cut -d "$sep" -f 3)
         vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
         vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
         _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
         _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
-        __trigger_validaton "$uri" "$keyauthorization"
+        __trigger_validation "$uri" "$keyauthorization"
       done
       done
     )
     )
   fi
   fi
@@ -3087,7 +3131,7 @@ __get_domain_new_authz() {
 }
 }
 
 
 #uri keyAuthorization
 #uri keyAuthorization
-__trigger_validaton() {
+__trigger_validation() {
   _debug2 "tigger domain validation."
   _debug2 "tigger domain validation."
   _t_url="$1"
   _t_url="$1"
   _debug2 _t_url "$_t_url"
   _debug2 _t_url "$_t_url"
@@ -3096,12 +3140,16 @@ __trigger_validaton() {
   _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}"
   _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}"
 }
 }
 
 
-#webroot, domain domainlist  keylength 
+#webroot, domain domainlist  keylength
 issue() {
 issue() {
   if [ -z "$2" ]; then
   if [ -z "$2" ]; then
     _usage "Usage: $PROJECT_ENTRY --issue  -d  a.com  -w /path/to/webroot/a.com/ "
     _usage "Usage: $PROJECT_ENTRY --issue  -d  a.com  -w /path/to/webroot/a.com/ "
     return 1
     return 1
   fi
   fi
+  if [ -z "$1" ]; then
+    _usage "Please specify at least one validation method: '--webroot', '--standalone', '--apache', '--nginx' or '--dns' etc."
+    return 1
+  fi
   _web_roots="$1"
   _web_roots="$1"
   _main_domain="$2"
   _main_domain="$2"
   _alt_domains="$3"
   _alt_domains="$3"
@@ -3467,9 +3515,12 @@ issue() {
         if [ ! "$usingApache" ]; then
         if [ ! "$usingApache" ]; then
           if webroot_owner=$(_stat "$_currentRoot"); then
           if webroot_owner=$(_stat "$_currentRoot"); then
             _debug "Changing owner/group of .well-known to $webroot_owner"
             _debug "Changing owner/group of .well-known to $webroot_owner"
-            chown -R "$webroot_owner" "$_currentRoot/.well-known"
+            if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then
+              _debug "$(cat "$_EXEC_TEMP_ERR")"
+              _exec_err >/dev/null 2>&1
+            fi
           else
           else
-            _debug "not chaning owner/group of webroot"
+            _debug "not changing owner/group of webroot"
           fi
           fi
         fi
         fi
 
 
@@ -3510,7 +3561,7 @@ issue() {
       fi
       fi
     fi
     fi
 
 
-    if ! __trigger_validaton "$uri" "$keyauthorization"; then
+    if ! __trigger_validation "$uri" "$keyauthorization"; then
       _err "$d:Can not get challenge: $response"
       _err "$d:Can not get challenge: $response"
       _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
       _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
       _clearup
       _clearup
@@ -3614,6 +3665,7 @@ issue() {
 
 
   _rcert="$response"
   _rcert="$response"
   Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
   Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+  _debug "Le_LinkCert" "$Le_LinkCert"
   _savedomainconf "Le_LinkCert" "$Le_LinkCert"
   _savedomainconf "Le_LinkCert" "$Le_LinkCert"
 
 
   if [ "$Le_LinkCert" ]; then
   if [ "$Le_LinkCert" ]; then
@@ -3621,7 +3673,7 @@ issue() {
 
 
     #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
     #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
     #  _debug "Get cert failed. Let's try last response."
     #  _debug "Get cert failed. Let's try last response."
-    #  printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH" 
+    #  printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH"
     #fi
     #fi
 
 
     if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then
     if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then
@@ -3660,16 +3712,34 @@ issue() {
   if ! _contains "$Le_LinkIssuer" ":"; then
   if ! _contains "$Le_LinkIssuer" ":"; then
     Le_LinkIssuer="$API$Le_LinkIssuer"
     Le_LinkIssuer="$API$Le_LinkIssuer"
   fi
   fi
-
+  _debug Le_LinkIssuer "$Le_LinkIssuer"
   _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
   _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
 
 
   if [ "$Le_LinkIssuer" ]; then
   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 $(__green " $CA_CERT_PATH ")"
-    cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH"
-    _info "And the full chain certs is there: $(__green " $CERT_FULLCHAIN_PATH ")"
+    _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 _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"
+
+        _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 ")"
+
+        rm -f "$CA_CERT_PATH.der"
+        break
+      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
+  else
+    _debug "No Le_LinkIssuer header found."
   fi
   fi
 
 
   Le_CertCreateTime=$(_time)
   Le_CertCreateTime=$(_time)
@@ -3690,6 +3760,12 @@ issue() {
     _clearaccountconf "CA_BUNDLE"
     _clearaccountconf "CA_BUNDLE"
   fi
   fi
 
 
+  if [ "$CA_PATH" ]; then
+    _saveaccountconf CA_PATH "$CA_PATH"
+  else
+    _clearaccountconf "CA_PATH"
+  fi
+
   if [ "$HTTPS_INSECURE" ]; then
   if [ "$HTTPS_INSECURE" ]; then
     _saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE"
     _saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE"
   else
   else
@@ -3814,7 +3890,7 @@ renewAll() {
         return "$rc"
         return "$rc"
       else
       else
         _ret="$rc"
         _ret="$rc"
-        _err "Error renew $d, Go ahead to next one."
+        _err "Error renew $d."
       fi
       fi
     fi
     fi
   done
   done
@@ -4008,7 +4084,7 @@ deploy() {
 installcert() {
 installcert() {
   _main_domain="$1"
   _main_domain="$1"
   if [ -z "$_main_domain" ]; then
   if [ -z "$_main_domain" ]; then
-    _usage "Usage: $PROJECT_ENTRY --installcert -d domain.com  [--ecc] [--certpath cert-file-path]  [--keypath key-file-path]  [--capath ca-cert-file-path]   [ --reloadCmd reloadCmd] [--fullchainpath fullchain-path]"
+    _usage "Usage: $PROJECT_ENTRY --installcert -d domain.com  [--ecc] [--cert-file cert-file-path]  [--key-file key-file-path]  [--ca-file ca-cert-file-path]   [ --reloadCmd reloadCmd] [--fullchain-file fullchain-path]"
     return 1
     return 1
   fi
   fi
 
 
@@ -4107,6 +4183,7 @@ _installcert() {
       export CERT_KEY_PATH
       export CERT_KEY_PATH
       export CA_CERT_PATH
       export CA_CERT_PATH
       export CERT_FULLCHAIN_PATH
       export CERT_FULLCHAIN_PATH
+      export Le_Domain
       cd "$DOMAIN_PATH" && eval "$_reload_cmd"
       cd "$DOMAIN_PATH" && eval "$_reload_cmd"
     ); then
     ); then
       _info "$(__green "Reload success")"
       _info "$(__green "Reload success")"
@@ -4435,7 +4512,7 @@ _precheck() {
     fi
     fi
   fi
   fi
 
 
-  if ! _exists "$ACME_OPENSSL_BIN"; then
+  if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then
     _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN"
     _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN"
     _err "We need openssl to generate keys."
     _err "We need openssl to generate keys."
     return 1
     return 1
@@ -4618,7 +4695,7 @@ install() {
     #Modify shebang
     #Modify shebang
     if _exists bash; then
     if _exists bash; then
       _info "Good, bash is found, so change the shebang to use bash as preferred."
       _info "Good, bash is found, so change the shebang to use bash as preferred."
-      _shebang='#!/usr/bin/env bash'
+      _shebang='#!'"$(env bash -c "command -v bash")"
       _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang"
       _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang"
       for subf in $_SUB_FOLDERS; do
       for subf in $_SUB_FOLDERS; do
         if [ -d "$LE_WORKING_DIR/$subf" ]; then
         if [ -d "$LE_WORKING_DIR/$subf" ]; then
@@ -4677,6 +4754,7 @@ _uninstallalias() {
 cron() {
 cron() {
   IN_CRON=1
   IN_CRON=1
   _initpath
   _initpath
+  _info "$(__green "===Starting cron===")"
   if [ "$AUTO_UPGRADE" = "1" ]; then
   if [ "$AUTO_UPGRADE" = "1" ]; then
     export LE_WORKING_DIR
     export LE_WORKING_DIR
     (
     (
@@ -4696,6 +4774,7 @@ cron() {
   renewAll
   renewAll
   _ret="$?"
   _ret="$?"
   IN_CRON=""
   IN_CRON=""
+  _info "$(__green "===End cron===")"
   exit $_ret
   exit $_ret
 }
 }
 
 
@@ -4735,7 +4814,7 @@ Commands:
   --create-domain-key      Create an domain private key, professional use.
   --create-domain-key      Create an domain private key, professional use.
   --createCSR, -ccsr       Create CSR , professional use.
   --createCSR, -ccsr       Create CSR , professional use.
   --deactivate             Deactivate the domain authz, professional use.
   --deactivate             Deactivate the domain authz, professional use.
-  
+
 Parameters:
 Parameters:
   --domain, -d   domain.tld         Specifies a domain, used to issue, renew or revoke etc.
   --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.
   --force, -f                       Used to force to install or force to renew a cert immediately.
@@ -4749,20 +4828,20 @@ Parameters:
   --apache                          Use apache mode.
   --apache                          Use apache mode.
   --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file]   Use dns mode or dns api.
   --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file]   Use dns mode or dns api.
   --dnssleep  [$DEFAULT_DNS_SLEEP]                  The time in seconds to wait for all the txt records to take effect in dns api mode. Default $DEFAULT_DNS_SLEEP seconds.
   --dnssleep  [$DEFAULT_DNS_SLEEP]                  The time in seconds to wait for all the txt records to take effect in dns api mode. Default $DEFAULT_DNS_SLEEP seconds.
-  
+
   --keylength, -k [2048]            Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384.
   --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.
   --accountkeylength, -ak [2048]    Specifies the account key length.
   --log    [/path/to/logfile]       Specifies the log file. The default is: \"$DEFAULT_LOG_FILE\" if you don't give a file path here.
   --log    [/path/to/logfile]       Specifies the log file. The default is: \"$DEFAULT_LOG_FILE\" if you don't give a file path here.
   --log-level 1|2                   Specifies the log level, default is 1.
   --log-level 1|2                   Specifies the log level, default is 1.
   --syslog [0|3|6|7]                Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
   --syslog [0|3|6|7]                Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
-  
+
   These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert:
   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.
-  
+
+  --cert-file                       After issue/renew, the cert will be copied to this path.
+  --key-file                        After issue/renew, the key will be copied to this path.
+  --ca-file                         After issue/renew, the intermediate cert will be copied to this path.
+  --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.
   --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
 
 
   --accountconf                     Specifies a customized account config file.
   --accountconf                     Specifies a customized account config file.
@@ -4779,12 +4858,13 @@ Parameters:
   --listraw                         Only used for '--list' command, list the certs in raw format.
   --listraw                         Only used for '--list' command, list the certs in raw format.
   --stopRenewOnError, -se           Only valid for '--renew-all' command. Stop if one cert has error in renewal.
   --stopRenewOnError, -se           Only valid for '--renew-all' command. Stop if one cert has error in renewal.
   --insecure                        Do not check the server certificate, in some devices, the api server's certificate may not be trusted.
   --insecure                        Do not check the server certificate, in some devices, the api server's certificate may not be trusted.
-  --ca-bundle                       Specifices the path to the CA certificate bundle to verify api server's certificate.
+  --ca-bundle                       Specifies the path to the CA certificate bundle to verify api server's certificate.
+  --ca-path                         Specifies directory containing CA certificates in PEM format, used by wget or curl.
   --nocron                          Only valid for '--install' command, which means: do not install the default cron job. In this case, the certs will not be renewed automatically.
   --nocron                          Only valid for '--install' command, which means: do not install the default cron job. In this case, the certs will not be renewed automatically.
   --ecc                             Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
   --ecc                             Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
   --csr                             Specifies the input csr.
   --csr                             Specifies the input csr.
   --pre-hook                        Command to be run before obtaining any certificates.
   --pre-hook                        Command to be run before obtaining any certificates.
-  --post-hook                       Command to be run after attempting to obtain/renew certificates. No matter the obain/renew is success or failed.
+  --post-hook                       Command to be run after attempting to obtain/renew certificates. No matter the obtain/renew is success or failed.
   --renew-hook                      Command to be run once for each successfully renewed certificate.
   --renew-hook                      Command to be run once for each successfully renewed certificate.
   --deploy-hook                     The hook file to deploy cert
   --deploy-hook                     The hook file to deploy cert
   --ocsp-must-staple, --ocsp        Generate ocsp must Staple extension.
   --ocsp-must-staple, --ocsp        Generate ocsp must Staple extension.
@@ -4886,10 +4966,10 @@ _process() {
   _webroot=""
   _webroot=""
   _keylength=""
   _keylength=""
   _accountkeylength=""
   _accountkeylength=""
-  _certpath=""
-  _keypath=""
-  _capath=""
-  _fullchainpath=""
+  _cert_file=""
+  _key_file=""
+  _ca_file=""
+  _fullchain_file=""
   _reloadcmd=""
   _reloadcmd=""
   _password=""
   _password=""
   _accountconf=""
   _accountconf=""
@@ -4905,6 +4985,7 @@ _process() {
   _stopRenewOnError=""
   _stopRenewOnError=""
   #_insecure=""
   #_insecure=""
   _ca_bundle=""
   _ca_bundle=""
+  _ca_path=""
   _nocron=""
   _nocron=""
   _ecc=""
   _ecc=""
   _csr=""
   _csr=""
@@ -5130,20 +5211,20 @@ _process() {
         shift
         shift
         ;;
         ;;
 
 
-      --certpath)
-        _certpath="$2"
+      --cert-file | --certpath)
+        _cert_file="$2"
         shift
         shift
         ;;
         ;;
-      --keypath)
-        _keypath="$2"
+      --key-file | --keypath)
+        _key_file="$2"
         shift
         shift
         ;;
         ;;
-      --capath)
-        _capath="$2"
+      --ca-file | --capath)
+        _ca_file="$2"
         shift
         shift
         ;;
         ;;
-      --fullchainpath)
-        _fullchainpath="$2"
+      --fullchain-file | --fullchainpath)
+        _fullchain_file="$2"
         shift
         shift
         ;;
         ;;
       --reloadcmd | --reloadCmd)
       --reloadcmd | --reloadCmd)
@@ -5219,6 +5300,11 @@ _process() {
         CA_BUNDLE="$_ca_bundle"
         CA_BUNDLE="$_ca_bundle"
         shift
         shift
         ;;
         ;;
+      --ca-path)
+        _ca_path="$2"
+        CA_PATH="$_ca_path"
+        shift
+        ;;
       --nocron)
       --nocron)
         _nocron="1"
         _nocron="1"
         ;;
         ;;
@@ -5360,7 +5446,7 @@ _process() {
     uninstall) uninstall "$_nocron" ;;
     uninstall) uninstall "$_nocron" ;;
     upgrade) upgrade ;;
     upgrade) upgrade ;;
     issue)
     issue)
-      issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath" "$_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"
       ;;
       ;;
     deploy)
     deploy)
       deploy "$_domain" "$_deploy_hook" "$_ecc"
       deploy "$_domain" "$_deploy_hook" "$_ecc"
@@ -5372,7 +5458,7 @@ _process() {
       showcsr "$_csr" "$_domain"
       showcsr "$_csr" "$_domain"
       ;;
       ;;
     installcert)
     installcert)
-      installcert "$_domain" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath" "$_ecc"
+      installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc"
       ;;
       ;;
     renew)
     renew)
       renew "$_domain" "$_ecc"
       renew "$_domain" "$_ecc"

+ 4 - 1
deploy/README.md

@@ -21,8 +21,11 @@ acme.sh --deploy -d example.com --deploy-hook cpanel
 ## 2. Deploy ssl cert on kong proxy engine based on api.
 ## 2. Deploy ssl cert on kong proxy engine based on api.
 
 
 Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert).
 Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert).
+Currently supports Kong-v0.10.x.
 
 
-(TODO)
+```sh
+acme.sh --deploy -d ftp.example.com --deploy-hook kong
+```
 
 
 ## 3. Deploy the cert to remote server through SSH access.
 ## 3. Deploy the cert to remote server through SSH access.
 
 

+ 2 - 2
deploy/exim4.sh

@@ -79,7 +79,7 @@ exim4_deploy() {
         _info "Restore conf success"
         _info "Restore conf success"
         eval "$_reload"
         eval "$_reload"
       else
       else
-        _err "Opps, error restore exim4 conf, please report bug to us."
+        _err "Oops, error restore exim4 conf, please report bug to us."
       fi
       fi
       return 1
       return 1
     fi
     fi
@@ -105,7 +105,7 @@ exim4_deploy() {
       _info "Restore conf success"
       _info "Restore conf success"
       eval "$_reload"
       eval "$_reload"
     else
     else
-      _err "Opps, error restore exim4 conf, please report bug to us."
+      _err "Oops, error restore exim4 conf, please report bug to us."
     fi
     fi
     return 1
     return 1
   fi
   fi

+ 25 - 29
deploy/kong.sh

@@ -1,13 +1,7 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
-
-# This deploy hook will deploy ssl cert on kong proxy engine based on api request_host parameter.
-# Note that ssl plugin should be available on Kong instance
-# The hook will match cdomain to request_host, in case of multiple domain it will always take the first
-# one (acme.sh behaviour).
-# If ssl config already exist it will update only cert and key not touching other parameter
-# If ssl config doesn't exist it will only upload cert and key and not set other parameter
-# Not that we deploy full chain
-# See https://getkong.org/plugins/dynamic-ssl/ for other options
+# If certificate already exist it will update only cert and key not touching other parameter
+# If certificate  doesn't exist it will only upload cert and key and not set other parameter
+# Note that we deploy full chain
 # Written by Geoffroi Genot <ggenot@voxbone.com>
 # Written by Geoffroi Genot <ggenot@voxbone.com>
 
 
 ########  Public functions #####################
 ########  Public functions #####################
@@ -31,29 +25,32 @@ kong_deploy() {
   _debug _cca "$_cca"
   _debug _cca "$_cca"
   _debug _cfullchain "$_cfullchain"
   _debug _cfullchain "$_cfullchain"
 
 
-  #Get uuid linked to the domain
-  uuid=$(_get "$KONG_URL/apis?request_host=$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
-  if [ -z "$uuid" ]; then
-    _err "Unable to get Kong uuid for domain $_cdomain"
-    _err "Make sure that KONG_URL is correctly configured"
-    _err "Make sure that a Kong api request_host match the domain"
-    _err "Kong url: $KONG_URL"
-    return 1
+  #Get ssl_uuid linked to the domain
+  ssl_uuid=$(_get "$KONG_URL/certificates/$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
+  if [ -z "$ssl_uuid" ]; then
+    _debug "Unable to get Kong ssl_uuid for domain $_cdomain"
+    _debug "Make sure that KONG_URL is correctly configured"
+    _debug "Make sure that a Kong certificate match the sni"
+    _debug "Kong url: $KONG_URL"
+    _info "No existing certificate, creating..."
+    #return 1
   fi
   fi
   #Save kong url if it's succesful (First run case)
   #Save kong url if it's succesful (First run case)
   _saveaccountconf KONG_URL "$KONG_URL"
   _saveaccountconf KONG_URL "$KONG_URL"
   #Generate DEIM
   #Generate DEIM
-  delim="-----MultipartDelimeter$(date "+%s%N")"
+  delim="-----MultipartDelimiter$(date "+%s%N")"
   nl="\015\012"
   nl="\015\012"
   #Set Header
   #Set Header
   _H1="Content-Type: multipart/form-data; boundary=$delim"
   _H1="Content-Type: multipart/form-data; boundary=$delim"
   #Generate data for request (Multipart/form-data with mixed content)
   #Generate data for request (Multipart/form-data with mixed content)
-  #set name to ssl
-  content="--$delim${nl}Content-Disposition: form-data; name=\"name\"${nl}${nl}ssl"
+  if [ -z "$ssl_uuid" ]; then
+    #set sni to domain
+    content="--$delim${nl}Content-Disposition: form-data; name=\"snis\"${nl}${nl}$_cdomain"
+  fi
   #add key
   #add key
-  content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
+  content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
   #Add cert
   #Add cert
-  content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
+  content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
   #Close multipart
   #Close multipart
   content="$content${nl}--$delim--${nl}"
   content="$content${nl}--$delim--${nl}"
   #Convert CRLF
   #Convert CRLF
@@ -61,18 +58,17 @@ kong_deploy() {
   #DEBUG
   #DEBUG
   _debug header "$_H1"
   _debug header "$_H1"
   _debug content "$content"
   _debug content "$content"
-  #Check if ssl plugins is aready enabled (if not => POST else => PATCH)
-  ssl_uuid=$(_get "$KONG_URL/apis/$uuid/plugins" | _egrep_o '"id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"[a-zA-Z0-9\-\,\"_\:]*"name":"ssl"' | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')
-  _debug ssl_uuid "$ssl_uuid"
+  #Check if sslcreated (if not => POST else => PATCH)
+
   if [ -z "$ssl_uuid" ]; then
   if [ -z "$ssl_uuid" ]; then
     #Post certificate to Kong
     #Post certificate to Kong
-    response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST")
+    response=$(_post "$content" "$KONG_URL/certificates" "" "POST")
   else
   else
     #patch
     #patch
-    response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH")
+    response=$(_post "$content" "$KONG_URL/certificates/$ssl_uuid" "" "PATCH")
   fi
   fi
-  if ! [ "$(echo "$response" | _egrep_o "ssl")" = "ssl" ]; then
-    _err "An error occured with cert upload. Check response:"
+  if ! [ "$(echo "$response" | _egrep_o "created_at")" = "created_at" ]; then
+    _err "An error occurred with cert upload. Check response:"
     _err "$response"
     _err "$response"
     return 1
     return 1
   fi
   fi

+ 2 - 2
deploy/vsftpd.sh

@@ -76,7 +76,7 @@ vsftpd_deploy() {
         _info "Restore conf success"
         _info "Restore conf success"
         eval "$_reload"
         eval "$_reload"
       else
       else
-        _err "Opps, error restore vsftpd conf, please report bug to us."
+        _err "Oops, error restore vsftpd conf, please report bug to us."
       fi
       fi
       return 1
       return 1
     fi
     fi
@@ -102,7 +102,7 @@ vsftpd_deploy() {
       _info "Restore conf success"
       _info "Restore conf success"
       eval "$_reload"
       eval "$_reload"
     else
     else
-      _err "Opps, error restore vsftpd conf, please report bug to us."
+      _err "Oops, error restore vsftpd conf, please report bug to us."
     fi
     fi
     return 1
     return 1
   fi
   fi

+ 104 - 2
dnsapi/README.md

@@ -302,7 +302,7 @@ acme.sh --issue --dns dns_freedns -d example.com -d www.example.com
 ```
 ```
 
 
 Note that you cannot use acme.sh automatic DNS validation for FreeDNS public domains or for a subdomain that
 Note that you cannot use acme.sh automatic DNS validation for FreeDNS public domains or for a subdomain that
-you create under a FreeDNS public domain.  You must own the top level domain in order to automaitcally
+you create under a FreeDNS public domain.  You must own the top level domain in order to automatically
 validate with acme.sh at FreeDNS.
 validate with acme.sh at FreeDNS.
 
 
 ## 16. Use cyon.ch
 ## 16. Use cyon.ch
@@ -394,7 +394,8 @@ acme.sh --issue --dns dns_knot -d example.com -d www.example.com
 
 
 The `KNOT_SERVER` and `KNOT_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 The `KNOT_SERVER` and `KNOT_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
-## 20. Use NS1. API
+
+## 20. Use NS1.com API
 
 
 ```
 ```
 export NS1_Key="fdmlfsdklmfdkmqsdfk"
 export NS1_Key="fdmlfsdklmfdkmqsdfk"
@@ -405,6 +406,107 @@ Ok, let's issue a cert now:
 acme.sh --issue --dns dns_nsone -d example.com -d www.example.com
 acme.sh --issue --dns dns_nsone -d example.com -d www.example.com
 ```
 ```
 
 
+## 20. Use DigitalOcean API (native)
+
+You need to obtain a read and write capable API key from your DigitalOcean account. See: https://www.digitalocean.com/help/api/
+
+```
+export DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_dgon -d example.com -d www.example.com
+```
+
+## 21. Use ClouDNS.net API
+
+You need to set the HTTP API user ID and password credentials. See: https://www.cloudns.net/wiki/article/42/
+
+```
+export CLOUDNS_AUTH_ID=XXXXX
+export CLOUDNS_AUTH_PASSWORD="YYYYYYYYY"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com
+```
+
+## 22. Use Infoblox API
+
+First you need to create/obtain API credentials on your Infoblox appliance.
+
+```
+export Infoblox_Creds="username:password"
+export Infoblox_Server="ip or fqdn of infoblox appliance"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_infoblox -d example.com -d www.example.com
+```
+
+Note: This script will automatically create and delete the ephemeral txt record.
+The `Infoblox_Creds` and `Infoblox_Server` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+
+## 23. Use VSCALE API
+
+First you need to create/obtain API tokens on your [settings panel](https://vscale.io/panel/settings/tokens/).
+
+```
+VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_vscale -d example.com -d www.example.com
+```
+
+##  24. Use Dynu API
+
+First you need to create/obtain API credentials from your Dynu account. See: https://www.dynu.com/resources/api/documentation
+
+```
+export Dynu_ClientId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+export Dynu_Secret="yyyyyyyyyyyyyyyyyyyyyyyyy"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_dynu -d example.com -d www.example.com
+```
+
+The `Dynu_ClientId` and `Dynu_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 25. Use DNSimple API
+
+First you need to login to your DNSimple account and generate a new oauth token.
+
+https://dnsimple.com/a/{your account id}/account/access_tokens
+
+Note that this is an _account_ token and not a user token. The account token is
+needed to infer the `account_id` used in requests. A user token will not be able
+to determine the correct account to use.
+
+```
+export DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje"
+```
+
+To issue the cert just specify the `dns_dnsimple` API.
+
+```
+acme.sh --issue --dns dns_dnsimple -d example.com
+```
+
+The `DNSimple_OAUTH_TOKEN` will be saved in `~/.acme.sh/account.conf` and will
+be reused when needed.
+
+If you have any issues with this integration please report them to
+https://github.com/pho3nixf1re/acme.sh/issues.
+
+
 # Use custom API
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS API.
 If your API is not supported yet, you can write your own DNS API.

+ 20 - 4
dnsapi/dns_aws.sh

@@ -88,12 +88,25 @@ _get_root() {
     while true; do
     while true; do
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       if [ -z "$h" ]; then
       if [ -z "$h" ]; then
+        if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then
+          _debug "IsTruncated"
+          _nextMarker="$(echo "$response" | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1)"
+          _debug "NextMarker" "$_nextMarker"
+          if aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"; then
+            _debug "Truncated request OK"
+            i=2
+            p=1
+            continue
+          else
+            _err "Truncated request error."
+          fi
+        fi
         #not valid
         #not valid
         return 1
         return 1
       fi
       fi
 
 
       if _contains "$response" "<Name>$h.</Name>"; then
       if _contains "$response" "<Name>$h.</Name>"; then
-        hostedzone="$(echo "$response" | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<.HostedZone>")"
+        hostedzone="$(echo "$response" | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>")"
         _debug hostedzone "$hostedzone"
         _debug hostedzone "$hostedzone"
         if [ -z "$hostedzone" ]; then
         if [ -z "$hostedzone" ]; then
           _err "Error, can not get hostedzone."
           _err "Error, can not get hostedzone."
@@ -143,7 +156,7 @@ aws_rest() {
   CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n"
   CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n"
   SignedHeaders="host;x-amz-date"
   SignedHeaders="host;x-amz-date"
   if [ -n "$AWS_SESSION_TOKEN" ]; then
   if [ -n "$AWS_SESSION_TOKEN" ]; then
-    export _H2="x-amz-security-token: $AWS_SESSION_TOKEN"
+    export _H3="x-amz-security-token: $AWS_SESSION_TOKEN"
     CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n"
     CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n"
     SignedHeaders="${SignedHeaders};x-amz-security-token"
     SignedHeaders="${SignedHeaders};x-amz-security-token"
   fi
   fi
@@ -204,10 +217,13 @@ aws_rest() {
   Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
   Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
   _debug2 Authorization "$Authorization"
   _debug2 Authorization "$Authorization"
 
 
-  _H3="Authorization: $Authorization"
-  _debug _H3 "$_H3"
+  _H2="Authorization: $Authorization"
+  _debug _H2 "$_H2"
 
 
   url="$AWS_URL/$ep"
   url="$AWS_URL/$ep"
+  if [ "$qsr" ]; then
+    url="$AWS_URL/$ep?$qsr"
+  fi
 
 
   if [ "$mtd" = "GET" ]; then
   if [ "$mtd" = "GET" ]; then
     response="$(_get "$url")"
     response="$(_get "$url")"

+ 15 - 2
dnsapi/dns_cf.sh

@@ -14,6 +14,8 @@ dns_cf_add() {
   fulldomain=$1
   fulldomain=$1
   txtvalue=$2
   txtvalue=$2
 
 
+  CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
+  CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}"
   if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
   if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
     CF_Key=""
     CF_Key=""
     CF_Email=""
     CF_Email=""
@@ -29,8 +31,8 @@ dns_cf_add() {
   fi
   fi
 
 
   #save the api key and email to the account conf file.
   #save the api key and email to the account conf file.
-  _saveaccountconf CF_Key "$CF_Key"
-  _saveaccountconf CF_Email "$CF_Email"
+  _saveaccountconf_mutable CF_Key "$CF_Key"
+  _saveaccountconf_mutable CF_Email "$CF_Email"
 
 
   _debug "First detect the root zone"
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
   if ! _get_root "$fulldomain"; then
@@ -83,6 +85,17 @@ dns_cf_add() {
 dns_cf_rm() {
 dns_cf_rm() {
   fulldomain=$1
   fulldomain=$1
   txtvalue=$2
   txtvalue=$2
+
+  CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
+  CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}"
+  if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
+    CF_Key=""
+    CF_Email=""
+    _err "You don't specify cloudflare api key and email yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
   _debug "First detect the root zone"
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain"; then
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"
     _err "invalid domain"

+ 170 - 0
dnsapi/dns_cloudns.sh

@@ -0,0 +1,170 @@
+#!/usr/bin/env sh
+
+# Author: Boyan Peychev <boyan at cloudns dot net>
+# Repository: https://github.com/ClouDNS/acme.sh/
+
+#CLOUDNS_AUTH_ID=XXXXX
+#CLOUDNS_AUTH_PASSWORD="YYYYYYYYY"
+CLOUDNS_API="https://api.cloudns.net"
+
+########  Public functions #####################
+
+#Usage: dns_cloudns_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_cloudns_add() {
+  _info "Using cloudns"
+
+  if ! _dns_cloudns_init_check; then
+    return 1
+  fi
+
+  zone="$(_dns_cloudns_get_zone_name "$1")"
+  if [ -z "$zone" ]; then
+    _err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup."
+    return 1
+  fi
+
+  host="$(echo "$1" | sed "s/\.$zone\$//")"
+  record=$2
+  record_id=$(_dns_cloudns_get_record_id "$zone" "$host")
+
+  _debug zone "$zone"
+  _debug host "$host"
+  _debug record "$record"
+  _debug record_id "$record_id"
+
+  if [ -z "$record_id" ]; then
+    _info "Adding the TXT record for $1"
+    _dns_cloudns_http_api_call "dns/add-record.json" "domain-name=$zone&record-type=TXT&host=$host&record=$record&ttl=60"
+    if ! _contains "$response" "\"status\":\"Success\""; then
+      _err "Record cannot be added."
+      return 1
+    fi
+    _info "Added."
+  else
+    _info "Updating the TXT record for $1"
+    _dns_cloudns_http_api_call "dns/mod-record.json" "domain-name=$zone&record-id=$record_id&record-type=TXT&host=$host&record=$record&ttl=60"
+    if ! _contains "$response" "\"status\":\"Success\""; then
+      _err "The TXT record for $1 cannot be updated."
+      return 1
+    fi
+    _info "Updated."
+  fi
+
+  return 0
+}
+
+#Usage: dns_cloudns_rm   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_cloudns_rm() {
+  _info "Using cloudns"
+
+  if ! _dns_cloudns_init_check; then
+    return 1
+  fi
+
+  if [ -z "$zone" ]; then
+    zone="$(_dns_cloudns_get_zone_name "$1")"
+    if [ -z "$zone" ]; then
+      _err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup."
+      return 1
+    fi
+  fi
+
+  host="$(echo "$1" | sed "s/\.$zone\$//")"
+  record=$2
+  record_id=$(_dns_cloudns_get_record_id "$zone" "$host")
+
+  _debug zone "$zone"
+  _debug host "$host"
+  _debug record "$record"
+  _debug record_id "$record_id"
+
+  if [ ! -z "$record_id" ]; then
+    _info "Deleting the TXT record for $1"
+    _dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id"
+    if ! _contains "$response" "\"status\":\"Success\""; then
+      _err "The TXT record for $1 cannot be deleted."
+      return 1
+    fi
+    _info "Deleted."
+  fi
+  return 0
+}
+
+####################  Private functions below ##################################
+_dns_cloudns_init_check() {
+  if [ ! -z "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then
+    return 0
+  fi
+
+  if [ -z "$CLOUDNS_AUTH_ID" ]; then
+    _err "CLOUDNS_AUTH_ID is not configured"
+    return 1
+  fi
+
+  if [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then
+    _err "CLOUDNS_AUTH_PASSWORD is not configured"
+    return 1
+  fi
+
+  _dns_cloudns_http_api_call "dns/login.json" ""
+
+  if ! _contains "$response" "\"status\":\"Success\""; then
+    _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials."
+    return 1
+  fi
+
+  CLOUDNS_INIT_CHECK_COMPLETED=1
+
+  return 0
+}
+
+_dns_cloudns_get_zone_name() {
+  i=2
+  while true; do
+    zoneForCheck=$(printf "%s" "$1" | cut -d . -f $i-100)
+
+    if [ -z "$zoneForCheck" ]; then
+      return 1
+    fi
+
+    _debug zoneForCheck "$zoneForCheck"
+
+    _dns_cloudns_http_api_call "dns/get-zone-info.json" "domain-name=$zoneForCheck"
+
+    if ! _contains "$response" "\"status\":\"Failed\""; then
+      echo "$zoneForCheck"
+      return 0
+    fi
+
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_dns_cloudns_get_record_id() {
+  _dns_cloudns_http_api_call "dns/records.json" "domain-name=$1&host=$2&type=TXT"
+  if _contains "$response" "\"id\":"; then
+    echo "$response" | cut -d '"' -f 2
+    return 0
+  fi
+  return 1
+}
+
+_dns_cloudns_http_api_call() {
+  method=$1
+
+  _debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID"
+  _debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD"
+
+  if [ -z "$2" ]; then
+    data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD"
+  else
+    data="auth-id=$CLOUDNS_AUTH_ID&auth-password=$CLOUDNS_AUTH_PASSWORD&$2"
+  fi
+
+  response="$(_get "$CLOUDNS_API/$method?$data")"
+
+  _debug2 response "$response"
+
+  return 0
+}

+ 3 - 4
dnsapi/dns_cx.sh

@@ -209,8 +209,7 @@ _rest() {
     return 1
     return 1
   fi
   fi
   _debug2 response "$response"
   _debug2 response "$response"
-  if ! _contains "$response" '"message":"success"'; then
-    return 1
-  fi
-  return 0
+
+  _contains "$response" '"code":1'
+
 }
 }

+ 1 - 1
dnsapi/dns_cyon.sh

@@ -50,7 +50,7 @@ _cyon_load_credentials() {
   fi
   fi
 
 
   if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then
   if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then
-    # Dummy entries to satify script checker.
+    # Dummy entries to satisfy script checker.
     CY_Username=""
     CY_Username=""
     CY_Password=""
     CY_Password=""
     CY_OTP_Secret=""
     CY_OTP_Secret=""

+ 205 - 0
dnsapi/dns_dgon.sh

@@ -0,0 +1,205 @@
+#!/usr/bin/env sh
+
+## Will be called by acme.sh to add the txt record to your api system.
+## returns 0 means success, otherwise error.
+
+## Author: thewer <github at thewer.com>
+## GitHub: https://github.com/gitwer/acme.sh
+
+##
+## Environment Variables Required:
+##
+## DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc"
+##
+
+#####################  Public functions  #####################
+
+## Create the text record for validation.
+## Usage: fulldomain txtvalue
+## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs"
+dns_dgon_add() {
+  fulldomain="$(echo "$1" | _lower_case)"
+  txtvalue=$2
+  _info "Using digitalocean dns validation - add record"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  ## save the env vars (key and domain split location) for later automated use
+  _saveaccountconf DO_API_KEY "$DO_API_KEY"
+
+  ## split the domain for DO API
+  if ! _get_base_domain "$fulldomain"; then
+    _err "domain not found in your account for addition"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  ## Set the header with our post type and key auth key
+  export _H1="Content-Type: application/json"
+  export _H2="Authorization: Bearer $DO_API_KEY"
+  PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records'
+  PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'"}'
+
+  _debug PURL "$PURL"
+  _debug PBODY "$PBODY"
+
+  ## the create request - post
+  ## args: BODY, URL, [need64, httpmethod]
+  response="$(_post "$PBODY" "$PURL")"
+
+  ## check response
+  if [ "$?" != "0" ]; then
+    _err "error in response: $response"
+    return 1
+  fi
+  _debug2 response "$response"
+
+  ## finished correctly
+  return 0
+}
+
+## Remove the txt record after validation.
+## Usage: fulldomain txtvalue
+## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs"
+dns_dgon_rm() {
+  fulldomain="$(echo "$1" | _lower_case)"
+  txtvalue=$2
+  _info "Using digitalocean dns validation - remove record"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  ## split the domain for DO API
+  if ! _get_base_domain "$fulldomain"; then
+    _err "domain not found in your account for removal"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  ## Set the header with our post type and key auth key
+  export _H1="Content-Type: application/json"
+  export _H2="Authorization: Bearer $DO_API_KEY"
+  ## get URL for the list of domains
+  ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}}
+  GURL="https://api.digitalocean.com/v2/domains/$_domain/records"
+
+  ## while we dont have a record ID we keep going
+  while [ -z "$record" ]; do
+    ## 1) get the URL
+    ## the create request - get
+    ## args: URL, [onlyheader, timeout]
+    domain_list="$(_get "$GURL")"
+    ## 2) find record
+    ## check for what we are looing for: "type":"A","name":"$_sub_domain"
+    record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*\d+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")"
+    ## 3) check record and get next page
+    if [ -z "$record" ]; then
+      ## find the next page if we dont have a match
+      nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=\d+")"
+      if [ -z "$nextpage" ]; then
+        _err "no record and no nextpage in digital ocean DNS removal"
+        return 1
+      fi
+      _debug2 nextpage "$nextpage"
+      GURL="$nextpage"
+    fi
+    ## we break out of the loop when we have a record
+  done
+
+  ## we found the record
+  rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*\d+" | _egrep_o "\d+")"
+  _debug rec_id "$rec_id"
+
+  ## delete the record
+  ## delete URL for removing the one we dont want
+  DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id"
+
+  ## the create request - delete
+  ## args: BODY, URL, [need64, httpmethod]
+  response="$(_post "" "$DURL" "" "DELETE")"
+
+  ## check response (sort of)
+  if [ "$?" != "0" ]; then
+    _err "error in remove response: $response"
+    return 1
+  fi
+  _debug2 response "$response"
+
+  ## finished correctly
+  return 0
+}
+
+#####################  Private functions below  #####################
+
+## Split the domain provided into the "bade domain" and the "start prefix".
+## This function searches for the longest subdomain in your account
+## for the full domain given and splits it into the base domain (zone)
+## and the prefix/record to be added/removed
+## USAGE: fulldomain
+## EG: "_acme-challenge.two.three.four.domain.com"
+## returns
+## _sub_domain="_acme-challenge.two"
+## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists
+## if only "domain.com" exists it will return
+## _sub_domain="_acme-challenge.two.three.four"
+## _domain="domain.com"
+_get_base_domain() {
+  # args
+  fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
+  _debug fulldomain "$fulldomain"
+
+  # domain max legal length = 253
+  MAX_DOM=255
+
+  ## get a list of domains for the account to check thru
+  ## Set the headers
+  export _H1="Content-Type: application/json"
+  export _H2="Authorization: Bearer $DO_API_KEY"
+  _debug DO_API_KEY "$DO_API_KEY"
+  ## get URL for the list of domains
+  ## havent seen this request paginated, tested with 18 domains (more requires manual requests with DO)
+  DOMURL="https://api.digitalocean.com/v2/domains"
+
+  ## get the domain list (DO gives basically a full XFER!)
+  domain_list="$(_get "$DOMURL")"
+
+  ## check response
+  if [ "$?" != "0" ]; then
+    _err "error in domain_list response: $domain_list"
+    return 1
+  fi
+  _debug2 domain_list "$domain_list"
+
+  ## for each shortening of our $fulldomain, check if it exists in the $domain_list
+  ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge"
+  i=2
+  while [ $i -gt 0 ]; do
+    ## get next longest domain
+    _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM")
+    ## check we got something back from our cut (or are we at the end)
+    if [ -z "$_domain" ]; then
+      ## we got to the end of the domain - invalid domain
+      _err "domain not found in DigitalOcean account"
+      return 1
+    fi
+    ## we got part of a domain back - grep it out
+    found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")"
+    ## check if it exists
+    if [ ! -z "$found" ]; then
+      ## exists - exit loop returning the parts
+      sub_point=$(_math $i - 1)
+      _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
+      _debug _domain "$_domain"
+      _debug _sub_domain "$_sub_domain"
+      return 0
+    fi
+    ## increment cut point $i
+    i=$(_math $i + 1)
+  done
+
+  ## we went through the entire domain zone list and dint find one that matched
+  ## doesnt look like we can add in the record
+  _err "domain not found in DigitalOcean account, but we should never get here"
+  return 1
+}

+ 215 - 0
dnsapi/dns_dnsimple.sh

@@ -0,0 +1,215 @@
+#!/usr/bin/env sh
+
+# DNSimple domain api
+# https://github.com/pho3nixf1re/acme.sh/issues
+#
+# This is your oauth token which can be acquired on the account page. Please
+# note that this must be an _account_ token and not a _user_ token.
+# https://dnsimple.com/a/<your account id>/account/access_tokens
+# DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+DNSimple_API="https://api.dnsimple.com/v2"
+
+########  Public functions #####################
+
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dnsimple_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$DNSimple_OAUTH_TOKEN" ]; then
+    DNSimple_OAUTH_TOKEN=""
+    _err "You have not set the dnsimple oauth token yet."
+    _err "Please visit https://dnsimple.com/user to generate it."
+    return 1
+  fi
+
+  # save the oauth token for later
+  _saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN"
+
+  if ! _get_account_id; then
+    _err "failed to retrive account id"
+    return 1
+  fi
+
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _get_records "$_account_id" "$_domain" "$_sub_domain"
+
+  if [ "$_records_count" = "0" ]; then
+    _info "Adding record"
+    if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+      if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then
+        _info "Added"
+        return 0
+      else
+        _err "Unexpected response while adding text record."
+        return 1
+      fi
+    fi
+    _err "Add txt record error."
+  else
+    _info "Updating record"
+    _extract_record_id "$_records" "$_sub_domain"
+
+    if _dnsimple_rest \
+      PATCH \
+      "$_account_id/zones/$_domain/records/$_record_id" \
+      "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+
+      _info "Updated!"
+      return 0
+    fi
+
+    _err "Update error"
+    return 1
+  fi
+}
+
+# fulldomain
+dns_dnsimple_rm() {
+  fulldomain=$1
+
+  if ! _get_account_id; then
+    _err "failed to retrive account id"
+    return 1
+  fi
+
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _get_records "$_account_id" "$_domain" "$_sub_domain"
+  _extract_record_id "$_records" "$_sub_domain"
+
+  if [ "$_record_id" ]; then
+
+    if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then
+      _info "removed record" "$_record_id"
+      return 0
+    fi
+  fi
+
+  _err "failed to remove record" "$_record_id"
+  return 1
+
+}
+
+####################  Private functions bellow ##################################
+# _acme-challenge.www.domain.com
+# returns
+#   _sub_domain=_acme-challenge.www
+#   _domain=domain.com
+_get_root() {
+  domain=$1
+  i=2
+  previous=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    if [ -z "$h" ]; then
+      # not valid
+      return 1
+    fi
+
+    if ! _dnsimple_rest GET "$_account_id/zones/$h"; then
+      return 1
+    fi
+
+    if _contains "$response" 'not found'; then
+      _debug "$h not found"
+    else
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$previous)
+      _domain="$h"
+
+      _debug _domain "$_domain"
+      _debug _sub_domain "$_sub_domain"
+
+      return 0
+    fi
+
+    previous="$i"
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+# returns _account_id
+_get_account_id() {
+  _debug "retrive account id"
+  if ! _dnsimple_rest GET "whoami"; then
+    return 1
+  fi
+
+  if _contains "$response" "\"account\":null"; then
+    _err "no account associated with this token"
+    return 1
+  fi
+
+  if _contains "$response" "timeout"; then
+    _err "timeout retrieving account id"
+    return 1
+  fi
+
+  _account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1)
+  _debug _account_id "$_account_id"
+
+  return 0
+}
+
+# returns
+#   _records
+#   _records_count
+_get_records() {
+  account_id=$1
+  domain=$2
+  sub_domain=$3
+
+  _debug "fetching txt records"
+  _dnsimple_rest GET "$account_id/zones/$domain/records?per_page=100"
+
+  if ! _contains "$response" "\"id\":"; then
+    _err "failed to retrieve records"
+    return 1
+  fi
+
+  _records_count=$(printf "%s" "$response" | _egrep_o "\"name\":\"$sub_domain\"" | wc -l | _egrep_o "[0-9]+")
+  _records=$response
+  _debug _records_count "$_records_count"
+}
+
+# returns _record_id
+_extract_record_id() {
+  _record_id=$(printf "%s" "$_records" | _egrep_o "\"id\":[^,]*,\"zone_id\":\"[^,]*\",\"parent_id\":null,\"name\":\"$_sub_domain\"" | cut -d: -f2 | cut -d, -f1)
+  _debug "_record_id" "$_record_id"
+}
+
+# returns response
+_dnsimple_rest() {
+  method=$1
+  path="$2"
+  data="$3"
+  request_url="$DNSimple_API/$path"
+  _debug "$path"
+
+  export _H1="Accept: application/json"
+  export _H2="Authorization: Bearer $DNSimple_OAUTH_TOKEN"
+
+  if [ "$data" ] || [ "$method" = "DELETE" ]; then
+    _H1="Content-Type: application/json"
+    _debug data "$data"
+    response="$(_post "$data" "$request_url" "" "$method")"
+  else
+    response="$(_get "$request_url" "" "" "$method")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $request_url"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 216 - 0
dnsapi/dns_dynu.sh

@@ -0,0 +1,216 @@
+#!/usr/bin/env sh
+
+#Client ID
+#Dynu_ClientId="0b71cae7-a099-4f6b-8ddf-94571cdb760d"
+#
+#Secret
+#Dynu_Secret="aCUEY4BDCV45KI8CSIC3sp2LKQ9"
+#
+#Token
+Dynu_Token=""
+#
+#Endpoint
+Dynu_EndPoint="https://api.dynu.com/v1"
+#
+#Author: Dynu Systems, Inc.
+#Report Bugs here: https://github.com/shar0119/acme.sh
+#
+########  Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dynu_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
+    Dynu_ClientId=""
+    Dynu_Secret=""
+    _err "Dynu client id and secret is not specified."
+    _err "Please create you API client id and secret and try again."
+    return 1
+  fi
+
+  #save the client id and secret to the account conf file.
+  _saveaccountconf Dynu_ClientId "$Dynu_ClientId"
+  _saveaccountconf Dynu_Secret "$Dynu_Secret"
+
+  if [ -z "$Dynu_Token" ]; then
+    _info "Getting Dynu token."
+    if ! _dynu_authentication; then
+      _err "Can not get token."
+    fi
+  fi
+
+  _debug "Detect root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Invalid domain."
+    return 1
+  fi
+
+  _debug _node "$_node"
+  _debug _domain_name "$_domain_name"
+
+  _info "Creating TXT record."
+  if ! _dynu_rest POST "dns/record/add" "{\"domain_name\":\"$_domain_name\",\"node_name\":\"$_node\",\"record_type\":\"TXT\",\"text_data\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "text_data"; then
+    _err "Could not add TXT record."
+    return 1
+  fi
+
+  return 0
+}
+
+#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dynu_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then
+    Dynu_ClientId=""
+    Dynu_Secret=""
+    _err "Dynu client id and secret is not specified."
+    _err "Please create you API client id and secret and try again."
+    return 1
+  fi
+
+  #save the client id and secret to the account conf file.
+  _saveaccountconf Dynu_ClientId "$Dynu_ClientId"
+  _saveaccountconf Dynu_Secret "$Dynu_Secret"
+
+  if [ -z "$Dynu_Token" ]; then
+    _info "Getting Dynu token."
+    if ! _dynu_authentication; then
+      _err "Can not get token."
+    fi
+  fi
+
+  _debug "Detect root zone."
+  if ! _get_root "$fulldomain"; then
+    _err "Invalid domain."
+    return 1
+  fi
+
+  _debug _node "$_node"
+  _debug _domain_name "$_domain_name"
+
+  _info "Checking for TXT record."
+  if ! _get_recordid "$fulldomain" "$txtvalue"; then
+    _err "Could not get TXT record id."
+    return 1
+  fi
+
+  if [ "$_dns_record_id" = "" ]; then
+    _err "TXT record not found."
+    return 1
+  fi
+
+  _info "Removing TXT record."
+  if ! _delete_txt_record "$_dns_record_id"; then
+    _err "Could not remove TXT record $_dns_record_id."
+  fi
+
+  return 0
+}
+
+########  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _node=_acme-challenge.www
+# _domain_name=domain.com
+_get_root() {
+  domain=$1
+  if ! _dynu_rest GET "dns/getroot/$domain"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "domain_name"; then
+    _debug "Domain name not found."
+    return 1
+  fi
+
+  _domain_name=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2)
+  _node=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 3 | cut -d : -f 2 | cut -d '"' -f 2)
+  return 0
+}
+
+_get_recordid() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _dynu_rest GET "dns/record/get?hostname=$fulldomain&rrtype=TXT"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "$txtvalue"; then
+    _dns_record_id=0
+    return 0
+  fi
+
+  _dns_record_id=$(printf "%s" "$response" | _egrep_o "{[^}]*}" | grep "\"text_data\":\"$txtvalue\"" | _egrep_o ",[^,]*," | grep ',"id":' | tr -d ",," | cut -d : -f 2)
+
+  return 0
+}
+
+_delete_txt_record() {
+  _dns_record_id=$1
+
+  if ! _dynu_rest GET "dns/record/delete/$_dns_record_id"; then
+    return 1
+  fi
+
+  if ! _contains "$response" "true"; then
+    return 1
+  fi
+
+  return 0
+}
+
+_dynu_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="Authorization: Bearer $Dynu_Token"
+  export _H2="Content-Type: application/json"
+
+  if [ "$data" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")"
+  else
+    _info "Getting $Dynu_EndPoint/$ep"
+    response="$(_get "$Dynu_EndPoint/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}
+
+_dynu_authentication() {
+  realm="$(printf "%s" "$Dynu_ClientId:$Dynu_Secret" | _base64)"
+
+  export _H1="Authorization: Basic $realm"
+  export _H2="Content-Type: application/json"
+
+  response="$(_get "$Dynu_EndPoint/oauth2/token")"
+  if [ "$?" != "0" ]; then
+    _err "Authentication failed."
+    return 1
+  fi
+  if _contains "$response" "accessToken"; then
+    Dynu_Token=$(printf "%s" "$response" | tr -d "[]" | cut -d , -f 2 | cut -d : -f 2 | cut -d '"' -f 2)
+  fi
+  if _contains "$Dynu_Token" "null"; then
+    Dynu_Token=""
+  fi
+
+  _debug2 response "$response"
+  return 0
+}

+ 10 - 10
dnsapi/dns_freedns.sh

@@ -10,7 +10,7 @@
 #
 #
 ########  Public functions #####################
 ########  Public functions #####################
 
 
-# Export FreeDNS userid and password in folowing variables...
+# Export FreeDNS userid and password in following variables...
 #  FREEDNS_User=username
 #  FREEDNS_User=username
 #  FREEDNS_Password=password
 #  FREEDNS_Password=password
 # login cookie is saved in acme account config file so userid / pw
 # login cookie is saved in acme account config file so userid / pw
@@ -53,7 +53,7 @@ dns_freedns_add() {
   i="$(_math "$i" - 1)"
   i="$(_math "$i" - 1)"
   sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")"
   sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")"
 
 
-  # Sometimes FreeDNS does not reurn the subdomain page but rather 
+  # Sometimes FreeDNS does not return the subdomain page but rather
   # returns a page regarding becoming a premium member.  This usually
   # returns a page regarding becoming a premium member.  This usually
   # happens after a period of inactivity.  Immediately trying again
   # happens after a period of inactivity.  Immediately trying again
   # returns the correct subdomain page.  So, we will try twice to
   # returns the correct subdomain page.  So, we will try twice to
@@ -65,14 +65,14 @@ dns_freedns_add() {
     htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")"
     htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")"
     if [ "$?" != "0" ]; then
     if [ "$?" != "0" ]; then
       if [ "$using_cached_cookies" = "true" ]; then
       if [ "$using_cached_cookies" = "true" ]; then
-        _err "Has your FreeDNS username and password channged?  If so..."
+        _err "Has your FreeDNS username and password changed?  If so..."
         _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
         _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
       fi
       fi
       return 1
       return 1
     fi
     fi
 
 
     # Now convert the tables in the HTML to CSV.  This litte gem from
     # Now convert the tables in the HTML to CSV.  This litte gem from
-    # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv    
+    # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv
     subdomain_csv="$(echo "$htmlpage" \
     subdomain_csv="$(echo "$htmlpage" \
       | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \
       | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \
       | sed 's/^[\ \t]*//g' \
       | sed 's/^[\ \t]*//g' \
@@ -112,7 +112,7 @@ dns_freedns_add() {
           # not produce accurate results as the value field is truncated
           # not produce accurate results as the value field is truncated
           # on this webpage. To get full value we would need to load
           # on this webpage. To get full value we would need to load
           # another page. However we don't really need this so long as
           # another page. However we don't really need this so long as
-          # there is only one TXT record for the acme chalenge subdomain.
+          # there is only one TXT record for the acme challenge subdomain.
           DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^&quot;]*&quot;//;s/&quot;.*//;s/<\/td>.*//')"
           DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^&quot;]*&quot;//;s/&quot;.*//;s/<\/td>.*//')"
           if [ $found != 0 ]; then
           if [ $found != 0 ]; then
             break
             break
@@ -192,11 +192,11 @@ dns_freedns_rm() {
 
 
   # Need to read cookie from conf file again in case new value set
   # Need to read cookie from conf file again in case new value set
   # during login to FreeDNS when TXT record was created.
   # during login to FreeDNS when TXT record was created.
-  # acme.sh does not have a _readaccountconf() fuction
+  # acme.sh does not have a _readaccountconf() function
   FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")"
   FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")"
   _debug "FreeDNS login cookies: $FREEDNS_COOKIE"
   _debug "FreeDNS login cookies: $FREEDNS_COOKIE"
 
 
-  # Sometimes FreeDNS does not reurn the subdomain page but rather 
+  # Sometimes FreeDNS does not return the subdomain page but rather
   # returns a page regarding becoming a premium member.  This usually
   # returns a page regarding becoming a premium member.  This usually
   # happens after a period of inactivity.  Immediately trying again
   # happens after a period of inactivity.  Immediately trying again
   # returns the correct subdomain page.  So, we will try twice to
   # returns the correct subdomain page.  So, we will try twice to
@@ -302,12 +302,12 @@ _freedns_retrieve_subdomain_page() {
   export _H2="Accept-Language:en-US"
   export _H2="Accept-Language:en-US"
   url="https://freedns.afraid.org/subdomain/"
   url="https://freedns.afraid.org/subdomain/"
 
 
-  _debug "Retrieve subdmoain page from FreeDNS"
+  _debug "Retrieve subdomain page from FreeDNS"
 
 
   htmlpage="$(_get "$url")"
   htmlpage="$(_get "$url")"
 
 
   if [ "$?" != "0" ]; then
   if [ "$?" != "0" ]; then
-    _err "FreeDNS retrieve subdomins failed bad RC from _get"
+    _err "FreeDNS retrieve subdomains failed bad RC from _get"
     return 1
     return 1
   elif [ -z "$htmlpage" ]; then
   elif [ -z "$htmlpage" ]; then
     _err "FreeDNS returned empty subdomain page"
     _err "FreeDNS returned empty subdomain page"
@@ -341,7 +341,7 @@ _freedns_add_txt_record() {
     return 1
     return 1
   elif _contains "$htmlpage" "security code was incorrect"; then
   elif _contains "$htmlpage" "security code was incorrect"; then
     _debug "$htmlpage"
     _debug "$htmlpage"
-    _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested seurity code"
+    _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested security code"
     _err "Note that you cannot use automatic DNS validation for FreeDNS public domains"
     _err "Note that you cannot use automatic DNS validation for FreeDNS public domains"
     return 1
     return 1
   fi
   fi

+ 1 - 1
dnsapi/dns_gandi_livedns.sh

@@ -19,7 +19,7 @@ dns_gandi_livedns_add() {
   txtvalue=$2
   txtvalue=$2
 
 
   if [ -z "$GANDI_LIVEDNS_KEY" ]; then
   if [ -z "$GANDI_LIVEDNS_KEY" ]; then
-    _err "No API key specifed for Gandi LiveDNS."
+    _err "No API key specified for Gandi LiveDNS."
     _err "Create your key and export it as GANDI_LIVEDNS_KEY"
     _err "Create your key and export it as GANDI_LIVEDNS_KEY"
     return 1
     return 1
   fi
   fi

+ 97 - 0
dnsapi/dns_infoblox.sh

@@ -0,0 +1,97 @@
+#!/usr/bin/env sh
+
+## Infoblox API integration by Jason Keller and Elijah Tenai
+##
+## Report any bugs via https://github.com/jasonkeller/acme.sh
+
+dns_infoblox_add() {
+
+  ## Nothing to see here, just some housekeeping
+  fulldomain=$1
+  txtvalue=$2
+  baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue"
+
+  _info "Using Infoblox API"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  ## Check for the credentials
+  if [ -z "$Infoblox_Creds" ] || [ -z "$Infoblox_Server" ]; then
+    Infoblox_Creds=""
+    Infoblox_Server=""
+    _err "You didn't specify the credentials or server yet (Infoblox_Creds and Infoblox_Server)."
+    _err "Please set them via EXPORT ([username:password] and [ip or hostname]) and try again."
+    return 1
+  fi
+
+  ## Save the credentials to the account file
+  _saveaccountconf Infoblox_Creds "$Infoblox_Creds"
+  _saveaccountconf Infoblox_Server "$Infoblox_Server"
+
+  ## Base64 encode the credentials
+  Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64)
+
+  ## Construct the HTTP Authorization header
+  export _H1="Accept-Language:en-US"
+  export _H2="Authorization: Basic $Infoblox_CredsEncoded"
+
+  ## Add the challenge record to the Infoblox grid member
+  result=$(_post "" "$baseurlnObject" "" "POST")
+
+  ## Let's see if we get something intelligible back from the unit
+  if echo "$result" | egrep 'record:txt/.*:.*/default'; then
+    _info "Successfully created the txt record"
+    return 0
+  else
+    _err "Error encountered during record addition"
+    _err "$result"
+    return 1
+  fi
+
+}
+
+dns_infoblox_rm() {
+
+  ## Nothing to see here, just some housekeeping
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Using Infoblox API"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  ## Base64 encode the credentials
+  Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64)
+
+  ## Construct the HTTP Authorization header
+  export _H1="Accept-Language:en-US"
+  export _H2="Authorization: Basic $Infoblox_CredsEncoded"
+
+  ## Does the record exist?  Let's check.
+  baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&_return_type=xml-pretty"
+  result=$(_get "$baseurlnObject")
+
+  ## Let's see if we get something intelligible back from the grid
+  if echo "$result" | egrep 'record:txt/.*:.*/default'; then
+    ## Extract the object reference
+    objRef=$(printf "%b" "$result" | _egrep_o 'record:txt/.*:.*/default')
+    objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef"
+    ## Delete them! All the stale records!
+    rmResult=$(_post "" "$objRmUrl" "" "DELETE")
+    ## Let's see if that worked
+    if echo "$rmResult" | egrep 'record:txt/.*:.*/default'; then
+      _info "Successfully deleted $objRef"
+      return 0
+    else
+      _err "Error occurred during txt record delete"
+      _err "$rmResult"
+      return 1
+    fi
+  else
+    _err "Record to delete didn't match an existing record"
+    _err "$result"
+    return 1
+  fi
+}
+
+####################  Private functions below ##################################

+ 4 - 4
dnsapi/dns_ovh.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-#Applcation Key
+#Application Key
 #OVH_AK="sdfsdfsdfljlbjkljlkjsdfoiwje"
 #OVH_AK="sdfsdfsdfljlbjkljlkjsdfoiwje"
 #
 #
 #Application Secret
 #Application Secret
@@ -14,7 +14,7 @@
 #'ovh-eu'
 #'ovh-eu'
 OVH_EU='https://eu.api.ovh.com/1.0'
 OVH_EU='https://eu.api.ovh.com/1.0'
 
 
-#'ovh-ca': 
+#'ovh-ca':
 OVH_CA='https://ca.api.ovh.com/1.0'
 OVH_CA='https://ca.api.ovh.com/1.0'
 
 
 #'kimsufi-eu'
 #'kimsufi-eu'
@@ -119,7 +119,7 @@ dns_ovh_add() {
 
 
   _info "Checking authentication"
   _info "Checking authentication"
 
 
-  response="$(_ovh_rest GET "domain/")"
+  response="$(_ovh_rest GET "domain")"
   if _contains "$response" "INVALID_CREDENTIAL"; then
   if _contains "$response" "INVALID_CREDENTIAL"; then
     _err "The consumer key is invalid: $OVH_CK"
     _err "The consumer key is invalid: $OVH_CK"
     _err "Please retry to create a new one."
     _err "Please retry to create a new one."
@@ -191,7 +191,7 @@ _ovh_authentication() {
   _H3=""
   _H3=""
   _H4=""
   _H4=""
 
 
-  _ovhdata='{"accessRules": [{"method": "GET","path": "/*"},{"method": "POST","path": "/*"},{"method": "PUT","path": "/*"},{"method": "DELETE","path": "/*"}],"redirection":"'$ovh_success'"}'
+  _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}'
 
 
   response="$(_post "$_ovhdata" "$OVH_API/auth/credential")"
   response="$(_post "$_ovhdata" "$OVH_API/auth/credential")"
   _debug3 response "$response"
   _debug3 response "$response"

+ 1 - 1
dnsapi/dns_pdns.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-#PowerDNS Emdedded API
+#PowerDNS Embedded API
 #https://doc.powerdns.com/md/httpapi/api_spec/
 #https://doc.powerdns.com/md/httpapi/api_spec/
 #
 #
 #PDNS_Url="http://ns.example.com:8081"
 #PDNS_Url="http://ns.example.com:8081"

+ 149 - 0
dnsapi/dns_vscale.sh

@@ -0,0 +1,149 @@
+#!/usr/bin/env sh
+
+#This is the vscale.io api wrapper for acme.sh
+#
+#Author: Alex Loban
+#Report Bugs here: https://github.com/LAV45/acme.sh
+
+#VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
+VSCALE_API_URL="https://api.vscale.io/v1"
+
+########  Public functions #####################
+
+#Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_vscale_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$VSCALE_API_KEY" ]; then
+    VSCALE_API_KEY=""
+    _err "You didn't specify the VSCALE api key yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  _saveaccountconf VSCALE_API_KEY "$VSCALE_API_KEY"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _vscale_tmpl_json="{\"type\":\"TXT\",\"name\":\"$_sub_domain.$_domain\",\"content\":\"$txtvalue\"}"
+
+  if _vscale_rest POST "domains/$_domain_id/records/" "$_vscale_tmpl_json"; then
+    response=$(printf "%s\n" "$response" | _egrep_o "{\"error\": \".+\"" | cut -d : -f 2)
+    if [ -z "$response" ]; then
+      _info "txt record updated success."
+      return 0
+    fi
+  fi
+
+  return 1
+}
+
+#fulldomain txtvalue
+dns_vscale_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  _vscale_rest GET "domains/$_domain_id/records/"
+
+  if [ -n "$response" ]; then
+    record_id=$(printf "%s\n" "$response" | _egrep_o "\"TXT\", \"id\": [0-9]+, \"name\": \"$_sub_domain.$_domain\"" | cut -d : -f 2 | tr -d ", \"name\"")
+    _debug record_id "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if _vscale_rest DELETE "domains/$_domain_id/records/$record_id" && [ -z "$response" ]; then
+      _info "txt record deleted success."
+      return 0
+    fi
+    _debug response "$response"
+    return 1
+  fi
+
+  return 1
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=12345
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+
+  if _vscale_rest GET "domains/"; then
+    response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')"
+    while true; do
+      h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+      _debug h "$h"
+      if [ -z "$h" ]; then
+        #not valid
+        return 1
+      fi
+
+      hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")"
+      if [ "$hostedzone" ]; then
+        _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+        if [ "$_domain_id" ]; then
+          _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+          _domain=$h
+          return 0
+        fi
+        return 1
+      fi
+      p=$i
+      i=$(_math "$i" + 1)
+    done
+  fi
+  return 1
+}
+
+#method uri qstr data
+_vscale_rest() {
+  mtd="$1"
+  ep="$2"
+  data="$3"
+
+  _debug mtd "$mtd"
+  _debug ep "$ep"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+  export _H3="X-Token: ${VSCALE_API_KEY}"
+
+  if [ "$mtd" != "GET" ]; then
+    # both POST and DELETE.
+    _debug data "$data"
+    response="$(_post "$data" "$VSCALE_API_URL/$ep" "" "$mtd")"
+  else
+    response="$(_get "$VSCALE_API_URL/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}