diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 189155e155..c9c1b555cd 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -2,15 +2,15 @@
我很忙, 每天可能只有 几秒钟 时间看你的 issue, 如果不按照我的要求写 issue, 你可能不会得到任何回复, 石沉大海.
请确保已经更新到最新的代码, 然后贴上来 `--debug 2` 的调试输出. 没有调试信息. 我做不了什么.
-如何调试 https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh
+如何调试 https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh
If it is a bug report:
-- make sure you are able to repro it on the latest released version.
+- make sure you are able to repro it on the latest released version.
You can install the latest version by: `acme.sh --upgrade`
- Search the existing issues.
- Refer to the [WIKI](https://wiki.acme.sh).
-- Debug info [Debug](https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh).
+- Debug info [Debug](https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh).
-->
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 3bd170b79f..4f7ceb47ba 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -3,7 +3,7 @@
Please send to `dev` branch instead.
Any PR to `master` branch will NOT be merged.
-2. For dns api support, read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
+2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
You will NOT get any review without passing this guide. You also need to fix the CI errors.
-->
\ No newline at end of file
diff --git a/.github/auto-comment.yml b/.github/auto-comment.yml
new file mode 100644
index 0000000000..520b3ce3a3
--- /dev/null
+++ b/.github/auto-comment.yml
@@ -0,0 +1,40 @@
+# Comment to a new issue.
+issuesOpened: >
+ If this is a bug report, please upgrade to the latest code and try again:
+
+ 如果有 bug, 请先更新到最新版试试:
+
+ ```
+ acme.sh --upgrade
+ ```
+
+ please also provide the log with `--debug 2`.
+
+ 同时请提供调试输出 `--debug 2`
+
+ see: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh
+
+ Without `--debug 2` log, your issue will NEVER get replied.
+
+ 没有调试输出, 你的 issue 不会得到任何解答.
+
+
+pullRequestOpened: >
+ First, NEVER send a PR to `master` branch, it will NEVER be accepted. Please send to the `dev` branch instead.
+
+ If this is a PR to support new DNS API or new notification API, please read this guide first:
+ https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
+
+ Please check the guide items one by one.
+
+ Then add your usage here:
+ https://github.com/acmesh-official/acme.sh/wiki/dnsapi
+
+ Or some other wiki pages:
+
+ https://github.com/acmesh-official/acme.sh/wiki/deployhooks
+
+ https://github.com/acmesh-official/acme.sh/wiki/notify
+
+
+
diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml
new file mode 100644
index 0000000000..f1c0025d96
--- /dev/null
+++ b/.github/workflows/dockerhub.yml
@@ -0,0 +1,16 @@
+
+name: Build DockerHub
+on:
+ push:
+ branches: [ master, dev ]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: trigger
+ run: curl -X POST https://hub.docker.com/api/build/v1/source/1813a660-2ee5-4583-a238-dd54e9a6ebac/trigger/c8cd9f1f-f269-45bc-9750-a08327257f62/call/
+
+
+
+
diff --git a/.travis.yml b/.travis.yml
index 04de19346d..91da27317e 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,4 @@
language: shell
-sudo: required
dist: trusty
os:
@@ -28,11 +27,11 @@ script:
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi
- cd ..
- - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
+ - git clone --depth 1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest
- if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./rundocker.sh testplat ubuntu:latest ; fi
- if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi
matrix:
fast_finish: true
-
-
+
+
diff --git a/Dockerfile b/Dockerfile
index a6e37999d9..5112bf0760 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,6 +3,7 @@ FROM alpine:3.10
RUN apk update -f \
&& apk --no-cache add -f \
openssl \
+ openssh-client \
coreutils \
bind-tools \
curl \
diff --git a/README.md b/README.md
index d5012d684d..d27a024ec6 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# An ACME Shell script: acme.sh [](https://travis-ci.org/Neilpang/acme.sh)
+# An ACME Shell script: acme.sh [](https://travis-ci.org/acmesh-official/acme.sh)
[](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
- An ACME protocol client written purely in Shell (Unix shell) language.
@@ -17,14 +17,14 @@
It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
-Wiki: https://github.com/Neilpang/acme.sh/wiki
+Wiki: https://github.com/acmesh-official/acme.sh/wiki
-For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/Neilpang/acme.sh/wiki/Run-acme.sh-in-docker)
+For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker)
Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
-# [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
+# [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E)
# Who:
- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/)
@@ -40,41 +40,42 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient)
- [CentOS Web Panel](http://centos-webpanel.com/)
- [lnmp.org](https://lnmp.org/)
-- [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials)
+- [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials)
# Tested OS
| NO | Status| Platform|
|----|-------|---------|
-|1|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu
-|2|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Debian
-|3|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|CentOS
-|4|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included)
-|5|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|FreeBSD
-|6|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|pfsense
-|7|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|openSUSE
-|8|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Alpine Linux (with curl)
-|9|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Archlinux
-|10|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|fedora
-|11|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Kali Linux
-|12|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Oracle Linux
-|13|[](https://github.com/Neilpang/letest#here-are-the-latest-status)| Proxmox https://pve.proxmox.com/wiki/HTTPSCertificateConfiguration#Let.27s_Encrypt_using_acme.sh
-|14|-----| Cloud Linux https://github.com/Neilpang/le/issues/111
-|15|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|OpenBSD
-|16|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Mageia
-|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT)
-|18|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris
-|19|[](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux
-|20|[](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX
-
-For all build statuses, check our [weekly build project](https://github.com/Neilpang/acmetest):
-
-https://github.com/Neilpang/acmetest
+|1|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Ubuntu
+|2|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Debian
+|3|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|CentOS
+|4|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included)
+|5|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|FreeBSD
+|6|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|pfsense
+|7|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|openSUSE
+|8|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Alpine Linux (with curl)
+|9|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Archlinux
+|10|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|fedora
+|11|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Kali Linux
+|12|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Oracle Linux
+|13|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox https://pve.proxmox.com/wiki/HTTPSCertificateConfiguration#Let.27s_Encrypt_using_acme.sh
+|14|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111
+|15|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|OpenBSD
+|16|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Mageia
+|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
+|18|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|SunOS/Solaris
+|19|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|Gentoo Linux
+|20|[](https://travis-ci.org/acmesh-official/acme.sh)|Mac OSX
+|21|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)|ClearLinux
+
+For all build statuses, check our [weekly build project](https://github.com/acmesh-official/acmetest):
+
+https://github.com/acmesh-official/acmetest
# Supported CA
- Letsencrypt.org CA(default)
-- [BuyPass.com CA](https://github.com/Neilpang/acme.sh/wiki/BuyPass.com-CA)
+- [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA)
- [Pebble strict Mode](https://github.com/letsencrypt/pebble)
# Supported modes
@@ -85,15 +86,15 @@ https://github.com/Neilpang/acmetest
- Apache mode
- Nginx mode
- DNS mode
-- [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode)
-- [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode)
+- [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode)
+- [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode)
# 1. How to install
### 1. Install online
-Check this project: https://github.com/Neilpang/get.acme.sh
+Check this project: https://github.com/acmesh-official/get.acme.sh
```bash
curl https://get.acme.sh | sh
@@ -111,14 +112,14 @@ wget -O - https://get.acme.sh | sh
Clone this project and launch installation:
```bash
-git clone https://github.com/Neilpang/acme.sh.git
+git clone https://github.com/acmesh-official/acme.sh.git
cd ./acme.sh
./acme.sh --install
```
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/acmesh-official/acme.sh/wiki/How-to-install
The installer will perform 3 actions:
@@ -180,7 +181,7 @@ The certs will be placed in `~/.acme.sh/example.com/`
The certs will be renewed automatically every **60** days.
-More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
+More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 3. Install the cert to Apache/Nginx etc.
@@ -226,7 +227,7 @@ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to
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
+More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 5. Use Standalone ssl server to issue cert
@@ -238,14 +239,14 @@ Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted t
acme.sh --issue --alpn -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
+More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 6. Use Apache mode
**(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, it is recommended to use the `Webroot mode`.
Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder.
@@ -257,15 +258,15 @@ acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com
**This apache mode is only to issue the cert, it will not change your apache config files.
You will need to configure your website config files to use the cert by yourself.
-We don't want to mess your apache server, don't worry.**
+We don't want to mess with your apache server, don't worry.**
-More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
+More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 7. Use Nginx mode
**(requires you to be root/sudoer, since it is required to interact with Nginx 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, it is recommended to use the `Webroot mode`.
Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder.
@@ -281,9 +282,9 @@ acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com
**This nginx mode is only to issue the cert, it will not change your nginx config files.
You will need to configure your website config files to use the cert by yourself.
-We don't want to mess your nginx server, don't worry.**
+We don't want to mess with your nginx server, don't worry.**
-More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert
+More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
# 8. Automatic DNS API integration
@@ -293,11 +294,11 @@ You don't have to do anything manually!
### Currently acme.sh supports most of the dns providers:
-https://github.com/Neilpang/acme.sh/wiki/dnsapi
+https://github.com/acmesh-official/acme.sh/wiki/dnsapi
# 9. Use DNS manual mode:
-See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first.
+See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first.
If your dns provider doesn't support any api access, you can add the txt record by your hand.
@@ -430,12 +431,12 @@ acme.sh --upgrade --auto-upgrade 0
# 15. Issue a cert from an existing CSR
-https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR
+https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR
# 16. Send notifications in cronjob
-https://github.com/Neilpang/acme.sh/wiki/notify
+https://github.com/acmesh-official/acme.sh/wiki/notify
# 17. Under the Hood
@@ -456,7 +457,7 @@ TODO:
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
-
+
### Financial Contributors
@@ -487,7 +488,7 @@ 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 welcome.
+[Issues](https://github.com/acmesh-official/acme.sh/issues) and [pull requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome.
# 20. Donate
@@ -495,4 +496,4 @@ Your donation makes **acme.sh** better:
1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/)
-[Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list)
+[Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list)
diff --git a/acme.sh b/acme.sh
index 041b5b4445..0e3110a656 100755
--- a/acme.sh
+++ b/acme.sh
@@ -1,12 +1,12 @@
#!/usr/bin/env sh
-VER=2.8.4
+VER=2.8.7
PROJECT_NAME="acme.sh"
PROJECT_ENTRY="acme.sh"
-PROJECT="https://github.com/Neilpang/$PROJECT_NAME"
+PROJECT="https://github.com/acmesh-official/$PROJECT_NAME"
DEFAULT_INSTALL_HOME="$HOME/.$PROJECT_NAME"
@@ -48,8 +48,6 @@ LOCAL_ANY_ADDRESS="0.0.0.0"
DEFAULT_RENEW=60
-DEFAULT_DNS_SLEEP=120
-
NO_VALUE="no"
W_DNS="dns"
@@ -126,19 +124,21 @@ NOTIFY_MODE_CERT=1
NOTIFY_MODE_DEFAULT=$NOTIFY_MODE_BULK
-_DEBUG_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-debug-acme.sh"
+_DEBUG_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh"
+
+_PREPARE_LINK="https://github.com/acmesh-official/acme.sh/wiki/Install-preparations"
-_PREPARE_LINK="https://github.com/Neilpang/acme.sh/wiki/Install-preparations"
+_STATELESS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode"
-_STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode"
+_DNS_ALIAS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode"
-_DNS_ALIAS_WIKI="https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode"
+_DNS_MANUAL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode"
-_DNS_MANUAL_WIKI="https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode"
+_NOTIFY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/notify"
-_NOTIFY_WIKI="https://github.com/Neilpang/acme.sh/wiki/notify"
+_SUDO_WIKI="https://github.com/acmesh-official/acme.sh/wiki/sudo"
-_SUDO_WIKI="https://github.com/Neilpang/acme.sh/wiki/sudo"
+_REVOKE_WIKI="https://github.com/acmesh-official/acme.sh/wiki/revokecert"
_DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
@@ -153,7 +153,7 @@ fi
__green() {
if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
- printf '\033[1;31;32m%b\033[0m' "$1"
+ printf '\33[1;32m%b\33[0m' "$1"
return
fi
printf -- "%b" "$1"
@@ -161,7 +161,7 @@ __green() {
__red() {
if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then
- printf '\033[1;31;40m%b\033[0m' "$1"
+ printf '\33[1;31m%b\33[0m' "$1"
return
fi
printf -- "%b" "$1"
@@ -178,7 +178,7 @@ _printargs() {
printf -- "%s" "$1='$2'"
fi
printf "\n"
- # return the saved exit status
+ # return the saved exit status
return "$_exitstatus"
}
@@ -207,7 +207,7 @@ _dlg_versions() {
echo "socat:"
if _exists "socat"; then
- socat -h 2>&1
+ socat -V 2>&1
else
_debug "socat doesn't exists."
fi
@@ -265,6 +265,37 @@ _usage() {
printf "\n" >&2
}
+__debug_bash_helper() {
+ # At this point only do for --debug 3
+ if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -lt "$DEBUG_LEVEL_3" ]; then
+ return
+ fi
+ # Return extra debug info when running with bash, otherwise return empty
+ # string.
+ if [ -z "${BASH_VERSION}" ]; then
+ return
+ fi
+ # We are a bash shell at this point, return the filename, function name, and
+ # line number as a string
+ _dbh_saveIFS=$IFS
+ IFS=" "
+ # Must use eval or syntax error happens under dash. The eval should use
+ # single quotes as older versions of busybox had a bug with double quotes and
+ # eval.
+ # Use 'caller 1' as we want one level up the stack as we should be called
+ # by one of the _debug* functions
+ eval '_dbh_called=($(caller 1))'
+ IFS=$_dbh_saveIFS
+ eval '_dbh_file=${_dbh_called[2]}'
+ if [ -n "${_script_home}" ]; then
+ # Trim off the _script_home directory name
+ eval '_dbh_file=${_dbh_file#$_script_home/}'
+ fi
+ eval '_dbh_function=${_dbh_called[1]}'
+ eval '_dbh_lineno=${_dbh_called[0]}'
+ printf "%-40s " "$_dbh_file:${_dbh_function}:${_dbh_lineno}"
+}
+
_debug() {
if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then
_log "$@"
@@ -273,7 +304,8 @@ _debug() {
_syslog "$SYSLOG_DEBUG" "$@"
fi
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then
- _printargs "$@" >&2
+ _bash_debug=$(__debug_bash_helper)
+ _printargs "${_bash_debug}$@" >&2
fi
}
@@ -306,7 +338,8 @@ _debug2() {
_syslog "$SYSLOG_DEBUG" "$@"
fi
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
- _printargs "$@" >&2
+ _bash_debug=$(__debug_bash_helper)
+ _printargs "${_bash_debug}$@" >&2
fi
}
@@ -338,7 +371,8 @@ _debug3() {
_syslog "$SYSLOG_DEBUG" "$@"
fi
if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then
- _printargs "$@" >&2
+ _bash_debug=$(__debug_bash_helper)
+ _printargs "${_bash_debug}$@" >&2
fi
}
@@ -814,6 +848,14 @@ _json_encode() {
echo "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n"
}
+#from: http:\/\/ to http://
+_json_decode() {
+ _j_str="$(sed 's#\\/#/#g')"
+ _debug3 "_json_decode"
+ _debug3 "_j_str" "$_j_str"
+ echo "$_j_str"
+}
+
#options file
_sed_i() {
options="$1"
@@ -961,7 +1003,7 @@ _sign() {
_sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile "
- if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
+ if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || grep "BEGIN PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
$_sign_openssl -$alg | _base64
elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then
if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then
@@ -1132,9 +1174,8 @@ _createcsr() {
_info "Multi domain" "$alt"
printf -- "\nsubjectAltName=$alt" >>"$csrconf"
fi
- if [ "$Le_OCSP_Staple" ] || [ "$Le_OCSP_Stable" ]; then
+ if [ "$Le_OCSP_Staple" = "1" ]; then
_savedomainconf Le_OCSP_Staple "$Le_OCSP_Staple"
- _cleardomainconf Le_OCSP_Stable
printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf"
fi
@@ -1945,7 +1986,9 @@ _send_signed_request() {
continue
fi
if [ "$ACME_VERSION" = "2" ]; then
- if [ "$url" = "$ACME_NEW_ACCOUNT" ] || [ "$url" = "$ACME_REVOKE_CERT" ]; then
+ if [ "$url" = "$ACME_NEW_ACCOUNT" ]; then
+ protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
+ elif [ "$url" = "$ACME_REVOKE_CERT" ] && [ "$keyfile" != "$ACCOUNT_KEY_PATH" ]; then
protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
else
protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}'
@@ -1985,7 +2028,7 @@ _send_signed_request() {
_debug code "$code"
_debug2 original "$response"
- if echo "$responseHeaders" | grep -i "Content-Type: application/json" >/dev/null 2>&1; then
+ if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then
response="$(echo "$response" | _normalizeJson)"
fi
_debug2 response "$response"
@@ -2006,8 +2049,10 @@ _send_signed_request() {
continue
fi
fi
- break
+ return 0
done
+ _info "Giving up sending to CA server after $MAX_REQUEST_RETRY_TIMES retries."
+ return 1
}
@@ -2130,7 +2175,7 @@ _getdeployconf() {
return 0 # do nothing
fi
_saved=$(_readdomainconf "SAVED_$_rac_key")
- eval "export $_rac_key=$_saved"
+ eval "export $_rac_key=\"$_saved\""
}
#_saveaccountconf key value base64encode
@@ -2379,7 +2424,7 @@ __initHome() {
if [ -z "$ACCOUNT_CONF_PATH" ]; then
ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH"
fi
-
+ _debug3 ACCOUNT_CONF_PATH "$ACCOUNT_CONF_PATH"
DEFAULT_LOG_FILE="$LE_CONFIG_HOME/$PROJECT_NAME.log"
DEFAULT_CA_HOME="$LE_CONFIG_HOME/ca"
@@ -3331,7 +3376,7 @@ _on_issue_success() {
fi
fi
- if _hasfield "$Le_Webroot" "$W_DNS"; then
+ if _hasfield "$Le_Webroot" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
_err "$_DNS_MANUAL_WARN"
fi
@@ -3383,13 +3428,13 @@ _regAccount() {
if [ "$ACME_VERSION" = "2" ]; then
regjson='{"termsOfServiceAgreed": true}'
if [ "$ACCOUNT_EMAIL" ]; then
- regjson='{"contact": ["mailto: '$ACCOUNT_EMAIL'"], "termsOfServiceAgreed": true}'
+ regjson='{"contact": ["mailto:'$ACCOUNT_EMAIL'"], "termsOfServiceAgreed": true}'
fi
else
_reg_res="$ACME_NEW_ACCOUNT_RES"
regjson='{"resource": "'$_reg_res'", "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
if [ "$ACCOUNT_EMAIL" ]; then
- regjson='{"resource": "'$_reg_res'", "contact": ["mailto: '$ACCOUNT_EMAIL'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
+ regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$ACCOUNT_EMAIL'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
fi
fi
@@ -3411,7 +3456,7 @@ _regAccount() {
fi
_debug2 responseHeaders "$responseHeaders"
- _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+ _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")"
_debug "_accUri" "$_accUri"
if [ -z "$_accUri" ]; then
_err "Can not find account id url."
@@ -3469,7 +3514,9 @@ updateaccount() {
if [ "$ACME_VERSION" = "2" ]; then
if [ "$ACCOUNT_EMAIL" ]; then
- updjson='{"contact": ["mailto: '$ACCOUNT_EMAIL'"]}'
+ updjson='{"contact": ["mailto:'$ACCOUNT_EMAIL'"]}'
+ else
+ updjson='{"contact": []}'
fi
else
# ACMEv1: Updates happen the same way a registration is done.
@@ -3482,6 +3529,7 @@ updateaccount() {
_send_signed_request "$_accUri" "$updjson"
if [ "$code" = '200' ]; then
+ echo "$response" >"$ACCOUNT_JSON_PATH"
_info "account update success for $_accUri."
else
_info "Error. The account was not updated."
@@ -3678,7 +3726,7 @@ _ns_purge_cf() {
#checks if cf server is available
_ns_is_available_cf() {
- if _get "https://cloudflare-dns.com"; then
+ if _get "https://cloudflare-dns.com" >/dev/null 2>&1; then
return 0
else
return 1
@@ -3785,9 +3833,11 @@ _check_dns_entries() {
_sleep 10
else
_info "All success, let's return"
- break
+ return 0
fi
done
+ _info "Timed out waiting for DNS."
+ return 1
}
@@ -3968,7 +4018,7 @@ issue() {
_on_issue_err "$_post_hook"
return 1
fi
- Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+ Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)"
_debug Le_LinkOrder "$Le_LinkOrder"
Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
_debug Le_OrderFinalize "$Le_OrderFinalize"
@@ -3982,7 +4032,7 @@ issue() {
#for dns manual mode
_savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
- _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
+ _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
_debug2 _authorizations_seg "$_authorizations_seg"
if [ -z "$_authorizations_seg" ]; then
_err "_authorizations_seg not found."
@@ -4047,7 +4097,18 @@ $_authorizations_map"
fi
if [ "$ACME_VERSION" = "2" ]; then
- response="$(echo "$_authorizations_map" | grep "^$(_idn "$d")," | sed "s/$d,//")"
+ _idn_d="$(_idn "$d")"
+ _candindates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")"
+ _debug2 _candindates "$_candindates"
+ if [ "$(echo "$_candindates" | wc -l)" -gt 1 ]; then
+ for _can in $_candindates; do
+ if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then
+ _candindates="$_can"
+ break
+ fi
+ done
+ fi
+ response="$(echo "$_candindates" | sed "s/$_idn_d,//")"
_debug2 "response" "$response"
if [ -z "$response" ]; then
_err "get to authz error."
@@ -4070,45 +4131,59 @@ $_authorizations_map"
entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
_debug entry "$entry"
+ keyauthorization=""
if [ -z "$entry" ]; then
- _err "Error, can not get domain token entry $d"
- _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
- if [ "$_supported_vtypes" ]; then
- _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype"
+ if ! _startswith "$d" '*.'; then
+ _debug "Not a wildcard domain, lets check whether the validation is already valid."
+ if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
+ _debug "$d is already valid."
+ keyauthorization="$STATE_VERIFIED"
+ _debug keyauthorization "$keyauthorization"
+ fi
+ fi
+ if [ -z "$keyauthorization" ]; then
+ _err "Error, can not get domain token entry $d for $vtype"
+ _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')"
+ if [ "$_supported_vtypes" ]; then
+ _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype"
+ fi
+ _clearup
+ _on_issue_err "$_post_hook"
+ return 1
fi
- _clearup
- _on_issue_err "$_post_hook"
- return 1
fi
- token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
- _debug token "$token"
- if [ -z "$token" ]; then
- _err "Error, can not get domain token $entry"
- _clearup
- _on_issue_err "$_post_hook"
- return 1
- fi
- if [ "$ACME_VERSION" = "2" ]; then
- uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
- else
- uri="$(echo "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)"
- fi
- _debug uri "$uri"
+ if [ -z "$keyauthorization" ]; then
+ token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')"
+ _debug token "$token"
- if [ -z "$uri" ]; then
- _err "Error, can not get domain uri. $entry"
- _clearup
- _on_issue_err "$_post_hook"
- return 1
- fi
- keyauthorization="$token.$thumbprint"
- _debug keyauthorization "$keyauthorization"
+ if [ -z "$token" ]; then
+ _err "Error, can not get domain token $entry"
+ _clearup
+ _on_issue_err "$_post_hook"
+ return 1
+ fi
+ if [ "$ACME_VERSION" = "2" ]; then
+ uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
+ else
+ uri="$(echo "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)"
+ fi
+ _debug uri "$uri"
- if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
- _debug "$d is already verified."
- keyauthorization="$STATE_VERIFIED"
+ if [ -z "$uri" ]; then
+ _err "Error, can not get domain uri. $entry"
+ _clearup
+ _on_issue_err "$_post_hook"
+ return 1
+ fi
+ keyauthorization="$token.$thumbprint"
_debug keyauthorization "$keyauthorization"
+
+ if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then
+ _debug "$d is already verified."
+ keyauthorization="$STATE_VERIFIED"
+ _debug keyauthorization "$keyauthorization"
+ fi
fi
dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot"
@@ -4224,7 +4299,7 @@ $_authorizations_map"
if [ "$dns_entries" ]; then
if [ -z "$Le_DNSSleep" ]; then
- _info "Let's check each dns records now. Sleep 20 seconds first."
+ _info "Let's check each DNS record now. Sleep 20 seconds first."
_sleep 20
if ! _check_dns_entries; then
_err "check dns error."
@@ -4472,13 +4547,13 @@ $_authorizations_map"
return 1
fi
if [ -z "$Le_LinkOrder" ]; then
- Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
+ Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d ":" -f 2-)"
fi
_savedomainconf "Le_LinkOrder" "$Le_LinkOrder"
_link_cert_retry=0
- _MAX_CERT_RETRY=5
+ _MAX_CERT_RETRY=30
while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
if _contains "$response" "\"status\":\"valid\""; then
_debug "Order status is valid."
@@ -4493,7 +4568,14 @@ $_authorizations_map"
break
elif _contains "$response" "\"processing\""; then
_info "Order status is processing, lets sleep and retry."
- _sleep 2
+ _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
+ _debug "_retryafter" "$_retryafter"
+ if [ "$_retryafter" ]; then
+ _info "Retry after: $_retryafter"
+ _sleep $_retryafter
+ else
+ _sleep 2
+ fi
else
_err "Sign error, wrong status"
_err "$response"
@@ -5384,6 +5466,7 @@ uninstallcronjob() {
}
+#domain isECC revokeReason
revoke() {
Le_Domain="$1"
if [ -z "$Le_Domain" ]; then
@@ -5392,7 +5475,10 @@ revoke() {
fi
_isEcc="$2"
-
+ _reason="$3"
+ if [ -z "$_reason" ]; then
+ _reason="0"
+ fi
_initpath "$Le_Domain" "$_isEcc"
if [ ! -f "$DOMAIN_CONF" ]; then
_err "$Le_Domain is not a issued domain, skip."
@@ -5414,7 +5500,7 @@ revoke() {
_initAPI
if [ "$ACME_VERSION" = "2" ]; then
- data="{\"certificate\": \"$cert\"}"
+ data="{\"certificate\": \"$cert\",\"reason\":$_reason}"
else
data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
fi
@@ -5523,7 +5609,7 @@ _deactivate() {
return 1
fi
- authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")"
+ authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
_debug "authzUri" "$authzUri"
if [ "$code" ] && [ ! "$code" = '201' ]; then
_err "new-authz error: $response"
@@ -6059,7 +6145,7 @@ _send_notify() {
_set_notify_hook() {
_nhooks="$1"
- _test_subject="Hello, this is notification from $PROJECT_NAME"
+ _test_subject="Hello, this is a notification from $PROJECT_NAME"
_test_content="If you receive this message, your notification works."
_send_notify "$_test_subject" "$_test_content" "$_nhooks" 0
@@ -6153,17 +6239,17 @@ Parameters:
--force, -f Used to force to install or force to renew a cert immediately.
--staging, --test Use staging server, just for test.
--debug Output debug info.
- --output-insecure Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for secure.
+ --output-insecure Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for security.
--webroot, -w /path/to/webroot Specifies the web root folder for web root mode.
--standalone Use standalone mode.
--alpn Use standalone alpn mode.
--stateless Use stateless mode, see: $_STATELESS_WIKI
--apache Use apache mode.
--dns [dns_cf|dns_dp|dns_cx|/path/to/api/file] Use dns mode or dns api.
- --dnssleep [$DEFAULT_DNS_SLEEP] The time in seconds to wait for all the txt records to take effect in dns api mode. Default $DEFAULT_DNS_SLEEP seconds.
+ --dnssleep 300 The time in seconds to wait for all the txt records to take effect in dns api mode. It's not necessary to use this by default, $PROJECT_NAME polls dns status automatically.
- --keylength, -k [2048] Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384.
- --accountkeylength, -ak [2048] Specifies the account key length.
+ --keylength, -k [2048] Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521.
+ --accountkeylength, -ak [2048] Specifies the account key length: 2048, 3072, 4096
--log [/path/to/logfile] Specifies the log file. The default is: \"$DEFAULT_LOG_FILE\" if you don't give a file path here.
--log-level 1|2 Specifies the log level, default is 1.
--syslog [0|3|6|7] Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug.
@@ -6177,7 +6263,7 @@ Parameters:
--reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
- --server SERVER ACME Directory Resource URI. (default: https://acme-v01.api.letsencrypt.org/directory)
+ --server SERVER ACME Directory Resource URI. (default: $DEFAULT_CA)
--accountconf Specifies a customized account config file.
--home Specifies the home dir for $PROJECT_NAME.
--cert-home Specifies the home dir to save all the certs, only valid for '--install' command.
@@ -6215,7 +6301,7 @@ Parameters:
--branch, -b Only valid for '--upgrade' command, specifies the branch name to upgrade to.
--notify-level 0|1|2|3 Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT.
- 0: disabled, no notification will be sent.
+ 0: disabled, no notification will be sent.
1: send notifications only when there is an error.
2: send notifications when a cert is successfully renewed, or there is an error.
3: send notifications when a cert is skipped, renewed, or error.
@@ -6223,6 +6309,7 @@ Parameters:
0: Bulk mode. Send all the domain's notifications in one message(mail).
1: Cert mode. Send a message for every single cert.
--notify-hook [hookname] Set the notify hook
+ --revoke-reason [0-10] The reason for '--revoke' command. See: $_REVOKE_WIKI
"
}
@@ -6254,6 +6341,8 @@ _installOnline() {
chmod +x $PROJECT_ENTRY
if ./$PROJECT_ENTRY install "$_nocron" "" "$_noprofile"; then
_info "Install success!"
+ _initpath
+ _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)"
fi
cd ..
@@ -6263,9 +6352,27 @@ _installOnline() {
)
}
+_getRepoHash() {
+ _hash_path=$1
+ shift
+ _hash_url="https://api.github.com/repos/acmesh-official/$PROJECT_NAME/git/refs/$_hash_path"
+ _get $_hash_url | tr -d "\r\n" | tr '{},' '\n' | grep '"sha":' | cut -d '"' -f 4
+}
+
+_getUpgradeHash() {
+ _b="$BRANCH"
+ if [ -z "$_b" ]; then
+ _b="master"
+ fi
+ _hash=$(_getRepoHash "heads/$_b")
+ if [ -z "$_hash" ]; then _hash=$(_getRepoHash "tags/$_b"); fi
+ echo $_hash
+}
+
upgrade() {
if (
_initpath
+ [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already uptodate!" && exit 0
export LE_WORKING_DIR
cd "$LE_WORKING_DIR"
_installOnline "nocron" "noprofile"
@@ -6378,6 +6485,7 @@ _process() {
_notify_hook=""
_notify_level=""
_notify_mode=""
+ _revoke_reason=""
while [ ${#} -gt 0 ]; do
case "${1}" in
@@ -6850,6 +6958,14 @@ _process() {
_notify_mode="$_nmode"
shift
;;
+ --revoke-reason)
+ _revoke_reason="$2"
+ if _startswith "$_revoke_reason" "-"; then
+ _err "'$_revoke_reason' is not a integer for '$1'"
+ return 1
+ fi
+ shift
+ ;;
*)
_err "Unknown parameter : $1"
return 1
@@ -6937,7 +7053,7 @@ _process() {
renewAll "$_stopRenewOnError"
;;
revoke)
- revoke "$_domain" "$_ecc"
+ revoke "$_domain" "$_ecc" "$_revoke_reason"
;;
remove)
remove "$_domain" "$_ecc"
diff --git a/deploy/README.md b/deploy/README.md
index fc633ad7a5..e3f239fade 100644
--- a/deploy/README.md
+++ b/deploy/README.md
@@ -2,5 +2,5 @@
deploy hook usage:
-https://github.com/Neilpang/acme.sh/wiki/deployhooks
+https://github.com/acmesh-official/acme.sh/wiki/deployhooks
diff --git a/deploy/docker.sh b/deploy/docker.sh
index 05333b3fe5..06d79855ca 100755
--- a/deploy/docker.sh
+++ b/deploy/docker.sh
@@ -8,7 +8,7 @@
#DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem"
#DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload"
-_DEPLOY_DOCKER_WIKI="https://github.com/Neilpang/acme.sh/wiki/deploy-to-docker-containers"
+_DEPLOY_DOCKER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers"
_DOCKER_HOST_DEFAULT="/var/run/docker.sock"
diff --git a/deploy/gcore_cdn.sh b/deploy/gcore_cdn.sh
index bbda58eff5..a2a35f7b69 100644
--- a/deploy/gcore_cdn.sh
+++ b/deploy/gcore_cdn.sh
@@ -77,15 +77,15 @@ gcore_cdn_deploy() {
_debug _regex "$_regex"
_resource=$(echo "$_response" | sed 's/},{/},\n{/g' | _egrep_o "$_regex")
_debug _resource "$_resource"
- _regex=".*\"id\":\([0-9]*\),.*$"
+ _regex=".*\"id\":\([0-9]*\).*\"rules\".*$"
_debug _regex "$_regex"
_resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _resourceId "$_resourceId"
- _regex=".*\"sslData\":\([0-9]*\)}.*$"
+ _regex=".*\"sslData\":\([0-9]*\).*$"
_debug _regex "$_regex"
_sslDataOld=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _sslDataOld "$_sslDataOld"
- _regex=".*\"originGroup\":\([0-9]*\),.*$"
+ _regex=".*\"originGroup\":\([0-9]*\).*$"
_debug _regex "$_regex"
_originGroup=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
_debug _originGroup "$_originGroup"
@@ -101,7 +101,7 @@ gcore_cdn_deploy() {
_debug _request "$_request"
_response=$(_post "$_request" "https://api.gcdn.co/sslData")
_debug _response "$_response"
- _regex=".*\"id\":\([0-9]*\),.*$"
+ _regex=".*\"id\":\([0-9]*\).*$"
_debug _regex "$_regex"
_sslDataAdd=$(echo "$_response" | sed -n "s/$_regex/\1/p")
_debug _sslDataAdd "$_sslDataAdd"
diff --git a/deploy/haproxy.sh b/deploy/haproxy.sh
index 836c518263..0a45ee0701 100644
--- a/deploy/haproxy.sh
+++ b/deploy/haproxy.sh
@@ -208,33 +208,36 @@ haproxy_deploy() {
_issuerdn=$(openssl x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
_debug _issuerdn "${_issuerdn}"
_info "Requesting OCSP response"
- # Request the OCSP response from the issuer and store it
+ # If the issuer is a CA cert then our command line has "-CAfile" added
if [ "${_subjectdn}" = "${_issuerdn}" ]; then
- # If the issuer is a CA cert then our command line has "-CAfile" added
- openssl ocsp \
- -issuer "${_issuer}" \
- -cert "${_pem}" \
- -url "${_ocsp_url}" \
- -header Host "${_ocsp_host}" \
- -respout "${_ocsp}" \
- -verify_other "${_issuer}" \
- -no_nonce \
- -CAfile "${_issuer}" \
- | grep -q "${_pem}: good"
- _ret=$?
+ _cafile_argument="-CAfile \"${_issuer}\""
+ else
+ _cafile_argument=""
+ fi
+ _debug _cafile_argument "${_cafile_argument}"
+ # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
+ _openssl_version=$(openssl version | cut -d' ' -f2)
+ _debug _openssl_version "${_openssl_version}"
+ _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
+ _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
+ if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
+ _header_sep="="
else
- # Issuer is not a root CA so no "-CAfile" option
- openssl ocsp \
- -issuer "${_issuer}" \
- -cert "${_pem}" \
- -url "${_ocsp_url}" \
- -header Host "${_ocsp_host}" \
- -respout "${_ocsp}" \
- -verify_other "${_issuer}" \
- -no_nonce \
- | grep -q "${_pem}: good"
- _ret=$?
+ _header_sep=" "
fi
+ # Request the OCSP response from the issuer and store it
+ _openssl_ocsp_cmd="openssl ocsp \
+ -issuer \"${_issuer}\" \
+ -cert \"${_pem}\" \
+ -url \"${_ocsp_url}\" \
+ -header Host${_header_sep}\"${_ocsp_host}\" \
+ -respout \"${_ocsp}\" \
+ -verify_other \"${_issuer}\" \
+ ${_cafile_argument} \
+ | grep -q \"${_pem}: good\""
+ _debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}"
+ eval "${_openssl_ocsp_cmd}"
+ _ret=$?
else
# Non fatal: No issuer file was present so no OCSP stapling file created
_err "OCSP stapling in use but no .issuer file was present"
diff --git a/deploy/panos.sh b/deploy/panos.sh
new file mode 100644
index 0000000000..ef622ded0d
--- /dev/null
+++ b/deploy/panos.sh
@@ -0,0 +1,139 @@
+#!/usr/bin/env sh
+
+# Script to deploy certificates to Palo Alto Networks PANOS via API
+# Note PANOS API KEY and IP address needs to be set prior to running.
+# The following variables exported from environment will be used.
+# If not set then values previously saved in domain.conf file are used.
+#
+# Firewall admin with superuser and IP address is required.
+#
+# export PANOS_USER="" # required
+# export PANOS_PASS="" # required
+# export PANOS_HOST="" # required
+
+# This function is to parse the XML
+parse_response() {
+ type=$2
+ if [ "$type" = 'keygen' ]; then
+ status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g')
+ if [ "$status" = "success" ]; then
+ panos_key=$(echo "$1" | sed 's/^.*\(\)\(.*\)<\/key>.*/\2/g')
+ _panos_key=$panos_key
+ else
+ message="PAN-OS Key could not be set."
+ fi
+ else
+ status=$(echo "$1" | sed 's/^.*"\([a-z]*\)".*/\1/g')
+ message=$(echo "$1" | sed 's/^.*\(.*\)<\/result.*/\1/g')
+ fi
+ return 0
+}
+
+deployer() {
+ content=""
+ type=$1 # Types are keygen, cert, key, commit
+ _debug "**** Deploying $type *****"
+ panos_url="https://$_panos_host/api/"
+ if [ "$type" = 'keygen' ]; then
+ _H1="Content-Type: application/x-www-form-urlencoded"
+ content="type=keygen&user=$_panos_user&password=$_panos_pass"
+ # content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}"
+ fi
+
+ if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then
+ #Generate DEIM
+ delim="-----MultipartDelimiter$(date "+%s%N")"
+ nl="\015\012"
+ #Set Header
+ export _H1="Content-Type: multipart/form-data; boundary=$delim"
+ if [ "$type" = 'cert' ]; then
+ panos_url="${panos_url}?type=import"
+ content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")"
+ fi
+ if [ "$type" = 'key' ]; then
+ panos_url="${panos_url}?type=import"
+ content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
+ fi
+ #Close multipart
+ content="$content${nl}--$delim--${nl}${nl}"
+ #Convert CRLF
+ content=$(printf %b "$content")
+ fi
+
+ if [ "$type" = 'commit' ]; then
+ export _H1="Content-Type: application/x-www-form-urlencoded"
+ cmd=$(printf "%s" "<$_panos_user>$_panos_user>" | _url_encode)
+ content="type=commit&key=$_panos_key&cmd=$cmd"
+ fi
+ response=$(_post "$content" "$panos_url" "" "POST")
+ parse_response "$response" "$type"
+ # Saving response to variables
+ response_status=$status
+ #DEBUG
+ _debug response_status "$response_status"
+ if [ "$response_status" = "success" ]; then
+ _debug "Successfully deployed $type"
+ return 0
+ else
+ _err "Deploy of type $type failed. Try deploying with --debug to troubleshoot."
+ _debug "$message"
+ return 1
+ fi
+}
+
+# This is the main function that will call the other functions to deploy everything.
+panos_deploy() {
+ _cdomain="$1"
+ _ckey="$2"
+ _cfullchain="$5"
+ # PANOS ENV VAR check
+ if [ -z "$PANOS_USER" ] || [ -z "$PANOS_PASS" ] || [ -z "$PANOS_HOST" ]; then
+ _debug "No ENV variables found lets check for saved variables"
+ _getdeployconf PANOS_USER
+ _getdeployconf PANOS_PASS
+ _getdeployconf PANOS_HOST
+ _panos_user=$PANOS_USER
+ _panos_pass=$PANOS_PASS
+ _panos_host=$PANOS_HOST
+ if [ -z "$_panos_user" ] && [ -z "$_panos_pass" ] && [ -z "$_panos_host" ]; then
+ _err "No host, user and pass found.. If this is the first time deploying please set PANOS_HOST, PANOS_USER and PANOS_PASS in environment variables. Delete them after you have succesfully deployed certs."
+ return 1
+ else
+ _debug "Using saved env variables."
+ fi
+ else
+ _debug "Detected ENV variables to be saved to the deploy conf."
+ # Encrypt and save user
+ _savedeployconf PANOS_USER "$PANOS_USER" 1
+ _savedeployconf PANOS_PASS "$PANOS_PASS" 1
+ _savedeployconf PANOS_HOST "$PANOS_HOST" 1
+ _panos_user="$PANOS_USER"
+ _panos_pass="$PANOS_PASS"
+ _panos_host="$PANOS_HOST"
+ fi
+ _debug "Let's use username and pass to generate token."
+ if [ -z "$_panos_user" ] || [ -z "$_panos_pass" ] || [ -z "$_panos_host" ]; then
+ _err "Please pass username and password and host as env variables PANOS_USER, PANOS_PASS and PANOS_HOST"
+ return 1
+ else
+ _debug "Getting PANOS KEY"
+ deployer keygen
+ if [ -z "$_panos_key" ]; then
+ _err "Missing apikey."
+ return 1
+ else
+ deployer cert
+ deployer key
+ deployer commit
+ fi
+ fi
+}
diff --git a/deploy/qiniu.sh b/deploy/qiniu.sh
index e46e6fb33a..70669917c1 100644
--- a/deploy/qiniu.sh
+++ b/deploy/qiniu.sh
@@ -1,11 +1,13 @@
#!/usr/bin/env sh
-# Script to create certificate to qiniu.com
+# Script to create certificate to qiniu.com
#
# This deployment required following variables
# export QINIU_AK="QINIUACCESSKEY"
# export QINIU_SK="QINIUSECRETKEY"
# export QINIU_CDN_DOMAIN="cdn.example.com"
+# If you have more than one domain, just
+# export QINIU_CDN_DOMAIN="cdn1.example.com cdn2.example.com"
QINIU_API_BASE="https://api.qiniu.com"
@@ -67,21 +69,23 @@ qiniu_deploy() {
_debug certId "$_certId"
## update domain ssl config
- update_path="/domain/$QINIU_CDN_DOMAIN/httpsconf"
update_body="{\"certid\":$_certId,\"forceHttps\":false}"
- update_access_token="$(_make_access_token "$update_path")"
- _debug update_access_token "$update_access_token"
- export _H1="Authorization: QBox $update_access_token"
- update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64 "multiline")
-
- if _contains "$update_response" "error"; then
- _err "Error in updating domain httpsconf:"
- _err "$update_response"
- return 1
- fi
-
- _debug update_response "$update_response"
- _info "Certificate successfully deployed"
+ for domain in $QINIU_CDN_DOMAIN; do
+ update_path="/domain/$domain/httpsconf"
+ update_access_token="$(_make_access_token "$update_path")"
+ _debug update_access_token "$update_access_token"
+ export _H1="Authorization: QBox $update_access_token"
+ update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64 "multiline")
+
+ if _contains "$update_response" "error"; then
+ _err "Error in updating domain $domain httpsconf:"
+ _err "$update_response"
+ return 1
+ fi
+
+ _debug update_response "$update_response"
+ _info "Domain $domain certificate has been deployed successfully"
+ done
return 0
}
diff --git a/deploy/routeros.sh b/deploy/routeros.sh
index 21c9196fa3..2f34999985 100644
--- a/deploy/routeros.sh
+++ b/deploy/routeros.sh
@@ -85,19 +85,19 @@ routeros_deploy() {
scp "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"
_info "Trying to push cert '$_cfullchain' to router"
scp "$_cfullchain" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer"
- DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=admin policy=ftp,read,write,password,sensitive
-source=\"## generated by routeros deploy script in acme.sh
-\n/certificate remove [ find name=$_cdomain.cer_0 ]
-\n/certificate remove [ find name=$_cdomain.cer_1 ]
-\ndelay 1
-\n/certificate import file-name=$_cdomain.cer passphrase=\\\"\\\"
-\n/certificate import file-name=$_cdomain.key passphrase=\\\"\\\"
-\ndelay 1
-\n/file remove $_cdomain.cer
-\n/file remove $_cdomain.key
-\ndelay 2
-\n/ip service set www-ssl certificate=$_cdomain.cer_0
-\n$ROUTER_OS_ADDITIONAL_SERVICES
+ DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=admin policy=ftp,read,write,password,sensitive \
+source=\"## generated by routeros deploy script in acme.sh;\
+\n/certificate remove [ find name=$_cdomain.cer_0 ];\
+\n/certificate remove [ find name=$_cdomain.cer_1 ];\
+\ndelay 1;\
+\n/certificate import file-name=$_cdomain.cer passphrase=\\\"\\\";\
+\n/certificate import file-name=$_cdomain.key passphrase=\\\"\\\";\
+\ndelay 1;\
+\n/file remove $_cdomain.cer;\
+\n/file remove $_cdomain.key;\
+\ndelay 2;\
+\n/ip service set www-ssl certificate=$_cdomain.cer_0;\
+\n$ROUTER_OS_ADDITIONAL_SERVICES;\
\n\"
"
# shellcheck disable=SC2029
diff --git a/deploy/ssh.sh b/deploy/ssh.sh
index 9cb0af9edb..06d4b2b4fe 100644
--- a/deploy/ssh.sh
+++ b/deploy/ssh.sh
@@ -12,7 +12,7 @@
# Only a username is required. All others are optional.
#
# The following examples are for QNAP NAS running QTS 4.2
-# export DEPLOY_SSH_CMD="" # defaults to ssh
+# export DEPLOY_SSH_CMD="" # defaults to "ssh -T"
# export DEPLOY_SSH_USER="admin" # required
# export DEPLOY_SSH_SERVER="qnap" # defaults to domain name
# export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem"
@@ -20,7 +20,9 @@
# export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem"
# export DEPLOY_SSH_FULLCHAIN=""
# export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart"
-# export DEPLOY_SSH_BACKUP="" # yes or no, default to yes
+# export DEPLOY_SSH_BACKUP="" # yes or no, default to yes or previously saved value
+# export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy
+# export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value
#
######## Public functions #####################
@@ -31,10 +33,7 @@ ssh_deploy() {
_ccert="$3"
_cca="$4"
_cfullchain="$5"
- _cmdstr=""
- _homedir='~'
- _backupprefix="$_homedir/.acme_ssh_deploy/$_cdomain-backup"
- _backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
+ _deploy_ssh_servers=""
if [ -f "$DOMAIN_CONF" ]; then
# shellcheck disable=SC1090
@@ -71,18 +70,74 @@ ssh_deploy() {
Le_Deploy_ssh_cmd="$DEPLOY_SSH_CMD"
_savedomainconf Le_Deploy_ssh_cmd "$Le_Deploy_ssh_cmd"
elif [ -z "$Le_Deploy_ssh_cmd" ]; then
- Le_Deploy_ssh_cmd="ssh"
+ Le_Deploy_ssh_cmd="ssh -T"
fi
- # BACKUP is optional. If not provided then default to yes
+ # BACKUP is optional. If not provided then default to previously saved value or yes.
if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then
Le_Deploy_ssh_backup="no"
- elif [ -z "$Le_Deploy_ssh_backup" ]; then
+ elif [ -z "$Le_Deploy_ssh_backup" ] || [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
Le_Deploy_ssh_backup="yes"
fi
_savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup"
+ # BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy
+ if [ -n "$DEPLOY_SSH_BACKUP_PATH" ]; then
+ Le_Deploy_ssh_backup_path="$DEPLOY_SSH_BACKUP_PATH"
+ elif [ -z "$Le_Deploy_ssh_backup_path" ]; then
+ Le_Deploy_ssh_backup_path=".acme_ssh_deploy"
+ fi
+ _savedomainconf Le_Deploy_ssh_backup_path "$Le_Deploy_ssh_backup_path"
+
+ # MULTI_CALL is optional. If not provided then default to previously saved
+ # value (which may be undefined... equivalent to "no").
+ if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
+ Le_Deploy_ssh_multi_call="yes"
+ _savedomainconf Le_Deploy_ssh_multi_call "$Le_Deploy_ssh_multi_call"
+ elif [ "$DEPLOY_SSH_MULTI_CALL" = "no" ]; then
+ Le_Deploy_ssh_multi_call=""
+ _cleardomainconf Le_Deploy_ssh_multi_call
+ fi
+
+ _deploy_ssh_servers=$Le_Deploy_ssh_server
+ for Le_Deploy_ssh_server in $_deploy_ssh_servers; do
+ _ssh_deploy
+ done
+}
+
+_ssh_deploy() {
+ _err_code=0
+ _cmdstr=""
+ _backupprefix=""
+ _backupdir=""
+
_info "Deploy certificates to remote server $Le_Deploy_ssh_user@$Le_Deploy_ssh_server"
+ if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
+ _info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host"
+ else
+ _info "Required commands batched and sent in single call to remote host"
+ fi
+
+ if [ "$Le_Deploy_ssh_backup" = "yes" ]; then
+ _backupprefix="$Le_Deploy_ssh_backup_path/$_cdomain-backup"
+ _backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')"
+ # run cleanup on the backup directory, erase all older
+ # than 180 days (15552000 seconds).
+ _cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \
+do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \
+then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr"
+ # Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr"
+ # Create our backup directory for overwritten cert files.
+ _cmdstr="mkdir -p $_backupdir; $_cmdstr"
+ _info "Backup of old certificate files will be placed in remote directory $_backupdir"
+ _info "Backup directories erased after 180 days."
+ if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
+ if ! _ssh_remote_cmd "$_cmdstr"; then
+ return $_err_code
+ fi
+ _cmdstr=""
+ fi
+ fi
# KEYFILE is optional.
# If provided then private key will be copied to provided filename.
@@ -98,6 +153,12 @@ ssh_deploy() {
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $Le_Deploy_ssh_keyfile;"
_info "will copy private key to remote file $Le_Deploy_ssh_keyfile"
+ if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
+ if ! _ssh_remote_cmd "$_cmdstr"; then
+ return $_err_code
+ fi
+ _cmdstr=""
+ fi
fi
# CERTFILE is optional.
@@ -118,6 +179,12 @@ ssh_deploy() {
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $Le_Deploy_ssh_certfile;"
_info "will copy certificate to remote file $Le_Deploy_ssh_certfile"
+ if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
+ if ! _ssh_remote_cmd "$_cmdstr"; then
+ return $_err_code
+ fi
+ _cmdstr=""
+ fi
fi
# CAFILE is optional.
@@ -139,6 +206,12 @@ ssh_deploy() {
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $Le_Deploy_ssh_cafile;"
_info "will copy CA file to remote file $Le_Deploy_ssh_cafile"
+ if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
+ if ! _ssh_remote_cmd "$_cmdstr"; then
+ return $_err_code
+ fi
+ _cmdstr=""
+ fi
fi
# FULLCHAIN is optional.
@@ -161,6 +234,12 @@ ssh_deploy() {
# copy new certificate into file.
_cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $Le_Deploy_ssh_fullchain;"
_info "will copy fullchain to remote file $Le_Deploy_ssh_fullchain"
+ if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
+ if ! _ssh_remote_cmd "$_cmdstr"; then
+ return $_err_code
+ fi
+ _cmdstr=""
+ fi
fi
# REMOTE_CMD is optional.
@@ -172,34 +251,36 @@ ssh_deploy() {
if [ -n "$Le_Deploy_ssh_remote_cmd" ]; then
_cmdstr="$_cmdstr $Le_Deploy_ssh_remote_cmd;"
_info "Will execute remote command $Le_Deploy_ssh_remote_cmd"
+ if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then
+ if ! _ssh_remote_cmd "$_cmdstr"; then
+ return $_err_code
+ fi
+ _cmdstr=""
+ fi
fi
- if [ -z "$_cmdstr" ]; then
- _err "No remote commands to excute. Failed to deploy certificates to remote server"
- return 1
- elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then
- # run cleanup on the backup directory, erase all older
- # than 180 days (15552000 seconds).
- _cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \
-do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \
-then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr"
- # Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr"
- # Create our backup directory for overwritten cert files.
- _cmdstr="mkdir -p $_backupdir; $_cmdstr"
- _info "Backup of old certificate files will be placed in remote directory $_backupdir"
- _info "Backup directories erased after 180 days."
+ # if commands not all sent in multiple calls then all commands sent in a single SSH call now...
+ if [ -n "$_cmdstr" ]; then
+ if ! _ssh_remote_cmd "$_cmdstr"; then
+ return $_err_code
+ fi
fi
+ return 0
+}
- _secure_debug "Remote commands to execute: " "$_cmdstr"
+#cmd
+_ssh_remote_cmd() {
+ _cmd="$1"
+ _secure_debug "Remote commands to execute: $_cmd"
_info "Submitting sequence of commands to remote server by ssh"
# quotations in bash cmd below intended. Squash travis spellcheck error
# shellcheck disable=SC2029
- $Le_Deploy_ssh_cmd -T "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmdstr'"
- _ret="$?"
+ $Le_Deploy_ssh_cmd "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmd'"
+ _err_code="$?"
- if [ "$_ret" != "0" ]; then
- _err "Error code $_ret returned from $Le_Deploy_ssh_cmd"
+ if [ "$_err_code" != "0" ]; then
+ _err "Error code $_err_code returned from ssh"
fi
- return $_ret
+ return $_err_code
}
diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh
new file mode 100644
index 0000000000..c57d50bc77
--- /dev/null
+++ b/deploy/synology_dsm.sh
@@ -0,0 +1,152 @@
+#!/usr/bin/env sh
+
+# Here is a script to deploy cert to Synology DSM
+#
+# it requires the jq and curl are in the $PATH and the following
+# environment variables must be set:
+#
+# SYNO_Username - Synology Username to login (must be an administrator)
+# SYNO_Password - Synology Password to login
+# SYNO_Certificate - Certificate description to target for replacement
+#
+# The following environmental variables may be set if you don't like their
+# default values:
+#
+# SYNO_Scheme - defaults to http
+# SYNO_Hostname - defaults to localhost
+# SYNO_Port - defaults to 5000
+# SYNO_DID - device ID to skip OTP - defaults to empty
+#
+#returns 0 means success, otherwise error.
+
+######## Public functions #####################
+
+_syno_get_cookie_data() {
+ grep "\W$1=" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';'
+}
+
+#domain keyfile certfile cafile fullchain
+synology_dsm_deploy() {
+
+ _cdomain="$1"
+ _ckey="$2"
+ _ccert="$3"
+ _cca="$4"
+
+ _debug _cdomain "$_cdomain"
+
+ # Get Username and Password, but don't save until we successfully authenticate
+ _getdeployconf SYNO_Username
+ _getdeployconf SYNO_Password
+ _getdeployconf SYNO_Create
+ _getdeployconf SYNO_DID
+ if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then
+ _err "SYNO_Username & SYNO_Password must be set"
+ return 1
+ fi
+ _debug2 SYNO_Username "$SYNO_Username"
+ _secure_debug2 SYNO_Password "$SYNO_Password"
+
+ # Optional scheme, hostname, and port for Synology DSM
+ _getdeployconf SYNO_Scheme
+ _getdeployconf SYNO_Hostname
+ _getdeployconf SYNO_Port
+
+ # default vaules for scheme, hostname, and port
+ # defaulting to localhost and http because it's localhost...
+ [ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http"
+ [ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost"
+ [ -n "${SYNO_Port}" ] || SYNO_Port="5000"
+
+ _savedeployconf SYNO_Scheme "$SYNO_Scheme"
+ _savedeployconf SYNO_Hostname "$SYNO_Hostname"
+ _savedeployconf SYNO_Port "$SYNO_Port"
+
+ _debug2 SYNO_Scheme "$SYNO_Scheme"
+ _debug2 SYNO_Hostname "$SYNO_Hostname"
+ _debug2 SYNO_Port "$SYNO_Port"
+
+ # Get the certificate description, but don't save it until we verfiy it's real
+ _getdeployconf SYNO_Certificate
+ _debug SYNO_Certificate "${SYNO_Certificate:-}"
+
+ _base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port"
+ _debug _base_url "$_base_url"
+
+ # Login, get the token from JSON and session id from cookie
+ _info "Logging into $SYNO_Hostname:$SYNO_Port"
+ encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)"
+ encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)"
+ encoded_did="$(printf "%s" "$SYNO_DID" | _url_encode)"
+ response=$(_get "$_base_url/webman/login.cgi?username=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_id=$encoded_did" 1)
+ token=$(echo "$response" | grep "X-SYNO-TOKEN:" | sed -n 's/^X-SYNO-TOKEN: \(.*\)$/\1/p' | tr -d "\r\n")
+ _debug3 response "$response"
+ _debug token "$token"
+
+ if [ -z "$token" ]; then
+ _err "Unable to authenticate to $SYNO_Hostname:$SYNO_Port using $SYNO_Scheme."
+ _err "Check your username and password."
+ return 1
+ fi
+
+ _H1="Cookie: $(echo "$response" | _syno_get_cookie_data "id"); $(echo "$response" | _syno_get_cookie_data "smid")"
+ _H2="X-SYNO-TOKEN: $token"
+ export _H1
+ export _H2
+ _debug2 H1 "${_H1}"
+ _debug2 H2 "${_H2}"
+
+ # Now that we know the username and password are good, save them
+ _savedeployconf SYNO_Username "$SYNO_Username"
+ _savedeployconf SYNO_Password "$SYNO_Password"
+ _savedeployconf SYNO_DID "$SYNO_DID"
+
+ _info "Getting certificates in Synology DSM"
+ response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1" "$_base_url/webapi/entry.cgi")
+ _debug3 response "$response"
+ id=$(echo "$response" | sed -n "s/.*\"desc\":\"$SYNO_Certificate\",\"id\":\"\([^\"]*\).*/\1/p")
+ _debug2 id "$id"
+
+ if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then
+ _err "Unable to find certificate: $SYNO_Certificate and \$SYNO_Create is not set"
+ return 1
+ fi
+
+ # we've verified this certificate description is a thing, so save it
+ _savedeployconf SYNO_Certificate "$SYNO_Certificate"
+
+ default=false
+ if echo "$response" | sed -n "s/.*\"desc\":\"$SYNO_Certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
+ default=true
+ fi
+ _debug2 default "$default"
+
+ _info "Generate form POST request"
+ nl="\0015\0012"
+ delim="--------------------------$(_utc_date | tr -d -- '-: ')"
+ content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}"
+ content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}${default}"
+ content="$content${nl}--$delim--${nl}"
+ content="$(printf "%b_" "$content")"
+ content="${content%_}" # protect trailing \n
+
+ _info "Upload certificate to the Synology DSM"
+ response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token" "" "POST" "multipart/form-data; boundary=${delim}")
+ _debug3 response "$response"
+
+ if ! echo "$response" | grep '"error":' >/dev/null; then
+ if echo "$response" | grep '"restart_httpd":true' >/dev/null; then
+ _info "http services were restarted"
+ else
+ _info "http services were NOT restarted"
+ fi
+ return 0
+ else
+ _err "Unable to update certificate, error code $response"
+ return 1
+ fi
+}
diff --git a/deploy/vault_cli.sh b/deploy/vault_cli.sh
index b93fdd516e..5395d87efb 100644
--- a/deploy/vault_cli.sh
+++ b/deploy/vault_cli.sh
@@ -2,10 +2,10 @@
# Here is a script to deploy cert to hashicorp vault
# (https://www.vaultproject.io/)
-#
+#
# it requires the vault binary to be available in PATH, and the following
# environment variables:
-#
+#
# VAULT_PREFIX - this contains the prefix path in vault
# VAULT_ADDR - vault requires this to find your vault server
#
diff --git a/dnsapi/README.md b/dnsapi/README.md
index 4fa59cf246..e81f7916a8 100644
--- a/dnsapi/README.md
+++ b/dnsapi/README.md
@@ -2,5 +2,5 @@
DNS api usage:
-https://github.com/Neilpang/acme.sh/wiki/dnsapi
+https://github.com/acmesh-official/acme.sh/wiki/dnsapi
diff --git a/dnsapi/dns_1984hosting.sh b/dnsapi/dns_1984hosting.sh
new file mode 100755
index 0000000000..09f027967f
--- /dev/null
+++ b/dnsapi/dns_1984hosting.sh
@@ -0,0 +1,254 @@
+#!/usr/bin/env sh
+#This file name is "dns_1984hosting.sh"
+#So, here must be a method dns_1984hosting_add()
+#Which will be called by acme.sh to add the txt record to your api system.
+#returns 0 means success, otherwise error.
+#
+#Author: Adrian Fedoreanu
+#Report Bugs here: https://github.com/acmesh-official/acme.sh
+# or here... https://github.com/acmesh-official/acme.sh/issues/2851
+#
+######## Public functions #####################
+
+# Export 1984HOSTING username and password in following variables
+#
+# One984HOSTING_Username=username
+# One984HOSTING_Password=password
+#
+# sessionid cookie is saved in ~/.acme.sh/account.conf
+# username/password need to be set only when changed.
+
+#Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_1984hosting_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Add TXT record using 1984Hosting"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ if ! _1984hosting_login; then
+ _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain" "$fulldomain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _1984hosting_add_txt_record "$_domain" "$_sub_domain" "$txtvalue"
+ return $?
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_1984hosting_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Delete TXT record using 1984Hosting"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ if ! _1984hosting_login; then
+ _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file"
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain" "$fulldomain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _1984hosting_delete_txt_record "$_domain" "$_sub_domain"
+ return $?
+}
+
+#################### Private functions below ##################################
+
+# usage _1984hosting_add_txt_record domain subdomain value
+# returns 0 success
+_1984hosting_add_txt_record() {
+ _debug "Add TXT record $1 with value '$3'"
+ domain="$1"
+ subdomain="$2"
+ value="$(printf '%s' "$3" | _url_encode)"
+ url="https://management.1984hosting.com/domains/entry/"
+
+ postdata="entry=new"
+ postdata="$postdata&type=TXT"
+ postdata="$postdata&ttl=3600"
+ postdata="$postdata&zone=$domain"
+ postdata="$postdata&host=$subdomain"
+ postdata="$postdata&rdata=%22$value%22"
+ _debug2 postdata "$postdata"
+
+ _authpost "$postdata" "$url"
+ response="$(echo "$_response" | _normalizeJson)"
+ _debug2 response "$response"
+
+ if _contains "$response" '"haserrors": true'; then
+ _err "1984Hosting failed to add TXT record for $subdomain bad RC from _post"
+ return 1
+ elif _contains "$response" ""; then
+ _err "1984Hosting failed to add TXT record for $subdomain. Check $HTTP_HEADER file"
+ return 1
+ elif [ "$response" = '{"auth": false, "ok": false}' ]; then
+ _err "1984Hosting failed to add TXT record for $subdomain. Invalid or expired cookie"
+ return 1
+ fi
+
+ _info "Added acme challenge TXT record for $fulldomain at 1984Hosting"
+ return 0
+}
+
+# usage _1984hosting_delete_txt_record entry_id
+# returns 0 success
+_1984hosting_delete_txt_record() {
+ _debug "Delete $fulldomain TXT record"
+ domain="$1"
+ subdomain="$2"
+ url="https://management.1984hosting.com/domains"
+
+ _htmlget "$url" "$domain"
+ _debug2 _response "$_response"
+ zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+')"
+ _debug2 zone_id "$zone_id"
+ if [ -z "$zone_id" ]; then
+ _err "Error getting zone_id for $1"
+ return 1
+ fi
+
+ _htmlget "$url/$zone_id" "$subdomain"
+ _debug2 _response "$_response"
+ entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')"
+ _debug2 entry_id "$entry_id"
+ if [ -z "$entry_id" ]; then
+ _err "Error getting TXT entry_id for $1"
+ return 1
+ fi
+
+ _authpost "entry=$entry_id" "$url/delentry/"
+ response="$(echo "$_response" | _normalizeJson)"
+ _debug2 response "$response"
+
+ if ! _contains "$response" '"ok": true'; then
+ _err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post"
+ return 1
+ fi
+
+ _info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting"
+ return 0
+}
+
+# usage: _1984hosting_login username password
+# returns 0 success
+_1984hosting_login() {
+ if ! _check_credentials; then return 1; fi
+
+ if _check_cookie; then
+ _debug "Already logged in"
+ return 0
+ fi
+
+ _debug "Login to 1984Hosting as user $One984HOSTING_Username"
+ username=$(printf '%s' "$One984HOSTING_Username" | _url_encode)
+ password=$(printf '%s' "$One984HOSTING_Password" | _url_encode)
+ url="https://management.1984hosting.com/accounts/checkuserauth/"
+
+ response="$(_post "username=$username&password=$password&otpkey=" "$url")"
+ response="$(echo "$response" | _normalizeJson)"
+ _debug2 response "$response"
+
+ if [ "$response" = '{"loggedin": true, "ok": true}' ]; then
+ One984HOSTING_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _tail_n 1 | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
+ export One984HOSTING_COOKIE
+ _saveaccountconf_mutable One984HOSTING_COOKIE "$One984HOSTING_COOKIE"
+ return 0
+ fi
+ return 1
+}
+
+_check_credentials() {
+ if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then
+ One984HOSTING_Username=""
+ One984HOSTING_Password=""
+ _err "You haven't specified 1984Hosting username or password yet."
+ _err "Please export as One984HOSTING_Username / One984HOSTING_Password and try again."
+ return 1
+ fi
+ return 0
+}
+
+_check_cookie() {
+ One984HOSTING_COOKIE="${One984HOSTING_COOKIE:-$(_readaccountconf_mutable One984HOSTING_COOKIE)}"
+ if [ -z "$One984HOSTING_COOKIE" ]; then
+ _debug "No cached cookie found"
+ return 1
+ fi
+
+ _authget "https://management.1984hosting.com/accounts/loginstatus/"
+ response="$(echo "$_response" | _normalizeJson)"
+ if [ "$_response" = '{"ok": true}' ]; then
+ _debug "Cached cookie still valid"
+ return 0
+ fi
+ _debug "Cached cookie no longer valid"
+ One984HOSTING_COOKIE=""
+ _saveaccountconf_mutable One984HOSTING_COOKIE "$One984HOSTING_COOKIE"
+ return 1
+}
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_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
+
+ _authget "https://management.1984hosting.com/domains/soacheck/?zone=$h&nameserver=ns0.1984.is."
+ if _contains "$_response" "serial"; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+# add extra headers to request
+_authget() {
+ export _H1="Cookie: $One984HOSTING_COOKIE"
+ _response=$(_get "$1")
+}
+
+# truncate huge HTML response
+# echo: Argument list too long
+_htmlget() {
+ export _H1="Cookie: $One984HOSTING_COOKIE"
+ _response=$(_get "$1" | grep "$2" | _head_n 1)
+}
+
+# add extra headers to request
+_authpost() {
+ export _H1="Cookie: $One984HOSTING_COOKIE"
+ _response=$(_post "$1" "$2")
+}
diff --git a/dnsapi/dns_arvan.sh b/dnsapi/dns_arvan.sh
new file mode 100644
index 0000000000..edeb56ca49
--- /dev/null
+++ b/dnsapi/dns_arvan.sh
@@ -0,0 +1,163 @@
+#!/usr/bin/env sh
+
+#Arvan_Token="xxxx"
+
+ARVAN_API_URL="https://napi.arvancloud.com/cdn/4.0/domains"
+
+#Author: Ehsan Aliakbar
+#Report Bugs here: https://github.com/Neilpang/acme.sh
+#
+######## Public functions #####################
+
+#Usage: dns_arvan_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_arvan_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _info "Using Arvan"
+
+ Arvan_Token="${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}"
+
+ if [ -z "$Arvan_Token" ]; then
+ _err "You didn't specify \"Arvan_Token\" token yet."
+ _err "You can get yours from here https://npanel.arvancloud.com/profile/api-keys"
+ return 1
+ fi
+ #save the api token to the account conf file.
+ _saveaccountconf_mutable Arvan_Token "$Arvan_Token"
+
+ _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"
+
+ _info "Adding record"
+ if _arvan_rest POST "$_domain/dns-records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":{\"text\":\"$txtvalue\"},\"ttl\":120}"; then
+ if _contains "$response" "$txtvalue"; then
+ _info "Added, OK"
+ return 0
+ elif _contains "$response" "Record Data is Duplicated"; then
+ _info "Already exists, OK"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_arvan_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _info "Using Arvan"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ Arvan_Token="${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}"
+
+ _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"
+ shorted_txtvalue=$(printf "%s" "$txtvalue" | cut -d "-" -d "_" -f1)
+ _arvan_rest GET "${_domain}/dns-records?search=$shorted_txtvalue"
+
+ if ! printf "%s" "$response" | grep \"current_page\":1 >/dev/null; then
+ _err "Error on Arvan Api"
+ _err "Please create a github issue with debbug log"
+ return 1
+ fi
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"total\":[^,]*" | 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 ! _arvan_rest "DELETE" "${_domain}/dns-records/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ _debug "$response"
+ _contains "$response" 'dns record deleted'
+ fi
+}
+
+#################### Private functions below ##################################
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+ i=1
+ 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
+ fi
+
+ if ! _arvan_rest GET "?search=$h"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"domain\":\"$h\"" || _contains "$response" '"total":1'; then
+ _domain_id=$(echo "$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
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_arvan_rest() {
+ mtd="$1"
+ ep="$2"
+ data="$3"
+
+ token_trimmed=$(echo "$Arvan_Token" | tr -d '"')
+
+ export _H1="Authorization: $token_trimmed"
+
+ if [ "$mtd" = "DELETE" ]; then
+ #DELETE Request shouldn't have Content-Type
+ _debug data "$data"
+ response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
+ elif [ "$mtd" = "POST" ]; then
+ export _H2="Content-Type: application/json"
+ _debug data "$data"
+ response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")"
+ else
+ response="$(_get "$ARVAN_API_URL/$ep$data")"
+ fi
+}
diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh
index 246f4774a3..ea4736c474 100755
--- a/dnsapi/dns_aws.sh
+++ b/dnsapi/dns_aws.sh
@@ -6,11 +6,13 @@
#AWS_SECRET_ACCESS_KEY="xxxxxxx"
#This is the Amazon Route53 api wrapper for acme.sh
+#All `_sleep` commands are included to avoid Route53 throttling, see
+#https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests
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"
+AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API"
######## Public functions #####################
@@ -21,6 +23,7 @@ dns_aws_add() {
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
+ AWS_DNS_SLOWRATE="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}"
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
_use_container_role || _use_instance_role
@@ -38,11 +41,13 @@ dns_aws_add() {
if [ -z "$_using_role" ]; then
_saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID"
_saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY"
+ _saveaccountconf_mutable AWS_DNS_SLOWRATE "$AWS_DNS_SLOWRATE"
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
+ _sleep 1
return 1
fi
_debug _domain_id "$_domain_id"
@@ -51,6 +56,7 @@ dns_aws_add() {
_info "Getting existing records for $fulldomain"
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
+ _sleep 1
return 1
fi
@@ -63,6 +69,7 @@ dns_aws_add() {
if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then
_info "The TXT record already exists. Skipping."
+ _sleep 1
return 0
fi
@@ -72,9 +79,16 @@ dns_aws_add() {
if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
_info "TXT record updated successfully."
+ if [ -n "$AWS_DNS_SLOWRATE" ]; then
+ _info "Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds"
+ _sleep "$AWS_DNS_SLOWRATE"
+ else
+ _sleep 1
+ fi
+
return 0
fi
-
+ _sleep 1
return 1
}
@@ -85,6 +99,7 @@ dns_aws_rm() {
AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}"
AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}"
+ AWS_DNS_SLOWRATE="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}"
if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
_use_container_role || _use_instance_role
@@ -93,6 +108,7 @@ dns_aws_rm() {
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
+ _sleep 1
return 1
fi
_debug _domain_id "$_domain_id"
@@ -101,6 +117,7 @@ dns_aws_rm() {
_info "Getting existing records for $fulldomain"
if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then
+ _sleep 1
return 1
fi
@@ -109,6 +126,7 @@ dns_aws_rm() {
_debug "_resource_record" "$_resource_record"
else
_debug "no records exist, skip"
+ _sleep 1
return 0
fi
@@ -116,9 +134,16 @@ dns_aws_rm() {
if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then
_info "TXT record deleted successfully."
+ if [ -n "$AWS_DNS_SLOWRATE" ]; then
+ _info "Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds"
+ _sleep "$AWS_DNS_SLOWRATE"
+ else
+ _sleep 1
+ fi
+
return 0
fi
-
+ _sleep 1
return 1
}
diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh
index 8b52dee745..bf7cf2bf8e 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
-WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Azure-DNS"
+WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS"
######## Public functions #####################
diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh
index 62e40caf23..36799dcd9a 100755
--- a/dnsapi/dns_cf.sh
+++ b/dnsapi/dns_cf.sh
@@ -7,6 +7,7 @@
#CF_Token="xxxx"
#CF_Account_ID="xxxx"
+#CF_Zone_ID="xxxx"
CF_Api="https://api.cloudflare.com/client/v4"
@@ -19,12 +20,14 @@ dns_cf_add() {
CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}"
CF_Account_ID="${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}"
+ CF_Zone_ID="${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}"
CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}"
if [ "$CF_Token" ]; then
_saveaccountconf_mutable CF_Token "$CF_Token"
_saveaccountconf_mutable CF_Account_ID "$CF_Account_ID"
+ _saveaccountconf_mutable CF_Zone_ID "$CF_Zone_ID"
else
if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
CF_Key=""
@@ -56,7 +59,7 @@ dns_cf_add() {
_debug "Getting txt records"
_cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain"
- if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then
+ if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
_err "Error"
return 1
fi
@@ -91,6 +94,7 @@ dns_cf_rm() {
CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}"
CF_Account_ID="${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}"
+ CF_Zone_ID="${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}"
CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}"
CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}"
@@ -106,17 +110,17 @@ dns_cf_rm() {
_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"
+ if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
+ _err "Error: $response"
return 1
fi
- count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+ count=$(echo "$response" | _egrep_o "\"count\": *[^,]*" | cut -d : -f 2 | tr -d " ")
_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)
+ record_id=$(echo "$response" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
_debug "record_id" "$record_id"
if [ -z "$record_id" ]; then
_err "Can not get record id to remove."
@@ -126,7 +130,7 @@ dns_cf_rm() {
_err "Delete record error."
return 1
fi
- _contains "$response" '"success":true'
+ echo "$response" | tr -d " " | grep \"success\":true >/dev/null
fi
}
@@ -141,6 +145,28 @@ _get_root() {
domain=$1
i=1
p=1
+
+ # Use Zone ID directly if provided
+ if [ "$CF_Zone_ID" ]; then
+ if ! _cf_rest GET "zones/$CF_Zone_ID"; then
+ return 1
+ else
+ if echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then
+ _domain=$(echo "$response" | _egrep_o "\"name\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
+ if [ "$_domain" ]; then
+ _cutlength=$((${#domain} - ${#_domain} - 1))
+ _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength")
+ _domain_id=$CF_Zone_ID
+ return 0
+ else
+ return 1
+ fi
+ else
+ return 1
+ fi
+ fi
+ fi
+
while true; do
h=$(printf "%s" "$domain" | cut -d . -f $i-100)
_debug h "$h"
@@ -160,7 +186,7 @@ _get_root() {
fi
if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_count":1'; then
- _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
+ _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
diff --git a/dnsapi/dns_clouddns.sh b/dnsapi/dns_clouddns.sh
new file mode 100755
index 0000000000..31ae4ee9d0
--- /dev/null
+++ b/dnsapi/dns_clouddns.sh
@@ -0,0 +1,197 @@
+#!/usr/bin/env sh
+
+# Author: Radek Sprta
+
+#CLOUDDNS_EMAIL=XXXXX
+#CLOUDDNS_PASSWORD="YYYYYYYYY"
+#CLOUDDNS_CLIENT_ID=XXXXX
+
+CLOUDDNS_API='https://admin.vshosting.cloud/clouddns'
+CLOUDDNS_LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login'
+
+######## Public functions #####################
+
+# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_clouddns_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug "fulldomain" "$fulldomain"
+
+ CLOUDDNS_CLIENT_ID="${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}"
+ CLOUDDNS_EMAIL="${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}"
+ CLOUDDNS_PASSWORD="${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}"
+
+ if [ -z "$CLOUDDNS_PASSWORD" ] || [ -z "$CLOUDDNS_EMAIL" ] || [ -z "$CLOUDDNS_CLIENT_ID" ]; then
+ CLOUDDNS_CLIENT_ID=""
+ CLOUDDNS_EMAIL=""
+ CLOUDDNS_PASSWORD=""
+ _err "You didn't specify a CloudDNS password, email and client ID yet."
+ return 1
+ fi
+ if ! _contains "$CLOUDDNS_EMAIL" "@"; then
+ _err "It seems that the CLOUDDNS_EMAIL=$CLOUDDNS_EMAIL is not a valid email address."
+ _err "Please check and retry."
+ return 1
+ fi
+ # Save CloudDNS client id, email and password to config file
+ _saveaccountconf_mutable CLOUDDNS_CLIENT_ID "$CLOUDDNS_CLIENT_ID"
+ _saveaccountconf_mutable CLOUDDNS_EMAIL "$CLOUDDNS_EMAIL"
+ _saveaccountconf_mutable CLOUDDNS_PASSWORD "$CLOUDDNS_PASSWORD"
+
+ _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"
+
+ # Add TXT record
+ data="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"value\":\"$txtvalue\",\"domainId\":\"$_domain_id\"}"
+ if _clouddns_api POST "record-txt" "$data"; then
+ if _contains "$response" "$txtvalue"; then
+ _info "Added, OK"
+ elif _contains "$response" '"code":4136'; then
+ _info "Already exists, OK"
+ else
+ _err "Add TXT record error."
+ return 1
+ fi
+ fi
+
+ _debug "Publishing record changes"
+ _clouddns_api PUT "domain/$_domain_id/publish" "{\"soaTtl\":300}"
+}
+
+# Usage: rm _acme-challenge.www.domain.com
+dns_clouddns_rm() {
+ fulldomain=$1
+ _debug "fulldomain" "$fulldomain"
+
+ CLOUDDNS_CLIENT_ID="${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}"
+ CLOUDDNS_EMAIL="${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}"
+ CLOUDDNS_PASSWORD="${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}"
+
+ _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"
+
+ # Get record ID
+ _clouddns_api GET "domain/$_domain_id"
+ if _contains "$response" "lastDomainRecordList"; then
+ re="\"lastDomainRecordList\".*\"id\":\"([^\"}]*)\"[^}]*\"name\":\"$fulldomain.\","
+ _last_domains=$(echo "$response" | _egrep_o "$re")
+ re2="\"id\":\"([^\"}]*)\"[^}]*\"name\":\"$fulldomain.\","
+ _record_id=$(echo "$_last_domains" | _egrep_o "$re2" | _head_n 1 | cut -d : -f 2 | cut -d , -f 1 | tr -d "\"")
+ _debug _record_id "$_record_id"
+ else
+ _err "Could not retrieve record ID"
+ return 1
+ fi
+
+ _info "Removing record"
+ if _clouddns_api DELETE "record/$_record_id"; then
+ if _contains "$response" "\"error\":"; then
+ _err "Could not remove record"
+ return 1
+ fi
+ fi
+
+ _debug "Publishing record changes"
+ _clouddns_api PUT "domain/$_domain_id/publish" "{\"soaTtl\":300}"
+}
+
+#################### Private functions below ##################################
+
+# Usage: _get_root _acme-challenge.www.domain.com
+# Returns:
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+
+ # Get domain root
+ data="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$CLOUDDNS_CLIENT_ID\"}]}"
+ _clouddns_api "POST" "domain/search" "$data"
+ domain_slice="$domain"
+ while [ -z "$domain_root" ]; do
+ if _contains "$response" "\"domainName\":\"$domain_slice\.\""; then
+ domain_root="$domain_slice"
+ _debug domain_root "$domain_root"
+ fi
+ domain_slice="$(echo "$domain_slice" | cut -d . -f 2-)"
+ done
+
+ # Get domain id
+ data="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$CLOUDDNS_CLIENT_ID\"}, \
+ {\"name\": \"domainName\", \"operator\": \"eq\", \"value\": \"$domain_root.\"}]}"
+ _clouddns_api "POST" "domain/search" "$data"
+ if _contains "$response" "\"id\":\""; then
+ re='domainType\":\"[^\"]*\",\"id\":\"([^\"]*)\",' # Match domain id
+ _domain_id=$(echo "$response" | _egrep_o "$re" | _head_n 1 | cut -d : -f 3 | tr -d "\",")
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | sed "s/.$domain_root//")
+ _domain="$domain_root"
+ return 0
+ fi
+ _err 'Domain name not found on your CloudDNS account'
+ return 1
+ fi
+ return 1
+}
+
+# Usage: _clouddns_api GET domain/search '{"data": "value"}'
+# Returns:
+# response='{"message": "api response"}'
+_clouddns_api() {
+ method=$1
+ endpoint="$2"
+ data="$3"
+ _debug endpoint "$endpoint"
+
+ if [ -z "$CLOUDDNS_TOKEN" ]; then
+ _clouddns_login
+ fi
+ _debug CLOUDDNS_TOKEN "$CLOUDDNS_TOKEN"
+
+ export _H1="Content-Type: application/json"
+ export _H2="Authorization: Bearer $CLOUDDNS_TOKEN"
+
+ if [ "$method" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$CLOUDDNS_API/$endpoint" "" "$method" | tr -d '\t\r\n ')"
+ else
+ response="$(_get "$CLOUDDNS_API/$endpoint" | tr -d '\t\r\n ')"
+ fi
+
+ # shellcheck disable=SC2181
+ if [ "$?" != "0" ]; then
+ _err "Error $endpoint"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
+
+# Returns:
+# CLOUDDNS_TOKEN=dslfje2rj23l
+_clouddns_login() {
+ login_data="{\"email\": \"$CLOUDDNS_EMAIL\", \"password\": \"$CLOUDDNS_PASSWORD\"}"
+ response="$(_post "$login_data" "$CLOUDDNS_LOGIN_API" "" "POST" "Content-Type: application/json")"
+
+ if _contains "$response" "\"accessToken\":\""; then
+ CLOUDDNS_TOKEN=$(echo "$response" | _egrep_o "\"accessToken\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
+ export CLOUDDNS_TOKEN
+ else
+ echo 'Could not get CloudDNS access token; check your credentials'
+ return 1
+ fi
+ return 0
+}
diff --git a/dnsapi/dns_constellix.sh b/dnsapi/dns_constellix.sh
new file mode 100644
index 0000000000..42df710d7c
--- /dev/null
+++ b/dnsapi/dns_constellix.sh
@@ -0,0 +1,141 @@
+#!/usr/bin/env sh
+
+# Author: Wout Decre
+
+CONSTELLIX_Api="https://api.dns.constellix.com/v1"
+#CONSTELLIX_Key="XXX"
+#CONSTELLIX_Secret="XXX"
+
+######## Public functions #####################
+
+# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+dns_constellix_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ CONSTELLIX_Key="${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}"
+ CONSTELLIX_Secret="${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}"
+
+ if [ -z "$CONSTELLIX_Key" ] || [ -z "$CONSTELLIX_Secret" ]; then
+ _err "You did not specify the Contellix API key and secret yet."
+ return 1
+ fi
+
+ _saveaccountconf_mutable CONSTELLIX_Key "$CONSTELLIX_Key"
+ _saveaccountconf_mutable CONSTELLIX_Secret "$CONSTELLIX_Secret"
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _info "Adding TXT record"
+ if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"add\":true,\"set\":{\"name\":\"${_sub_domain}\",\"ttl\":120,\"roundRobin\":[{\"value\":\"${txtvalue}\"}]}}]"; then
+ if printf -- "%s" "$response" | grep "{\"success\":\"1 record(s) added, 0 record(s) updated, 0 record(s) deleted\"}" >/dev/null; then
+ _info "Added"
+ return 0
+ else
+ _err "Error adding TXT record"
+ return 1
+ fi
+ fi
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_constellix_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ CONSTELLIX_Key="${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}"
+ CONSTELLIX_Secret="${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}"
+
+ if [ -z "$CONSTELLIX_Key" ] || [ -z "$CONSTELLIX_Secret" ]; then
+ _err "You did not specify the Contellix API key and secret yet."
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _info "Removing TXT record"
+ if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"delete\":true,\"filter\":{\"field\":\"name\",\"op\":\"eq\",\"value\":\"${_sub_domain}\"}}]"; then
+ if printf -- "%s" "$response" | grep "{\"success\":\"0 record(s) added, 0 record(s) updated, 1 record(s) deleted\"}" >/dev/null; then
+ _info "Removed"
+ return 0
+ else
+ _err "Error removing TXT record"
+ return 1
+ fi
+ fi
+}
+
+#################### Private functions below ##################################
+
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ _debug "Detecting root zone"
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ if ! _constellix_rest GET "domains/search?exact=$h"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\""; then
+ _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]+" | cut -d ':' -f 2)
+ if [ "$_domain_id" ]; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-$p)
+ _domain="$h"
+
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_constellix_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ rdate=$(date +"%s")"000"
+ hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64)
+
+ export _H1="x-cnsdns-apiKey: $CONSTELLIX_Key"
+ export _H2="x-cnsdns-requestDate: $rdate"
+ export _H3="x-cnsdns-hmac: $hmac"
+ export _H4="Accept: application/json"
+ export _H5="Content-Type: application/json"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$CONSTELLIX_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$CONSTELLIX_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "Error $ep"
+ return 1
+ fi
+
+ _debug response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh
index d7ad712c04..8db3011de5 100644
--- a/dnsapi/dns_cyon.sh
+++ b/dnsapi/dns_cyon.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
########
-# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/Neilpang/acme.sh)
+# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/acmesh-official/acme.sh)
#
# Usage: acme.sh --issue --dns dns_cyon -d www.domain.com
#
diff --git a/dnsapi/dns_da.sh b/dnsapi/dns_da.sh
index 7755c7e1d6..4e9c4ef088 100755
--- a/dnsapi/dns_da.sh
+++ b/dnsapi/dns_da.sh
@@ -9,7 +9,7 @@
#
# User must provide login data and URL to DirectAdmin incl. port.
# You can create login key, by using the Login Keys function
-# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to
+# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to
# - CMD_API_DNS_CONTROL
# - CMD_API_SHOW_DOMAINS
#
diff --git a/dnsapi/dns_ddnss.sh b/dnsapi/dns_ddnss.sh
index 903b961919..ecc4f174a4 100644
--- a/dnsapi/dns_ddnss.sh
+++ b/dnsapi/dns_ddnss.sh
@@ -12,7 +12,7 @@
# --
#
-DDNSS_DNS_API="https://ddnss.de/upd.php"
+DDNSS_DNS_API="https://ip4.ddnss.de/upd.php"
######## Public functions #####################
@@ -119,7 +119,7 @@ _ddnss_rest() {
# DDNSS uses GET to update domain info
if [ "$method" = "GET" ]; then
- response="$(_get "$url" | sed 's/<[a-zA-Z\/][^>]*>//g' | _tail_n 1)"
+ response="$(_get "$url" | sed 's/<[a-zA-Z\/][^>]*>//g' | tr -s "\n" | _tail_n 1)"
else
_err "Unsupported method"
return 1
diff --git a/dnsapi/dns_df.sh b/dnsapi/dns_df.sh
new file mode 100644
index 0000000000..c0499ddfe9
--- /dev/null
+++ b/dnsapi/dns_df.sh
@@ -0,0 +1,65 @@
+#!/usr/bin/env sh
+
+########################################################################
+# https://dyndnsfree.de hook script for acme.sh
+#
+# Environment variables:
+#
+# - $DF_user (your dyndnsfree.de username)
+# - $DF_password (your dyndnsfree.de password)
+#
+# Author: Thilo Gass
+# Git repo: https://github.com/ThiloGa/acme.sh
+
+#-- dns_df_add() - Add TXT record --------------------------------------
+# Usage: dns_df_add _acme-challenge.subdomain.domain.com "XyZ123..."
+
+dyndnsfree_api="https://dynup.de/acme.php"
+
+dns_df_add() {
+ fulldomain=$1
+ txt_value=$2
+ _info "Using DNS-01 dyndnsfree.de hook"
+
+ DF_user="${DF_user:-$(_readaccountconf_mutable DF_user)}"
+ DF_password="${DF_password:-$(_readaccountconf_mutable DF_password)}"
+ if [ -z "$DF_user" ] || [ -z "$DF_password" ]; then
+ DF_user=""
+ DF_password=""
+ _err "No auth details provided. Please set user credentials using the \$DF_user and \$DF_password environment variables."
+ return 1
+ fi
+ #save the api user and password to the account conf file.
+ _debug "Save user and password"
+ _saveaccountconf_mutable DF_user "$DF_user"
+ _saveaccountconf_mutable DF_password "$DF_password"
+
+ domain="$(printf "%s" "$fulldomain" | cut -d"." -f2-)"
+
+ get="$dyndnsfree_api?username=$DF_user&password=$DF_password&hostname=$domain&add_hostname=$fulldomain&txt=$txt_value"
+
+ if ! erg="$(_get "$get")"; then
+ _err "error Adding $fulldomain TXT: $txt_value"
+ return 1
+ fi
+
+ if _contains "$erg" "success"; then
+ _info "Success, TXT Added, OK"
+ else
+ _err "error Adding $fulldomain TXT: $txt_value erg: $erg"
+ return 1
+ fi
+
+ _debug "ok Auto $fulldomain TXT: $txt_value erg: $erg"
+ return 0
+}
+
+dns_df_rm() {
+
+ fulldomain=$1
+ txtvalue=$2
+ _info "TXT enrty in $fulldomain is deleted automatically"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+}
diff --git a/dnsapi/dns_doapi.sh b/dnsapi/dns_doapi.sh
index 135f0b0368..a001d52cd5 100755
--- a/dnsapi/dns_doapi.sh
+++ b/dnsapi/dns_doapi.sh
@@ -1,11 +1,11 @@
#!/usr/bin/env sh
# Official Let's Encrypt API for do.de / Domain-Offensive
-#
+#
# This is different from the dns_do adapter, because dns_do is only usable for enterprise customers
# This API is also available to private customers/individuals
-#
-# Provide the required LetsEncrypt token like this:
+#
+# Provide the required LetsEncrypt token like this:
# DO_LETOKEN="FmD408PdqT1E269gUK57"
DO_API="https://www.do.de/api/letsencrypt"
diff --git a/dnsapi/dns_dp.sh b/dnsapi/dns_dp.sh
index 480c1f9ad7..033fa5aae0 100755
--- a/dnsapi/dns_dp.sh
+++ b/dnsapi/dns_dp.sh
@@ -53,7 +53,7 @@ dns_dp_rm() {
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
+ if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain"; then
_err "Record.Lis error."
return 1
fi
@@ -70,12 +70,12 @@ dns_dp_rm() {
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
+ if ! _rest POST "Record.Remove" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&record_id=$record_id"; then
_err "Record.Remove error."
return 1
fi
- _contains "$response" "Action completed successful"
+ _contains "$response" "successful"
}
@@ -89,11 +89,11 @@ add_record() {
_info "Adding record"
- if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then
+ if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then
return 1
fi
- _contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists"
+ _contains "$response" "successful" || _contains "$response" "Domain record already exists"
}
#################### Private functions below ##################################
@@ -113,11 +113,11 @@ _get_root() {
return 1
fi
- if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&domain=$h"; then
+ if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain=$h"; then
return 1
fi
- if _contains "$response" "Action completed successful"; then
+ if _contains "$response" "successful"; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
_debug _domain_id "$_domain_id"
if [ "$_domain_id" ]; then
diff --git a/dnsapi/dns_durabledns.sh b/dnsapi/dns_durabledns.sh
index 9a05eb32d3..677ae24dd8 100644
--- a/dnsapi/dns_durabledns.sh
+++ b/dnsapi/dns_durabledns.sh
@@ -147,11 +147,11 @@ _dd_soap() {
# build SOAP XML
_xml='
-
'"$body"'
'
diff --git a/dnsapi/dns_dynv6.sh b/dnsapi/dns_dynv6.sh
new file mode 100644
index 0000000000..cf39282b80
--- /dev/null
+++ b/dnsapi/dns_dynv6.sh
@@ -0,0 +1,121 @@
+#!/usr/bin/env sh
+#Author StefanAbl
+#Usage specify a private keyfile to use with dynv6 'export KEY="path/to/keyfile"'
+#if no keyfile is specified, you will be asked if you want to create one in /home/$USER/.ssh/dynv6 and /home/$USER/.ssh/dynv6.pub
+######## Public functions #####################
+# Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
+#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dynv6_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _info "Using dynv6 api"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+ _get_keyfile
+ _info "using keyfile $dynv6_keyfile"
+ _get_domain "$fulldomain"
+ _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)"
+ if ! _contains "$_your_hosts" "$_host"; then
+ _debug "The host is $_host and the record $_record"
+ _debug "Dynv6 returned $_your_hosts"
+ _err "The host $_host does not exists on your dynv6 account"
+ return 1
+ fi
+ _debug "found host on your account"
+ returnval="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts \""$_host"\" records set \""$_record"\" txt data \""$txtvalue"\")"
+ _debug "Dynv6 returend this after record was added: $returnval"
+ if _contains "$returnval" "created"; then
+ return 0
+ elif _contains "$returnval" "updated"; then
+ return 0
+ else
+ _err "Something went wrong! it does not seem like the record was added succesfully"
+ return 1
+ fi
+ return 1
+}
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_dynv6_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _info "Using dynv6 api"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+ _get_keyfile
+ _info "using keyfile $dynv6_keyfile"
+ _get_domain "$fulldomain"
+ _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)"
+ if ! _contains "$_your_hosts" "$_host"; then
+ _debug "The host is $_host and the record $_record"
+ _debug "Dynv6 returned $_your_hosts"
+ _err "The host $_host does not exists on your dynv6 account"
+ return 1
+ fi
+ _debug "found host on your account"
+ _info "$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts "\"$_host\"" records del "\"$_record\"" txt)"
+ return 0
+
+}
+#################### Private functions below ##################################
+#Usage: No Input required
+#returns
+#dynv6_keyfile the path to the new keyfile that has been generated
+_generate_new_key() {
+ dynv6_keyfile="$(eval echo ~"$USER")/.ssh/dynv6"
+ _info "Path to key file used: $dynv6_keyfile"
+ if [ ! -f "$dynv6_keyfile" ] && [ ! -f "$dynv6_keyfile.pub" ]; then
+ _debug "generating key in $dynv6_keyfile and $dynv6_keyfile.pub"
+ ssh-keygen -f "$dynv6_keyfile" -t ssh-ed25519 -N ''
+ else
+ _err "There is already a file in $dynv6_keyfile or $dynv6_keyfile.pub"
+ return 1
+ fi
+}
+#Usage: _acme-challenge.www.example.dynv6.net
+#returns
+#_host= example.dynv6.net
+#_record=_acme-challenge.www
+#aborts if not a valid domain
+_get_domain() {
+ _full_domain="$1"
+ _debug "getting domain for $_full_domain"
+ if ! _contains "$_full_domain" 'dynv6.net' && ! _contains "$_full_domain" 'dns.army' && ! _contains "$_full_domain" 'dns.navy'; then
+ _err "The hosts does not seem to be a dynv6 host"
+ return 1
+ fi
+ _record="${_full_domain%.*}"
+ _record="${_record%.*}"
+ _record="${_record%.*}"
+ _debug "The record we are ging to use is $_record"
+ _host="$_full_domain"
+ while [ "$(echo "$_host" | grep -o '\.' | wc -l)" != "2" ]; do
+ _host="${_host#*.}"
+ done
+ _debug "And the host is $_host"
+ return 0
+
+}
+
+# Usage: No input required
+#returns
+#dynv6_keyfile path to the key that will be used
+_get_keyfile() {
+ _debug "get keyfile method called"
+ dynv6_keyfile="${dynv6_keyfile:-$(_readaccountconf_mutable dynv6_keyfile)}"
+ _debug Your key is "$dynv6_keyfile"
+ if [ -z "$dynv6_keyfile" ]; then
+ if [ -z "$KEY" ]; then
+ _err "You did not specify a key to use with dynv6"
+ _info "Creating new dynv6 api key to add to dynv6.com"
+ _generate_new_key
+ _info "Please add this key to dynv6.com $(cat "$dynv6_keyfile.pub")"
+ _info "Hit Enter to contiue"
+ read -r _
+ #save the credentials to the account conf file.
+ else
+ dynv6_keyfile="$KEY"
+ fi
+ _saveaccountconf_mutable dynv6_keyfile "$dynv6_keyfile"
+ fi
+}
diff --git a/dnsapi/dns_easydns.sh b/dnsapi/dns_easydns.sh
new file mode 100644
index 0000000000..f466f1e2f9
--- /dev/null
+++ b/dnsapi/dns_easydns.sh
@@ -0,0 +1,171 @@
+#!/usr/bin/env sh
+
+#######################################################
+#
+# easyDNS REST API for acme.sh by Neilpang based on dns_cf.sh
+#
+# API Documentation: https://sandbox.rest.easydns.net:3001/
+#
+# Author: wurzelpanzer [wurzelpanzer@maximolider.net]
+# Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/2647
+#
+#################### Public functions #################
+
+#EASYDNS_Key="xxxxxxxxxxxxxxxxxxxxxxxx"
+#EASYDNS_Token="xxxxxxxxxxxxxxxxxxxxxxxx"
+EASYDNS_Api="https://rest.easydns.net"
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_easydns_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}"
+ EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}"
+
+ if [ -z "$EASYDNS_Token" ] || [ -z "$EASYDNS_Key" ]; then
+ _err "You didn't specify an easydns.net token or api key. Signup at https://cp.easydns.com/manage/security/api/signup.php"
+ return 1
+ else
+ _saveaccountconf_mutable EASYDNS_Token "$EASYDNS_Token"
+ _saveaccountconf_mutable EASYDNS_Key "$EASYDNS_Key"
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting txt records"
+ _EASYDNS_rest GET "zones/records/all/${_domain}/search/${_sub_domain}"
+
+ if ! printf "%s" "$response" | grep \"status\":200 >/dev/null; then
+ _err "Error"
+ return 1
+ fi
+
+ _info "Adding record"
+ if _EASYDNS_rest PUT "zones/records/add/$_domain/TXT" "{\"host\":\"$_sub_domain\",\"rdata\":\"$txtvalue\"}"; then
+ if _contains "$response" "\"status\":201"; then
+ _info "Added, OK"
+ return 0
+ elif _contains "$response" "Record already exists"; then
+ _info "Already exists, OK"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+
+}
+
+dns_easydns_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}"
+ EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting txt records"
+ _EASYDNS_rest GET "zones/records/all/${_domain}/search/${_sub_domain}"
+
+ if ! printf "%s" "$response" | grep \"status\":200 >/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 ! _EASYDNS_rest DELETE "zones/records/$_domain/$record_id"; then
+ _err "Delete record error."
+ return 1
+ fi
+ _contains "$response" "\"status\":200"
+ fi
+
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+ 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
+ fi
+
+ if ! _EASYDNS_rest GET "zones/records/all/$h"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"status\":200"; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain=$h
+ return 0
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_EASYDNS_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ basicauth=$(printf "%s" "$EASYDNS_Token":"$EASYDNS_Key" | _base64)
+
+ export _H1="accept: application/json"
+ if [ "$basicauth" ]; then
+ export _H2="Authorization: Basic $basicauth"
+ fi
+
+ if [ "$m" != "GET" ]; then
+ export _H3="Content-Type: application/json"
+ _debug data "$data"
+ response="$(_post "$data" "$EASYDNS_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$EASYDNS_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_euserv.sh b/dnsapi/dns_euserv.sh
index 3810156594..cfb4b814f4 100644
--- a/dnsapi/dns_euserv.sh
+++ b/dnsapi/dns_euserv.sh
@@ -127,7 +127,7 @@ dns_euserv_rm() {
else
# find XML block where txtvalue is in. The record_id is allways prior this line!
_endLine=$(echo "$response" | grep -n '>dns_record_content<.*>'"$txtvalue"'<' | cut -d ':' -f 1)
- # record_id is the last Tag with a number before the row _endLine, identified by
+ # record_id is the last Tag with a number before the row _endLine, identified by
_record_id=$(echo "$response" | sed -n '1,'"$_endLine"'p' | grep '' | _tail_n 1 | sed 's/.*\([0-9]*\)<\/name>.*/\1/')
_info "Deleting record"
_euserv_delete_record "$_record_id"
diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh
index e76e649539..4a58931ffd 100755
--- a/dnsapi/dns_freedns.sh
+++ b/dnsapi/dns_freedns.sh
@@ -7,7 +7,7 @@
#
#Author: David Kerr
#Report Bugs here: https://github.com/dkerr64/acme.sh
-#or here... https://github.com/Neilpang/acme.sh/issues/2305
+#or here... https://github.com/acmesh-official/acme.sh/issues/2305
#
######## Public functions #####################
@@ -303,9 +303,9 @@ _freedns_domain_id() {
return 1
fi
- domain_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's//@
/g' | tr '@' '\n' \
+ domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's/
/@
/g' | tr '@' '\n' \
| grep "$search_domain | \|$search_domain(.*) | " \
- | _egrep_o "edit\.php\?edit_domain_id=[0-9a-zA-Z]+" \
+ | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' \
| cut -d = -f 2)"
# The above beauty extracts domain ID from the html page...
# strip out all blank space and new lines. Then insert newlines
@@ -349,17 +349,17 @@ _freedns_data_id() {
return 1
fi
- data_id="$(echo "$htmlpage" | tr -d "[:space:]" | sed 's/
/@
/g' | tr '@' '\n' \
+ data_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's/
/@
/g' | tr '@' '\n' \
| grep "$record_type | " \
| grep "$search_domain" \
- | _egrep_o "edit\.php\?data_id=[0-9a-zA-Z]+" \
+ | sed -n 's/.*\(edit\.php?data_id=[0-9a-zA-Z]*\).*/\1/p' \
| cut -d = -f 2)"
# The above beauty extracts data ID from the html page...
# strip out all blank space and new lines. Then insert newlines
# before each table row
# search for the record type withing each row (e.g. TXT)
# search for the domain within each row (which is within a
- # anchor. And finally extract the domain ID.
+ # anchor. And finally extract the domain ID.
if [ -n "$data_id" ]; then
printf "%s" "$data_id"
return 0
diff --git a/dnsapi/dns_gcloud.sh b/dnsapi/dns_gcloud.sh
index ebbeecf28a..6365b33808 100755
--- a/dnsapi/dns_gcloud.sh
+++ b/dnsapi/dns_gcloud.sh
@@ -131,7 +131,7 @@ _dns_gcloud_find_zone() {
filter="$filter$part. "
part="$(echo "$part" | sed 's/[^.]*\.*//')"
done
- filter="$filter)"
+ filter="$filter) AND visibility=public"
_debug filter "$filter"
# List domains and find the zone with the deepest sub-domain (in case of some levels of delegation)
diff --git a/dnsapi/dns_gdnsdk.sh b/dnsapi/dns_gdnsdk.sh
index 8c4962c00f..90842b258a 100755
--- a/dnsapi/dns_gdnsdk.sh
+++ b/dnsapi/dns_gdnsdk.sh
@@ -157,9 +157,18 @@ _successful_update() {
}
_findentry() {
+ #args $1: fulldomain, $2: txtvalue
#returns id of dns entry, if it exists
_myget "action=dns_primary_changeDNSsetup&user_domain=$_domain"
- _id=$(echo "$_result" | _egrep_o "$1 | \s*$2 | [^?]*[^&]*&id=[^&]*" | sed 's/^.*=//')
+ _debug3 "_result: $_result"
+
+ _tmp_result=$(echo "$_result" | tr -d '\n\r' | _egrep_o "$1 | \s*$2 | [^?]*[^&]*&id=[^&]*")
+ _debug _tmp_result "$_tmp_result"
+ if [ -z "${_tmp_result:-}" ]; then
+ _debug "The variable is _tmp_result is not supposed to be empty, there may be something wrong with the script"
+ fi
+
+ _id=$(echo "$_tmp_result" | sed 's/^.*=//')
if [ -n "$_id" ]; then
_debug "Entry found with _id=$_id"
return 0
diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh
index caa4d2c4ff..5829e00e87 100755
--- a/dnsapi/dns_he.sh
+++ b/dnsapi/dns_he.sh
@@ -24,7 +24,7 @@ dns_he_add() {
if [ -z "$HE_Username" ] || [ -z "$HE_Password" ]; then
HE_Username=
HE_Password=
- _err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password envoronment variables."
+ _err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password environment variables."
return 1
fi
_saveaccountconf_mutable HE_Username "$HE_Username"
diff --git a/dnsapi/dns_hetzner.sh b/dnsapi/dns_hetzner.sh
new file mode 100644
index 0000000000..d994d665a6
--- /dev/null
+++ b/dnsapi/dns_hetzner.sh
@@ -0,0 +1,252 @@
+#!/usr/bin/env sh
+
+#
+#HETZNER_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+
+HETZNER_Api="https://dns.hetzner.com/api/v1"
+
+######## Public functions #####################
+
+# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+# Ref: https://dns.hetzner.com/api-docs/
+dns_hetzner_add() {
+ full_domain=$1
+ txt_value=$2
+
+ HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
+
+ if [ -z "$HETZNER_Token" ]; then
+ HETZNER_Token=""
+ _err "You didn't specify a Hetzner api token."
+ _err "You can get yours from here https://dns.hetzner.com/settings/api-token."
+ return 1
+ fi
+
+ #save the api key and email to the account conf file.
+ _saveaccountconf_mutable HETZNER_Token "$HETZNER_Token"
+
+ _debug "First detect the root zone"
+
+ if ! _get_root "$full_domain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting TXT records"
+ if ! _find_record "$_sub_domain" "$txt_value"; then
+ return 1
+ fi
+
+ if [ -z "$_record_id" ]; then
+ _info "Adding record"
+ if _hetzner_rest POST "records" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
+ if _contains "$response" "$txt_value"; then
+ _info "Record added, OK"
+ _sleep 2
+ return 0
+ fi
+ fi
+ _err "Add txt record error${_response_error}"
+ return 1
+ else
+ _info "Found record id: $_record_id."
+ _info "Record found, do nothing."
+ return 0
+ # we could modify a record, if the names for txt records for *.example.com and example.com would be not the same
+ #if _hetzner_rest PUT "records/${_record_id}" "{\"zone_id\":\"${HETZNER_Zone_ID}\",\"type\":\"TXT\",\"name\":\"$full_domain\",\"value\":\"$txt_value\",\"ttl\":120}"; then
+ # if _contains "$response" "$txt_value"; then
+ # _info "Modified, OK"
+ # return 0
+ # fi
+ #fi
+ #_err "Add txt record error (modify)."
+ #return 1
+ fi
+}
+
+# Usage: full_domain txt_value
+# Used to remove the txt record after validation
+dns_hetzner_rm() {
+ full_domain=$1
+ txt_value=$2
+
+ HETZNER_Token="${HETZNER_Token:-$(_readaccountconf_mutable HETZNER_Token)}"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$full_domain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting TXT records"
+ if ! _find_record "$_sub_domain" "$txt_value"; then
+ return 1
+ fi
+
+ if [ -z "$_record_id" ]; then
+ _info "Remove not needed. Record not found."
+ else
+ if ! _hetzner_rest DELETE "records/$_record_id"; then
+ _err "Delete record error${_response_error}"
+ return 1
+ fi
+ _sleep 2
+ _info "Record deleted"
+ fi
+}
+
+#################### Private functions below ##################################
+#returns
+# _record_id=a8d58f22d6931bf830eaa0ec6464bf81 if found; or 1 if error
+_find_record() {
+ unset _record_id
+ _record_name=$1
+ _record_value=$2
+
+ if [ -z "$_record_value" ]; then
+ _record_value='[^"]*'
+ fi
+
+ _debug "Getting all records"
+ _hetzner_rest GET "records?zone_id=${_domain_id}"
+
+ if _response_has_error; then
+ _err "Error${_response_error}"
+ return 1
+ else
+ _record_id=$(
+ echo "$response" \
+ | grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" \
+ | grep "\"value\":\"$_record_value\"" \
+ | while read -r record; do
+ # test for type and
+ if [ -n "$(echo "$record" | _egrep_o '"type":"TXT"')" ]; then
+ echo "$record" | _egrep_o '"id":"[^"]*"' | cut -d : -f 2 | tr -d \"
+ break
+ fi
+ done
+ )
+ fi
+}
+
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+ i=1
+ p=1
+
+ domain_without_acme=$(echo "$domain" | cut -d . -f 2-)
+ domain_param_name=$(echo "HETZNER_Zone_ID_for_${domain_without_acme}" | sed 's/[\.\-]/_/g')
+
+ _debug "Reading zone_id for '$domain_without_acme' from config..."
+ HETZNER_Zone_ID=$(_readdomainconf "$domain_param_name")
+ if [ "$HETZNER_Zone_ID" ]; then
+ _debug "Found, using: $HETZNER_Zone_ID"
+ if ! _hetzner_rest GET "zones/${HETZNER_Zone_ID}"; then
+ _debug "Zone with id '$HETZNER_Zone_ID' not exists."
+ _cleardomainconf "$domain_param_name"
+ unset HETZNER_Zone_ID
+ else
+ if _contains "$response" "\"id\":\"$HETZNER_Zone_ID\""; then
+ _domain=$(printf "%s\n" "$response" | _egrep_o '"name":"[^"]*"' | cut -d : -f 2 | tr -d \" | head -n 1)
+ if [ "$_domain" ]; then
+ _cut_length=$((${#domain} - ${#_domain} - 1))
+ _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cut_length")
+ _domain_id="$HETZNER_Zone_ID"
+ return 0
+ else
+ return 1
+ fi
+ else
+ return 1
+ fi
+ fi
+ fi
+
+ _debug "Trying to get zone id by domain name for '$domain_without_acme'."
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+ _debug h "$h"
+
+ _hetzner_rest GET "zones?name=$h"
+
+ if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_entries":1'; then
+ _domain_id=$(echo "$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
+ HETZNER_Zone_ID=$_domain_id
+ _savedomainconf "$domain_param_name" "$HETZNER_Zone_ID"
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+#returns
+# _response_error
+_response_has_error() {
+ unset _response_error
+
+ err_part="$(echo "$response" | _egrep_o '"error":{[^}]*}')"
+
+ if [ -n "$err_part" ]; then
+ err_code=$(echo "$err_part" | _egrep_o '"code":[0-9]+' | cut -d : -f 2)
+ err_message=$(echo "$err_part" | _egrep_o '"message":"[^"]+"' | cut -d : -f 2 | tr -d \")
+
+ if [ -n "$err_code" ] && [ -n "$err_message" ]; then
+ _response_error=" - message: ${err_message}, code: ${err_code}"
+ return 0
+ fi
+ fi
+
+ return 1
+}
+
+#returns
+# response
+_hetzner_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ key_trimmed=$(echo "$HETZNER_Token" | tr -d \")
+
+ export _H1="Content-TType: application/json"
+ export _H2="Auth-API-Token: $key_trimmed"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$HETZNER_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$HETZNER_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ] || _response_has_error; then
+ _debug "Error$_response_error"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh
index f4590cf829..ba789da923 100755
--- a/dnsapi/dns_inwx.sh
+++ b/dnsapi/dns_inwx.sh
@@ -34,6 +34,10 @@ dns_inwx_add() {
_saveaccountconf_mutable INWX_Password "$INWX_Password"
_saveaccountconf_mutable INWX_Shared_Secret "$INWX_Shared_Secret"
+ if ! _inwx_login; then
+ return 1
+ fi
+
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
@@ -55,6 +59,7 @@ dns_inwx_rm() {
INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
+ INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}"
if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
INWX_User=""
INWX_Password=""
@@ -63,9 +68,9 @@ dns_inwx_rm() {
return 1
fi
- #save the api key and email to the account conf file.
- _saveaccountconf_mutable INWX_User "$INWX_User"
- _saveaccountconf_mutable INWX_Password "$INWX_Password"
+ if ! _inwx_login; then
+ return 1
+ fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
@@ -126,8 +131,42 @@ dns_inwx_rm() {
#################### Private functions below ##################################
+_inwx_check_cookie() {
+ INWX_Cookie="${INWX_Cookie:-$(_readaccountconf_mutable INWX_Cookie)}"
+ if [ -z "$INWX_Cookie" ]; then
+ _debug "No cached cookie found"
+ return 1
+ fi
+ _H1="$INWX_Cookie"
+ export _H1
+
+ xml_content=$(printf '
+
+ account.info
+ ')
+
+ response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+ if _contains "$response" "code1000"; then
+ _debug "Cached cookie still valid"
+ return 0
+ fi
+
+ _debug "Cached cookie no longer valid"
+ _H1=""
+ export _H1
+ INWX_Cookie=""
+ _saveaccountconf_mutable INWX_Cookie "$INWX_Cookie"
+ return 1
+}
+
_inwx_login() {
+ if _inwx_check_cookie; then
+ _debug "Already logged in"
+ return 0
+ fi
+
xml_content=$(printf '
account.login
@@ -151,17 +190,25 @@ _inwx_login() {
- ' $INWX_User $INWX_Password)
+ ' "$INWX_User" "$INWX_Password")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
- _H1=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
+
+ INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')")
+ _H1=$INWX_Cookie
export _H1
+ export INWX_Cookie
+ _saveaccountconf_mutable INWX_Cookie "$INWX_Cookie"
+
+ if ! _contains "$response" "code1000"; then
+ _err "INWX API: Authentication error (username/password correct?)"
+ return 1
+ fi
#https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
- if _contains "$response" "code1000" \
- && _contains "$response" "tfaGOOGLE-AUTH"; then
+ if _contains "$response" "tfaGOOGLE-AUTH"; then
if [ -z "$INWX_Shared_Secret" ]; then
- _err "Mobile TAN detected."
+ _err "INWX API: Mobile TAN detected."
_err "Please define a shared secret."
return 1
fi
@@ -194,6 +241,11 @@ _inwx_login() {
' "$tan")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
+
+ if ! _contains "$response" "code1000"; then
+ _err "INWX API: Mobile TAN not correct."
+ return 1
+ fi
fi
}
@@ -206,11 +258,23 @@ _get_root() {
i=2
p=1
- _inwx_login
-
xml_content='
nameserver.list
+
+
+
+
+
+ pagelimit
+
+ 9999
+
+
+
+
+
+
'
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
diff --git a/dnsapi/dns_joker.sh b/dnsapi/dns_joker.sh
new file mode 100644
index 0000000000..5d50953ed4
--- /dev/null
+++ b/dnsapi/dns_joker.sh
@@ -0,0 +1,129 @@
+#!/usr/bin/env sh
+
+# Joker.com API for acme.sh
+#
+# This script adds the necessary TXT record to a domain in Joker.com.
+#
+# You must activate Dynamic DNS in Joker.com DNS configuration first.
+# Username and password below refer to Dynamic DNS authentication,
+# not your Joker.com login credentials.
+# See: https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html
+#
+# NOTE: This script does not support wildcard certificates, because
+# Joker.com API does not support adding two TXT records with the same
+# subdomain. Adding the second record will overwrite the first one.
+# See: https://joker.com/faq/content/6/496/en/let_s-encrypt-support.html
+# "... this request will replace all TXT records for the specified
+# label by the provided content"
+#
+# Author: aattww (https://github.com/aattww/)
+#
+# Report bugs to https://github.com/acmesh-official/acme.sh/issues/2840
+#
+# JOKER_USERNAME="xxxx"
+# JOKER_PASSWORD="xxxx"
+
+JOKER_API="https://svc.joker.com/nic/replace"
+
+######## Public functions #####################
+
+#Usage: dns_joker_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_joker_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}"
+ JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}"
+
+ if [ -z "$JOKER_USERNAME" ] || [ -z "$JOKER_PASSWORD" ]; then
+ _err "No Joker.com username and password specified."
+ return 1
+ fi
+
+ _saveaccountconf_mutable JOKER_USERNAME "$JOKER_USERNAME"
+ _saveaccountconf_mutable JOKER_PASSWORD "$JOKER_PASSWORD"
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _info "Adding TXT record"
+ if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=$txtvalue"; then
+ if _startswith "$response" "OK"; then
+ _info "Added, OK"
+ return 0
+ fi
+ fi
+ _err "Error adding TXT record."
+ return 1
+}
+
+#fulldomain txtvalue
+dns_joker_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}"
+ JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}"
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _info "Removing TXT record"
+ # TXT record is removed by setting its value to empty.
+ if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value="; then
+ if _startswith "$response" "OK"; then
+ _info "Removed, OK"
+ return 0
+ fi
+ fi
+ _err "Error removing TXT record."
+ return 1
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ fulldomain=$1
+ i=1
+ while true; do
+ h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ # Try to remove a test record. With correct root domain, username and password this will return "OK: ..." regardless
+ # of record in question existing or not.
+ if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$h&label=jokerTXTUpdateTest&type=TXT&value="; then
+ if _startswith "$response" "OK"; then
+ _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
+ _domain=$h
+ return 0
+ fi
+ fi
+
+ i=$(_math "$i" + 1)
+ done
+
+ _debug "Root domain not found"
+ return 1
+}
+
+_joker_rest() {
+ data="$1"
+ _debug data "$data"
+
+ if ! response="$(_post "$data" "$JOKER_API" "" "POST")"; then
+ _err "Error POSTing"
+ return 1
+ fi
+ _debug response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_kas.sh b/dnsapi/dns_kas.sh
new file mode 100755
index 0000000000..2cb0b439e2
--- /dev/null
+++ b/dnsapi/dns_kas.sh
@@ -0,0 +1,168 @@
+#!/usr/bin/env sh
+########################################################################
+# All-inkl Kasserver hook script for acme.sh
+#
+# Environment variables:
+#
+# - $KAS_Login (Kasserver API login name)
+# - $KAS_Authtype (Kasserver API auth type. Default: sha1)
+# - $KAS_Authdata (Kasserver API auth data.)
+#
+# Author: Martin Kammerlander, Phlegx Systems OG
+# Updated by: Marc-Oliver Lange
+# Credits: Inspired by dns_he.sh. Thanks a lot man!
+# Git repo: https://github.com/phlegx/acme.sh
+# TODO: Better Error handling
+########################################################################
+KAS_Api="https://kasapi.kasserver.com/dokumentation/formular.php"
+######## Public functions #####################
+dns_kas_add() {
+ _fulldomain=$1
+ _txtvalue=$2
+ _info "Using DNS-01 All-inkl/Kasserver hook"
+ _info "Adding $_fulldomain DNS TXT entry on All-inkl/Kasserver"
+ _info "Check and Save Props"
+ _check_and_save
+ _info "Checking Zone and Record_Name"
+ _get_zone_and_record_name "$_fulldomain"
+ _info "Getting Record ID"
+ _get_record_id
+
+ _info "Creating TXT DNS record"
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&var1=record_name"
+ params="$params&wert1=$_record_name"
+ params="$params&var2=record_type"
+ params="$params&wert2=TXT"
+ params="$params&var3=record_data"
+ params="$params&wert3=$_txtvalue"
+ params="$params&var4=record_aux"
+ params="$params&wert4=0"
+ params="$params&kas_action=add_dns_settings"
+ params="$params&var5=zone_host"
+ params="$params&wert5=$_zone"
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params")"
+ _debug2 "response" "$response"
+
+ if ! _contains "$response" "TRUE"; then
+ _err "An unkown error occurred, please check manually."
+ return 1
+ fi
+ return 0
+}
+
+dns_kas_rm() {
+ _fulldomain=$1
+ _txtvalue=$2
+ _info "Using DNS-01 All-inkl/Kasserver hook"
+ _info "Cleaning up after All-inkl/Kasserver hook"
+ _info "Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
+
+ _info "Check and Save Props"
+ _check_and_save
+ _info "Checking Zone and Record_Name"
+ _get_zone_and_record_name "$_fulldomain"
+ _info "Getting Record ID"
+ _get_record_id
+
+ # If there is a record_id, delete the entry
+ if [ -n "$_record_id" ]; then
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&kas_action=delete_dns_settings"
+
+ for i in $_record_id; do
+ params2="$params&var1=record_id"
+ params2="$params2&wert1=$i"
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params2")"
+ _debug2 "response" "$response"
+ if ! _contains "$response" "TRUE"; then
+ _err "Either the txt record is not found or another error occurred, please check manually."
+ return 1
+ fi
+ done
+ else # Cannot delete or unkown error
+ _err "No record_id found that can be deleted. Please check manually."
+ return 1
+ fi
+ return 0
+}
+
+########################## PRIVATE FUNCTIONS ###########################
+
+# Checks for the ENV variables and saves them
+_check_and_save() {
+ KAS_Login="${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}"
+ KAS_Authtype="${KAS_Authtype:-$(_readaccountconf_mutable KAS_Authtype)}"
+ KAS_Authdata="${KAS_Authdata:-$(_readaccountconf_mutable KAS_Authdata)}"
+
+ if [ -z "$KAS_Login" ] || [ -z "$KAS_Authtype" ] || [ -z "$KAS_Authdata" ]; then
+ KAS_Login=
+ KAS_Authtype=
+ KAS_Authdata=
+ _err "No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
+ return 1
+ fi
+ _saveaccountconf_mutable KAS_Login "$KAS_Login"
+ _saveaccountconf_mutable KAS_Authtype "$KAS_Authtype"
+ _saveaccountconf_mutable KAS_Authdata "$KAS_Authdata"
+ return 0
+}
+
+# Gets back the base domain/zone and record name.
+# See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
+_get_zone_and_record_name() {
+ params="?kas_login=$KAS_Login"
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&kas_action=get_domains"
+
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params")"
+ _debug2 "response" "$response"
+ _zonen="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "domain_name" | tr '<' '\n' | grep "domain_name" | sed "s/domain_name>=>//g")"
+ _domain="$1"
+ _temp_domain="$(echo "$1" | sed 's/\.$//')"
+ _rootzone="$_domain"
+ for i in $_zonen; do
+ l1=${#_rootzone}
+ l2=${#i}
+ if _endswith "$_domain" "$i" && [ "$l1" -ge "$l2" ]; then
+ _rootzone="$i"
+ fi
+ done
+ _zone="${_rootzone}."
+ _temp_record_name="$(echo "$_temp_domain" | sed "s/$_rootzone//g")"
+ _record_name="$(echo "$_temp_record_name" | sed 's/\.$//')"
+ _debug2 "Zone:" "$_zone"
+ _debug2 "Domain:" "$_domain"
+ _debug2 "Record_Name:" "$_record_name"
+ return 0
+}
+
+# Retrieve the DNS record ID
+_get_record_id() {
+ params="?kas_login=$KAS_Login"
+ params="$params&kas_auth_type=$KAS_Authtype"
+ params="$params&kas_auth_data=$KAS_Authdata"
+ params="$params&kas_action=get_dns_settings"
+ params="$params&var1=zone_host"
+ params="$params&wert1=$_zone"
+
+ _debug2 "Wait for 10 seconds by default before calling KAS API."
+ _sleep 10
+ response="$(_get "$KAS_Api$params")"
+ _debug2 "response" "$response"
+ _record_id="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "=>$_record_name<" | grep '>TXT<' | tr '<' '\n' | grep record_id | sed "s/record_id>=>//g")"
+ _debug2 _record_id "$_record_id"
+ return 0
+}
diff --git a/dnsapi/dns_leaseweb.sh b/dnsapi/dns_leaseweb.sh
new file mode 100644
index 0000000000..a1d9e7491d
--- /dev/null
+++ b/dnsapi/dns_leaseweb.sh
@@ -0,0 +1,149 @@
+#!/usr/bin/env sh
+
+#Author: Rolph Haspers
+#Utilize leaseweb.com API to finish dns-01 verifications.
+#Requires a Leaseweb API Key (export LSW_Key="Your Key")
+#See http://developer.leaseweb.com for more information.
+######## Public functions #####################
+
+LSW_API="https://api.leaseweb.com/hosting/v2/domains/"
+
+#Usage: dns_leaseweb_add _acme-challenge.www.domain.com
+dns_leaseweb_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
+ if [ -z "$LSW_Key" ]; then
+ LSW_Key=""
+ _err "You don't specify Leaseweb api key yet."
+ _err "Please create your key and try again."
+ return 1
+ fi
+
+ #save the api key to the account conf file.
+ _saveaccountconf_mutable LSW_Key "$LSW_Key"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _root_domain "$_domain"
+ _debug _domain "$fulldomain"
+
+ if _lsw_api "POST" "$_domain" "$fulldomain" "$txtvalue"; then
+ if [ "$_code" = "201" ]; then
+ _info "Added, OK"
+ return 0
+ else
+ _err "Add txt record error, invalid code. Code: $_code"
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+
+ return 1
+}
+
+#Usage: fulldomain txtvalue
+#Remove the txt record after validation.
+dns_leaseweb_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ LSW_Key="${LSW_Key:-$(_readaccountconf_mutable LSW_Key)}"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _root_domain "$_domain"
+ _debug _domain "$fulldomain"
+
+ if _lsw_api "DELETE" "$_domain" "$fulldomain" "$txtvalue"; then
+ if [ "$_code" = "204" ]; then
+ _info "Deleted, OK"
+ return 0
+ else
+ _err "Delete txt record error."
+ return 1
+ fi
+ fi
+ _err "Delete txt record error."
+
+ return 1
+}
+
+#################### Private functions below ##################################
+# _acme-challenge.www.domain.com
+# returns
+# _domain=domain.com
+_get_root() {
+ rdomain=$1
+ i="$(echo "$rdomain" | tr '.' ' ' | wc -w)"
+ i=$(_math "$i" - 1)
+
+ while true; do
+ h=$(printf "%s" "$rdomain" | cut -d . -f "$i"-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1 #not valid domain
+ fi
+
+ #Check API if domain exists
+ if _lsw_api "GET" "$h"; then
+ if [ "$_code" = "200" ]; then
+ _domain="$h"
+ return 0
+ fi
+ fi
+ i=$(_math "$i" - 1)
+ if [ "$i" -lt 2 ]; then
+ return 1 #not found, no need to check _acme-challenge.sub.domain in leaseweb api.
+ fi
+ done
+
+ return 1
+}
+
+_lsw_api() {
+ cmd=$1
+ d=$2
+ fd=$3
+ tvalue=$4
+
+ # Construct the HTTP Authorization header
+ export _H2="Content-Type: application/json"
+ export _H1="X-Lsw-Auth: ${LSW_Key}"
+
+ if [ "$cmd" = "GET" ]; then
+ response="$(_get "$LSW_API/$d")"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ _debug "http response code $_code"
+ _debug response "$response"
+ return 0
+ fi
+
+ if [ "$cmd" = "POST" ]; then
+ data="{\"name\": \"$fd.\",\"type\": \"TXT\",\"content\": [\"$tvalue\"],\"ttl\": 60}"
+ response="$(_post "$data" "$LSW_API/$d/resourceRecordSets" "$data" "POST")"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ _debug "http response code $_code"
+ _debug response "$response"
+ return 0
+ fi
+
+ if [ "$cmd" = "DELETE" ]; then
+ response="$(_post "" "$LSW_API/$d/resourceRecordSets/$fd/TXT" "" "DELETE")"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ _debug "http response code $_code"
+ _debug response "$response"
+ return 0
+ fi
+
+ return 1
+}
diff --git a/dnsapi/dns_lexicon.sh b/dnsapi/dns_lexicon.sh
index f6f544645b..44bfa73526 100755
--- a/dnsapi/dns_lexicon.sh
+++ b/dnsapi/dns_lexicon.sh
@@ -5,7 +5,7 @@
# https://github.com/AnalogJ/lexicon
lexicon_cmd="lexicon"
-wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api"
+wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-lexicon-dns-api"
_lexicon_init() {
if ! _exists "$lexicon_cmd"; then
@@ -63,6 +63,16 @@ _lexicon_init() {
_saveaccountconf_mutable "$Lx_domaintoken" "$Lx_domaintoken_v"
eval export "$Lx_domaintoken"
fi
+
+ # shellcheck disable=SC2018,SC2019
+ Lx_api_key=$(echo LEXICON_"${PROVIDER}"_API_KEY | tr 'a-z' 'A-Z')
+ eval "$Lx_api_key=\${$Lx_api_key:-$(_readaccountconf_mutable "$Lx_api_key")}"
+ Lx_api_key_v=$(eval echo \$"$Lx_api_key")
+ _secure_debug "$Lx_api_key" "$Lx_api_key_v"
+ if [ "$Lx_api_key_v" ]; then
+ _saveaccountconf_mutable "$Lx_api_key" "$Lx_api_key_v"
+ eval export "$Lx_api_key"
+ fi
}
######## Public functions #####################
@@ -82,7 +92,7 @@ dns_lexicon_add() {
_savedomainconf LEXICON_OPTS "$LEXICON_OPTS"
# shellcheck disable=SC2086
- $lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
+ $lexicon_cmd "$PROVIDER" $LEXICON_OPTS create "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" --output QUIET
}
@@ -98,6 +108,6 @@ dns_lexicon_rm() {
domain=$(printf "%s" "$fulldomain" | cut -d . -f 2-999)
# shellcheck disable=SC2086
- $lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}"
+ $lexicon_cmd "$PROVIDER" $LEXICON_OPTS delete "${domain}" TXT --name="_acme-challenge.${domain}." --content="${txtvalue}" --output QUIET
}
diff --git a/dnsapi/dns_linode_v4.sh b/dnsapi/dns_linode_v4.sh
index ee7ee89244..c2bebc570e 100755
--- a/dnsapi/dns_linode_v4.sh
+++ b/dnsapi/dns_linode_v4.sh
@@ -36,7 +36,7 @@ dns_linode_v4_add() {
}"
if _rest POST "/$_domain_id/records" "$_payload" && [ -n "$response" ]; then
- _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
+ _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"id\": *[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1)
_debug _resource_id "$_resource_id"
if [ -z "$_resource_id" ]; then
@@ -74,9 +74,9 @@ dns_linode_v4_rm() {
if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then
response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")"
- resource="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$_sub_domain\".*}")"
+ resource="$(echo "$response" | _egrep_o "\{.*\"name\": *\"$_sub_domain\".*}")"
if [ "$resource" ]; then
- _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
+ _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ )
if [ "$_resource_id" ]; then
_debug _resource_id "$_resource_id"
@@ -139,9 +139,9 @@ _get_root() {
return 1
fi
- hostedzone="$(echo "$response" | _egrep_o "{.*\"domain\":\s*\"$h\".*}")"
+ hostedzone="$(echo "$response" | _egrep_o "\{.*\"domain\": *\"$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 \ )
+ _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\": *[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
diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh
index 1316a27413..7760b53e61 100644
--- a/dnsapi/dns_loopia.sh
+++ b/dnsapi/dns_loopia.sh
@@ -217,7 +217,7 @@ _loopia_add_record() {
ttl
- 60
+ 300
rdata
diff --git a/dnsapi/dns_me.sh b/dnsapi/dns_me.sh
index 382eeedd82..490074022e 100644
--- a/dnsapi/dns_me.sh
+++ b/dnsapi/dns_me.sh
@@ -2,7 +2,7 @@
# bug reports to dev@1e.ca
-# ME_Key=qmlkdjflmkqdjf
+# ME_Key=qmlkdjflmkqdjf
# ME_Secret=qmsdlkqmlksdvnnpae
ME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed
@@ -114,7 +114,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 | tr -d '}')
+ _domain_id=$(printf "%s\n" "$response" | sed 's/^{//; s/}$//; s/{.*}//' | sed -r 's/^.*"id":([0-9]+).*$/\1/')
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
diff --git a/dnsapi/dns_miab.sh b/dnsapi/dns_miab.sh
new file mode 100644
index 0000000000..7e697704a8
--- /dev/null
+++ b/dnsapi/dns_miab.sh
@@ -0,0 +1,210 @@
+#!/usr/bin/env sh
+
+# Name: dns_miab.sh
+#
+# Authors:
+# Darven Dissek 2018
+# William Gertz 2019
+#
+# Thanks to Neil Pang and other developers here for code reused from acme.sh from DNS-01
+# used to communicate with the MailinaBox Custom DNS API
+# Report Bugs here:
+# https://github.com/billgertz/MIAB_dns_api (for dns_miab.sh)
+# https://github.com/acmesh-official/acme.sh (for acme.sh)
+#
+######## Public functions #####################
+
+#Usage: dns_miab_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_miab_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _info "Using miab challange add"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ #retrieve MIAB environemt vars
+ if ! _retrieve_miab_env; then
+ return 1
+ fi
+
+ #check domain and seperate into doamin and host
+ if ! _get_root "$fulldomain"; then
+ _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
+ return 1
+ fi
+
+ _debug2 _sub_domain "$_sub_domain"
+ _debug2 _domain "$_domain"
+
+ #add the challenge record
+ _api_path="custom/${fulldomain}/txt"
+ _miab_rest "$txtvalue" "$_api_path" "POST"
+
+ #check if result was good
+ if _contains "$response" "updated DNS"; then
+ _info "Successfully created the txt record"
+ return 0
+ else
+ _err "Error encountered during record add"
+ _err "$response"
+ return 1
+ fi
+}
+
+#Usage: dns_miab_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_miab_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Using miab challage delete"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ #retrieve MIAB environemt vars
+ if ! _retrieve_miab_env; then
+ return 1
+ fi
+
+ #check domain and seperate into doamin and host
+ if ! _get_root "$fulldomain"; then
+ _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}"
+ return 1
+ fi
+
+ _debug2 _sub_domain "$_sub_domain"
+ _debug2 _domain "$_domain"
+
+ #Remove the challenge record
+ _api_path="custom/${fulldomain}/txt"
+ _miab_rest "$txtvalue" "$_api_path" "DELETE"
+
+ #check if result was good
+ if _contains "$response" "updated DNS"; then
+ _info "Successfully removed the txt record"
+ return 0
+ else
+ _err "Error encountered during record remove"
+ _err "$response"
+ return 1
+ fi
+}
+
+#################### Private functions below ##################################
+#
+#Usage: _get_root _acme-challenge.www.domain.com
+#Returns:
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ _passed_domain=$1
+ _debug _passed_domain "$_passed_domain"
+ _i=2
+ _p=1
+
+ #get the zones hosed on MIAB server, must be a json stream
+ _miab_rest "" "zones" "GET"
+
+ if ! _is_json "$response"; then
+ _err "ERROR fetching domain list"
+ _err "$response"
+ return 1
+ fi
+
+ #cycle through the passed domain seperating out a test domain discarding
+ # the subdomain by marching thorugh the dots
+ while true; do
+ _test_domain=$(printf "%s" "$_passed_domain" | cut -d . -f ${_i}-100)
+ _debug _test_domain "$_test_domain"
+
+ if [ -z "$_test_domain" ]; then
+ return 1
+ fi
+
+ #report found if the test domain is in the json response and
+ # report the subdomain
+ if _contains "$response" "\"$_test_domain\""; then
+ _sub_domain=$(printf "%s" "$_passed_domain" | cut -d . -f 1-${_p})
+ _domain=${_test_domain}
+ return 0
+ fi
+
+ #cycle to the next dot in the passed domain
+ _p=${_i}
+ _i=$(_math "$_i" + 1)
+ done
+
+ return 1
+}
+
+#Usage: _retrieve_miab_env
+#Returns (from store or environment variables):
+# MIAB_Username
+# MIAB_Password
+# MIAB_Server
+#retrieve MIAB environment variables, report errors and quit if problems
+_retrieve_miab_env() {
+ MIAB_Username="${MIAB_Username:-$(_readaccountconf_mutable MIAB_Username)}"
+ MIAB_Password="${MIAB_Password:-$(_readaccountconf_mutable MIAB_Password)}"
+ MIAB_Server="${MIAB_Server:-$(_readaccountconf_mutable MIAB_Server)}"
+
+ #debug log the environmental variables
+ _debug MIAB_Username "$MIAB_Username"
+ _debug MIAB_Password "$MIAB_Password"
+ _debug MIAB_Server "$MIAB_Server"
+
+ #check if MIAB environemt vars set and quit if not
+ if [ -z "$MIAB_Username" ] || [ -z "$MIAB_Password" ] || [ -z "$MIAB_Server" ]; then
+ _err "You didn't specify one or more of MIAB_Username, MIAB_Password or MIAB_Server."
+ _err "Please check these environment variables and try again."
+ return 1
+ fi
+
+ #save the credentials to the account conf file.
+ _saveaccountconf_mutable MIAB_Username "$MIAB_Username"
+ _saveaccountconf_mutable MIAB_Password "$MIAB_Password"
+ _saveaccountconf_mutable MIAB_Server "$MIAB_Server"
+}
+
+#Useage: _miab_rest "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" "custom/_acme-challenge.www.domain.com/txt "POST"
+#Returns: "updated DNS: domain.com"
+#rest interface MIAB dns
+_miab_rest() {
+ _data="$1"
+ _api_path="$2"
+ _httpmethod="$3"
+
+ #encode username and password for basic authentication
+ _credentials="$(printf "%s" "$MIAB_Username:$MIAB_Password" | _base64)"
+ export _H1="Authorization: Basic $_credentials"
+ _url="https://${MIAB_Server}/admin/dns/${_api_path}"
+
+ _debug2 _data "$_data"
+ _debug _api_path "$_api_path"
+ _debug2 _url "$_url"
+ _debug2 _credentails "$_credentials"
+ _debug _httpmethod "$_httpmethod"
+
+ if [ "$_httpmethod" = "GET" ]; then
+ response="$(_get "$_url")"
+ else
+ response="$(_post "$_data" "$_url" "" "$_httpmethod")"
+ fi
+
+ _retcode="$?"
+
+ if [ "$_retcode" != "0" ]; then
+ _err "MIAB REST authentication failed on $_httpmethod"
+ return 1
+ fi
+
+ _debug response "$response"
+ return 0
+}
+
+#Usage: _is_json "\[\n "mydomain.com"\n]"
+#Reurns "\[\n "mydomain.com"\n]"
+#returns the string if it begins and ends with square braces
+_is_json() {
+ _str="$(echo "$1" | _normalizeJson)"
+ echo "$_str" | grep '^\[.*\]$' >/dev/null 2>&1
+}
diff --git a/dnsapi/dns_misaka.sh b/dnsapi/dns_misaka.sh
new file mode 100755
index 0000000000..eed4170e1e
--- /dev/null
+++ b/dnsapi/dns_misaka.sh
@@ -0,0 +1,159 @@
+#!/usr/bin/env sh
+
+# bug reports to support+acmesh@misaka.io
+# based on dns_nsone.sh by dev@1e.ca
+
+#
+#Misaka_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+
+Misaka_Api="https://dnsapi.misaka.io/dns"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_misaka_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ if [ -z "$Misaka_Key" ]; then
+ Misaka_Key=""
+ _err "You didn't specify misaka.io dns api key yet."
+ _err "Please create you key and try again."
+ return 1
+ fi
+
+ #save the api key and email to the account conf file.
+ _saveaccountconf Misaka_Key "$Misaka_Key"
+
+ _debug "checking root zone [$fulldomain]"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting txt records"
+ _misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}"
+
+ if ! _contains "$response" "\"results\":"; then
+ _err "Error"
+ return 1
+ fi
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Adding record"
+
+ if _misaka_rest PUT "zones/${_domain}/recordsets/${_sub_domain}/TXT" "{\"records\":[{\"value\":\"\\\"$txtvalue\\\"\"}],\"filters\":[],\"ttl\":1}"; then
+ _debug response "$response"
+ if _contains "$response" "$_sub_domain"; then
+ _info "Added"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ else
+ _info "Updating record"
+
+ _misaka_rest POST "zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true" "{\"records\": [{\"value\": \"\\\"$txtvalue\\\"\"}],\"ttl\":1}"
+ if [ "$?" = "0" ] && _contains "$response" "$_sub_domain"; then
+ _info "Updated!"
+ #todo: check if the record takes effect
+ return 0
+ fi
+ _err "Update error"
+ return 1
+ fi
+
+}
+
+#fulldomain
+dns_misaka_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting txt records"
+ _misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}"
+
+ count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ")
+ _debug count "$count"
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ if ! _misaka_rest DELETE "zones/${_domain}/recordsets/${_sub_domain}/TXT"; then
+ _err "Delete record error."
+ return 1
+ fi
+ _contains "$response" ""
+ fi
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ if ! _misaka_rest GET "zones?limit=1000"; then
+ return 1
+ fi
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\""; then
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_misaka_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Content-Type: application/json"
+ export _H2="User-Agent: acme.sh/$VER misaka-dns-acmesh/20191213"
+ export _H3="Authorization: Token $Misaka_Key"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$Misaka_Api/$ep" "" "$m")"
+ else
+ response="$(_get "$Misaka_Api/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_myapi.sh b/dnsapi/dns_myapi.sh
index 2451d193d3..7f3c5a863f 100755
--- a/dnsapi/dns_myapi.sh
+++ b/dnsapi/dns_myapi.sh
@@ -7,11 +7,11 @@
#returns 0 means success, otherwise error.
#
#Author: Neilpang
-#Report Bugs here: https://github.com/Neilpang/acme.sh
+#Report Bugs here: https://github.com/acmesh-official/acme.sh
#
######## Public functions #####################
-# Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
+# Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_myapi_add() {
diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh
index a82e12d7cc..2e389265be 100755
--- a/dnsapi/dns_namecheap.sh
+++ b/dnsapi/dns_namecheap.sh
@@ -3,10 +3,10 @@
# Namecheap API
# https://www.namecheap.com/support/api/intro.aspx
#
-# Requires Namecheap API key set in
-#NAMECHEAP_API_KEY,
+# Requires Namecheap API key set in
+#NAMECHEAP_API_KEY,
#NAMECHEAP_USERNAME,
-#NAMECHEAP_SOURCEIP
+#NAMECHEAP_SOURCEIP
# Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
######## Public functions #####################
diff --git a/dnsapi/dns_nic.sh b/dnsapi/dns_nic.sh
new file mode 100644
index 0000000000..5052ee1056
--- /dev/null
+++ b/dnsapi/dns_nic.sh
@@ -0,0 +1,205 @@
+#!/usr/bin/env sh
+
+#
+#NIC_ClientID='0dc0xxxxxxxxxxxxxxxxxxxxxxxxce88'
+#NIC_ClientSecret='3LTtxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxnuW8'
+#NIC_Username="000000/NIC-D"
+#NIC_Password="xxxxxxx"
+
+NIC_Api="https://api.nic.ru"
+
+dns_nic_add() {
+ fulldomain="${1}"
+ txtvalue="${2}"
+
+ if ! _nic_get_authtoken save; then
+ _err "get NIC auth token failed"
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ _debug _service "$_service"
+
+ _info "Adding record"
+ if ! _nic_rest PUT "services/$_service/zones/$_domain/records" "$_sub_domainTXT$txtvalue"; then
+ _err "Add TXT record error"
+ return 1
+ fi
+
+ if ! _nic_rest POST "services/$_service/zones/$_domain/commit" ""; then
+ return 1
+ fi
+ _info "Added, OK"
+}
+
+dns_nic_rm() {
+ fulldomain="${1}"
+ txtvalue="${2}"
+
+ if ! _nic_get_authtoken; then
+ _err "get NIC auth token failed"
+ return 1
+ fi
+
+ if ! _get_root "$fulldomain"; then
+ _err "Invalid domain"
+ return 1
+ fi
+
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+ _debug _service "$_service"
+
+ if ! _nic_rest GET "services/$_service/zones/$_domain/records"; then
+ _err "Get records error"
+ return 1
+ fi
+
+ _domain_id=$(printf "%s" "$response" | grep "$_sub_domain" | grep -- "$txtvalue" | sed -r "s/.*"; then
+ error=$(printf "%s" "$response" | grep "error code" | sed -r "s/.*(.*)<\/error>/\1/g")
+ _err "Error: $error"
+ return 1
+ fi
+
+ if ! _contains "$response" "success"; then
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_njalla.sh b/dnsapi/dns_njalla.sh
new file mode 100644
index 0000000000..e924328860
--- /dev/null
+++ b/dnsapi/dns_njalla.sh
@@ -0,0 +1,168 @@
+#!/usr/bin/env sh
+
+#
+#NJALLA_Token="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+NJALLA_Api="https://njal.la/api/1/"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_njalla_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NJALLA_Token="${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}"
+
+ if [ "$NJALLA_Token" ]; then
+ _saveaccountconf_mutable NJALLA_Token "$NJALLA_Token"
+ else
+ NJALLA_Token=""
+ _err "You didn't specify a Njalla api token yet."
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
+ # we can not use updating anymore.
+ # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+ # _debug count "$count"
+ # if [ "$count" = "0" ]; then
+ _info "Adding record"
+ if _njalla_rest "{\"method\":\"add-record\",\"params\":{\"domain\":\"$_domain\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}}"; then
+ if _contains "$response" "$txtvalue"; then
+ _info "Added, OK"
+ return 0
+ else
+ _err "Add txt record error."
+ return 1
+ fi
+ fi
+ _err "Add txt record error."
+ return 1
+
+}
+
+#fulldomain txtvalue
+dns_njalla_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NJALLA_Token="${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}"
+
+ if [ "$NJALLA_Token" ]; then
+ _saveaccountconf_mutable NJALLA_Token "$NJALLA_Token"
+ else
+ NJALLA_Token=""
+ _err "You didn't specify a Njalla api token yet."
+ return 1
+ fi
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug "Getting records for domain"
+ if ! _njalla_rest "{\"method\":\"list-records\",\"params\":{\"domain\":\"${_domain}\"}}"; then
+ return 1
+ fi
+
+ if ! echo "$response" | tr -d " " | grep "\"id\":" >/dev/null; then
+ _err "Error: $response"
+ return 1
+ fi
+
+ records=$(echo "$response" | _egrep_o "\"records\":\s?\[(.*)\]\}" | _egrep_o "\[.*\]" | _egrep_o "\{[^\{\}]*\"id\":[^\{\}]*\}")
+ count=$(echo "$records" | wc -l)
+ _debug count "$count"
+
+ if [ "$count" = "0" ]; then
+ _info "Don't need to remove."
+ else
+ echo "$records" | while read -r record; do
+ record_name=$(echo "$record" | _egrep_o "\"name\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \")
+ record_content=$(echo "$record" | _egrep_o "\"content\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \")
+ record_id=$(echo "$record" | _egrep_o "\"id\":\s?[0-9]+" | cut -d : -f 2 | tr -d " " | tr -d \")
+ if [ "$_sub_domain" = "$record_name" ]; then
+ if [ "$txtvalue" = "$record_content" ]; then
+ _debug "record_id" "$record_id"
+ if ! _njalla_rest "{\"method\":\"remove-record\",\"params\":{\"domain\":\"${_domain}\",\"id\":${record_id}}}"; then
+ _err "Delete record error."
+ return 1
+ fi
+ echo "$response" | tr -d " " | grep "\"result\"" >/dev/null
+ fi
+ fi
+ done
+ fi
+
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+ domain=$1
+ i=1
+ 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
+ fi
+
+ if ! _njalla_rest "{\"method\":\"get-domain\",\"params\":{\"domain\":\"${h}\"}}"; then
+ return 1
+ fi
+
+ if _contains "$response" "\"$h\""; then
+ _domain_returned=$(echo "$response" | _egrep_o "\{\"name\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+ if [ "$_domain_returned" ]; 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
+ return 1
+}
+
+_njalla_rest() {
+ data="$1"
+
+ token_trimmed=$(echo "$NJALLA_Token" | tr -d '"')
+
+ export _H1="Content-Type: application/json"
+ export _H2="Accept: application/json"
+ export _H3="Authorization: Njalla $token_trimmed"
+
+ _debug data "$data"
+ response="$(_post "$data" "$NJALLA_Api" "" "POST")"
+
+ if [ "$?" != "0" ]; then
+ _err "error $data"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_nm.sh b/dnsapi/dns_nm.sh
new file mode 100644
index 0000000000..4dfcc777a2
--- /dev/null
+++ b/dnsapi/dns_nm.sh
@@ -0,0 +1,88 @@
+#!/usr/bin/env sh
+
+########################################################################
+# https://namemaster.de hook script for acme.sh
+#
+# Environment variables:
+#
+# - $NM_user (your namemaster.de API username)
+# - $NM_sha256 (your namemaster.de API password_as_sha256hash)
+#
+# Author: Thilo Gass
+# Git repo: https://github.com/ThiloGa/acme.sh
+
+#-- dns_nm_add() - Add TXT record --------------------------------------
+# Usage: dns_nm_add _acme-challenge.subdomain.domain.com "XyZ123..."
+
+namemaster_api="https://namemaster.de/api/api.php"
+
+dns_nm_add() {
+ fulldomain=$1
+ txt_value=$2
+ _info "Using DNS-01 namemaster hook"
+
+ NM_user="${NM_user:-$(_readaccountconf_mutable NM_user)}"
+ NM_sha256="${NM_sha256:-$(_readaccountconf_mutable NM_sha256)}"
+ if [ -z "$NM_user" ] || [ -z "$NM_sha256" ]; then
+ NM_user=""
+ NM_sha256=""
+ _err "No auth details provided. Please set user credentials using the \$NM_user and \$NM_sha256 environment variables."
+ return 1
+ fi
+ #save the api user and sha256 password to the account conf file.
+ _debug "Save user and hash"
+ _saveaccountconf_mutable NM_user "$NM_user"
+ _saveaccountconf_mutable NM_sha256 "$NM_sha256"
+
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain" "$fulldomain"
+ return 1
+ fi
+
+ _info "die Zone lautet:" "$zone"
+
+ get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Antwort=csv&Typ=ACME&zone=$zone&hostname=$fulldomain&TXT=$txt_value&Action=Auto&Lifetime=3600"
+
+ if ! erg="$(_get "$get")"; then
+ _err "error Adding $fulldomain TXT: $txt_value"
+ return 1
+ fi
+
+ if _contains "$erg" "Success"; then
+ _info "Success, TXT Added, OK"
+ else
+ _err "error Adding $fulldomain TXT: $txt_value erg: $erg"
+ return 1
+ fi
+
+ _debug "ok Auto $fulldomain TXT: $txt_value erg: $erg"
+ return 0
+}
+
+dns_nm_rm() {
+
+ fulldomain=$1
+ txtvalue=$2
+ _info "TXT enrty in $fulldomain is deleted automatically"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+}
+
+_get_root() {
+
+ domain=$1
+
+ get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Typ=acme&hostname=$domain&Action=getzone&antwort=csv"
+
+ if ! zone="$(_get "$get")"; then
+ _err "error getting Zone"
+ return 1
+ else
+ if _contains "$zone" "hostname not found"; then
+ return 1
+ fi
+ fi
+
+}
diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh
index dfb3672afa..cd4b71409c 100755
--- a/dnsapi/dns_nsupdate.sh
+++ b/dnsapi/dns_nsupdate.sh
@@ -27,7 +27,7 @@ dns_nsupdate_add() {
[ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D"
if [ -z "${NSUPDATE_ZONE}" ]; then
nsupdate -k "${NSUPDATE_KEY}" $nsdebug < \"$subdomain.$maindomain\"")'")"
+ _dns_one_addrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
- id=$(echo "$response" | sed -n "s/{\"result\":{\"data\":{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}}},\"metadata\":null}/\1/p")
+ _info "Not valid yet, let's wait 1 hour to take effect."
+ _sleep 3600
+ fi
+ fi
+ #Check if the TXT exists
+ _dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
+ if [ -n "$id" ]; then
+ _info "$(__green "Txt record with the same value found. Skip adding.")"
+ return 0
+ fi
+
+ _dns_one_addrecord "TXT" "$subdomain" "$txtvalue"
if [ -z "$id" ]; then
- _err "Add txt record error."
+ _err "Add TXT record error."
return 1
else
- _info "Added, OK ($id)"
+ _info "$(__green "Added, OK ($id)")"
return 0
fi
-
}
dns_one_rm() {
@@ -73,36 +89,45 @@ dns_one_rm() {
return 1
fi
- mysubdomain=$_sub_domain
- mydomain=$_domain
- _debug mysubdomain "$mysubdomain"
- _debug mydomain "$mydomain"
+ subdomain="${_sub_domain}"
+ maindomain=${_domain}
- # get entries
- response="$(_get "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records")"
- response="$(echo "$response" | _normalizeJson)"
- _debug response "$response"
+ useProxy=0
+ if [ "${_sub_domain}" = "_acme-challenge" ]; then
+ subdomain="proxy${_sub_domain}"
+ useProxy=1
+ fi
- id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$mysubdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ _debug subdomain "$subdomain"
+ _debug maindomain "$maindomain"
+ if [ $useProxy -eq 1 ]; then
+ if [ "$ONECOM_KeepCnameProxy" = "1" ]; then
+ _info "$(__red "Keeping CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
+ else
+ #Check if the CNAME exists
+ _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
+ if [ -n "$id" ]; then
+ _info "$(__red "Removing CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
+ _dns_one_delrecord "$id"
+ fi
+ fi
+ fi
+ #Check if the TXT exists
+ _dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
if [ -z "$id" ]; then
_err "Txt record not found."
return 1
fi
# delete entry
- response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records/$id" "" "DELETE" "application/json")"
- response="$(echo "$response" | _normalizeJson)"
- _debug response "$response"
-
- if [ "$response" = '{"result":null,"metadata":null}' ]; then
- _info "Removed, OK"
+ if _dns_one_delrecord "$id"; then
+ _info "$(__green Removed, OK)"
return 0
else
_err "Removing txt record error."
return 1
fi
-
}
#_acme-challenge.www.domain.com
@@ -138,6 +163,8 @@ _get_root() {
_dns_one_login() {
# get credentials
+ ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-$(_readaccountconf_mutable ONECOM_KeepCnameProxy)}"
+ ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-0}"
ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
@@ -149,6 +176,7 @@ _dns_one_login() {
fi
#save the api key and email to the account conf file.
+ _saveaccountconf_mutable ONECOM_KeepCnameProxy "$ONECOM_KeepCnameProxy"
_saveaccountconf_mutable ONECOM_User "$ONECOM_User"
_saveaccountconf_mutable ONECOM_Password "$ONECOM_Password"
@@ -177,3 +205,75 @@ _dns_one_login() {
return 0
}
+
+_dns_one_getrecord() {
+ type="$1"
+ name="$2"
+ value="$3"
+ if [ -z "$type" ]; then
+ type="TXT"
+ fi
+ if [ -z "$name" ]; then
+ _err "Record name is empty."
+ return 1
+ fi
+
+ response="$(_get "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records")"
+ response="$(echo "$response" | _normalizeJson)"
+ _debug response "$response"
+
+ if [ -z "${value}" ]; then
+ id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"[^\"]*\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ response=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"[^\"]*\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"\([^\"]*\)\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ else
+ id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"${value}\",\"priority\":0,\"ttl\":600}.*/\1/p")
+ fi
+ if [ -z "$id" ]; then
+ return 1
+ fi
+ return 0
+}
+
+_dns_one_addrecord() {
+ type="$1"
+ name="$2"
+ value="$3"
+ if [ -z "$type" ]; then
+ type="TXT"
+ fi
+ if [ -z "$name" ]; then
+ _err "Record name is empty."
+ return 1
+ fi
+
+ postdata="{\"type\":\"dns_custom_records\",\"attributes\":{\"priority\":0,\"ttl\":600,\"type\":\"${type}\",\"prefix\":\"${name}\",\"content\":\"${value}\"}}"
+ _debug postdata "$postdata"
+ response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records" "" "POST" "application/json")"
+ response="$(echo "$response" | _normalizeJson)"
+ _debug response "$response"
+
+ id=$(echo "$response" | sed -n "s/{\"result\":{\"data\":{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$subdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}}},\"metadata\":null}/\1/p")
+
+ if [ -z "$id" ]; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+_dns_one_delrecord() {
+ id="$1"
+ if [ -z "$id" ]; then
+ return 1
+ fi
+
+ response="$(_post "" "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records/$id" "" "DELETE" "application/json")"
+ response="$(echo "$response" | _normalizeJson)"
+ _debug response "$response"
+
+ if [ "$response" = '{"result":null,"metadata":null}' ]; then
+ return 0
+ else
+ return 1
+ fi
+}
diff --git a/dnsapi/dns_openprovider.sh b/dnsapi/dns_openprovider.sh
index 1b1b760eb3..0a9e5ade5f 100755
--- a/dnsapi/dns_openprovider.sh
+++ b/dnsapi/dns_openprovider.sh
@@ -3,7 +3,7 @@
# This is the OpenProvider API wrapper for acme.sh
#
# Author: Sylvia van Os
-# Report Bugs here: https://github.com/Neilpang/acme.sh/issues/2104
+# Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/2104
#
# export OPENPROVIDER_USER="username"
# export OPENPROVIDER_PASSWORDHASH="hashed_password"
@@ -59,16 +59,17 @@ dns_openprovider_add() {
break
fi
- items="$(echo "$items" | sed "s|${item}||")"
+ tmpitem="$(echo "$item" | sed 's/\*/\\*/g')"
+ items="$(echo "$items" | sed "s|${tmpitem}||")"
results_retrieved="$(_math "$results_retrieved" + 1)"
new_item="$(echo "$item" | sed -n 's/.*- .*\(\(.*\)\.'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
if [ -z "$new_item" ]; then
- # Base record
+ # Domain apex
new_item="$(echo "$item" | sed -n 's/.*
- .*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
fi
- if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
+ if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
_debug "not an allowed record type, skipping" "$new_item"
continue
fi
@@ -86,7 +87,7 @@ dns_openprovider_add() {
_debug "Creating acme record"
acme_record="$(echo "$fulldomain" | sed -e "s/.$_domain_name.$_domain_extension$//")"
- _openprovider_request "$(printf '%s%smaster%s
- %sTXT%s86400
' "$_domain_name" "$_domain_extension" "$existing_items" "$acme_record" "$txtvalue")"
+ _openprovider_request "$(printf '%s%smaster%s- %sTXT%s600
' "$_domain_name" "$_domain_extension" "$existing_items" "$acme_record" "$txtvalue")"
return 0
}
@@ -136,7 +137,8 @@ dns_openprovider_rm() {
break
fi
- items="$(echo "$items" | sed "s|${item}||")"
+ tmpitem="$(echo "$item" | sed 's/\*/\\*/g')"
+ items="$(echo "$items" | sed "s|${tmpitem}||")"
results_retrieved="$(_math "$results_retrieved" + 1)"
if ! echo "$item" | grep -v "$fulldomain"; then
@@ -147,11 +149,11 @@ dns_openprovider_rm() {
new_item="$(echo "$item" | sed -n 's/.*- .*\(\(.*\)\.'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
if [ -z "$new_item" ]; then
- # Base record
+ # domain apex
new_item="$(echo "$item" | sed -n 's/.*
- .*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/
- \2<\/name>\3\4\5\6<\/item>/p')"
fi
- if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then
+ if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then
_debug "not an allowed record type, skipping" "$new_item"
continue
fi
@@ -205,7 +207,8 @@ _get_root() {
break
fi
- items="$(echo "$items" | sed "s|${item}||")"
+ tmpitem="$(echo "$item" | sed 's/\*/\\*/g')"
+ items="$(echo "$items" | sed "s|${tmpitem}||")"
results_retrieved="$(_math "$results_retrieved" + 1)"
diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh
new file mode 100755
index 0000000000..069f6c328a
--- /dev/null
+++ b/dnsapi/dns_opnsense.sh
@@ -0,0 +1,273 @@
+#!/usr/bin/env sh
+
+#OPNsense Bind API
+#https://docs.opnsense.org/development/api.html
+#
+#OPNs_Host="opnsense.example.com"
+#OPNs_Port="443"
+# optional, defaults to 443 if unset
+#OPNs_Key="qocfU9RSbt8vTIBcnW8bPqCrpfAHMDvj5OzadE7Str+rbjyCyk7u6yMrSCHtBXabgDDXx/dY0POUp7ZA"
+#OPNs_Token="pZEQ+3ce8dDlfBBdg3N8EpqpF5I1MhFqdxX06le6Gl8YzyQvYCfCzNaFX9O9+IOSyAs7X71fwdRiZ+Lv"
+#OPNs_Api_Insecure=0
+# optional, defaults to 0 if unset
+# 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: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000"
+#fulldomain
+#txtvalue
+OPNs_DefaultPort=443
+OPNs_DefaultApi_Insecure=0
+
+dns_opnsense_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _opns_check_auth || return 1
+
+ if ! set_record "$fulldomain" "$txtvalue"; then
+ return 1
+ fi
+
+ return 0
+}
+
+#fulldomain
+dns_opnsense_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _opns_check_auth || return 1
+
+ if ! rm_record "$fulldomain" "$txtvalue"; then
+ return 1
+ fi
+
+ return 0
+}
+
+set_record() {
+ fulldomain=$1
+ new_challenge=$2
+ _info "Adding record $fulldomain with challenge: $new_challenge"
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain "$_domain"
+ _debug _host "$_host"
+ _debug _domainid "$_domainid"
+ _return_str=""
+ _record_string=""
+ _build_record_string "$_domainid" "$_host" "$new_challenge"
+ _uuid=""
+ if _existingchallenge "$_domain" "$_host" "$new_challenge"; then
+ # Update
+ if _opns_rest "POST" "/record/setRecord/${_uuid}" "$_record_string"; then
+ _return_str="$response"
+ else
+ return 1
+ fi
+
+ else
+ #create
+ if _opns_rest "POST" "/record/addRecord" "$_record_string"; then
+ _return_str="$response"
+ else
+ return 1
+ fi
+ fi
+
+ if echo "$_return_str" | _egrep_o "\"result\":\"saved\"" >/dev/null; then
+ _opns_rest "POST" "/service/reconfigure" "{}"
+ _debug "Record created"
+ else
+ _err "Error creating record $_record_string"
+ return 1
+ fi
+
+ return 0
+}
+
+rm_record() {
+ fulldomain=$1
+ new_challenge="$2"
+ _info "Remove record $fulldomain with challenge: $new_challenge"
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain "$_domain"
+ _debug _host "$_host"
+ _debug _domainid "$_domainid"
+ _uuid=""
+ if _existingchallenge "$_domain" "$_host" "$new_challenge"; then
+ # Delete
+ if _opns_rest "POST" "/record/delRecord/${_uuid}" "\{\}"; then
+ if echo "$_return_str" | _egrep_o "\"result\":\"deleted\"" >/dev/null; then
+ _opns_rest "POST" "/service/reconfigure" "{}"
+ _debug "Record deleted"
+ else
+ _err "Error deleting record $_host from domain $fulldomain"
+ return 1
+ fi
+ else
+ _err "Error deleting record $_host from domain $fulldomain"
+ return 1
+ fi
+ else
+ _info "Record not found, nothing to remove"
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domainid=domid
+#_domain=domain.com
+_get_root() {
+ domain=$1
+ i=2
+ p=1
+ if _opns_rest "GET" "/domain/get"; then
+ _domain_response="$response"
+ else
+ return 1
+ fi
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ if [ -z "$h" ]; then
+ #not valid
+ return 1
+ fi
+ _debug h "$h"
+ id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2)
+
+ if [ -n "$id" ]; then
+ _debug id "$id"
+ _host=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="${h}"
+ _domainid="${id}"
+ return 0
+ fi
+ p=$i
+ i=$(_math $i + 1)
+ done
+ _debug "$domain not found"
+
+ return 1
+}
+
+_opns_rest() {
+ method=$1
+ ep=$2
+ data=$3
+ #Percent encode user and token
+ key=$(echo "$OPNs_Key" | tr -d "\n\r" | _url_encode)
+ token=$(echo "$OPNs_Token" | tr -d "\n\r" | _url_encode)
+
+ opnsense_url="https://${key}:${token}@${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}"
+ export _H1="Content-Type: application/json"
+ _debug2 "Try to call api: https://${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}"
+ if [ ! "$method" = "GET" ]; then
+ _debug data "$data"
+ export _H1="Content-Type: application/json"
+ response="$(_post "$data" "$opnsense_url" "" "$method")"
+ else
+ export _H1=""
+ response="$(_get "$opnsense_url")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+
+ return 0
+}
+
+_build_record_string() {
+ _record_string="{\"record\":{\"enabled\":\"1\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"}}"
+}
+
+_existingchallenge() {
+ if _opns_rest "GET" "/record/searchRecord"; then
+ _record_response="$response"
+ else
+ return 1
+ fi
+ _uuid=""
+ _uuid=$(echo "$_record_response" | _egrep_o "\"uuid\":\"[^\"]*\",\"enabled\":\"[01]\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
+
+ if [ -n "$_uuid" ]; then
+ _debug uuid "$_uuid"
+ return 0
+ fi
+ _debug "${2}.$1{1} record not found"
+
+ return 1
+}
+
+_opns_check_auth() {
+ OPNs_Host="${OPNs_Host:-$(_readaccountconf_mutable OPNs_Host)}"
+ OPNs_Port="${OPNs_Port:-$(_readaccountconf_mutable OPNs_Port)}"
+ OPNs_Key="${OPNs_Key:-$(_readaccountconf_mutable OPNs_Key)}"
+ OPNs_Token="${OPNs_Token:-$(_readaccountconf_mutable OPNs_Token)}"
+ OPNs_Api_Insecure="${OPNs_Api_Insecure:-$(_readaccountconf_mutable OPNs_Api_Insecure)}"
+
+ if [ -z "$OPNs_Host" ]; then
+ _err "You don't specify OPNsense address."
+ return 1
+ else
+ _saveaccountconf_mutable OPNs_Host "$OPNs_Host"
+ fi
+
+ if ! printf '%s' "$OPNs_Port" | grep '^[0-9]*$' >/dev/null; then
+ _err 'OPNs_Port specified but not numeric value'
+ return 1
+ elif [ -z "$OPNs_Port" ]; then
+ _info "OPNSense port not specified. Defaulting to using port $OPNs_DefaultPort"
+ else
+ _saveaccountconf_mutable OPNs_Port "$OPNs_Port"
+ fi
+
+ if ! printf '%s' "$OPNs_Api_Insecure" | grep '^[01]$' >/dev/null; then
+ _err 'OPNs_Api_Insecure specified but not 0/1 value'
+ return 1
+ elif [ -n "$OPNs_Api_Insecure" ]; then
+ _saveaccountconf_mutable OPNs_Api_Insecure "$OPNs_Api_Insecure"
+ fi
+ export HTTPS_INSECURE="${OPNs_Api_Insecure:-$OPNs_DefaultApi_Insecure}"
+
+ if [ -z "$OPNs_Key" ]; then
+ _err "you have not specified your OPNsense api key id."
+ _err "Please set OPNs_Key and try again."
+ return 1
+ else
+ _saveaccountconf_mutable OPNs_Key "$OPNs_Key"
+ fi
+
+ if [ -z "$OPNs_Token" ]; then
+ _err "you have not specified your OPNsense token."
+ _err "Please create OPNs_Token and try again."
+ return 1
+ else
+ _saveaccountconf_mutable OPNs_Token "$OPNs_Token"
+ fi
+
+ if ! _opns_rest "GET" "/general/get"; then
+ _err "Call to OPNsense API interface failed. Unable to access OPNsense API."
+ return 1
+ fi
+ return 0
+}
diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh
index 65567efd9d..7c18d009ae 100755
--- a/dnsapi/dns_ovh.sh
+++ b/dnsapi/dns_ovh.sh
@@ -32,9 +32,9 @@ SYS_CA='https://ca.api.soyoustart.com/1.0'
#'runabove-ca'
RAV_CA='https://api.runabove.com/1.0'
-wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api"
+wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api"
-ovh_success="https://github.com/Neilpang/acme.sh/wiki/OVH-Success"
+ovh_success="https://github.com/acmesh-official/acme.sh/wiki/OVH-Success"
_ovh_get_api() {
_ogaep="$1"
diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh
new file mode 100644
index 0000000000..fe18bef419
--- /dev/null
+++ b/dnsapi/dns_pleskxml.sh
@@ -0,0 +1,414 @@
+#!/usr/bin/env sh
+
+## Name: dns_pleskxml.sh
+## Created by Stilez.
+## Also uses some code from PR#1832 by @romanlum (https://github.com/acmesh-official/acme.sh/pull/1832/files)
+
+## This DNS-01 method uses the Plesk XML API described at:
+## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709
+## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784
+
+## Note: a DNS ID with host = empty string is OK for this API, see
+## https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798
+## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action.
+## So this API module can handle such a request, if needed.
+
+## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records.
+
+## The user credentials (username+password) and URL/URI for the Plesk XML API must be set by the user
+## before this module is called (case sensitive):
+##
+## ```
+## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php"
+## (or probably something similar)
+## export pleskxml_user="my plesk username"
+## export pleskxml_pass="my plesk password"
+## ```
+
+## Ok, let's issue a cert now:
+## ```
+## acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com
+## ```
+##
+## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed.
+
+#################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ##################################
+
+pleskxml_init_checks_done=0
+
+# Variable containing bare newline - not a style issue
+# shellcheck disable=SC1004
+NEWLINE='\
+'
+
+pleskxml_tplt_get_domains=""
+# Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh
+# Also used to test credentials and URI.
+# No params.
+
+pleskxml_tplt_get_dns_records="%s"
+# Get all DNS records for a Plesk domain ID.
+# PARAM = Plesk domain id to query
+
+pleskxml_tplt_add_txt_record="%sTXT%s%s"
+# Add a TXT record to a domain.
+# PARAMS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value
+
+pleskxml_tplt_rmv_dns_record="%s"
+# Delete a specific TXT record from a domain.
+# PARAM = the Plesk internal ID for the DNS record to be deleted
+
+#################### Public functions ##################################
+
+#Usage: dns_pleskxml_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_pleskxml_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'..."
+
+ # Get credentials if not already checked, and confirm we can log in to Plesk XML API
+ if ! _credential_check; then
+ return 1
+ fi
+
+ # Get root and subdomain details, and Plesk domain ID
+ if ! _pleskxml_get_root_domain "$fulldomain"; then
+ return 1
+ fi
+
+ _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record'
+
+ # printf using template in a variable - not a style issue
+ # shellcheck disable=SC2059
+ request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")"
+ if ! _call_api "$request"; then
+ return 1
+ fi
+
+ # OK, we should have added a TXT record. Let's check and return success if so.
+ # All that should be left in the result, is one section, containing okNEW_DNS_RECORD_ID
+
+ results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')"
+
+ if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then
+ # Error - doesn't contain expected string. Something's wrong.
+ _err 'Error when calling Plesk XML API.'
+ _err 'The result did not contain the expected XXXXX section, or contained other values as well.'
+ _err 'This is unexpected: something has gone wrong.'
+ _err 'The full response was:'
+ _err "$pleskxml_prettyprint_result"
+ return 1
+ fi
+
+ recid="$(_value "$results" | grep '[0-9]\{1,\}' | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')"
+
+ _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()."
+
+ return 0
+}
+
+#Usage: dns_pleskxml_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_pleskxml_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ _info "Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'..."
+
+ # Get credentials if not already checked, and confirm we can log in to Plesk XML API
+ if ! _credential_check; then
+ return 1
+ fi
+
+ # Get root and subdomain details, and Plesk domain ID
+ if ! _pleskxml_get_root_domain "$fulldomain"; then
+ return 1
+ fi
+
+ _debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs'
+
+ # printf using template in a variable - not a style issue
+ # shellcheck disable=SC2059
+ request="$(printf "$pleskxml_tplt_get_dns_records" "$root_domain_id")"
+ if ! _call_api "$request"; then
+ return 1
+ fi
+
+ # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have)
+ # Also strip out spaces between tags, redundant and group tags and any tags
+ reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \
+ | sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s#\{0,1\}data>##g;s#<[a-z][^/<>]*/>##g' \
+ | grep "${root_domain_id}" \
+ | grep '[0-9]\{1,\}' \
+ | grep 'TXT'
+ )"
+
+ if [ -z "$reclist" ]; then
+ _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting."
+ return 1
+ fi
+
+ _debug "Got list of DNS TXT records for root domain '$root_domain_name':"
+ _debug "$reclist"
+
+ recid="$(_value "$reclist" \
+ | grep "${fulldomain}." \
+ | grep "${txtvalue}" \
+ | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/'
+ )"
+
+ if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then
+ _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'"
+ _err "Cannot delete TXT record. Exiting."
+ return 1
+ fi
+
+ _debug "Found Plesk record ID for target text string '${txtvalue}': ID=${recid}"
+ _debug 'Calling Plesk XML API to remove TXT record'
+
+ # printf using template in a variable - not a style issue
+ # shellcheck disable=SC2059
+ request="$(printf "$pleskxml_tplt_rmv_dns_record" "$recid")"
+ if ! _call_api "$request"; then
+ return 1
+ fi
+
+ # OK, we should have removed a TXT record. Let's check and return success if so.
+ # All that should be left in the result, is one section, containing okPLESK_DELETED_DNS_RECORD_ID
+
+ results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')"
+
+ if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then
+ # Error - doesn't contain expected string. Something's wrong.
+ _err 'Error when calling Plesk XML API.'
+ _err 'The result did not contain the expected XXXXX section, or contained other values as well.'
+ _err 'This is unexpected: something has gone wrong.'
+ _err 'The full response was:'
+ _err "$pleskxml_prettyprint_result"
+ return 1
+ fi
+
+ _info "Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm()."
+ return 0
+}
+
+#################### Private functions below (utility functions) ##################################
+
+# Outputs value of a variable without additional newlines etc
+_value() {
+ printf '%s' "$1"
+}
+
+# Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between
+# $1, $2 = where to cut
+# $3 = FQDN
+_valuecut() {
+ printf '%s' "$3" | cut -d . -f "${1}-${2}"
+}
+
+# Counts '.' present in a domain name or other string
+# $1 = domain name
+_countdots() {
+ _value "$1" | tr -dc '.' | wc -c | sed 's/ //g'
+}
+
+# Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines
+# $1 - result string from API
+# $2 - plain text tag to resplit on (usually "result" or "domain"). NOT REGEX
+# $3 - basic regex to recognise useful return lines
+# note: $3 matches via basic NOT extended regex (BRE), as extended regex capabilities not needed at the moment.
+# Last line could change to instead, with suitable escaping of ['"/$],
+# if future Plesk XML API changes ever require extended regex
+_api_response_split() {
+ printf '%s' "$1" \
+ | sed 's/^ +//;s/ +$//' \
+ | tr -d '\n\r' \
+ | sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" \
+ | grep "$3"
+}
+
+#################### Private functions below (DNS functions) ##################################
+
+# Calls Plesk XML API, and checks results for obvious issues
+_call_api() {
+ request="$1"
+ errtext=''
+
+ _debug 'Entered _call_api(). Calling Plesk XML API with request:'
+ _debug "'$request'"
+
+ export _H1="HTTP_AUTH_LOGIN: $pleskxml_user"
+ export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass"
+ export _H3="content-Type: text/xml"
+ export _H4="HTTP_PRETTY_PRINT: true"
+ pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")"
+ pleskxml_retcode="$?"
+ _debug 'The responses from the Plesk XML server were:'
+ _debug "retcode=$pleskxml_retcode. Literal response:"
+ _debug "'$pleskxml_prettyprint_result'"
+
+ # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly.
+ # Also detect if there simply aren't any status lines (null result?) and report that, as well.
+
+ statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *[^<]* *$')"
+ statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *ok *$')"
+
+ if [ -z "$statuslines_count_total" ]; then
+
+ # We have no status lines at all. Results are empty
+ errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.'
+
+ elif [ "$statuslines_count_okay" -ne "$statuslines_count_total" ]; then
+
+ # We have some status lines that aren't "ok". Any available details are in API response fields "status" "errcode" and "errtext"
+ # Workaround for basic regex:
+ # - filter output to keep only lines like this: "SPACEStextSPACES" (shouldn't be necessary with prettyprint but guarantees subsequent code is ok)
+ # - then edit the 3 "useful" error tokens individually and remove closing tags on all lines
+ # - then filter again to remove all lines not edited (which will be the lines not starting A-Z)
+ errtext="$(_value "$pleskxml_prettyprint_result" \
+ | grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' \
+ | sed 's/^ */Status: /;s/^ */Error code: /;s/^ */Error text: /;s/<\/.*$//' \
+ | grep '^[A-Z]'
+ )"
+
+ fi
+
+ if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then
+ # Call failed, for reasons either in the retcode or the response text...
+
+ if [ "$pleskxml_retcode" -eq 0 ]; then
+ _err "The POST request was successfully sent to the Plesk server."
+ else
+ _err "The return code for the POST request was $pleskxml_retcode (non-zero = failure in submitting request to server)."
+ fi
+
+ if [ "$errtext" != "" ]; then
+ _err 'The error responses received from the Plesk server were:'
+ _err "$errtext"
+ else
+ _err "No additional error messages were received back from the Plesk server"
+ fi
+
+ _err "The Plesk XML API call failed."
+ return 1
+
+ fi
+
+ _debug "Leaving _call_api(). Successful call."
+
+ return 0
+}
+
+# Startup checks (credentials, URI)
+_credential_check() {
+ _debug "Checking Plesk XML API login credentials and URI..."
+
+ if [ "$pleskxml_init_checks_done" -eq 1 ]; then
+ _debug "Initial checks already done, no need to repeat. Skipped."
+ return 0
+ fi
+
+ pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}"
+ pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}"
+ pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}"
+
+ if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then
+ pleskxml_user=""
+ pleskxml_pass=""
+ pleskxml_uri=""
+ _err "You didn't specify one or more of the Plesk XML API username, password, or URI."
+ _err "Please create these and try again."
+ _err "Instructions are in the 'dns_pleskxml' plugin source code or in the acme.sh documentation."
+ return 1
+ fi
+
+ # Test the API is usable, by trying to read the list of managed domains...
+ _call_api "$pleskxml_tplt_get_domains"
+ if [ "$pleskxml_retcode" -ne 0 ]; then
+ _err 'Failed to access Plesk XML API.'
+ _err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again."
+ return 1
+ fi
+
+ _saveaccountconf_mutable pleskxml_uri "$pleskxml_uri"
+ _saveaccountconf_mutable pleskxml_user "$pleskxml_user"
+ _saveaccountconf_mutable pleskxml_pass "$pleskxml_pass"
+
+ _debug "Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use."
+
+ pleskxml_init_checks_done=1
+
+ return 0
+}
+
+# For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any.
+
+# IMPORTANT NOTE: a result with host = empty string is OK for this API, see
+# https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798
+# See notes at top of this file
+
+_pleskxml_get_root_domain() {
+ original_full_domain_name="$1"
+
+ _debug "Identifying DNS root domain for '$original_full_domain_name' that is managed by the Plesk account."
+
+ # test if the domain as provided is valid for splitting.
+
+ if [ "$(_countdots "$original_full_domain_name")" -eq 0 ]; then
+ _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record."
+ return 1
+ fi
+
+ _debug "Querying Plesk server for list of managed domains..."
+
+ _call_api "$pleskxml_tplt_get_domains"
+ if [ "$pleskxml_retcode" -ne 0 ]; then
+ return 1
+ fi
+
+ # Generate a crude list of domains known to this Plesk account.
+ # We convert tags to so it'll flag on a hit with either or fields,
+ # for non-Western character sets.
+ # Output will be one line per known domain, containing 2 tages and a single tag
+ # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned.
+
+ output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')"
+
+ _debug 'Domains managed by Plesk server are (ignore the hacked output):'
+ _debug "$output"
+
+ # loop and test if domain, or any parent domain, is managed by Plesk
+ # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain
+
+ root_domain_name="$original_full_domain_name"
+
+ while true; do
+
+ _debug "Checking if '$root_domain_name' is managed by the Plesk server..."
+
+ root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')"
+
+ if [ -n "$root_domain_id" ]; then
+ # Found a match
+ # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT.
+ # SO WE HANDLE IT AND DON'T PREVENT IT
+ sub_domain_name="$(_value "$original_full_domain_name" | sed "s/\.\{0,1\}${root_domain_name}"'$//')"
+ _info "Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning."
+ return 0
+ fi
+
+ # No match, try next parent up (if any)...
+
+ root_domain_name="$(_valuecut 2 1000 "$root_domain_name")"
+
+ if [ "$(_countdots "$root_domain_name")" -eq 0 ]; then
+ _debug "No match, and next parent would be a TLD..."
+ _err "Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk."
+ _err "Are you sure that this domain is managed by this Plesk server?"
+ return 1
+ fi
+
+ _debug "No match, trying next parent up..."
+
+ done
+}
diff --git a/dnsapi/dns_rackspace.sh b/dnsapi/dns_rackspace.sh
index 3939fd81d6..03e1fa686e 100644
--- a/dnsapi/dns_rackspace.sh
+++ b/dnsapi/dns_rackspace.sh
@@ -9,7 +9,7 @@ RACKSPACE_Endpoint="https://dns.api.rackspacecloud.com/v1.0"
# 20190213 - The name & id fields swapped in the API response; fix sed
# 20190101 - Duplicating file for new pull request to dev branch
-# Original - tcocca:rackspace_dnsapi https://github.com/Neilpang/acme.sh/pull/1297
+# Original - tcocca:rackspace_dnsapi https://github.com/acmesh-official/acme.sh/pull/1297
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -73,7 +73,7 @@ _get_root_zone() {
#not valid
return 1
fi
- if ! _rackspace_rest GET "$RACKSPACE_Tenant/domains"; then
+ if ! _rackspace_rest GET "$RACKSPACE_Tenant/domains/search?name=$h"; then
return 1
fi
_debug2 response "$response"
diff --git a/dnsapi/dns_rcode0.sh b/dnsapi/dns_rcode0.sh
new file mode 100755
index 0000000000..d3f7f219ec
--- /dev/null
+++ b/dnsapi/dns_rcode0.sh
@@ -0,0 +1,224 @@
+#!/usr/bin/env sh
+
+#Rcode0 API Integration
+#https://my.rcodezero.at/api-doc
+#
+# log into https://my.rcodezero.at/enableapi and get your ACME API Token (the ACME API token has limited
+# access to the REST calls needed for acme.sh only)
+#
+#RCODE0_URL="https://my.rcodezero.at"
+#RCODE0_API_TOKEN="0123456789ABCDEF"
+#RCODE0_TTL=60
+
+DEFAULT_RCODE0_URL="https://my.rcodezero.at"
+DEFAULT_RCODE0_TTL=60
+
+######## Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000"
+#fulldomain
+#txtvalue
+dns_rcode0_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
+ RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
+ RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
+
+ if [ -z "$RCODE0_URL" ]; then
+ RCODE0_URL="$DEFAULT_RCODE0_URL"
+ fi
+
+ if [ -z "$RCODE0_API_TOKEN" ]; then
+ RCODE0_API_TOKEN=""
+ _err "Missing Rcode0 ACME API Token."
+ _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
+ return 1
+ fi
+
+ if [ -z "$RCODE0_TTL" ]; then
+ RCODE0_TTL="$DEFAULT_RCODE0_TTL"
+ fi
+
+ #save the token to the account conf file.
+ _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
+
+ if [ "$RCODE0_URL" != "$DEFAULT_RCODE0_URL" ]; then
+ _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
+ fi
+
+ if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
+ _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
+ fi
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "No 'MASTER' zone for $fulldomain found at RcodeZero Anycast."
+ return 1
+ fi
+ _debug _domain "$_domain"
+
+ _debug "Adding record"
+
+ _record_string=""
+ _build_record_string "$txtvalue"
+ _list_existingchallenges
+ for oldchallenge in $_existing_challenges; do
+ _build_record_string "$oldchallenge"
+ done
+
+ _debug "Challenges: $_existing_challenges"
+
+ if [ -z "$_existing_challenges" ]; then
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"add\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+ _err "Add txt record error."
+ return 1
+ fi
+ else
+ # try update in case a records exists (need for wildcard certs)
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+ _err "Set txt record error."
+ return 1
+ fi
+ fi
+
+ return 0
+}
+
+#fulldomain txtvalue
+dns_rcode0_rm() {
+ fulldomain=$1
+ txtvalue=$2
+
+ RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}"
+ RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}"
+ RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}"
+
+ if [ -z "$RCODE0_URL" ]; then
+ RCODE0_URL="$DEFAULT_RCODE0_URL"
+ fi
+
+ if [ -z "$RCODE0_API_TOKEN" ]; then
+ RCODE0_API_TOKEN=""
+ _err "Missing Rcode0 API Token."
+ _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again."
+ return 1
+ fi
+
+ #save the api addr and key to the account conf file.
+ _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL"
+ _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN"
+
+ if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then
+ _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL"
+ fi
+
+ if [ -z "$RCODE0_TTL" ]; then
+ RCODE0_TTL="$DEFAULT_RCODE0_TTL"
+ fi
+
+ _debug "Detect root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug "Remove record"
+
+ #Enumerate existing acme challenges
+ _list_existingchallenges
+
+ if _contains "$_existing_challenges" "$txtvalue"; then
+ #Delete all challenges (PowerDNS API does not allow to delete content)
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"delete\", \"name\": \"$fulldomain.\", \"type\": \"TXT\"}]"; then
+ _err "Delete txt record error."
+ return 1
+ fi
+ _record_string=""
+ #If the only existing challenge was the challenge to delete: nothing to do
+ if ! [ "$_existing_challenges" = "$txtvalue" ]; then
+ for oldchallenge in $_existing_challenges; do
+ #Build up the challenges to re-add, ommitting the one what should be deleted
+ if ! [ "$oldchallenge" = "$txtvalue" ]; then
+ _build_record_string "$oldchallenge"
+ fi
+ done
+ #Recreate the existing challenges
+ if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then
+ _err "Set txt record error."
+ return 1
+ fi
+ fi
+ else
+ _info "Record not found, nothing to remove"
+ fi
+
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domain=domain.com
+_get_root() {
+ domain=$1
+ i=1
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+
+ _debug "try to find: $h"
+ if _rcode0_rest "GET" "/api/v1/acme/zones/$h"; then
+ if [ "$response" = "[\"found\"]" ]; then
+ _domain="$h"
+ if [ -z "$h" ]; then
+ _domain="=2E"
+ fi
+ return 0
+ elif [ "$response" = "[\"not a master domain\"]" ]; then
+ return 1
+ fi
+ fi
+
+ if [ -z "$h" ]; then
+ return 1
+ fi
+ i=$(_math $i + 1)
+ done
+ _debug "no matching domain for $domain found"
+
+ return 1
+}
+
+_rcode0_rest() {
+ method=$1
+ ep=$2
+ data=$3
+
+ export _H1="Authorization: Bearer $RCODE0_API_TOKEN"
+
+ if [ ! "$method" = "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$RCODE0_URL$ep" "" "$method")"
+ else
+ response="$(_get "$RCODE0_URL$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+
+ return 0
+}
+
+_build_record_string() {
+ _record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}"
+}
+
+_list_existingchallenges() {
+ _rcode0_rest "GET" "/api/v1/acme/zones/$_domain/rrsets"
+ _existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p')
+ _debug2 "$_existing_challenges"
+}
diff --git a/dnsapi/dns_servercow.sh b/dnsapi/dns_servercow.sh
index be4e59dae1..e73d85b097 100755
--- a/dnsapi/dns_servercow.sh
+++ b/dnsapi/dns_servercow.sh
@@ -1,7 +1,7 @@
#!/usr/bin/env sh
##########
-# Custom servercow.de DNS API v1 for use with [acme.sh](https://github.com/Neilpang/acme.sh)
+# Custom servercow.de DNS API v1 for use with [acme.sh](https://github.com/acmesh-official/acme.sh)
#
# Usage:
# export SERVERCOW_API_Username=username
diff --git a/dnsapi/dns_transip.sh b/dnsapi/dns_transip.sh
new file mode 100644
index 0000000000..23debe0d87
--- /dev/null
+++ b/dnsapi/dns_transip.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env sh
+TRANSIP_Api_Url="https://api.transip.nl/v6"
+TRANSIP_Token_Read_Only="false"
+TRANSIP_Token_Global_Key="false"
+TRANSIP_Token_Expiration="30 minutes"
+# You can't reuse a label token, so we leave this empty normally
+TRANSIP_Token_Label=""
+
+######## Public functions #####################
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_transip_add() {
+ fulldomain="$1"
+ _debug fulldomain="$fulldomain"
+ txtvalue="$2"
+ _debug txtvalue="$txtvalue"
+ _transip_setup "$fulldomain" || return 1
+ _info "Creating TXT record."
+ if ! _transip_rest POST "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then
+ _err "Could not add TXT record."
+ return 1
+ fi
+ return 0
+}
+
+dns_transip_rm() {
+ fulldomain=$1
+ _debug fulldomain="$fulldomain"
+ txtvalue=$2
+ _debug txtvalue="$txtvalue"
+ _transip_setup "$fulldomain" || return 1
+ _info "Removing TXT record."
+ if ! _transip_rest DELETE "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then
+ _err "Could not remove TXT record $_sub_domain for $domain"
+ return 1
+ fi
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_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
+
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+
+ if _transip_rest GET "domains/$h/dns" && _contains "$response" "dnsEntries"; then
+ return 0
+ fi
+
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ _err "Unable to parse this domain"
+ return 1
+}
+
+_transip_rest() {
+ m="$1"
+ ep="$2"
+ data="$3"
+ _debug ep "$ep"
+ export _H1="Accept: application/json"
+ export _H2="Authorization: Bearer $_token"
+ export _H4="Content-Type: application/json"
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$TRANSIP_Api_Url/$ep" "" "$m")"
+ retcode=$?
+ else
+ response="$(_get "$TRANSIP_Api_Url/$ep")"
+ retcode=$?
+ fi
+
+ if [ "$retcode" != "0" ]; then
+ _err "error $ep"
+ return 1
+ fi
+ _debug2 response "$response"
+ return 0
+}
+
+_transip_get_token() {
+ nonce=$(echo "TRANSIP$(_time)" | _digest sha1 hex | cut -c 1-32)
+ _debug nonce "$nonce"
+
+ data="{\"login\":\"${TRANSIP_Username}\",\"nonce\":\"${nonce}\",\"read_only\":\"${TRANSIP_Token_Read_Only}\",\"expiration_time\":\"${TRANSIP_Token_Expiration}\",\"label\":\"${TRANSIP_Token_Label}\",\"global_key\":\"${TRANSIP_Token_Global_Key}\"}"
+ _debug data "$data"
+
+ #_signature=$(printf "%s" "$data" | openssl dgst -sha512 -sign "$TRANSIP_Key_File" | _base64)
+ _signature=$(printf "%s" "$data" | _sign "$TRANSIP_Key_File" "sha512")
+ _debug2 _signature "$_signature"
+
+ export _H1="Signature: $_signature"
+ export _H2="Content-Type: application/json"
+
+ response="$(_post "$data" "$TRANSIP_Api_Url/auth" "" "POST")"
+ retcode=$?
+ _debug2 response "$response"
+ if [ "$retcode" != "0" ]; then
+ _err "Authentication failed."
+ return 1
+ fi
+ if _contains "$response" "token"; then
+ _token="$(echo "$response" | _normalizeJson | sed -n 's/^{"token":"\(.*\)"}/\1/p')"
+ _debug _token "$_token"
+ return 0
+ fi
+ return 1
+}
+
+_transip_setup() {
+ fulldomain=$1
+
+ # retrieve the transip creds
+ TRANSIP_Username="${TRANSIP_Username:-$(_readaccountconf_mutable TRANSIP_Username)}"
+ TRANSIP_Key_File="${TRANSIP_Key_File:-$(_readaccountconf_mutable TRANSIP_Key_File)}"
+ # check their vals for null
+ if [ -z "$TRANSIP_Username" ] || [ -z "$TRANSIP_Key_File" ]; then
+ TRANSIP_Username=""
+ TRANSIP_Key_File=""
+ _err "You didn't specify a TransIP username and api key file location"
+ _err "Please set those values and try again."
+ return 1
+ fi
+ # save the username and api key to the account conf file.
+ _saveaccountconf_mutable TRANSIP_Username "$TRANSIP_Username"
+ _saveaccountconf_mutable TRANSIP_Key_File "$TRANSIP_Key_File"
+
+ if [ -f "$TRANSIP_Key_File" ]; then
+ if ! grep "BEGIN PRIVATE KEY" "$TRANSIP_Key_File" >/dev/null 2>&1; then
+ _err "Key file doesn't seem to be a valid key: ${TRANSIP_Key_File}"
+ return 1
+ fi
+ else
+ _err "Can't read private key file: ${TRANSIP_Key_File}"
+ return 1
+ fi
+
+ if [ -z "$_token" ]; then
+ if ! _transip_get_token; then
+ _err "Can not get token."
+ return 1
+ fi
+ fi
+
+ _get_root "$fulldomain" || return 1
+
+ return 0
+}
diff --git a/dnsapi/dns_unoeuro.sh b/dnsapi/dns_unoeuro.sh
index 9132f13699..c4593a6383 100644
--- a/dnsapi/dns_unoeuro.sh
+++ b/dnsapi/dns_unoeuro.sh
@@ -52,7 +52,7 @@ dns_unoeuro_add() {
fi
_info "Adding record"
- if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then
+ if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120,\"priority\":0}"; then
if _contains "$response" "\"status\": 200" >/dev/null; then
_info "Added, OK"
return 0
diff --git a/dnsapi/dns_variomedia.sh b/dnsapi/dns_variomedia.sh
new file mode 100644
index 0000000000..729cda5ef7
--- /dev/null
+++ b/dnsapi/dns_variomedia.sh
@@ -0,0 +1,147 @@
+#!/usr/bin/env sh
+
+#
+#VARIOMEDIA_API_TOKEN=000011112222333344445555666677778888
+
+VARIOMEDIA_API="https://api.variomedia.de"
+
+######## Public functions #####################
+
+#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_variomedia_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
+ if test -z "$VARIOMEDIA_API_TOKEN"; then
+ VARIOMEDIA_API_TOKEN=""
+ _err 'VARIOMEDIA_API_TOKEN was not exported'
+ return 1
+ fi
+
+ _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ if ! _variomedia_rest POST "dns-records" "{\"data\": {\"type\": \"dns-record\", \"attributes\": {\"record_type\": \"TXT\", \"name\": \"$_sub_domain\", \"domain\": \"$_domain\", \"data\": \"$txtvalue\", \"ttl\":300}}}"; then
+ _err "$response"
+ return 1
+ fi
+
+ _debug2 _response "$response"
+ return 0
+}
+
+#fulldomain txtvalue
+dns_variomedia_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}"
+ if test -z "$VARIOMEDIA_API_TOKEN"; then
+ VARIOMEDIA_API_TOKEN=""
+ _err 'VARIOMEDIA_API_TOKEN was not exported'
+ return 1
+ fi
+
+ _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN"
+
+ _debug 'First detect the root zone'
+ if ! _get_root "$fulldomain"; then
+ return 1
+ fi
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ _debug 'Getting txt records'
+
+ if ! _variomedia_rest GET "dns-records?filter[domain]=$_domain"; then
+ _err 'Error'
+ return 1
+ fi
+
+ _record_id="$(echo "$response" | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')"
+ _debug _record_id "$_record_id"
+ if [ "$_record_id" ]; then
+ _info "Successfully retrieved the record id for ACME challenge."
+ else
+ _info "Empty record id, it seems no such record."
+ return 0
+ fi
+
+ if ! _variomedia_rest DELETE "/dns-records/$_record_id"; then
+ _err "$response"
+ return 1
+ fi
+
+ _debug2 _response "$response"
+ return 0
+}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+ fulldomain=$1
+ i=1
+ while true; do
+ h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100)
+ _debug h "$h"
+ if [ -z "$h" ]; then
+ return 1
+ fi
+
+ if ! _variomedia_rest GET "domains/$h"; then
+ return 1
+ fi
+
+ if _startswith "$response" "\{\"data\":"; then
+ if _contains "$response" "\"id\": \"$h\""; then
+ _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
+ _domain=$h
+ return 0
+ fi
+ fi
+ i=$(_math "$i" + 1)
+ done
+
+ _debug "root domain not found"
+ return 1
+}
+
+_variomedia_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Authorization: token $VARIOMEDIA_API_TOKEN"
+ export _H2="Content-Type: application/vnd.api+json"
+ export _H3="Accept: application/vnd.variomedia.v1+json"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$VARIOMEDIA_API/$ep" "" "$m")"
+ else
+ response="$(_get "$VARIOMEDIA_API/$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "Error $ep"
+ return 1
+ fi
+
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_vscale.sh b/dnsapi/dns_vscale.sh
index e50b7d8b7b..d717d6e215 100755
--- a/dnsapi/dns_vscale.sh
+++ b/dnsapi/dns_vscale.sh
@@ -102,7 +102,7 @@ _get_root() {
return 1
fi
- hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")"
+ hostedzone="$(echo "$response" | tr "{" "\n" | _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
diff --git a/dnsapi/dns_vultr.sh b/dnsapi/dns_vultr.sh
index f15e7c4947..c7b52e8454 100644
--- a/dnsapi/dns_vultr.sh
+++ b/dnsapi/dns_vultr.sh
@@ -106,9 +106,9 @@ _get_root() {
domain=$1
i=1
while true; do
- h=$(printf "%s" "$domain" | cut -d . -f $i-100)
- _debug h "$h"
- if [ -z "$h" ]; then
+ _domain=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug h "$_domain"
+ if [ -z "$_domain" ]; then
return 1
fi
@@ -119,11 +119,9 @@ _get_root() {
if printf "%s\n" "$response" | grep '^\[.*\]' >/dev/null; then
if _contains "$response" "\"domain\":\"$_domain\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
- _domain=$_domain
return 0
else
- _err 'Invalid domain'
- return 1
+ _debug "Go to next level of $_domain"
fi
else
_err "$response"
diff --git a/dnsapi/dns_yandex.sh b/dnsapi/dns_yandex.sh
index a4f3978474..5721f994e5 100755
--- a/dnsapi/dns_yandex.sh
+++ b/dnsapi/dns_yandex.sh
@@ -6,6 +6,9 @@
# Values to export:
# export PDD_Token="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+# Sometimes cloudflare / google doesn't pick new dns records fast enough.
+# You can add --dnssleep XX to params as workaround.
+
######## Public functions #####################
#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
@@ -13,97 +16,106 @@ dns_yandex_add() {
fulldomain="${1}"
txtvalue="${2}"
_debug "Calling: dns_yandex_add() '${fulldomain}' '${txtvalue}'"
+
_PDD_credentials || return 1
- export _H1="PddToken: $PDD_Token"
- _PDD_get_domain "$fulldomain" || return 1
- _debug "Found suitable domain in pdd: $curDomain"
- curData="domain=${curDomain}&type=TXT&subdomain=${curSubdomain}&ttl=360&content=${txtvalue}"
- curUri="https://pddimp.yandex.ru/api2/admin/dns/add"
- curResult="$(_post "${curData}" "${curUri}")"
- _debug "Result: $curResult"
+ _PDD_get_domain || return 1
+ _debug "Found suitable domain: $domain"
+
+ _PDD_get_record_ids || return 1
+ _debug "Record_ids: $record_ids"
+
+ if [ ! -z "$record_ids" ]; then
+ _info "All existing $subdomain records from $domain will be removed at the very end."
+ fi
+
+ data="domain=${domain}&type=TXT&subdomain=${subdomain}&ttl=300&content=${txtvalue}"
+ uri="https://pddimp.yandex.ru/api2/admin/dns/add"
+ result="$(_post "${data}" "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
+
+ if ! _contains "$result" '"success":"ok"'; then
+ if _contains "$result" '"success":"error"' && _contains "$result" '"error":"record_exists"'; then
+ _info "Record already exists."
+ else
+ _err "Can't add $subdomain to $domain."
+ return 1
+ fi
+ fi
}
#Usage: dns_myapi_rm _acme-challenge.www.domain.com
dns_yandex_rm() {
fulldomain="${1}"
_debug "Calling: dns_yandex_rm() '${fulldomain}'"
+
_PDD_credentials || return 1
- export _H1="PddToken: $PDD_Token"
_PDD_get_domain "$fulldomain" || return 1
- _debug "Found suitable domain in pdd: $curDomain"
+ _debug "Found suitable domain: $domain"
- record_id=$(pdd_get_record_id "${fulldomain}")
- _debug "Result: $record_id"
+ _PDD_get_record_ids "${domain}" "${subdomain}" || return 1
+ _debug "Record_ids: $record_ids"
- for rec_i in $record_id; do
- curUri="https://pddimp.yandex.ru/api2/admin/dns/del"
- curData="domain=${curDomain}&record_id=${rec_i}"
- curResult="$(_post "${curData}" "${curUri}")"
- _debug "Result: $curResult"
+ for record_id in $record_ids; do
+ data="domain=${domain}&record_id=${record_id}"
+ uri="https://pddimp.yandex.ru/api2/admin/dns/del"
+ result="$(_post "${data}" "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
+
+ if ! _contains "$result" '"success":"ok"'; then
+ _info "Can't remove $subdomain from $domain."
+ fi
done
}
#################### Private functions below ##################################
_PDD_get_domain() {
- fulldomain="${1}"
- __page=1
- __last=0
- while [ $__last -eq 0 ]; do
- uri1="https://pddimp.yandex.ru/api2/admin/domain/domains?page=${__page}&on_page=20"
- res1="$(_get "$uri1" | _normalizeJson)"
- _debug2 "res1" "$res1"
- __found="$(echo "$res1" | sed -n -e 's#.* "found": \([^,]*\),.*#\1#p')"
- _debug "found: $__found results on page"
- if [ "0$__found" -lt 20 ]; then
- _debug "last page: $__page"
- __last=1
+ subdomain_start=1
+ while true; do
+ domain_start=$(_math $subdomain_start + 1)
+ domain=$(echo "$fulldomain" | cut -d . -f "$domain_start"-)
+ subdomain=$(echo "$fulldomain" | cut -d . -f -"$subdomain_start")
+
+ _debug "Checking domain $domain"
+ if [ -z "$domain" ]; then
+ return 1
fi
- __all_domains="$__all_domains $(echo "$res1" | tr "," "\n" | grep '"name"' | cut -d: -f2 | sed -e 's@"@@g')"
+ uri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=$domain"
+ result="$(_get "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
- __page=$(_math $__page + 1)
- done
-
- k=2
- while [ $k -lt 10 ]; do
- __t=$(echo "$fulldomain" | cut -d . -f $k-100)
- _debug "finding zone for domain $__t"
- for d in $__all_domains; do
- if [ "$d" = "$__t" ]; then
- p=$(_math $k - 1)
- curSubdomain="$(echo "$fulldomain" | cut -d . -f "1-$p")"
- curDomain="$__t"
- return 0
- fi
- done
- k=$(_math $k + 1)
+ if _contains "$result" '"success":"ok"'; then
+ return 0
+ fi
+ subdomain_start=$(_math $subdomain_start + 1)
done
- _err "No suitable domain found in your account"
- return 1
}
_PDD_credentials() {
if [ -z "${PDD_Token}" ]; then
PDD_Token=""
- _err "You need to export PDD_Token=xxxxxxxxxxxxxxxxx"
- _err "You can get it at https://pddimp.yandex.ru/api2/admin/get_token"
+ _err "You need to export PDD_Token=xxxxxxxxxxxxxxxxx."
+ _err "You can get it at https://pddimp.yandex.ru/api2/admin/get_token."
return 1
else
_saveaccountconf PDD_Token "${PDD_Token}"
fi
+ export _H1="PddToken: $PDD_Token"
}
-pdd_get_record_id() {
- fulldomain="${1}"
+_PDD_get_record_ids() {
+ _debug "Check existing records for $subdomain"
+
+ uri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${domain}"
+ result="$(_get "${uri}" | _normalizeJson)"
+ _debug "Result: $result"
- _PDD_get_domain "$fulldomain"
- _debug "Found suitable domain in pdd: $curDomain"
+ if ! _contains "$result" '"success":"ok"'; then
+ return 1
+ fi
- curUri="https://pddimp.yandex.ru/api2/admin/dns/list?domain=${curDomain}"
- curResult="$(_get "${curUri}" | _normalizeJson)"
- _debug "Result: $curResult"
- echo "$curResult" | _egrep_o "{[^{]*\"content\":[^{]*\"subdomain\":\"${curSubdomain}\"" | sed -n -e 's#.* "record_id": \(.*\),[^,]*#\1#p'
+ record_ids=$(echo "$result" | _egrep_o "{[^{]*\"subdomain\":\"${subdomain}\"[^}]*}" | sed -n -e 's#.*"record_id": \([0-9]*\).*#\1#p')
}
diff --git a/dnsapi/dns_zone.sh b/dnsapi/dns_zone.sh
index 847e32cdd2..176fc4943d 100755
--- a/dnsapi/dns_zone.sh
+++ b/dnsapi/dns_zone.sh
@@ -136,10 +136,10 @@ _get_root() {
if [ -z "$h" ]; then
return 1
fi
- if ! _zone_rest GET "dns/$h/a"; then
+ if ! _zone_rest GET "dns/$h"; then
return 1
fi
- if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+ if _contains "$response" "\"identificator\":\"$h\"" >/dev/null; then
_domain=$h
return 0
fi
diff --git a/notify/cqhttp.sh b/notify/cqhttp.sh
new file mode 100644
index 0000000000..ac76f5b8a5
--- /dev/null
+++ b/notify/cqhttp.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env sh
+
+#Support for CQHTTP api. Push notification on CoolQ
+#CQHTTP_TOKEN="" Recommended to be not empty, QQ application token
+#CQHTTP_USER="" Required, QQ receiver ID
+#CQHTTP_APIROOT="" Required, CQHTTP Server URL (without slash suffix)
+#CQHTTP_CUSTOM_MSGHEAD="" Optional, custom message header
+
+CQHTTP_APIPATH="/send_private_msg"
+
+cqhttp_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_statusCode" "$_statusCode"
+
+ CQHTTP_TOKEN="${CQHTTP_TOKEN:-$(_readaccountconf_mutable CQHTTP_TOKEN)}"
+ if [ -z "$CQHTTP_TOKEN" ]; then
+ CQHTTP_TOKEN=""
+ _info "You didn't specify a CQHTTP application token yet, which is unsafe. Assuming it to be empty."
+ else
+ _saveaccountconf_mutable CQHTTP_TOKEN "$CQHTTP_TOKEN"
+ fi
+
+ CQHTTP_USER="${CQHTTP_USER:-$(_readaccountconf_mutable CQHTTP_USER)}"
+ if [ -z "$CQHTTP_USER" ]; then
+ CQHTTP_USER=""
+ _err "You didn't specify a QQ user yet."
+ return 1
+ fi
+ _saveaccountconf_mutable CQHTTP_USER "$CQHTTP_USER"
+
+ CQHTTP_APIROOT="${CQHTTP_APIROOT:-$(_readaccountconf_mutable CQHTTP_APIROOT)}"
+ if [ -z "$CQHTTP_APIROOT" ]; then
+ CQHTTP_APIROOT=""
+ _err "You didn't specify the API root yet."
+ return 1
+ fi
+ _saveaccountconf_mutable CQHTTP_APIROOT "$CQHTTP_APIROOT"
+
+ CQHTTP_CUSTOM_MSGHEAD="${CQHTTP_CUSTOM_MSGHEAD:-$(_readaccountconf_mutable CQHTTP_CUSTOM_MSGHEAD)}"
+ if [ -z "$CQHTTP_CUSTOM_MSGHEAD" ]; then
+ CQHTTP_CUSTOM_MSGHEAD="A message from acme.sh:"
+ else
+ _saveaccountconf_mutable CQHTTP_CUSTOM_MSGHEAD "$CQHTTP_CUSTOM_MSGHEAD"
+ fi
+
+ _access_token="$(printf "%s" "$CQHTTP_TOKEN" | _url_encode)"
+ _user_id="$(printf "%s" "$CQHTTP_USER" | _url_encode)"
+ _message="$(printf "$CQHTTP_CUSTOM_MSGHEAD %s\\n%s" "$_subject" "$_content" | _url_encode)"
+
+ _finalUrl="$CQHTTP_APIROOT$CQHTTP_APIPATH?access_token=$_access_token&user_id=$_user_id&message=$_message"
+ response="$(_get "$_finalUrl")"
+
+ if [ "$?" = "0" ] && _contains "$response" "\"retcode\":0,\"status\":\"ok\""; then
+ _info "QQ send success."
+ return 0
+ fi
+
+ _err "QQ send error."
+ _debug "URL" "$_finalUrl"
+ _debug "Response" "$response"
+ return 1
+}
diff --git a/notify/dingtalk.sh b/notify/dingtalk.sh
new file mode 100644
index 0000000000..c547da6ebf
--- /dev/null
+++ b/notify/dingtalk.sh
@@ -0,0 +1,68 @@
+#!/usr/bin/env sh
+
+#Support dingtalk webhooks api
+
+#DINGTALK_WEBHOOK="xxxx"
+
+#optional
+#DINGTALK_KEYWORD="yyyy"
+
+#DINGTALK_SIGNING_KEY="SEC08ffdbd403cbc3fc8a65xxxxxxxxxxxxxxxxxxxx"
+
+# subject content statusCode
+dingtalk_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_subject" "$_subject"
+ _debug "_content" "$_content"
+ _debug "_statusCode" "$_statusCode"
+
+ DINGTALK_WEBHOOK="${DINGTALK_WEBHOOK:-$(_readaccountconf_mutable DINGTALK_WEBHOOK)}"
+ if [ -z "$DINGTALK_WEBHOOK" ]; then
+ DINGTALK_WEBHOOK=""
+ _err "You didn't specify a dingtalk webhooks DINGTALK_WEBHOOK yet."
+ _err "You can get yours from https://dingtalk.com"
+ return 1
+ fi
+ _saveaccountconf_mutable DINGTALK_WEBHOOK "$DINGTALK_WEBHOOK"
+
+ DINGTALK_KEYWORD="${DINGTALK_KEYWORD:-$(_readaccountconf_mutable DINGTALK_KEYWORD)}"
+ if [ "$DINGTALK_KEYWORD" ]; then
+ _saveaccountconf_mutable DINGTALK_KEYWORD "$DINGTALK_KEYWORD"
+ fi
+
+ # DINGTALK_SIGNING_KEY="${DINGTALK_SIGNING_KEY:-$(_readaccountconf_mutable DINGTALK_SIGNING_KEY)}"
+ # if [ -z "$DINGTALK_SIGNING_KEY" ]; then
+ # DINGTALK_SIGNING_KEY="value1"
+ # _info "The DINGTALK_SIGNING_KEY is not set, so use the default value1 as key."
+ # elif ! _hasfield "$_IFTTT_AVAIL_MSG_KEYS" "$DINGTALK_SIGNING_KEY"; then
+ # _err "The DINGTALK_SIGNING_KEY \"$DINGTALK_SIGNING_KEY\" is not available, should be one of $_IFTTT_AVAIL_MSG_KEYS"
+ # DINGTALK_SIGNING_KEY=""
+ # return 1
+ # else
+ # _saveaccountconf_mutable DINGTALK_SIGNING_KEY "$DINGTALK_SIGNING_KEY"
+ # fi
+
+ # if [ "$DINGTALK_SIGNING_KEY" = "$IFTTT_CONTENT_KEY" ]; then
+ # DINGTALK_SIGNING_KEY=""
+ # IFTTT_CONTENT_KEY=""
+ # _err "The DINGTALK_SIGNING_KEY must not be same as IFTTT_CONTENT_KEY."
+ # return 1
+ # fi
+
+ _content=$(echo "$_content" | _json_encode)
+ _subject=$(echo "$_subject" | _json_encode)
+ _data="{\"msgtype\": \"text\", \"text\": {\"content\": \"[$DINGTALK_KEYWORD]\n$_subject\n$_content\"}}"
+
+ response="$(_post "$_data" "$DINGTALK_WEBHOOK" "" "POST" "application/json")"
+
+ if [ "$?" = "0" ] && _contains "$response" "errmsg\":\"ok"; then
+ _info "dingtalk webhooks event fired success."
+ return 0
+ fi
+
+ _err "dingtalk webhooks event fired error."
+ _err "$response"
+ return 1
+}
diff --git a/notify/mail.sh b/notify/mail.sh
index ec9aa0de1d..54b2a6d4e8 100644
--- a/notify/mail.sh
+++ b/notify/mail.sh
@@ -6,6 +6,7 @@
#MAIL_FROM="yyyy@gmail.com"
#MAIL_TO="yyyy@gmail.com"
#MAIL_NOVALIDATE=""
+#MAIL_MSMTP_ACCOUNT=""
mail_send() {
_subject="$1"
@@ -76,18 +77,17 @@ mail_send() {
}
_mail_bin() {
- if [ -n "$MAIL_BIN" ]; then
- _MAIL_BIN="$MAIL_BIN"
- elif _exists "sendmail"; then
- _MAIL_BIN="sendmail"
- elif _exists "ssmtp"; then
- _MAIL_BIN="ssmtp"
- elif _exists "mutt"; then
- _MAIL_BIN="mutt"
- elif _exists "mail"; then
- _MAIL_BIN="mail"
- else
- _err "Please install sendmail, ssmtp, mutt or mail first."
+ _MAIL_BIN=""
+
+ for b in "$MAIL_BIN" sendmail ssmtp mutt mail msmtp; do
+ if _exists "$b"; then
+ _MAIL_BIN="$b"
+ break
+ fi
+ done
+
+ if [ -z "$_MAIL_BIN" ]; then
+ _err "Please install sendmail, ssmtp, mutt, mail or msmtp first."
return 1
fi
@@ -95,30 +95,35 @@ _mail_bin() {
}
_mail_cmnd() {
+ _MAIL_ARGS=""
+
case $(basename "$_MAIL_BIN") in
sendmail)
if [ -n "$MAIL_FROM" ]; then
- echo "'$_MAIL_BIN' -f '$MAIL_FROM' '$MAIL_TO'"
- else
- echo "'$_MAIL_BIN' '$MAIL_TO'"
+ _MAIL_ARGS="-f '$MAIL_FROM'"
fi
;;
- ssmtp)
- echo "'$_MAIL_BIN' '$MAIL_TO'"
- ;;
mutt | mail)
- echo "'$_MAIL_BIN' -s '$_subject' '$MAIL_TO'"
+ _MAIL_ARGS="-s '$_subject'"
;;
- *)
- _err "Command $MAIL_BIN is not supported, use sendmail, ssmtp, mutt or mail."
- return 1
+ msmtp)
+ if [ -n "$MAIL_FROM" ]; then
+ _MAIL_ARGS="-f '$MAIL_FROM'"
+ fi
+
+ if [ -n "$MAIL_MSMTP_ACCOUNT" ]; then
+ _MAIL_ARGS="$_MAIL_ARGS -a '$MAIL_MSMTP_ACCOUNT'"
+ fi
;;
+ *) ;;
esac
+
+ echo "'$_MAIL_BIN' $_MAIL_ARGS '$MAIL_TO'"
}
_mail_body() {
case $(basename "$_MAIL_BIN") in
- sendmail | ssmtp)
+ sendmail | ssmtp | msmtp)
if [ -n "$MAIL_FROM" ]; then
echo "From: $MAIL_FROM"
fi
diff --git a/notify/mailgun.sh b/notify/mailgun.sh
index 4b6ee3baba..93cdf7939b 100644
--- a/notify/mailgun.sh
+++ b/notify/mailgun.sh
@@ -7,7 +7,7 @@
#MAILGUN_REGION="us|eu" #optional, use "us" as default
#MAILGUN_API_DOMAIN="xxxxxx.com" #optional, use the default sandbox domain
-#MAILGUN_FROM="xxx@xxxxx.com" #optional, use the default sendbox account
+#MAILGUN_FROM="xxx@xxxxx.com" #optional, use the default sandbox account
_MAILGUN_BASE_US="https://api.mailgun.net/v3"
_MAILGUN_BASE_EU="https://api.eu.mailgun.net/v3"
diff --git a/notify/teams.sh b/notify/teams.sh
new file mode 100644
index 0000000000..e50ea703a3
--- /dev/null
+++ b/notify/teams.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env sh
+
+#Support Microsoft Teams webhooks
+
+#TEAMS_WEBHOOK_URL=""
+#TEAMS_THEME_COLOR=""
+#TEAMS_SUCCESS_COLOR=""
+#TEAMS_ERROR_COLOR=""
+#TEAMS_SKIP_COLOR=""
+
+teams_send() {
+ _subject="$1"
+ _content="$2"
+ _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+ _debug "_statusCode" "$_statusCode"
+
+ _color_success="2cbe4e" # green
+ _color_danger="cb2431" # red
+ _color_muted="586069" # gray
+
+ TEAMS_WEBHOOK_URL="${TEAMS_WEBHOOK_URL:-$(_readaccountconf_mutable TEAMS_WEBHOOK_URL)}"
+ if [ -z "$TEAMS_WEBHOOK_URL" ]; then
+ TEAMS_WEBHOOK_URL=""
+ _err "You didn't specify a Microsoft Teams webhook url TEAMS_WEBHOOK_URL yet."
+ return 1
+ fi
+ _saveaccountconf_mutable TEAMS_WEBHOOK_URL "$TEAMS_WEBHOOK_URL"
+
+ TEAMS_THEME_COLOR="${TEAMS_THEME_COLOR:-$(_readaccountconf_mutable TEAMS_THEME_COLOR)}"
+ if [ -n "$TEAMS_THEME_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_THEME_COLOR "$TEAMS_THEME_COLOR"
+ fi
+
+ TEAMS_SUCCESS_COLOR="${TEAMS_SUCCESS_COLOR:-$(_readaccountconf_mutable TEAMS_SUCCESS_COLOR)}"
+ if [ -n "$TEAMS_SUCCESS_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_SUCCESS_COLOR "$TEAMS_SUCCESS_COLOR"
+ fi
+
+ TEAMS_ERROR_COLOR="${TEAMS_ERROR_COLOR:-$(_readaccountconf_mutable TEAMS_ERROR_COLOR)}"
+ if [ -n "$TEAMS_ERROR_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_ERROR_COLOR "$TEAMS_ERROR_COLOR"
+ fi
+
+ TEAMS_SKIP_COLOR="${TEAMS_SKIP_COLOR:-$(_readaccountconf_mutable TEAMS_SKIP_COLOR)}"
+ if [ -n "$TEAMS_SKIP_COLOR" ]; then
+ _saveaccountconf_mutable TEAMS_SKIP_COLOR "$TEAMS_SKIP_COLOR"
+ fi
+
+ export _H1="Content-Type: application/json"
+
+ _subject=$(echo "$_subject" | _json_encode)
+ _content=$(echo "$_content" | _json_encode)
+
+ case "$_statusCode" in
+ 0)
+ _color="${TEAMS_SUCCESS_COLOR:-$_color_success}"
+ ;;
+ 1)
+ _color="${TEAMS_ERROR_COLOR:-$_color_danger}"
+ ;;
+ 2)
+ _color="${TEAMS_SKIP_COLOR:-$_color_muted}"
+ ;;
+ esac
+
+ _color=$(echo "$_color" | tr -cd 'a-fA-F0-9')
+ if [ -z "$_color" ]; then
+ _color=$(echo "${TEAMS_THEME_COLOR:-$_color_muted}" | tr -cd 'a-fA-F0-9')
+ fi
+
+ _data="{\"title\": \"$_subject\","
+ if [ -n "$_color" ]; then
+ _data="$_data\"themeColor\": \"$_color\", "
+ fi
+ _data="$_data\"text\": \"$_content\"}"
+
+ if response=$(_post "$_data" "$TEAMS_WEBHOOK_URL"); then
+ if ! _contains "$response" error; then
+ _info "teams send success."
+ return 0
+ fi
+ fi
+ _err "teams send error."
+ _err "$response"
+ return 1
+}