Browse Source

Merge branch 'dev' into WETOOP-patch-alicdn

pull/1466/head
WETOOP 7 years ago
committed by GitHub
parent
commit
3546e60737
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      .travis.yml
  2. 1
      Dockerfile
  3. 13
      README.md
  4. 84
      acme.sh
  5. 45
      deploy/README.md
  6. 18
      deploy/cpanel_uapi.sh
  7. 8
      deploy/fritzbox.sh
  8. 80
      deploy/gitlab.sh
  9. 34
      deploy/haproxy.sh
  10. 4
      deploy/ssh.sh
  11. 202
      dnsapi/README.md
  12. 55
      dnsapi/dns_acmedns.sh
  13. 14
      dnsapi/dns_aws.sh
  14. 19
      dnsapi/dns_azure.sh
  15. 253
      dnsapi/dns_conoha.sh
  16. 4
      dnsapi/dns_cx.sh
  17. 161
      dnsapi/dns_dpi.sh
  18. 358
      dnsapi/dns_euserv.sh
  19. 167
      dnsapi/dns_gcloud.sh
  20. 12
      dnsapi/dns_gd.sh
  21. 168
      dnsapi/dns_gdnsdk.sh
  22. 21
      dnsapi/dns_he.sh
  23. 50
      dnsapi/dns_inwx.sh
  24. 4
      dnsapi/dns_ispconfig.sh
  25. 55
      dnsapi/dns_lexicon.sh
  26. 227
      dnsapi/dns_loopia.sh
  27. 308
      dnsapi/dns_namecheap.sh
  28. 133
      dnsapi/dns_netcup.sh
  29. 7
      dnsapi/dns_nsupdate.sh
  30. 53
      dnsapi/dns_pdns.sh
  31. 69
      dnsapi/dns_tele3.sh
  32. 23
      dnsapi/dns_unoeuro.sh

11
.travis.yml

@ -13,12 +13,6 @@ env:
global:
- SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64
addons:
apt:
sources:
- debian-sid # Grab shellcheck from the Debian repo (o_O)
packages:
- shellcheck
install:
- if [ "$TRAVIS_OS_NAME" = 'osx' ]; then
@ -29,9 +23,7 @@ install:
script:
- echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)"
- command -V openssl && openssl version
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then chmod +x ~/shfmt ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/shfmt -l -w -i 2 . ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt && chmod +x ~/shfmt && ~/shfmt -l -w -i 2 . ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then git diff --exit-code && echo "shfmt OK" ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi
@ -40,7 +32,6 @@ script:
- if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./rundocker.sh testplat ubuntu:latest ; fi
- if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi
matrix:
fast_finish: true

1
Dockerfile

@ -3,6 +3,7 @@ FROM alpine:3.6
RUN apk update -f \
&& apk --no-cache add -f \
openssl \
coreutils \
curl \
socat \
&& rm -rf /var/cache/apk/*

13
README.md

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

84
acme.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh
VER=2.7.8
VER=2.8.0
PROJECT_NAME="acme.sh"
@ -124,21 +124,21 @@ if [ -t 1 ]; then
fi
__green() {
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
printf '\033[1;31;32m'
fi
printf -- "%b" "$1"
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
printf '\033[0m'
fi
}
__red() {
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
printf '\033[1;31;40m'
fi
printf -- "%b" "$1"
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" ]; then
if [ "$__INTERACTIVE${ACME_NO_COLOR}" = "1" -o "${ACME_FORCE_COLOR}" = "1" ]; then
printf '\033[0m'
fi
}
@ -1327,6 +1327,7 @@ createDomainKey() {
if _createkey "$_cdl" "$CERT_KEY_PATH"; then
_savedomainconf Le_Keylength "$_cdl"
_info "The domain key is here: $(__green $CERT_KEY_PATH)"
return 0
fi
else
if [ "$IS_RENEW" ]; then
@ -1374,17 +1375,17 @@ _url_replace() {
}
_time2str() {
#Linux
if date -u -d@"$1" 2>/dev/null; then
#BSD
if date -u -r "$1" 2>/dev/null; then
return
fi
#BSD
if date -u -r "$1" 2>/dev/null; then
#Linux
if date -u -d@"$1" 2>/dev/null; then
return
fi
#Soaris
#Solaris
if _exists adb; then
_t_s_a=$(echo "0t${1}=Y" | adb)
echo "$_t_s_a"
@ -1607,7 +1608,7 @@ _inithttp() {
}
# body url [needbase64] [POST|PUT] [ContentType]
# body url [needbase64] [POST|PUT|DELETE] [ContentType]
_post() {
body="$1"
_post_url="$2"
@ -1795,15 +1796,13 @@ _send_signed_request() {
return 1
fi
if [ "$ACME_VERSION" = "2" ]; then
__request_conent_type="$CONTENT_TYPE_JSON"
else
__request_conent_type=""
fi
payload64=$(printf "%s" "$payload" | _base64 | _url_replace)
_debug3 payload64 "$payload64"
MAX_REQUEST_RETRY_TIMES=5
MAX_REQUEST_RETRY_TIMES=20
_sleep_retry_sec=1
_request_retry_times=0
while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do
_request_retry_times=$(_math "$_request_retry_times" + 1)
@ -1811,14 +1810,14 @@ _send_signed_request() {
if [ -z "$_CACHED_NONCE" ]; then
_headers=""
if [ "$ACME_NEW_NONCE" ]; then
_debug2 "Get nonce. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
_debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$ACME_NEW_NONCE"
nonceurl="$ACME_NEW_NONCE"
if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then
_headers="$(cat "$HTTP_HEADER")"
fi
fi
if [ -z "$_headers" ]; then
_debug2 "Get nonce. ACME_DIRECTORY" "$ACME_DIRECTORY"
_debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY"
nonceurl="$ACME_DIRECTORY"
_headers="$(_get "$nonceurl" "onlyheader")"
fi
@ -1897,9 +1896,10 @@ _send_signed_request() {
_debug3 _body "$_body"
fi
if _contains "$_body" "JWS has invalid anti-replay nonce"; then
_info "It seems the CA server is busy now, let's wait and retry."
_sleep 5
if _contains "$_body" "JWS has invalid anti-replay nonce" || _contains "$_body" "JWS has an invalid anti-replay nonce"; then
_info "It seems the CA server is busy now, let's wait and retry. Sleeping $_sleep_retry_sec seconds."
_CACHED_NONCE=""
_sleep $_sleep_retry_sec
continue
fi
break
@ -4287,20 +4287,21 @@ $_authorizations_map"
Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
_savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
_err "Call hook error."
return 1
fi
if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then
_savedomainconf "Le_RealCertPath" "$_real_cert"
_savedomainconf "Le_RealCACertPath" "$_real_ca"
_savedomainconf "Le_RealKeyPath" "$_real_key"
_savedomainconf "Le_ReloadCmd" "$_reload_cmd"
_savedomainconf "Le_RealFullChainPath" "$_real_fullchain"
_installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"
if ! _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then
return 1
fi
fi
if ! _on_issue_success "$_post_hook" "$_renew_hook"; then
_err "Call hook error."
return 1
fi
}
#domain [isEcc]
@ -4601,7 +4602,8 @@ deploy() {
_initpath "$_d" "$_isEcc"
if [ ! -d "$DOMAIN_PATH" ]; then
_err "Domain is not valid:'$_d'"
_err "The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install."
_err "Can not find path:'$DOMAIN_PATH'"
return 1
fi
@ -4628,7 +4630,8 @@ installcert() {
_initpath "$_main_domain" "$_isEcc"
if [ ! -d "$DOMAIN_PATH" ]; then
_err "Domain is not valid:'$_main_domain'"
_err "The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install."
_err "Can not find path:'$DOMAIN_PATH'"
return 1
fi
@ -4675,19 +4678,19 @@ _installcert() {
if [ -f "$_real_cert" ] && [ ! "$IS_RENEW" ]; then
cp "$_real_cert" "$_backup_path/cert.bak"
fi
cat "$CERT_PATH" >"$_real_cert"
cat "$CERT_PATH" >"$_real_cert" || return 1
fi
if [ "$_real_ca" ]; then
_info "Installing CA to:$_real_ca"
if [ "$_real_ca" = "$_real_cert" ]; then
echo "" >>"$_real_ca"
cat "$CA_CERT_PATH" >>"$_real_ca"
cat "$CA_CERT_PATH" >>"$_real_ca" || return 1
else
if [ -f "$_real_ca" ] && [ ! "$IS_RENEW" ]; then
cp "$_real_ca" "$_backup_path/ca.bak"
fi
cat "$CA_CERT_PATH" >"$_real_ca"
cat "$CA_CERT_PATH" >"$_real_ca" || return 1
fi
fi
@ -4697,9 +4700,9 @@ _installcert() {
cp "$_real_key" "$_backup_path/key.bak"
fi
if [ -f "$_real_key" ]; then
cat "$CERT_KEY_PATH" >"$_real_key"
cat "$CERT_KEY_PATH" >"$_real_key" || return 1
else
cat "$CERT_KEY_PATH" >"$_real_key"
cat "$CERT_KEY_PATH" >"$_real_key" || return 1
chmod 600 "$_real_key"
fi
fi
@ -4709,7 +4712,7 @@ _installcert() {
if [ -f "$_real_fullchain" ] && [ ! "$IS_RENEW" ]; then
cp "$_real_fullchain" "$_backup_path/fullchain.bak"
fi
cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain"
cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
fi
if [ "$_reload_cmd" ]; then
@ -5473,7 +5476,7 @@ 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.
These parameters are to install the cert to nginx/apache or anyother server after issue/renew a cert:
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.
--key-file After issue/renew, the key will be copied to this path.
@ -5500,6 +5503,7 @@ Parameters:
--ca-path Specifies directory containing CA certificates in PEM format, used by wget or curl.
--nocron Only valid for '--install' command, which means: do not install the default cron job. In this case, the certs will not be renewed automatically.
--no-color Do not output color text.
--force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails.
--ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR'
--csr Specifies the input csr.
--pre-hook Command to be run before obtaining any certificates.
@ -5514,6 +5518,7 @@ Parameters:
--openssl-bin Specifies a custom openssl bin location.
--use-wget Force to use wget, if you have both curl and wget installed.
--yes-I-know-dns-manual-mode-enough-go-ahead-please Force to use dns manual mode: $_DNS_MANUAL_WIKI
--branch, -b Only valid for '--upgrade' command, specifies the branch name to upgrade to.
"
}
@ -5964,6 +5969,9 @@ _process() {
--no-color)
export ACME_NO_COLOR=1
;;
--force-color)
export ACME_FORCE_COLOR=1
;;
--ecc)
_ecc="isEcc"
;;
@ -6058,6 +6066,10 @@ _process() {
_use_wget="1"
ACME_USE_WGET="1"
;;
--branch | -b)
export BRANCH="$2"
shift
;;
*)
_err "Unknown parameter : $1"
return 1

45
deploy/README.md

@ -256,7 +256,48 @@ acme.sh --deploy -d fritzbox.example.com --deploy-hook fritzbox
acme.sh --deploy -d ftp.example.com --deploy-hook strongswan
```
## 10. Deploy the cert to your AliCDN account
## 10. Deploy the cert to HAProxy
You must specify the path where you want the concatenated key and certificate chain written.
```sh
export DEPLOY_HAPROXY_PEM_PATH=/etc/haproxy
```
You may optionally define the command to reload HAProxy. The value shown below will be used as the default if you don't set this environment variable.
```sh
export DEPLOY_HAPROXY_RELOAD="/usr/sbin/service haproxy restart"
```
You can then deploy the certificate as follows
```sh
acme.sh --deploy -d haproxy.example.com --deploy-hook haproxy
```
The path for the PEM file will be stored with the domain configuration and will be available when renewing, so that deploy will happen automatically when renewed.
## 11. Deploy your cert to Gitlab pages
You must define the API key and the informations for the project and Gitlab page you are updating the certificate for.
```sh
# The token can be created in your user settings under "Access Tokens"
export GITLAB_TOKEN="xxxxxxxxxxx"
# The project ID is displayed on the home page of the project
export GITLAB_PROJECT_ID=12345678
# The domain must match the one defined for the Gitlab page, without "https://"
export GITLAB_DOMAIN="www.mydomain.com"
```
You can then deploy the certificate as follows
```sh
acme.sh --deploy -d www.mydomain.com --deploy-hook gitlab
```
## 12. Deploy the cert to your AliCDN account
You must specify the aliyun account credentials in order to deploy the certificate, optionally specify the prefix of your domain if you use wildcard in cdn domain, through the following environment variables:
```sh
@ -276,7 +317,7 @@ If your cdn domain is cdn.example.com and cert domain is also cdn.example.com, y
export DEPLOY_CDN_Ali_Key="AK"
export DEPLOY_CDN_Ali_Secret="SK"
export DEPLOY_CDN_Ali_Prefix=""
acme.sh --deploy -d example.com --deploy-hook cdn_ali
acme.sh --deploy -d cdn.example.com --deploy-hook cdn_ali
```
If your cdn domain is cdn.example.com but cert domain is example.com(subject alternative names: example.com,\*.example.com), you must specify the prefix in deployment:

18
deploy/cpanel_uapi.sh

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

8
deploy/fritzbox.sh

@ -28,9 +28,11 @@ fritzbox_deploy() {
_debug _cfullchain "$_cfullchain"
if ! _exists iconv; then
_err "iconv not found"
if ! _exists perl; then
_err "iconv or perl not found"
return 1
fi
fi
_fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}"
_fritzbox_password="${DEPLOY_FRITZBOX_PASSWORD}"
@ -61,7 +63,11 @@ fritzbox_deploy() {
_info "Log in to the FRITZ!Box"
_fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*<Challenge>//' -e 's/<\/Challenge>.*$//')"
if _exists iconv; then
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')"
else
_fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | perl -p -e 'use Encode qw/encode/; print encode("UTF-16LE","$_"); $_="";' | md5sum | awk '{print $1}')"
fi
_fritzbox_sid="$(_get "${_fritzbox_url}/login_sid.lua?sid=0000000000000000&username=${_fritzbox_username}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*<SID>//' -e 's/<\/SID>.*$//')"
if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then

80
deploy/gitlab.sh

@ -0,0 +1,80 @@
#!/usr/bin/env sh
# Script to deploy certificate to a Gitlab hosted page
# The following variables exported from environment will be used.
# If not set then values previously saved in domain.conf file are used.
# All the variables are required
# export GITLAB_TOKEN="xxxxxxx"
# export GITLAB_PROJECT_ID=012345
# export GITLAB_DOMAIN="mydomain.com"
gitlab_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 [ -z "$GITLAB_TOKEN" ]; then
if [ -z "$Le_Deploy_gitlab_token" ]; then
_err "GITLAB_TOKEN not defined."
return 1
fi
else
Le_Deploy_gitlab_token="$GITLAB_TOKEN"
_savedomainconf Le_Deploy_gitlab_token "$Le_Deploy_gitlab_token"
fi
if [ -z "$GITLAB_PROJECT_ID" ]; then
if [ -z "$Le_Deploy_gitlab_project_id" ]; then
_err "GITLAB_PROJECT_ID not defined."
return 1
fi
else
Le_Deploy_gitlab_project_id="$GITLAB_PROJECT_ID"
_savedomainconf Le_Deploy_gitlab_project_id "$Le_Deploy_gitlab_project_id"
fi
if [ -z "$GITLAB_DOMAIN" ]; then
if [ -z "$Le_Deploy_gitlab_domain" ]; then
_err "GITLAB_DOMAIN not defined."
return 1
fi
else
Le_Deploy_gitlab_domain="$GITLAB_DOMAIN"
_savedomainconf Le_Deploy_gitlab_domain "$Le_Deploy_gitlab_domain"
fi
string_fullchain=$(_url_encode <"$_cfullchain")
string_key=$(_url_encode <"$_ckey")
body="certificate=$string_fullchain&key=$string_key"
export _H1="PRIVATE-TOKEN: $Le_Deploy_gitlab_token"
gitlab_url="https://gitlab.com/api/v4/projects/$Le_Deploy_gitlab_project_id/pages/domains/$Le_Deploy_gitlab_domain"
_response=$(_post "$body" "$gitlab_url" 0 PUT | _dbase64 "multiline")
error_response="error"
if test "${_response#*$error_response}" != "$_response"; then
_err "Error in deploying certificate:"
_err "$_response"
return 1
fi
_debug response "$_response"
_info "Certificate successfully deployed"
return 0
}

34
deploy/haproxy.sh

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

4
deploy/ssh.sh

@ -101,7 +101,7 @@ ssh_deploy() {
fi
# CERTFILE is optional.
# If provided then private key will be copied or appended to provided filename.
# If provided then certificate will be copied or appended to provided filename.
if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
_savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
@ -190,7 +190,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
_info "Backup directories erased after 180 days."
fi
_debug "Remote commands to execute: $_cmdstr"
_secure_debug "Remote commands to execute: " "$_cmdstr"
_info "Submitting sequence of commands to remote server by ssh"
# quotations in bash cmd below intended. Squash travis spellcheck error
# shellcheck disable=SC2029

202
dnsapi/README.md

@ -1,5 +1,9 @@
# How to use DNS API
If your dns provider doesn't provide api access, you can use our dns alias mode:
https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode
## 1. Use CloudFlare domain API to automatically issue cert
First you need to login to your CloudFlare account to get your API key.
@ -641,6 +645,14 @@ acme.sh --issue --dns dns_inwx -d example.com -d www.example.com
The `INWX_User` and `INWX_Password` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
If your account is secured by mobile tan you have also defined the shared secret.
```
export INWX_Shared_Secret="shared secret"
```
You may need to re-enable the mobile tan to gain the shared secret.
## 34. User Servercow API v1
Create a new user from the servercow control center. Don't forget to activate **DNS API** for this user.
@ -753,7 +765,7 @@ DNS API keys may be created at https://panel.dreamhost.com/?tree=home.api.
Ensure the created key has add and remove privelages.
```
export DH_API_Key="<api key>"
export DH_API_KEY="<api key>"
acme.sh --issue --dns dns_dreamhost -d example.com -d www.example.com
```
@ -814,6 +826,194 @@ acme.sh --issue --dns dns_zilore -d example.com -d *.example.com
The `Zilore_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
## 44. Use Loopia.se API
User must provide login credentials to the Loopia API.
The user needs the following permissions:
- addSubdomain
- updateZoneRecord
- getDomains
- removeSubdomain
Set the login credentials:
```
export LOOPIA_User="user@loopiaapi"
export LOOPIA_Password="password"
```
And to issue a cert:
```
acme.sh --issue --dns dns_loopia -d example.com -d *.example.com
```
The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
## 45. Use ACME DNS API
ACME DNS is a limited DNS server with RESTful HTTP API to handle ACME DNS challenges easily and securely.
https://github.com/joohoi/acme-dns
```
export ACMEDNS_UPDATE_URL="https://auth.acme-dns.io/update"
export ACMEDNS_USERNAME="<username>"
export ACMEDNS_PASSWORD="<password>"
export ACMEDNS_SUBDOMAIN="<subdomain>"
acme.sh --issue --dns dns_acmedns -d example.com -d www.example.com
```
The credentials will be saved in `~/.acme.sh/account.conf` and will
be reused when needed.
## 46. Use TELE3 API
First you need to login to your TELE3 account to set your API-KEY.
https://www.tele3.cz/system-acme-api.html
```
export TELE3_Key="MS2I4uPPaI..."
export TELE3_Secret="kjhOIHGJKHg"
acme.sh --issue --dns dns_tele3 -d example.com -d *.example.com
```
The TELE3_Key and TELE3_Secret will be saved in ~/.acme.sh/account.conf and will be reused when needed.
## 47. Use Euserv.eu API
First you need to login to your euserv.eu account and activate your API Administration (API Verwaltung).
[https://support.euserv.com](https://support.euserv.com)
Once you've activate, login to your API Admin Interface and create an API account.
Please specify the scope (active groups: domain) and assign the allowed IPs.
```
export EUSERV_Username="99999.user123"
export EUSERV_Password="Asbe54gHde"
```
Ok, let's issue a cert now: (Be aware to use the `--insecure` flag, cause euserv.eu is still using self-signed certificates!)
```
acme.sh --issue --dns dns_euserv -d example.com -d *.example.com --insecure
```
The `EUSERV_Username` and `EUSERV_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
Please report any issues to https://github.com/initit/acme.sh or to <github@initit.de>
## 48. Use DNSPod.com domain API to automatically issue cert
First you need to get your API Key and ID by this [get-the-user-token](https://www.dnspod.com/docs/info.html#get-the-user-token).
```
export DPI_Id="1234"
export DPI_Key="sADDsdasdgdsf"
```
Ok, let's issue a cert now:
```
acme.sh --issue --dns dns_dpi -d example.com -d www.example.com
```
The `DPI_Id` and `DPI_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
## 49. Use Google Cloud DNS API to automatically issue cert
First you need to authenticate to gcloud.
```
gcloud init
```
**The `dns_gcloud` script uses the active gcloud configuration and credentials.**
There is no logic inside `dns_gcloud` to override the project and other settings.
If needed, create additional [gcloud configurations](https://cloud.google.com/sdk/gcloud/reference/topic/configurations).
You can change the configuration being used without *activating* it; simply set the `CLOUDSDK_ACTIVE_CONFIG_NAME` environment variable.
To issue a certificate you can:
```
export CLOUDSDK_ACTIVE_CONFIG_NAME=default # see the note above
acme.sh --issue --dns dns_gcloud -d example.com -d '*.example.com'
```
`dns_gcloud` also supports [DNS alias mode](https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode).
## 50. Use ConoHa API
First you need to login to your ConoHa account to get your API credentials.
```
export CONOHA_Username="xxxxxx"
export CONOHA_Password="xxxxxx"
export CONOHA_TenantId="xxxxxx"
export CONOHA_IdentityServiceApi="https://identity.xxxx.conoha.io/v2.0"
```
To issue a cert:
```
acme.sh --issue --dns dns_conoha -d example.com -d www.example.com
```
The `CONOHA_Username`, `CONOHA_Password`, `CONOHA_TenantId` and `CONOHA_IdentityServiceApi` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
## 51. Use netcup DNS API to automatically issue cert
First you need to login in your CCP account to get your API Key and API Password.
```
export NC_Apikey="<Apikey>"
export NC_Apipw="<Apipassword>"
export NC_CID="<Customernumber>"
```
Now, let's issue a cert:
```
acme.sh --issue --dns dns_netcup -d example.com -d www.example.com
```
The `NC_Apikey`,`NC_Apipw` and `NC_CID` will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
## 52. Use GratisDNS.dk
GratisDNS.dk (https://gratisdns.dk/) does not provide an API to update DNS records (other than IPv4 and IPv6
dynamic DNS addresses). The acme.sh plugin therefore retrieves and updates domain TXT records by logging
into the GratisDNS website to read the HTML and posting updates as HTTP. The plugin needs to know your
userid and password for the GratisDNS website.
```sh
export GDNSDK_Username="..."
export GDNSDK_Password="..."
```
The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
Now you can issue a certificate.
Note: It usually takes a few minutes (usually 3-4 minutes) before the changes propagates to gratisdns.dk nameservers (ns3.gratisdns.dk often are slow),
and in rare cases I have seen over 5 minutes before google DNS catches it. Therefor a DNS sleep of at least 300 seconds are recommended-
```sh
acme.sh --issue --dns dns_gdnsdk --dnssleep 300 -d example.com -d *.example.com
```
## 53. Use Namecheap
You will need your namecheap username, API KEY (https://www.namecheap.com/support/api/intro.aspx) and your external IP address (or an URL to get it), this IP will need to be whitelisted at Namecheap.
Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
```sh
export NAMECHEAP_USERNAME="..."
export NAMECHEAP_API_KEY="..."
export NAMECHEAP_SOURCEIP="..."
```
NAMECHEAP_SOURCEIP can either be an IP address or an URL to provide it (e.g. https://ifconfig.co/ip).
The username and password will be saved in `~/.acme.sh/account.conf` and will be reused when needed.
Now you can issue a certificate.
```sh
acme.sh --issue --dns dns_namecheap -d example.com -d *.example.com
```
# Use custom API
If your API is not supported yet, you can write your own DNS API.

55
dnsapi/dns_acmedns.sh

@ -0,0 +1,55 @@
#!/usr/bin/env sh
#
#Author: Wolfgang Ebner
#Report Bugs here: https://github.com/webner/acme.sh
#
######## Public functions #####################
#Usage: dns_acmedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_acmedns_add() {
fulldomain=$1
txtvalue=$2
_info "Using acme-dns"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
ACMEDNS_UPDATE_URL="${ACMEDNS_UPDATE_URL:-$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)}"
ACMEDNS_USERNAME="${ACMEDNS_USERNAME:-$(_readaccountconf_mutable ACMEDNS_USERNAME)}"
ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readaccountconf_mutable ACMEDNS_PASSWORD)}"
ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readaccountconf_mutable ACMEDNS_SUBDOMAIN)}"
if [ "$ACMEDNS_UPDATE_URL" = "" ]; then
ACMEDNS_UPDATE_URL="https://auth.acme-dns.io/update"
fi
_saveaccountconf_mutable ACMEDNS_UPDATE_URL "$ACMEDNS_UPDATE_URL"
_saveaccountconf_mutable ACMEDNS_USERNAME "$ACMEDNS_USERNAME"
_saveaccountconf_mutable ACMEDNS_PASSWORD "$ACMEDNS_PASSWORD"
_saveaccountconf_mutable ACMEDNS_SUBDOMAIN "$ACMEDNS_SUBDOMAIN"
export _H1="X-Api-User: $ACMEDNS_USERNAME"
export _H2="X-Api-Key: $ACMEDNS_PASSWORD"
data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}"
_debug data "$data"
response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")"
_debug response "$response"
if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then
_err "invalid response of acme-dns"
return 1
fi
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_acmedns_rm() {
fulldomain=$1
txtvalue=$2
_info "Using acme-dns"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
}
#################### Private functions below ##################################

14
dnsapi/dns_aws.sh

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

19
dnsapi/dns_azure.sh

@ -76,10 +76,10 @@ dns_azure_add() {
values="{\"value\":[\"$txtvalue\"]}"
timestamp="$(_time)"
if [ "$_code" = "200" ]; then
vlist="$(echo "$response" | _egrep_o "\"value\"\s*:\s*\[\s*\"[^\"]*\"\s*]" | cut -d : -f 2 | tr -d "[]\"")"
vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"")"
_debug "existing TXT found"
_debug "$vlist"
existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\s*:\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")"
existingts="$(echo "$response" | _egrep_o "\"acmetscheck\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")"
if [ -z "$existingts" ]; then
# the record was not created by acme.sh. Copy the exisiting entires
existingts=$timestamp
@ -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
@ -230,7 +230,7 @@ _azure_rest() {
fi
_ret="$?"
_secure_debug2 "response $response"
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")"
_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
_debug "http response code $_code"
if [ "$_code" = "401" ]; then
# we have an invalid access token set to expired
@ -308,7 +308,7 @@ _get_root() {
domain=$1
subscriptionId=$2
accesstoken=$3
i=2
i=1
p=1
## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list
@ -328,9 +328,14 @@ _get_root() {
fi
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_domain_id=$(echo "$response" | _egrep_o "\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
_domain_id=$(echo "$response" | _egrep_o "\\{\"id\":\"[^\"]*$h\"" | head -n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
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

253
dnsapi/dns_conoha.sh

@ -0,0 +1,253 @@
#!/usr/bin/env sh
CONOHA_DNS_EP_PREFIX_REGEXP="https://dns-service\."
######## Public functions #####################
#Usage: dns_conoha_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_conoha_add() {
fulldomain=$1
txtvalue=$2
_info "Using conoha"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug "Check uesrname and password"
CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}"
CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}"
CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}"
CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}"
if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then
CONOHA_Username=""
CONOHA_Password=""
CONOHA_TenantId=""
CONOHA_IdentityServiceApi=""
_err "You didn't specify a conoha api username and password yet."
_err "Please create the user and try again."
return 1
fi
_saveaccountconf_mutable CONOHA_Username "$CONOHA_Username"
_saveaccountconf_mutable CONOHA_Password "$CONOHA_Password"
_saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId"
_saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi"
if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then
accesstoken="$(printf "%s" "$token" | sed -n 1p)"
CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)"
else
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then
_err "invalid domain"
return 1
fi
_debug _domain_id "$_domain_id"
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_info "Adding record"
body="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"data\":\"$txtvalue\",\"ttl\":60}"
if _conoha_rest POST "$CONOHA_Api/v1/domains/$_domain_id/records" "$body" "$accesstoken"; then
if _contains "$response" '"data":"'"$txtvalue"'"'; then
_info "Added, 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_conoha_rm() {
fulldomain=$1
txtvalue=$2
_info "Using conoha"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug "Check uesrname and password"
CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}"
CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}"
CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}"
CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}"
if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then
CONOHA_Username=""
CONOHA_Password=""
CONOHA_TenantId=""
CONOHA_IdentityServiceApi=""
_err "You didn't specify a conoha api username and password yet."
_err "Please create the user and try again."
return 1
fi
_saveaccountconf_mutable CONOHA_Username "$CONOHA_Username"
_saveaccountconf_mutable CONOHA_Password "$CONOHA_Password"
_saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId"
_saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi"
if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then
accesstoken="$(printf "%s" "$token" | sed -n 1p)"
CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)"
else
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; 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 ! _conoha_rest GET "$CONOHA_Api/v1/domains/$_domain_id/records" "" "$accesstoken"; then
_err "Error"
return 1
fi
record_id=$(printf "%s" "$response" | _egrep_o '{[^}]*}' \
| grep '"type":"TXT"' | grep "\"data\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" \
| _head_n 1 | cut -d : -f 2 | tr -d \")
if [ -z "$record_id" ]; then
_err "Can not get record id to remove."
return 1
fi
_debug record_id "$record_id"
_info "Removing the txt record"
if ! _conoha_rest DELETE "$CONOHA_Api/v1/domains/$_domain_id/records/$record_id" "" "$accesstoken"; then
_err "Delete record error."
return 1
fi
return 0
}
#################### Private functions below ##################################
_conoha_rest() {
m="$1"
ep="$2"
data="$3"
accesstoken="$4"
export _H1="Accept: application/json"
export _H2="Content-Type: application/json"
if [ -n "$accesstoken" ]; then
export _H3="X-Auth-Token: $accesstoken"
fi
_debug "$ep"
if [ "$m" != "GET" ]; then
_secure_debug2 data "$data"
response="$(_post "$data" "$ep" "" "$m")"
else
response="$(_get "$ep")"
fi
_ret="$?"
_secure_debug2 response "$response"
if [ "$_ret" != "0" ]; then
_err "error $ep"
return 1
fi
response="$(printf "%s" "$response" | _normalizeJson)"
return 0
}
_conoha_get_accesstoken() {
ep="$1"
username="$2"
password="$3"
tenantId="$4"
accesstoken="$(_readaccountconf_mutable conoha_accesstoken)"
expires="$(_readaccountconf_mutable conoha_tokenvalidto)"
CONOHA_Api="$(_readaccountconf_mutable conoha_dns_ep)"
# can we reuse the access token?
if [ -n "$accesstoken" ] && [ -n "$expires" ] && [ -n "$CONOHA_Api" ]; then
utc_date="$(_utc_date | sed "s/ /T/")"
if expr "$utc_date" "<" "$expires" >/dev/null; then
# access token is still valid - reuse it
_debug "reusing access token"
printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api"
return 0
else
_debug "access token expired"
fi
fi
_debug "getting new access token"
body="$(printf '{"auth":{"passwordCredentials":{"username":"%s","password":"%s"},"tenantId":"%s"}}' "$username" "$password" "$tenantId")"
if ! _conoha_rest POST "$ep" "$body" ""; then
_err error "$response"
return 1
fi
accesstoken=$(printf "%s" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
expires=$(printf "%s" "$response" | _egrep_o "\"expires\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2-4 | tr -d \" | tr -d Z) #expect UTC
if [ -z "$accesstoken" ] || [ -z "$expires" ]; then
_err "no acccess token received. Check your Conoha settings see $WIKI"
return 1
fi
_saveaccountconf_mutable conoha_accesstoken "$accesstoken"
_saveaccountconf_mutable conoha_tokenvalidto "$expires"
CONOHA_Api=$(printf "%s" "$response" | _egrep_o 'publicURL":"'"$CONOHA_DNS_EP_PREFIX_REGEXP"'[^"]*"' | _head_n 1 | cut -d : -f 2-3 | tr -d \")
if [ -z "$CONOHA_Api" ]; then
_err "failed to get conoha dns endpoint url"
return 1
fi
_saveaccountconf_mutable conoha_dns_ep "$CONOHA_Api"
printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api"
return 0
}
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
# _domain_id=sdjkglgdfewsdfg
_get_root() {
domain="$1"
ep="$2"
accesstoken="$3"
i=2
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 ! _conoha_rest GET "$ep/v1/domains?name=$h" "" "$accesstoken"; then
return 1
fi
if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
_domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \")
if [ "$_domain_id" ]; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain=$h
return 0
fi
return 1
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}

4
dnsapi/dns_cx.sh

@ -1,6 +1,6 @@
#!/usr/bin/env sh
# Cloudxns.com Domain api
# CloudXNS Domain api
#
#CX_Key="1234"
#
@ -19,7 +19,7 @@ dns_cx_add() {
if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ]; then
CX_Key=""
CX_Secret=""
_err "You don't specify cloudxns.com api key or secret yet."
_err "You don't specify cloudxns.net api key or secret yet."
_err "Please create you key and try again."
return 1
fi

161
dnsapi/dns_dpi.sh

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

358
dnsapi/dns_euserv.sh

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

167
dnsapi/dns_gcloud.sh

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

12
dnsapi/dns_gd.sh

@ -59,19 +59,13 @@ dns_gd_add() {
_info "Adding record"
if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
if [ "$response" = "{}" ]; then
_info "Added, sleeping 10 seconds"
_sleep 10
#todo: check if the record takes effect
return 0
else
_err "Add txt record error."
_err "$response"
return 1
fi
fi
_err "Add txt record error."
return 1
}
#fulldomain
@ -174,5 +168,9 @@ _gd_rest() {
return 1
fi
_debug2 response "$response"
if _contains "$response" "UNABLE_TO_AUTHENTICATE"; then
_err "It seems that your api key or secret is not correct."
return 1
fi
return 0
}

168
dnsapi/dns_gdnsdk.sh

@ -0,0 +1,168 @@
#!/usr/bin/env sh
#Author: Herman Sletteng
#Report Bugs here: https://github.com/loial/acme.sh
#
#
# Note, gratisdns requires a login first, so the script needs to handle
# temporary cookies. Since acme.sh _get/_post currently don't directly support
# cookies, I've defined wrapper functions _myget/_mypost to set the headers
GDNSDK_API="https://admin.gratisdns.com"
######## Public functions #####################
#Usage: dns_gdnsdk_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_gdnsdk_add() {
fulldomain=$1
txtvalue=$2
_info "Using gratisdns.dk"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _gratisdns_login; then
_err "Login failed!"
return 1
fi
#finding domain zone
if ! _get_domain; then
_err "No matching root domain for $fulldomain found"
return 1
fi
# adding entry
_info "Adding the entry"
_mypost "action=dns_primary_record_added_txt&user_domain=$_domain&name=$fulldomain&txtdata=$txtvalue&ttl=1"
if _successful_update; then return 0; fi
_err "Couldn't create entry!"
return 1
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_gdnsdk_rm() {
fulldomain=$1
txtvalue=$2
_info "Using gratisdns.dk"
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
if ! _gratisdns_login; then
_err "Login failed!"
return 1
fi
if ! _get_domain; then
_err "No matching root domain for $fulldomain found"
return 1
fi
_findentry "$fulldomain" "$txtvalue"
if [ -z "$_id" ]; then
_info "Entry doesn't exist, nothing to delete"
return 0
fi
_debug "Deleting record..."
_mypost "action=dns_primary_delete_txt&user_domain=$_domain&id=$_id"
# removing entry
if _successful_update; then return 0; fi
_err "Couldn't delete entry!"
return 1
}
#################### Private functions below ##################################
_checkcredentials() {
GDNSDK_Username="${GDNSDK_Username:-$(_readaccountconf_mutable GDNSDK_Username)}"
GDNSDK_Password="${GDNSDK_Password:-$(_readaccountconf_mutable GDNSDK_Password)}"
if [ -z "$GDNSDK_Username" ] || [ -z "$GDNSDK_Password" ]; then
GDNSDK_Username=""
GDNSDK_Password=""
_err "You haven't specified gratisdns.dk username and password yet."
_err "Please add credentials and try again."
return 1
fi
#save the credentials to the account conf file.
_saveaccountconf_mutable GDNSDK_Username "$GDNSDK_Username"
_saveaccountconf_mutable GDNSDK_Password "$GDNSDK_Password"
return 0
}
_checkcookie() {
GDNSDK_Cookie="${GDNSDK_Cookie:-$(_readaccountconf_mutable GDNSDK_Cookie)}"
if [ -z "$GDNSDK_Cookie" ]; then
_debug "No cached cookie found"
return 1
fi
_myget "action="
if (echo "$_result" | grep -q "logmeout"); then
_debug "Cached cookie still valid"
return 0
fi
_debug "Cached cookie no longer valid"
GDNSDK_Cookie=""
_saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie"
return 1
}
_gratisdns_login() {
if ! _checkcredentials; then return 1; fi
if _checkcookie; then
_debug "Already logged in"
return 0
fi
_debug "Logging into GratisDNS with user $GDNSDK_Username"
if ! _mypost "login=$GDNSDK_Username&password=$GDNSDK_Password&action=logmein"; then
_err "GratisDNS login failed for user $GDNSDK_Username bad RC from _post"
return 1
fi
GDNSDK_Cookie="$(grep -A 15 '302 Found' "$HTTP_HEADER" | _egrep_o 'Cookie: [^;]*' | _head_n 1 | cut -d ' ' -f2)"
if [ -z "$GDNSDK_Cookie" ]; then
_err "GratisDNS login failed for user $GDNSDK_Username. Check $HTTP_HEADER file"
return 1
fi
export GDNSDK_Cookie
_saveaccountconf_mutable GDNSDK_Cookie "$GDNSDK_Cookie"
return 0
}
_myget() {
#Adds cookie to request
export _H1="Cookie: $GDNSDK_Cookie"
_result=$(_get "$GDNSDK_API?$1")
}
_mypost() {
#Adds cookie to request
export _H1="Cookie: $GDNSDK_Cookie"
_result=$(_post "$1" "$GDNSDK_API")
}
_get_domain() {
_myget 'action=dns_primarydns'
_domains=$(echo "$_result" | _egrep_o ' domain="[[:alnum:].-_]+' | sed 's/^.*"//')
if [ -z "$_domains" ]; then
_err "Primary domain list not found!"
return 1
fi
for _domain in $_domains; do
if (_endswith "$fulldomain" "$_domain"); then
_debug "Root domain: $_domain"
return 0
fi
done
return 1
}
_successful_update() {
if (echo "$_result" | grep -q 'table-success'); then return 0; fi
return 1
}
_findentry() {
#returns id of dns entry, if it exists
_myget "action=dns_primary_changeDNSsetup&user_domain=$_domain"
_id=$(echo "$_result" | _egrep_o "<td>$1</td>\s*<td>$2</td>[^?]*[^&]*&id=[^&]*" | sed 's/^.*=//')
if [ -n "$_id" ]; then
_debug "Entry found with _id=$_id"
return 0
fi
return 1
}

21
dnsapi/dns_he.sh

@ -33,8 +33,9 @@ dns_he_add() {
# Fills in the $_zone_id
_find_zone "$_full_domain" || return 1
_debug "Zone id \"$_zone_id\" will be used."
body="email=${HE_Username}&pass=${HE_Password}"
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
body="$body&account="
body="$body&menu=edit_zone"
body="$body&Type=TXT"
@ -71,7 +72,9 @@ dns_he_rm() {
_debug "Zone id \"$_zone_id\" will be used."
# Find the record id to clean
body="email=${HE_Username}&pass=${HE_Password}"
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
body="$body&hosted_dns_zoneid=$_zone_id"
body="$body&menu=edit_zone"
body="$body&hosted_dns_editzone="
@ -89,7 +92,9 @@ dns_he_rm() {
return 1
fi
# Remove the record
body="email=${HE_Username}&pass=${HE_Password}"
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
body="$body&menu=edit_zone"
body="$body&hosted_dns_zoneid=$_zone_id"
body="$body&hosted_dns_recordid=$_record_id"
@ -112,9 +117,15 @@ dns_he_rm() {
_find_zone() {
_domain="$1"
body="email=${HE_Username}&pass=${HE_Password}"
username_encoded="$(printf "%s" "${HE_Username}" | _url_encode)"
password_encoded="$(printf "%s" "${HE_Password}" | _url_encode)"
body="email=${username_encoded}&pass=${password_encoded}"
response="$(_post "$body" "https://dns.he.net/")"
_debug2 response "$response"
if _contains "$response" '>Incorrect<'; then
_err "Unable to login to dns.he.net please check username and password"
return 1
fi
_table="$(echo "$response" | tr -d "#" | sed "s/<table/#<table/g" | tr -d "\n" | tr "#" "\n" | grep 'id="domains_table"')"
_debug2 _table "$_table"
_matches="$(echo "$_table" | sed "s/<tr/#<tr/g" | tr "#" "\n" | grep 'alt="edit"' | tr -d " " | sed "s/<td/#<td/g" | tr "#" "\n" | grep 'hosted_dns_zoneid')"

50
dnsapi/dns_inwx.sh

@ -4,6 +4,10 @@
#INWX_User="username"
#
#INWX_Password="password"
#
# Dependencies:
# -------------
# - oathtool (When using 2 Factor Authentication)
INWX_Api="https://api.domrobot.com/xmlrpc/"
@ -16,6 +20,7 @@ dns_inwx_add() {
INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}"
INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}"
INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}"
if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then
INWX_User=""
INWX_Password=""
@ -27,6 +32,7 @@ dns_inwx_add() {
#save the api key and email to the account conf file.
_saveaccountconf_mutable INWX_User "$INWX_User"
_saveaccountconf_mutable INWX_Password "$INWX_Password"
_saveaccountconf_mutable INWX_Shared_Secret "$INWX_Shared_Secret"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
@ -148,8 +154,46 @@ _inwx_login() {
</methodCall>' $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 ';')")
export _H1
#https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71
if _contains "$response" "tfa"; then
if [ -z "$INWX_Shared_Secret" ]; then
_err "Mobile TAN detected."
_err "Please define a shared secret."
return 1
fi
if ! _exists oathtool; then
_err "Please install oathtool to use 2 Factor Authentication."
_err ""
return 1
fi
printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')"
tan="$(oathtool --base32 --totp "${INWX_Shared_Secret}" 2>/dev/null)"
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>account.unlock</methodName>
<params>
<param>
<value>
<struct>
<member>
<name>tan</name>
<value>
<string>%s</string>
</value>
</member>
</struct>
</value>
</param>
</params>
</methodCall>' "$tan")
response="$(_post "$xml_content" "$INWX_Api" "" "POST")"
fi
}
@ -161,8 +205,8 @@ _get_root() {
i=2
p=1
_H1=$(_inwx_login)
export _H1
_inwx_login
xml_content='<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>nameserver.list</methodName>

4
dnsapi/dns_ispconfig.sh

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

55
dnsapi/dns_lexicon.sh

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

227
dnsapi/dns_loopia.sh

@ -0,0 +1,227 @@
#!/usr/bin/env sh
#
#LOOPIA_User="username"
#
#LOOPIA_Password="password"
LOOPIA_Api="https://api.loopia.se/RPCSERV"
######## Public functions #####################
#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_loopia_add() {
fulldomain=$1
txtvalue=$2
LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
LOOPIA_User=""
LOOPIA_Password=""
_err "You don't specify loopia user and password yet."
_err "Please create you key and try again."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable LOOPIA_User "$LOOPIA_User"
_saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _sub_domain "$_sub_domain"
_debug _domain "$_domain"
_info "Adding record"
_loopia_add_record "$_domain" "$_sub_domain"
_loopia_update_record "$_domain" "$_sub_domain" "$txtvalue"
}
dns_loopia_rm() {
fulldomain=$1
txtvalue=$2
LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}"
LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}"
if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then
LOOPIA_User=""
LOOPIA_Password=""
_err "You don't specify LOOPIA user and password yet."
_err "Please create you key and try again."
return 1
fi
#save the api key and email to the account conf file.
_saveaccountconf_mutable LOOPIA_User "$LOOPIA_User"
_saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password"
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>removeSubdomain</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$_domain" "$_sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
_err "Error could not get txt records"
return 1
fi
}
#################### Private functions below ##################################
_get_root() {
domain=$1
_debug "get root"
domain=$1
i=2
p=1
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>getDomains</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password)
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
while true; do
h=$(echo "$domain" | cut -d . -f $i-100)
if [ -z "$h" ]; then
#not valid
return 1
fi
if _contains "$response" "$h"; then
_sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
_domain="$h"
return 0
fi
p=$i
i=$(_math "$i" + 1)
done
return 1
}
_loopia_update_record() {
domain=$1
sub_domain=$2
txtval=$3
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>updateZoneRecord</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<struct>
<member>
<name>type</name>
<value><string>TXT</string></value>
</member>
<member>
<name>priority</name>
<value><int>0</int></value>
</member>
<member>
<name>ttl</name>
<value><int>60</int></value>
</member>
<member>
<name>rdata</name>
<value><string>%s</string></value>
</member>
<member>
<name>record_id</name>
<value><int>0</int></value>
</member>
</struct>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain" "$txtval")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
_err "Error"
return 1
fi
return 0
}
_loopia_add_record() {
domain=$1
sub_domain=$2
xml_content=$(printf '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>addSubdomain</methodName>
<params>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
<param>
<value><string>%s</string></value>
</param>
</params>
</methodCall>' $LOOPIA_User $LOOPIA_Password "$domain" "$sub_domain")
response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")"
if ! _contains "$response" "OK"; then
_err "Error"
return 1
fi
return 0
}

308
dnsapi/dns_namecheap.sh

@ -0,0 +1,308 @@
#!/usr/bin/env sh
# Namecheap API
# https://www.namecheap.com/support/api/intro.aspx
#
# Requires Namecheap API key set in NAMECHEAP_API_KEY, NAMECHEAP_SOURCEIP and NAMECHEAP_USERNAME set as environment variable
# Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise.
######## Public functions #####################
if [ "$STAGE" -eq 1 ]; then
NAMECHEAP_API="https://api.sandbox.namecheap.com/xml.response"
else
NAMECHEAP_API="https://api.namecheap.com/xml.response"
fi
#Usage: dns_namecheap_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
dns_namecheap_add() {
fulldomain=$1
txtvalue=$2
if ! _namecheap_check_config; then
_err "$error"
return 1
fi
if ! _namecheap_set_publicip; then
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug domain "$_domain"
_debug sub_domain "$_sub_domain"
_set_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue"
}
#Usage: fulldomain txtvalue
#Remove the txt record after validation.
dns_namecheap_rm() {
fulldomain=$1
txtvalue=$2
if ! _namecheap_set_publicip; then
return 1
fi
if ! _namecheap_check_config; then
_err "$error"
return 1
fi
_debug "First detect the root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug fulldomain "$fulldomain"
_debug txtvalue "$txtvalue"
_debug domain "$_domain"
_debug sub_domain "$_sub_domain"
_del_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue"
}
#################### Private functions below ##################################
#_acme-challenge.www.domain.com
#returns
# _sub_domain=_acme-challenge.www
# _domain=domain.com
_get_root() {
domain=$1
if ! _namecheap_post "namecheap.domains.getList"; then
_err "$error"
return 1
fi
i=2
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 ! _contains "$response" "$h"; 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
}
_namecheap_set_publicip() {
if [ -z "$NAMECHEAP_SOURCEIP" ]; then
_err "No Source IP specified for Namecheap API."
_err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
else
_saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP"
_debug sourceip "$NAMECHEAP_SOURCEIP"
ip=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}')
addr=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '(http|https)://.*')
_debug2 ip "$ip"
_debug2 addr "$addr"
if [ -n "$ip" ]; then
_publicip="$ip"
elif [ -n "$addr" ]; then
_publicip=$(_get "$addr")
else
_err "No Source IP specified for Namecheap API."
_err "Use your public ip address or an url to retrieve it (e.g. https://ipconfig.co/ip) and export it as NAMECHEAP_SOURCEIP"
return 1
fi
fi
_debug publicip "$_publicip"
return 0
}
_namecheap_post() {
command=$1
data="ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}"
response="$(_post "$data" "$NAMECHEAP_API" "" "POST")"
_debug2 response "$response"
if _contains "$response" "Status=\"ERROR\"" >/dev/null; then
error=$(echo "$response" | _egrep_o ">.*<\\/Error>" | cut -d '<' -f 1 | tr -d '>')
_err "error $error"
return 1
fi
return 0
}
_namecheap_parse_host() {
_host=$1
_debug _host "$_host"
_hostid=$(echo "$_host" | _egrep_o '\sHostId="[^"]*' | cut -d '"' -f 2)
_hostname=$(echo "$_host" | _egrep_o '\sName="[^"]*' | cut -d '"' -f 2)
_hosttype=$(echo "$_host" | _egrep_o '\sType="[^"]*' | cut -d '"' -f 2)
_hostaddress=$(echo "$_host" | _egrep_o '\sAddress="[^"]*' | cut -d '"' -f 2)
_hostmxpref=$(echo "$_host" | _egrep_o '\sMXPref="[^"]*' | cut -d '"' -f 2)
_hostttl=$(echo "$_host" | _egrep_o '\sTTL="[^"]*' | cut -d '"' -f 2)
_debug hostid "$_hostid"
_debug hostname "$_hostname"
_debug hosttype "$_hosttype"
_debug hostaddress "$_hostaddress"
_debug hostmxpref "$_hostmxpref"
_debug hostttl "$_hostttl"
}
_namecheap_check_config() {
if [ -z "$NAMECHEAP_API_KEY" ]; then
_err "No API key specified for Namecheap API."
_err "Create your key and export it as NAMECHEAP_API_KEY"
return 1
fi
if [ -z "$NAMECHEAP_USERNAME" ]; then
_err "No username key specified for Namecheap API."
_err "Create your key and export it as NAMECHEAP_USERNAME"
return 1
fi
_saveaccountconf NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY"
_saveaccountconf NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME"
return 0
}
_set_namecheap_TXT() {
subdomain=$2
txt=$3
tld=$(echo "$1" | cut -d '.' -f 2)
sld=$(echo "$1" | cut -d '.' -f 1)
request="namecheap.domains.dns.getHosts&SLD=$sld&TLD=$tld"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
hosts=$(echo "$response" | _egrep_o '<host[^>]*')
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
_error "Hosts not found"
return 1
fi
_namecheap_reset_hostList
while read -r host; do
if _contains "$host" "<host"; then
_namecheap_parse_host "$host"
_namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
fi
done <<EOT
echo "$hosts"
EOT
_namecheap_add_host "$subdomain" "TXT" "$txt" 10 120
_debug hostrequestfinal "$_hostrequest"
request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
return 0
}
_del_namecheap_TXT() {
subdomain=$2
txt=$3
tld=$(echo "$1" | cut -d '.' -f 2)
sld=$(echo "$1" | cut -d '.' -f 1)
request="namecheap.domains.dns.getHosts&SLD=$sld&TLD=$tld"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
hosts=$(echo "$response" | _egrep_o '<host[^>]*')
_debug hosts "$hosts"
if [ -z "$hosts" ]; then
_error "Hosts not found"
return 1
fi
_namecheap_reset_hostList
found=0
while read -r host; do
if _contains "$host" "<host"; then
_namecheap_parse_host "$host"
if [ "$_hosttype" = "TXT" ] && [ "$_hostname" = "$subdomain" ] && [ "$_hostaddress" = "$txt" ]; then
_debug "TXT entry found"
found=1
else
_namecheap_add_host "$_hostname" "$_hosttype" "$_hostaddress" "$_hostmxpref" "$_hostttl"
fi
fi
done <<EOT
echo "$hosts"
EOT
if [ $found -eq 0 ]; then
_debug "TXT entry not found"
return 0
fi
_debug hostrequestfinal "$_hostrequest"
request="namecheap.domains.dns.setHosts&SLD=${sld}&TLD=${tld}${_hostrequest}"
if ! _namecheap_post "$request"; then
_err "$error"
return 1
fi
return 0
}
_namecheap_reset_hostList() {
_hostindex=0
_hostrequest=""
}
#Usage: _namecheap_add_host HostName RecordType Address MxPref TTL
_namecheap_add_host() {
_hostindex=$(_math "$_hostindex" + 1)
_hostrequest=$(printf '%s&HostName%d=%s&RecordType%d=%s&Address%d=%s&MXPref%d=%d&TTL%d=%d' "$_hostrequest" "$_hostindex" "$1" "$_hostindex" "$2" "$_hostindex" "$3" "$_hostindex" "$4" "$_hostindex" "$5")
}

133
dnsapi/dns_netcup.sh

@ -0,0 +1,133 @@
#!/usr/bin/env sh
#developed by linux-insideDE
NC_Apikey="${NC_Apikey:-$(_readaccountconf_mutable NC_Apikey)}"
NC_Apipw="${NC_Apipw:-$(_readaccountconf_mutable NC_Apipw)}"
NC_CID="${NC_CID:-$(_readaccountconf_mutable NC_CID)}"
end="https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON"
client=""
dns_netcup_add() {
login
if [ "$NC_Apikey" = "" ] || [ "$NC_Apipw" = "" ] || [ "$NC_CID" = "" ]; then
_err "No Credentials given"
return 1
fi
_saveaccountconf_mutable NC_Apikey "$NC_Apikey"
_saveaccountconf_mutable NC_Apipw "$NC_Apipw"
_saveaccountconf_mutable NC_CID "$NC_CID"
fulldomain=$1
txtvalue=$2
domain=""
exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
exit=$(_math "$exit" + 1)
i=$exit
while
[ "$exit" -gt 0 ]
do
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
domain="$tmp"
else
domain="$tmp.$domain"
fi
if [ "$(_math "$i" - "$exit")" -ge 1 ]; then
msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"false\", \"state\": \"yes\"} ]}}}" "$end" "" "POST")
_debug "$msg"
if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
else
break
fi
fi
fi
exit=$(_math "$exit" - 1)
done
logout
}
dns_netcup_rm() {
login
fulldomain=$1
txtvalue=$2
domain=""
exit=$(echo "$fulldomain" | tr -dc '.' | wc -c)
exit=$(_math "$exit" + 1)
i=$exit
rec=""
while
[ "$exit" -gt 0 ]
do
tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit")
if [ "$(_math "$i" - "$exit")" -eq 0 ]; then
domain="$tmp"
else
domain="$tmp.$domain"
fi
if [ "$(_math "$i" - "$exit")" -ge 1 ]; then
msg=$(_post "{\"action\": \"infoDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\", \"domainname\": \"$domain\"}}" "$end" "" "POST")
rec=$(echo "$msg" | sed 's/\[//g' | sed 's/\]//g' | sed 's/{\"serverrequestid\".*\"dnsrecords\"://g' | sed 's/},{/};{/g' | sed 's/{//g' | sed 's/}//g')
_debug "$msg"
if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
else
break
fi
fi
fi
exit=$(_math "$exit" - 1)
done
ida=0000
idv=0001
ids=0000000000
i=1
while
[ "$i" -ne 0 ]
do
specrec=$(_getfield "$rec" "$i" ";")
idv="$ida"
ida=$(_getfield "$specrec" "1" "," | sed 's/\"id\":\"//g' | sed 's/\"//g')
txtv=$(_getfield "$specrec" "5" "," | sed 's/\"destination\":\"//g' | sed 's/\"//g')
i=$(_math "$i" + 1)
if [ "$txtvalue" = "$txtv" ]; then
i=0
ids="$ida"
fi
if [ "$ida" = "$idv" ]; then
i=0
fi
done
msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"$ids\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"TRUE\", \"state\": \"yes\"} ]}}}" "$end" "" "POST")
_debug "$msg"
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
fi
logout
}
login() {
tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
sid=$(_getfield "$tmp" "8" | sed s/\"responsedata\":\{\"apisessionid\":\"//g | sed 's/\"\}\}//g')
_debug "$tmp"
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
fi
}
logout() {
tmp=$(_post "{\"action\": \"logout\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
_debug "$tmp"
if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
_err "$msg"
return 1
fi
}

7
dnsapi/dns_nsupdate.sh

@ -8,12 +8,14 @@ dns_nsupdate_add() {
txtvalue=$2
_checkKeyFile || return 1
[ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
[ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
# save the dns server and key to the account conf file.
_saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}"
_saveaccountconf NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}"
_saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}"
_info "adding ${fulldomain}. 60 in txt \"${txtvalue}\""
nsupdate -k "${NSUPDATE_KEY}" <<EOF
server ${NSUPDATE_SERVER}
server ${NSUPDATE_SERVER} ${NSUPDATE_SERVER_PORT}
update add ${fulldomain}. 60 in txt "${txtvalue}"
send
EOF
@ -30,9 +32,10 @@ dns_nsupdate_rm() {
fulldomain=$1
_checkKeyFile || return 1
[ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost"
[ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53
_info "removing ${fulldomain}. txt"
nsupdate -k "${NSUPDATE_KEY}" <<EOF
server ${NSUPDATE_SERVER}
server ${NSUPDATE_SERVER} ${NSUPDATE_SERVER_PORT}
update delete ${fulldomain}. txt
send
EOF

53
dnsapi/dns_pdns.sh

@ -69,15 +69,21 @@ dns_pdns_add() {
#fulldomain
dns_pdns_rm() {
fulldomain=$1
txtvalue=$2
if [ -z "$PDNS_Ttl" ]; then
PDNS_Ttl="$DEFAULT_PDNS_TTL"
fi
_debug "Detect root zone"
if ! _get_root "$fulldomain"; then
_err "invalid domain"
return 1
fi
_debug _domain "$_domain"
if ! rm_record "$_domain" "$fulldomain"; then
if ! rm_record "$_domain" "$fulldomain" "$txtvalue"; then
return 1
fi
@ -88,9 +94,16 @@ set_record() {
_info "Adding record"
root=$1
full=$2
txtvalue=$3
new_challenge=$3
_record_string=""
_build_record_string "$new_challenge"
_list_existingchallenges
for oldchallenge in $_existing_challenges; do
_build_record_string "$oldchallenge"
done
if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then
if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
_err "Set txt record error."
return 1
fi
@ -106,15 +119,38 @@ rm_record() {
_info "Remove record"
root=$1
full=$2
txtvalue=$3
#Enumerate existing acme challenges
_list_existingchallenges
if _contains "$_existing_challenges" "$txtvalue"; then
#Delete all challenges (PowerDNS API does not allow to delete content)
if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
_err "Delete txt record error."
return 1
fi
_record_string=""
#If the only existing challenge was the challenge to delete: nothing to do
if ! [ "$_existing_challenges" = "$txtvalue" ]; then
for oldchallenge in $_existing_challenges; do
#Build up the challenges to re-add, ommitting the one what should be deleted
if ! [ "$oldchallenge" = "$txtvalue" ]; then
_build_record_string "$oldchallenge"
fi
done
#Recreate the existing challenges
if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
_err "Set txt record error."
return 1
fi
fi
if ! notify_slaves "$root"; then
return 1
fi
else
_info "Record not found, nothing to remove"
fi
return 0
}
@ -185,3 +221,12 @@ _pdns_rest() {
return 0
}
_build_record_string() {
_record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}"
}
_list_existingchallenges() {
_pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones/$root"
_existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p')
}

69
dnsapi/dns_tele3.sh

@ -0,0 +1,69 @@
#!/usr/bin/env sh
#
# tele3.cz DNS API
#
# Author: Roman Blizik
# Report Bugs here: https://github.com/par-pa/acme.sh
#
# --
# export TELE3_Key="MS2I4uPPaI..."
# export TELE3_Secret="kjhOIHGJKHg"
# --
TELE3_API="https://www.tele3.cz/acme/"
######## Public functions #####################
dns_tele3_add() {
_info "Using TELE3 DNS"
data="\"ope\":\"add\", \"domain\":\"$1\", \"value\":\"$2\""
if ! _tele3_call; then
_err "Publish zone failed"
return 1
fi
_info "Zone published"
}
dns_tele3_rm() {
_info "Using TELE3 DNS"
data="\"ope\":\"rm\", \"domain\":\"$1\", \"value\":\"$2\""
if ! _tele3_call; then
_err "delete TXT record failed"
return 1
fi
_info "TXT record successfully deleted"
}
#################### Private functions below ##################################
_tele3_init() {
TELE3_Key="${TELE3_Key:-$(_readaccountconf_mutable TELE3_Key)}"
TELE3_Secret="${TELE3_Secret:-$(_readaccountconf_mutable TELE3_Secret)}"
if [ -z "$TELE3_Key" ] || [ -z "$TELE3_Secret" ]; then
TELE3_Key=""
TELE3_Secret=""
_err "You must export variables: TELE3_Key and TELE3_Secret"
return 1
fi
#save the config variables to the account conf file.
_saveaccountconf_mutable TELE3_Key "$TELE3_Key"
_saveaccountconf_mutable TELE3_Secret "$TELE3_Secret"
}
_tele3_call() {
_tele3_init
data="{\"key\":\"$TELE3_Key\", \"secret\":\"$TELE3_Secret\", $data}"
_debug data "$data"
response="$(_post "$data" "$TELE3_API" "" "POST")"
_debug response "$response"
if [ "$response" != "success" ]; then
_err "$response"
return 1
fi
}

23
dnsapi/dns_unoeuro.sh

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

Loading…
Cancel
Save