diff --git a/.github/auto-comment.yml b/.github/auto-comment.yml
index 1e7b389e..520b3ce3 100644
--- a/.github/auto-comment.yml
+++ b/.github/auto-comment.yml
@@ -4,17 +4,23 @@ issuesOpened: >
如果有 bug, 请先更新到最新版试试:
- ```sh
+ ```
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.
+ 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
@@ -23,6 +29,12 @@ pullRequestOpened: >
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 00000000..f1c0025d
--- /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 155ec64b..a9785d0c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,5 @@
language: shell
-sudo: required
-dist: trusty
+dist: bionic
os:
- linux
diff --git a/Dockerfile b/Dockerfile
index 8a923ce8..147e70de 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM alpine:3.10
+FROM alpine:3.12
RUN apk update -f \
&& apk --no-cache add -f \
diff --git a/README.md b/README.md
index 249dc85f..e50489dc 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,13 @@
# 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)
+[](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub")
+[](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub")
+
+
+acme.sh is being sponsored by the following tool; please help to support us by taking a look and signing up to a free trial
+
+
- An ACME protocol client written purely in Shell (Unix shell) language.
- Full ACME protocol implementation.
- Support ACME v1 and ACME v2
@@ -29,7 +36,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
# Who:
- [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/)
- [ruby-china.org](https://ruby-china.org/topics/31983)
-- [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer))
+- [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management)
- [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89)
- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt)
- [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty)
@@ -46,27 +53,27 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
| NO | Status| Platform|
|----|-------|---------|
-|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
+|1|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Ubuntu
+|2|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Debian
+|3|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|CentOS
+|4|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included)
+|5|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|FreeBSD
+|6|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|pfsense
+|7|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|openSUSE
+|8|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Alpine Linux (with curl)
+|9|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Archlinux
+|10|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|fedora
+|11|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Kali Linux
+|12|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Oracle Linux
+|13|[](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
|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
+|15|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|OpenBSD
+|16|[](https://github.com/acmesh-official/acmetest#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
+|18|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|SunOS/Solaris
+|19|[](https://github.com/acmesh-official/acmetest#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
+|21|[](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|ClearLinux
For all build statuses, check our [weekly build project](https://github.com/acmesh-official/acmetest):
@@ -75,6 +82,7 @@ https://github.com/acmesh-official/acmetest
# Supported CA
- Letsencrypt.org CA(default)
+- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)
- [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA)
- [Pebble strict Mode](https://github.com/letsencrypt/pebble)
@@ -246,7 +254,7 @@ More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-ce
**(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.
@@ -266,7 +274,7 @@ More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-ce
**(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.
@@ -300,7 +308,7 @@ https://github.com/acmesh-official/acme.sh/wiki/dnsapi
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.
+If your dns provider doesn't support any api access, you can add the txt record by hand.
```bash
acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com
diff --git a/acme.sh b/acme.sh
index f672710d..a7e6d7ef 100755
--- a/acme.sh
+++ b/acme.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
-VER=2.8.6
+VER=2.8.7
PROJECT_NAME="acme.sh"
@@ -23,23 +23,35 @@ _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY"
LETSENCRYPT_CA_V1="https://acme-v01.api.letsencrypt.org/directory"
LETSENCRYPT_STAGING_CA_V1="https://acme-staging.api.letsencrypt.org/directory"
-LETSENCRYPT_CA_V2="https://acme-v02.api.letsencrypt.org/directory"
-LETSENCRYPT_STAGING_CA_V2="https://acme-staging-v02.api.letsencrypt.org/directory"
+CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory"
+CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory"
-DEFAULT_CA=$LETSENCRYPT_CA_V2
-DEFAULT_STAGING_CA=$LETSENCRYPT_STAGING_CA_V2
+CA_BUYPASS="https://api.buypass.com/acme/directory"
+CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory"
+
+CA_ZEROSSL="https://acme.zerossl.com/v2/DV90"
+_ZERO_EAB_ENDPOINT="http://api.zerossl.com/acme/eab-credentials-email"
+
+DEFAULT_CA=$CA_LETSENCRYPT_V2
+DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST
+
+CA_NAMES="
+Letsencrypt.org,letsencrypt
+Letsencrypt.org_test,letsencrypt_test,letsencrypttest
+BuyPass.com,buypass
+BuyPass.com_test,buypass_test,buypasstest
+ZeroSSL.com,zerossl
+"
+
+CA_SERVERS="$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_ZEROSSL"
DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
-DEFAULT_ACCOUNT_EMAIL=""
DEFAULT_ACCOUNT_KEY_LENGTH=2048
DEFAULT_DOMAIN_KEY_LENGTH=2048
DEFAULT_OPENSSL_BIN="openssl"
-_OLD_CA_HOST="https://acme-v01.api.letsencrypt.org"
-_OLD_STAGE_CA_HOST="https://acme-staging.api.letsencrypt.org"
-
VTYPE_HTTP="http-01"
VTYPE_DNS="dns-01"
VTYPE_ALPN="tls-alpn-01"
@@ -138,6 +150,12 @@ _NOTIFY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/notify"
_SUDO_WIKI="https://github.com/acmesh-official/acme.sh/wiki/sudo"
+_REVOKE_WIKI="https://github.com/acmesh-official/acme.sh/wiki/revokecert"
+
+_ZEROSSL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA"
+
+_SERVER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Server"
+
_DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead."
_DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR"
@@ -186,28 +204,28 @@ _dlg_versions() {
if _exists "${ACME_OPENSSL_BIN:-openssl}"; then
${ACME_OPENSSL_BIN:-openssl} version 2>&1
else
- echo "$ACME_OPENSSL_BIN doesn't exists."
+ echo "$ACME_OPENSSL_BIN doesn't exist."
fi
echo "apache:"
if [ "$_APACHECTL" ] && _exists "$_APACHECTL"; then
$_APACHECTL -V 2>&1
else
- echo "apache doesn't exists."
+ echo "apache doesn't exist."
fi
echo "nginx:"
if _exists "nginx"; then
nginx -V 2>&1
else
- echo "nginx doesn't exists."
+ echo "nginx doesn't exist."
fi
echo "socat:"
if _exists "socat"; then
socat -V 2>&1
else
- _debug "socat doesn't exists."
+ _debug "socat doesn't exist."
fi
}
@@ -1001,7 +1019,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
@@ -1012,8 +1030,32 @@ _sign() {
fi
_debug3 "_signedECText" "$_signedECText"
_ec_r="$(echo "$_signedECText" | _head_n 2 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
- _debug3 "_ec_r" "$_ec_r"
_ec_s="$(echo "$_signedECText" | _head_n 3 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")"
+ if [ "$__ECC_KEY_LEN" -eq "256" ]; then
+ while [ "${#_ec_r}" -lt "64" ]; do
+ _ec_r="0${_ec_r}"
+ done
+ while [ "${#_ec_s}" -lt "64" ]; do
+ _ec_s="0${_ec_s}"
+ done
+ fi
+ if [ "$__ECC_KEY_LEN" -eq "384" ]; then
+ while [ "${#_ec_r}" -lt "96" ]; do
+ _ec_r="0${_ec_r}"
+ done
+ while [ "${#_ec_s}" -lt "96" ]; do
+ _ec_s="0${_ec_s}"
+ done
+ fi
+ if [ "$__ECC_KEY_LEN" -eq "512" ]; then
+ while [ "${#_ec_r}" -lt "132" ]; do
+ _ec_r="0${_ec_r}"
+ done
+ while [ "${#_ec_s}" -lt "132" ]; do
+ _ec_s="0${_ec_s}"
+ done
+ fi
+ _debug3 "_ec_r" "$_ec_r"
_debug3 "_ec_s" "$_ec_s"
printf "%s" "$_ec_r$_ec_s" | _h2b | _base64
else
@@ -1172,9 +1214,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
@@ -1482,6 +1523,19 @@ _url_replace() {
tr '/+' '_-' | tr -d '= '
}
+#base64 string
+_durl_replace_base64() {
+ _l=$((${#1} % 4))
+ if [ $_l -eq 2 ]; then
+ _s="$1"'=='
+ elif [ $_l -eq 3 ]; then
+ _s="$1"'='
+ else
+ _s="$1"
+ fi
+ echo "$_s" | tr '_-' '/+'
+}
+
_time2str() {
#BSD
if date -u -r "$1" 2>/dev/null; then
@@ -1985,7 +2039,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}\""'}'
@@ -2536,17 +2592,18 @@ _initpath() {
CA_HOME="$DEFAULT_CA_HOME"
fi
- if [ "$ACME_VERSION" = "2" ]; then
- DEFAULT_CA="$LETSENCRYPT_CA_V2"
- DEFAULT_STAGING_CA="$LETSENCRYPT_STAGING_CA_V2"
- fi
-
if [ -z "$ACME_DIRECTORY" ]; then
- if [ -z "$STAGE" ]; then
- ACME_DIRECTORY="$DEFAULT_CA"
- else
+ if [ "$STAGE" ]; then
ACME_DIRECTORY="$DEFAULT_STAGING_CA"
_info "Using stage ACME_DIRECTORY: $ACME_DIRECTORY"
+ else
+ default_acme_server=$(_readaccountconf "DEFAULT_ACME_SERVER")
+ _debug default_acme_server "$default_acme_server"
+ if [ "$default_acme_server" ]; then
+ ACME_DIRECTORY="$default_acme_server"
+ else
+ ACME_DIRECTORY="$DEFAULT_CA"
+ fi
fi
fi
@@ -2798,10 +2855,10 @@ _setApache() {
apacheVer="$($_APACHECTL -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2)"
_debug "apacheVer" "$apacheVer"
- apacheMajer="$(echo "$apacheVer" | cut -d . -f 1)"
+ apacheMajor="$(echo "$apacheVer" | cut -d . -f 1)"
apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)"
- if [ "$apacheVer" ] && [ "$apacheMajer$apacheMinor" -ge "24" ]; then
+ if [ "$apacheVer" ] && [ "$apacheMajor$apacheMinor" -ge "24" ]; then
echo "
Alias /.well-known/acme-challenge $ACME_DIR
@@ -3379,10 +3436,13 @@ _on_issue_success() {
}
+#account_key_length eab-kid eab-hmac-key
registeraccount() {
- _reg_length="$1"
+ _account_key_length="$1"
+ _eab_id="$2"
+ _eab_hmac_key="$3"
_initpath
- _regAccount "$_reg_length"
+ _regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key"
}
__calcAccountKeyHash() {
@@ -3393,10 +3453,27 @@ __calc_account_thumbprint() {
printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace
}
+_getAccountEmail() {
+ if [ "$ACCOUNT_EMAIL" ]; then
+ echo "$ACCOUNT_EMAIL"
+ return 0
+ fi
+ if [ -z "$CA_EMAIL" ]; then
+ CA_EMAIL="$(_readcaconf CA_EMAIL)"
+ fi
+ if [ "$CA_EMAIL" ]; then
+ echo "$CA_EMAIL"
+ return 0
+ fi
+ _readaccountconf "ACCOUNT_EMAIL"
+}
+
#keylength
_regAccount() {
_initpath
_reg_length="$1"
+ _eab_id="$2"
+ _eab_hmac_key="$3"
_debug3 _regAccount "$_regAccount"
_initAPI
@@ -3421,46 +3498,115 @@ _regAccount() {
if ! _calcjwk "$ACCOUNT_KEY_PATH"; then
return 1
fi
-
+ if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
+ _savecaconf CA_EAB_KEY_ID "$_eab_id"
+ _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
+ fi
+ _eab_id=$(_readcaconf "CA_EAB_KEY_ID")
+ _eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY")
+ _secure_debug3 _eab_id "$_eab_id"
+ _secure_debug3 _eab_hmac_key "$_eab_hmac_key"
+ _email="$(_getAccountEmail)"
+ if [ "$_email" ]; then
+ _savecaconf "CA_EMAIL" "$_email"
+ fi
if [ "$ACME_VERSION" = "2" ]; then
- regjson='{"termsOfServiceAgreed": true}'
- if [ "$ACCOUNT_EMAIL" ]; then
- regjson='{"contact": ["mailto:'$ACCOUNT_EMAIL'"], "termsOfServiceAgreed": true}'
+ if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then
+ if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then
+ _info "No EAB credentials found for ZeroSSL, let's get one"
+ if [ -z "$_email" ]; then
+ _err "Please provide a email address for ZeroSSL account."
+ _err "See ZeroSSL usage: $_ZEROSSL_WIKI"
+ return 1
+ fi
+ _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT)
+ if [ "$?" != "0" ]; then
+ _debug2 "$_eabresp"
+ _err "Can not get EAB credentials from ZeroSSL."
+ return 1
+ fi
+ _eab_id="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
+ if [ -z "$_eab_id" ]; then
+ _err "Can not resolve _eab_id"
+ return 1
+ fi
+ _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')"
+ if [ -z "$_eab_hmac_key" ]; then
+ _err "Can not resolve _eab_hmac_key"
+ return 1
+ fi
+ _savecaconf CA_EAB_KEY_ID "$_eab_id"
+ _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
+ fi
fi
+ if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
+ eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
+ _debug3 eab_protected "$eab_protected"
+
+ eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace)
+ _debug3 eab_protected64 "$eab_protected64"
+
+ eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace)
+ _debug3 eab_payload64 "$eab_payload64"
+
+ eab_sign_t="$eab_protected64.$eab_payload64"
+ _debug3 eab_sign_t "$eab_sign_t"
+
+ key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')"
+ _debug3 key_hex "$key_hex"
+
+ eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)
+ _debug3 eab_signature "$eab_signature"
+
+ externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}"
+ _debug3 externalBinding "$externalBinding"
+ fi
+ if [ "$_email" ]; then
+ email_sg="\"contact\": [\"mailto:$_email\"], "
+ fi
+ regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}"
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'"}'
+ if [ "$_email" ]; then
+ regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$_email'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
fi
fi
- _info "Registering account"
+ _info "Registering account: $ACME_DIRECTORY"
if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then
_err "Register account Error: $response"
return 1
fi
+ _eabAlreadyBound=""
if [ "$code" = "" ] || [ "$code" = '201' ]; then
echo "$response" >"$ACCOUNT_JSON_PATH"
_info "Registered"
elif [ "$code" = '409' ] || [ "$code" = '200' ]; then
_info "Already registered"
+ elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then
+ _info "Already register EAB."
+ _eabAlreadyBound=1
else
_err "Register account Error: $response"
return 1
fi
- _debug2 responseHeaders "$responseHeaders"
- _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."
- _err "$responseHeaders"
- return 1
+ if [ -z "$_eabAlreadyBound" ]; then
+ _debug2 responseHeaders "$responseHeaders"
+ _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."
+ _err "$responseHeaders"
+ return 1
+ fi
+ _savecaconf "ACCOUNT_URL" "$_accUri"
+ else
+ ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)"
fi
- _savecaconf "ACCOUNT_URL" "$_accUri"
export ACCOUNT_URL="$_accUri"
CA_KEY_HASH="$(__calcAccountKeyHash)"
@@ -3509,9 +3655,12 @@ updateaccount() {
fi
_initAPI
+ _email="$(_getAccountEmail)"
if [ "$ACME_VERSION" = "2" ]; then
if [ "$ACCOUNT_EMAIL" ]; then
- updjson='{"contact": ["mailto:'$ACCOUNT_EMAIL'"]}'
+ updjson='{"contact": ["mailto:'$_email'"]}'
+ else
+ updjson='{"contact": []}'
fi
else
# ACMEv1: Updates happen the same way a registration is done.
@@ -3931,13 +4080,10 @@ issue() {
_cleardomainconf "Le_ChallengeAlias"
fi
- if [ "$ACME_DIRECTORY" != "$DEFAULT_CA" ]; then
- Le_API="$ACME_DIRECTORY"
- _savedomainconf "Le_API" "$Le_API"
- else
- _cleardomainconf Le_API
- fi
+ Le_API="$ACME_DIRECTORY"
+ _savedomainconf "Le_API" "$Le_API"
+ _info "Using CA: $ACME_DIRECTORY"
if [ "$_alt_domains" = "$NO_VALUE" ]; then
_alt_domains=""
fi
@@ -4093,17 +4239,17 @@ $_authorizations_map"
if [ "$ACME_VERSION" = "2" ]; then
_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
+ _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")"
+ _debug2 _candidates "$_candidates"
+ if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then
+ for _can in $_candidates; do
if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then
- _candindates="$_can"
+ _candidates="$_can"
break
fi
done
fi
- response="$(echo "$_candindates" | sed "s/$_idn_d,//")"
+ response="$(echo "$_candidates" | sed "s/$_idn_d,//")"
_debug2 "response" "$response"
if [ -z "$response" ]; then
_err "get to authz error."
@@ -4294,7 +4440,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."
@@ -4563,7 +4709,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"
@@ -4816,14 +4969,6 @@ renew() {
fi
if [ "$Le_API" ]; then
- if [ "$_OLD_CA_HOST" = "$Le_API" ]; then
- export Le_API="$DEFAULT_CA"
- _savedomainconf Le_API "$Le_API"
- fi
- if [ "$_OLD_STAGE_CA_HOST" = "$Le_API" ]; then
- export Le_API="$DEFAULT_STAGING_CA"
- _savedomainconf Le_API "$Le_API"
- fi
export ACME_DIRECTORY="$Le_API"
#reload ca configs
ACCOUNT_KEY_PATH=""
@@ -5089,7 +5234,7 @@ list() {
_sep="|"
if [ "$_raw" ]; then
- printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Created${_sep}Renew"
+ printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew"
for di in "${CERT_HOME}"/*.*/; do
d=$(basename "$di")
_debug d "$d"
@@ -5101,7 +5246,8 @@ list() {
DOMAIN_CONF="$di/$d.conf"
if [ -f "$DOMAIN_CONF" ]; then
. "$DOMAIN_CONF"
- printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr"
+ _ca="$(_getCAShortName "$Le_API")"
+ printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr"
fi
)
done
@@ -5454,6 +5600,7 @@ uninstallcronjob() {
}
+#domain isECC revokeReason
revoke() {
Le_Domain="$1"
if [ -z "$Le_Domain" ]; then
@@ -5462,7 +5609,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."
@@ -5484,7 +5634,7 @@ revoke() {
_initAPI
if [ "$ACME_VERSION" = "2" ]; then
- data="{\"certificate\": \"$cert\"}"
+ data="{\"certificate\": \"$cert\",\"reason\":$_reason}"
else
data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
fi
@@ -5503,7 +5653,7 @@ revoke() {
fi
fi
else
- _info "Domain key file doesn't exists."
+ _info "Domain key file doesn't exist."
fi
_info "Try account key."
@@ -5602,7 +5752,7 @@ _deactivate() {
_URL_NAME="uri"
fi
- entries="$(echo "$response" | _egrep_o "{ *\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")"
+ entries="$(echo "$response" | _egrep_o "[^{]*\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")"
if [ -z "$entries" ]; then
_info "No valid entries found."
if [ -z "$thumbprint" ]; then
@@ -6214,6 +6364,7 @@ Commands:
--createCSR, -ccsr Create CSR , professional use.
--deactivate Deactivate the domain authz, professional use.
--set-notify Set the cron notification hook, level or mode.
+ --set-default-ca Used with '--server' , to set the default CA to use to use.
Parameters:
@@ -6238,6 +6389,10 @@ Parameters:
--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.
+ --eab-kid EAB_KID Key Identifier for External Account Binding.
+ --eab-hmac-key EAB_HMAC_KEY HMAC key for External Account Binding.
+
+
These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert:
--cert-file After issue/renew, the cert will be copied to this path.
@@ -6247,13 +6402,13 @@ Parameters:
--reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server.
- --server SERVER ACME Directory Resource URI. (default: $DEFAULT_CA)
+ --server SERVER ACME Directory Resource URI. See: $_SERVER_WIKI (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.
--config-home Specifies the home dir to save all the configurations.
--useragent Specifies the user agent string. it will be saved for future use too.
- --accountemail Specifies the account email, only valid for the '--install' and '--update-account' command.
+ --accountemail, -m Specifies the account email, only valid for the '--install' and '--update-account' command.
--accountkey Specifies the account key path, only valid for the '--install' command.
--days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days.
--httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer.
@@ -6293,6 +6448,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
"
}
@@ -6375,12 +6531,6 @@ _processAccountConf() {
_saveaccountconf "USER_AGENT" "$USER_AGENT"
fi
- if [ "$_accountemail" ]; then
- _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail"
- elif [ "$ACCOUNT_EMAIL" ] && [ "$ACCOUNT_EMAIL" != "$DEFAULT_ACCOUNT_EMAIL" ]; then
- _saveaccountconf "ACCOUNT_EMAIL" "$ACCOUNT_EMAIL"
- fi
-
if [ "$_openssl_bin" ]; then
_saveaccountconf "ACME_OPENSSL_BIN" "$_openssl_bin"
elif [ "$ACME_OPENSSL_BIN" ] && [ "$ACME_OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then
@@ -6407,10 +6557,10 @@ _checkSudo() {
#it's root using sudo, no matter it's using sudo or not, just fine
return 0
fi
- if [ "$SUDO_COMMAND" = "/bin/su" ] || [ "$SUDO_COMMAND" = "/bin/bash" ]; then
+ if [ -n "$SUDO_COMMAND" ]; then
#it's a normal user doing "sudo su", or `sudo -i` or `sudo -s`
- #fine
- return 0
+ _endswith "$SUDO_COMMAND" /bin/su || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1
+ return $?
fi
#otherwise
return 1
@@ -6418,6 +6568,61 @@ _checkSudo() {
return 0
}
+#server
+_selectServer() {
+ _server="$1"
+ _server_lower="$(echo "$_server" | _lower_case)"
+ _sindex=0
+ for snames in $CA_NAMES; do
+ snames="$(echo "$snames" | _lower_case)"
+ _sindex="$(_math $_sindex + 1)"
+ _debug2 "_selectServer try snames" "$snames"
+ for sname in $(echo "$snames" | tr ',' ' '); do
+ if [ "$_server_lower" = "$sname" ]; then
+ _debug2 "_selectServer match $sname"
+ _serverdir="$(_getfield "$CA_SERVERS" $_sindex)"
+ _debug "Selected server: $_serverdir"
+ ACME_DIRECTORY="$_serverdir"
+ export ACME_DIRECTORY
+ return
+ fi
+ done
+ done
+ ACME_DIRECTORY="$_server"
+ export ACME_DIRECTORY
+}
+
+#url
+_getCAShortName() {
+ caurl="$1"
+ caurl_lower="$(echo $caurl | _lower_case)"
+ _sindex=0
+ for surl in $(echo "$CA_SERVERS" | _lower_case | tr , ' '); do
+ _sindex="$(_math $_sindex + 1)"
+ if [ "$caurl_lower" = "$surl" ]; then
+ _nindex=0
+ for snames in $CA_NAMES; do
+ _nindex="$(_math $_nindex + 1)"
+ if [ $_nindex -ge $_sindex ]; then
+ _getfield "$snames" 1
+ return
+ fi
+ done
+ fi
+ done
+ echo "$caurl"
+}
+
+#set default ca to $ACME_DIRECTORY
+setdefaultca() {
+ if [ -z "$ACME_DIRECTORY" ]; then
+ _err "Please give a --server parameter."
+ return 1
+ fi
+ _saveaccountconf "DEFAULT_ACME_SERVER" "$ACME_DIRECTORY"
+ _info "Changed default CA to: $(__green "$ACME_DIRECTORY")"
+}
+
_process() {
_CMD=""
_domain=""
@@ -6468,6 +6673,9 @@ _process() {
_notify_hook=""
_notify_level=""
_notify_mode=""
+ _revoke_reason=""
+ _eab_kid=""
+ _eab_hmac_key=""
while [ ${#} -gt 0 ]; do
case "${1}" in
@@ -6557,6 +6765,9 @@ _process() {
--set-notify)
_CMD="setnotify"
;;
+ --set-default-ca)
+ _CMD="setdefaultca"
+ ;;
--domain | -d)
_dvalue="$2"
@@ -6595,9 +6806,8 @@ _process() {
STAGE="1"
;;
--server)
- ACME_DIRECTORY="$2"
- _server="$ACME_DIRECTORY"
- export ACME_DIRECTORY
+ _server="$2"
+ _selectServer "$_server"
shift
;;
--debug)
@@ -6754,7 +6964,7 @@ _process() {
USER_AGENT="$_useragent"
shift
;;
- --accountemail)
+ --accountemail | -m)
_accountemail="$2"
ACCOUNT_EMAIL="$_accountemail"
shift
@@ -6940,6 +7150,22 @@ _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
+ ;;
+ --eab-kid)
+ _eab_kid="$2"
+ shift
+ ;;
+ --eab-hmac-key)
+ _eab_hmac_key="$2"
+ shift
+ ;;
*)
_err "Unknown parameter : $1"
return 1
@@ -7027,7 +7253,7 @@ _process() {
renewAll "$_stopRenewOnError"
;;
revoke)
- revoke "$_domain" "$_ecc"
+ revoke "$_domain" "$_ecc" "$_revoke_reason"
;;
remove)
remove "$_domain" "$_ecc"
@@ -7036,7 +7262,7 @@ _process() {
deactivate "$_domain,$_altdomains"
;;
registeraccount)
- registeraccount "$_accountkeylength"
+ registeraccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key"
;;
updateaccount)
updateaccount
@@ -7068,6 +7294,9 @@ _process() {
setnotify)
setnotify "$_notify_hook" "$_notify_level" "$_notify_mode"
;;
+ setdefaultca)
+ setdefaultca
+ ;;
*)
if [ "$_CMD" ]; then
_err "Invalid command: $_CMD"
diff --git a/deploy/docker.sh b/deploy/docker.sh
index 06d79855..451d5d00 100755
--- a/deploy/docker.sh
+++ b/deploy/docker.sh
@@ -91,7 +91,7 @@ docker_deploy() {
_getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
_debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then
- _savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"
+ _savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" "base64"
fi
_cid="$(_get_id "$DEPLOY_DOCKER_CONTAINER_LABEL")"
diff --git a/deploy/haproxy.sh b/deploy/haproxy.sh
index 3cd2a80a..0a45ee07 100644
--- a/deploy/haproxy.sh
+++ b/deploy/haproxy.sh
@@ -233,7 +233,6 @@ haproxy_deploy() {
-header Host${_header_sep}\"${_ocsp_host}\" \
-respout \"${_ocsp}\" \
-verify_other \"${_issuer}\" \
- -no_nonce \
${_cafile_argument} \
| grep -q \"${_pem}: good\""
_debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}"
diff --git a/deploy/kong.sh b/deploy/kong.sh
index d3a6bc47..1e1e310c 100755
--- a/deploy/kong.sh
+++ b/deploy/kong.sh
@@ -1,6 +1,6 @@
#!/usr/bin/env sh
-# If certificate already exist it will update only cert and key not touching other parameter
-# If certificate doesn't exist it will only upload cert and key and not set other parameter
+# If certificate already exists it will update only cert and key, not touching other parameters
+# If certificate doesn't exist it will only upload cert and key, and not set other parameters
# Note that we deploy full chain
# Written by Geoffroi Genot
diff --git a/deploy/openstack.sh b/deploy/openstack.sh
new file mode 100644
index 00000000..f2058853
--- /dev/null
+++ b/deploy/openstack.sh
@@ -0,0 +1,262 @@
+#!/usr/bin/env sh
+
+# OpenStack Barbican deploy hook
+#
+# This requires you to have OpenStackClient and python-barbicanclient
+# installed.
+#
+# You will require Keystone V3 credentials loaded into your environment, which
+# could be either password or v3applicationcredential type.
+#
+# Author: Andy Botting
+
+openstack_deploy() {
+ _cdomain="$1"
+ _ckey="$2"
+ _ccert="$3"
+ _cca="$4"
+ _cfullchain="$5"
+
+ _debug _cdomain "$_cdomain"
+ _debug _ckey "$_ckey"
+ _debug _ccert "$_ccert"
+ _debug _cca "$_cca"
+ _debug _cfullchain "$_cfullchain"
+
+ if ! _exists openstack; then
+ _err "OpenStack client not found"
+ return 1
+ fi
+
+ _openstack_credentials || return $?
+
+ _info "Generate import pkcs12"
+ _import_pkcs12="$(_mktemp)"
+ if ! _openstack_to_pkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca"; then
+ _err "Error creating pkcs12 certificate"
+ return 1
+ fi
+ _debug _import_pkcs12 "$_import_pkcs12"
+ _base64_pkcs12=$(_base64 "multiline" <"$_import_pkcs12")
+
+ secretHrefs=$(_openstack_get_secrets)
+ _debug secretHrefs "$secretHrefs"
+ _openstack_store_secret || return $?
+
+ if [ -n "$secretHrefs" ]; then
+ _info "Cleaning up existing secret"
+ _openstack_delete_secrets || return $?
+ fi
+
+ _info "Certificate successfully deployed"
+ return 0
+}
+
+_openstack_store_secret() {
+ if ! openstack secret store --name "$_cdomain." -t 'application/octet-stream' -e base64 --payload "$_base64_pkcs12"; then
+ _err "Failed to create OpenStack secret"
+ return 1
+ fi
+ return
+}
+
+_openstack_delete_secrets() {
+ echo "$secretHrefs" | while read -r secretHref; do
+ _info "Deleting old secret $secretHref"
+ if ! openstack secret delete "$secretHref"; then
+ _err "Failed to delete OpenStack secret"
+ return 1
+ fi
+ done
+ return
+}
+
+_openstack_get_secrets() {
+ if ! secretHrefs=$(openstack secret list -f value --name "$_cdomain." | cut -d' ' -f1); then
+ _err "Failed to list secrets"
+ return 1
+ fi
+ echo "$secretHrefs"
+}
+
+_openstack_to_pkcs() {
+ # The existing _toPkcs command can't allow an empty password, due to sh
+ # -z test, so copied here and forcing the empty password.
+ _cpfx="$1"
+ _ckey="$2"
+ _ccert="$3"
+ _cca="$4"
+
+ ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:"
+}
+
+_openstack_credentials() {
+ _debug "Check OpenStack credentials"
+
+ # If we have OS_AUTH_URL already set in the environment, then assume we want
+ # to use those, otherwise use stored credentials
+ if [ -n "$OS_AUTH_URL" ]; then
+ _debug "OS_AUTH_URL env var found, using environment"
+ else
+ _debug "OS_AUTH_URL not found, loading stored credentials"
+ OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}"
+ OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}"
+ OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}"
+ OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}"
+ OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}"
+ OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}"
+ OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}"
+ OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}"
+ OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}"
+ OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}"
+ OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}"
+ OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}"
+ OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}"
+ fi
+
+ # Check each var and either save or clear it depending on whether its set.
+ # The helps us clear out old vars in the case where a user may want
+ # to switch between password and app creds
+ _debug "OS_AUTH_URL" "$OS_AUTH_URL"
+ if [ -n "$OS_AUTH_URL" ]; then
+ export OS_AUTH_URL
+ _saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL"
+ else
+ unset OS_AUTH_URL
+ _clearaccountconf SAVED_OS_AUTH_URL
+ fi
+
+ _debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION"
+ if [ -n "$OS_IDENTITY_API_VERSION" ]; then
+ export OS_IDENTITY_API_VERSION
+ _saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION"
+ else
+ unset OS_IDENTITY_API_VERSION
+ _clearaccountconf SAVED_OS_IDENTITY_API_VERSION
+ fi
+
+ _debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE"
+ if [ -n "$OS_AUTH_TYPE" ]; then
+ export OS_AUTH_TYPE
+ _saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE"
+ else
+ unset OS_AUTH_TYPE
+ _clearaccountconf SAVED_OS_AUTH_TYPE
+ fi
+
+ _debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID"
+ if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then
+ export OS_APPLICATION_CREDENTIAL_ID
+ _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID"
+ else
+ unset OS_APPLICATION_CREDENTIAL_ID
+ _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID
+ fi
+
+ _secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET"
+ if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
+ export OS_APPLICATION_CREDENTIAL_SECRET
+ _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET"
+ else
+ unset OS_APPLICATION_CREDENTIAL_SECRET
+ _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET
+ fi
+
+ _debug "OS_USERNAME" "$OS_USERNAME"
+ if [ -n "$OS_USERNAME" ]; then
+ export OS_USERNAME
+ _saveaccountconf_mutable OS_USERNAME "$OS_USERNAME"
+ else
+ unset OS_USERNAME
+ _clearaccountconf SAVED_OS_USERNAME
+ fi
+
+ _secure_debug "OS_PASSWORD" "$OS_PASSWORD"
+ if [ -n "$OS_PASSWORD" ]; then
+ export OS_PASSWORD
+ _saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD"
+ else
+ unset OS_PASSWORD
+ _clearaccountconf SAVED_OS_PASSWORD
+ fi
+
+ _debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME"
+ if [ -n "$OS_PROJECT_NAME" ]; then
+ export OS_PROJECT_NAME
+ _saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME"
+ else
+ unset OS_PROJECT_NAME
+ _clearaccountconf SAVED_OS_PROJECT_NAME
+ fi
+
+ _debug "OS_PROJECT_ID" "$OS_PROJECT_ID"
+ if [ -n "$OS_PROJECT_ID" ]; then
+ export OS_PROJECT_ID
+ _saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID"
+ else
+ unset OS_PROJECT_ID
+ _clearaccountconf SAVED_OS_PROJECT_ID
+ fi
+
+ _debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME"
+ if [ -n "$OS_USER_DOMAIN_NAME" ]; then
+ export OS_USER_DOMAIN_NAME
+ _saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME"
+ else
+ unset OS_USER_DOMAIN_NAME
+ _clearaccountconf SAVED_OS_USER_DOMAIN_NAME
+ fi
+
+ _debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID"
+ if [ -n "$OS_USER_DOMAIN_ID" ]; then
+ export OS_USER_DOMAIN_ID
+ _saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID"
+ else
+ unset OS_USER_DOMAIN_ID
+ _clearaccountconf SAVED_OS_USER_DOMAIN_ID
+ fi
+
+ _debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME"
+ if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then
+ export OS_PROJECT_DOMAIN_NAME
+ _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME"
+ else
+ unset OS_PROJECT_DOMAIN_NAME
+ _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME
+ fi
+
+ _debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID"
+ if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then
+ export OS_PROJECT_DOMAIN_ID
+ _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID"
+ else
+ unset OS_PROJECT_DOMAIN_ID
+ _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID
+ fi
+
+ if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then
+ # Application Credential auth
+ if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
+ _err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID"
+ _err "and OS_APPLICATION_CREDENTIAL_SECRET must be set."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+ else
+ # Password auth
+ if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then
+ _err "OpenStack username or password not found."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+
+ if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then
+ _err "When using password authentication, OS_PROJECT_NAME or"
+ _err "OS_PROJECT_ID must be set."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+ fi
+
+ return 0
+}
diff --git a/deploy/ssh.sh b/deploy/ssh.sh
index 9cb0af9e..06d4b2b4 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
index 5aef3b93..2ec0ceb3 100644
--- a/deploy/synology_dsm.sh
+++ b/deploy/synology_dsm.sh
@@ -22,7 +22,7 @@
######## Public functions #####################
_syno_get_cookie_data() {
- grep "\W$1=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';'
+ grep -i "\W$1=" | grep -i "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';'
}
#domain keyfile certfile cafile fullchain
@@ -40,9 +40,7 @@ synology_dsm_deploy() {
_getdeployconf SYNO_Password
_getdeployconf SYNO_Create
_getdeployconf SYNO_DID
- if [ -z "$SYNO_Username" ] || [ -z "$SYNO_Password" ]; then
- SYNO_Username=""
- SYNO_Password=""
+ if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then
_err "SYNO_Username & SYNO_Password must be set"
return 1
fi
@@ -70,20 +68,20 @@ synology_dsm_deploy() {
# Get the certificate description, but don't save it until we verfiy it's real
_getdeployconf SYNO_Certificate
- if [ -z "${SYNO_Certificate:?}" ]; then
- _err "SYNO_Certificate needs to be defined (with the Certificate description name)"
- return 1
- fi
- _debug SYNO_Certificate "$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"
- response=$(_get "$_base_url/webman/login.cgi?username=$SYNO_Username&passwd=$SYNO_Password&enable_syno_token=yes&device_id=$SYNO_DID")
- token=$(echo "$response" | grep "SynoToken" | sed -n 's/.*"SynoToken" *: *"\([^"]*\).*/\1/p')
+ 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 -i "X-SYNO-TOKEN:" | sed -n 's/^X-SYNO-TOKEN: \(.*\)$/\1/pI' | 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."
@@ -91,7 +89,7 @@ synology_dsm_deploy() {
return 1
fi
- _H1="Cookie: $(_syno_get_cookie_data "id"); $(_syno_get_cookie_data "smid")"
+ _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
@@ -102,7 +100,6 @@ synology_dsm_deploy() {
_savedeployconf SYNO_Username "$SYNO_Username"
_savedeployconf SYNO_Password "$SYNO_Password"
_savedeployconf SYNO_DID "$SYNO_DID"
- _debug token "$token"
_info "Getting certificates in Synology DSM"
response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1" "$_base_url/webapi/entry.cgi")
@@ -110,7 +107,7 @@ synology_dsm_deploy() {
id=$(echo "$response" | sed -n "s/.*\"desc\":\"$SYNO_Certificate\",\"id\":\"\([^\"]*\).*/\1/p")
_debug2 id "$id"
- if [ -z "$id" ] && [ -z "${SYNO_Create:?}" ]; then
+ if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then
_err "Unable to find certificate: $SYNO_Certificate and \$SYNO_Create is not set"
return 1
fi
@@ -125,11 +122,11 @@ synology_dsm_deploy() {
_debug2 default "$default"
_info "Generate form POST request"
- nl="\015\012"
+ 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")\012"
- content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\012"
- 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")\012"
+ 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}"
diff --git a/dnsapi/dns_1984hosting.sh b/dnsapi/dns_1984hosting.sh
index b7cb36d7..bcb675ab 100755
--- a/dnsapi/dns_1984hosting.sh
+++ b/dnsapi/dns_1984hosting.sh
@@ -3,7 +3,7 @@
#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
@@ -100,7 +100,7 @@ _1984hosting_add_txt_record() {
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
+ elif _contains "$response" '"auth": false'; then
_err "1984Hosting failed to add TXT record for $subdomain. Invalid or expired cookie"
return 1
fi
@@ -167,8 +167,8 @@ _1984hosting_login() {
response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response"
- if [ "$response" = '{"loggedin": true, "ok": true}' ]; then
- One984HOSTING_COOKIE="$(grep '^Set-Cookie:' "$HTTP_HEADER" | _tail_n 1 | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
+ if _contains "$response" '"loggedin": 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
@@ -196,7 +196,7 @@ _check_cookie() {
_authget "https://management.1984hosting.com/accounts/loginstatus/"
response="$(echo "$_response" | _normalizeJson)"
- if [ "$_response" = '{"ok": true}' ]; then
+ if _contains "$response" '"ok": true'; then
_debug "Cached cookie still valid"
return 0
fi
diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh
index 0c2365d7..c2105672 100755
--- a/dnsapi/dns_ali.sh
+++ b/dnsapi/dns_ali.sh
@@ -181,6 +181,7 @@ _describe_records_query() {
_clean() {
_check_exist_query "$_domain" "$_sub_domain"
+ # do not correct grammar here
if ! _ali_rest "Check exist records" "ignore"; then
return 1
fi
diff --git a/dnsapi/dns_arvan.sh b/dnsapi/dns_arvan.sh
new file mode 100644
index 00000000..edeb56ca
--- /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_azure.sh b/dnsapi/dns_azure.sh
index bf7cf2bf..28b6572a 100644
--- a/dnsapi/dns_azure.sh
+++ b/dnsapi/dns_azure.sh
@@ -172,7 +172,7 @@ dns_azure_rm() {
_azure_rest GET "$acmeRecordURI" "" "$accesstoken"
timestamp="$(_time)"
if [ "$_code" = "200" ]; then
- vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")"
+ vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v -- "$txtvalue")"
values=""
comma=""
for v in $vlist; do
diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh
index 43bc1428..36799dcd 100755
--- a/dnsapi/dns_cf.sh
+++ b/dnsapi/dns_cf.sh
@@ -59,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
@@ -110,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
+ 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."
@@ -130,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
}
@@ -151,8 +151,8 @@ _get_root() {
if ! _cf_rest GET "zones/$CF_Zone_ID"; then
return 1
else
- if _contains "$response" '"success":true'; then
- _domain=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
+ 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")
@@ -186,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_cloudns.sh b/dnsapi/dns_cloudns.sh
index df824e86..381d17ec 100755
--- a/dnsapi/dns_cloudns.sh
+++ b/dnsapi/dns_cloudns.sh
@@ -69,7 +69,7 @@ dns_cloudns_rm() {
for i in $(echo "$response" | tr '{' "\n" | grep "$record"); do
record_id=$(echo "$i" | tr ',' "\n" | grep -E '^"id"' | sed -re 's/^\"id\"\:\"([0-9]+)\"$/\1/g')
- if [ ! -z "$record_id" ]; then
+ if [ -n "$record_id" ]; then
_debug zone "$zone"
_debug host "$host"
_debug record "$record"
@@ -91,7 +91,7 @@ dns_cloudns_rm() {
#################### Private functions below ##################################
_dns_cloudns_init_check() {
- if [ ! -z "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then
+ if [ -n "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then
return 0
fi
@@ -164,7 +164,7 @@ _dns_cloudns_http_api_call() {
_debug CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID"
_debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD"
- if [ ! -z "$CLOUDNS_SUB_AUTH_ID" ]; then
+ if [ -n "$CLOUDNS_SUB_AUTH_ID" ]; then
auth_user="sub-auth-id=$CLOUDNS_SUB_AUTH_ID"
else
auth_user="auth-id=$CLOUDNS_AUTH_ID"
diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh
index 8db3011d..2dca90c0 100644
--- a/dnsapi/dns_cyon.sh
+++ b/dnsapi/dns_cyon.sh
@@ -66,7 +66,7 @@ _cyon_load_credentials() {
_debug "Save credentials to account.conf"
_saveaccountconf CY_Username "${CY_Username}"
_saveaccountconf CY_Password_B64 "$CY_Password_B64"
- if [ ! -z "${CY_OTP_Secret}" ]; then
+ if [ -n "${CY_OTP_Secret}" ]; then
_saveaccountconf CY_OTP_Secret "$CY_OTP_Secret"
else
_clearaccountconf CY_OTP_Secret
@@ -164,7 +164,7 @@ _cyon_login() {
# todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request.
# 2FA authentication with OTP?
- if [ ! -z "${CY_OTP_Secret}" ]; then
+ if [ -n "${CY_OTP_Secret}" ]; then
_info " - Authorising with OTP code..."
if ! _exists oathtool; then
diff --git a/dnsapi/dns_ddnss.sh b/dnsapi/dns_ddnss.sh
index 53781d0d..ecc4f174 100644
--- a/dnsapi/dns_ddnss.sh
+++ b/dnsapi/dns_ddnss.sh
@@ -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 00000000..c0499ddf
--- /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_dgon.sh b/dnsapi/dns_dgon.sh
index c176afd3..ac14da48 100755
--- a/dnsapi/dns_dgon.sh
+++ b/dnsapi/dns_dgon.sh
@@ -22,7 +22,7 @@ dns_dgon_add() {
txtvalue=$2
DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}"
- # Check if API Key Exist
+ # Check if API Key Exists
if [ -z "$DO_API_KEY" ]; then
DO_API_KEY=""
_err "You did not specify DigitalOcean API key."
@@ -77,7 +77,7 @@ dns_dgon_rm() {
txtvalue=$2
DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}"
- # Check if API Key Exist
+ # Check if API Key Exists
if [ -z "$DO_API_KEY" ]; then
DO_API_KEY=""
_err "You did not specify DigitalOcean API key."
@@ -122,12 +122,12 @@ dns_dgon_rm() {
## check for what we are looking for: "type":"A","name":"$_sub_domain"
record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")"
- if [ ! -z "$record" ]; then
+ if [ -n "$record" ]; then
## we found records
rec_ids="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")"
_debug rec_ids "$rec_ids"
- if [ ! -z "$rec_ids" ]; then
+ if [ -n "$rec_ids" ]; then
echo "$rec_ids" | while IFS= read -r rec_id; do
## delete the record
## delete URL for removing the one we dont want
@@ -218,7 +218,7 @@ _get_base_domain() {
## we got part of a domain back - grep it out
found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")"
## check if it exists
- if [ ! -z "$found" ]; then
+ if [ -n "$found" ]; then
## exists - exit loop returning the parts
sub_point=$(_math $i - 1)
_sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point")
diff --git a/dnsapi/dns_duckdns.sh b/dnsapi/dns_duckdns.sh
index 711b81ee..11b685c0 100755
--- a/dnsapi/dns_duckdns.sh
+++ b/dnsapi/dns_duckdns.sh
@@ -91,13 +91,12 @@ dns_duckdns_rm() {
#################### Private functions below ##################################
-#fulldomain=_acme-challenge.domain.duckdns.org
-#returns
-# _duckdns_domain=domain
+# fulldomain may be 'domain.duckdns.org' (if using --domain-alias) or '_acme-challenge.domain.duckdns.org'
+# either way, return 'domain'. (duckdns does not allow further subdomains and restricts domains to [a-z0-9-].)
_duckdns_get_domain() {
# We'll extract the domain/username from full domain
- _duckdns_domain="$(printf "%s" "$fulldomain" | _lower_case | _egrep_o '[.][^.][^.]*[.]duckdns.org' | cut -d . -f 2)"
+ _duckdns_domain="$(printf "%s" "$fulldomain" | _lower_case | _egrep_o '^(_acme-challenge\.)?[a-z0-9-]*\.duckdns\.org' | sed 's/^\(_acme-challenge\.\)\?\([a-z0-9-]*\)\.duckdns\.org/\2/')"
if [ -z "$_duckdns_domain" ]; then
_err "Error extracting the domain."
diff --git a/dnsapi/dns_dynu.sh b/dnsapi/dns_dynu.sh
index 506ef53e..406ef17d 100644
--- a/dnsapi/dns_dynu.sh
+++ b/dnsapi/dns_dynu.sh
@@ -216,6 +216,10 @@ _dynu_authentication() {
_err "Authentication failed."
return 1
fi
+ if _contains "$response" "Authentication Exception"; then
+ _err "Authentication failed."
+ return 1
+ fi
if _contains "$response" "access_token"; then
Dynu_Token=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2)
fi
diff --git a/dnsapi/dns_dynv6.sh b/dnsapi/dns_dynv6.sh
index cf39282b..3c222d3a 100644
--- a/dnsapi/dns_dynv6.sh
+++ b/dnsapi/dns_dynv6.sh
@@ -18,7 +18,7 @@ dns_dynv6_add() {
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"
+ _err "The host $_host does not exist on your dynv6 account"
return 1
fi
_debug "found host on your account"
@@ -49,7 +49,7 @@ dns_dynv6_rm() {
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"
+ _err "The host $_host does not exist on your dynv6 account"
return 1
fi
_debug "found host on your account"
@@ -80,7 +80,7 @@ _generate_new_key() {
_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
+ if ! _contains "$_full_domain" 'dynv6.net' && ! _contains "$_full_domain" 'dns.army' && ! _contains "$_full_domain" 'dns.navy' && ! _contains "$_full_domain" 'v6.rocks'; then
_err "The hosts does not seem to be a dynv6 host"
return 1
fi
diff --git a/dnsapi/dns_easydns.sh b/dnsapi/dns_easydns.sh
index ca8faab2..f466f1e2 100644
--- a/dnsapi/dns_easydns.sh
+++ b/dnsapi/dns_easydns.sh
@@ -4,8 +4,7 @@
#
# easyDNS REST API for acme.sh by Neilpang based on dns_cf.sh
#
-# Please note: # API is currently beta and subject to constant change
-# http://sandbox.rest.easydns.net:3000/
+# 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
@@ -25,7 +24,7 @@ dns_easydns_add() {
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. Please sign up at http://docs.sandbox.rest.easydns.net/beta_signup.php"
+ _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"
diff --git a/dnsapi/dns_gd.sh b/dnsapi/dns_gd.sh
index 7cf47386..7f8efca9 100755
--- a/dnsapi/dns_gd.sh
+++ b/dnsapi/dns_gd.sh
@@ -91,7 +91,7 @@ dns_gd_rm() {
fi
if ! _contains "$response" "$txtvalue"; then
- _info "The record is not existing, skip"
+ _info "The record does not exist, skip"
return 0
fi
diff --git a/dnsapi/dns_gdnsdk.sh b/dnsapi/dns_gdnsdk.sh
index 8c4962c0..90842b25 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 caa4d2c4..5829e00e 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 00000000..5db0418c
--- /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' does not exist."
+ _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_hexonet.sh b/dnsapi/dns_hexonet.sh
index f1503118..525efe73 100755
--- a/dnsapi/dns_hexonet.sh
+++ b/dnsapi/dns_hexonet.sh
@@ -42,7 +42,7 @@ dns_hexonet_add() {
_debug _domain "$_domain"
_debug "Getting txt records"
- _hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT"
+ _hexonet_rest "command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT"
if ! _contains "$response" "CODE=200"; then
_err "Error"
@@ -88,7 +88,7 @@ dns_hexonet_rm() {
_debug _domain "$_domain"
_debug "Getting txt records"
- _hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT&RR=${txtvalue}"
+ _hexonet_rest "command=QueryDNSZoneRRList&dnszone=${h}.&RRTYPE=TXT&RR=${_sub_domain}%20IN%20TXT%20\"${txtvalue}\""
if ! _contains "$response" "CODE=200"; then
_err "Error"
@@ -100,7 +100,7 @@ dns_hexonet_rm() {
if [ "$count" = "0" ]; then
_info "Don't need to remove."
else
- if ! _hexonet_rest "&command=UpdateDNSZone&dnszone=${_domain}.&delrr0='${_sub_domain}%20IN%20TXT%20\"${txtvalue}\""; then
+ if ! _hexonet_rest "command=UpdateDNSZone&dnszone=${_domain}.&delrr0=${_sub_domain}%20IN%20TXT%20\"${txtvalue}\""; then
_err "Delete record error."
return 1
fi
@@ -126,7 +126,7 @@ _get_root() {
return 1
fi
- if ! _hexonet_rest "&command=QueryDNSZoneRRList&dnszone=${h}."; then
+ if ! _hexonet_rest "command=QueryDNSZoneRRList&dnszone=${h}."; then
return 1
fi
diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh
index 7c08d72e..ba789da9 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"
@@ -64,6 +68,10 @@ dns_inwx_rm() {
return 1
fi
+ if ! _inwx_login; then
+ return 1
+ fi
+
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
@@ -123,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
@@ -148,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
@@ -191,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
}
@@ -203,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_kappernet.sh b/dnsapi/dns_kappernet.sh
new file mode 100644
index 00000000..47a871c1
--- /dev/null
+++ b/dnsapi/dns_kappernet.sh
@@ -0,0 +1,150 @@
+#!/usr/bin/env sh
+
+# kapper.net domain api
+# for further questions please contact: support@kapper.net
+# please report issues here: https://github.com/acmesh-official/acme.sh/issues/2977
+
+#KAPPERNETDNS_Key="yourKAPPERNETapikey"
+#KAPPERNETDNS_Secret="yourKAPPERNETapisecret"
+
+KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret"
+
+###############################################################################
+# called with
+# fullhostname: something.example.com
+# txtvalue: someacmegenerated string
+dns_kappernet_add() {
+ fullhostname=$1
+ txtvalue=$2
+
+ KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}"
+ KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}"
+
+ if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then
+ KAPPERNETDNS_Key=""
+ KAPPERNETDNS_Secret=""
+ _err "Please specify your kapper.net api key and secret."
+ _err "If you have not received yours - send your mail to"
+ _err "support@kapper.net to get your key and secret."
+ return 1
+ fi
+
+ #store the api key and email to the account conf file.
+ _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key"
+ _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret"
+ _debug "Checking Domain ..."
+ if ! _get_root "$fullhostname"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _sub_domain "SUBDOMAIN: $_sub_domain"
+ _debug _domain "DOMAIN: $_domain"
+
+ _info "Trying to add TXT DNS Record"
+ data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D"
+ if _kappernet_api GET "action=new&subject=$_domain&data=$data"; then
+
+ if _contains "$response" "{\"OK\":true"; then
+ _info "Waiting 120 seconds for DNS to spread the new record"
+ _sleep 120
+ return 0
+ else
+ _err "Error creating a TXT DNS Record: $fullhostname TXT $txtvalue"
+ _err "Error Message: $response"
+ return 1
+ fi
+ fi
+ _err "Failed creating TXT Record"
+}
+
+###############################################################################
+# called with
+# fullhostname: something.example.com
+dns_kappernet_rm() {
+ fullhostname=$1
+ txtvalue=$2
+
+ KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}"
+ KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}"
+
+ if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then
+ KAPPERNETDNS_Key=""
+ KAPPERNETDNS_Secret=""
+ _err "Please specify your kapper.net api key and secret."
+ _err "If you have not received yours - send your mail to"
+ _err "support@kapper.net to get your key and secret."
+ return 1
+ fi
+
+ #store the api key and email to the account conf file.
+ _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key"
+ _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret"
+
+ _info "Trying to remove the TXT Record: $fullhostname containing $txtvalue"
+ data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D"
+ if _kappernet_api GET "action=del&subject=$fullhostname&data=$data"; then
+ if _contains "$response" "{\"OK\":true"; then
+ return 0
+ else
+ _err "Error deleting DNS Record: $fullhostname containing $txtvalue"
+ _err "Problem: $response"
+ return 1
+ fi
+ fi
+ _err "Problem deleting TXT DNS record"
+}
+
+#################### Private functions below ##################################
+# called with hostname
+# e.g._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
+ if ! _kappernet_api GET "action=list&subject=$h"; then
+ return 1
+ fi
+ if _contains "$response" '"OK":false'; then
+ _debug "$h not found"
+ else
+ _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+ _domain="$h"
+ return 0
+ fi
+ p="$i"
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+################################################################################
+# calls the kapper.net DNS Panel API
+# with
+# method
+# param
+_kappernet_api() {
+ method=$1
+ param="$2"
+
+ _debug param "PARAMETER=$param"
+ url="$KAPPERNETDNS_Api&$param"
+ _debug url "URL=$url"
+
+ if [ "$method" = "GET" ]; then
+ response="$(_get "$url")"
+ else
+ _err "Unsupported method"
+ return 1
+ fi
+
+ _debug2 response "$response"
+ return 0
+}
diff --git a/dnsapi/dns_kinghost.sh b/dnsapi/dns_kinghost.sh
index 898ab286..6253c71d 100644
--- a/dnsapi/dns_kinghost.sh
+++ b/dnsapi/dns_kinghost.sh
@@ -37,7 +37,7 @@ dns_kinghost_add() {
_debug "Getting txt records"
_kinghost_rest GET "dns" "name=$fulldomain&content=$txtvalue"
- #This API call returns "status":"ok" if dns record does not exists
+ #This API call returns "status":"ok" if dns record does not exist
#We are creating a new txt record here, so we expect the "ok" status
if ! echo "$response" | grep '"status":"ok"' >/dev/null; then
_err "Error"
diff --git a/dnsapi/dns_lexicon.sh b/dnsapi/dns_lexicon.sh
index 516b6eff..19702343 100755
--- a/dnsapi/dns_lexicon.sh
+++ b/dnsapi/dns_lexicon.sh
@@ -92,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
}
@@ -108,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_loopia.sh b/dnsapi/dns_loopia.sh
index 1316a274..7760b53e 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_netlify.sh b/dnsapi/dns_netlify.sh
new file mode 100644
index 00000000..137ac1fb
--- /dev/null
+++ b/dnsapi/dns_netlify.sh
@@ -0,0 +1,162 @@
+#!/usr/bin/env sh
+
+#NETLIFY_ACCESS_TOKEN="xxxx"
+
+NETLIFY_HOST="api.netlify.com/api/v1/"
+NETLIFY_URL="https://$NETLIFY_HOST"
+
+######## Public functions #####################
+
+#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_netlify_add() {
+ fulldomain=$1
+ txtvalue=$2
+
+ NETLIFY_ACCESS_TOKEN="${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}"
+
+ if [ -z "$NETLIFY_ACCESS_TOKEN" ]; then
+ NETLIFY_ACCESS_TOKEN=""
+ _err "Please specify your Netlify Access Token and try again."
+ return 1
+ fi
+
+ _info "Using Netlify"
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
+
+ if ! _get_root "$fulldomain" "$accesstoken"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ dnsRecordURI="dns_zones/$_domain_id/dns_records"
+
+ body="{\"type\":\"TXT\", \"hostname\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"ttl\":\"10\"}"
+
+ _netlify_rest POST "$dnsRecordURI" "$body" "$NETLIFY_ACCESS_TOKEN"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then
+ _info "validation value added"
+ return 0
+ else
+ _err "error adding validation value ($_code)"
+ return 1
+ fi
+
+ _err "Not fully implemented!"
+ return 1
+}
+
+#Usage: dns_myapi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+#Remove the txt record after validation.
+dns_netlify_rm() {
+ _info "Using Netlify"
+ txtdomain="$1"
+ txt="$2"
+ _debug txtdomain "$txtdomain"
+ _debug txt "$txt"
+
+ _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN"
+
+ if ! _get_root "$txtdomain" "$accesstoken"; then
+ _err "invalid domain"
+ return 1
+ fi
+
+ _debug _domain_id "$_domain_id"
+ _debug _sub_domain "$_sub_domain"
+ _debug _domain "$_domain"
+
+ dnsRecordURI="dns_zones/$_domain_id/dns_records"
+
+ _netlify_rest GET "$dnsRecordURI" "" "$NETLIFY_ACCESS_TOKEN"
+
+ _record_id=$(echo "$response" | _egrep_o "\"type\":\"TXT\",[^\}]*\"value\":\"$txt\"" | head -n 1 | _egrep_o "\"id\":\"[^\"\}]*\"" | cut -d : -f 2 | tr -d \")
+ _debug _record_id "$_record_id"
+ if [ "$_record_id" ]; then
+ _netlify_rest DELETE "$dnsRecordURI/$_record_id" "" "$NETLIFY_ACCESS_TOKEN"
+ _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+ if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then
+ _info "validation value removed"
+ return 0
+ else
+ _err "error removing validation value ($_code)"
+ return 1
+ fi
+ return 0
+ fi
+ return 1
+}
+
+#################### Private functions below ##################################
+
+_get_root() {
+ domain=$1
+ accesstoken=$2
+ i=1
+ p=1
+
+ _netlify_rest GET "dns_zones" "" "$accesstoken"
+
+ while true; do
+ h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+ _debug2 "Checking domain: $h"
+ if [ -z "$h" ]; then
+ #not valid
+ _err "Invalid domain"
+ return 1
+ fi
+
+ if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
+ _domain_id=$(echo "$response" | _egrep_o "\"[^\"]*\",\"name\":\"$h" | cut -d , -f 1 | tr -d \")
+ if [ "$_domain_id" ]; then
+ if [ "$i" = 1 ]; then
+ #create the record at the domain apex (@) if only the domain name was provided as --domain-alias
+ _sub_domain="@"
+ else
+ _sub_domain=$(echo "$domain" | cut -d . -f 1-$p)
+ fi
+ _domain=$h
+ return 0
+ fi
+ return 1
+ fi
+ p=$i
+ i=$(_math "$i" + 1)
+ done
+ return 1
+}
+
+_netlify_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ token_trimmed=$(echo "$NETLIFY_ACCESS_TOKEN" | tr -d '"')
+
+ export _H1="Content-Type: application/json"
+ export _H2="Authorization: Bearer $token_trimmed"
+
+ :>"$HTTP_HEADER"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$NETLIFY_URL$ep" "" "$m")"
+ else
+ response="$(_get "$NETLIFY_URL$ep")"
+ fi
+
+ if [ "$?" != "0" ]; then
+ _err "error $ep"
+ 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 00000000..e9243288
--- /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_one.sh b/dnsapi/dns_one.sh
index 0fdc3d5e..96ef5969 100644
--- a/dnsapi/dns_one.sh
+++ b/dnsapi/dns_one.sh
@@ -5,8 +5,11 @@
# Author: github: @diseq
# Created: 2019-02-17
# Fixed by: @der-berni
-# Modified: 2019-05-31
-#
+# Modified: 2020-04-07
+#
+# Use ONECOM_KeepCnameProxy to keep the CNAME DNS record
+# export ONECOM_KeepCnameProxy="1"
+#
# export ONECOM_User="username"
# export ONECOM_Password="password"
#
@@ -30,32 +33,45 @@ dns_one_add() {
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")"
- _debug response "$response"
+ useProxy=0
+ if [ "${_sub_domain}" = "_acme-challenge" ]; then
+ subdomain="proxy${_sub_domain}"
+ useProxy=1
+ fi
- # Update the IP address for domain entry
- postdata="{\"type\":\"dns_custom_records\",\"attributes\":{\"priority\":0,\"ttl\":600,\"type\":\"TXT\",\"prefix\":\"$mysubdomain\",\"content\":\"$txtvalue\"}}"
- _debug postdata "$postdata"
- response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$mydomain/dns/custom_records" "" "POST" "application/json")"
- response="$(echo "$response" | _normalizeJson)"
- _debug response "$response"
+ _debug subdomain "$subdomain"
+ _debug maindomain "$maindomain"
+
+ if [ $useProxy -eq 1 ]; then
+ #Check if the CNAME exists
+ _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
+ if [ -z "$id" ]; then
+ _info "$(__red "Add CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$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 ad1e5838..0a9e5ade 100755
--- a/dnsapi/dns_openprovider.sh
+++ b/dnsapi/dns_openprovider.sh
@@ -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_openstack.sh b/dnsapi/dns_openstack.sh
new file mode 100755
index 00000000..38619e6f
--- /dev/null
+++ b/dnsapi/dns_openstack.sh
@@ -0,0 +1,348 @@
+#!/usr/bin/env sh
+
+# OpenStack Designate API plugin
+#
+# This requires you to have OpenStackClient and python-desginateclient
+# installed.
+#
+# You will require Keystone V3 credentials loaded into your environment, which
+# could be either password or v3applicationcredential type.
+#
+# Author: Andy Botting
+
+######## Public functions #####################
+
+# Usage: dns_openstack_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_openstack_add() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ _dns_openstack_credentials || return $?
+ _dns_openstack_check_setup || return $?
+ _dns_openstack_find_zone || return $?
+ _dns_openstack_get_recordset || return $?
+ _debug _recordset_id "$_recordset_id"
+ if [ -n "$_recordset_id" ]; then
+ _dns_openstack_get_records || return $?
+ _debug _records "$_records"
+ fi
+ _dns_openstack_create_recordset || return $?
+}
+
+# Usage: dns_openstack_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Remove the txt record after validation.
+dns_openstack_rm() {
+ fulldomain=$1
+ txtvalue=$2
+ _debug fulldomain "$fulldomain"
+ _debug txtvalue "$txtvalue"
+
+ _dns_openstack_credentials || return $?
+ _dns_openstack_check_setup || return $?
+ _dns_openstack_find_zone || return $?
+ _dns_openstack_get_recordset || return $?
+ _debug _recordset_id "$_recordset_id"
+ if [ -n "$_recordset_id" ]; then
+ _dns_openstack_get_records || return $?
+ _debug _records "$_records"
+ fi
+ _dns_openstack_delete_recordset || return $?
+}
+
+#################### Private functions below ##################################
+
+_dns_openstack_create_recordset() {
+
+ if [ -z "$_recordset_id" ]; then
+ _info "Creating a new recordset"
+ if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record "$txtvalue" "$_zone_id" "$fulldomain."); then
+ _err "No recordset ID found after create"
+ return 1
+ fi
+ else
+ _info "Updating existing recordset"
+ # Build new list of --record args for update
+ _record_args="--record $txtvalue"
+ for _rec in $_records; do
+ _record_args="$_record_args --record $_rec"
+ done
+ # shellcheck disable=SC2086
+ if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain."); then
+ _err "Recordset update failed"
+ return 1
+ fi
+ fi
+
+ _max_retries=60
+ _sleep_sec=5
+ _retry_times=0
+ while [ "$_retry_times" -lt "$_max_retries" ]; do
+ _retry_times=$(_math "$_retry_times" + 1)
+ _debug3 _retry_times "$_retry_times"
+
+ _record_status=$(openstack recordset show -c status -f value "$_zone_id" "$_recordset_id")
+ _info "Recordset status is $_record_status"
+ if [ "$_record_status" = "ACTIVE" ]; then
+ return 0
+ elif [ "$_record_status" = "ERROR" ]; then
+ return 1
+ else
+ _sleep $_sleep_sec
+ fi
+ done
+
+ _err "Recordset failed to become ACTIVE"
+ return 1
+}
+
+_dns_openstack_delete_recordset() {
+
+ if [ "$_records" = "$txtvalue" ]; then
+ _info "Only one record found, deleting recordset"
+ if ! openstack recordset delete "$_zone_id" "$fulldomain." >/dev/null; then
+ _err "Failed to delete recordset"
+ return 1
+ fi
+ else
+ _info "Found existing records, updating recordset"
+ # Build new list of --record args for update
+ _record_args=""
+ for _rec in $_records; do
+ if [ "$_rec" = "$txtvalue" ]; then
+ continue
+ fi
+ _record_args="$_record_args --record $_rec"
+ done
+ # shellcheck disable=SC2086
+ if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then
+ _err "Recordset update failed"
+ return 1
+ fi
+ fi
+}
+
+_dns_openstack_get_root() {
+ # Take the full fqdn and strip away pieces until we get an exact zone name
+ # match. For example, _acme-challenge.something.domain.com might need to go
+ # into something.domain.com or domain.com
+ _zone_name=$1
+ _zone_list=$2
+ while [ "$_zone_name" != "" ]; do
+ _zone_name="$(echo "$_zone_name" | sed 's/[^.]*\.*//')"
+ echo "$_zone_list" | while read -r id name; do
+ if _startswith "$_zone_name." "$name"; then
+ echo "$id"
+ fi
+ done
+ done | _head_n 1
+}
+
+_dns_openstack_find_zone() {
+ if ! _zone_list="$(openstack zone list -c id -c name -f value)"; then
+ _err "Can't list zones. Check your OpenStack credentials"
+ return 1
+ fi
+ _debug _zone_list "$_zone_list"
+
+ if ! _zone_id="$(_dns_openstack_get_root "$fulldomain" "$_zone_list")"; then
+ _err "Can't find a matching zone. Check your OpenStack credentials"
+ return 1
+ fi
+ _debug _zone_id "$_zone_id"
+}
+
+_dns_openstack_get_records() {
+ if ! _records=$(openstack recordset show -c records -f value "$_zone_id" "$fulldomain."); then
+ _err "Failed to get records"
+ return 1
+ fi
+ return 0
+}
+
+_dns_openstack_get_recordset() {
+ if ! _recordset_id=$(openstack recordset list -c id -f value --name "$fulldomain." "$_zone_id"); then
+ _err "Failed to get recordset"
+ return 1
+ fi
+ return 0
+}
+
+_dns_openstack_check_setup() {
+ if ! _exists openstack; then
+ _err "OpenStack client not found"
+ return 1
+ fi
+}
+
+_dns_openstack_credentials() {
+ _debug "Check OpenStack credentials"
+
+ # If we have OS_AUTH_URL already set in the environment, then assume we want
+ # to use those, otherwise use stored credentials
+ if [ -n "$OS_AUTH_URL" ]; then
+ _debug "OS_AUTH_URL env var found, using environment"
+ else
+ _debug "OS_AUTH_URL not found, loading stored credentials"
+ OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}"
+ OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}"
+ OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}"
+ OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}"
+ OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}"
+ OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}"
+ OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}"
+ OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}"
+ OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}"
+ OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}"
+ OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}"
+ OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}"
+ OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}"
+ fi
+
+ # Check each var and either save or clear it depending on whether its set.
+ # The helps us clear out old vars in the case where a user may want
+ # to switch between password and app creds
+ _debug "OS_AUTH_URL" "$OS_AUTH_URL"
+ if [ -n "$OS_AUTH_URL" ]; then
+ export OS_AUTH_URL
+ _saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL"
+ else
+ unset OS_AUTH_URL
+ _clearaccountconf SAVED_OS_AUTH_URL
+ fi
+
+ _debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION"
+ if [ -n "$OS_IDENTITY_API_VERSION" ]; then
+ export OS_IDENTITY_API_VERSION
+ _saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION"
+ else
+ unset OS_IDENTITY_API_VERSION
+ _clearaccountconf SAVED_OS_IDENTITY_API_VERSION
+ fi
+
+ _debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE"
+ if [ -n "$OS_AUTH_TYPE" ]; then
+ export OS_AUTH_TYPE
+ _saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE"
+ else
+ unset OS_AUTH_TYPE
+ _clearaccountconf SAVED_OS_AUTH_TYPE
+ fi
+
+ _debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID"
+ if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then
+ export OS_APPLICATION_CREDENTIAL_ID
+ _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID"
+ else
+ unset OS_APPLICATION_CREDENTIAL_ID
+ _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID
+ fi
+
+ _secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET"
+ if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
+ export OS_APPLICATION_CREDENTIAL_SECRET
+ _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET"
+ else
+ unset OS_APPLICATION_CREDENTIAL_SECRET
+ _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET
+ fi
+
+ _debug "OS_USERNAME" "$OS_USERNAME"
+ if [ -n "$OS_USERNAME" ]; then
+ export OS_USERNAME
+ _saveaccountconf_mutable OS_USERNAME "$OS_USERNAME"
+ else
+ unset OS_USERNAME
+ _clearaccountconf SAVED_OS_USERNAME
+ fi
+
+ _secure_debug "OS_PASSWORD" "$OS_PASSWORD"
+ if [ -n "$OS_PASSWORD" ]; then
+ export OS_PASSWORD
+ _saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD"
+ else
+ unset OS_PASSWORD
+ _clearaccountconf SAVED_OS_PASSWORD
+ fi
+
+ _debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME"
+ if [ -n "$OS_PROJECT_NAME" ]; then
+ export OS_PROJECT_NAME
+ _saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME"
+ else
+ unset OS_PROJECT_NAME
+ _clearaccountconf SAVED_OS_PROJECT_NAME
+ fi
+
+ _debug "OS_PROJECT_ID" "$OS_PROJECT_ID"
+ if [ -n "$OS_PROJECT_ID" ]; then
+ export OS_PROJECT_ID
+ _saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID"
+ else
+ unset OS_PROJECT_ID
+ _clearaccountconf SAVED_OS_PROJECT_ID
+ fi
+
+ _debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME"
+ if [ -n "$OS_USER_DOMAIN_NAME" ]; then
+ export OS_USER_DOMAIN_NAME
+ _saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME"
+ else
+ unset OS_USER_DOMAIN_NAME
+ _clearaccountconf SAVED_OS_USER_DOMAIN_NAME
+ fi
+
+ _debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID"
+ if [ -n "$OS_USER_DOMAIN_ID" ]; then
+ export OS_USER_DOMAIN_ID
+ _saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID"
+ else
+ unset OS_USER_DOMAIN_ID
+ _clearaccountconf SAVED_OS_USER_DOMAIN_ID
+ fi
+
+ _debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME"
+ if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then
+ export OS_PROJECT_DOMAIN_NAME
+ _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME"
+ else
+ unset OS_PROJECT_DOMAIN_NAME
+ _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME
+ fi
+
+ _debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID"
+ if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then
+ export OS_PROJECT_DOMAIN_ID
+ _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID"
+ else
+ unset OS_PROJECT_DOMAIN_ID
+ _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID
+ fi
+
+ if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then
+ # Application Credential auth
+ if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then
+ _err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID"
+ _err "and OS_APPLICATION_CREDENTIAL_SECRET must be set."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+ else
+ # Password auth
+ if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then
+ _err "OpenStack username or password not found."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+
+ if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then
+ _err "When using password authentication, OS_PROJECT_NAME or"
+ _err "OS_PROJECT_ID must be set."
+ _err "Please check your credentials and try again."
+ return 1
+ fi
+ fi
+
+ return 0
+}
diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh
index b2a3746f..069f6c32 100755
--- a/dnsapi/dns_opnsense.sh
+++ b/dnsapi/dns_opnsense.sh
@@ -150,7 +150,7 @@ _get_root() {
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\":\"[^\"]*\",\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2)
+ 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"
diff --git a/dnsapi/dns_rackspace.sh b/dnsapi/dns_rackspace.sh
index 159671f9..03e1fa68 100644
--- a/dnsapi/dns_rackspace.sh
+++ b/dnsapi/dns_rackspace.sh
@@ -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_regru.sh b/dnsapi/dns_regru.sh
index 369f62ad..b5729fda 100644
--- a/dnsapi/dns_regru.sh
+++ b/dnsapi/dns_regru.sh
@@ -5,7 +5,6 @@
#
# REGRU_API_Password="test"
#
-_domain=$_domain
REGRU_API_URL="https://api.reg.ru/api/regru2"
@@ -27,10 +26,17 @@ dns_regru_add() {
_saveaccountconf_mutable REGRU_API_Username "$REGRU_API_Username"
_saveaccountconf_mutable REGRU_API_Password "$REGRU_API_Password"
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain "$_domain"
+
_info "Adding TXT record to ${fulldomain}"
- response="$(_get "$REGRU_API_URL/zone/add_txt?input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json")"
+ _regru_rest POST "zone/add_txt" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json"
- if _contains "${response}" 'success'; then
+ if ! _contains "${response}" 'error'; then
return 0
fi
_err "Could not create resource record, check logs"
@@ -51,13 +57,64 @@ dns_regru_rm() {
return 1
fi
+ _debug "First detect the root zone"
+ if ! _get_root "$fulldomain"; then
+ _err "invalid domain"
+ return 1
+ fi
+ _debug _domain "$_domain"
+
_info "Deleting resource record $fulldomain"
- response="$(_get "$REGRU_API_URL/zone/remove_record?input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json")"
+ _regru_rest POST "zone/remove_record" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json"
- if _contains "${response}" 'success'; then
+ if ! _contains "${response}" 'error'; then
return 0
fi
_err "Could not delete resource record, check logs"
_err "${response}"
return 1
}
+
+#################### Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _domain=domain.com
+_get_root() {
+ domain=$1
+
+ _regru_rest POST "service/get_list" "username=${REGRU_API_Username}&password=${REGRU_API_Password}&output_format=xml&servtype=domain"
+ domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g")
+
+ for ITEM in ${domains_list}; do
+ case "${domain}" in
+ *${ITEM}*)
+ _domain=${ITEM}
+ _debug _domain "${_domain}"
+ return 0
+ ;;
+ esac
+ done
+
+ return 1
+}
+
+#returns
+# response
+_regru_rest() {
+ m=$1
+ ep="$2"
+ data="$3"
+ _debug "$ep"
+
+ export _H1="Content-Type: application/x-www-form-urlencoded"
+
+ if [ "$m" != "GET" ]; then
+ _debug data "$data"
+ response="$(_post "$data" "$REGRU_API_URL/$ep" "" "$m")"
+ else
+ response="$(_get "$REGRU_API_URL/$ep?$data")"
+ fi
+
+ _debug response "${response}"
+ return 0
+}
diff --git a/dnsapi/dns_transip.sh b/dnsapi/dns_transip.sh
new file mode 100644
index 00000000..23debe0d
--- /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 c4593a63..13ba8a00 100644
--- a/dnsapi/dns_unoeuro.sh
+++ b/dnsapi/dns_unoeuro.sh
@@ -5,7 +5,7 @@
#
#UNO_User="UExxxxxx"
-Uno_Api="https://api.unoeuro.com/1"
+Uno_Api="https://api.simply.com/1"
######## Public functions #####################
@@ -24,12 +24,6 @@ dns_unoeuro_add() {
return 1
fi
- if ! _contains "$UNO_User" "UE"; then
- _err "It seems that the UNO_User=$UNO_User is not a valid username."
- _err "Please check and retry."
- return 1
- fi
-
#save the api key and email to the account conf file.
_saveaccountconf_mutable UNO_Key "$UNO_Key"
_saveaccountconf_mutable UNO_User "$UNO_User"
diff --git a/dnsapi/dns_variomedia.sh b/dnsapi/dns_variomedia.sh
index 729cda5e..a35b8f0f 100644
--- a/dnsapi/dns_variomedia.sh
+++ b/dnsapi/dns_variomedia.sh
@@ -107,7 +107,7 @@ _get_root() {
fi
if _startswith "$response" "\{\"data\":"; then
- if _contains "$response" "\"id\": \"$h\""; then
+ if _contains "$response" "\"id\":\"$h\""; then
_sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")"
_domain=$h
return 0
diff --git a/dnsapi/dns_yandex.sh b/dnsapi/dns_yandex.sh
index 5721f994..0a2c3330 100755
--- a/dnsapi/dns_yandex.sh
+++ b/dnsapi/dns_yandex.sh
@@ -25,7 +25,7 @@ dns_yandex_add() {
_PDD_get_record_ids || return 1
_debug "Record_ids: $record_ids"
- if [ ! -z "$record_ids" ]; then
+ if [ -n "$record_ids" ]; then
_info "All existing $subdomain records from $domain will be removed at the very end."
fi
diff --git a/notify/mail.sh b/notify/mail.sh
index ec9aa0de..54b2a6d4 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/teams.sh b/notify/teams.sh
new file mode 100644
index 00000000..e50ea703
--- /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
+}