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.
 - Just one script to issue, renew and install your certificates automatically.
 - 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
 
+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)
 
@@ -29,6 +31,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 - [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html)
 - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
 - [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)
 
 # Tested OS
@@ -133,13 +136,25 @@ root@v1:~# acme.sh -h
 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.
 
 ```bash
 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.
 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:
 ```bash
 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"
 ```
 
 **Nginx** example:
 ```bash
 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"
 ```
 
@@ -289,6 +304,7 @@ You don't have to do anything manually!
 
 1. CloudFlare.com API
 1. DNSPod.cn API
+1. DNSimple API
 1. CloudXNS.com API
 1. GoDaddy.com 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. Gandi LiveDNS 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...**
 
@@ -327,7 +349,7 @@ Just set the `length` parameter with a prefix `ec-`.
 
 For example:
 
-### Single domain ECC cerfiticate
+### Single domain ECC certificate
 
 ```bash
 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
 
-VER=2.6.7
+VER=2.6.9
 
 PROJECT_NAME="acme.sh"
 
@@ -107,7 +107,7 @@ __green() {
   if [ "$__INTERACTIVE" ]; then
     printf '\033[1;31;32m'
   fi
-  printf -- "$1"
+  printf -- "%b" "$1"
   if [ "$__INTERACTIVE" ]; then
     printf '\033[0m'
   fi
@@ -117,7 +117,7 @@ __red() {
   if [ "$__INTERACTIVE" ]; then
     printf '\033[1;31;40m'
   fi
-  printf -- "$1"
+  printf -- "%b" "$1"
   if [ "$__INTERACTIVE" ]; then
     printf '\033[0m'
   fi
@@ -138,8 +138,8 @@ _printargs() {
 _dlg_versions() {
   echo "Diagnosis versions: "
   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
     echo "$ACME_OPENSSL_BIN doesn't exists."
   fi
@@ -166,7 +166,14 @@ _syslog() {
   fi
   _logclass="$1"
   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() {
@@ -299,6 +306,16 @@ _secure_debug3() {
   fi
 }
 
+_upper_case() {
+  # shellcheck disable=SC2018,SC2019
+  tr 'a-z' 'A-Z'
+}
+
+_lower_case() {
+  # shellcheck disable=SC2018,SC2019
+  tr 'A-Z' 'a-z'
+}
+
 _startswith() {
   _str="$1"
   _sub="$2"
@@ -330,14 +347,14 @@ _hasfield() {
     _sep=","
   fi
 
-  for f in $(echo "$_str" | tr ',' ' '); do
+  for f in $(echo "$_str" | tr "$_sep" ' '); do
     if [ "$f" = "$_field" ]; then
       _debug2 "'$_str' contains '$_field'"
       return 0 #contains ok
     fi
   done
   _debug2 "'$_str' does not contain '$_field'"
-  return 1 #not contains 
+  return 1 #not contains
 }
 
 _getfield() {
@@ -712,7 +729,7 @@ _url_encode() {
       "7e")
         printf "%s" "~"
         ;;
-      #other hex  
+      #other hex
       *)
         printf '%%%s' "$_hex_code"
         ;;
@@ -780,19 +797,19 @@ _base64() {
   [ "" ] #urgly
   if [ "$1" ]; then
     _debug3 "base64 multiline:'$1'"
-    $ACME_OPENSSL_BIN base64 -e
+    ${ACME_OPENSSL_BIN:-openssl} base64 -e
   else
     _debug3 "base64 single line."
-    $ACME_OPENSSL_BIN base64 -e | tr -d '\r\n'
+    ${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n'
   fi
 }
 
 #Usage: multiline
 _dbase64() {
   if [ "$1" ]; then
-    $ACME_OPENSSL_BIN base64 -d -A
+    ${ACME_OPENSSL_BIN:-openssl} base64 -d -A
   else
-    $ACME_OPENSSL_BIN base64 -d
+    ${ACME_OPENSSL_BIN:-openssl} base64 -d
   fi
 }
 
@@ -809,9 +826,9 @@ _digest() {
 
   if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; 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
-      $ACME_OPENSSL_BIN dgst -"$alg" -binary | _base64
+      ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64
     fi
   else
     _err "$alg is not supported yet"
@@ -834,9 +851,9 @@ _hmac() {
 
   if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; 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
-      $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
   else
     _err "$alg is not supported yet"
@@ -855,7 +872,7 @@ _sign() {
     return 1
   fi
 
-  _sign_openssl="$ACME_OPENSSL_BIN   dgst -sign $keyfile "
+  _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
   if [ "$alg" = "sha256" ]; then
     _sign_openssl="$_sign_openssl -$alg"
   else
@@ -866,10 +883,10 @@ _sign() {
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
     $_sign_openssl | _base64
   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 "Key file: $keyfile"
-      _err "Key content:$(wc -l <"$keyfile") lises"
+      _err "Key content:$(wc -l <"$keyfile") lines"
       return 1
     fi
     _debug3 "_signedECText" "$_signedECText"
@@ -938,10 +955,10 @@ _createkey() {
 
   if _isEccKey "$length"; then
     _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
     _debug "Using RSA: $length"
-    $ACME_OPENSSL_BIN genrsa "$length" 2>/dev/null >"$f"
+    ${ACME_OPENSSL_BIN:-openssl} genrsa "$length" 2>/dev/null >"$f"
   fi
 
   if [ "$?" != "0" ]; then
@@ -1015,7 +1032,7 @@ _createcsr() {
     else
       alt="DNS:$domainlist"
     fi
-    #multi 
+    #multi
     _info "Multi domain" "$alt"
     printf -- "\nsubjectAltName=$alt" >>"$csrconf"
   fi
@@ -1028,9 +1045,9 @@ _createcsr() {
   _csr_cn="$(_idn "$domain")"
   _debug2 _csr_cn "$_csr_cn"
   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
-    $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
 }
 
@@ -1042,7 +1059,7 @@ _signcsr() {
   cert="$4"
   _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="$?"
   _debug "$_msg"
   return $_ret
@@ -1055,7 +1072,7 @@ _readSubjectFromCSR() {
     _usage "_readSubjectFromCSR mycsr.csr"
     return 1
   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
@@ -1070,7 +1087,7 @@ _readSubjectAltNamesFromCSR() {
   _csrsubj="$(_readSubjectFromCSR "$_csrfile")"
   _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"
 
   if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then
@@ -1083,7 +1100,7 @@ _readSubjectAltNamesFromCSR() {
   printf "%s" "$_dnsAltnames" | sed "s/DNS://g"
 }
 
-#_csrfile 
+#_csrfile
 _readKeyLengthFromCSR() {
   _csrfile="$1"
   if [ -z "$_csrfile" ]; then
@@ -1091,13 +1108,14 @@ _readKeyLengthFromCSR() {
     return 1
   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
     _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
     _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
 }
 
@@ -1121,8 +1139,12 @@ _ss() {
       elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then
         #for solaris
         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 "
+      else
+        #for busybox (embedded linux; no pid support)
+        netstat -ntl 2>/dev/null | grep ":$_port "
       fi
     fi
     return 0
@@ -1145,9 +1167,9 @@ toPkcs() {
   _initpath "$domain" "$_isEcc"
 
   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
-    $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
 
   if [ "$?" = "0" ]; then
@@ -1169,7 +1191,7 @@ toPkcs8() {
 
   _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
     _info "Success, $CERT_PKCS8_PATH"
@@ -1177,7 +1199,7 @@ toPkcs8() {
 
 }
 
-#[2048]  
+#[2048]
 createAccountKey() {
   _info "Creating account key"
   if [ -z "$1" ]; then
@@ -1330,7 +1352,7 @@ _calcjwk() {
 
   if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
     _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
       pub_exp=0$pub_exp
     fi
@@ -1339,7 +1361,7 @@ _calcjwk() {
     e=$(echo "$pub_exp" | _h2b | _base64)
     _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"
     n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)"
     _debug3 n "$n"
@@ -1352,12 +1374,12 @@ _calcjwk() {
     JWK_HEADERPLACE_PART2='", "alg": "RS256", "jwk": '$jwk'}'
   elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
     _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"
 
     if [ -z "$crv" ]; then
       _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"
       case "${crv_oid}" in
         "prime256v1")
@@ -1377,15 +1399,15 @@ _calcjwk() {
       _debug3 crv "$crv"
     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)
     _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)
     _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"
 
     xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)"
@@ -1469,7 +1491,9 @@ _inithttp() {
       _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
     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 "
     fi
 
@@ -1480,8 +1504,10 @@ _inithttp() {
     if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
       _ACME_WGET="$_ACME_WGET -d "
     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
 
@@ -1828,6 +1854,24 @@ _saveaccountconf() {
   _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() {
   _clear_conf "$ACCOUNT_CONF_PATH" "$1"
@@ -1999,7 +2043,7 @@ _starttlsserver() {
     return 1
   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
     __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port"
   else
@@ -2240,16 +2284,16 @@ _initpath() {
   fi
 
   if [ -z "$TLS_CONF" ]; then
-    TLS_CONF="$DOMAIN_PATH/tls.valdation.conf"
+    TLS_CONF="$DOMAIN_PATH/tls.validation.conf"
   fi
   if [ -z "$TLS_CERT" ]; then
-    TLS_CERT="$DOMAIN_PATH/tls.valdation.cert"
+    TLS_CERT="$DOMAIN_PATH/tls.validation.cert"
   fi
   if [ -z "$TLS_KEY" ]; then
-    TLS_KEY="$DOMAIN_PATH/tls.valdation.key"
+    TLS_KEY="$DOMAIN_PATH/tls.validation.key"
   fi
   if [ -z "$TLS_CSR" ]; then
-    TLS_CSR="$DOMAIN_PATH/tls.valdation.csr"
+    TLS_CSR="$DOMAIN_PATH/tls.validation.csr"
   fi
 
 }
@@ -2367,7 +2411,7 @@ _setApache() {
   _debug "Backup apache config file" "$httpdconf"
   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 "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
   fi
   _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]+)\$\" {
   default_type text/plain;
   return 200 \"\$1.$_thumbpt\";
-}  
+}
 #NGINX_START
 " >>"$FOUND_REAL_NGINX_CONF"
 
@@ -2546,7 +2590,7 @@ _checkConf() {
   if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then
     _debug "wildcard"
     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
       fi
     done
@@ -2580,10 +2624,10 @@ _checkConf() {
 _isRealNginxConf() {
   _debug "_isRealNginxConf $1 $2"
   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"
       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"
         _start_n=$(echo "$_start" | cut -d : -f 1)
         _start_nn=$(_math $_start_n + 1)
@@ -2592,8 +2636,8 @@ _isRealNginxConf() {
 
         _left="$(sed -n "${_start_nn},99999p" "$2")"
         _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"
           _end_n=$(echo "$_end" | cut -d : -f 1)
           _debug "_end_n" "$_end_n"
@@ -2865,7 +2909,7 @@ _on_issue_err() {
         uri=$(echo "$ventry" | cut -d "$sep" -f 3)
         vtype=$(echo "$ventry" | cut -d "$sep" -f 4)
         _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5)
-        __trigger_validaton "$uri" "$keyauthorization"
+        __trigger_validation "$uri" "$keyauthorization"
       done
     )
   fi
@@ -3087,7 +3131,7 @@ __get_domain_new_authz() {
 }
 
 #uri keyAuthorization
-__trigger_validaton() {
+__trigger_validation() {
   _debug2 "tigger domain validation."
   _t_url="$1"
   _debug2 _t_url "$_t_url"
@@ -3096,12 +3140,16 @@ __trigger_validaton() {
   _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"keyAuthorization\": \"$_t_key_authz\"}"
 }
 
-#webroot, domain domainlist  keylength 
+#webroot, domain domainlist  keylength
 issue() {
   if [ -z "$2" ]; then
     _usage "Usage: $PROJECT_ENTRY --issue  -d  a.com  -w /path/to/webroot/a.com/ "
     return 1
   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"
   _main_domain="$2"
   _alt_domains="$3"
@@ -3467,9 +3515,12 @@ issue() {
         if [ ! "$usingApache" ]; then
           if webroot_owner=$(_stat "$_currentRoot"); then
             _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
-            _debug "not chaning owner/group of webroot"
+            _debug "not changing owner/group of webroot"
           fi
         fi
 
@@ -3510,7 +3561,7 @@ issue() {
       fi
     fi
 
-    if ! __trigger_validaton "$uri" "$keyauthorization"; then
+    if ! __trigger_validation "$uri" "$keyauthorization"; then
       _err "$d:Can not get challenge: $response"
       _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
       _clearup
@@ -3614,6 +3665,7 @@ issue() {
 
   _rcert="$response"
   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"
 
   if [ "$Le_LinkCert" ]; then
@@ -3621,7 +3673,7 @@ issue() {
 
     #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
     #  _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
 
     if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then
@@ -3660,16 +3712,34 @@ issue() {
   if ! _contains "$Le_LinkIssuer" ":"; then
     Le_LinkIssuer="$API$Le_LinkIssuer"
   fi
-
+  _debug Le_LinkIssuer "$Le_LinkIssuer"
   _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
 
   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
 
   Le_CertCreateTime=$(_time)
@@ -3690,6 +3760,12 @@ issue() {
     _clearaccountconf "CA_BUNDLE"
   fi
 
+  if [ "$CA_PATH" ]; then
+    _saveaccountconf CA_PATH "$CA_PATH"
+  else
+    _clearaccountconf "CA_PATH"
+  fi
+
   if [ "$HTTPS_INSECURE" ]; then
     _saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE"
   else
@@ -3814,7 +3890,7 @@ renewAll() {
         return "$rc"
       else
         _ret="$rc"
-        _err "Error renew $d, Go ahead to next one."
+        _err "Error renew $d."
       fi
     fi
   done
@@ -4008,7 +4084,7 @@ deploy() {
 installcert() {
   _main_domain="$1"
   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
   fi
 
@@ -4107,6 +4183,7 @@ _installcert() {
       export CERT_KEY_PATH
       export CA_CERT_PATH
       export CERT_FULLCHAIN_PATH
+      export Le_Domain
       cd "$DOMAIN_PATH" && eval "$_reload_cmd"
     ); then
       _info "$(__green "Reload success")"
@@ -4435,7 +4512,7 @@ _precheck() {
     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 "We need openssl to generate keys."
     return 1
@@ -4618,7 +4695,7 @@ install() {
     #Modify shebang
     if _exists bash; then
       _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"
       for subf in $_SUB_FOLDERS; do
         if [ -d "$LE_WORKING_DIR/$subf" ]; then
@@ -4677,6 +4754,7 @@ _uninstallalias() {
 cron() {
   IN_CRON=1
   _initpath
+  _info "$(__green "===Starting cron===")"
   if [ "$AUTO_UPGRADE" = "1" ]; then
     export LE_WORKING_DIR
     (
@@ -4696,6 +4774,7 @@ cron() {
   renewAll
   _ret="$?"
   IN_CRON=""
+  _info "$(__green "===End cron===")"
   exit $_ret
 }
 
@@ -4735,7 +4814,7 @@ Commands:
   --create-domain-key      Create an domain private key, professional use.
   --createCSR, -ccsr       Create CSR , professional use.
   --deactivate             Deactivate the domain authz, professional use.
-  
+
 Parameters:
   --domain, -d   domain.tld         Specifies a domain, used to issue, renew or revoke etc.
   --force, -f                       Used to force to install or force to renew a cert immediately.
@@ -4749,20 +4828,20 @@ Parameters:
   --apache                          Use apache mode.
   --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.
-  
+
   --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.
   --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.
   --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:
-  
-  --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.
 
   --accountconf                     Specifies a customized account config file.
@@ -4779,12 +4858,13 @@ Parameters:
   --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.
   --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.
   --ecc                             Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
   --csr                             Specifies the input csr.
   --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.
   --deploy-hook                     The hook file to deploy cert
   --ocsp-must-staple, --ocsp        Generate ocsp must Staple extension.
@@ -4886,10 +4966,10 @@ _process() {
   _webroot=""
   _keylength=""
   _accountkeylength=""
-  _certpath=""
-  _keypath=""
-  _capath=""
-  _fullchainpath=""
+  _cert_file=""
+  _key_file=""
+  _ca_file=""
+  _fullchain_file=""
   _reloadcmd=""
   _password=""
   _accountconf=""
@@ -4905,6 +4985,7 @@ _process() {
   _stopRenewOnError=""
   #_insecure=""
   _ca_bundle=""
+  _ca_path=""
   _nocron=""
   _ecc=""
   _csr=""
@@ -5130,20 +5211,20 @@ _process() {
         shift
         ;;
 
-      --certpath)
-        _certpath="$2"
+      --cert-file | --certpath)
+        _cert_file="$2"
         shift
         ;;
-      --keypath)
-        _keypath="$2"
+      --key-file | --keypath)
+        _key_file="$2"
         shift
         ;;
-      --capath)
-        _capath="$2"
+      --ca-file | --capath)
+        _ca_file="$2"
         shift
         ;;
-      --fullchainpath)
-        _fullchainpath="$2"
+      --fullchain-file | --fullchainpath)
+        _fullchain_file="$2"
         shift
         ;;
       --reloadcmd | --reloadCmd)
@@ -5219,6 +5300,11 @@ _process() {
         CA_BUNDLE="$_ca_bundle"
         shift
         ;;
+      --ca-path)
+        _ca_path="$2"
+        CA_PATH="$_ca_path"
+        shift
+        ;;
       --nocron)
         _nocron="1"
         ;;
@@ -5360,7 +5446,7 @@ _process() {
     uninstall) uninstall "$_nocron" ;;
     upgrade) upgrade ;;
     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 "$_domain" "$_deploy_hook" "$_ecc"
@@ -5372,7 +5458,7 @@ _process() {
       showcsr "$_csr" "$_domain"
       ;;
     installcert)
-      installcert "$_domain" "$_certpath" "$_keypath" "$_capath" "$_reloadcmd" "$_fullchainpath" "$_ecc"
+      installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc"
       ;;
     renew)
       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.
 
 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.
 

+ 2 - 2
deploy/exim4.sh

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

+ 25 - 29
deploy/kong.sh

@@ -1,13 +1,7 @@
 #!/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>
 
 ########  Public functions #####################
@@ -31,29 +25,32 @@ kong_deploy() {
   _debug _cca "$_cca"
   _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
   #Save kong url if it's succesful (First run case)
   _saveaccountconf KONG_URL "$KONG_URL"
   #Generate DEIM
-  delim="-----MultipartDelimeter$(date "+%s%N")"
+  delim="-----MultipartDelimiter$(date "+%s%N")"
   nl="\015\012"
   #Set Header
   _H1="Content-Type: multipart/form-data; boundary=$delim"
   #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
-  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
-  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
   content="$content${nl}--$delim--${nl}"
   #Convert CRLF
@@ -61,18 +58,17 @@ kong_deploy() {
   #DEBUG
   _debug header "$_H1"
   _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
     #Post certificate to Kong
-    response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST")
+    response=$(_post "$content" "$KONG_URL/certificates" "" "POST")
   else
     #patch
-    response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH")
+    response=$(_post "$content" "$KONG_URL/certificates/$ssl_uuid" "" "PATCH")
   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"
     return 1
   fi

+ 2 - 2
deploy/vsftpd.sh

@@ -76,7 +76,7 @@ vsftpd_deploy() {
         _info "Restore conf success"
         eval "$_reload"
       else
-        _err "Opps, error restore vsftpd conf, please report bug to us."
+        _err "Oops, error restore vsftpd conf, please report bug to us."
       fi
       return 1
     fi
@@ -102,7 +102,7 @@ vsftpd_deploy() {
       _info "Restore conf success"
       eval "$_reload"
     else
-      _err "Opps, error restore vsftpd conf, please report bug to us."
+      _err "Oops, error restore vsftpd conf, please report bug to us."
     fi
     return 1
   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
-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.
 
 ## 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.
 
-## 20. Use NS1. API
+
+## 20. Use NS1.com API
 
 ```
 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
 ```
 
+## 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
 
 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
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
       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
         return 1
       fi
 
       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"
         if [ -z "$hostedzone" ]; then
           _err "Error, can not get hostedzone."
@@ -143,7 +156,7 @@ aws_rest() {
   CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n"
   SignedHeaders="host;x-amz-date"
   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"
     SignedHeaders="${SignedHeaders};x-amz-security-token"
   fi
@@ -204,10 +217,13 @@ aws_rest() {
   Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
   _debug2 Authorization "$Authorization"
 
-  _H3="Authorization: $Authorization"
-  _debug _H3 "$_H3"
+  _H2="Authorization: $Authorization"
+  _debug _H2 "$_H2"
 
   url="$AWS_URL/$ep"
+  if [ "$qsr" ]; then
+    url="$AWS_URL/$ep?$qsr"
+  fi
 
   if [ "$mtd" = "GET" ]; then
     response="$(_get "$url")"

+ 15 - 2
dnsapi/dns_cf.sh

@@ -14,6 +14,8 @@ dns_cf_add() {
   fulldomain=$1
   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=""
@@ -29,8 +31,8 @@ dns_cf_add() {
   fi
 
   #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"
   if ! _get_root "$fulldomain"; then
@@ -83,6 +85,17 @@ dns_cf_add() {
 dns_cf_rm() {
   fulldomain=$1
   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"
   if ! _get_root "$fulldomain"; then
     _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
   fi
   _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
 
   if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then
-    # Dummy entries to satify script checker.
+    # Dummy entries to satisfy script checker.
     CY_Username=""
     CY_Password=""
     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 #####################
 
-# Export FreeDNS userid and password in folowing variables...
+# Export FreeDNS userid and password in following variables...
 #  FREEDNS_User=username
 #  FREEDNS_Password=password
 # login cookie is saved in acme account config file so userid / pw
@@ -53,7 +53,7 @@ dns_freedns_add() {
   i="$(_math "$i" - 1)"
   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
   # happens after a period of inactivity.  Immediately trying again
   # 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")"
     if [ "$?" != "0" ]; 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."
       fi
       return 1
     fi
 
     # 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" \
       | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \
       | sed 's/^[\ \t]*//g' \
@@ -112,7 +112,7 @@ dns_freedns_add() {
           # not produce accurate results as the value field is truncated
           # on this webpage. To get full value we would need to load
           # 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>.*//')"
           if [ $found != 0 ]; then
             break
@@ -192,11 +192,11 @@ dns_freedns_rm() {
 
   # Need to read cookie from conf file again in case new value set
   # 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")"
   _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
   # happens after a period of inactivity.  Immediately trying again
   # 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"
   url="https://freedns.afraid.org/subdomain/"
 
-  _debug "Retrieve subdmoain page from FreeDNS"
+  _debug "Retrieve subdomain page from FreeDNS"
 
   htmlpage="$(_get "$url")"
 
   if [ "$?" != "0" ]; then
-    _err "FreeDNS retrieve subdomins failed bad RC from _get"
+    _err "FreeDNS retrieve subdomains failed bad RC from _get"
     return 1
   elif [ -z "$htmlpage" ]; then
     _err "FreeDNS returned empty subdomain page"
@@ -341,7 +341,7 @@ _freedns_add_txt_record() {
     return 1
   elif _contains "$htmlpage" "security code was incorrect"; then
     _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"
     return 1
   fi

+ 1 - 1
dnsapi/dns_gandi_livedns.sh

@@ -19,7 +19,7 @@ dns_gandi_livedns_add() {
   txtvalue=$2
 
   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"
     return 1
   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
 
-#Applcation Key
+#Application Key
 #OVH_AK="sdfsdfsdfljlbjkljlkjsdfoiwje"
 #
 #Application Secret
@@ -14,7 +14,7 @@
 #'ovh-eu'
 OVH_EU='https://eu.api.ovh.com/1.0'
 
-#'ovh-ca': 
+#'ovh-ca':
 OVH_CA='https://ca.api.ovh.com/1.0'
 
 #'kimsufi-eu'
@@ -119,7 +119,7 @@ dns_ovh_add() {
 
   _info "Checking authentication"
 
-  response="$(_ovh_rest GET "domain/")"
+  response="$(_ovh_rest GET "domain")"
   if _contains "$response" "INVALID_CREDENTIAL"; then
     _err "The consumer key is invalid: $OVH_CK"
     _err "Please retry to create a new one."
@@ -191,7 +191,7 @@ _ovh_authentication() {
   _H3=""
   _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")"
   _debug3 response "$response"

+ 1 - 1
dnsapi/dns_pdns.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-#PowerDNS Emdedded API
+#PowerDNS Embedded API
 #https://doc.powerdns.com/md/httpapi/api_spec/
 #
 #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
+}