Browse Source

Merge remote-tracking branch 'upstream/master'

nytral 8 years ago
parent
commit
e82ea94bb6

+ 9 - 0
.github/PULL_REQUEST_TEMPLATE.md

@@ -0,0 +1,9 @@
+<!--
+
+Do NOT send pull request to `master` branch.
+
+Please send to `dev` branch instead.
+
+Any PR to `master` branch will NOT be merged.
+
+-->

+ 54 - 24
.travis.yml

@@ -1,24 +1,54 @@
-language: shell
-
-env:
-  global:
-    - SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64
-
-addons:
-  apt:
-    sources:
-    - debian-sid    # Grab shellcheck from the Debian repo (o_O)
-    packages:
-    - shellcheck
-
-script:
-  - curl -sSL $SHFMT_URL -o ~/shfmt
-  - chmod +x ~/shfmt
-  - shellcheck -V
-  - shellcheck -e SC2021,SC2126,SC2034 **/*.sh && echo "shellcheck OK"
-  - ~/shfmt -l -w -i 2 . && echo "shfmt OK" || git diff --exit-code || (echo "Run shfmt to fix the formatting issues" && false)
-
-matrix:
-  fast_finish: true
-  
-  
+language: shell
+sudo: required
+
+os:
+  - linux
+  - osx
+
+env:
+  global:
+    - SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64
+
+addons:
+  apt:
+    sources:
+    - debian-sid    # Grab shellcheck from the Debian repo (o_O)
+    packages:
+    - shellcheck
+
+install:
+  - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then
+      brew update && brew install openssl;
+      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/;
+      ln -s /usr/local/Cellar/openssl/1.0.2j/bin/openssl /usr/local/openssl;
+      _old_path="$PATH";
+      echo "PATH=$PATH";
+      export PATH="";
+      export OPENSSL_BIN="/usr/local/openssl";
+      openssl version 2>&1 || true;
+      $OPENSSL_BIN version 2>&1 || true;
+      export PATH="$_old_path";
+    fi
+  
+script:
+  - echo "TEST_LOCAL=$TEST_LOCAL"
+  - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)"
+  - command -V openssl && openssl version
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt ; fi
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then chmod +x ~/shfmt ; fi
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/shfmt -l -w -i 2 . ; fi
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then git diff --exit-code && echo "shfmt OK" ; fi
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi
+  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck **/*.sh && echo "shellcheck OK" ; fi
+  - cd ..
+  - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
+  - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi
+  - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" OPENSSL_BIN="$OPENSSL_BIN" ./letest.sh ; fi
+
+
+matrix:
+  fast_finish: true
+  
+  

+ 124 - 97
README.md

@@ -1,21 +1,26 @@
 # An ACME Shell script: acme.sh [![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)
 - An ACME protocol client written purely in Shell (Unix shell) language.
-- Fully ACME protocol implementation.
-- Simple, powerful and very easy to use. You only need 3 minutes to learn.
-- Bash, dash and sh compatible. 
+- Full ACME protocol implementation.
+- Simple, powerful and very easy to use. You only need 3 minutes to learn it.
+- Bash, dash and sh compatible.
 - Simplest shell script for Let's Encrypt free certificate client.
-- Purely written in Shell with no dependencies on python or Let's Encrypt official client.
-- Just one script, to issue, renew and install your certificates automatically.
+- 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.
 
 It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
 
-
 Wiki: https://github.com/Neilpang/acme.sh/wiki
 
+
+Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
+
+
 # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
 
-#Tested OS
+
+# Tested OS
+
 | NO | Status| Platform|
 |----|-------|---------|
 |1|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/ubuntu-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu
@@ -37,42 +42,41 @@ Wiki: https://github.com/Neilpang/acme.sh/wiki
 |17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT)
 |18|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/solaris.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris
 |19|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/gentoo-stage3-amd64.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux
+|20|[![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX
 
-For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest): 
+For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest):
 
 https://github.com/Neilpang/acmetest
 
-# Supported Mode
 
-1. Webroot mode
-2. Standalone mode
-3. Apache mode
-4. Dns mode
+# Supported modes
 
+- Webroot mode
+- Standalone mode
+- Apache mode
+- DNS mode
 
 
 # 1. How to install
 
-### 1. Install online:
+### 1. Install online
 
 Check this project: https://github.com/Neilpang/get.acme.sh
 
 ```bash
 curl https://get.acme.sh | sh
-
 ```
 
 Or:
 
 ```bash
 wget -O -  https://get.acme.sh | sh
-
 ```
 
 
-### 2. Or, Install from git:
+### 2. Or, Install from git
 
-Clone this project: 
+Clone this project and launch installation:
 
 ```bash
 git clone https://github.com/Neilpang/acme.sh.git
@@ -82,14 +86,14 @@ cd ./acme.sh
 
 You `don't have to be root` then, although `it is recommended`.
 
-Advanced Installation:  https://github.com/Neilpang/acme.sh/wiki/How-to-install
+Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install
 
 The installer will perform 3 actions:
 
-1. Create and copy `acme.sh` to your home dir (`$HOME`):  `~/.acme.sh/`.
-All certs will be placed in this folder.
-2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. 
-3. Create everyday cron job to check and renew the cert if needed.
+1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`.
+All certs will be placed in this folder too.
+2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`.
+3. Create daily cron job to check and renew the certs if needed.
 
 Cron entry example:
 
@@ -97,18 +101,17 @@ Cron entry example:
 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null
 ```
 
-After the installation, you must close current terminal and reopen again to make the alias take effect.
+After the installation, you must close the current terminal and reopen it to make the alias take effect.
+
+Ok, you are ready to issue certs now.
 
-Ok, you are ready to issue cert now.
 Show help message:
 
 ```
-
 root@v1:~# acme.sh -h
-
 ```
- 
-# 2. Just issue a cert:
+
+# 2. Just issue a cert
 
 **Example 1:** Single domain.
 
@@ -119,56 +122,59 @@ acme.sh --issue -d example.com -w /home/wwwroot/example.com
 **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 
+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.
 
-Second argument **"example.com"** is the main domain you want to issue cert for.
-You must have at least a domain there.
+Second argument **"example.com"** is the main domain you want to issue the cert for.
+You must have at least one domain there.
 
 You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`.
 
-Generate/issued certs will be placed in `~/.acme.sh/example.com/`
+Generated/issued certs will be placed in `~/.acme.sh/example.com/`
 
-The issued cert will be renewed every **60** days automatically.
+The issued cert will be renewed automatically every **60** days.
 
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 
 
-# 3. Install the issued cert to apache/nginx etc.
+# 3. Install the issued cert to Apache/Nginx etc.
 
-After you issue a cert, you probably want to install/copy the cert to your nginx/apache or other servers. 
-You **MUST** use this command to copy the certs to the target files,  **Do NOT** use the certs files in **.acme.sh/** folder, they are for internal use only, the folder structure may change in future.
+After you issue a cert, you probably want to install/copy the cert to your Apache/Nginx or other servers.
+You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future.
 
-**nginx** example
+**Apache** example:
 ```bash
 acme.sh --installcert -d example.com \
---keypath  /path/to/keyfile/in/nginx/key.pem  \
---fullchainpath path/to/fullchain/nginx/cert.pem \
---reloadcmd  "service nginx restart"
+--certpath      /path/to/certfile/in/apache/cert.pem  \
+--keypath       /path/to/keyfile/in/apache/key.pem  \
+--fullchainpath /path/to/fullchain/certfile/apache/fullchain.pem \
+--reloadcmd     "service apache2 force-reload"
 ```
 
-**apache** example
+**Nginx** example:
 ```bash
 acme.sh --installcert -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 \
---reloadcmd  "service apache2 restart"
+--keypath       /path/to/keyfile/in/nginx/key.pem  \
+--fullchainpath /path/to/fullchain/nginx/cert.pem \
+--reloadcmd     "service nginx force-reload"
 ```
 
 Only the domain is required, all the other parameters are optional.
 
-Install/copy the issued cert/key to the production apache or nginx path.
+The ownership and permission info of existing files are preserved. You may want to precreate the files to have defined ownership and permission.
+
+Install/copy the issued cert/key to the production Apache or Nginx path.
+
+The cert will be `renewed every **60** days by default` (which is configurable). Once the cert is renewed, the Apache/Nginx service will be restarted automatically by the command: `service apache2 restart` or `service nginx restart`.
 
-The cert will be `renewed every **60** days by default` (which is configurable). Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: `service apache2 reload` or `service nginx reload`.
 
 # 4. Use Standalone server to issue cert
 
-**(requires you be root/sudoer, or you have permission to listen tcp 80 port)**
+**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))**
 
-The tcp `80` port **MUST** be free to listen, otherwise you will be prompted to free the `80` port and try again.
+Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
 
 ```bash
 acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com
@@ -176,13 +182,14 @@ acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com
 
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 
-# 5. Use Standalone tls server to issue cert
 
-**(requires you be root/sudoer, or you have permission to listen tcp 443 port)**
+# 5. Use Standalone TLS server to issue cert
+
+**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))**
 
 acme.sh supports `tls-sni-01` validation.
 
-The tcp `443` port **MUST** be free to listen, otherwise you will be prompted to free the `443` port and try again.
+Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again.
 
 ```bash
 acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com
@@ -190,31 +197,33 @@ acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com
 
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 
+
 # 6. Use Apache mode
 
-**(requires you be root/sudoer, since it is required to interact with apache server)**
+**(requires you to be root/sudoer, since it is required to interact with Apache server)**
 
-If you are running a web server, apache or nginx, it is recommended to use the `Webroot mode`.
+If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`.
 
-Particularly, if you are running an apache server, you should use apache mode instead. This mode doesn't write any files to your web root folder.
+Particularly, if you are running an Apache server, you should use Apache mode instead. This mode doesn't write any files to your web root folder.
 
-Just set string "apache" as the second argument, it will force use of apache plugin automatically.
+Just set string "apache" as the second argument and it will force use of apache plugin automatically.
 
 ```
-acme.sh --issue --apache -d example.com -d www.example.com -d user.example.com
+acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com
 ```
 
 More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
 
+
 # 7. Use DNS mode:
 
 Support the `dns-01` challenge.
 
 ```bash
-acme.sh --issue --dns -d example.com -d www.example.com -d user.example.com
+acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com
 ```
 
-You should get the output like below:
+You should get an output like below:
 
 ```
 Add the following txt record:
@@ -226,7 +235,6 @@ Domain:_acme-challenge.www.example.com
 Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
 
 Please add those txt records to the domains. Waiting for the dns to take effect.
-
 ```
 
 Then just rerun with `renew` argument:
@@ -237,52 +245,60 @@ acme.sh --renew -d example.com
 
 Ok, it's finished.
 
+
 # 8. Automatic DNS API integration
 
-If your DNS provider supports API access, we can use API to automatically issue the certs.
+If your DNS provider supports API access, we can use that API to automatically issue the certs.
 
-You don't have do anything manually!
+You don't have to do anything manually!
 
 ### Currently acme.sh supports:
 
-1. Cloudflare.com API
-2. Dnspod.cn API
-3. Cloudxns.com API
-4. Godaddy.com API
-5. OVH, kimsufi, soyoustart and runabove API
-6. AWS Route 53, see: https://github.com/Neilpang/acme.sh/issues/65
-7. PowerDNS API
-8. lexicon dns api: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api
-   (DigitalOcean, DNSimple, DnsMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.)
-9. LuaDNS.com API
-10. DNSMadeEasy.com API
+1. CloudFlare.com API
+1. DNSPod.cn API
+1. CloudXNS.com API
+1. GoDaddy.com API
+1. OVH, kimsufi, soyoustart and runabove API
+1. AWS Route 53
+1. PowerDNS.com API
+1. lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api
+   (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.)
+1. LuaDNS.com API
+1. DNSMadeEasy.com API
+1. nsupdate API
+1. aliyun.com(阿里云) API
+1. ISPConfig 3.1 API
+1. Alwaysdata.com API
+1. Linode.com API
+1. FreeDNS (https://freedns.afraid.org/)
 
-##### More APIs are coming soon...
+**More APIs coming soon...**
 
-If your DNS provider is not on the supported list above, you can write your own script API easily. If you do please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute to the project.
+If your DNS provider is not on the supported list above, you can write your own DNS API script easily. If you do, please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute it to the project.
 
-For more details: [How to use dns api](dnsapi)
+For more details: [How to use DNS API](dnsapi)
 
-# 9. Issue ECC certificate:
 
-`Let's Encrypt` now can issue **ECDSA** certificates.
+# 9. Issue ECC certificates
 
-And we also support it.
+`Let's Encrypt` can now issue **ECDSA** certificates.
+
+And we support them too!
 
 Just set the `length` parameter with a prefix `ec-`.
 
 For example:
 
-### Single domain ECC cerfiticate:
+### Single domain ECC cerfiticate
 
 ```bash
-acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength  ec-256
+acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256
 ```
 
-SAN multi domain ECC certificate:
+### SAN multi domain ECC certificate
 
 ```bash
-acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength  ec-256
+acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256
 ```
 
 Please look at the last parameter above.
@@ -294,40 +310,48 @@ Valid values are:
 3. **ec-521 (secp521r1,  "ECDSA P-521", which is not supported by Let's Encrypt yet.)**
 
 
-# 10. How to renew the cert
+# 10. How to renew the issued certs
 
-No, you don't need to renew the certs manually.  All the certs will be renewed automatically every **60** days.
+No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days.
 
 However, you can also force to renew any cert:
 
 ```
-acme.sh --renew  -d  example.com --force
+acme.sh --renew -d example.com --force
 ```
 
 or, for ECC cert:
+
 ```
-acme.sh --renew  -d  example.com  --force --ecc
+acme.sh --renew -d example.com --force --ecc
 ```
 
+
 # 11. How to upgrade `acme.sh`
-acme.sh is in developing, it's strongly recommended to use the latest code.
+
+acme.sh is in constant development, so it's strongly recommended to use the latest code.
 
 You can update acme.sh to the latest code:
+
 ```
 acme.sh --upgrade
 ```
 
-You can enable auto upgrade:
+You can also enable auto upgrade:
+
 ```
-acme.sh  --upgrade  --auto-upgrade
+acme.sh --upgrade --auto-upgrade
 ```
-Then **acme.sh** will keep up to date automatically.
+
+Then **acme.sh** will be kept up to date automatically.
 
 Disable auto upgrade:
+
 ```
-acme.sh  --upgrade  --auto-upgrade 0
+acme.sh --upgrade --auto-upgrade 0
 ```
 
+
 # 12. Issue a cert from an existing CSR
 
 https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR
@@ -339,22 +363,25 @@ Speak ACME language using shell, directly to "Let's Encrypt".
 
 TODO:
 
-# Acknowledgment
+
+# Acknowledgments
+
 1. Acme-tiny: https://github.com/diafygi/acme-tiny
 2. ACME protocol: https://github.com/ietf-wg-acme/acme
 3. Certbot: https://github.com/certbot/certbot
 
+
 # License & Others
 
 License is GPLv3
 
 Please Star and Fork me.
 
-[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcomed.
+[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome.
 
 
 # Donate
-1. PayPal:  donate@acme.sh
 
-[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list)
+1. PayPal: donate@acme.sh
 
+[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list)

File diff suppressed because it is too large
+ 408 - 126
acme.sh


+ 1 - 0
deploy/README.md

@@ -0,0 +1 @@
+#Using deploy api

+ 81 - 0
deploy/kong.sh

@@ -0,0 +1,81 @@
+#!/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
+# Written by Geoffroi Genot <ggenot@voxbone.com>
+
+########  Public functions #####################
+
+#domain keyfile certfile cafile fullchain
+kong_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+  _info "Deploying certificate on Kong instance"
+  if [ -z "$KONG_URL" ]; then
+    _debug "KONG_URL Not set, using default http://localhost:8001"
+    KONG_URL="http://localhost:8001"
+  fi
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _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
+  fi
+  #Save kong url if it's succesful (First run case)
+  _saveaccountconf KONG_URL "$KONG_URL"
+  #Generate DEIM
+  delim="-----MultipartDelimeter$(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"
+  #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")"
+  #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")"
+  #Close multipart
+  content="$content${nl}--$delim--${nl}"
+  #Convert CRLF
+  content=$(printf %b "$content")
+  #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"
+  if [ -z "$ssl_uuid" ]; then
+    #Post certificate to Kong
+    response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST")
+  else
+    #patch
+    response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH")
+  fi
+  if ! [ "$(echo "$response" | _egrep_o "ssl")" = "ssl" ]; then
+    _err "An error occured with cert upload. Check response:"
+    _err "$response"
+    return 1
+  fi
+  _debug response "$response"
+  _info "Certificate successfully deployed"
+}

+ 218 - 67
dnsapi/README.md

@@ -1,99 +1,80 @@
-# How to use dns api
+# How to use DNS API
 
-## Use CloudFlare domain api to automatically issue cert
+## 1. Use CloudFlare domain API to automatically issue cert
 
-For now, we support clourflare integeration.
-
-First you need to login to your clourflare account to get your api key.
+First you need to login to your CloudFlare account to get your API key.
 
 ```
 export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
-
 export CF_Email="xxxx@sss.com"
-
 ```
 
-Ok, let's issue cert now:
+Ok, let's issue a cert now:
 ```
-acme.sh   --issue   --dns dns_cf   -d example.com  -d www.example.com
+acme.sh --issue --dns dns_cf -d example.com -d www.example.com
 ```
 
-The `CF_Key` and `CF_Email`  will be saved in `~/.acme.sh/account.conf`, when next time you use cloudflare api, it will reuse this key.
-
+The `CF_Key` and `CF_Email` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
-## Use Dnspod.cn domain api to automatically issue cert
+## 2. Use DNSPod.cn domain API to automatically issue cert
 
-For now, we support dnspod.cn integeration.
-
-First you need to login to your dnspod.cn account to get your api key and key id.
+First you need to login to your DNSPod account to get your API Key and ID.
 
 ```
 export DP_Id="1234"
-
 export DP_Key="sADDsdasdgdsf"
-
 ```
 
-Ok, let's issue cert now:
+Ok, let's issue a cert now:
 ```
-acme.sh   --issue   --dns dns_dp   -d example.com  -d www.example.com
+acme.sh --issue --dns dns_dp -d example.com -d www.example.com
 ```
 
-The `DP_Id` and `DP_Key`  will be saved in `~/.acme.sh/account.conf`, when next time you use dnspod.cn api, it will reuse this key.
+The `DP_Id` and `DP_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
-## Use Cloudxns.com domain api to automatically issue cert
+## 3. Use CloudXNS.com domain API to automatically issue cert
 
-For now, we support Cloudxns.com integeration.
-
-First you need to login to your Cloudxns.com account to get your api key and key secret.
+First you need to login to your CloudXNS account to get your API Key and Secret.
 
 ```
 export CX_Key="1234"
-
 export CX_Secret="sADDsdasdgdsf"
-
 ```
 
-Ok, let's issue cert now:
+Ok, let's issue a cert now:
 ```
-acme.sh   --issue   --dns dns_cx   -d example.com  -d www.example.com
+acme.sh --issue --dns dns_cx -d example.com -d www.example.com
 ```
 
-The `CX_Key` and `CX_Secret`  will be saved in `~/.acme.sh/account.conf`, when next time you use Cloudxns.com api, it will reuse this key.
+The `CX_Key` and `CX_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
 
-## Use Godaddy.com domain api to automatically issue cert
+## 4. Use GoDaddy.com domain API to automatically issue cert
 
-We support Godaddy integration.
-
-First you need to login to your Godaddy account to get your api key and api secret.
+First you need to login to your GoDaddy account to get your API Key and Secret.
 
 https://developer.godaddy.com/keys/
 
-Please Create a Production key, instead of a Test key.
-
+Please create a Production key, instead of a Test key.
 
 ```
 export GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
-
 export GD_Secret="asdfsdafdsfdsfdsfdsfdsafd"
-
 ```
 
-Ok, let's issue cert now:
+Ok, let's issue a cert now:
 ```
-acme.sh   --issue   --dns dns_gd   -d example.com  -d www.example.com
+acme.sh --issue --dns dns_gd -d example.com -d www.example.com
 ```
 
-The `GD_Key` and `GD_Secret`  will be saved in `~/.acme.sh/account.conf`, when next time you use cloudflare api, it will reuse this key.
+The `GD_Key` and `GD_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
-## Use PowerDNS embedded api to automatically issue cert
 
-We support PowerDNS embedded API integration.
+## 5. Use PowerDNS embedded API to automatically issue cert
 
-First you need to enable api and set your api-token in PowerDNS configuration.
+First you need to login to your PowerDNS account to enable the API and set your API-Token in the configuration.
 
 https://doc.powerdns.com/md/httpapi/README/
 
@@ -102,75 +83,245 @@ export PDNS_Url="http://ns.example.com:8081"
 export PDNS_ServerId="localhost"
 export PDNS_Token="0123456789ABCDEF"
 export PDNS_Ttl=60
-
 ```
 
-Ok, let's issue cert now:
+Ok, let's issue a cert now:
 ```
-acme.sh   --issue   --dns dns_pdns   -d example.com  -d www.example.com
+acme.sh --issue --dns dns_pdns -d example.com -d www.example.com
 ```
 
-The `PDNS_Url`, `PDNS_ServerId`, `PDNS_Token` and `PDNS_Ttl` will be saved in `~/.acme.sh/account.conf`.
+The `PDNS_Url`, `PDNS_ServerId`, `PDNS_Token` and `PDNS_Ttl` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
-## Use OVH/kimsufi/soyoustart/runabove API
+
+## 6. Use OVH/kimsufi/soyoustart/runabove API to automatically issue cert
 
 https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api
 
-# Use custom api
 
-If your api is not supported yet,  you can write your own dns api.
+## 7. Use nsupdate to automatically issue cert
 
-Let's assume you want to name it 'myapi',
+First, generate a key for updating the zone
+```
+b=$(dnssec-keygen -a hmac-sha512 -b 512 -n USER -K /tmp foo)
+cat > /etc/named/keys/update.key <<EOF
+key "update" {
+    algorithm hmac-sha512;
+    secret "$(awk '/^Key/{print $2}' /tmp/$b.private)";
+};
+EOF
+rm -f /tmp/$b.{private,key}
+```
 
-1. Create a bash script named  `~/.acme.sh/dns_myapi.sh`,
-2. In the script, you must have a function named `dns_myapi_add()`. Which will be called by acme.sh to add dns records.
-3. Then you can use your api to issue cert like:
+Include this key in your named configuration
+```
+include "/etc/named/keys/update.key";
+```
 
+Next, configure your zone to allow dynamic updates.
+
+Depending on your named version, use either
+```
+zone "example.com" {
+    type master;
+    allow-update { key "update"; };
+};
+```
+or
 ```
-acme.sh  --issue  --dns  dns_myapi  -d example.com  -d www.example.com
+zone "example.com" {
+    type master;
+    update-policy {
+        grant update subdomain example.com.;
+    };
+}
 ```
 
-For more details, please check our sample script: [dns_myapi.sh](dns_myapi.sh)
+Finally, make the DNS server and update Key available to `acme.sh`
 
-# Use lexicon dns api
+```
+export NSUPDATE_SERVER="dns.example.com"
+export NSUPDATE_KEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=="
+```
 
-https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_nsupdate -d example.com -d www.example.com
+```
+
+The `NSUPDATE_SERVER` and `NSUPDATE_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
-## Use LuaDNS domain API
+
+## 8. Use LuaDNS domain API
 
 Get your API token at https://api.luadns.com/settings
 
 ```
 export LUA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
-
 export LUA_Email="xxxx@sss.com"
-
 ```
 
 To issue a cert:
 ```
-acme.sh   --issue   --dns dns_lua --dnssleep 3  -d example.com  -d www.example.com
+acme.sh --issue --dns dns_lua -d example.com -d www.example.com
 ```
 
-The `LUA_Key` and `LUA_Email`  will be saved in `~/.acme.sh/account.conf`, and will be reused when needed.
+The `LUA_Key` and `LUA_Email` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
 
-## Use DNSMadeEasy domain API
+
+## 9. Use DNSMadeEasy domain API
 
 Get your API credentials at https://cp.dnsmadeeasy.com/account/info
 
 ```
 export ME_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
-
 export ME_Secret="qdfqsdfkjdskfj"
+```
+
+To issue a cert:
+```
+acme.sh --issue --dns dns_me -d example.com -d www.example.com
+```
+
+The `ME_Key` and `ME_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+
+## 10. Use Amazon Route53 domain API
+
+https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API
+
+```
+export  AWS_ACCESS_KEY_ID=XXXXXXXXXX
+export  AWS_SECRET_ACCESS_KEY=XXXXXXXXXXXXXXX
+```
+
+To issue a cert:
+```
+acme.sh --issue --dns dns_aws -d example.com -d www.example.com
+```
+
+The `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 11. Use Aliyun domain API to automatically issue cert
+
+First you need to login to your Aliyun account to get your API key.
+[https://ak-console.aliyun.com/#/accesskey](https://ak-console.aliyun.com/#/accesskey)
+
+```
+export Ali_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+export Ali_Secret="jlsdflanljkljlfdsaklkjflsa"
+```
+
+Ok, let's issue a cert now:
+```
+acme.sh --issue --dns dns_ali -d example.com -d www.example.com
+```
+
+The `Ali_Key` and `Ali_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 12. Use ISPConfig 3.1 API
+
+This only works for ISPConfig 3.1 (and newer).
+
+Create a Remote User in the ISPConfig Control Panel. The Remote User must have access to at least `DNS zone functions` and `DNS txt functions`.
 
 ```
+export ISPC_User="xxx"
+export ISPC_Password="xxx"
+export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php"
+export ISPC_Api_Insecure=1
+```
+If you have installed ISPConfig on a different port, then alter the 8080 accordingly.
+Leaver ISPC_Api_Insecure set to 1 if you have not a valid ssl cert for your installation. Change it to 0 if you have a valid ssl cert.
 
 To issue a cert:
 ```
-acme.sh   --issue   --dns dns_me --dnssleep 3  -d example.com  -d www.example.com
+acme.sh --issue --dns dns_ispconfig -d example.com -d www.example.com
 ```
 
-The `ME_Key` and `ME_Secret`  will be saved in `~/.acme.sh/account.conf`, and will be reused when needed.
+The `ISPC_User`, `ISPC_Password`, `ISPC_Api`and `ISPC_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 13. Use Alwaysdata domain API
 
+First you need to login to your Alwaysdata account to get your API Key.
+
+```sh
+export AD_API_KEY="myalwaysdataapikey"
+```
+
+Ok, let's issue a cert now:
+
+```sh
+acme.sh --issue --dns dns_ad -d example.com -d www.example.com
+```
+
+The `AD_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused
+when needed.
+
+## 14. Use Linode domain API
+
+First you need to login to your Linode account to get your API Key.
+[https://manager.linode.com/profile/api](https://manager.linode.com/profile/api)
+
+Then add an API key with label *ACME* and copy the new key.
+
+```sh
+export LINODE_API_KEY="..."
+```
 
+Due to the reload time of any changes in the DNS records, we have to use the `dnssleep` option to wait at least 15 minutes for the changes to take effect.
 
+Ok, let's issue a cert now:
+
+```sh
+acme.sh --issue --dns dns_linode --dnssleep 900 -d example.com -d www.example.com
+```
+
+The `LINODE_API_KEY` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
+
+## 15. Use FreeDNS
+
+FreeDNS (https://freedns.afraid.org/) does not provide an API to update DNS records (other than IPv4 and IPv6
+dynamic DNS addresses).  The acme.sh plugin therefore retrieves and updates domain TXT records by logging
+into the FreeDNS website to read the HTML and posting updates as HTTP.  The plugin needs to know your
+userid and password for the FreeDNS website.
+
+```sh
+export FREEDNS_User="..."
+export FREEDNS_Password="..."
+```
+
+You need only provide this the first time you run the acme.sh client with FreeDNS validation and then again
+whenever you change your password at the FreeDNS site.  The acme.sh FreeDNS plugin does not store your userid
+or password but rather saves an authentication token returned by FreeDNS in `~/.acme.sh/account.conf` and
+reuses that when needed.
+
+Now you can issue a certificate.
+
+```sh
+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
+validate with acme.sh at FreeDNS.
+
+# Use custom API
+
+If your API is not supported yet, you can write your own DNS API.
+
+Let's assume you want to name it 'myapi':
+
+1. Create a bash script named `~/.acme.sh/dns_myapi.sh`,
+2. In the script you must have a function named `dns_myapi_add()` which will be called by acme.sh to add the DNS records.
+3. Then you can use your API to issue cert like this:
+
+```
+acme.sh --issue --dns dns_myapi -d example.com -d www.example.com
+```
+
+For more details, please check our sample script: [dns_myapi.sh](dns_myapi.sh)
+
+
+# Use lexicon DNS API
+
+https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api

+ 147 - 0
dnsapi/dns_ad.sh

@@ -0,0 +1,147 @@
+#!/usr/bin/env sh
+
+#
+#AD_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+#This is the Alwaysdata api wrapper for acme.sh
+#
+#Author: Paul Koppen
+#Report Bugs here: https://github.com/wpk-/acme.sh
+
+AD_API_URL="https://$AD_API_KEY:@api.alwaysdata.com/v1"
+
+########  Public functions #####################
+
+#Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_ad_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$AD_API_KEY" ]; then
+    AD_API_KEY=""
+    _err "You didn't specify the AD api key yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  _saveaccountconf AD_API_KEY "$AD_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"
+
+  _ad_tmpl_json="{\"domain\":$_domain_id,\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\"}"
+
+  if _ad_rest POST "record/" "$_ad_tmpl_json" && [ -z "$response" ]; then
+    _info "txt record updated success."
+    return 0
+  fi
+
+  return 1
+}
+
+#fulldomain txtvalue
+dns_ad_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"
+  _ad_rest GET "record/?domain=$_domain_id&name=$_sub_domain"
+
+  if [ -n "$response" ]; then
+    record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+    _debug record_id "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if _ad_rest DELETE "record/$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 _ad_rest GET "domain/"; 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
+_ad_rest() {
+  mtd="$1"
+  ep="$2"
+  data="$3"
+
+  _debug mtd "$mtd"
+  _debug ep "$ep"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+
+  if [ "$mtd" != "GET" ]; then
+    # both POST and DELETE.
+    _debug data "$data"
+    response="$(_post "$data" "$AD_API_URL/$ep" "" "$mtd")"
+  else
+    response="$(_get "$AD_API_URL/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 187 - 0
dnsapi/dns_ali.sh

@@ -0,0 +1,187 @@
+#!/usr/bin/env sh
+
+Ali_API="https://alidns.aliyuncs.com/"
+
+#Ali_Key="LTqIA87hOKdjevsf5"
+#Ali_Secret="0p5EYueFNq501xnCPzKNbx6K51qPH2"
+
+#Usage: dns_ali_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_ali_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then
+    Ali_Key=""
+    Ali_Secret=""
+    _err "You don't specify aliyun api key and secret yet."
+    return 1
+  fi
+
+  #save the api key and secret to the account conf file.
+  _saveaccountconf Ali_Key "$Ali_Key"
+  _saveaccountconf Ali_Secret "$Ali_Secret"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    return 1
+  fi
+
+  _debug "Add record"
+  _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record"
+}
+
+dns_ali_rm() {
+  fulldomain=$1
+  _clean
+}
+
+####################  Private functions below ##################################
+
+_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
+
+    _describe_records_query "$h"
+    if ! _ali_rest "Get root" "ignore"; then
+      return 1
+    fi
+
+    if _contains "$response" "PageNumber"; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _debug _sub_domain "$_sub_domain"
+      _domain="$h"
+      _debug _domain "$_domain"
+      return 0
+    fi
+    p="$i"
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_ali_rest() {
+  signature=$(printf "%s" "GET&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64)
+  signature=$(_ali_urlencode "$signature")
+  url="$Ali_API?$query&Signature=$signature"
+
+  if ! response="$(_get "$url")"; then
+    _err "Error <$1>"
+    return 1
+  fi
+
+  if [ -z "$2" ]; then
+    message="$(printf "%s" "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
+    if [ -n "$message" ]; then
+      _err "$message"
+      return 1
+    fi
+  fi
+
+  _debug2 response "$response"
+  return 0
+}
+
+_ali_urlencode() {
+  _str="$1"
+  _str_len=${#_str}
+  _u_i=1
+  while [ "$_u_i" -le "$_str_len" ]; do
+    _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")"
+    case $_str_c in [a-zA-Z0-9.~_-])
+      printf "%s" "$_str_c"
+      ;;
+    *)
+      printf "%%%02X" "'$_str_c"
+      ;;
+    esac
+    _u_i="$(_math "$_u_i" + 1)"
+  done
+}
+
+_ali_nonce() {
+  #_head_n 1 </dev/urandom | _digest "sha256" hex | cut -c 1-31
+  #Not so good...
+  date +"%s%N"
+}
+
+_check_exist_query() {
+  query=''
+  query=$query'AccessKeyId='$Ali_Key
+  query=$query'&Action=DescribeDomainRecords'
+  query=$query'&DomainName='$1
+  query=$query'&Format=json'
+  query=$query'&RRKeyWord=_acme-challenge'
+  query=$query'&SignatureMethod=HMAC-SHA1'
+  query=$query"&SignatureNonce=$(_ali_nonce)"
+  query=$query'&SignatureVersion=1.0'
+  query=$query'&Timestamp='$(_timestamp)
+  query=$query'&TypeKeyWord=TXT'
+  query=$query'&Version=2015-01-09'
+}
+
+_add_record_query() {
+  query=''
+  query=$query'AccessKeyId='$Ali_Key
+  query=$query'&Action=AddDomainRecord'
+  query=$query'&DomainName='$1
+  query=$query'&Format=json'
+  query=$query'&RR='$2
+  query=$query'&SignatureMethod=HMAC-SHA1'
+  query=$query"&SignatureNonce=$(_ali_nonce)"
+  query=$query'&SignatureVersion=1.0'
+  query=$query'&Timestamp='$(_timestamp)
+  query=$query'&Type=TXT'
+  query=$query'&Value='$3
+  query=$query'&Version=2015-01-09'
+}
+
+_delete_record_query() {
+  query=''
+  query=$query'AccessKeyId='$Ali_Key
+  query=$query'&Action=DeleteDomainRecord'
+  query=$query'&Format=json'
+  query=$query'&RecordId='$1
+  query=$query'&SignatureMethod=HMAC-SHA1'
+  query=$query"&SignatureNonce=$(_ali_nonce)"
+  query=$query'&SignatureVersion=1.0'
+  query=$query'&Timestamp='$(_timestamp)
+  query=$query'&Version=2015-01-09'
+}
+
+_describe_records_query() {
+  query=''
+  query=$query'AccessKeyId='$Ali_Key
+  query=$query'&Action=DescribeDomainRecords'
+  query=$query'&DomainName='$1
+  query=$query'&Format=json'
+  query=$query'&SignatureMethod=HMAC-SHA1'
+  query=$query"&SignatureNonce=$(_ali_nonce)"
+  query=$query'&SignatureVersion=1.0'
+  query=$query'&Timestamp='$(_timestamp)
+  query=$query'&Version=2015-01-09'
+}
+
+_clean() {
+  _check_exist_query "$_domain"
+  if ! _ali_rest "Check exist records" "ignore"; then
+    return 1
+  fi
+
+  records="$(echo "$response" -n | _egrep_o "\"RecordId\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")"
+  printf "%s" "$records" \
+    | while read -r record_id; do
+      _delete_record_query "$record_id"
+      _ali_rest "Delete record $record_id" "ignore"
+    done
+}
+
+_timestamp() {
+  date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ"
+}

+ 227 - 0
dnsapi/dns_aws.sh

@@ -0,0 +1,227 @@
+#!/usr/bin/env sh
+
+#
+#AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+#AWS_SECRET_ACCESS_KEY="xxxxxxx"
+
+#This is the Amazon Route53 api wrapper for acme.sh
+
+AWS_HOST="route53.amazonaws.com"
+AWS_URL="https://$AWS_HOST"
+
+AWS_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API"
+
+########  Public functions #####################
+
+#Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_aws_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
+    AWS_ACCESS_KEY_ID=""
+    AWS_SECRET_ACCESS_KEY=""
+    _err "You don't specify aws route53 api key id and and api key secret yet."
+    _err "Please create you key and try again. see $(__green $AWS_WIKI)"
+    return 1
+  fi
+
+  if [ -z "$AWS_SESSION_TOKEN" ]; then
+    _saveaccountconf AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID"
+    _saveaccountconf AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY"
+  fi
+
+  _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"
+
+  _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><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
+    _info "txt record updated success."
+    return 0
+  fi
+
+  return 1
+}
+
+#fulldomain txtvalue
+dns_aws_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"
+
+  _aws_tmpl_xml="<ChangeResourceRecordSetsRequest xmlns=\"https://route53.amazonaws.com/doc/2013-04-01/\"><ChangeBatch><Changes><Change><Action>DELETE</Action><ResourceRecordSet><ResourceRecords><ResourceRecord><Value>\"$txtvalue\"</Value></ResourceRecord></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
+    _info "txt record deleted success."
+    return 0
+  fi
+
+  return 1
+
+}
+
+####################  Private functions below ##################################
+
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+
+  if aws_rest GET "2013-04-01/hostedzone"; then
+    _debug "response" "$response"
+    while true; do
+      h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+      if [ -z "$h" ]; then
+        #not valid
+        return 1
+      fi
+
+      if _contains "$response" "<Name>$h.</Name>"; then
+        hostedzone="$(echo "$response" | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<.HostedZone>")"
+        _debug hostedzone "$hostedzone"
+        if [ -z "$hostedzone" ]; then
+          _err "Error, can not get hostedzone."
+          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)
+    done
+  fi
+  return 1
+}
+
+#method uri qstr data
+aws_rest() {
+  mtd="$1"
+  ep="$2"
+  qsr="$3"
+  data="$4"
+
+  _debug mtd "$mtd"
+  _debug ep "$ep"
+  _debug qsr "$qsr"
+  _debug data "$data"
+
+  CanonicalURI="/$ep"
+  _debug2 CanonicalURI "$CanonicalURI"
+
+  CanonicalQueryString="$qsr"
+  _debug2 CanonicalQueryString "$CanonicalQueryString"
+
+  RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")"
+  _debug2 RequestDate "$RequestDate"
+
+  #RequestDate="20161120T141056Z" ##############
+
+  export _H1="x-amz-date: $RequestDate"
+
+  aws_host="$AWS_HOST"
+  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"
+    CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n"
+    SignedHeaders="${SignedHeaders};x-amz-security-token"
+  fi
+  _debug2 CanonicalHeaders "$CanonicalHeaders"
+  _debug2 SignedHeaders "$SignedHeaders"
+
+  RequestPayload="$data"
+  _debug2 RequestPayload "$RequestPayload"
+
+  Hash="sha256"
+
+  CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)"
+  _debug2 CanonicalRequest "$CanonicalRequest"
+
+  HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)"
+  _debug2 HashedCanonicalRequest "$HashedCanonicalRequest"
+
+  Algorithm="AWS4-HMAC-SHA256"
+  _debug2 Algorithm "$Algorithm"
+
+  RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)"
+  _debug2 RequestDateOnly "$RequestDateOnly"
+
+  Region="us-east-1"
+  Service="route53"
+
+  CredentialScope="$RequestDateOnly/$Region/$Service/aws4_request"
+  _debug2 CredentialScope "$CredentialScope"
+
+  StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest"
+
+  _debug2 StringToSign "$StringToSign"
+
+  kSecret="AWS4$AWS_SECRET_ACCESS_KEY"
+
+  #kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################
+
+  _debug2 kSecret "$kSecret"
+
+  kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")"
+  _debug2 kSecretH "$kSecretH"
+
+  kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)"
+  _debug2 kDateH "$kDateH"
+
+  kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)"
+  _debug2 kRegionH "$kRegionH"
+
+  kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)"
+  _debug2 kServiceH "$kServiceH"
+
+  kSigningH="$(printf "aws4_request%s" | _hmac "$Hash" "$kServiceH" hex)"
+  _debug2 kSigningH "$kSigningH"
+
+  signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)"
+  _debug2 signature "$signature"
+
+  Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature"
+  _debug2 Authorization "$Authorization"
+
+  _H3="Authorization: $Authorization"
+  _debug _H3 "$_H3"
+
+  url="$AWS_URL/$ep"
+
+  if [ "$mtd" = "GET" ]; then
+    response="$(_get "$url")"
+  else
+    response="$(_post "$data" "$url")"
+  fi
+
+  _ret="$?"
+  if [ "$_ret" = "0" ]; then
+    if _contains "$response" "<ErrorResponse"; then
+      _err "Response error:$response"
+      return 1
+    fi
+  fi
+
+  return "$_ret"
+}

+ 52 - 14
dnsapi/dns_cf.sh

@@ -22,6 +22,12 @@ dns_cf_add() {
     return 1
   fi
 
+  if ! _contains "$CF_Email" "@"; then
+    _err "It seems that the CF_Email=$CF_Email is not a valid email address."
+    _err "Please check and retry."
+    return 1
+  fi
+
   #save the api key and email to the account conf file.
   _saveaccountconf CF_Key "$CF_Key"
   _saveaccountconf CF_Email "$CF_Email"
@@ -49,9 +55,7 @@ dns_cf_add() {
     _info "Adding record"
     if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
       if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then
-        _info "Added, sleeping 10 seconds"
-        sleep 10
-        #todo: check if the record takes effect
+        _info "Added, OK"
         return 0
       else
         _err "Add txt record error."
@@ -66,9 +70,7 @@ dns_cf_add() {
 
     _cf_rest PUT "zones/$_domain_id/dns_records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"zone_name\":\"$_domain\"}"
     if [ "$?" = "0" ]; then
-      _info "Updated, sleeping 10 seconds"
-      sleep 10
-      #todo: check if the record takes effect
+      _info "Updated, OK"
       return 0
     fi
     _err "Update error"
@@ -77,13 +79,48 @@ dns_cf_add() {
 
 }
 
-#fulldomain
+#fulldomain txtvalue
 dns_cf_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"
+  _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain&content=$txtvalue"
+
+  if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then
+    _err "Error"
+    return 1
+  fi
+
+  count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+  _debug count "$count"
+  if [ "$count" = "0" ]; then
+    _info "Don't need to remove."
+  else
+    record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
+    _debug "record_id" "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if ! _cf_rest DELETE "zones/$_domain_id/dns_records/$record_id"; then
+      _err "Delete record error."
+      return 1
+    fi
+    _contains "$response" '"success":true'
+  fi
 
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 #_acme-challenge.www.domain.com
 #returns
 # _sub_domain=_acme-challenge.www
@@ -95,6 +132,7 @@ _get_root() {
   p=1
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
     if [ -z "$h" ]; then
       #not valid
       return 1
@@ -104,8 +142,8 @@ _get_root() {
       return 1
     fi
 
-    if printf "%s" "$response" | grep "\"name\":\"$h\"" >/dev/null; then
-      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
+    if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | 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
@@ -125,11 +163,11 @@ _cf_rest() {
   data="$3"
   _debug "$ep"
 
-  _H1="X-Auth-Email: $CF_Email"
-  _H2="X-Auth-Key: $CF_Key"
-  _H3="Content-Type: application/json"
+  export _H1="X-Auth-Email: $CF_Email"
+  export _H2="X-Auth-Key: $CF_Key"
+  export _H3="Content-Type: application/json"
 
-  if [ "$data" ]; then
+  if [ "$m" != "GET" ]; then
     _debug data "$data"
     response="$(_post "$data" "$CF_Api/$ep" "" "$m")"
   else

+ 21 - 13
dnsapi/dns_cx.sh

@@ -58,7 +58,15 @@ dns_cx_add() {
 #fulldomain
 dns_cx_rm() {
   fulldomain=$1
-
+  REST_API="$CX_Api"
+  if _get_root "$fulldomain"; then
+    record_id=""
+    existing_records "$_domain" "$_sub_domain"
+    if ! [ "$record_id" = "" ]; then
+      _rest DELETE "record/$record_id/$_domain_id" "{}"
+      _info "Deleted record ${fulldomain}"
+    fi
+  fi
 }
 
 #usage:  root  sub
@@ -69,12 +77,12 @@ existing_records() {
   _debug "Getting txt records"
   root=$1
   sub=$2
-
+  count=0
   if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then
     return 1
   fi
-  count=0
-  seg=$(printf "%s\n" "$response" | _egrep_o "{[^\{]*host\":\"$_sub_domain\"[^\}]*\}")
+
+  seg=$(printf "%s\n" "$response" | _egrep_o '"record_id":[^{]*host":"'"$_sub_domain"'"[^}]*\}')
   _debug seg "$seg"
   if [ -z "$seg" ]; then
     return 0
@@ -82,7 +90,7 @@ existing_records() {
 
   if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then
     count=1
-    record_id=$(printf "%s\n" "$seg" | _egrep_o "\"record_id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
+    record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1)
     _debug record_id "$record_id"
     return 0
   fi
@@ -123,7 +131,7 @@ update_record() {
   return 1
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 #_acme-challenge.www.domain.com
 #returns
 # _sub_domain=_acme-challenge.www
@@ -147,9 +155,9 @@ _get_root() {
     fi
 
     if _contains "$response" "$h."; then
-      seg=$(printf "%s" "$response" | _egrep_o "\{[^\{]*\"$h\.\"[^\}]*\}")
+      seg=$(printf "%s\n" "$response" | _egrep_o '"id":[^{]*"'"$h"'."[^}]*}')
       _debug seg "$seg"
-      _domain_id=$(printf "%s" "$seg" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
+      _domain_id=$(printf "%s\n" "$seg" | _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)
@@ -170,7 +178,7 @@ _get_root() {
 _rest() {
   m=$1
   ep="$2"
-  _debug "$ep"
+  _debug ep "$ep"
   url="$REST_API/$ep"
   _debug url "$url"
 
@@ -185,10 +193,10 @@ _rest() {
   hmac=$(printf "%s" "$sec" | _digest md5 hex)
   _debug hmac "$hmac"
 
-  _H1="API-KEY: $CX_Key"
-  _H2="API-REQUEST-DATE: $cdate"
-  _H3="API-HMAC: $hmac"
-  _H4="Content-Type: application/json"
+  export _H1="API-KEY: $CX_Key"
+  export _H2="API-REQUEST-DATE: $cdate"
+  export _H3="API-HMAC: $hmac"
+  export _H4="Content-Type: application/json"
 
   if [ "$data" ]; then
     response="$(_post "$data" "$url" "" "$m")"

+ 40 - 12
dnsapi/dns_dp.sh

@@ -6,9 +6,8 @@
 #
 #DP_Key="sADDsdasdgdsf"
 
-DP_Api="https://dnsapi.cn"
+REST_API="https://dnsapi.cn"
 
-#REST_API
 ########  Public functions #####################
 
 #Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -24,8 +23,6 @@ dns_dp_add() {
     return 1
   fi
 
-  REST_API="$DP_Api"
-
   #save the api key and email to the account conf file.
   _saveaccountconf DP_Id "$DP_Id"
   _saveaccountconf DP_Key "$DP_Key"
@@ -50,9 +47,39 @@ dns_dp_add() {
   fi
 }
 
-#fulldomain
+#fulldomain txtvalue
 dns_dp_rm() {
   fulldomain=$1
+  txtvalue=$2
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_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" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then
+    _err "Record.Remove error."
+    return 1
+  fi
+
+  _contains "$response" "Action completed successful"
 
 }
 
@@ -75,8 +102,9 @@ existing_records() {
   fi
 
   if _contains "$response" "Action completed successful"; then
-    count=$(printf "%s" "$response" | grep '<type>TXT</type>' | wc -l)
+    count=$(printf "%s" "$response" | grep -c '<type>TXT</type>' | tr -d ' ')
     record_id=$(printf "%s" "$response" | grep '^<id>' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1)
+    _debug record_id "$record_id"
     return 0
   else
     _err "get existing records error."
@@ -130,7 +158,7 @@ update_record() {
   return 1 #error
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 #_acme-challenge.www.domain.com
 #returns
 # _sub_domain=_acme-challenge.www
@@ -171,7 +199,7 @@ _get_root() {
 
 #Usage: method  URI  data
 _rest() {
-  m=$1
+  m="$1"
   ep="$2"
   data="$3"
   _debug "$ep"
@@ -179,11 +207,11 @@ _rest() {
 
   _debug url "$url"
 
-  if [ "$data" ]; then
-    _debug2 data "$data"
-    response="$(_post "$data" "$url")"
+  if [ "$m" = "GET" ]; then
+    response="$(_get "$url" | tr -d '\r')"
   else
-    response="$(_get "$url")"
+    _debug2 data "$data"
+    response="$(_post "$data" "$url" | tr -d '\r')"
   fi
 
   if [ "$?" != "0" ]; then

+ 375 - 0
dnsapi/dns_freedns.sh

@@ -0,0 +1,375 @@
+#!/usr/bin/env sh
+
+#This file name is "dns_freedns.sh"
+#So, here must be a method dns_freedns_add()
+#Which will be called by acme.sh to add the txt record to your api system.
+#returns 0 means success, otherwise error.
+#
+#Author: David Kerr
+#Report Bugs here: https://github.com/dkerr64/acme.sh
+#
+########  Public functions #####################
+
+# Export FreeDNS userid and password in folowing variables...
+#  FREEDNS_User=username
+#  FREEDNS_Password=password
+# login cookie is saved in acme account config file so userid / pw
+# need to be set only when changed.
+
+#Usage: dns_freedns_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_freedns_add() {
+  fulldomain="$1"
+  txtvalue="$2"
+
+  _info "Add TXT record using FreeDNS"
+  _debug "fulldomain: $fulldomain"
+  _debug "txtvalue: $txtvalue"
+
+  if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then
+    FREEDNS_User=""
+    FREEDNS_Password=""
+    if [ -z "$FREEDNS_COOKIE" ]; then
+      _err "You did not specify the FreeDNS username and password yet."
+      _err "Please export as FREEDNS_User / FREEDNS_Password and try again."
+      return 1
+    fi
+    using_cached_cookies="true"
+  else
+    FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")"
+    if [ -z "$FREEDNS_COOKIE" ]; then
+      return 1
+    fi
+    using_cached_cookies="false"
+  fi
+
+  _debug "FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)"
+
+  _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE"
+
+  # split our full domain name into two parts...
+  i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)"
+  i="$(_math "$i" - 1)"
+  top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)"
+  i="$(_math "$i" - 1)"
+  sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")"
+
+  # Sometimes FreeDNS does not reurn 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
+  # load the page and obtain our domain ID
+  attempts=2
+  while [ "$attempts" -gt "0" ]; do
+    attempts="$(_math "$attempts" - 1)"
+
+    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 "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    
+    subdomain_csv="$(echo "$htmlpage" \
+      | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \
+      | sed 's/^[\ \t]*//g' \
+      | tr -d '\n' \
+      | sed 's/<\/TR[^>]*>/\n/Ig' \
+      | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \
+      | sed 's/^<T[DH][^>]*>\|<\/\?T[DH][^>]*>$//Ig' \
+      | sed 's/<\/T[DH][^>]*><T[DH][^>]*>/,/Ig' \
+      | grep 'edit.php?' \
+      | grep "$top_domain")"
+    # The above beauty ends with striping out rows that do not have an
+    # href to edit.php and do not have the top domain we are looking for.
+    # So all we should be left with is CSV of table of subdomains we are
+    # interested in.
+
+    # Now we have to read through this table and extract the data we need
+    lines="$(echo "$subdomain_csv" | wc -l)"
+    nl='
+'
+    i=0
+    found=0
+    while [ "$i" -lt "$lines" ]; do
+      i="$(_math "$i" + 1)"
+      line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")"
+      tmp="$(echo "$line" | cut -d ',' -f 1)"
+      if [ $found = 0 ] && _startswith "$tmp" "<td>$top_domain"; then
+        # this line will contain DNSdomainid for the top_domain
+        DNSdomainid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*domain_id=//;s/>.*//')"
+        found=1
+      else
+        # lines contain DNS records for all subdomains
+        DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')"
+        DNStype="$(echo "$line" | cut -d ',' -f 3)"
+        if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then
+          DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')"
+          # Now get current value for the TXT record.  This method may
+          # 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.
+          DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^&quot;]*&quot;//;s/&quot;.*//;s/<\/td>.*//')"
+          if [ $found != 0 ]; then
+            break
+            # we are breaking out of the loop at the first match of DNS name
+            # and DNS type (if we are past finding the domainid). This assumes
+            # that there is only ever one TXT record for the LetsEncrypt/acme
+            # challenge subdomain.  This seems to be a reasonable assumption
+            # as the acme client deletes the TXT record on successful validation.
+          fi
+        else
+          DNSname=""
+          DNStype=""
+        fi
+      fi
+    done
+
+    _debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid"
+    _debug "DNSvalue: $DNSvalue"
+
+    if [ -z "$DNSdomainid" ]; then
+      # If domain ID is empty then something went wrong (top level
+      # domain not found at FreeDNS).
+      if [ "$attempts" = "0" ]; then
+        # exhausted maximum retry attempts
+        _debug "$htmlpage"
+        _debug "$subdomain_csv"
+        _err "Domain $top_domain not found at FreeDNS"
+        return 1
+      fi
+    else
+      # break out of the 'retry' loop... we have found our domain ID
+      break
+    fi
+    _info "Domain $top_domain not found at FreeDNS"
+    _info "Retry loading subdomain page ($attempts attempts remaining)"
+  done
+
+  if [ -z "$DNSdataid" ]; then
+    # If data ID is empty then specific subdomain does not exist yet, need
+    # to create it this should always be the case as the acme client
+    # deletes the entry after domain is validated.
+    _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue"
+    return $?
+  else
+    if [ "$txtvalue" = "$DNSvalue" ]; then
+      # if value in TXT record matches value requested then DNS record
+      # does not need to be updated. But...
+      # Testing value match fails.  Website is truncating the value field.
+      # So for now we will always go down the else path.  Though in theory
+      # should never come here anyway as the acme client deletes
+      # the TXT record on successful validation, so we should not even
+      # have found a TXT record !!
+      _info "No update necessary for $fulldomain at FreeDNS"
+      return 0
+    else
+      # Delete the old TXT record (with the wrong value)
+      _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid"
+      if [ "$?" = "0" ]; then
+        # And add in new TXT record with the value provided
+        _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue"
+      fi
+      return $?
+    fi
+  fi
+  return 0
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_freedns_rm() {
+  fulldomain="$1"
+  txtvalue="$2"
+
+  _info "Delete TXT record using FreeDNS"
+  _debug "fulldomain: $fulldomain"
+  _debug "txtvalue: $txtvalue"
+
+  # 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
+  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 
+  # 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
+  # load the page and obtain our TXT record.
+  attempts=2
+  while [ "$attempts" -gt "0" ]; do
+    attempts="$(_math "$attempts" - 1)"
+
+    htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")"
+    if [ "$?" != "0" ]; then
+      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
+    subdomain_csv="$(echo "$htmlpage" \
+      | grep -i -e '</\?TABLE\|</\?TD\|</\?TR\|</\?TH' \
+      | sed 's/^[\ \t]*//g' \
+      | tr -d '\n' \
+      | sed 's/<\/TR[^>]*>/\n/Ig' \
+      | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \
+      | sed 's/^<T[DH][^>]*>\|<\/\?T[DH][^>]*>$//Ig' \
+      | sed 's/<\/T[DH][^>]*><T[DH][^>]*>/,/Ig' \
+      | grep 'edit.php?' \
+      | grep "$fulldomain")"
+    # The above beauty ends with striping out rows that do not have an
+    # href to edit.php and do not have the domain name we are looking for.
+    # So all we should be left with is CSV of table of subdomains we are
+    # interested in.
+
+    # Now we have to read through this table and extract the data we need
+    lines="$(echo "$subdomain_csv" | wc -l)"
+    nl='
+'
+    i=0
+    found=0
+    while [ "$i" -lt "$lines" ]; do
+      i="$(_math "$i" + 1)"
+      line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")"
+      DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')"
+      DNStype="$(echo "$line" | cut -d ',' -f 3)"
+      if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then
+        DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')"
+        DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^&quot;]*&quot;//;s/&quot;.*//;s/<\/td>.*//')"
+        _debug "DNSvalue: $DNSvalue"
+        #     if [ "$DNSvalue" = "$txtvalue" ]; then
+        # Testing value match fails.  Website is truncating the value
+        # field. So for now we will assume that there is only one TXT
+        # field for the sub domain and just delete it. Currently this
+        # is a safe assumption.
+        _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid"
+        return $?
+        #     fi
+      fi
+    done
+  done
+
+  # If we get this far we did not find a match (after two attempts)
+  # Not necessarily an error, but log anyway.
+  _debug2 "$subdomain_csv"
+  _info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS"
+  return 0
+}
+
+####################  Private functions below ##################################
+
+# usage: _freedns_login username password
+# print string "cookie=value" etc.
+# returns 0 success
+_freedns_login() {
+  export _H1="Accept-Language:en-US"
+  username="$1"
+  password="$2"
+  url="https://freedns.afraid.org/zc.php?step=2"
+
+  _debug "Login to FreeDNS as user $username"
+
+  htmlpage="$(_post "username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" "$url")"
+
+  if [ "$?" != "0" ]; then
+    _err "FreeDNS login failed for user $username bad RC from _post"
+    return 1
+  fi
+
+  cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+
+  # if cookies is not empty then logon successful
+  if [ -z "$cookies" ]; then
+    _debug "$htmlpage"
+    _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file"
+    return 1
+  fi
+
+  printf "%s" "$cookies"
+  return 0
+}
+
+# usage _freedns_retrieve_subdomain_page login_cookies
+# echo page retrieved (html)
+# returns 0 success
+_freedns_retrieve_subdomain_page() {
+  export _H1="Cookie:$1"
+  export _H2="Accept-Language:en-US"
+  url="https://freedns.afraid.org/subdomain/"
+
+  _debug "Retrieve subdmoain page from FreeDNS"
+
+  htmlpage="$(_get "$url")"
+
+  if [ "$?" != "0" ]; then
+    _err "FreeDNS retrieve subdomins failed bad RC from _get"
+    return 1
+  elif [ -z "$htmlpage" ]; then
+    _err "FreeDNS returned empty subdomain page"
+    return 1
+  fi
+
+  _debug2 "$htmlpage"
+
+  printf "%s" "$htmlpage"
+  return 0
+}
+
+# usage _freedns_add_txt_record login_cookies domain_id subdomain value
+# returns 0 success
+_freedns_add_txt_record() {
+  export _H1="Cookie:$1"
+  export _H2="Accept-Language:en-US"
+  domain_id="$2"
+  subdomain="$3"
+  value="$(printf '%s' "$4" | _url_encode)"
+  url="http://freedns.afraid.org/subdomain/save.php?step=2"
+
+  htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")"
+
+  if [ "$?" != "0" ]; then
+    _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post"
+    return 1
+  elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then
+    _debug "$htmlpage"
+    _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file"
+    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 "Note that you cannot use automatic DNS validation for FreeDNS public domains"
+    return 1
+  fi
+
+  _debug2 "$htmlpage"
+  _info "Added acme challenge TXT record for $fulldomain at FreeDNS"
+  return 0
+}
+
+# usage _freedns_delete_txt_record login_cookies data_id
+# returns 0 success
+_freedns_delete_txt_record() {
+  export _H1="Cookie:$1"
+  export _H2="Accept-Language:en-US"
+  data_id="$2"
+  url="https://freedns.afraid.org/subdomain/delete2.php"
+
+  htmlheader="$(_get "$url?data_id%5B%5D=$data_id&submit=delete+selected" "onlyheader")"
+
+  if [ "$?" != "0" ]; then
+    _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get"
+    return 1
+  elif ! _contains "$htmlheader" "200 OK"; then
+    _debug "$htmlheader"
+    _err "FreeDNS failed to delete TXT record $data_id"
+    return 1
+  fi
+
+  _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS"
+  return 0
+}

+ 3 - 3
dnsapi/dns_gd.sh

@@ -59,7 +59,7 @@ dns_gd_rm() {
 
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 #_acme-challenge.www.domain.com
 #returns
 # _sub_domain=_acme-challenge.www
@@ -98,8 +98,8 @@ _gd_rest() {
   data="$3"
   _debug "$ep"
 
-  _H1="Authorization: sso-key $GD_Key:$GD_Secret"
-  _H2="Content-Type: application/json"
+  export _H1="Authorization: sso-key $GD_Key:$GD_Secret"
+  export _H2="Content-Type: application/json"
 
   if [ "$data" ]; then
     _debug data "$data"

+ 177 - 0
dnsapi/dns_ispconfig.sh

@@ -0,0 +1,177 @@
+#!/usr/bin/env sh
+
+# ISPConfig 3.1 API
+# User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to:
+# - DNS zone Functions
+# - DNS txt Functions
+
+# Report bugs to https://github.com/sjau/acme.sh
+
+# Values to export:
+# export ISPC_User="remoteUser"
+# export ISPC_Password="remotePassword"
+# export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php"
+# export ISPC_Api_Insecure=1     # Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1)
+
+########  Public functions #####################
+
+#Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_ispconfig_add() {
+  fulldomain="${1}"
+  txtvalue="${2}"
+  _debug "Calling: dns_ispconfig_add() '${fulldomain}' '${txtvalue}'"
+  _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt
+}
+
+#Usage: dns_myapi_rm   _acme-challenge.www.domain.com
+dns_ispconfig_rm() {
+  fulldomain="${1}"
+  _debug "Calling: dns_ispconfig_rm() '${fulldomain}'"
+  _ISPC_credentials && _ISPC_login && _ISPC_rmTxt
+}
+
+####################  Private functions below ##################################
+
+_ISPC_credentials() {
+  if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then
+    ISPC_User=""
+    ISPC_Password=""
+    ISPC_Api=""
+    ISPC_Api_Insecure=""
+    _err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again."
+    return 1
+  else
+    _saveaccountconf ISPC_User "${ISPC_User}"
+    _saveaccountconf ISPC_Password "${ISPC_Password}"
+    _saveaccountconf ISPC_Api "${ISPC_Api}"
+    _saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}"
+    # Set whether curl should use secure or insecure mode
+    export HTTPS_INSECURE="${ISPC_Api_Insecure}"
+  fi
+}
+
+_ISPC_login() {
+  _info "Getting Session ID"
+  curData="{\"username\":\"${ISPC_User}\",\"password\":\"${ISPC_Password}\",\"client_login\":false}"
+  curResult="$(_post "${curData}" "${ISPC_Api}?login")"
+  _debug "Calling _ISPC_login: '${curData}' '${ISPC_Api}?login'"
+  _debug "Result of _ISPC_login: '$curResult'"
+  if _contains "${curResult}" '"code":"ok"'; then
+    sessionID=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+    _info "Retrieved Session ID."
+    _debug "Session ID: '${sessionID}'"
+  else
+    _err "Couldn't retrieve the Session ID."
+    return 1
+  fi
+}
+
+_ISPC_getZoneInfo() {
+  _info "Getting Zoneinfo"
+  zoneEnd=false
+  curZone="${fulldomain}"
+  while [ "${zoneEnd}" = false ]; do
+    # we can strip the first part of the fulldomain, since it's just the _acme-challenge string
+    curZone="${curZone#*.}"
+    # suffix . needed for zone -> domain.tld.
+    curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}"
+    curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")"
+    _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?login'"
+    _debug "Result of _ISPC_getZoneInfo: '$curResult'"
+    if _contains "${curResult}" '"id":"'; then
+      zoneFound=true
+      zoneEnd=true
+      _info "Retrieved zone data."
+      _debug "Zone data: '${curResult}'"
+    fi
+    if [ "${curZone#*.}" != "$curZone" ]; then
+      _debug2 "$curZone still contains a '.' - so we can check next higher level"
+    else
+      zoneEnd=true
+      _err "Couldn't retrieve zone data."
+      return 1
+    fi
+  done
+  if [ "${zoneFound}" ]; then
+    server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+    _debug "Server ID: '${server_id}'"
+    case "${server_id}" in
+      '' | *[!0-9]*)
+        _err "Server ID is not numeric."
+        return 1
+        ;;
+      *) _info "Retrieved Server ID" ;;
+    esac
+    zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+    _debug "Zone: '${zone}'"
+    case "${zone}" in
+      '' | *[!0-9]*)
+        _err "Zone ID is not numeric."
+        return 1
+        ;;
+      *) _info "Retrieved Zone ID" ;;
+    esac
+    client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+    _debug "Client ID: '${client_id}'"
+    case "${client_id}" in
+      '' | *[!0-9]*)
+        _err "Client ID is not numeric."
+        return 1
+        ;;
+      *) _info "Retrieved Client ID." ;;
+    esac
+    zoneFound=""
+    zoneEnd=""
+  fi
+}
+
+_ISPC_addTxt() {
+  curSerial="$(date +%s)"
+  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}\""
+  curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}}}"
+  curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")"
+  _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'"
+  _debug "Result of _ISPC_addTxt: '$curResult'"
+  record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+  _debug "Record ID: '${record_id}'"
+  case "${record_id}" in
+    '' | *[!0-9]*)
+      _err "Couldn't add ACME Challenge TXT record to zone."
+      return 1
+      ;;
+    *) _info "Added ACME Challenge TXT record to zone." ;;
+  esac
+}
+
+_ISPC_rmTxt() {
+  # Need to get the record ID.
+  curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"name\":\"${fulldomain}.\",\"type\":\"TXT\"}}"
+  curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_get")"
+  _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_get'"
+  _debug "Result of _ISPC_rmTxt: '$curResult'"
+  if _contains "${curResult}" '"code":"ok"'; then
+    record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2)
+    _debug "Record ID: '${record_id}'"
+    case "${record_id}" in
+      '' | *[!0-9]*)
+        _err "Record ID is not numeric."
+        return 1
+        ;;
+      *)
+        unset IFS
+        _info "Retrieved Record ID."
+        curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}"
+        curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")"
+        _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'"
+        _debug "Result of _ISPC_rmTxt: '$curResult'"
+        if _contains "${curResult}" '"code":"ok"'; then
+          _info "Removed ACME Challenge TXT record from zone."
+        else
+          _err "Couldn't remove ACME Challenge TXT record from zone."
+          return 1
+        fi
+        ;;
+    esac
+  fi
+}

+ 10 - 5
dnsapi/dns_lexicon.sh

@@ -2,7 +2,7 @@
 
 # dns api wrapper of lexicon for acme.sh
 
-lexicon_url="https://github.com/AnalogJ/lexicon"
+# https://github.com/AnalogJ/lexicon
 lexicon_cmd="lexicon"
 
 wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api"
@@ -30,7 +30,9 @@ dns_lexicon_add() {
   _savedomainconf PROVIDER "$PROVIDER"
   export PROVIDER
 
-  Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr '[a-z]' '[A-Z]')
+  # e.g. busybox-ash does not know [:upper:]
+  # shellcheck disable=SC2018,SC2019
+  Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z')
   Lx_name_v=$(eval echo \$"$Lx_name")
   _debug "$Lx_name" "$Lx_name_v"
   if [ "$Lx_name_v" ]; then
@@ -38,7 +40,8 @@ dns_lexicon_add() {
     eval export "$Lx_name"
   fi
 
-  Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr '[a-z]' '[A-Z]')
+  # shellcheck disable=SC2018,SC2019
+  Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z')
   Lx_token_v=$(eval echo \$"$Lx_token")
   _debug "$Lx_token" "$Lx_token_v"
   if [ "$Lx_token_v" ]; then
@@ -46,7 +49,8 @@ dns_lexicon_add() {
     eval export "$Lx_token"
   fi
 
-  Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr '[a-z]' '[A-Z]')
+  # shellcheck disable=SC2018,SC2019
+  Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z')
   Lx_password_v=$(eval echo \$"$Lx_password")
   _debug "$Lx_password" "$Lx_password_v"
   if [ "$Lx_password_v" ]; then
@@ -54,7 +58,8 @@ dns_lexicon_add() {
     eval export "$Lx_password"
   fi
 
-  Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr '[a-z]' '[A-Z]')
+  # shellcheck disable=SC2018,SC2019
+  Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z')
   Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken")
   _debug "$Lx_domaintoken" "$Lx_domaintoken_v"
   if [ "$Lx_domaintoken_v" ]; then

+ 183 - 0
dnsapi/dns_linode.sh

@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#Author: Philipp Grosswiler <philipp.grosswiler@swiss-design.net>
+
+LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action="
+
+########  Public functions #####################
+
+#Usage: dns_linode_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_linode_add() {
+  fulldomain="${1}"
+  txtvalue="${2}"
+
+  if ! _Linode_API; then
+    return 1
+  fi
+
+  _info "Using Linode"
+  _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Domain does not exist."
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue"
+
+  if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then
+    _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+    _debug _resource_id "$_resource_id"
+
+    if [ -z "$_resource_id" ]; then
+      _err "Error adding the domain resource."
+      return 1
+    fi
+
+    _info "Domain resource successfully added."
+    return 0
+  fi
+
+  return 1
+}
+
+#Usage: dns_linode_rm   _acme-challenge.www.domain.com
+dns_linode_rm() {
+  fulldomain="${1}"
+
+  if ! _Linode_API; then
+    return 1
+  fi
+
+  _info "Using Linode"
+  _debug "Calling: dns_linode_rm() '${fulldomain}'"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Domain does not exist."
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _parameters="&DomainID=$_domain_id"
+
+  if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then
+    response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')"
+
+    resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")"
+    if [ "$resource" ]; then
+      _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+      if [ "$_resource_id" ]; then
+        _debug _resource_id "$_resource_id"
+
+        _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id"
+
+        if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then
+          _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+          _debug _resource_id "$_resource_id"
+
+          if [ -z "$_resource_id" ]; then
+            _err "Error deleting the domain resource."
+            return 1
+          fi
+
+          _info "Domain resource successfully deleted."
+          return 0
+        fi
+      fi
+
+      return 1
+    fi
+
+    return 0
+  fi
+
+  return 1
+}
+
+####################  Private functions below ##################################
+
+_Linode_API() {
+  if [ -z "$LINODE_API_KEY" ]; then
+    LINODE_API_KEY=""
+
+    _err "You didn't specify the Linode API key yet."
+    _err "Please create your key and try again."
+
+    return 1
+  fi
+
+  _saveaccountconf LINODE_API_KEY "$LINODE_API_KEY"
+}
+
+####################  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 _rest GET "domain.list"; 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 "{.*\"DOMAIN\":\s*\"$h\".*}")"
+      if [ "$hostedzone" ]; then
+        _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\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 method action data
+_rest() {
+  mtd="$1"
+  ep="$2"
+  data="$3"
+
+  _debug mtd "$mtd"
+  _debug ep "$ep"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+
+  if [ "$mtd" != "GET" ]; then
+    # both POST and DELETE.
+    _debug data "$data"
+    response="$(_post "$data" "$LINODE_API_URL$ep" "" "$mtd")"
+  else
+    response="$(_get "$LINODE_API_URL$ep$data")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}

+ 10 - 8
dnsapi/dns_lua.sh

@@ -46,12 +46,12 @@ dns_lua_add() {
     return 1
   fi
 
-  count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain\"" | wc -l)
+  count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ")
   _debug count "$count"
   if [ "$count" = "0" ]; then
     _info "Adding record"
     if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
-      if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then
+      if _contains "$response" "$fulldomain"; then
         _info "Added"
         #todo: check if the record takes effect
         return 0
@@ -63,11 +63,11 @@ dns_lua_add() {
     _err "Add txt record error."
   else
     _info "Updating record"
-    record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | cut -d: -f2 | cut -d, -f1)
+    record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | _head_n 1 | cut -d: -f2 | cut -d, -f1)
     _debug "record_id" "$record_id"
 
-    _LUA_rest PUT "zones/$_domain_id/records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"ttl\":120}"
-    if [ "$?" = "0" ]; then
+    _LUA_rest PUT "zones/$_domain_id/records/$record_id" "{\"id\":$record_id,\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"zone_id\":$_domain_id,\"ttl\":120}"
+    if [ "$?" = "0" ] && _contains "$response" "updated_at"; then
       _info "Updated!"
       #todo: check if the record takes effect
       return 0
@@ -84,7 +84,7 @@ dns_lua_rm() {
 
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 #_acme-challenge.www.domain.com
 #returns
 # _sub_domain=_acme-challenge.www
@@ -99,6 +99,7 @@ _get_root() {
   fi
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
     if [ -z "$h" ]; then
       #not valid
       return 1
@@ -106,6 +107,7 @@ _get_root() {
 
     if _contains "$response" "\"name\":\"$h\""; then
       _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1)
+      _debug _domain_id "$_domain_id"
       if [ "$_domain_id" ]; then
         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
         _domain="$h"
@@ -125,8 +127,8 @@ _LUA_rest() {
   data="$3"
   _debug "$ep"
 
-  _H1="Accept: application/json"
-  _H2="Authorization: Basic $LUA_auth"
+  export _H1="Accept: application/json"
+  export _H2="Authorization: Basic $LUA_auth"
   if [ "$data" ]; then
     _debug data "$data"
     response="$(_post "$data" "$LUA_Api/$ep" "" "$m")"

+ 6 - 6
dnsapi/dns_me.sh

@@ -81,7 +81,7 @@ dns_me_rm() {
 
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 #_acme-challenge.www.domain.com
 #returns
 # _sub_domain=_acme-challenge.www
@@ -103,7 +103,7 @@ _get_root() {
     fi
 
     if _contains "$response" "\"name\":\"$h\""; then
-      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2)
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | 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"
@@ -124,11 +124,11 @@ _me_rest() {
   _debug "$ep"
 
   cdate=$(date -u +"%a, %d %b %Y %T %Z")
-  hmac=$(printf "%s" "$cdate" | _hmac sha1 "$ME_Secret" 1)
+  hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex)
 
-  _H1="x-dnsme-apiKey: $ME_Key"
-  _H2="x-dnsme-requestDate: $cdate"
-  _H3="x-dnsme-hmac: $hmac"
+  export _H1="x-dnsme-apiKey: $ME_Key"
+  export _H2="x-dnsme-requestDate: $cdate"
+  export _H3="x-dnsme-hmac: $hmac"
 
   if [ "$data" ]; then
     _debug data "$data"

+ 14 - 31
dnsapi/dns_myapi.sh

@@ -5,48 +5,31 @@
 #So, here must be a method   dns_myapi_add()
 #Which will be called by acme.sh to add the txt record to your api system.
 #returns 0 means success, otherwise error.
-
+#
+#Author: Neilpang
+#Report Bugs here: https://github.com/Neilpang/acme.sh
+#
 ########  Public functions #####################
 
 #Usage: dns_myapi_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_myapi_add() {
   fulldomain=$1
   txtvalue=$2
+  _info "Using myapi"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
   _err "Not implemented!"
   return 1
 }
 
-#fulldomain
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
 dns_myapi_rm() {
   fulldomain=$1
-
-}
-
-####################  Private functions bellow ##################################
-_info() {
-  if [ -z "$2" ]; then
-    echo "[$(date)] $1"
-  else
-    echo "[$(date)] $1='$2'"
-  fi
-}
-
-_err() {
-  _info "$@" >&2
-  return 1
-}
-
-_debug() {
-  if [ -z "$DEBUG" ]; then
-    return
-  fi
-  _err "$@"
-  return 0
+  txtvalue=$2
+  _info "Using myapi"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
 }
 
-_debug2() {
-  if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
-    _debug "$@"
-  fi
-  return
-}
+####################  Private functions below ##################################

+ 58 - 0
dnsapi/dns_nsupdate.sh

@@ -0,0 +1,58 @@
+#!/usr/bin/env sh
+
+########  Public functions #####################
+
+#Usage: dns_nsupdate_add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_nsupdate_add() {
+  fulldomain=$1
+  txtvalue=$2
+  _checkKeyFile || return 1
+  [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
+  # save the dns server and key to the account conf file.
+  _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
+  _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
+  _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
+  nsupdate -k "${NSUPDATE_KEY}" <<EOF
+server ${NSUPDATE_SERVER}
+update add ${fulldomain}. 60 in txt "${txtvalue}"
+send
+EOF
+  if [ $? -ne 0 ]; then
+    _err "error updating domain"
+    return 1
+  fi
+
+  return 0
+}
+
+#Usage: dns_nsupdate_rm   _acme-challenge.www.domain.com
+dns_nsupdate_rm() {
+  fulldomain=$1
+  _checkKeyFile || return 1
+  [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
+  _info "removing ${fulldomain}. txt"
+  nsupdate -k "${NSUPDATE_KEY}" <<EOF
+server ${NSUPDATE_SERVER}
+update delete ${fulldomain}. txt
+send
+EOF
+  if [ $? -ne 0 ]; then
+    _err "error updating domain"
+    return 1
+  fi
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+_checkKeyFile() {
+  if [ -z "${NSUPDATE_KEY}" ]; then
+    _err "you must specify a path to the nsupdate key file"
+    return 1
+  fi
+  if [ ! -r "${NSUPDATE_KEY}" ]; then
+    _err "key ${NSUPDATE_KEY} is unreadable"
+    return 1
+  fi
+}

+ 6 - 6
dnsapi/dns_ovh.sh

@@ -182,7 +182,7 @@ dns_ovh_rm() {
 
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 
 _ovh_authentication() {
 
@@ -273,12 +273,12 @@ _ovh_rest() {
   _ovh_hex="$(printf "%s" "$_ovh_p" | _digest sha1 hex)"
   _debug2 _ovh_hex "$_ovh_hex"
 
-  _H1="X-Ovh-Application: $OVH_AK"
-  _H2="X-Ovh-Signature: \$1\$$_ovh_hex"
+  export _H1="X-Ovh-Application: $OVH_AK"
+  export _H2="X-Ovh-Signature: \$1\$$_ovh_hex"
   _debug2 _H2 "$_H2"
-  _H3="X-Ovh-Timestamp: $_ovh_t"
-  _H4="X-Ovh-Consumer: $OVH_CK"
-  _H5="Content-Type: application/json;charset=utf-8"
+  export _H3="X-Ovh-Timestamp: $_ovh_t"
+  export _H4="X-Ovh-Consumer: $OVH_CK"
+  export _H5="Content-Type: application/json;charset=utf-8"
   if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ]; then
     _debug data "$data"
     response="$(_post "$data" "$_ovh_url" "" "$m")"

+ 49 - 5
dnsapi/dns_pdns.sh

@@ -12,6 +12,8 @@ DEFAULT_PDNS_TTL=60
 
 ########  Public functions #####################
 #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000"
+#fulldomain
+#txtvalue
 dns_pdns_add() {
   fulldomain=$1
   txtvalue=$2
@@ -50,7 +52,7 @@ dns_pdns_add() {
     _saveaccountconf PDNS_Ttl "$PDNS_Ttl"
   fi
 
-  _debug "First detect the root zone"
+  _debug "Detect root zone"
   if ! _get_root "$fulldomain"; then
     _err "invalid domain"
     return 1
@@ -68,6 +70,18 @@ dns_pdns_add() {
 dns_pdns_rm() {
   fulldomain=$1
 
+  _debug "Detect root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain "$_domain"
+
+  if ! rm_record "$_domain" "$fulldomain"; then
+    return 1
+  fi
+
+  return 0
 }
 
 set_record() {
@@ -76,18 +90,47 @@ set_record() {
   full=$2
   txtvalue=$3
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"name\": \"$full.\", \"changetype\": \"REPLACE\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then
+  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then
     _err "Set txt record error."
     return 1
   fi
+
+  if ! notify_slaves "$root"; then
+    return 1
+  fi
+
+  return 0
+}
+
+rm_record() {
+  _info "Remove record"
+  root=$1
+  full=$2
+
+  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
+    _err "Delete txt record error."
+    return 1
+  fi
+
+  if ! notify_slaves "$root"; then
+    return 1
+  fi
+
+  return 0
+}
+
+notify_slaves() {
+  root=$1
+
   if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root./notify"; then
-    _err "Notify servers error."
+    _err "Notify slaves error."
     return 1
   fi
+
   return 0
 }
 
-####################  Private functions bellow ##################################
+####################  Private functions below ##################################
 #_acme-challenge.www.domain.com
 #returns
 # _domain=domain.com
@@ -113,6 +156,7 @@ _get_root() {
     i=$(_math $i + 1)
   done
   _debug "$domain not found"
+
   return 1
 }
 
@@ -121,7 +165,7 @@ _pdns_rest() {
   ep=$2
   data=$3
 
-  _H1="X-API-Key: $PDNS_Token"
+  export _H1="X-API-Key: $PDNS_Token"
 
   if [ ! "$method" = "GET" ]; then
     _debug data "$data"

Some files were not shown because too many files changed in this diff