Browse Source

Merge branch 'master' of https://github.com/Neilpang/acme.sh into ssh-deploy

David Kerr 7 years ago
parent
commit
c809b33161
14 changed files with 414 additions and 177 deletions
  1. 2 1
      .travis.yml
  2. 2 2
      Dockerfile
  3. 78 103
      acme.sh
  4. 41 5
      deploy/README.md
  5. 0 29
      deploy/cpanel.sh
  6. 64 0
      deploy/cpanel_uapi.sh
  7. 108 0
      deploy/fritzbox.sh
  8. 32 0
      deploy/strongswan.sh
  9. 2 4
      dnsapi/README.md
  10. 10 9
      dnsapi/dns_aws.sh
  11. 14 0
      dnsapi/dns_cloudns.sh
  12. 58 21
      dnsapi/dns_duckdns.sh
  13. 1 1
      dnsapi/dns_gandi_livedns.sh
  14. 2 2
      dnsapi/dns_he.sh

+ 2 - 1
.travis.yml

@@ -18,7 +18,7 @@ addons:
 
 install:
   - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then
-      brew update && brew install openssl;
+      brew update && brew install openssl socat;
       brew info openssl;
       ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/;
       ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/;
@@ -30,6 +30,7 @@ install:
       openssl version 2>&1 || true;
       $ACME_OPENSSL_BIN version 2>&1 || true;
       export PATH="$_old_path";
+    else sudo apt-get install socat;
     fi
   
 script:

+ 2 - 2
Dockerfile

@@ -4,7 +4,7 @@ RUN apk update -f \
   && apk --no-cache add -f \
   openssl \
   curl \
-  netcat-openbsd \
+  socat \
   && rm -rf /var/cache/apk/*
 
 ENV LE_CONFIG_HOME /acme.sh
@@ -16,7 +16,7 @@ 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) && rm -rf /install_acme.sh/
 
 
-RUN ln -s  /root/.acme.sh/acme.sh  /usr/local/bin/acme.sh && crontab -l | sed 's#> /dev/null##' | crontab -
+RUN ln -s  /root/.acme.sh/acme.sh  /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null##' | crontab -
 
 RUN for verb in help \ 
   version \

+ 78 - 103
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-VER=2.7.3
+VER=2.7.4
 
 PROJECT_NAME="acme.sh"
 
@@ -100,6 +100,10 @@ _PREPARE_LINK="https://github.com/Neilpang/acme.sh/wiki/Install-preparations"
 
 _STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode"
 
+_DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
+
+_DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR"
+
 __INTERACTIVE=""
 if [ -t 1 ]; then
   __INTERACTIVE="1"
@@ -160,11 +164,11 @@ _dlg_versions() {
     echo "nginx doesn't exists."
   fi
 
-  echo "nc:"
-  if _exists "nc"; then
-    nc -h 2>&1
+  echo "socat:"
+  if _exists "socat"; then
+    socat -h 2>&1
   else
-    _debug "nc doesn't exists."
+    _debug "socat doesn't exists."
   fi
 }
 
@@ -1367,6 +1371,10 @@ _time2str() {
     echo "$_t_s_a"
   fi
 
+  #Busybox
+  if echo "$1" | awk '{ print strftime("%c", $0); }' 2>/dev/null; then
+    return
+  fi
 }
 
 _normalizeJson() {
@@ -1806,7 +1814,13 @@ _send_signed_request() {
 
     _CACHED_NONCE="$(echo "$responseHeaders" | grep "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
 
-    if _contains "$response" "JWS has invalid anti-replay nonce"; then
+    _body="$response"
+    if [ "$needbase64" ]; then
+      _body="$(echo "$_body" | _dbase64)"
+      _debug2 _body "$_body"
+    fi
+
+    if _contains "$_body" "JWS has invalid anti-replay nonce"; then
       _info "It seems the CA server is busy now, let's wait and retry."
       _request_retry_times=$(_math "$_request_retry_times" + 1)
       _sleep 5
@@ -1959,68 +1973,22 @@ _startserver() {
   _debug "ncaddr" "$ncaddr"
 
   _debug "startserver: $$"
-  nchelp="$(nc -h 2>&1)"
 
   _debug Le_HTTPPort "$Le_HTTPPort"
   _debug Le_Listen_V4 "$Le_Listen_V4"
   _debug Le_Listen_V6 "$Le_Listen_V6"
-  _NC="nc"
 
+  _NC="socat"
   if [ "$Le_Listen_V4" ]; then
     _NC="$_NC -4"
   elif [ "$Le_Listen_V6" ]; then
     _NC="$_NC -6"
   fi
 
-  if [ "$Le_Listen_V4$Le_Listen_V6$ncaddr" ]; then
-    if ! _contains "$nchelp" "-4"; then
-      _err "The nc doesn't support '-4', '-6' or local-address, please install 'netcat-openbsd' and try again."
-      _err "See $(__green $_PREPARE_LINK)"
-      return 1
-    fi
-  fi
-
-  if echo "$nchelp" | grep "\-q[ ,]" >/dev/null; then
-    _NC="$_NC -q 1 -l $ncaddr"
-  else
-    if echo "$nchelp" | grep "GNU netcat" >/dev/null && echo "$nchelp" | grep "\-c, \-\-close" >/dev/null; then
-      _NC="$_NC -c -l $ncaddr"
-    elif echo "$nchelp" | grep "\-N" | grep "Shutdown the network socket after EOF on stdin" >/dev/null; then
-      _NC="$_NC -N -l $ncaddr"
-    else
-      _NC="$_NC -l $ncaddr"
-    fi
-  fi
-
   _debug "_NC" "$_NC"
-
-  #for centos ncat
-  if _contains "$nchelp" "nmap.org"; then
-    _debug "Using ncat: nmap.org"
-    if ! _exec "printf \"%s\r\n\r\n%s\" \"HTTP/1.1 200 OK\" \"$content\" | $_NC \"$Le_HTTPPort\" >&2"; then
-      _exec_err
-      return 1
-    fi
-    if [ "$DEBUG" ]; then
-      _exec_err
-    fi
-    return
-  fi
-
-  #  while true ; do
-  if ! _exec "printf \"%s\r\n\r\n%s\" \"HTTP/1.1 200 OK\" \"$content\" | $_NC -p \"$Le_HTTPPort\" >&2"; then
-    _exec "printf \"%s\r\n\r\n%s\" \"HTTP/1.1 200 OK\" \"$content\" | $_NC \"$Le_HTTPPort\" >&2"
-  fi
-
-  if [ "$?" != "0" ]; then
-    _err "nc listen error."
-    _exec_err
-    exit 1
-  fi
-  if [ "$DEBUG" ]; then
-    _exec_err
-  fi
-  #  done
+  #todo  listen address
+  $_NC TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork SYSTEM:"sleep 0.5; echo HTTP/1.1 200 OK; echo ; echo  $content; echo;" &
+  serverproc="$!"
 }
 
 _stopserver() {
@@ -2030,25 +1998,8 @@ _stopserver() {
     return
   fi
 
-  _debug2 "Le_HTTPPort" "$Le_HTTPPort"
-  if [ "$Le_HTTPPort" ]; then
-    if [ "$DEBUG" ] && [ "$DEBUG" -gt "3" ]; then
-      _get "http://localhost:$Le_HTTPPort" "" 1
-    else
-      _get "http://localhost:$Le_HTTPPort" "" 1 >/dev/null 2>&1
-    fi
-  fi
+  kill $pid
 
-  _debug2 "Le_TLSPort" "$Le_TLSPort"
-  if [ "$Le_TLSPort" ]; then
-    if [ "$DEBUG" ] && [ "$DEBUG" -gt "3" ]; then
-      _get "https://localhost:$Le_TLSPort" "" 1
-      _get "https://localhost:$Le_TLSPort" "" 1
-    else
-      _get "https://localhost:$Le_TLSPort" "" 1 >/dev/null 2>&1
-      _get "https://localhost:$Le_TLSPort" "" 1 >/dev/null 2>&1
-    fi
-  fi
 }
 
 # sleep sec
@@ -2103,7 +2054,7 @@ _starttlsserver() {
     return 1
   fi
 
-  __S_OPENSSL="${ACME_OPENSSL_BIN:-openssl} s_server -cert $TLS_CERT  -key $TLS_KEY "
+  __S_OPENSSL="${ACME_OPENSSL_BIN:-openssl} s_server -www -cert $TLS_CERT  -key $TLS_KEY "
   if [ "$opaddr" ]; then
     __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port"
   else
@@ -2120,9 +2071,9 @@ _starttlsserver() {
 
   _debug "$__S_OPENSSL"
   if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
-    (printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $__S_OPENSSL -tlsextdebug) &
+    $__S_OPENSSL -tlsextdebug &
   else
-    (printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $__S_OPENSSL >/dev/null 2>&1) &
+    $__S_OPENSSL >/dev/null 2>&1 &
   fi
 
   serverproc="$!"
@@ -2298,6 +2249,7 @@ _initpath() {
     fi
   fi
 
+  _debug2 ACME_DIRECTORY "$ACME_DIRECTORY"
   _ACME_SERVER_HOST="$(echo "$ACME_DIRECTORY" | cut -d : -f 2 | tr -s / | cut -d / -f 2)"
   _debug2 "_ACME_SERVER_HOST" "$_ACME_SERVER_HOST"
 
@@ -2935,8 +2887,8 @@ _on_before_issue() {
   fi
 
   if _hasfield "$_chk_web_roots" "$NO_VALUE"; then
-    if ! _exists "nc"; then
-      _err "Please install netcat(nc) tools first."
+    if ! _exists "socat"; then
+      _err "Please install socat tools first."
       return 1
     fi
   fi
@@ -3042,6 +2994,10 @@ _on_issue_err() {
     )
   fi
 
+  if [ "$IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "dns"; then
+    _err "$_DNS_MANUAL_ERR"
+  fi
+
   if [ "$DEBUG" ] && [ "$DEBUG" -gt "0" ]; then
     _debug "$(_dlg_versions)"
   fi
@@ -3074,6 +3030,10 @@ _on_issue_success() {
     fi
   fi
 
+  if _hasfield "$Le_Webroot" "dns"; then
+    _err "$_DNS_MANUAL_WARN"
+  fi
+
 }
 
 updateaccount() {
@@ -3175,7 +3135,7 @@ _regAccount() {
       fi
       if [ "$code" = '202' ]; then
         _info "Update account tos info success."
-
+        echo "$response" >"$ACCOUNT_JSON_PATH"
         CA_KEY_HASH="$(__calcAccountKeyHash)"
         _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH"
         _savecaconf CA_KEY_HASH "$CA_KEY_HASH"
@@ -3649,13 +3609,12 @@ issue() {
         _info "Standalone mode server"
         _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")"
         _ncIndex="$(_math $_ncIndex + 1)"
-        _startserver "$keyauthorization" "$_ncaddr" &
+        _startserver "$keyauthorization" "$_ncaddr"
         if [ "$?" != "0" ]; then
           _clearup
           _on_issue_err "$_post_hook" "$vlist"
           return 1
         fi
-        serverproc="$!"
         sleep 1
         _debug serverproc "$serverproc"
       elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then
@@ -3990,7 +3949,10 @@ issue() {
   Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
   _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
 
-  _on_issue_success "$_post_hook" "$_renew_hook"
+  if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
+    _err "Call hook error."
+    return 1
+  fi
 
   if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
     _savedomainconf "Le_RealCertPath" "$_real_cert"
@@ -4417,15 +4379,19 @@ _installcert() {
 installcronjob() {
   _c_home="$1"
   _initpath
-  if ! _exists "crontab"; then
-    _err "crontab doesn't exist, so, we can not install cron jobs."
+  _CRONTAB="crontab"
+  if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
+    _CRONTAB="fcrontab"
+  fi
+  if ! _exists "$_CRONTAB"; then
+    _err "crontab/fcrontab doesn't exist, so, we can not install cron jobs."
     _err "All your certs will not be renewed automatically."
     _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' everyday."
     return 1
   fi
 
   _info "Installing cron job"
-  if ! crontab -l | grep "$PROJECT_ENTRY --cron"; then
+  if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then
     if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]; then
       lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY"
     else
@@ -4439,15 +4405,15 @@ installcronjob() {
     _t=$(_time)
     random_minute=$(_math $_t % 60)
     if _exists uname && uname -a | grep SunOS >/dev/null; then
-      crontab -l | {
+      $_CRONTAB -l | {
         cat
         echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
-      } | crontab --
+      } | $_CRONTAB --
     else
-      crontab -l | {
+      $_CRONTAB -l | {
         cat
         echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null"
-      } | crontab -
+      } | $_CRONTAB -
     fi
   fi
   if [ "$?" != "0" ]; then
@@ -4459,16 +4425,21 @@ installcronjob() {
 }
 
 uninstallcronjob() {
-  if ! _exists "crontab"; then
+  _CRONTAB="crontab"
+  if ! _exists "$_CRONTAB" && _exists "fcrontab"; then
+    _CRONTAB="fcrontab"
+  fi
+
+  if ! _exists "$_CRONTAB"; then
     return
   fi
   _info "Removing cron job"
-  cr="$(crontab -l | grep "$PROJECT_ENTRY --cron")"
+  cr="$($_CRONTAB -l | grep "$PROJECT_ENTRY --cron")"
   if [ "$cr" ]; then
     if _exists uname && uname -a | grep solaris >/dev/null; then
-      crontab -l | sed "/$PROJECT_ENTRY --cron/d" | crontab --
+      $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB --
     else
-      crontab -l | sed "/$PROJECT_ENTRY --cron/d" | crontab -
+      $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB -
     fi
     LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 9 | tr -d '"')"
     _info LE_WORKING_DIR "$LE_WORKING_DIR"
@@ -4745,7 +4716,7 @@ _precheck() {
   fi
 
   if [ -z "$_nocron" ]; then
-    if ! _exists "crontab"; then
+    if ! _exists "crontab" && ! _exists "fcrontab"; then
       _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'."
       _err "We need to set cron job to renew the certs automatically."
       _err "Otherwise, your certs will not be able to be renewed automatically."
@@ -4763,9 +4734,9 @@ _precheck() {
     return 1
   fi
 
-  if ! _exists "nc"; then
-    _err "It is recommended to install nc first, try to install 'nc' or 'netcat'."
-    _err "We use nc for standalone server if you use standalone mode."
+  if ! _exists "socat"; then
+    _err "It is recommended to install socat first."
+    _err "We use socat for standalone server if you use standalone mode."
     _err "If you don't use standalone mode, just ignore this warning."
   fi
 
@@ -4865,9 +4836,11 @@ install() {
     _debug "Skip install cron job"
   fi
 
-  if ! _precheck "$_nocron"; then
-    _err "Pre-check failed, can not install."
-    return 1
+  if [ "$IN_CRON" != "1" ]; then
+    if ! _precheck "$_nocron"; then
+      _err "Pre-check failed, can not install."
+      return 1
+    fi
   fi
 
   if [ -z "$_c_home" ] && [ "$LE_CONFIG_HOME" != "$LE_WORKING_DIR" ]; then
@@ -4920,7 +4893,9 @@ install() {
 
   _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY"
 
-  _installalias "$_c_home"
+  if [ "$IN_CRON" != "1" ]; then
+    _installalias "$_c_home"
+  fi
 
   for subf in $_SUB_FOLDERS; do
     if [ -d "$subf" ]; then
@@ -5010,7 +4985,7 @@ _uninstallalias() {
 }
 
 cron() {
-  IN_CRON=1
+  export IN_CRON=1
   _initpath
   _info "$(__green "===Starting cron===")"
   if [ "$AUTO_UPGRADE" = "1" ]; then

+ 41 - 5
deploy/README.md

@@ -4,7 +4,9 @@ Before you can deploy your cert, you must [issue the cert first](https://github.
 
 Here are the scripts to deploy the certs/key to the server/services.
 
-## 1. Deploy the certs to your cpanel host.
+## 1. Deploy the certs to your cpanel host
+
+If you want to deploy using cpanel UAPI see 7.
 
 (cpanel deploy hook is not finished yet, this is just an example.)
 
@@ -18,7 +20,7 @@ export DEPLOY_CPANEL_PASSWORD=PASSWORD
 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).
 Currently supports Kong-v0.10.x.
@@ -27,7 +29,7 @@ Currently supports Kong-v0.10.x.
 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
 
 The ssh deploy plugin allows you to deploy certificates to a remote host
 using SSH command to connect to the remote server.  The ssh plugin is invoked
@@ -170,7 +172,7 @@ export DEPLOY_SSH_BACKUP=no
 && service unifi restart
 ```
 
-## 4. Deploy the cert to local vsftpd server.
+## 4. Deploy the cert to local vsftpd server
 
 ```sh
 acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd
@@ -192,7 +194,7 @@ export DEPLOY_VSFTPD_RELOAD="/etc/init.d/vsftpd restart"
 acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd
 ```
 
-## 5. Deploy the cert to local exim4 server.
+## 5. Deploy the cert to local exim4 server
 
 ```sh
 acme.sh --deploy -d ftp.example.com --deploy-hook exim4
@@ -219,3 +221,37 @@ acme.sh --deploy -d ftp.example.com --deploy-hook exim4
 ```sh
 acme.sh --deploy -d ftp.example.com --deploy-hook keychain
 ```
+
+## 7. Deploy to cpanel host using UAPI
+
+This hook is using UAPI and works in cPanel & WHM version 56 or newer.
+```
+acme.sh  --deploy  -d example.com  --deploy-hook cpanel_uapi
+```
+DEPLOY_CPANEL_USER is required only if you run the script as root and it should contain cpanel username.
+```sh
+export DEPLOY_CPANEL_USER=username
+acme.sh  --deploy  -d example.com  --deploy-hook cpanel_uapi
+```
+Please note, that the cpanel_uapi hook will deploy only the first domain when your certificate will automatically renew. Therefore you should issue a separete certificate for each domain. 
+
+## 8. Deploy the cert to your FRITZ!Box router
+
+You must specify the credentials that have administrative privileges on the FRITZ!Box in order to deploy the certificate, plus the URL of your FRITZ!Box, through the following environment variables:
+```sh
+$ export DEPLOY_FRITZBOX_USERNAME=my_username
+$ export DEPLOY_FRITZBOX_PASSWORD=the_password
+$ export DEPLOY_FRITZBOX_URL=https://fritzbox.example.com
+```
+
+After the first deployment, these values will be stored in your $HOME/.acme.sh/account.conf. You may now deploy the certificate like this:
+
+```sh
+acme.sh --deploy -d fritzbox.example.com --deploy-hook fritzbox
+```
+
+## 9. Deploy the cert to strongswan
+
+```sh
+acme.sh --deploy -d ftp.example.com --deploy-hook strongswan
+```

+ 0 - 29
deploy/cpanel.sh

@@ -1,29 +0,0 @@
-#!/usr/bin/env sh
-
-#Here is the script to deploy the cert to your cpanel account by the cpanel APIs.
-
-#returns 0 means success, otherwise error.
-
-#export DEPLOY_CPANEL_USER=myusername
-#export DEPLOY_CPANEL_PASSWORD=PASSWORD
-
-########  Public functions #####################
-
-#domain keyfile certfile cafile fullchain
-cpanel_deploy() {
-  _cdomain="$1"
-  _ckey="$2"
-  _ccert="$3"
-  _cca="$4"
-  _cfullchain="$5"
-
-  _debug _cdomain "$_cdomain"
-  _debug _ckey "$_ckey"
-  _debug _ccert "$_ccert"
-  _debug _cca "$_cca"
-  _debug _cfullchain "$_cfullchain"
-
-  _err "Not implemented yet"
-  return 1
-
-}

+ 64 - 0
deploy/cpanel_uapi.sh

@@ -0,0 +1,64 @@
+#!/usr/bin/env sh
+# Here is the script to deploy the cert to your cpanel using the cpanel API.
+# Uses command line uapi.  --user option is needed only if run as root.
+# Returns 0 when success.
+# Written by Santeri Kannisto <santeri.kannisto@2globalnomads.info>
+# Public domain, 2017
+
+#export DEPLOY_CPANEL_USER=myusername
+
+########  Public functions #####################
+
+#domain keyfile certfile cafile fullchain
+
+cpanel_uapi_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  if ! _exists uapi; then
+    _err "The command uapi is not found."
+    return 1
+  fi
+  if ! _exists php; then
+    _err "The command php is not found."
+    return 1
+  fi
+  # read cert and key files and urlencode both
+  _certstr=$(cat "$_ccert")
+  _keystr=$(cat "$_ckey")
+  _cert=$(php -r "echo urlencode(\"$_certstr\");")
+  _key=$(php -r "echo urlencode(\"$_keystr\");")
+
+  _debug _cert "$_cert"
+  _debug _key "$_key"
+
+  if [ "$(id -u)" = 0 ]; then
+    if [ -z "$DEPLOY_CPANEL_USER" ]; then
+      _err "It seems that you are root, please define the target user name: export DEPLOY_CPANEL_USER=username"
+      return 1
+    fi
+    _savedomainconf DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER"
+    _response=$(uapi --user="$DEPLOY_CPANEL_USER" SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
+  else
+    _response=$(uapi SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key")
+  fi
+  error_response="status: 0"
+  if test "${_response#*$error_response}" != "$_response"; then
+    _err "Error in deploying certificate:"
+    _err "$_response"
+    return 1
+  fi
+
+  _debug response "$_response"
+  _info "Certificate successfully deployed"
+  return 0
+}

+ 108 - 0
deploy/fritzbox.sh

@@ -0,0 +1,108 @@
+#!/usr/bin/env sh
+
+#Here is a script to deploy cert to an AVM FRITZ!Box router.
+
+#returns 0 means success, otherwise error.
+
+#DEPLOY_FRITZBOX_USERNAME="username"
+#DEPLOY_FRITZBOX_PASSWORD="password"
+#DEPLOY_FRITZBOX_URL="https://fritz.box"
+
+# Kudos to wikrie at Github for his FRITZ!Box update script:
+# https://gist.github.com/wikrie/f1d5747a714e0a34d0582981f7cb4cfb
+
+########  Public functions #####################
+
+#domain keyfile certfile cafile fullchain
+fritzbox_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  if ! _exists iconv; then
+    _err "iconv not found"
+    return 1
+  fi
+
+  _fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}"
+  _fritzbox_password="${DEPLOY_FRITZBOX_PASSWORD}"
+  _fritzbox_url="${DEPLOY_FRITZBOX_URL}"
+
+  _debug _fritzbox_url "$_fritzbox_url"
+  _debug _fritzbox_username "$_fritzbox_username"
+  _secure_debug _fritzbox_password "$_fritzbox_password"
+  if [ -z "$_fritzbox_username" ]; then
+    _err "FRITZ!Box username is not found, please define DEPLOY_FRITZBOX_USERNAME."
+    return 1
+  fi
+  if [ -z "$_fritzbox_password" ]; then
+    _err "FRITZ!Box password is not found, please define DEPLOY_FRITZBOX_PASSWORD."
+    return 1
+  fi
+  if [ -z "$_fritzbox_url" ]; then
+    _err "FRITZ!Box url is not found, please define DEPLOY_FRITZBOX_URL."
+    return 1
+  fi
+
+  _saveaccountconf DEPLOY_FRITZBOX_USERNAME "${_fritzbox_username}"
+  _saveaccountconf DEPLOY_FRITZBOX_PASSWORD "${_fritzbox_password}"
+  _saveaccountconf DEPLOY_FRITZBOX_URL "${_fritzbox_url}"
+
+  # Do not check for a valid SSL certificate, because initially the cert is not valid, so it could not install the LE generated certificate
+  export HTTPS_INSECURE=1
+
+  _info "Log in to the FRITZ!Box"
+  _fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*<Challenge>//' -e 's/<\/Challenge>.*$//')"
+  _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')"
+  _fritzbox_sid="$(_get "${_fritzbox_url}/login_sid.lua?sid=0000000000000000&username=${_fritzbox_username}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*<SID>//' -e 's/<\/SID>.*$//')"
+
+  if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then
+    _err "Logging in to the FRITZ!Box failed. Please check username, password and URL."
+    return 1
+  fi
+
+  _info "Generate form POST request"
+  _post_request="$(_mktemp)"
+  _post_boundary="---------------------------$(date +%Y%m%d%H%M%S)"
+  # _CERTPASSWORD_ is unset because Let's Encrypt certificates don't have a password. But if they ever do, here's the place to use it!
+  _CERTPASSWORD_=
+  {
+    printf -- "--"
+    printf -- "%s\r\n" "${_post_boundary}"
+    printf "Content-Disposition: form-data; name=\"sid\"\r\n\r\n%s\r\n" "${_fritzbox_sid}"
+    printf -- "--"
+    printf -- "%s\r\n" "${_post_boundary}"
+    printf "Content-Disposition: form-data; name=\"BoxCertPassword\"\r\n\r\n%s\r\n" "${_CERTPASSWORD_}"
+    printf -- "--"
+    printf -- "%s\r\n" "${_post_boundary}"
+    printf "Content-Disposition: form-data; name=\"BoxCertImportFile\"; filename=\"BoxCert.pem\"\r\n"
+    printf "Content-Type: application/octet-stream\r\n\r\n"
+    cat "${_ckey}" "${_cfullchain}"
+    printf "\r\n"
+    printf -- "--"
+    printf -- "%s--" "${_post_boundary}"
+  } >>"${_post_request}"
+
+  _info "Upload certificate to the FRITZ!Box"
+
+  export _H1="Content-type: multipart/form-data boundary=${_post_boundary}"
+  _post "$(cat "${_post_request}")" "${_fritzbox_url}/cgi-bin/firmwarecfg" | grep SSL
+
+  retval=$?
+  if [ $retval = 0 ]; then
+    _info "Upload successful"
+  else
+    _err "Upload failed"
+  fi
+  rm "${_post_request}"
+
+  return $retval
+}

+ 32 - 0
deploy/strongswan.sh

@@ -0,0 +1,32 @@
+#!/usr/bin/env sh
+
+#Here is a sample custom api script.
+#This file name is "myapi.sh"
+#So, here must be a method   myapi_deploy()
+#Which will be called by acme.sh to deploy the cert
+#returns 0 means success, otherwise error.
+
+########  Public functions #####################
+
+#domain keyfile certfile cafile fullchain
+strongswan_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  cat "$_ckey" >"/etc/ipsec.d/private/$(basename "$_ckey")"
+  cat "$_ccert" >"/etc/ipsec.d/certs/$(basename "$_ccert")"
+  cat "$_cca" >"/etc/ipsec.d/cacerts/$(basename "$_cca")"
+  cat "$_cfullchain" >"/etc/ipsec.d/cacerts/$(basename "$_cfullchain")"
+
+  ipsec reload
+
+}

+ 2 - 4
dnsapi/README.md

@@ -420,6 +420,7 @@ Ok, let's issue a cert now:
 ```
 acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com
 ```
+The `CLOUDNS_AUTH_ID` and `CLOUDNS_AUTH_PASSWORD` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 ## 22. Use Infoblox API
 
@@ -512,14 +513,11 @@ export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
 ```
 
 Please note that since DuckDNS uses StartSSL as their cert provider, thus 
---insecure must be used when issuing certs:
+--insecure may need to be used when issuing certs:
 ```
 acme.sh --insecure --issue --dns dns_duckdns -d mydomain.duckdns.org
 ```
 
-Also, DuckDNS uses the domain name as username for recording changing, so the
-account file will always store the lastly used domain name.
-
 For issues, please report to https://github.com/raidenii/acme.sh/issues.
 
 ## 28. Use Name.com API

+ 10 - 9
dnsapi/dns_aws.sh

@@ -87,6 +87,7 @@ _get_root() {
     _debug "response" "$response"
     while true; do
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+      _debug2 "Checking domain: $h"
       if [ -z "$h" ]; then
         if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then
           _debug "IsTruncated"
@@ -102,23 +103,23 @@ _get_root() {
           fi
         fi
         #not valid
+        _err "Invalid domain"
         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>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>")"
         _debug hostedzone "$hostedzone"
-        if [ -z "$hostedzone" ]; then
-          _err "Error, can not get hostedzone."
+        if [ "$hostedzone" ]; then
+          _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>")
+          if [ "$_domain_id" ]; then
+            _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+            _domain=$h
+            return 0
+          fi
+          _err "Can not find domain id: $h"
           return 1
         fi
-        _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | 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)

+ 14 - 0
dnsapi/dns_cloudns.sh

@@ -96,6 +96,16 @@ _dns_cloudns_init_check() {
     return 0
   fi
 
+  CLOUDNS_AUTH_ID="${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}"
+  CLOUDNS_AUTH_PASSWORD="${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}"
+  if [ -z "$CLOUDNS_AUTH_ID" ] || [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then
+    CLOUDNS_AUTH_ID=""
+    CLOUDNS_AUTH_PASSWORD=""
+    _err "You don't specify cloudns api id and password yet."
+    _err "Please create you id and password and try again."
+    return 1
+  fi
+
   if [ -z "$CLOUDNS_AUTH_ID" ]; then
     _err "CLOUDNS_AUTH_ID is not configured"
     return 1
@@ -113,6 +123,10 @@ _dns_cloudns_init_check() {
     return 1
   fi
 
+  #save the api id and password to the account conf file.
+  _saveaccountconf_mutable CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID"
+  _saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD"
+
   CLOUDNS_INIT_CHECK_COMPLETED=1
 
   return 0

+ 58 - 21
dnsapi/dns_duckdns.sh

@@ -3,11 +3,14 @@
 #Created by RaidenII, to use DuckDNS's API to add/remove text records
 #06/27/2017
 
-# Currently only support single domain access
-# Due to the fact that DuckDNS uses StartSSL as cert provider, --insecure must be used with acme.sh
+# Pass credentials before "acme.sh --issue --dns dns_duckdns ..."
+# --
+# export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
+# --
+#
+# Due to the fact that DuckDNS uses StartSSL as cert provider, --insecure may need to be used with acme.sh
 
 DuckDNS_API="https://www.duckdns.org/update"
-API_Params="domains=$DuckDNS_Domain&token=$DuckDNS_Token"
 
 ########  Public functions #####################
 
@@ -16,35 +19,36 @@ dns_duckdns_add() {
   fulldomain=$1
   txtvalue=$2
 
-  # We'll extract the domain/username from full domain
-  DuckDNS_Domain=$(echo "$fulldomain" | _lower_case | _egrep_o '.[^.]*.duckdns.org' | cut -d . -f 2)
-
-  if [ -z "$DuckDNS_Domain" ]; then
-    _err "Error extracting the domain."
-    return 1
-  fi
-
+  DuckDNS_Token="${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}"
   if [ -z "$DuckDNS_Token" ]; then
-    DuckDNS_Token=""
+    _err "You must export variable: DuckDNS_Token"
     _err "The token for your DuckDNS account is necessary."
     _err "You can look it up in your DuckDNS account."
     return 1
   fi
 
   # Now save the credentials.
-  _saveaccountconf DuckDNS_Domain "$DuckDNS_Domain"
-  _saveaccountconf DuckDNS_Token "$DuckDNS_Token"
+  _saveaccountconf_mutable DuckDNS_Token "$DuckDNS_Token"
 
   # Unfortunately, DuckDNS does not seems to support lookup domain through API
   # So I assume your credentials (which are your domain and token) are correct
   # If something goes wrong, we will get a KO response from DuckDNS
 
+  if ! _duckdns_get_domain; then
+    return 1
+  fi
+
   # Now add the TXT record to DuckDNS
   _info "Trying to add TXT record"
-  if _duckdns_rest GET "$API_Params&txt=$txtvalue" && [ "$response" = "OK" ]; then
-    _info "TXT record has been successfully added to your DuckDNS domain."
-    _info "Note that all subdomains under this domain uses the same TXT record."
-    return 0
+  if _duckdns_rest GET "domains=$_duckdns_domain&token=$DuckDNS_Token&txt=$txtvalue"; then
+    if [ "$response" = "OK" ]; then
+      _info "TXT record has been successfully added to your DuckDNS domain."
+      _info "Note that all subdomains under this domain uses the same TXT record."
+      return 0
+    else
+      _err "Errors happened during adding the TXT record, response=$response"
+      return 1
+    fi
   else
     _err "Errors happened during adding the TXT record."
     return 1
@@ -57,11 +61,28 @@ dns_duckdns_rm() {
   fulldomain=$1
   txtvalue=$2
 
+  DuckDNS_Token="${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}"
+  if [ -z "$DuckDNS_Token" ]; then
+    _err "You must export variable: DuckDNS_Token"
+    _err "The token for your DuckDNS account is necessary."
+    _err "You can look it up in your DuckDNS account."
+    return 1
+  fi
+
+  if ! _duckdns_get_domain; then
+    return 1
+  fi
+
   # Now remove the TXT record from DuckDNS
   _info "Trying to remove TXT record"
-  if _duckdns_rest GET "$API_Params&txt=&clear=true" && [ "$response" = "OK" ]; then
-    _info "TXT record has been successfully removed from your DuckDNS domain."
-    return 0
+  if _duckdns_rest GET "domains=$_duckdns_domain&token=$DuckDNS_Token&txt=&clear=true"; then
+    if [ "$response" = "OK" ]; then
+      _info "TXT record has been successfully removed from your DuckDNS domain."
+      return 0
+    else
+      _err "Errors happened during removing the TXT record, response=$response"
+      return 1
+    fi
   else
     _err "Errors happened during removing the TXT record."
     return 1
@@ -70,6 +91,22 @@ dns_duckdns_rm() {
 
 ####################  Private functions below ##################################
 
+#fulldomain=_acme-challenge.domain.duckdns.org
+#returns
+# _duckdns_domain=domain
+_duckdns_get_domain() {
+
+  # We'll extract the domain/username from full domain
+  _duckdns_domain="$(printf "%s" "$fulldomain" | _lower_case | _egrep_o '[.][^.][^.]*[.]duckdns.org' | cut -d . -f 2)"
+
+  if [ -z "$_duckdns_domain" ]; then
+    _err "Error extracting the domain."
+    return 1
+  fi
+
+  return 0
+}
+
 #Usage: method URI
 _duckdns_rest() {
   method=$1

+ 1 - 1
dnsapi/dns_gandi_livedns.sh

@@ -11,7 +11,7 @@
 #
 ########  Public functions #####################
 
-GANDI_LIVEDNS_API="https://dns.beta.gandi.net/api/v5"
+GANDI_LIVEDNS_API="https://dns.api.gandi.net/api/v5"
 
 #Usage: dns_gandi_livedns_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_gandi_livedns_add() {

+ 2 - 2
dnsapi/dns_he.sh

@@ -47,7 +47,7 @@ dns_he_add() {
   response="$(_post "$body" "https://dns.he.net/")"
   exit_code="$?"
   if [ "$exit_code" -eq 0 ]; then
-    _info "TXT record added successfuly."
+    _info "TXT record added successfully."
   else
     _err "Couldn't add the TXT record."
   fi
@@ -96,7 +96,7 @@ dns_he_rm() {
       >/dev/null
   exit_code="$?"
   if [ "$exit_code" -eq 0 ]; then
-    _info "Record removed successfuly."
+    _info "Record removed successfully."
   else
     _err "Could not clean (remove) up the record. Please go to HE administration interface and clean it by hand."
     return "$exit_code"