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 2801692c..c5da6af5 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,8 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) - [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) +- [CentOS Web Panel](http://centos-webpanel.com/) +- [lnmp.org](https://lnmp.org/) - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials) # Tested OS @@ -220,22 +222,7 @@ acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert -# 5. Use Standalone TLS server to issue cert - -**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** - -acme.sh supports `tls-sni-01` validation. - -Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. - -```bash -acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com -``` - -More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert - - -# 6. Use Apache mode +# 5. Use Apache mode **(requires you to be root/sudoer, since it is required to interact with Apache server)** @@ -255,7 +242,7 @@ We don't want to mess your apache server, don't worry.** More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert -# 7. Use Nginx mode +# 6. Use Nginx mode **(requires you to be root/sudoer, since it is required to interact with Nginx server)** @@ -279,7 +266,7 @@ We don't want to mess your nginx server, don't worry.** More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert -# 8. Automatic DNS API integration +# 7. Automatic DNS API integration If your DNS provider supports API access, we can use that API to automatically issue the certs. @@ -327,14 +314,14 @@ You don't have to do anything manually! 1. selectel.com(selectel.ru) DNS API 1. zonomi.com DNS API 1. DreamHost.com API +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. DNSEver(https://www.dnsever.com) - - - - - - And: **lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api @@ -347,7 +334,9 @@ If your DNS provider is not on the supported list above, you can write your own For more details: [How to use DNS API](dnsapi) -# 9. Use DNS manual mode: +# 8. Use DNS manual mode: + +See: https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode first. If your dns provider doesn't support any api access, you can add the txt record by your hand. @@ -381,7 +370,7 @@ Ok, it's done. **Please use dns api mode instead.** -# 10. Issue ECC certificates +# 9. Issue ECC certificates `Let's Encrypt` can now issue **ECDSA** certificates. @@ -413,17 +402,17 @@ Valid values are: -# 11. Issue Wildcard certificates +# 10. Issue Wildcard certificates It's simple, just give a wildcard domain as the `-d` parameter. ```sh -acme.sh --issue -d example.com -d *.example.com --dns dns_cf +acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf ``` -# 12. How to renew the certs +# 11. How to renew the certs No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. @@ -440,7 +429,7 @@ acme.sh --renew -d example.com --force --ecc ``` -# 13. How to stop cert renewal +# 12. How to stop cert renewal To stop renewal of a cert, you can execute the following to remove the cert from the renewal list: @@ -453,7 +442,7 @@ The cert/key file is not removed from the disk. You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself. -# 14. How to upgrade `acme.sh` +# 13. How to upgrade `acme.sh` acme.sh is in constant development, so it's strongly recommended to use the latest code. @@ -478,25 +467,25 @@ acme.sh --upgrade --auto-upgrade 0 ``` -# 15. Issue a cert from an existing CSR +# 14. Issue a cert from an existing CSR https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR -# 16. Under the Hood +# 15. Under the Hood Speak ACME language using shell, directly to "Let's Encrypt". TODO: -# 17. Acknowledgments +# 16. Acknowledgments 1. Acme-tiny: https://github.com/diafygi/acme-tiny 2. ACME protocol: https://github.com/ietf-wg-acme/acme -# 18. License & Others +# 17. License & Others License is GPLv3 @@ -505,7 +494,7 @@ Please Star and Fork me. [Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome. -# 19. Donate +# 18. Donate Your donation makes **acme.sh** better: 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) diff --git a/acme.sh b/acme.sh index 2ca7fa97..713170b7 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=2.7.7 +VER=2.7.9 PROJECT_NAME="acme.sh" @@ -47,6 +47,7 @@ DEFAULT_DNS_SLEEP=120 NO_VALUE="no" W_TLS="tls" +W_DNS="dns" DNS_ALIAS_PREFIX="=" MODE_STATELESS="stateless" @@ -63,6 +64,7 @@ END_CSR="-----END CERTIFICATE REQUEST-----" BEGIN_CERT="-----BEGIN CERTIFICATE-----" END_CERT="-----END CERTIFICATE-----" +CONTENT_TYPE_JSON="application/jose+json" RENEW_SKIP=2 ECC_SEP="_" @@ -108,31 +110,35 @@ _STATELESS_WIKI="https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode" _DNS_ALIAS_WIKI="https://github.com/Neilpang/acme.sh/wiki/DNS-alias-mode" +_DNS_MANUAL_WIKI="https://github.com/Neilpang/acme.sh/wiki/dns-manual-mode" + _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead." _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR" +_DNS_MANUAL_ERROR="It seems that you are using dns manual mode. Read this link first: $_DNS_MANUAL_WIKI" + __INTERACTIVE="" if [ -t 1 ]; then __INTERACTIVE="1" 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 } @@ -848,6 +854,16 @@ _dbase64() { fi } +#file +_checkcert() { + _cf="$1" + if [ "$DEBUG" ]; then + openssl x509 -noout -text -in "$_cf" + else + openssl x509 -noout -text -in "$_cf" >/dev/null 2>&1 + fi +} + #Usage: hashalg [outputhex] #Output Base64-encoded digest _digest() { @@ -1591,12 +1607,13 @@ _inithttp() { } -# body url [needbase64] [POST|PUT] +# body url [needbase64] [POST|PUT] [ContentType] _post() { body="$1" _post_url="$2" needbase64="$3" httpmethod="$4" + _postContentType="$5" if [ -z "$httpmethod" ]; then httpmethod="POST" @@ -1604,6 +1621,7 @@ _post() { _debug $httpmethod _debug "_post_url" "$_post_url" _debug2 "body" "$body" + _debug2 "_postContentType" "$_postContentType" _inithttp @@ -1614,9 +1632,17 @@ _post() { fi _debug "_CURL" "$_CURL" if [ "$needbase64" ]; then - response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)" + if [ "$_postContentType" ]; then + response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)" + else + response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url" | _base64)" + fi else - response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")" + if [ "$_postContentType" ]; then + response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "Content-Type: $_postContentType" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")" + else + response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$_post_url")" + fi fi _ret="$?" if [ "$_ret" != "0" ]; then @@ -1634,15 +1660,31 @@ _post() { _debug "_WGET" "$_WGET" if [ "$needbase64" ]; then if [ "$httpmethod" = "POST" ]; then - response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)" + if [ "$_postContentType" ]; then + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)" + else + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)" + fi else - response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)" + if [ "$_postContentType" ]; then + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)" + else + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER" | _base64)" + fi fi else if [ "$httpmethod" = "POST" ]; then - response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")" + if [ "$_postContentType" ]; then + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")" + else + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$_post_url" 2>"$HTTP_HEADER")" + fi else - response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")" + if [ "$_postContentType" ]; then + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --header "Content-Type: $_postContentType" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")" + else + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$_post_url" 2>"$HTTP_HEADER")" + fi fi fi _ret="$?" @@ -1753,19 +1795,25 @@ _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 _request_retry_times=0 while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do + _request_retry_times=$(_math "$_request_retry_times" + 1) _debug3 _request_retry_times "$_request_retry_times" if [ -z "$_CACHED_NONCE" ]; then _headers="" if [ "$ACME_NEW_NONCE" ]; then _debug2 "Get nonce. ACME_NEW_NONCE" "$ACME_NEW_NONCE" nonceurl="$ACME_NEW_NONCE" - if _post "" "$nonceurl" "" "HEAD"; then + if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type"; then _headers="$(cat "$HTTP_HEADER")" fi fi @@ -1789,7 +1837,11 @@ _send_signed_request() { fi nonce="$_CACHED_NONCE" _debug2 nonce "$nonce" - + if [ -z "$nonce" ]; then + _info "Could not get nonce, let's try again." + _sleep 2 + continue + fi if [ "$ACME_VERSION" = "2" ]; then if [ "$url" = "$ACME_NEW_ACCOUNT" ] || [ "$url" = "$ACME_REVOKE_CERT" ]; then protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' @@ -1820,7 +1872,7 @@ _send_signed_request() { fi _debug3 body "$body" - response="$(_post "$body" "$url" "$needbase64")" + response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")" _CACHED_NONCE="" if [ "$?" != "0" ]; then @@ -1841,13 +1893,12 @@ _send_signed_request() { _body="$response" if [ "$needbase64" ]; then - _body="$(echo "$_body" | _dbase64)" + _body="$(echo "$_body" | _dbase64 | tr -d '\0')" _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." - _request_retry_times=$(_math "$_request_retry_times" + 1) _sleep 5 continue fi @@ -2310,7 +2361,7 @@ _initpath() { fi fi - _debug2 ACME_DIRECTORY "$ACME_DIRECTORY" + _debug ACME_DIRECTORY "$ACME_DIRECTORY" _ACME_SERVER_HOST="$(echo "$ACME_DIRECTORY" | cut -d : -f 2 | tr -s / | cut -d / -f 2)" _debug2 "_ACME_SERVER_HOST" "$_ACME_SERVER_HOST" @@ -2967,6 +3018,8 @@ _on_before_issue() { _chk_pre_hook="$4" _chk_local_addr="$5" _debug _on_before_issue + _debug _chk_main_domain "$_chk_main_domain" + _debug _chk_alt_domains "$_chk_alt_domains" #run pre hook if [ "$_chk_pre_hook" ]; then _info "Run pre hook:'$_chk_pre_hook'" @@ -2987,11 +3040,17 @@ _on_before_issue() { _debug Le_LocalAddress "$_chk_local_addr" - alldomains=$(echo "$_chk_main_domain,$_chk_alt_domains" | tr ',' ' ') _index=1 _currentRoot="" _addrIndex=1 - for d in $alldomains; do + _w_index=1 + while true; do + d="$(echo "$_chk_main_domain,$_chk_alt_domains," | cut -d , -f "$_w_index")" + _w_index="$(_math "$_w_index" + 1)" + _debug d "$d" + if [ -z "$d" ]; then + break + fi _debug "Check for domain" "$d" _currentRoot="$(_getfield "$_chk_web_roots" $_index)" _debug "_currentRoot" "$_currentRoot" @@ -3087,7 +3146,7 @@ _on_issue_err() { ) fi - if [ "$IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "dns"; then + if [ "$IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "$W_DNS"; then _err "$_DNS_MANUAL_ERR" fi @@ -3123,7 +3182,7 @@ _on_issue_success() { fi fi - if _hasfield "$Le_Webroot" "dns"; then + if _hasfield "$Le_Webroot" "$W_DNS"; then _err "$_DNS_MANUAL_WARN" fi @@ -3207,10 +3266,16 @@ _regAccount() { return 1 fi + _debug2 responseHeaders "$responseHeaders" _accUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ' ' -f 2 | tr -d "\r\n")" _debug "_accUri" "$_accUri" + if [ -z "$_accUri" ]; then + _err "Can not find account id url." + _err "$responseHeaders" + return 1 + fi _savecaconf "ACCOUNT_URL" "$_accUri" - export ACCOUNT_URL="$ACCOUNT_URL" + export ACCOUNT_URL="$_accUri" CA_KEY_HASH="$(__calcAccountKeyHash)" _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH" @@ -3390,6 +3455,9 @@ issue() { _main_domain=$(echo "$2,$3" | cut -d , -f 1) _alt_domains=$(echo "$2,$3" | cut -d , -f 2- | sed "s/,${NO_VALUE}$//") fi + _debug _main_domain "$_main_domain" + _debug _alt_domains "$_alt_domains" + _key_length="$4" _real_cert="$5" _real_key="$6" @@ -3417,6 +3485,11 @@ issue() { mkdir -p "$DOMAIN_PATH" fi + if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then + _err "$_DNS_MANUAL_ERROR" + return 1 + fi + _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY" _initAPI @@ -3478,7 +3551,7 @@ issue() { _saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")" _debug2 _saved_account_key_hash "$_saved_account_key_hash" - if [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then + if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then if ! _regAccount "$_accountkeylength"; then _on_issue_err "$_post_hook" return 1 @@ -3520,10 +3593,15 @@ issue() { if [ "$ACME_VERSION" = "2" ]; then #make new order request _identifiers="{\"type\":\"dns\",\"value\":\"$_main_domain\"}" - for d in $(echo "$_alt_domains" | tr ',' ' '); do - if [ "$d" ]; then - _identifiers="$_identifiers,{\"type\":\"dns\",\"value\":\"$d\"}" + _w_index=1 + while true; do + d="$(echo "$_alt_domains," | cut -d , -f "$_w_index")" + _w_index="$(_math "$_w_index" + 1)" + _debug d "$d" + if [ -z "$d" ]; then + break fi + _identifiers="$_identifiers,{\"type\":\"dns\",\"value\":\"$d\"}" done _debug2 _identifiers "$_identifiers" if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then @@ -3560,6 +3638,8 @@ issue() { _debug2 "_authz_url" "$_authz_url" if ! response="$(_get "$_authz_url")"; then _err "get to authz error." + _err "_authorizations_seg" "$_authorizations_seg" + _err "_authz_url" "$_authz_url" _clearup _on_issue_err "$_post_hook" return 1 @@ -3578,10 +3658,16 @@ $_authorizations_map" _debug2 _authorizations_map "$_authorizations_map" fi - alldomains=$(echo "$_main_domain,$_alt_domains" | tr ',' ' ') _index=0 _currentRoot="" - for d in $alldomains; do + _w_index=1 + while true; do + d="$(echo "$_main_domain,$_alt_domains," | cut -d , -f "$_w_index")" + _w_index="$(_math "$_w_index" + 1)" + _debug d "$d" + if [ -z "$d" ]; then + break + fi _info "Getting webroot for domain" "$d" _index=$(_math $_index + 1) _w="$(echo $_web_roots | cut -d , -f $_index)" @@ -3593,7 +3679,7 @@ $_authorizations_map" vtype="$VTYPE_HTTP" #todo, v2 wildcard force to use dns - if _startswith "$_currentRoot" "dns"; then + if _startswith "$_currentRoot" "$W_DNS"; then vtype="$VTYPE_DNS" fi @@ -3610,6 +3696,7 @@ $_authorizations_map" _debug2 "response" "$response" if [ -z "$response" ]; then _err "get to authz error." + _err "_authorizations_map" "$_authorizations_map" _clearup _on_issue_err "$_post_hook" return 1 @@ -3720,6 +3807,10 @@ $_authorizations_map" if [ "$d_api" ]; then _info "Found domain api file: $d_api" else + if [ "$_currentRoot" != "$W_DNS" ]; then + _err "Can not find dns api hook for: $_currentRoot" + _info "You need to add the txt record manually." + fi _info "$(__red "Add the following TXT record:")" _info "$(__red "Domain: '$(__green "$txtdomain")'")" _info "$(__red "TXT value: '$(__green "$txt")'")" @@ -3758,7 +3849,7 @@ $_authorizations_map" if [ "$dnsadded" = '0' ]; then _savedomainconf "Le_Vlist" "$vlist" _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit." - _err "Please add the TXT records to the domains, and retry again." + _err "Please add the TXT records to the domains, and re-run with --renew." _clearup _on_issue_err "$_post_hook" return 1 @@ -4022,13 +4113,15 @@ $_authorizations_map" fi if [ "$code" != "200" ]; then _err "Sign failed, code is not 200." + _err "$response" _on_issue_err "$_post_hook" return 1 fi Le_LinkCert="$(echo "$response" | tr -d '\r\n' | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)" if ! _get "$Le_LinkCert" >"$CERT_PATH"; then - _err "Sign failed, code is not 200." + _err "Sign failed, can not download cert:$Le_LinkCert." + _err "$response" _on_issue_err "$_post_hook" return 1 fi @@ -4044,12 +4137,12 @@ $_authorizations_map" fi else if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then - _err "Sign failed." + _err "Sign failed. $response" _on_issue_err "$_post_hook" return 1 fi _rcert="$response" - Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)" + Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)" echo "$BEGIN_CERT" >"$CERT_PATH" #if ! _get "$Le_LinkCert" | _base64 "multiline" >> "$CERT_PATH" ; then @@ -4068,6 +4161,13 @@ $_authorizations_map" _debug "Le_LinkCert" "$Le_LinkCert" _savedomainconf "Le_LinkCert" "$Le_LinkCert" + if [ -z "$Le_LinkCert" ] || ! _checkcert "$CERT_PATH"; then + response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)" + _err "Sign failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')" + _on_issue_err "$_post_hook" + return 1 + fi + if [ "$Le_LinkCert" ]; then _info "$(__green "Cert success.")" cat "$CERT_PATH" @@ -4078,26 +4178,18 @@ $_authorizations_map" _info "Your cert key is in $(__green " $CERT_KEY_PATH ")" fi - cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH" - if [ ! "$USER_PATH" ] || [ ! "$IN_CRON" ]; then USER_PATH="$PATH" _saveaccountconf "USER_PATH" "$USER_PATH" fi fi - if [ -z "$Le_LinkCert" ]; then - response="$(echo "$response" | _dbase64 "multiline" | _normalizeJson)" - _err "Sign failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')" - _on_issue_err "$_post_hook" - return 1 - fi - _cleardomainconf "Le_Vlist" if [ "$ACME_VERSION" = "2" ]; then _debug "v2 chain." else + cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH" Le_LinkIssuer=$(grep -i '^Link' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | cut -d ';' -f 1 | tr -d '<>') if [ "$Le_LinkIssuer" ]; then @@ -4121,6 +4213,10 @@ $_authorizations_map" echo "$BEGIN_CERT" >"$CA_CERT_PATH" _base64 "multiline" <"$CA_CERT_PATH.der" >>"$CA_CERT_PATH" echo "$END_CERT" >>"$CA_CERT_PATH" + if ! _checkcert "$CA_CERT_PATH"; then + _err "Can not get the ca cert." + break + fi cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH" rm -f "$CA_CERT_PATH.der" break @@ -4191,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] @@ -4230,7 +4327,7 @@ renew() { fi . "$DOMAIN_CONF" - + _debug Le_API "$Le_API" if [ "$Le_API" ]; then if [ "$_OLD_CA_HOST" = "$Le_API" ]; then export Le_API="$DEFAULT_CA" @@ -4579,19 +4676,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 @@ -4601,9 +4698,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 @@ -4613,7 +4710,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 @@ -4834,6 +4931,8 @@ _deactivate() { _debug2 "authzUri" "$authzUri" if ! response="$(_get "$authzUri")"; then _err "get to authz error." + _err "_authorizations_seg" "$_authorizations_seg" + _err "authzUri" "$authzUri" _clearup _on_issue_err "$_post_hook" return 1 @@ -5365,7 +5464,6 @@ Parameters: --webroot, -w /path/to/webroot Specifies the web root folder for web root mode. --standalone Use standalone mode. --stateless Use stateless mode, see: $_STATELESS_WIKI - --tls Use standalone tls mode. --apache Use apache mode. --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file] Use dns mode or dns api. --dnssleep [$DEFAULT_DNS_SLEEP] The time in seconds to wait for all the txt records to take effect in dns api mode. Default $DEFAULT_DNS_SLEEP seconds. @@ -5391,11 +5489,10 @@ Parameters: --cert-home Specifies the home dir to save all the certs, only valid for '--install' command. --config-home Specifies the home dir to save all the configurations. --useragent Specifies the user agent string. it will be saved for future use too. - --accountemail Specifies the account email for registering, Only valid for the '--install' command. - --accountkey Specifies the account key path, Only valid for the '--install' command. + --accountemail Specifies the account email, only valid for the '--install' and '--update-account' command. + --accountkey Specifies the account key path, only valid for the '--install' command. --days Specifies the days to renew the cert when using '--issue' command. The max value is $MAX_RENEW days. --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. - --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses. --listraw Only used for '--list' command, list the certs in raw format. --stopRenewOnError, -se Only valid for '--renew-all' command. Stop if one cert has error in renewal. @@ -5404,6 +5501,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. @@ -5417,6 +5515,8 @@ Parameters: --listen-v6 Force standalone/tls server to listen at ipv6. --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. " } @@ -5746,16 +5846,8 @@ _process() { _webroot="$_webroot,$wvalue" fi ;; - --tls) - wvalue="$W_TLS" - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; --dns) - wvalue="dns" + wvalue="$W_DNS" if [ "$2" ] && ! _startswith "$2" "-"; then wvalue="$2" shift @@ -5849,12 +5941,6 @@ _process() { Le_HTTPPort="$_httpport" shift ;; - --tlsport) - _tlsport="$2" - Le_TLSPort="$_tlsport" - shift - ;; - --listraw) _listraw="raw" ;; @@ -5881,6 +5967,9 @@ _process() { --no-color) export ACME_NO_COLOR=1 ;; + --force-color) + export ACME_FORCE_COLOR=1 + ;; --ecc) _ecc="isEcc" ;; @@ -5919,6 +6008,9 @@ _process() { shift fi ;; + --yes-I-know-dns-manual-mode-enough-go-ahead-please) + export FORCE_DNS_MANUAL=1 + ;; --log | --logfile) _log="1" _logfile="$2" @@ -5972,6 +6064,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/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/keychain.sh b/deploy/keychain.sh index a99ed465..d86b4d03 100644 --- a/deploy/keychain.sh +++ b/deploy/keychain.sh @@ -1,11 +1,5 @@ #!/usr/bin/env sh -#Here is a sample custom api script. -#This file name is "myapi.sh" -#So, here must be a method myapi_deploy() -#Which will be called by acme.sh to deploy the cert -#returns 0 means success, otherwise error. - ######## Public functions ##################### #domain keyfile certfile cafile fullchain diff --git a/deploy/vault_cli.sh b/deploy/vault_cli.sh index 02617c5e..79c25aa2 100644 --- a/deploy/vault_cli.sh +++ b/deploy/vault_cli.sh @@ -51,6 +51,7 @@ vault_cli_deploy() { $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 + $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 } diff --git a/dnsapi/README.md b/dnsapi/README.md index bd3c8df3..32769511 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. @@ -325,6 +329,8 @@ The `CY_Username`, `CY_Password` and `CY_OTP_Secret` will be saved in `~/.acme.s ## 17. Use Domain-Offensive/Resellerinterface/Domainrobot API +ATTENTION: You need to be a registered Reseller to be able to use the ResellerInterface. As a normal user you can not use this method. + You will need your login credentials (Partner ID+Password) to the Resellerinterface, and export them before you run `acme.sh`: ``` export DO_PID="KD-1234567" @@ -525,8 +531,9 @@ For issues, please report to https://github.com/raidenii/acme.sh/issues. ## 28. Use Name.com API -You'll need to fill out the form at https://www.name.com/reseller/apply to apply -for API username and token. +Create your API token here: https://www.name.com/account/settings/api + +Note: `Namecom_Username` should be your Name.com username and not the token name. If you accidentally run the script with the token name as the username see `~/.acme.sh/account.conf` to fix the issue ``` export Namecom_Username="testuser" @@ -638,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. @@ -750,23 +765,125 @@ 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 ``` The 'DH_API_KEY' will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -## 41. Use DNSEver (https://www.dnsever.com/) +## 41. Use DirectAdmin API +The DirectAdmin interface has it's own Let's encrypt functionality, but this +script can be used to generate certificates for names which are not hosted on +DirectAdmin + +User must provide login data and URL to the DirectAdmin incl. port. +You can create an user which only has access to + +- CMD_API_DNS_CONTROL +- CMD_API_SHOW_DOMAINS + +By using the Login Keys function. +See also https://www.directadmin.com/api.php and https://www.directadmin.com/features.php?id=1298 + +``` +export DA_Api="https://remoteUser:remotePassword@da.domain.tld:8443" +export DA_Api_Insecure=1 +``` +Set `DA_Api_Insecure` to 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_da -d example.com -d www.example.com +``` + +The `DA_Api` and `DA_Api_Insecure` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 42. Use KingHost DNS API + +API access must be enabled at https://painel.kinghost.com.br/painel.api.php + +``` +export KINGHOST_Username="yourusername" +export KINGHOST_Password="yourpassword" +acme.sh --issue --dns dns_kinghost -d example.com -d *.example.com +``` + +The `KINGHOST_username` and `KINGHOST_Password` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 43. Use Zilore DNS API + +First, get your API key at https://my.zilore.com/account/api + +``` +export Zilore_Key="5dcad3a2-36cb-50e8-cb92-000002f9" +``` + +Ok, let's issue a cert now: +``` +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 DNSEver (https://www.dnsever.com/) You will need your login credentials (ID+PW) to the DNSEver, and export them before you run acme.sh: ``` export DNSEVER_ID="KD-1234567" export DNSEVER_PW="cdfkjl3n2" -``` -Ok, let's issue a cert now: -``` acme.sh --issue --dns dns_dnsever -d example.com -d www.example.com ``` The DNSEVER_ID and DNSEVER_PW will be saved in ~/.acme.sh/account.conf and will be reused when needed. 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_azure.sh b/dnsapi/dns_azure.sh index 677a9f75..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 @@ -99,6 +99,7 @@ dns_azure_add() { _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken" if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then _info "validation value added" + return 0 else _err "error adding validation value ($_code)" return 1 @@ -171,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 @@ -194,6 +195,7 @@ dns_azure_rm() { _azure_rest PUT "$acmeRecordURI" "$body" "$accesstoken" if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then _info "validation value removed" + return 0 else _err "error removing validation value ($_code)" return 1 @@ -226,8 +228,9 @@ _azure_rest() { else response="$(_get "$ep")" 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 @@ -236,7 +239,7 @@ _azure_rest() { return 1 fi # See https://docs.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes - if [ "$?" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then + if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then _request_retry_times="$(_math "$_request_retry_times" + 1)" _info "REST call error $_code retrying $ep in $_request_retry_times s" _sleep "$_request_retry_times" @@ -281,6 +284,7 @@ _azure_getaccess_token() { body="resource=$(printf "%s" 'https://management.core.windows.net/' | _url_encode)&client_id=$(printf "%s" "$clientID" | _url_encode)&client_secret=$(printf "%s" "$clientSecret" | _url_encode)&grant_type=client_credentials" _secure_debug2 "data $body" response="$(_post "$body" "https://login.microsoftonline.com/$tenantID/oauth2/token" "" "POST")" + _ret="$?" _secure_debug2 "response $response" response="$(echo "$response" | _normalizeJson)" accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") @@ -290,7 +294,7 @@ _azure_getaccess_token() { _err "no acccess token received. Check your Azure settings see $WIKI" return 1 fi - if [ "$?" != "0" ]; then + if [ "$_ret" != "0" ]; then _err "error $response" return 1 fi @@ -304,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 @@ -324,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_cf.sh b/dnsapi/dns_cf.sh index 68264a42..3595b9b0 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -19,8 +19,8 @@ dns_cf_add() { if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then CF_Key="" CF_Email="" - _err "You don't specify cloudflare api key and email yet." - _err "Please create you key and try again." + _err "You didn't specify a cloudflare api key and email yet." + _err "Please create the key and try again." return 1 fi @@ -94,8 +94,8 @@ dns_cf_rm() { if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then CF_Key="" CF_Email="" - _err "You don't specify cloudflare api key and email yet." - _err "Please create you key and try again." + _err "You didn't specify a cloudflare api key and email yet." + _err "Please create the key and try again." return 1 fi diff --git a/dnsapi/dns_da.sh b/dnsapi/dns_da.sh new file mode 100755 index 00000000..7755c7e1 --- /dev/null +++ b/dnsapi/dns_da.sh @@ -0,0 +1,184 @@ +#!/usr/bin/env sh +# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*- +# vim: et ts=2 sw=2 +# +# DirectAdmin 1.41.0 API +# The DirectAdmin interface has it's own Let's encrypt functionality, but this +# script can be used to generate certificates for names which are not hosted on +# DirectAdmin +# +# User must provide login data and URL to DirectAdmin incl. port. +# You can create login key, by using the Login Keys function +# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to +# - CMD_API_DNS_CONTROL +# - CMD_API_SHOW_DOMAINS +# +# See also https://www.directadmin.com/api.php and +# https://www.directadmin.com/features.php?id=1298 +# +# Report bugs to https://github.com/TigerP/acme.sh/issues +# +# Values to export: +# export DA_Api="https://remoteUser:remotePassword@da.example.com:8443" +# export DA_Api_Insecure=1 +# +# Set DA_Api_Insecure to 1 for insecure and 0 for secure -> difference is +# whether ssl cert is checked for validity (0) or whether it is just accepted +# (1) +# +######## Public functions ##################### + +# Usage: dns_myapi_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_da_add() { + fulldomain="${1}" + txtvalue="${2}" + _debug "Calling: dns_da_add() '${fulldomain}' '${txtvalue}'" + _DA_credentials && _DA_getDomainInfo && _DA_addTxt +} + +# Usage: dns_da_rm _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to remove the txt record after validation +dns_da_rm() { + fulldomain="${1}" + txtvalue="${2}" + _debug "Calling: dns_da_rm() '${fulldomain}' '${txtvalue}'" + _DA_credentials && _DA_getDomainInfo && _DA_rmTxt +} + +#################### Private functions below ################################## +# Usage: _DA_credentials +# It will check if the needed settings are available +_DA_credentials() { + DA_Api="${DA_Api:-$(_readaccountconf_mutable DA_Api)}" + DA_Api_Insecure="${DA_Api_Insecure:-$(_readaccountconf_mutable DA_Api_Insecure)}" + if [ -z "${DA_Api}" ] || [ -z "${DA_Api_Insecure}" ]; then + DA_Api="" + DA_Api_Insecure="" + _err "You haven't specified the DirectAdmin Login data, URL and whether you want check the DirectAdmin SSL cert. Please try again." + return 1 + else + _saveaccountconf_mutable DA_Api "${DA_Api}" + _saveaccountconf_mutable DA_Api_Insecure "${DA_Api_Insecure}" + # Set whether curl should use secure or insecure mode + export HTTPS_INSECURE="${DA_Api_Insecure}" + fi +} + +# Usage: _get_root _acme-challenge.www.example.com +# Split the full domain to a domain and subdomain +#returns +# _sub_domain=_acme-challenge.www +# _domain=example.com +_get_root() { + domain=$1 + i=2 + p=1 + # Get a list of all the domains + # response will contain "list[]=example.com&list[]=example.org" + _da_api CMD_API_SHOW_DOMAINS "" "${domain}" + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + # not valid + _debug "The given domain $h is not valid" + return 1 + fi + if _contains "$response" "$h" >/dev/null; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + p=$i + i=$(_math "$i" + 1) + done + _debug "Stop on 100" + return 1 +} + +# Usage: _da_api CMD_API_* data example.com +# Use the DirectAdmin API and check the result +# returns +# response="error=0&text=Result text&details=" +_da_api() { + cmd=$1 + data=$2 + domain=$3 + _debug "$domain; $data" + response="$(_post "$data" "$DA_Api/$cmd" "" "POST")" + + if [ "$?" != "0" ]; then + _err "error $cmd" + return 1 + fi + _debug response "$response" + + case "${cmd}" in + CMD_API_DNS_CONTROL) + # Parse the result in general + # error=0&text=Records Deleted&details= + # error=1&text=Cannot View Dns Record&details=No domain provided + err_field="$(_getfield "$response" 1 '&')" + txt_field="$(_getfield "$response" 2 '&')" + details_field="$(_getfield "$response" 3 '&')" + error="$(_getfield "$err_field" 2 '=')" + text="$(_getfield "$txt_field" 2 '=')" + details="$(_getfield "$details_field" 2 '=')" + _debug "error: ${error}, text: ${text}, details: ${details}" + if [ "$error" != "0" ]; then + _err "error $response" + return 1 + fi + ;; + CMD_API_SHOW_DOMAINS) ;; + esac + return 0 +} + +# Usage: _DA_getDomainInfo +# Get the root zone if possible +_DA_getDomainInfo() { + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + else + _debug "The root domain: $_domain" + _debug "The sub domain: $_sub_domain" + fi + return 0 +} + +# Usage: _DA_addTxt +# Use the API to add a record +_DA_addTxt() { + curData="domain=${_domain}&action=add&type=TXT&name=${_sub_domain}&value=\"${txtvalue}\"" + _debug "Calling _DA_addTxt: '${curData}' '${DA_Api}/CMD_API_DNS_CONTROL'" + _da_api CMD_API_DNS_CONTROL "${curData}" "${_domain}" + _debug "Result of _DA_addTxt: '$response'" + if _contains "${response}" 'error=0'; then + _debug "Add TXT succeeded" + return 0 + fi + _debug "Add TXT failed" + return 1 +} + +# Usage: _DA_rmTxt +# Use the API to remove a record +_DA_rmTxt() { + curData="domain=${_domain}&action=select&txtrecs0=name=${_sub_domain}&value=\"${txtvalue}\"" + _debug "Calling _DA_rmTxt: '${curData}' '${DA_Api}/CMD_API_DNS_CONTROL'" + if _da_api CMD_API_DNS_CONTROL "${curData}" "${_domain}"; then + _debug "Result of _DA_rmTxt: '$response'" + else + _err "Result of _DA_rmTxt: '$response'" + fi + if _contains "${response}" 'error=0'; then + _debug "RM TXT succeeded" + return 0 + fi + _debug "RM TXT failed" + return 1 +} diff --git a/dnsapi/dns_dgon.sh b/dnsapi/dns_dgon.sh index 7e1f1fec..5d38ef76 100755 --- a/dnsapi/dns_dgon.sh +++ b/dnsapi/dns_dgon.sh @@ -20,12 +20,22 @@ dns_dgon_add() { fulldomain="$(echo "$1" | _lower_case)" txtvalue=$2 + + DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}" + # Check if API Key Exist + if [ -z "$DO_API_KEY" ]; then + DO_API_KEY="" + _err "You did not specify DigitalOcean API key." + _err "Please export DO_API_KEY and try again." + return 1 + fi + _info "Using digitalocean dns validation - add record" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" ## save the env vars (key and domain split location) for later automated use - _saveaccountconf DO_API_KEY "$DO_API_KEY" + _saveaccountconf_mutable DO_API_KEY "$DO_API_KEY" ## split the domain for DO API if ! _get_base_domain "$fulldomain"; then @@ -39,7 +49,7 @@ dns_dgon_add() { export _H1="Content-Type: application/json" export _H2="Authorization: Bearer $DO_API_KEY" PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records' - PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'"}' + PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'","ttl":120}' _debug PURL "$PURL" _debug PBODY "$PBODY" @@ -65,6 +75,16 @@ dns_dgon_add() { dns_dgon_rm() { fulldomain="$(echo "$1" | _lower_case)" txtvalue=$2 + + DO_API_KEY="${DO_API_KEY:-$(_readaccountconf_mutable DO_API_KEY)}" + # Check if API Key Exist + if [ -z "$DO_API_KEY" ]; then + DO_API_KEY="" + _err "You did not specify DigitalOcean API key." + _err "Please export DO_API_KEY and try again." + return 1 + fi + _info "Using digitalocean dns validation - remove record" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" @@ -92,11 +112,11 @@ dns_dgon_rm() { domain_list="$(_get "$GURL")" ## 2) find record ## check for what we are looing for: "type":"A","name":"$_sub_domain" - record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*\d+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" + record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" ## 3) check record and get next page if [ -z "$record" ]; then ## find the next page if we dont have a match - nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=\d+")" + nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")" if [ -z "$nextpage" ]; then _err "no record and no nextpage in digital ocean DNS removal" return 1 @@ -108,7 +128,7 @@ dns_dgon_rm() { done ## we found the record - rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*\d+" | _egrep_o "\d+")" + rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" _debug rec_id "$rec_id" ## delete the record diff --git a/dnsapi/dns_dnsimple.sh b/dnsapi/dns_dnsimple.sh index 0bfe2b99..0dd3918a 100644 --- a/dnsapi/dns_dnsimple.sh +++ b/dnsapi/dns_dnsimple.sh @@ -39,34 +39,17 @@ dns_dnsimple_add() { _get_records "$_account_id" "$_domain" "$_sub_domain" - if [ "$_records_count" = "0" ]; then - _info "Adding record" - if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then - if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then - _info "Added" - return 0 - else - _err "Unexpected response while adding text record." - return 1 - fi - fi - _err "Add txt record error." - else - _info "Updating record" - _extract_record_id "$_records" "$_sub_domain" - - if _dnsimple_rest \ - PATCH \ - "$_account_id/zones/$_domain/records/$_record_id" \ - "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then - - _info "Updated!" + _info "Adding record" + if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then + if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then + _info "Added" return 0 + else + _err "Unexpected response while adding text record." + return 1 fi - - _err "Update error" - return 1 fi + _err "Add txt record error." } # fulldomain @@ -84,19 +67,19 @@ dns_dnsimple_rm() { fi _get_records "$_account_id" "$_domain" "$_sub_domain" + _extract_record_id "$_records" "$_sub_domain" - if [ "$_record_id" ]; then - - if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then - _info "removed record" "$_record_id" - return 0 - fi + echo "$_record_id" | while read -r item; do + if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$item"; then + _info "removed record" "$item" + return 0 + else + _err "failed to remove record" "$item" + return 1 + fi + done fi - - _err "failed to remove record" "$_record_id" - return 1 - } #################### Private functions bellow ################################## diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index afd8b796..7262755e 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -53,8 +53,9 @@ dns_freedns_add() { i="$(_math "$i" - 1)" sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")" - _debug top_domain "$top_domain" - _debug sub_domain "$sub_domain" + _debug "top_domain: $top_domain" + _debug "sub_domain: $sub_domain" + # Sometimes FreeDNS does not return the subdomain page but rather # returns a page regarding becoming a premium member. This usually # happens after a period of inactivity. Immediately trying again @@ -63,6 +64,7 @@ dns_freedns_add() { attempts=2 while [ "$attempts" -gt "0" ]; do attempts="$(_math "$attempts" - 1)" + htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" if [ "$?" != "0" ]; then if [ "$using_cached_cookies" = "true" ]; then @@ -71,10 +73,9 @@ dns_freedns_add() { fi return 1 fi - _debug2 htmlpage "$htmlpage" subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '
' | sed 's//@/g' | tr '@' '\n' | grep edit.php | grep "$top_domain")" - _debug2 subdomain_csv "$subdomain_csv" + _debug3 "subdomain_csv: $subdomain_csv" # The above beauty ends with striping out rows that do not have an # href to edit.php and do not have the top domain we are looking for. @@ -85,55 +86,25 @@ dns_freedns_add() { lines="$(echo "$subdomain_csv" | wc -l)" i=0 found=0 + DNSdomainid="" while [ "$i" -lt "$lines" ]; do i="$(_math "$i" + 1)" line="$(echo "$subdomain_csv" | sed -n "${i}p")" - _debug2 line "$line" + _debug2 "line: $line" if [ $found = 0 ] && _contains "$line" "$top_domain"; then # this line will contain DNSdomainid for the top_domain DNSdomainid="$(echo "$line" | _egrep_o "edit_domain_id *= *.*>" | cut -d = -f 2 | cut -d '>' -f 1)" - _debug2 DNSdomainid "$DNSdomainid" + _debug2 "DNSdomainid: $DNSdomainid" found=1 - else - # lines contain DNS records for all subdomains - DNSname="$(echo "$line" | _egrep_o 'edit.php.*' | cut -d '>' -f 2 | cut -d '<' -f 1)" - _debug2 DNSname "$DNSname" - DNStype="$(echo "$line" | sed 's/' -f 2 | cut -d '<' -f 1)" - _debug2 DNStype "$DNStype" - if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then - DNSdataid="$(echo "$line" | _egrep_o 'data_id=.*' | cut -d = -f 2 | cut -d '>' -f 1)" - # Now get current value for the TXT record. This method may - # not produce accurate results as the value field is truncated - # on this webpage. To get full value we would need to load - # another page. However we don't really need this so long as - # there is only one TXT record for the acme challenge subdomain. - DNSvalue="$(echo "$line" | sed 's/' -f 2 | cut -d '<' -f 1)" - _debug2 DNSvalue "$DNSvalue" - if [ $found != 0 ]; then - break - # we are breaking out of the loop at the first match of DNS name - # and DNS type (if we are past finding the domainid). This assumes - # that there is only ever one TXT record for the LetsEncrypt/acme - # challenge subdomain. This seems to be a reasonable assumption - # as the acme client deletes the TXT record on successful validation. - fi - else - DNSname="" - DNStype="" - fi + break fi done - _debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid" - _debug "DNSvalue: $DNSvalue" - if [ -z "$DNSdomainid" ]; then # If domain ID is empty then something went wrong (top level # domain not found at FreeDNS). if [ "$attempts" = "0" ]; then # exhausted maximum retry attempts - _debug "$htmlpage" - _debug "$subdomain_csv" _err "Domain $top_domain not found at FreeDNS" return 1 fi @@ -145,33 +116,10 @@ dns_freedns_add() { _info "Retry loading subdomain page ($attempts attempts remaining)" done - if [ -z "$DNSdataid" ]; then - # If data ID is empty then specific subdomain does not exist yet, need - # to create it this should always be the case as the acme client - # deletes the entry after domain is validated. - _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" - return $? - else - if [ "$txtvalue" = "$DNSvalue" ]; then - # if value in TXT record matches value requested then DNS record - # does not need to be updated. But... - # Testing value match fails. Website is truncating the value field. - # So for now we will always go down the else path. Though in theory - # should never come here anyway as the acme client deletes - # the TXT record on successful validation, so we should not even - # have found a TXT record !! - _info "No update necessary for $fulldomain at FreeDNS" - return 0 - else - # Delete the old TXT record (with the wrong value) - if _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid"; then - # And add in new TXT record with the value provided - _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" - fi - return $? - fi - fi - return 0 + # Add in new TXT record with the value provided + _debug "Adding TXT record for $fulldomain, $txtvalue" + _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" + return $? } #Usage: fulldomain txtvalue @@ -205,7 +153,7 @@ dns_freedns_rm() { fi subdomain_csv="$(echo "$htmlpage" | tr -d "\n\r" | _egrep_o '' | sed 's//@/g' | tr '@' '\n' | grep edit.php | grep "$fulldomain")" - _debug2 subdomain_csv "$subdomain_csv" + _debug3 "subdomain_csv: $subdomain_csv" # The above beauty ends with striping out rows that do not have an # href to edit.php and do not have the domain name we are looking for. @@ -216,35 +164,51 @@ dns_freedns_rm() { lines="$(echo "$subdomain_csv" | wc -l)" i=0 found=0 + DNSdataid="" while [ "$i" -lt "$lines" ]; do i="$(_math "$i" + 1)" line="$(echo "$subdomain_csv" | sed -n "${i}p")" - _debug2 line "$line" + _debug3 "line: $line" DNSname="$(echo "$line" | _egrep_o 'edit.php.*' | cut -d '>' -f 2 | cut -d '<' -f 1)" - _debug2 DNSname "$DNSname" - DNStype="$(echo "$line" | sed 's/' -f 2 | cut -d '<' -f 1)" - _debug2 DNStype "$DNStype" - if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then - DNSdataid="$(echo "$line" | _egrep_o 'data_id=.*' | cut -d = -f 2 | cut -d '>' -f 1)" - _debug2 DNSdataid "$DNSdataid" - DNSvalue="$(echo "$line" | sed 's/' -f 2 | cut -d '<' -f 1)" - _debug2 DNSvalue "$DNSvalue" - # if [ "$DNSvalue" = "$txtvalue" ]; then - # Testing value match fails. Website is truncating the value - # field. So for now we will assume that there is only one TXT - # field for the sub domain and just delete it. Currently this - # is a safe assumption. - _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" - return $? - # fi + _debug2 "DNSname: $DNSname" + if [ "$DNSname" = "$fulldomain" ]; then + DNStype="$(echo "$line" | sed 's/' -f 2 | cut -d '<' -f 1)" + _debug2 "DNStype: $DNStype" + if [ "$DNStype" = "TXT" ]; then + DNSdataid="$(echo "$line" | _egrep_o 'data_id=.*' | cut -d = -f 2 | cut -d '>' -f 1)" + _debug2 "DNSdataid: $DNSdataid" + DNSvalue="$(echo "$line" | sed 's/' -f 2 | cut -d '<' -f 1)" + if _startswith "$DNSvalue" """; then + # remove the quotation from the start + DNSvalue="$(echo "$DNSvalue" | cut -c 7-)" + fi + if _endswith "$DNSvalue" "..."; then + # value was truncated, remove the dot dot dot from the end + DNSvalue="$(echo "$DNSvalue" | sed 's/...$//')" + elif _endswith "$DNSvalue" """; then + # else remove the closing quotation from the end + DNSvalue="$(echo "$DNSvalue" | sed 's/......$//')" + fi + _debug2 "DNSvalue: $DNSvalue" + + if [ -n "$DNSdataid" ] && _startswith "$txtvalue" "$DNSvalue"; then + # Found a match. But note... Website is truncating the + # value field so we are only testing that part that is not + # truncated. This should be accurate enough. + _debug "Deleting TXT record for $fulldomain, $txtvalue" + _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" + return $? + fi + + fi fi done done # If we get this far we did not find a match (after two attempts) # Not necessarily an error, but log anyway. - _debug2 "$subdomain_csv" - _info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS" + _debug3 "$subdomain_csv" + _info "Cannot delete TXT record for $fulldomain, $txtvalue. Does not exist at FreeDNS" return 0 } @@ -272,7 +236,7 @@ _freedns_login() { # if cookies is not empty then logon successful if [ -z "$cookies" ]; then - _debug "$htmlpage" + _debug3 "htmlpage: $htmlpage" _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file" return 1 fi @@ -301,7 +265,7 @@ _freedns_retrieve_subdomain_page() { return 1 fi - _debug2 "$htmlpage" + _debug3 "htmlpage: $htmlpage" printf "%s" "$htmlpage" return 0 @@ -315,7 +279,7 @@ _freedns_add_txt_record() { domain_id="$2" subdomain="$3" value="$(printf '%s' "$4" | _url_encode)" - url="http://freedns.afraid.org/subdomain/save.php?step=2" + url="https://freedns.afraid.org/subdomain/save.php?step=2" htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")" @@ -323,17 +287,17 @@ _freedns_add_txt_record() { _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" return 1 elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then - _debug "$htmlpage" + _debug3 "htmlpage: $htmlpage" _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" return 1 elif _contains "$htmlpage" "security code was incorrect"; then - _debug "$htmlpage" + _debug3 "htmlpage: $htmlpage" _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested security code" _err "Note that you cannot use automatic DNS validation for FreeDNS public domains" return 1 fi - _debug2 "$htmlpage" + _debug3 "htmlpage: $htmlpage" _info "Added acme challenge TXT record for $fulldomain at FreeDNS" return 0 } @@ -352,7 +316,7 @@ _freedns_delete_txt_record() { _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" return 1 elif ! _contains "$htmlheader" "200 OK"; then - _debug "$htmlheader" + _debug2 "htmlheader: $htmlheader" _err "FreeDNS failed to delete TXT record $data_id" return 1 fi 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_he.sh b/dnsapi/dns_he.sh index f42d56af..da4a1b81 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=" @@ -112,9 +115,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_kinghost.sh b/dnsapi/dns_kinghost.sh new file mode 100644 index 00000000..898ab286 --- /dev/null +++ b/dnsapi/dns_kinghost.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env sh + +############################################################ +# KingHost API support # +# http://api.kinghost.net/doc/ # +# # +# Author: Felipe Keller Braz # +# Report Bugs here: https://github.com/kinghost/acme.sh # +# # +# Values to export: # +# export KINGHOST_Username="email@provider.com" # +# export KINGHOST_Password="xxxxxxxxxx" # +############################################################ + +KING_Api="https://api.kinghost.net/acme" + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +dns_kinghost_add() { + fulldomain=$1 + txtvalue=$2 + + KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}" + KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}" + if [ -z "$KINGHOST_Username" ] || [ -z "$KINGHOST_Password" ]; then + KINGHOST_Username="" + KINGHOST_Password="" + _err "You don't specify KingHost api password and email yet." + _err "Please create you key and try again." + return 1 + fi + + #save the credentials to the account conf file. + _saveaccountconf_mutable KINGHOST_Username "$KINGHOST_Username" + _saveaccountconf_mutable KINGHOST_Password "$KINGHOST_Password" + + _debug "Getting txt records" + _kinghost_rest GET "dns" "name=$fulldomain&content=$txtvalue" + + #This API call returns "status":"ok" if dns record does not exists + #We are creating a new txt record here, so we expect the "ok" status + if ! echo "$response" | grep '"status":"ok"' >/dev/null; then + _err "Error" + _err "$response" + return 1 + fi + + _kinghost_rest POST "dns" "name=$fulldomain&content=$txtvalue" + if ! echo "$response" | grep '"status":"ok"' >/dev/null; then + _err "Error" + _err "$response" + return 1 + fi + + return 0 +} + +# Usage: fulldomain txtvalue +# Used to remove the txt record after validation +dns_kinghost_rm() { + fulldomain=$1 + txtvalue=$2 + + KINGHOST_Password="${KINGHOST_Password:-$(_readaccountconf_mutable KINGHOST_Password)}" + KINGHOST_Username="${KINGHOST_Username:-$(_readaccountconf_mutable KINGHOST_Username)}" + if [ -z "$KINGHOST_Password" ] || [ -z "$KINGHOST_Username" ]; then + KINGHOST_Password="" + KINGHOST_Username="" + _err "You don't specify KingHost api key and email yet." + _err "Please create you key and try again." + return 1 + fi + + _kinghost_rest DELETE "dns" "name=$fulldomain&content=$txtvalue" + if ! echo "$response" | grep '"status":"ok"' >/dev/null; then + _err "Error" + _err "$response" + return 1 + fi + + return 0 +} + +#################### Private functions below ################################## +_kinghost_rest() { + method=$1 + uri="$2" + data="$3" + _debug "$uri" + + export _H1="X-Auth-Email: $KINGHOST_Username" + export _H2="X-Auth-Key: $KINGHOST_Password" + + if [ "$method" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$KING_Api/$uri.json" "" "$method")" + else + response="$(_get "$KING_Api/$uri.json?$data")" + fi + + if [ "$?" != "0" ]; then + _err "error $uri" + return 1 + fi + _debug2 response "$response" + return 0 +} 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_namecom.sh b/dnsapi/dns_namecom.sh index 3af8bf4c..254952d6 100755 --- a/dnsapi/dns_namecom.sh +++ b/dnsapi/dns_namecom.sh @@ -1,11 +1,12 @@ #!/usr/bin/env sh -#Author: RaidneII +#Author: RaidenII #Created 06/28/2017 +#Updated 03/01/2018, rewrote to support name.com API v4 #Utilize name.com API to finish dns-01 verifications. ######## Public functions ##################### -Namecom_API="https://api.name.com/api" +Namecom_API="https://api.name.com/v4" #Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_namecom_add() { @@ -39,21 +40,18 @@ dns_namecom_add() { # Find domain in domain list. if ! _namecom_get_root "$fulldomain"; then _err "Unable to find domain specified." - _namecom_logout return 1 fi # Add TXT record. - _namecom_addtxt_json="{\"hostname\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":\"300\",\"priority\":\"10\"}" - if _namecom_rest POST "dns/create/$_domain" "$_namecom_addtxt_json"; then - retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100") - if [ "$retcode" ]; then + _namecom_addtxt_json="{\"host\":\"$_sub_domain\",\"type\":\"TXT\",\"answer\":\"$txtvalue\",\"ttl\":\"300\"}" + if _namecom_rest POST "domains/$_domain/records" "$_namecom_addtxt_json"; then + _retvalue=$(printf "%s\n" "$response" | _egrep_o "\"$_sub_domain\"") + if [ "$_retvalue" ]; then _info "Successfully added TXT record, ready for validation." - _namecom_logout return 0 else _err "Unable to add the DNS record." - _namecom_logout return 1 fi fi @@ -72,37 +70,28 @@ dns_namecom_rm() { # Find domain in domain list. if ! _namecom_get_root "$fulldomain"; then _err "Unable to find domain specified." - _namecom_logout return 1 fi # Get the record id. - if _namecom_rest GET "dns/list/$_domain"; then - retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100") - if [ "$retcode" ]; then - _record_id=$(printf "%s\n" "$response" | _egrep_o "\"record_id\":\"[0-9]+\",\"name\":\"$fulldomain\",\"type\":\"TXT\"" | cut -d \" -f 4) - _debug record_id "$_record_id" + if _namecom_rest GET "domains/$_domain/records"; then + _record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]+,\"domainName\":\"$_domain\",\"host\":\"$_sub_domain\",\"fqdn\":\"$fulldomain.\",\"type\":\"TXT\",\"answer\":\"$txtvalue\"" | cut -d \" -f 3 | _egrep_o [0-9]+) + _debug record_id "$_record_id" + if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." else _err "Unable to retrieve the record id." - _namecom_logout return 1 fi fi # Remove the DNS record using record id. - _namecom_rmtxt_json="{\"record_id\":\"$_record_id\"}" - if _namecom_rest POST "dns/delete/$_domain" "$_namecom_rmtxt_json"; then - retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100") - if [ "$retcode" ]; then - _info "Successfully removed the TXT record." - _namecom_logout - return 0 - else - _err "Unable to remove the DNS record." - _namecom_logout - return 1 - fi + if _namecom_rest DELETE "domains/$_domain/records/$_record_id"; then + _info "Successfully removed the TXT record." + return 0 + else + _err "Unable to delete record id." + return 1 fi } @@ -112,8 +101,9 @@ _namecom_rest() { param=$2 data=$3 - export _H1="Content-Type: application/json" - export _H2="Api-Session-Token: $sessionkey" + export _H1="Authorization: Basic $_namecom_auth" + export _H2="Content-Type: application/json" + if [ "$method" != "GET" ]; then response="$(_post "$data" "$Namecom_API/$param" "" "$method")" else @@ -130,20 +120,15 @@ _namecom_rest() { } _namecom_login() { - namecom_login_json="{\"username\":\"$Namecom_Username\",\"api_token\":\"$Namecom_Token\"}" + # Auth string + # Name.com API v4 uses http basic auth to authenticate + # need to convert the token for http auth + _namecom_auth=$(printf "%s:%s" "$Namecom_Username" "$Namecom_Token" | _base64) - if _namecom_rest POST "login" "$namecom_login_json"; then - retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100") + if _namecom_rest GET "hello"; then + retcode=$(printf "%s\n" "$response" | _egrep_o "\"username\"\:\"$Namecom_Username\"") if [ "$retcode" ]; then - _info "Successfully logged in. Fetching session token..." - sessionkey=$(printf "%s\n" "$response" | _egrep_o "\"session_token\":\".+" | cut -d \" -f 4) - if [ ! -z "$sessionkey" ]; then - _debug sessionkey "$sessionkey" - _info "Session key obtained." - else - _err "Unable to get session key." - return 1 - fi + _info "Successfully logged in." else _err "Logging in failed." return 1 @@ -151,24 +136,12 @@ _namecom_login() { fi } -_namecom_logout() { - if _namecom_rest GET "logout"; then - retcode=$(printf "%s\n" "$response" | _egrep_o "\"code\":100") - if [ "$retcode" ]; then - _info "Successfully logged out." - else - _err "Error logging out." - return 1 - fi - fi -} - _namecom_get_root() { domain=$1 i=2 p=1 - if ! _namecom_rest GET "domain/list"; then + if ! _namecom_rest GET "domains"; then return 1 fi diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh index 7acb2ef7..555f4d29 100755 --- a/dnsapi/dns_nsupdate.sh +++ b/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}" </dev/null; then + _info "Added TXT record, waiting for validation" + return 0 + else + _debug response "$response" + _err "Error while adding DNS records" + return 1 + fi + fi + + return 1 +} + +dns_zilore_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Using Zilore" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + Zilore_Key="${Zilore_Key:-$(_readaccountconf_mutable Zilore_Key)}" + if [ -z "$Zilore_Key" ]; then + Zilore_Key="" + _err "Please define Zilore API key" + return 1 + fi + _saveaccountconf_mutable Zilore_Key "$Zilore_Key" + + if ! _get_root "$fulldomain"; then + _err "Unable to determine root domain" + return 1 + else + _debug _domain "$_domain" + fi + + _debug "Getting TXT records" + _zilore_rest GET "domains/${_domain}/records?search_text=$txtvalue&search_record_type=TXT" + _debug response "$response" + + if ! _contains "$response" '"ok"' >/dev/null; then + _err "Error while getting records list" + return 1 + else + _record_id=$(printf "%s\n" "$response" | _egrep_o "\"record_id\":\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1) + if [ -z "$_record_id" ]; then + _err "Cannot determine _record_id" + return 1 + else + _debug _record_id "$_record_id" + fi + if ! _zilore_rest DELETE "domains/${_domain}/records?record_id=$_record_id"; then + _err "Error while deleting chosen record" + return 1 + fi + _contains "$response" '"ok"' + fi +} + +#################### Private functions below ################################## + +_get_root() { + domain=$1 + i=2 + 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 ! _zilore_rest GET "domains?search_text=$h"; then + return 1 + fi + + if _contains "$response" "\"$h\"" >/dev/null; then + _domain=$h + return 0 + else + _debug "$h not found" + fi + i=$(_math "$i" + 1) + done + return 1 +} + +_zilore_rest() { + method=$1 + param=$2 + data=$3 + + export _H1="X-Auth-Key: $Zilore_Key" + + if [ "$method" != "GET" ]; then + response="$(_post "$data" "$Zilore_API/$param" "" "$method")" + else + response="$(_get "$Zilore_API/$param")" + fi + + if [ "$?" != "0" ]; then + _err "error $param" + return 1 + fi + + _debug2 response "$response" + return 0 +}