Browse Source

Merge branch 'dev' into dev

linux-insideDE 7 years ago
parent
commit
dc267663a7
15 changed files with 915 additions and 103 deletions
  1. 4 1
      README.md
  2. 22 22
      acme.sh
  3. 20 0
      deploy/README.md
  4. 8 10
      deploy/cpanel_uapi.sh
  5. 34 2
      deploy/haproxy.sh
  6. 3 3
      deploy/ssh.sh
  7. 63 2
      dnsapi/README.md
  8. 7 7
      dnsapi/dns_aws.sh
  9. 161 0
      dnsapi/dns_dpi.sh
  10. 358 0
      dnsapi/dns_euserv.sh
  11. 167 0
      dnsapi/dns_gcloud.sh
  12. 4 0
      dnsapi/dns_gd.sh
  13. 2 2
      dnsapi/dns_ispconfig.sh
  14. 40 15
      dnsapi/dns_lexicon.sh
  15. 22 39
      dnsapi/dns_unoeuro.sh

+ 4 - 1
README.md

@@ -33,7 +33,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 - [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt)
 - [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt)
 - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty)
 - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty)
 - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709)
 - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709)
-- [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html)
+- [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html)
 - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
 - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297)
 - [archlinux](https://aur.archlinux.org/packages/acme.sh-git/)
 - [archlinux](https://aur.archlinux.org/packages/acme.sh-git/)
 - [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
 - [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
@@ -320,6 +320,9 @@ You don't have to do anything manually!
 1. Loopia.se API
 1. Loopia.se API
 1. acme-dns (https://github.com/joohoi/acme-dns)
 1. acme-dns (https://github.com/joohoi/acme-dns)
 1. TELE3 (https://www.tele3.cz)
 1. TELE3 (https://www.tele3.cz)
+1. EUSERV.EU (https://www.euserv.eu)
+1. DNSPod.com API (https://www.dnspod.com)
+1. Google Cloud DNS API
 1. netcup DNS API (https://www.netcup.de)
 1. netcup DNS API (https://www.netcup.de)
 
 
 And: 
 And: 

+ 22 - 22
acme.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 #!/usr/bin/env sh
 
 
-VER=2.7.9
+VER=2.8.0
 
 
 PROJECT_NAME="acme.sh"
 PROJECT_NAME="acme.sh"
 
 
@@ -1327,6 +1327,7 @@ createDomainKey() {
     if _createkey "$_cdl" "$CERT_KEY_PATH"; then
     if _createkey "$_cdl" "$CERT_KEY_PATH"; then
       _savedomainconf Le_Keylength "$_cdl"
       _savedomainconf Le_Keylength "$_cdl"
       _info "The domain key is here: $(__green $CERT_KEY_PATH)"
       _info "The domain key is here: $(__green $CERT_KEY_PATH)"
+      return 0
     fi
     fi
   else
   else
     if [ "$IS_RENEW" ]; then
     if [ "$IS_RENEW" ]; then
@@ -1374,17 +1375,17 @@ _url_replace() {
 }
 }
 
 
 _time2str() {
 _time2str() {
-  #Linux
-  if date -u -d@"$1" 2>/dev/null; then
+  #BSD
+  if date -u -r "$1" 2>/dev/null; then
     return
     return
   fi
   fi
 
 
-  #BSD
-  if date -u -r "$1" 2>/dev/null; then
+  #Linux
+  if date -u -d@"$1" 2>/dev/null; then
     return
     return
   fi
   fi
 
 
-  #Soaris
+  #Solaris
   if _exists adb; then
   if _exists adb; then
     _t_s_a=$(echo "0t${1}=Y" | adb)
     _t_s_a=$(echo "0t${1}=Y" | adb)
     echo "$_t_s_a"
     echo "$_t_s_a"
@@ -1607,7 +1608,7 @@ _inithttp() {
 
 
 }
 }
 
 
-# body  url [needbase64] [POST|PUT] [ContentType]
+# body  url [needbase64] [POST|PUT|DELETE] [ContentType]
 _post() {
 _post() {
   body="$1"
   body="$1"
   _post_url="$2"
   _post_url="$2"
@@ -1795,15 +1796,13 @@ _send_signed_request() {
     return 1
     return 1
   fi
   fi
 
 
-  if [ "$ACME_VERSION" = "2" ]; then
-    __request_conent_type="$CONTENT_TYPE_JSON"
-  else
-    __request_conent_type=""
-  fi
+  __request_conent_type="$CONTENT_TYPE_JSON"
+
   payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
   payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
   _debug3 payload64 "$payload64"
   _debug3 payload64 "$payload64"
 
 
-  MAX_REQUEST_RETRY_TIMES=5
+  MAX_REQUEST_RETRY_TIMES=20
+  _sleep_retry_sec=1
   _request_retry_times=0
   _request_retry_times=0
   while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
   while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
     _request_retry_times=$(_math "$_request_retry_times" + 1)
     _request_retry_times=$(_math "$_request_retry_times" + 1)
@@ -1897,9 +1896,10 @@ _send_signed_request() {
       _debug3 _body "$_body"
       _debug3 _body "$_body"
     fi
     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."
-      _sleep 5
+    if _contains "$_body" "JWS has invalid anti-replay nonce" || _contains "$_body" "JWS has an invalid anti-replay nonce"; then
+      _info "It seems the CA server is busy now, let's wait and retry. Sleeping $_sleep_retry_sec seconds."
+      _CACHED_NONCE=""
+      _sleep $_sleep_retry_sec
       continue
       continue
     fi
     fi
     break
     break
@@ -4676,19 +4676,19 @@ _installcert() {
     if [ -f "$_real_cert" ] && [ ! "$IS_RENEW" ]; then
     if [ -f "$_real_cert" ] && [ ! "$IS_RENEW" ]; then
       cp "$_real_cert" "$_backup_path/cert.bak"
       cp "$_real_cert" "$_backup_path/cert.bak"
     fi
     fi
-    cat "$CERT_PATH" >"$_real_cert"
+    cat "$CERT_PATH" >"$_real_cert" || return 1
   fi
   fi
 
 
   if [ "$_real_ca" ]; then
   if [ "$_real_ca" ]; then
     _info "Installing CA to:$_real_ca"
     _info "Installing CA to:$_real_ca"
     if [ "$_real_ca" = "$_real_cert" ]; then
     if [ "$_real_ca" = "$_real_cert" ]; then
       echo "" >>"$_real_ca"
       echo "" >>"$_real_ca"
-      cat "$CA_CERT_PATH" >>"$_real_ca"
+      cat "$CA_CERT_PATH" >>"$_real_ca" || return 1
     else
     else
       if [ -f "$_real_ca" ] && [ ! "$IS_RENEW" ]; then
       if [ -f "$_real_ca" ] && [ ! "$IS_RENEW" ]; then
         cp "$_real_ca" "$_backup_path/ca.bak"
         cp "$_real_ca" "$_backup_path/ca.bak"
       fi
       fi
-      cat "$CA_CERT_PATH" >"$_real_ca"
+      cat "$CA_CERT_PATH" >"$_real_ca" || return 1
     fi
     fi
   fi
   fi
 
 
@@ -4698,9 +4698,9 @@ _installcert() {
       cp "$_real_key" "$_backup_path/key.bak"
       cp "$_real_key" "$_backup_path/key.bak"
     fi
     fi
     if [ -f "$_real_key" ]; then
     if [ -f "$_real_key" ]; then
-      cat "$CERT_KEY_PATH" >"$_real_key"
+      cat "$CERT_KEY_PATH" >"$_real_key" || return 1
     else
     else
-      cat "$CERT_KEY_PATH" >"$_real_key"
+      cat "$CERT_KEY_PATH" >"$_real_key" || return 1
       chmod 600 "$_real_key"
       chmod 600 "$_real_key"
     fi
     fi
   fi
   fi
@@ -4710,7 +4710,7 @@ _installcert() {
     if [ -f "$_real_fullchain" ] && [ ! "$IS_RENEW" ]; then
     if [ -f "$_real_fullchain" ] && [ ! "$IS_RENEW" ]; then
       cp "$_real_fullchain" "$_backup_path/fullchain.bak"
       cp "$_real_fullchain" "$_backup_path/fullchain.bak"
     fi
     fi
-    cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain"
+    cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
   fi
   fi
 
 
   if [ "$_reload_cmd" ]; then
   if [ "$_reload_cmd" ]; then

+ 20 - 0
deploy/README.md

@@ -255,3 +255,23 @@ acme.sh --deploy -d fritzbox.example.com --deploy-hook fritzbox
 ```sh
 ```sh
 acme.sh --deploy -d ftp.example.com --deploy-hook strongswan
 acme.sh --deploy -d ftp.example.com --deploy-hook strongswan
 ```
 ```
+
+## 10. Deploy the cert to HAProxy
+
+You must specify the path where you want the concatenated key and certificate chain written.
+```sh
+export DEPLOY_HAPROXY_PEM_PATH=/etc/haproxy
+```
+
+You may optionally define the command to reload HAProxy. The value shown below will be used as the default if you don't set this environment variable.
+
+```sh
+export DEPLOY_HAPROXY_RELOAD="/usr/sbin/service haproxy restart"
+```
+
+You can then deploy the certificate as follows
+```sh
+acme.sh --deploy -d haproxy.example.com --deploy-hook haproxy
+```
+
+The path for the PEM file will be stored with the domain configuration and will be available when renewing, so that deploy will happen automatically when renewed.

+ 8 - 10
deploy/cpanel_uapi.sh

@@ -2,8 +2,12 @@
 # Here is the script to deploy the cert to your cpanel using the cpanel API.
 # 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.
 # Uses command line uapi.  --user option is needed only if run as root.
 # Returns 0 when success.
 # Returns 0 when success.
-# Written by Santeri Kannisto <santeri.kannisto@2globalnomads.info>
-# Public domain, 2017
+#
+# Please note that I am no longer using Github. If you want to report an issue
+# or contact me, visit https://forum.webseodesigners.com/web-design-seo-and-hosting-f16/
+#
+# Written by Santeri Kannisto <santeri.kannisto@webseodesigners.com>
+# Public domain, 2017-2018
 
 
 #export DEPLOY_CPANEL_USER=myusername
 #export DEPLOY_CPANEL_USER=myusername
 
 
@@ -28,15 +32,9 @@ cpanel_uapi_deploy() {
     _err "The command uapi is not found."
     _err "The command uapi is not found."
     return 1
     return 1
   fi
   fi
-  if ! _exists php; then
-    _err "The command php is not found."
-    return 1
-  fi
   # read cert and key files and urlencode both
   # 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\");")
+  _cert=$(_url_encode <"$_ccert")
+  _key=$(_url_encode <"$_ckey")
 
 
   _debug _cert "$_cert"
   _debug _cert "$_cert"
   _debug _key "$_key"
   _debug _key "$_key"

+ 34 - 2
deploy/haproxy.sh

@@ -20,7 +20,39 @@ haproxy_deploy() {
   _debug _cca "$_cca"
   _debug _cca "$_cca"
   _debug _cfullchain "$_cfullchain"
   _debug _cfullchain "$_cfullchain"
 
 
-  _err "deploy cert to haproxy server, Not implemented yet"
-  return 1
+  # handle reload preference
+  DEFAULT_HAPROXY_RELOAD="/usr/sbin/service haproxy restart"
+  if [ -z "${DEPLOY_HAPROXY_RELOAD}" ]; then
+    _reload="${DEFAULT_HAPROXY_RELOAD}"
+    _cleardomainconf DEPLOY_HAPROXY_RELOAD
+  else
+    _reload="${DEPLOY_HAPROXY_RELOAD}"
+    _savedomainconf DEPLOY_HAPROXY_RELOAD "$DEPLOY_HAPROXY_RELOAD"
+  fi
+  _savedomainconf DEPLOY_HAPROXY_PEM_PATH "$DEPLOY_HAPROXY_PEM_PATH"
+
+  # work out the path where the PEM file should go
+  _pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
+  if [ -z "$_pem_path" ]; then
+    _err "Path to save PEM file not found. Please define DEPLOY_HAPROXY_PEM_PATH."
+    return 1
+  fi
+  _pem_full_path="$_pem_path/$_cdomain.pem"
+  _info "Full path to PEM $_pem_full_path"
+
+  # combine the key and fullchain into a single pem and install
+  cat "$_cfullchain" "$_ckey" >"$_pem_full_path"
+  chmod 600 "$_pem_full_path"
+  _info "Certificate successfully deployed"
+
+  # restart HAProxy
+  _info "Run reload: $_reload"
+  if eval "$_reload"; then
+    _info "Reload success!"
+    return 0
+  else
+    _err "Reload error"
+    return 1
+  fi
 
 
 }
 }

+ 3 - 3
deploy/ssh.sh

@@ -11,7 +11,7 @@
 #
 #
 # Only a username is required.  All others are optional.
 # Only a username is required.  All others are optional.
 #
 #
-# The following examples are for QNAP NAS running QTS 4.2 
+# The following examples are for QNAP NAS running QTS 4.2
 # export DEPLOY_SSH_CMD=""  # defaults to ssh
 # export DEPLOY_SSH_CMD=""  # defaults to ssh
 # export DEPLOY_SSH_USER="admin"  # required
 # export DEPLOY_SSH_USER="admin"  # required
 # export DEPLOY_SSH_SERVER="qnap"  # defaults to domain name
 # export DEPLOY_SSH_SERVER="qnap"  # defaults to domain name
@@ -101,7 +101,7 @@ ssh_deploy() {
   fi
   fi
 
 
   # CERTFILE is optional.
   # CERTFILE is optional.
-  # If provided then private key will be copied or appended to provided filename.
+  # If provided then certificate will be copied or appended to provided filename.
   if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
   if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
     Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
     Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
     _savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
     _savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
@@ -190,7 +190,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
     _info "Backup directories erased after 180 days."
     _info "Backup directories erased after 180 days."
   fi
   fi
 
 
-  _debug "Remote commands to execute: $_cmdstr"
+  _secure_debug "Remote commands to execute: " "$_cmdstr"
   _info "Submitting sequence of commands to remote server by ssh"
   _info "Submitting sequence of commands to remote server by ssh"
   # quotations in bash cmd below intended.  Squash travis spellcheck error
   # quotations in bash cmd below intended.  Squash travis spellcheck error
   # shellcheck disable=SC2029
   # shellcheck disable=SC2029

+ 63 - 2
dnsapi/README.md

@@ -876,9 +876,69 @@ acme.sh --issue --dns dns_tele3 -d example.com -d *.example.com
 ```
 ```
 
 
 The TELE3_Key and TELE3_Secret will be saved in ~/.acme.sh/account.conf and will be reused when needed.
 The TELE3_Key and TELE3_Secret will be saved in ~/.acme.sh/account.conf and will be reused when needed.
-## 47. Use netcup DNS API to automatically issue cert
 
 
-First you need to login to your CCP account to get your API Key and API Password.
+## 47. Use Euserv.eu API
+
+First you need to login to your euserv.eu account and activate your API Administration (API Verwaltung).
+[https://support.euserv.com](https://support.euserv.com)
+
+Once you've activate, login to your API Admin Interface and create an API account.
+Please specify the scope (active groups: domain) and assign the allowed IPs.
+
+```
+export EUSERV_Username="99999.user123"
+export EUSERV_Password="Asbe54gHde"
+```
+
+Ok, let's issue a cert now: (Be aware to use the `--insecure` flag, cause euserv.eu is still using self-signed certificates!)
+```
+acme.sh --issue --dns dns_euserv -d example.com -d *.example.com --insecure
+```
+
+The `EUSERV_Username` and `EUSERV_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+Please report any issues to https://github.com/initit/acme.sh or to <github@initit.de>
+
+## 48. Use DNSPod.com domain API to automatically issue cert
+
+First you need to get your API Key and ID by this [get-the-user-token](https://www.dnspod.com/docs/info.html#get-the-user-token).
+
+```
+export DPI_Id="1234"
+export DPI_Key="sADDsdasdgdsf"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_dpi -d example.com -d www.example.com
+```
+
+The `DPI_Id` and `DPI_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 49. Use Google Cloud DNS API to automatically issue cert
+
+First you need to authenticate to gcloud.
+
+```
+gcloud init
+```
+
+**The `dns_gcloud` script uses the active gcloud configuration and credentials.**
+There is no logic inside `dns_gcloud` to override the project and other settings.
+If needed, create additional [gcloud configurations](https://cloud.google.com/sdk/gcloud/reference/topic/configurations).
+You can change the configuration being used without *activating* it; simply set the `CLOUDSDK_ACTIVE_CONFIG_NAME` environment variable.
+
+To issue a certificate you can:
+```
+export CLOUDSDK_ACTIVE_CONFIG_NAME=default  # see the note above
+acme.sh --issue --dns dns_gcloud -d example.com -d '*.example.com'
+```
+
+`dns_gcloud` also supports [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode).
+
+## 50. Use netcup DNS API to automatically issue cert
+
+First you need to login in your CCP account to get your API Key and API Password.
 ```
 ```
 export NC_Apikey="<Apikey>"
 export NC_Apikey="<Apikey>"
 export NC_Apipw="<Apipassword>"
 export NC_Apipw="<Apipassword>"
@@ -891,6 +951,7 @@ acme.sh --issue --dns dns_netcup -d example.com -d www.example.com
 ```
 ```
 
 
 The `NC_Apikey`,`NC_Apipw` and `NC_CID` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 The `NC_Apikey`,`NC_Apipw` and `NC_CID` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
 # Use custom API
 # Use custom API
 
 
 If your API is not supported yet, you can write your own DNS API.
 If your API is not supported yet, you can write your own DNS API.

+ 7 - 7
dnsapi/dns_aws.sh

@@ -29,7 +29,7 @@ dns_aws_add() {
   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
     AWS_ACCESS_KEY_ID=""
     AWS_ACCESS_KEY_ID=""
     AWS_SECRET_ACCESS_KEY=""
     AWS_SECRET_ACCESS_KEY=""
-    _err "You don't specify aws route53 api key id and and api key secret yet."
+    _err "You haven't specifed the aws route53 api key id and and api key secret yet."
     _err "Please create your key and try again. see $(__green $AWS_WIKI)"
     _err "Please create your key and try again. see $(__green $AWS_WIKI)"
     return 1
     return 1
   fi
   fi
@@ -62,7 +62,7 @@ dns_aws_add() {
   fi
   fi
 
 
   if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then
   if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then
-    _info "The txt record already exists, skip"
+    _info "The TXT record already exists. Skipping."
     return 0
     return 0
   fi
   fi
 
 
@@ -71,7 +71,7 @@ dns_aws_add() {
   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords>$_resource_record<ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>UPSERT</Action><ResourceRecordSet><Name>$fulldomain</Name><Type>TXT</Type><TTL>300</TTL><ResourceRecords>$_resource_record<ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></ResourceRecords></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
 
 
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
-    _info "txt record updated success."
+    _info "TXT record updated successfully."
     return 0
     return 0
   fi
   fi
 
 
@@ -99,7 +99,7 @@ dns_aws_rm() {
   _debug _sub_domain "$_sub_domain"
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
   _debug _domain "$_domain"
 
 
-  _info "Geting existing records for $fulldomain"
+  _info "Getting existing records for $fulldomain"
   if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
   if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
     return 1
     return 1
   fi
   fi
@@ -108,14 +108,14 @@ dns_aws_rm() {
     _resource_record="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep "<Name>$fulldomain.</Name>" | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##")"
     _resource_record="$(echo "$response" | sed 's/<ResourceRecordSet>/"/g' | tr '"' "\n" | grep "<Name>$fulldomain.</Name>" | _egrep_o "<ResourceRecords.*</ResourceRecords>" | sed "s/<ResourceRecords>//" | sed "s#</ResourceRecords>##")"
     _debug "_resource_record" "$_resource_record"
     _debug "_resource_record" "$_resource_record"
   else
   else
-    _debug "no records exists, skip"
+    _debug "no records exist, skip"
     return 0
     return 0
   fi
   fi
 
 
   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords>$_resource_record</ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
   _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords>$_resource_record</ResourceRecords><Name>$fulldomain.</Name><Type>TXT</Type><TTL>300</TTL></ResourceRecordSet></Change></Changes></ChangeBatch></ChangeResourceRecordSetsRequest>"
 
 
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
   if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
-    _info "txt record deleted success."
+    _info "TXT record deleted successfully."
     return 0
     return 0
   fi
   fi
 
 
@@ -163,7 +163,7 @@ _get_root() {
             _domain=$h
             _domain=$h
             return 0
             return 0
           fi
           fi
-          _err "Can not find domain id: $h"
+          _err "Can't find domain with id: $h"
           return 1
           return 1
         fi
         fi
       fi
       fi

+ 161 - 0
dnsapi/dns_dpi.sh

@@ -0,0 +1,161 @@
+#!/usr/bin/env sh
+
+# Dnspod.com Domain api
+#
+#DPI_Id="1234"
+#
+#DPI_Key="sADDsdasdgdsf"
+
+REST_API="https://api.dnspod.com"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dpi_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}"
+  DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}"
+  if [ -z "$DPI_Id" ] || [ -z "$DPI_Key" ]; then
+    DPI_Id=""
+    DPI_Key=""
+    _err "You don't specify dnspod api key and key id yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable DPI_Id "$DPI_Id"
+  _saveaccountconf_mutable DPI_Key "$DPI_Key"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  add_record "$_domain" "$_sub_domain" "$txtvalue"
+
+}
+
+#fulldomain txtvalue
+dns_dpi_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}"
+  DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  if ! _rest POST "Record.List" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then
+    _err "Record.Lis error."
+    return 1
+  fi
+
+  if _contains "$response" 'No records'; then
+    _info "Don't need to remove."
+    return 0
+  fi
+
+  record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \")
+  _debug record_id "$record_id"
+  if [ -z "$record_id" ]; then
+    _err "Can not get record id."
+    return 1
+  fi
+
+  if ! _rest POST "Record.Remove" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then
+    _err "Record.Remove error."
+    return 1
+  fi
+
+  _contains "$response" "Action completed successful"
+
+}
+
+#add the txt record.
+#usage: root  sub  txtvalue
+add_record() {
+  root=$1
+  sub=$2
+  txtvalue=$3
+  fulldomain="$sub.$root"
+
+  _info "Adding record"
+
+  if ! _rest POST "Record.Create" "user_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=default"; then
+    return 1
+  fi
+
+  _contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists"
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if ! _rest POST "Domain.Info" "user_token=$DPI_Id,$DPI_Key&format=json&domain=$h"; then
+      return 1
+    fi
+
+    if _contains "$response" "Action completed successful"; then
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
+      _debug _domain_id "$_domain_id"
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _debug _sub_domain "$_sub_domain"
+        _domain="$h"
+        _debug _domain "$_domain"
+        return 0
+      fi
+      return 1
+    fi
+    p="$i"
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+#Usage: method  URI  data
+_rest() {
+  m="$1"
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+  url="$REST_API/$ep"
+
+  _debug url "$url"
+
+  if [ "$m" = "GET" ]; then
+    response="$(_get "$url" | tr -d '\r')"
+  else
+    _debug2 data "$data"
+    response="$(_post "$data" "$url" | tr -d '\r')"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 358 - 0
dnsapi/dns_euserv.sh

@@ -0,0 +1,358 @@
+#!/usr/bin/env sh
+
+#This is the euserv.eu api wrapper for acme.sh
+#
+#Author: Michael Brueckner
+#Report Bugs: https://www.github.com/initit/acme.sh  or  mbr@initit.de
+
+#
+#EUSERV_Username="username"
+#
+#EUSERV_Password="password"
+#
+# Dependencies:
+# -------------
+# - none -
+
+EUSERV_Api="https://api.euserv.net"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_euserv_add() {
+  fulldomain="$(echo "$1" | _lower_case)"
+  txtvalue=$2
+
+  EUSERV_Username="${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}"
+  EUSERV_Password="${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}"
+  if [ -z "$EUSERV_Username" ] || [ -z "$EUSERV_Password" ]; then
+    EUSERV_Username=""
+    EUSERV_Password=""
+    _err "You don't specify euserv user and password yet."
+    _err "Please create your key and try again."
+    return 1
+  fi
+
+  #save the user and email to the account conf file.
+  _saveaccountconf_mutable EUSERV_Username "$EUSERV_Username"
+  _saveaccountconf_mutable EUSERV_Password "$EUSERV_Password"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug "_sub_domain" "$_sub_domain"
+  _debug "_domain" "$_domain"
+  _info "Adding record"
+  if ! _euserv_add_record "$_domain" "$_sub_domain" "$txtvalue"; then
+    return 1
+  fi
+
+}
+
+#fulldomain txtvalue
+dns_euserv_rm() {
+
+  fulldomain="$(echo "$1" | _lower_case)"
+  txtvalue=$2
+
+  EUSERV_Username="${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}"
+  EUSERV_Password="${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}"
+  if [ -z "$EUSERV_Username" ] || [ -z "$EUSERV_Password" ]; then
+    EUSERV_Username=""
+    EUSERV_Password=""
+    _err "You don't specify euserv user and password yet."
+    _err "Please create your key and try again."
+    return 1
+  fi
+
+  #save the user and email to the account conf file.
+  _saveaccountconf_mutable EUSERV_Username "$EUSERV_Username"
+  _saveaccountconf_mutable EUSERV_Password "$EUSERV_Password"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug "_sub_domain" "$_sub_domain"
+  _debug "_domain" "$_domain"
+
+  _debug "Getting txt records"
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>domain.dns_get_active_records</methodName>
+    <params>
+      <param>
+       <value>
+         <struct>
+           <member>
+             <name>login</name>
+             <value>
+               <string>%s</string>
+             </value>
+            </member>
+            <member>
+              <name>password</name>
+              <value>
+                <string>%s</string>
+              </value>
+            </member>
+            <member>
+              <name>domain_id</name>
+              <value>
+                <int>%s</int>
+              </value>
+            </member>
+          </struct>
+        </value>
+      </param>
+    </params>
+  </methodCall>' "$EUSERV_Username" "$EUSERV_Password" "$_euserv_domain_id")
+
+  export _H1="Content-Type: text/xml"
+  response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
+
+  if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
+    _err "Error could not get txt records"
+    _debug "xml_content" "$xml_content"
+    _debug "response" "$response"
+    return 1
+  fi
+
+  if ! echo "$response" | grep '>dns_record_content<.*>'"$txtvalue"'<' >/dev/null; then
+    _info "Do not need to delete record"
+  else
+    # find XML block where txtvalue is in. The record_id is allways prior this line!
+    _endLine=$(echo "$response" | grep -n '>dns_record_content<.*>'"$txtvalue"'<' | cut -d ':' -f 1)
+    # record_id is the last <name> Tag with a number before the row _endLine, identified by </name><value><struct> 
+    _record_id=$(echo "$response" | sed -n '1,'"$_endLine"'p' | grep '</name><value><struct>' | _tail_n 1 | sed 's/.*<name>\([0-9]*\)<\/name>.*/\1/')
+    _info "Deleting record"
+    _euserv_delete_record "$_record_id"
+  fi
+
+}
+
+####################  Private functions below ##################################
+
+_get_root() {
+  domain=$1
+  _debug "get root"
+
+  # Just to read the domain_orders once
+
+  domain=$1
+  i=2
+  p=1
+
+  if ! _euserv_get_domain_orders; then
+    return 1
+  fi
+
+  # Get saved response with domain_orders
+  response="$_euserv_domain_orders"
+
+  while true; do
+    h=$(echo "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if _contains "$response" "$h"; then
+      _sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
+      _domain="$h"
+      if ! _euserv_get_domain_id "$_domain"; then
+        _err "invalid domain"
+        return 1
+      fi
+      return 0
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+
+  return 1
+}
+
+_euserv_get_domain_orders() {
+  # returns: _euserv_domain_orders
+
+  _debug "get domain_orders"
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>domain.get_domain_orders</methodName>
+    <params>
+      <param>
+        <value>
+          <struct>
+            <member>
+              <name>login</name>
+              <value><string>%s</string></value>
+            </member>
+            <member>
+              <name>password</name>
+              <value><string>%s</string></value>
+            </member>
+          </struct>
+        </value>
+      </param>
+    </params>
+  </methodCall>' "$EUSERV_Username" "$EUSERV_Password")
+
+  export _H1="Content-Type: text/xml"
+  response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
+
+  if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
+    _err "Error could not get domain orders"
+    _debug "xml_content" "$xml_content"
+    _debug "response" "$response"
+    return 1
+  fi
+
+  # save response to reduce API calls
+  _euserv_domain_orders="$response"
+  return 0
+}
+
+_euserv_get_domain_id() {
+  # returns: _euserv_domain_id
+  domain=$1
+  _debug "get domain_id"
+
+  # find line where the domain name is within the $response
+  _startLine=$(echo "$_euserv_domain_orders" | grep -n '>domain_name<.*>'"$domain"'<' | cut -d ':' -f 1)
+  # next occurency of domain_id after the domain_name is the correct one
+  _euserv_domain_id=$(echo "$_euserv_domain_orders" | sed -n "$_startLine"',$p' | grep '>domain_id<' | _head_n 1 | sed 's/.*<i4>\([0-9]*\)<\/i4>.*/\1/')
+
+  if [ -z "$_euserv_domain_id" ]; then
+    _err "Could not find domain_id for domain $domain"
+    _debug "_euserv_domain_orders" "$_euserv_domain_orders"
+    return 1
+  fi
+
+  return 0
+}
+
+_euserv_delete_record() {
+  record_id=$1
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+    <methodName>domain.dns_delete_record</methodName>
+    <params>
+      <param>
+       <value>
+         <struct>
+           <member>
+             <name>login</name>
+             <value>
+               <string>%s</string>
+             </value>
+            </member>
+            <member>
+              <name>password</name>
+              <value>
+                <string>%s</string>
+              </value>
+            </member>
+            <member>
+              <name>dns_record_id</name>
+              <value>
+                <int>%s</int>
+              </value>
+            </member>
+          </struct>
+        </value>
+      </param>
+    </params>
+  </methodCall>' "$EUSERV_Username" "$EUSERV_Password" "$record_id")
+
+  export _H1="Content-Type: text/xml"
+  response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
+
+  if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
+    _err "Error deleting record"
+    _debug "xml_content" "$xml_content"
+    _debug "response" "$response"
+    return 1
+  fi
+
+  return 0
+
+}
+
+_euserv_add_record() {
+  domain=$1
+  sub_domain=$2
+  txtval=$3
+
+  xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
+  <methodCall>
+  <methodName>domain.dns_create_record</methodName>
+  <params>
+   <param>
+    <value>
+     <struct>
+      <member>
+       <name>login</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>password</name>
+       <value>
+        <string>%s</string></value>
+      </member>
+      <member>
+       <name>domain_id</name>
+       <value>
+        <int>%s</int>
+       </value>
+      </member>
+      <member>
+       <name>dns_record_subdomain</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>dns_record_type</name>
+       <value>
+        <string>TXT</string>
+       </value>
+      </member>
+      <member>
+       <name>dns_record_value</name>
+       <value>
+        <string>%s</string>
+       </value>
+      </member>
+      <member>
+       <name>dns_record_ttl</name>
+       <value>
+        <int>300</int>
+       </value>
+      </member>
+     </struct>
+    </value>
+   </param>
+  </params>
+  </methodCall>' "$EUSERV_Username" "$EUSERV_Password" "$_euserv_domain_id" "$sub_domain" "$txtval")
+
+  export _H1="Content-Type: text/xml"
+  response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")"
+
+  if ! _contains "$response" "<member><name>status</name><value><i4>100</i4></value></member>"; then
+    _err "Error could not create record"
+    _debug "xml_content" "$xml_content"
+    _debug "response" "$response"
+    return 1
+  fi
+
+  return 0
+}

+ 167 - 0
dnsapi/dns_gcloud.sh

@@ -0,0 +1,167 @@
+#!/usr/bin/env sh
+
+# Author: Janos Lenart <janos@lenart.io>
+
+########  Public functions #####################
+
+# Usage: dns_gcloud_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_gcloud_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gcloud"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _dns_gcloud_find_zone || return $?
+
+  # Add an extra RR
+  _dns_gcloud_start_tr || return $?
+  _dns_gcloud_get_rrdatas || return $?
+  echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
+  printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $?
+  _dns_gcloud_execute_tr || return $?
+
+  _info "$fulldomain record added"
+}
+
+# Usage: dns_gcloud_rm   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Remove the txt record after validation.
+dns_gcloud_rm() {
+  fulldomain=$1
+  txtvalue=$2
+  _info "Using gcloud"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  _dns_gcloud_find_zone || return $?
+
+  # Remove one RR
+  _dns_gcloud_start_tr || return $?
+  _dns_gcloud_get_rrdatas || return $?
+  echo "$rrdatas" | _dns_gcloud_remove_rrs || return $?
+  echo "$rrdatas" | grep -F -v "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $?
+  _dns_gcloud_execute_tr || return $?
+
+  _info "$fulldomain record added"
+}
+
+####################  Private functions below ##################################
+
+_dns_gcloud_start_tr() {
+  if ! trd=$(mktemp -d); then
+    _err "_dns_gcloud_start_tr: failed to create temporary directory"
+    return 1
+  fi
+  tr="$trd/tr.yaml"
+  _debug tr "$tr"
+
+  if ! gcloud dns record-sets transaction start \
+    --transaction-file="$tr" \
+    --zone="$managedZone"; then
+    rm -r "$trd"
+    _err "_dns_gcloud_start_tr: failed to execute transaction"
+    return 1
+  fi
+}
+
+_dns_gcloud_execute_tr() {
+  if ! gcloud dns record-sets transaction execute \
+    --transaction-file="$tr" \
+    --zone="$managedZone"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_execute_tr: failed to execute transaction"
+    return 1
+  fi
+  rm -r "$trd"
+
+  for i in $(seq 1 120); do
+    if gcloud dns record-sets changes list \
+      --zone="$managedZone" \
+      --filter='status != done' \
+      | grep -q '^.*'; then
+      _info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..."
+      sleep 5
+    else
+      return 0
+    fi
+  done
+
+  _err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes"
+  rm -r "$trd"
+  return 1
+}
+
+_dns_gcloud_remove_rrs() {
+  if ! xargs --no-run-if-empty gcloud dns record-sets transaction remove \
+    --name="$fulldomain." \
+    --ttl="$ttl" \
+    --type=TXT \
+    --zone="$managedZone" \
+    --transaction-file="$tr"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_remove_rrs: failed to remove RRs"
+    return 1
+  fi
+}
+
+_dns_gcloud_add_rrs() {
+  ttl=60
+  if ! xargs --no-run-if-empty gcloud dns record-sets transaction add \
+    --name="$fulldomain." \
+    --ttl="$ttl" \
+    --type=TXT \
+    --zone="$managedZone" \
+    --transaction-file="$tr"; then
+    _debug tr "$(cat "$tr")"
+    rm -r "$trd"
+    _err "_dns_gcloud_add_rrs: failed to add RRs"
+    return 1
+  fi
+}
+
+_dns_gcloud_find_zone() {
+  # Prepare a filter that matches zones that are suiteable for this entry.
+  # For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com;
+  # this function finds the longest postfix that has a managed zone.
+  part="$fulldomain"
+  filter="dnsName=( "
+  while [ "$part" != "" ]; do
+    filter="$filter$part. "
+    part="$(echo "$part" | sed 's/[^.]*\.*//')"
+  done
+  filter="$filter)"
+  _debug filter "$filter"
+
+  # List domains and find the longest match (in case of some levels of delegation)
+  if ! match=$(gcloud dns managed-zones list \
+    --format="value(name, dnsName)" \
+    --filter="$filter" \
+    | while read -r dnsName name; do
+      printf "%s\t%s\t%s\n" "${#dnsName}" "$dnsName" "$name"
+    done \
+    | sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then
+    _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?"
+    return 1
+  fi
+
+  dnsName=$(echo "$match" | cut -f2)
+  _debug dnsName "$dnsName"
+  managedZone=$(echo "$match" | cut -f1)
+  _debug managedZone "$managedZone"
+}
+
+_dns_gcloud_get_rrdatas() {
+  if ! rrdatas=$(gcloud dns record-sets list \
+    --zone="$managedZone" \
+    --name="$fulldomain." \
+    --type=TXT \
+    --format="value(ttl,rrdatas)"); then
+    _err "_dns_gcloud_get_rrdatas: Failed to list record-sets"
+    rm -r "$trd"
+    return 1
+  fi
+  ttl=$(echo "$rrdatas" | cut -f1)
+  rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/","/"\n"/g')
+}

+ 4 - 0
dnsapi/dns_gd.sh

@@ -168,5 +168,9 @@ _gd_rest() {
     return 1
     return 1
   fi
   fi
   _debug2 response "$response"
   _debug2 response "$response"
+  if _contains "$response" "UNABLE_TO_AUTHENTICATE"; then
+    _err "It seems that your api key or secret is not correct."
+    return 1
+  fi
   return 0
   return 0
 }
 }

+ 2 - 2
dnsapi/dns_ispconfig.sh

@@ -128,7 +128,7 @@ _ISPC_addTxt() {
   curSerial="$(date +%s)"
   curSerial="$(date +%s)"
   curStamp="$(date +'%F %T')"
   curStamp="$(date +'%F %T')"
   params="\"server_id\":\"${server_id}\",\"zone\":\"${zone}\",\"name\":\"${fulldomain}.\",\"type\":\"txt\",\"data\":\"${txtvalue}\",\"aux\":\"0\",\"ttl\":\"3600\",\"active\":\"y\",\"stamp\":\"${curStamp}\",\"serial\":\"${curSerial}\""
   params="\"server_id\":\"${server_id}\",\"zone\":\"${zone}\",\"name\":\"${fulldomain}.\",\"type\":\"txt\",\"data\":\"${txtvalue}\",\"aux\":\"0\",\"ttl\":\"3600\",\"active\":\"y\",\"stamp\":\"${curStamp}\",\"serial\":\"${curSerial}\""
-  curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}}}"
+  curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}},\"update_serial\":true}"
   curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")"
   curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")"
   _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'"
   _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'"
   _debug "Result of _ISPC_addTxt: '$curResult'"
   _debug "Result of _ISPC_addTxt: '$curResult'"
@@ -160,7 +160,7 @@ _ISPC_rmTxt() {
       *)
       *)
         unset IFS
         unset IFS
         _info "Retrieved Record ID."
         _info "Retrieved Record ID."
-        curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}"
+        curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\",\"update_serial\":true}"
         curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")"
         curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")"
         _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'"
         _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'"
         _debug "Result of _ISPC_rmTxt: '$curResult'"
         _debug "Result of _ISPC_rmTxt: '$curResult'"

+ 40 - 15
dnsapi/dns_lexicon.sh

@@ -7,20 +7,13 @@ lexicon_cmd="lexicon"
 
 
 wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api"
 wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api"
 
 
-########  Public functions #####################
-
-#Usage: add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
-dns_lexicon_add() {
-  fulldomain=$1
-  txtvalue=$2
-
-  domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
-
+_lexicon_init() {
   if ! _exists "$lexicon_cmd"; then
   if ! _exists "$lexicon_cmd"; then
     _err "Please install $lexicon_cmd first: $wiki"
     _err "Please install $lexicon_cmd first: $wiki"
     return 1
     return 1
   fi
   fi
 
 
+  PROVIDER="${PROVIDER:-$(_readdomainconf PROVIDER)}"
   if [ -z "$PROVIDER" ]; then
   if [ -z "$PROVIDER" ]; then
     PROVIDER=""
     PROVIDER=""
     _err "Please define env PROVIDER first: $wiki"
     _err "Please define env PROVIDER first: $wiki"
@@ -33,46 +26,78 @@ dns_lexicon_add() {
   # e.g. busybox-ash does not know [:upper:]
   # e.g. busybox-ash does not know [:upper:]
   # shellcheck disable=SC2018,SC2019
   # shellcheck disable=SC2018,SC2019
   Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z')
   Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z')
+  eval "$Lx_name=\${$Lx_name:-$(_readaccountconf_mutable "$Lx_name")}"
   Lx_name_v=$(eval echo \$"$Lx_name")
   Lx_name_v=$(eval echo \$"$Lx_name")
   _secure_debug "$Lx_name" "$Lx_name_v"
   _secure_debug "$Lx_name" "$Lx_name_v"
   if [ "$Lx_name_v" ]; then
   if [ "$Lx_name_v" ]; then
-    _saveaccountconf "$Lx_name" "$Lx_name_v"
+    _saveaccountconf_mutable "$Lx_name" "$Lx_name_v"
     eval export "$Lx_name"
     eval export "$Lx_name"
   fi
   fi
 
 
   # shellcheck disable=SC2018,SC2019
   # shellcheck disable=SC2018,SC2019
   Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z')
   Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z')
+  eval "$Lx_token=\${$Lx_token:-$(_readaccountconf_mutable "$Lx_token")}"
   Lx_token_v=$(eval echo \$"$Lx_token")
   Lx_token_v=$(eval echo \$"$Lx_token")
   _secure_debug "$Lx_token" "$Lx_token_v"
   _secure_debug "$Lx_token" "$Lx_token_v"
   if [ "$Lx_token_v" ]; then
   if [ "$Lx_token_v" ]; then
-    _saveaccountconf "$Lx_token" "$Lx_token_v"
+    _saveaccountconf_mutable "$Lx_token" "$Lx_token_v"
     eval export "$Lx_token"
     eval export "$Lx_token"
   fi
   fi
 
 
   # shellcheck disable=SC2018,SC2019
   # shellcheck disable=SC2018,SC2019
   Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z')
   Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z')
+  eval "$Lx_password=\${$Lx_password:-$(_readaccountconf_mutable "$Lx_password")}"
   Lx_password_v=$(eval echo \$"$Lx_password")
   Lx_password_v=$(eval echo \$"$Lx_password")
   _secure_debug "$Lx_password" "$Lx_password_v"
   _secure_debug "$Lx_password" "$Lx_password_v"
   if [ "$Lx_password_v" ]; then
   if [ "$Lx_password_v" ]; then
-    _saveaccountconf "$Lx_password" "$Lx_password_v"
+    _saveaccountconf_mutable "$Lx_password" "$Lx_password_v"
     eval export "$Lx_password"
     eval export "$Lx_password"
   fi
   fi
 
 
   # shellcheck disable=SC2018,SC2019
   # shellcheck disable=SC2018,SC2019
   Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z')
   Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z')
+  eval "$Lx_domaintoken=\${$Lx_domaintoken:-$(_readaccountconf_mutable "$Lx_domaintoken")}"
   Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken")
   Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken")
   _secure_debug "$Lx_domaintoken" "$Lx_domaintoken_v"
   _secure_debug "$Lx_domaintoken" "$Lx_domaintoken_v"
   if [ "$Lx_domaintoken_v" ]; then
   if [ "$Lx_domaintoken_v" ]; then
+    _saveaccountconf_mutable "$Lx_domaintoken" "$Lx_domaintoken_v"
     eval export "$Lx_domaintoken"
     eval export "$Lx_domaintoken"
-    _saveaccountconf "$Lx_domaintoken" "$Lx_domaintoken_v"
   fi
   fi
+}
+
+########  Public functions #####################
+
+#Usage: dns_lexicon_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_lexicon_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if ! _lexicon_init; then
+    return 1
+  fi
+
+  domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
+
+  _secure_debug LEXICON_OPTS "$LEXICON_OPTS"
+  _savedomainconf LEXICON_OPTS "$LEXICON_OPTS"
 
 
-  $lexicon_cmd "$PROVIDER" create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
+  # shellcheck disable=SC2086
+  $lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
 
 
 }
 }
 
 
-#fulldomain
+#Usage: dns_lexicon_rm   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_lexicon_rm() {
 dns_lexicon_rm() {
   fulldomain=$1
   fulldomain=$1
+  txtvalue=$2
+
+  if ! _lexicon_init; then
+    return 1
+  fi
+
+  domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
+
+  # shellcheck disable=SC2086
+  $lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
 
 
 }
 }

+ 22 - 39
dnsapi/dns_unoeuro.sh

@@ -50,34 +50,16 @@ dns_unoeuro_add() {
     _err "Error"
     _err "Error"
     return 1
     return 1
   fi
   fi
+  _info "Adding record"
 
 
-  if ! _contains "$response" "$_sub_domain" >/dev/null; then
-    _info "Adding record"
-
-    if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then
-      if _contains "$response" "\"status\": 200" >/dev/null; then
-        _info "Added, OK"
-        return 0
-      else
-        _err "Add txt record error."
-        return 1
-      fi
-    fi
-    _err "Add txt record error."
-  else
-    _info "Updating record"
-    record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1)
-    record_line_number=$(_math "$record_line_number" - 1)
-    record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
-    _debug "record_id" "$record_id"
-
-    _uno_rest PUT "my/products/$h/dns/records/$record_id" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"
+  if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then
     if _contains "$response" "\"status\": 200" >/dev/null; then
     if _contains "$response" "\"status\": 200" >/dev/null; then
-      _info "Updated, OK"
+      _info "Added, OK"
       return 0
       return 0
+    else
+      _err "Add txt record error."
+      return 1
     fi
     fi
-    _err "Update error"
-    return 1
   fi
   fi
 }
 }
 
 
@@ -122,23 +104,24 @@ dns_unoeuro_rm() {
   if ! _contains "$response" "$_sub_domain"; then
   if ! _contains "$response" "$_sub_domain"; then
     _info "Don't need to remove."
     _info "Don't need to remove."
   else
   else
-    record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1)
-    record_line_number=$(_math "$record_line_number" - 1)
-    record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
-    _debug "record_id" "$record_id"
-
-    if [ -z "$record_id" ]; then
-      _err "Can not get record id to remove."
-      return 1
-    fi
+    for record_line_number in $(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1); do
+      record_line_number=$(_math "$record_line_number" - 1)
+      _debug "record_line_number" "$record_line_number"
+      record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}")
+      _debug "record_id" "$record_id"
+
+      if [ -z "$record_id" ]; then
+        _err "Can not get record id to remove."
+        return 1
+      fi
 
 
-    if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then
-      _err "Delete record error."
-      return 1
-    fi
-    _contains "$response" "\"status\": 200"
+      if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then
+        _err "Delete record error."
+        return 1
+      fi
+      _contains "$response" "\"status\": 200"
+    done
   fi
   fi
-
 }
 }
 
 
 ####################  Private functions below ##################################
 ####################  Private functions below ##################################