diff --git a/.travis.yml b/.travis.yml index b6b57423..04de1934 100644 --- a/.travis.yml +++ b/.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 diff --git a/Dockerfile b/Dockerfile index b2866739..5a64c720 100644 --- a/Dockerfile +++ b/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/* diff --git a/README.md b/README.md index b5d5aff0..b9a5cc59 100644 --- a/README.md +++ b/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: diff --git a/acme.sh b/acme.sh index da8e60c9..7944d5df 100755 --- a/acme.sh +++ b/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 + __request_conent_type="$CONTENT_TYPE_JSON" + 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 diff --git a/deploy/README.md b/deploy/README.md index 0833173c..3811a5b5 100644 --- a/deploy/README.md +++ b/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: diff --git a/deploy/cpanel_uapi.sh b/deploy/cpanel_uapi.sh index 4563b9c4..44844f79 100644 --- a/deploy/cpanel_uapi.sh +++ b/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 -# 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 +# 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" diff --git a/deploy/fritzbox.sh b/deploy/fritzbox.sh index 943b198d..21ea6cfd 100644 --- a/deploy/fritzbox.sh +++ b/deploy/fritzbox.sh @@ -28,8 +28,10 @@ fritzbox_deploy() { _debug _cfullchain "$_cfullchain" if ! _exists iconv; then - _err "iconv not found" - return 1 + if ! _exists perl; then + _err "iconv or perl not found" + return 1 + fi fi _fritzbox_username="${DEPLOY_FRITZBOX_USERNAME}" @@ -61,7 +63,11 @@ fritzbox_deploy() { _info "Log in to the FRITZ!Box" _fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*//' -e 's/<\/Challenge>.*$//')" - _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')" + 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/^.*//' -e 's/<\/SID>.*$//')" if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then diff --git a/deploy/gitlab.sh b/deploy/gitlab.sh new file mode 100644 index 00000000..ba2d3122 --- /dev/null +++ b/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 +} diff --git a/deploy/haproxy.sh b/deploy/haproxy.sh index 34efbb1f..5c1a40e2 100644 --- a/deploy/haproxy.sh +++ b/deploy/haproxy.sh @@ -20,7 +20,39 @@ haproxy_deploy() { _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" - _err "deploy cert to haproxy server, Not implemented yet" - return 1 + # handle reload preference + DEFAULT_HAPROXY_RELOAD="/usr/sbin/service haproxy restart" + if [ -z "${DEPLOY_HAPROXY_RELOAD}" ]; then + _reload="${DEFAULT_HAPROXY_RELOAD}" + _cleardomainconf DEPLOY_HAPROXY_RELOAD + else + _reload="${DEPLOY_HAPROXY_RELOAD}" + _savedomainconf DEPLOY_HAPROXY_RELOAD "$DEPLOY_HAPROXY_RELOAD" + fi + _savedomainconf DEPLOY_HAPROXY_PEM_PATH "$DEPLOY_HAPROXY_PEM_PATH" + + # work out the path where the PEM file should go + _pem_path="${DEPLOY_HAPROXY_PEM_PATH}" + if [ -z "$_pem_path" ]; then + _err "Path to save PEM file not found. Please define DEPLOY_HAPROXY_PEM_PATH." + return 1 + fi + _pem_full_path="$_pem_path/$_cdomain.pem" + _info "Full path to PEM $_pem_full_path" + + # combine the key and fullchain into a single pem and install + cat "$_cfullchain" "$_ckey" >"$_pem_full_path" + chmod 600 "$_pem_full_path" + _info "Certificate successfully deployed" + + # restart HAProxy + _info "Run reload: $_reload" + if eval "$_reload"; then + _info "Reload success!" + return 0 + else + _err "Reload error" + return 1 + fi } diff --git a/deploy/ssh.sh b/deploy/ssh.sh index a68da356..9cb0af9e 100644 --- a/deploy/ssh.sh +++ b/deploy/ssh.sh @@ -11,7 +11,7 @@ # # Only a username is required. All others are optional. # -# The following examples are for QNAP NAS running QTS 4.2 +# The following examples are for QNAP NAS running QTS 4.2 # export DEPLOY_SSH_CMD="" # defaults to ssh # export DEPLOY_SSH_USER="admin" # required # export DEPLOY_SSH_SERVER="qnap" # defaults to domain name @@ -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 diff --git a/dnsapi/README.md b/dnsapi/README.md index ba1c045c..2cecfa5a 100644 --- a/dnsapi/README.md +++ b/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="" +export DH_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="" +export ACMEDNS_PASSWORD="" +export ACMEDNS_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 + +## 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="" +export NC_Apipw="" +export NC_CID="" +``` + +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. diff --git a/dnsapi/dns_acmedns.sh b/dnsapi/dns_acmedns.sh new file mode 100644 index 00000000..9b3efa48 --- /dev/null +++ b/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 ################################## diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 8ce7c347..2ad3c819 100755 --- a/dnsapi/dns_aws.sh +++ b/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="UPSERT$fulldomainTXT300$_resource_record\"$txtvalue\"" 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//"/g' | tr '"' "\n" | grep "$fulldomain." | _egrep_o "" | sed "s///" | sed "s###")" _debug "_resource_record" "$_resource_record" else - _debug "no records exists, skip" + _debug "no records exist, skip" return 0 fi _aws_tmpl_xml="DELETE$_resource_record$fulldomain.TXT300" 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 diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh index e0d9516f..c6893a0c 100644 --- a/dnsapi/dns_azure.sh +++ b/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 diff --git a/dnsapi/dns_conoha.sh b/dnsapi/dns_conoha.sh new file mode 100755 index 00000000..d3bee130 --- /dev/null +++ b/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 +} diff --git a/dnsapi/dns_cx.sh b/dnsapi/dns_cx.sh index f2d3eadb..d07d8e0c 100755 --- a/dnsapi/dns_cx.sh +++ b/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 diff --git a/dnsapi/dns_dpi.sh b/dnsapi/dns_dpi.sh new file mode 100755 index 00000000..831150a9 --- /dev/null +++ b/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 +} diff --git a/dnsapi/dns_euserv.sh b/dnsapi/dns_euserv.sh new file mode 100644 index 00000000..38101565 --- /dev/null +++ b/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 ' + + domain.dns_get_active_records + + + + + + login + + %s + + + + password + + %s + + + + domain_id + + %s + + + + + + + ' "$EUSERV_Username" "$EUSERV_Password" "$_euserv_domain_id") + + export _H1="Content-Type: text/xml" + response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")" + + if ! _contains "$response" "status100"; 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 Tag with a number before the row _endLine, identified by + _record_id=$(echo "$response" | sed -n '1,'"$_endLine"'p' | grep '' | _tail_n 1 | sed 's/.*\([0-9]*\)<\/name>.*/\1/') + _info "Deleting record" + _euserv_delete_record "$_record_id" + 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 ' + + domain.get_domain_orders + + + + + + login + %s + + + password + %s + + + + + + ' "$EUSERV_Username" "$EUSERV_Password") + + export _H1="Content-Type: text/xml" + response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")" + + if ! _contains "$response" "status100"; 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/.*\([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 ' + + domain.dns_delete_record + + + + + + login + + %s + + + + password + + %s + + + + dns_record_id + + %s + + + + + + + ' "$EUSERV_Username" "$EUSERV_Password" "$record_id") + + export _H1="Content-Type: text/xml" + response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")" + + if ! _contains "$response" "status100"; 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 ' + + domain.dns_create_record + + + + + + login + + %s + + + + password + + %s + + + domain_id + + %s + + + + dns_record_subdomain + + %s + + + + dns_record_type + + TXT + + + + dns_record_value + + %s + + + + dns_record_ttl + + 300 + + + + + + + ' "$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" "status100"; then + _err "Error could not create record" + _debug "xml_content" "$xml_content" + _debug "response" "$response" + return 1 + fi + + return 0 +} diff --git a/dnsapi/dns_gcloud.sh b/dnsapi/dns_gcloud.sh new file mode 100755 index 00000000..99fbf410 --- /dev/null +++ b/dnsapi/dns_gcloud.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env sh + +# Author: Janos Lenart + +######## 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') +} diff --git a/dnsapi/dns_gd.sh b/dnsapi/dns_gd.sh index 5fb1b174..7cf47386 100755 --- a/dnsapi/dns_gd.sh +++ b/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 + _info "Added, sleeping 10 seconds" + _sleep 10 + #todo: check if the record takes effect + return 0 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 } diff --git a/dnsapi/dns_gdnsdk.sh b/dnsapi/dns_gdnsdk.sh new file mode 100755 index 00000000..7dc7894a --- /dev/null +++ b/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 "$1\s*$2[^?]*[^&]*&id=[^&]*" | sed 's/^.*=//') + if [ -n "$_id" ]; then + _debug "Entry found with _id=$_id" + return 0 + fi + return 1 +} diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh index d196fbec..df00c746 100755 --- a/dnsapi/dns_he.sh +++ b/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/' $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 - printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')" + #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 + + tan="$(oathtool --base32 --totp "${INWX_Shared_Secret}" 2>/dev/null)" + + xml_content=$(printf ' + + account.unlock + + + + + + tan + + %s + + + + + + + ' "$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=' nameserver.list diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index 1e500ad6..2d8d6b0a 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/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'" diff --git a/dnsapi/dns_lexicon.sh b/dnsapi/dns_lexicon.sh index c09f16fd..f6f54464 100755 --- a/dnsapi/dns_lexicon.sh +++ b/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}" } diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh new file mode 100644 index 00000000..5d761187 --- /dev/null +++ b/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 ' + + removeSubdomain + + + %s + + + %s + + + %s + + + %s + + + ' $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 ' + + getDomains + + + %s + + + %s + + + ' $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 ' + + updateZoneRecord + + + %s + + + %s + + + %s + + + %s + + + + + type + TXT + + + priority + 0 + + + ttl + 60 + + + rdata + %s + + + record_id + 0 + + + + + ' $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 ' + + addSubdomain + + + %s + + + %s + + + %s + + + %s + + + ' $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 +} diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh new file mode 100755 index 00000000..7089c2d0 --- /dev/null +++ b/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 ']*') + _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" "]*') + _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" "/dev/null; then - _info "Adding record" - - if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then - if _contains "$response" "\"status\": 200" >/dev/null; then - _info "Added, OK" - return 0 - else - _err "Add txt record error." - return 1 - fi - fi - _err "Add txt record error." - else - _info "Updating record" - record_line_number=$(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1) - record_line_number=$(_math "$record_line_number" - 1) - record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") - _debug "record_id" "$record_id" - - _uno_rest PUT "my/products/$h/dns/records/$record_id" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}" + if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120}"; then if _contains "$response" "\"status\": 200" >/dev/null; then - _info "Updated, OK" + _info "Added, OK" return 0 + else + _err "Add txt record error." + return 1 fi - _err "Update error" - return 1 fi } @@ -122,23 +104,24 @@ 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) - record_line_number=$(_math "$record_line_number" - 1) - record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") - _debug "record_id" "$record_id" - - if [ -z "$record_id" ]; then - _err "Can not get record id to remove." - return 1 - fi + for record_line_number in $(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1); do + record_line_number=$(_math "$record_line_number" - 1) + _debug "record_line_number" "$record_line_number" + record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") + _debug "record_id" "$record_id" + + if [ -z "$record_id" ]; then + _err "Can not get record id to remove." + return 1 + fi - if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then - _err "Delete record error." - return 1 - fi - _contains "$response" "\"status\": 200" + if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then + _err "Delete record error." + return 1 + fi + _contains "$response" "\"status\": 200" + done fi - } #################### Private functions below ##################################