diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 5591b8f1..5dc2d453 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -1,8 +1,6 @@ name: DNS on: push: - branches: - - 'dev' paths: - 'dnsapi/*.sh' - '.github/workflows/DNS.yml' @@ -38,10 +36,10 @@ jobs: steps: - name: "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test" run: | - echo "Plese see this page to fix the error: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test" - - name: Fail - if: "github.actor != 'Neilpang'" - run: false + echo "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test" + if [ "${{github.actor}}" != "Neilpang" ]; then + false + fi Docker: runs-on: ubuntu-latest @@ -96,7 +94,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install tools - run: brew update && brew install socat; + run: brew install socat - name: Clone acmetest run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest @@ -145,8 +143,9 @@ jobs: C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git shell: cmd - name: Set ENV + shell: cmd run: | - echo '::set-env name=PATH::C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' + echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV% - name: Clone acmetest run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest @@ -170,4 +169,81 @@ jobs: cd ../acmetest ./letest.sh + FreeBSD: + runs-on: macos-latest + needs: Windows + env: + TEST_DNS : ${{ secrets.TEST_DNS }} + TestingDomain: ${{ secrets.TestingDomain }} + TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} + TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} + CASE: le_test_dnsapi + TEST_LOCAL: 1 + DEBUG: 1 + steps: + - uses: actions/checkout@v2 + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/freebsd-vm@v0.0.7 + with: + envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' + prepare: pkg install -y socat curl + usesh: true + run: | + if [ "${{ secrets.TokenName1}}" ] ; then + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} + fi + if [ "${{ secrets.TokenName2}}" ] ; then + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} + fi + if [ "${{ secrets.TokenName3}}" ] ; then + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} + fi + if [ "${{ secrets.TokenName4}}" ] ; then + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} + fi + if [ "${{ secrets.TokenName5}}" ] ; then + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} + fi + cd ../acmetest + ./letest.sh + + Solaris: + runs-on: macos-latest + needs: FreeBSD + env: + TEST_DNS : ${{ secrets.TEST_DNS }} + TestingDomain: ${{ secrets.TestingDomain }} + TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} + TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} + CASE: le_test_dnsapi + TEST_LOCAL: 1 + DEBUG: 1 + steps: + - uses: actions/checkout@v2 + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/solaris-vm@v0.0.1 + with: + envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' + prepare: pkgutil -y -i socat curl + run: | + if [ "${{ secrets.TokenName1}}" ] ; then + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} + fi + if [ "${{ secrets.TokenName2}}" ] ; then + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} + fi + if [ "${{ secrets.TokenName3}}" ] ; then + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} + fi + if [ "${{ secrets.TokenName4}}" ] ; then + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} + fi + if [ "${{ secrets.TokenName5}}" ] ; then + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} + fi + cd ../acmetest + ./letest.sh + diff --git a/.github/workflows/LetsEncrypt.yml b/.github/workflows/LetsEncrypt.yml index 9a0175b5..8d0c4eb0 100644 --- a/.github/workflows/LetsEncrypt.yml +++ b/.github/workflows/LetsEncrypt.yml @@ -58,7 +58,7 @@ jobs: steps: - uses: actions/checkout@v2 - name: Install tools - run: brew update && brew install socat; + run: brew install socat - name: Clone acmetest run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest @@ -87,8 +87,13 @@ jobs: C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git shell: cmd - name: Set ENV + shell: cmd + run: | + echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV% + - name: Check ENV + shell: cmd run: | - echo '::set-env name=PATH::C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' + echo "PATH=%PATH%" - name: Clone acmetest shell: cmd run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ @@ -96,4 +101,47 @@ jobs: shell: cmd run: cd ../acmetest && bash.exe -c ./letest.sh + FreeBSD: + runs-on: macos-latest + needs: Windows + env: + NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }} + TEST_LOCAL: 1 + steps: + - uses: actions/checkout@v2 + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/freebsd-vm@v0.0.7 + with: + envs: 'NGROK_TOKEN TEST_LOCAL' + prepare: pkg install -y socat curl + usesh: true + run: | + cd ../acmetest && ./letest.sh + + Solaris: + runs-on: macos-latest + needs: FreeBSD + env: + NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }} + TEST_LOCAL: 1 + steps: + - uses: actions/checkout@v2 + - uses: vmactions/ngrok-tunnel@v0.0.1 + id: ngrok + with: + protocol: http + port: 8080 + - name: Set envs + run: echo "TestingDomain=${{steps.ngrok.outputs.server}}" >> $GITHUB_ENV + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/solaris-vm@v0.0.1 + with: + envs: 'TEST_LOCAL TestingDomain' + nat: | + "8080": "80" + prepare: pkgutil -y -i socat curl + run: | + cd ../acmetest && ./letest.sh diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 92308218..89915af7 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -63,4 +63,4 @@ jobs: --tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \ --output "type=image,push=true" \ --build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \ - --platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386 . + --platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x . diff --git a/Dockerfile b/Dockerfile index 2ccf6800..6b382242 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN apk update -f \ coreutils \ bind-tools \ curl \ + sed \ socat \ tzdata \ oath-toolkit-oathtool \ diff --git a/README.md b/README.md index 953c44a5..cd747666 100644 --- a/README.md +++ b/README.md @@ -57,26 +57,26 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) | NO | Status| Platform| |----|-------|---------| -|1|[![](https://acmesh-official.github.io/acmetest/status/ubuntu-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Ubuntu -|2|[![](https://acmesh-official.github.io/acmetest/status/debian-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Debian -|3|[![](https://acmesh-official.github.io/acmetest/status/centos-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|CentOS -|4|[![](https://acmesh-official.github.io/acmetest/status/windows-cygwin.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included) -|5|[![](https://acmesh-official.github.io/acmetest/status/freebsd.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|FreeBSD +|1|[![MacOS](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Mac OSX +|2|[![Windows](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Windows (cygwin with curl, openssl and crontab included) +|3|[![FreeBSD](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|FreeBSD +|4|[![Solaris](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Solaris +|5|[![Ubuntu](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)| Ubuntu |6|[![](https://acmesh-official.github.io/acmetest/status/pfsense.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|pfsense -|7|[![](https://acmesh-official.github.io/acmetest/status/opensuse-leap-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|openSUSE -|8|[![](https://acmesh-official.github.io/acmetest/status/alpine-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Alpine Linux (with curl) -|9|[![](https://acmesh-official.github.io/acmetest/status/archlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Archlinux -|10|[![](https://acmesh-official.github.io/acmetest/status/fedora-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|fedora -|11|[![](https://acmesh-official.github.io/acmetest/status/kalilinux-kali.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Kali Linux -|12|[![](https://acmesh-official.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Oracle Linux -|13|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management) -|14|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 -|15|[![](https://acmesh-official.github.io/acmetest/status/openbsd.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|OpenBSD -|16|[![](https://acmesh-official.github.io/acmetest/status/mageia.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Mageia -|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) -|18|[![](https://acmesh-official.github.io/acmetest/status/solaris.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|SunOS/Solaris -|19|[![](https://acmesh-official.github.io/acmetest/status/gentoo-stage3-amd64.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Gentoo Linux -|20|[![Build Status](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Mac OSX +|7|[![](https://acmesh-official.github.io/acmetest/status/openbsd.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|OpenBSD +|8|[![](https://acmesh-official.github.io/acmetest/status/debian-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Debian +|9|[![](https://acmesh-official.github.io/acmetest/status/centos-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|CentOS +|10|[![](https://acmesh-official.github.io/acmetest/status/opensuse-leap-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|openSUSE +|11|[![](https://acmesh-official.github.io/acmetest/status/alpine-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Alpine Linux (with curl) +|12|[![](https://acmesh-official.github.io/acmetest/status/archlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Archlinux +|13|[![](https://acmesh-official.github.io/acmetest/status/fedora-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|fedora +|14|[![](https://acmesh-official.github.io/acmetest/status/kalilinux-kali.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Kali Linux +|15|[![](https://acmesh-official.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Oracle Linux +|16|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management) +|17|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 +|18|[![](https://acmesh-official.github.io/acmetest/status/mageia.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Mageia +|19|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) +|20|[![](https://acmesh-official.github.io/acmetest/status/gentoo-stage3-amd64.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Gentoo Linux |21|[![](https://acmesh-official.github.io/acmetest/status/clearlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|ClearLinux For all build statuses, check our [weekly build project](https://github.com/acmesh-official/acmetest): diff --git a/acme.sh b/acme.sh index 3be3849d..ae387535 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=2.8.8 +VER=2.8.9 PROJECT_NAME="acme.sh" @@ -1722,6 +1722,14 @@ _mktemp() { _err "Can not create temp file." } +#clear all the https envs to cause _inithttp() to run next time. +_resethttp() { + __HTTP_INITIALIZED="" + _ACME_CURL="" + _ACME_WGET="" + ACME_HTTP_NO_REDIRECTS="" +} + _inithttp() { if [ -z "$HTTP_HEADER" ] || ! touch "$HTTP_HEADER"; then @@ -1737,7 +1745,10 @@ _inithttp() { fi if [ -z "$_ACME_CURL" ] && _exists "curl"; then - _ACME_CURL="curl -L --silent --dump-header $HTTP_HEADER " + _ACME_CURL="curl --silent --dump-header $HTTP_HEADER " + if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then + _ACME_CURL="$_ACME_CURL -L " + fi if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _CURL_DUMP="$(_mktemp)" _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP " @@ -1756,6 +1767,9 @@ _inithttp() { if [ -z "$_ACME_WGET" ] && _exists "wget"; then _ACME_WGET="wget -q" + if [ "$ACME_HTTP_NO_REDIRECTS" ]; then + _ACME_WGET="$_ACME_WGET --max-redirect 0 " + fi if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _ACME_WGET="$_ACME_WGET -d " fi @@ -2086,7 +2100,7 @@ _send_signed_request() { _debug2 original "$response" if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then - response="$(echo "$response" | _normalizeJson | _json_decode)" + response="$(echo "$response" | _json_decode | _normalizeJson)" fi _debug2 response "$response" @@ -3993,7 +4007,7 @@ _check_dns_entries() { #file _get_cert_issuers() { _cfile="$1" - if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7"; then + if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)" "unknown option help"; then ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 else ${ACME_OPENSSL_BIN:-openssl} x509 -in $_cfile -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 @@ -5823,7 +5837,7 @@ _deactivate() { _URL_NAME="uri" fi - entries="$(echo "$response" | _egrep_o "[^{]*\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")" + entries="$(echo "$response" | tr '][' '==' | _egrep_o "challenges\": *=[^=]*=" | tr '}{' '\n' | grep "\"status\": *\"valid\"")" if [ -z "$entries" ]; then _info "No valid entries found." if [ -z "$thumbprint" ]; then @@ -5866,7 +5880,7 @@ _deactivate() { _debug _vtype "$_vtype" _info "Found $_vtype" - uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')" + uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*\"" | tr -d '" ' | cut -d : -f 2-)" _debug uri "$uri" if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then @@ -6649,8 +6663,8 @@ _checkSudo() { return 0 fi if [ -n "$SUDO_COMMAND" ]; then - #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s` - _endswith "$SUDO_COMMAND" /bin/su || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1 + #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s`, or `sudo su acmeuser1` + _endswith "$SUDO_COMMAND" /bin/su || _contains "$SUDO_COMMAND" "/bin/su " || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1 return $? fi #otherwise diff --git a/deploy/cleverreach.sh b/deploy/cleverreach.sh new file mode 100644 index 00000000..552d8149 --- /dev/null +++ b/deploy/cleverreach.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env sh +# Here is the script to deploy the cert to your CleverReach Account using the CleverReach REST API. +# Your OAuth needs the right scope, please contact CleverReach support for that. +# +# Written by Jan-Philipp Benecke +# Public domain, 2020 +# +# Following environment variables must be set: +# +#export DEPLOY_CLEVERREACH_CLIENT_ID=myid +#export DEPLOY_CLEVERREACH_CLIENT_SECRET=mysecret + +cleverreach_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" + + _getdeployconf DEPLOY_CLEVERREACH_CLIENT_ID + _getdeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET + + if [ -z "${DEPLOY_CLEVERREACH_CLIENT_ID}" ]; then + _err "CleverReach Client ID is not found, please define DEPLOY_CLEVERREACH_CLIENT_ID." + return 1 + fi + if [ -z "${DEPLOY_CLEVERREACH_CLIENT_SECRET}" ]; then + _err "CleverReach client secret is not found, please define DEPLOY_CLEVERREACH_CLIENT_SECRET." + return 1 + fi + + _savedeployconf DEPLOY_CLEVERREACH_CLIENT_ID "${DEPLOY_CLEVERREACH_CLIENT_ID}" + _savedeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET "${DEPLOY_CLEVERREACH_CLIENT_SECRET}" + + _info "Obtaining a CleverReach access token" + + _data="{\"grant_type\": \"client_credentials\", \"client_id\": \"${DEPLOY_CLEVERREACH_CLIENT_ID}\", \"client_secret\": \"${DEPLOY_CLEVERREACH_CLIENT_SECRET}\"}" + _auth_result="$(_post "$_data" "https://rest.cleverreach.com/oauth/token.php" "" "POST" "application/json")" + + _debug _data "$_data" + _debug _auth_result "$_auth_result" + + _regex=".*\"access_token\":\"\([-._0-9A-Za-z]*\)\".*$" + _debug _regex "$_regex" + _access_token=$(echo "$_auth_result" | _json_decode | sed -n "s/$_regex/\1/p") + + _info "Uploading certificate and key to CleverReach" + + _certData="{\"cert\":\"$(_json_encode <"$_cfullchain")\", \"key\":\"$(_json_encode <"$_ckey")\"}" + export _H1="Authorization: Bearer ${_access_token}" + _add_cert_result="$(_post "$_certData" "https://rest.cleverreach.com/v3/ssl" "" "POST" "application/json")" + + _debug "Destroying token at CleverReach" + _post "" "https://rest.cleverreach.com/v3/oauth/token.json" "" "DELETE" "application/json" + + if ! echo "$_add_cert_result" | grep '"error":' >/dev/null; then + _info "Uploaded certificate successfully" + return 0 + else + _debug _add_cert_result "$_add_cert_result" + _err "Unable to update certificate" + return 1 + fi +} diff --git a/deploy/fritzbox.sh b/deploy/fritzbox.sh index 21ea6cfd..2ca7ab7d 100644 --- a/deploy/fritzbox.sh +++ b/deploy/fritzbox.sh @@ -28,9 +28,11 @@ fritzbox_deploy() { _debug _cfullchain "$_cfullchain" if ! _exists iconv; then - if ! _exists perl; then - _err "iconv or perl not found" - return 1 + if ! _exists uconv; then + if ! _exists perl; then + _err "iconv or uconv or perl not found" + return 1 + fi fi fi @@ -64,9 +66,11 @@ fritzbox_deploy() { _info "Log in to the FRITZ!Box" _fritzbox_challenge="$(_get "${_fritzbox_url}/login_sid.lua" | sed -e 's/^.*//' -e 's/<\/Challenge>.*$//')" if _exists iconv; then - _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | md5sum | awk '{print $1}')" + _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | iconv -f ASCII -t UTF16LE | _digest md5 hex)" + elif _exists uconv; then + _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | uconv -f ASCII -t UTF16LE | _digest md5 hex)" 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}')" + _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${_fritzbox_password}" | perl -p -e 'use Encode qw/encode/; print encode("UTF-16LE","$_"); $_="";' | _digest md5 hex)" 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>.*$//')" diff --git a/deploy/mailcow.sh b/deploy/mailcow.sh index 3a806e83..c3535e7e 100644 --- a/deploy/mailcow.sh +++ b/deploy/mailcow.sh @@ -27,26 +27,43 @@ mailcow_deploy() { return 1 fi - _ssl_path="${_mailcow_path}/data/assets/ssl/" + #Tests if _ssl_path is the mailcow root directory. + if [ -f "${_mailcow_path}/generate_config.sh" ]; then + _ssl_path="${_mailcow_path}/data/assets/ssl/" + else + _ssl_path="${_mailcow_path}" + fi + if [ ! -d "$_ssl_path" ]; then _err "Cannot find mailcow ssl path: $_ssl_path" return 1 fi + # ECC or RSA + if [ -z "${Le_Keylength}" ]; then + Le_Keylength="" + fi + if _isEccKey "${Le_Keylength}"; then + _info "ECC key type detected" + _cert_name_prefix="ecdsa-" + else + _info "RSA key type detected" + _cert_name_prefix="" + fi _info "Copying key and cert" - _real_key="$_ssl_path/key.pem" + _real_key="$_ssl_path/${_cert_name_prefix}key.pem" if ! cat "$_ckey" >"$_real_key"; then _err "Error: write key file to: $_real_key" return 1 fi - _real_fullchain="$_ssl_path/cert.pem" + _real_fullchain="$_ssl_path/${_cert_name_prefix}cert.pem" if ! cat "$_cfullchain" >"$_real_fullchain"; then _err "Error: write cert file to: $_real_fullchain" return 1 fi - DEFAULT_MAILCOW_RELOAD="cd ${_mailcow_path} && docker-compose restart postfix-mailcow dovecot-mailcow nginx-mailcow" + DEFAULT_MAILCOW_RELOAD="docker restart $(docker ps -qaf name=postfix-mailcow); docker restart $(docker ps -qaf name=nginx-mailcow); docker restart $(docker ps -qaf name=dovecot-mailcow)" _reload="${DEPLOY_MAILCOW_RELOAD:-$DEFAULT_MAILCOW_RELOAD}" _info "Run reload: $_reload" diff --git a/deploy/vault.sh b/deploy/vault.sh new file mode 100644 index 00000000..70c80444 --- /dev/null +++ b/deploy/vault.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env sh + +# Here is a script to deploy cert to hashicorp vault using curl +# (https://www.vaultproject.io/) +# +# it requires following environment variables: +# +# VAULT_PREFIX - this contains the prefix path in vault +# VAULT_ADDR - vault requires this to find your vault server +# +# additionally, you need to ensure that VAULT_TOKEN is avialable +# to access the vault server + +#returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +vault_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" + + # validate required env vars + _getdeployconf VAULT_PREFIX + if [ -z "$VAULT_PREFIX" ]; then + _err "VAULT_PREFIX needs to be defined (contains prefix path in vault)" + return 1 + fi + _savedeployconf VAULT_PREFIX "$VAULT_PREFIX" + + _getdeployconf VAULT_ADDR + if [ -z "$VAULT_ADDR" ]; then + _err "VAULT_ADDR needs to be defined (contains vault connection address)" + return 1 + fi + _savedeployconf VAULT_ADDR "$VAULT_ADDR" + + # JSON does not allow multiline strings. + # So replacing new-lines with "\n" here + _ckey=$(sed -z 's/\n/\\n/g' <"$2") + _ccert=$(sed -z 's/\n/\\n/g' <"$3") + _cca=$(sed -z 's/\n/\\n/g' <"$4") + _cfullchain=$(sed -z 's/\n/\\n/g' <"$5") + + URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain" + export _H1="X-Vault-Token: $VAULT_TOKEN" + + if [ -n "$FABIO" ]; then + _post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" + else + _post "{\"value\": \"$_ccert\"}" "$URL/cert.pem" + _post "{\"value\": \"$_ckey\"}" "$URL/cert.key" + _post "{\"value\": \"$_cca\"}" "$URL/chain.pem" + _post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem" + fi + +} diff --git a/dnsapi/dns_1984hosting.sh b/dnsapi/dns_1984hosting.sh index bcb675ab..d720c1c5 100755 --- a/dnsapi/dns_1984hosting.sh +++ b/dnsapi/dns_1984hosting.sh @@ -40,8 +40,35 @@ dns_1984hosting_add() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _1984hosting_add_txt_record "$_domain" "$_sub_domain" "$txtvalue" - return $? + _debug "Add TXT record $fulldomain with value '$txtvalue'" + value="$(printf '%s' "$txtvalue" | _url_encode)" + url="https://management.1984hosting.com/domains/entry/" + + postdata="entry=new" + postdata="$postdata&type=TXT" + postdata="$postdata&ttl=3600" + postdata="$postdata&zone=$_domain" + postdata="$postdata&host=$_sub_domain" + postdata="$postdata&rdata=%22$value%22" + _debug2 postdata "$postdata" + + _authpost "$postdata" "$url" + response="$(echo "$_response" | _normalizeJson)" + _debug2 response "$response" + + if _contains "$response" '"haserrors": true'; then + _err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post" + return 1 + elif _contains "$response" ""; then + _err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file" + return 1 + elif _contains "$response" '"auth": false'; then + _err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie" + return 1 + fi + + _info "Added acme challenge TXT record for $fulldomain at 1984Hosting" + return 0 } #Usage: fulldomain txtvalue @@ -67,57 +94,10 @@ dns_1984hosting_rm() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _1984hosting_delete_txt_record "$_domain" "$_sub_domain" - return $? -} - -#################### Private functions below ################################## - -# usage _1984hosting_add_txt_record domain subdomain value -# returns 0 success -_1984hosting_add_txt_record() { - _debug "Add TXT record $1 with value '$3'" - domain="$1" - subdomain="$2" - value="$(printf '%s' "$3" | _url_encode)" - url="https://management.1984hosting.com/domains/entry/" - - postdata="entry=new" - postdata="$postdata&type=TXT" - postdata="$postdata&ttl=3600" - postdata="$postdata&zone=$domain" - postdata="$postdata&host=$subdomain" - postdata="$postdata&rdata=%22$value%22" - _debug2 postdata "$postdata" - - _authpost "$postdata" "$url" - response="$(echo "$_response" | _normalizeJson)" - _debug2 response "$response" - - if _contains "$response" '"haserrors": true'; then - _err "1984Hosting failed to add TXT record for $subdomain bad RC from _post" - return 1 - elif _contains "$response" ""; then - _err "1984Hosting failed to add TXT record for $subdomain. Check $HTTP_HEADER file" - return 1 - elif _contains "$response" '"auth": false'; then - _err "1984Hosting failed to add TXT record for $subdomain. Invalid or expired cookie" - return 1 - fi - - _info "Added acme challenge TXT record for $fulldomain at 1984Hosting" - return 0 -} - -# usage _1984hosting_delete_txt_record entry_id -# returns 0 success -_1984hosting_delete_txt_record() { _debug "Delete $fulldomain TXT record" - domain="$1" - subdomain="$2" url="https://management.1984hosting.com/domains" - _htmlget "$url" "$domain" + _htmlget "$url" "$_domain" _debug2 _response "$_response" zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+')" _debug2 zone_id "$zone_id" @@ -126,7 +106,7 @@ _1984hosting_delete_txt_record() { return 1 fi - _htmlget "$url/$zone_id" "$subdomain" + _htmlget "$url/$zone_id" "$_sub_domain" _debug2 _response "$_response" entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')" _debug2 entry_id "$entry_id" @@ -148,6 +128,8 @@ _1984hosting_delete_txt_record() { return 0 } +#################### Private functions below ################################## + # usage: _1984hosting_login username password # returns 0 success _1984hosting_login() { diff --git a/dnsapi/dns_anx.sh b/dnsapi/dns_anx.sh new file mode 100644 index 00000000..c1a1130a --- /dev/null +++ b/dnsapi/dns_anx.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env sh + +# Anexia CloudDNS acme.sh hook +# Author: MA + +#ANX_Token="xxxx" + +ANX_API='https://engine.anexia-it.com/api/clouddns/v1' + +######## Public functions ##################### + +dns_anx_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Using ANX CDNS API" + + ANX_Token="${ANX_Token:-$(_readaccountconf_mutable ANX_Token)}" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + if [ "$ANX_Token" ]; then + _saveaccountconf_mutable ANX_Token "$ANX_Token" + else + _err "You didn't specify a ANEXIA Engine API token." + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + # Always add records, wildcard need two records with the same name + _anx_rest POST "zone.json/${_domain}/records" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"rdata\":\"$txtvalue\"}" + if _contains "$response" "$txtvalue"; then + return 0 + else + return 1 + fi +} + +dns_anx_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Using ANX CDNS API" + + ANX_Token="${ANX_Token:-$(_readaccountconf_mutable ANX_Token)}" + + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _get_record_id + + if _is_uuid "$_record_id"; then + if ! _anx_rest DELETE "zone.json/${_domain}/records/$_record_id"; then + _err "Delete record" + return 1 + fi + else + _info "No record found." + fi + echo "$response" | tr -d " " | grep \"status\":\"OK\" >/dev/null +} + +#################### Private functions below ################################## + +_is_uuid() { + pattern='^\{?[A-Z0-9a-z]{8}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{12}\}?$' + if echo "$1" | _egrep_o "$pattern" >/dev/null; then + return 0 + fi + return 1 +} + +_get_record_id() { + _debug subdomain "$_sub_domain" + _debug domain "$_domain" + + if _anx_rest GET "zone.json/${_domain}/records?name=$_sub_domain&type=TXT"; then + _debug response "$response" + if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then + _record_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"identifier\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") + else + _record_id='' + fi + else + _err "Search existing record" + fi +} + +_anx_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + export _H1="Content-Type: application/json" + export _H2="Authorization: Token $ANX_Token" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "${ANX_API}/$ep" "" "$m")" + else + response="$(_get "${ANX_API}/$ep")" + fi + + # shellcheck disable=SC2181 + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug response "$response" + return 0 +} + +_get_root() { + domain=$1 + i=1 + p=1 + + _anx_rest GET "zone.json" + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if _contains "$response" "\"name\":\"$h\""; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + + p=$i + i=$(_math "$i" + 1) + done + return 1 +} diff --git a/dnsapi/dns_desec.sh b/dnsapi/dns_desec.sh index 61d080bd..f64660a8 100644 --- a/dnsapi/dns_desec.sh +++ b/dnsapi/dns_desec.sh @@ -61,7 +61,7 @@ dns_desec_add() { fi _debug txtvalues "$txtvalues" _info "Adding record" - body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":60}]" + body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":3600}]" if _desec_rest PUT "$REST_API/$DEDYN_NAME/rrsets/" "$body"; then if _contains "$response" "$txtvalue"; then @@ -130,7 +130,7 @@ dns_desec_rm() { _debug txtvalues "$txtvalues" _info "Deleting record" - body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":60}]" + body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":3600}]" _desec_rest PUT "$REST_API/$DEDYN_NAME/rrsets/" "$body" if [ "$_code" = "200" ]; then _info "Deleted, OK" diff --git a/dnsapi/dns_dpi.sh b/dnsapi/dns_dpi.sh index 831150a9..9cbf4d51 100755 --- a/dnsapi/dns_dpi.sh +++ b/dnsapi/dns_dpi.sh @@ -75,7 +75,7 @@ dns_dpi_rm() { return 1 fi - _contains "$response" "Action completed successful" + _contains "$response" "Operation successful" } @@ -93,7 +93,7 @@ add_record() { return 1 fi - _contains "$response" "Action completed successful" || _contains "$response" "Domain record already exists" + _contains "$response" "Operation successful" || _contains "$response" "Domain record already exists" } #################### Private functions below ################################## @@ -117,7 +117,7 @@ _get_root() { return 1 fi - if _contains "$response" "Action completed successful"; then + if _contains "$response" "Operation successful"; then _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then diff --git a/dnsapi/dns_edgedns.sh b/dnsapi/dns_edgedns.sh new file mode 100755 index 00000000..2e5c7d30 --- /dev/null +++ b/dnsapi/dns_edgedns.sh @@ -0,0 +1,466 @@ +#!/usr/bin/env sh + +# Akamai Edge DNS v2 API +# User must provide Open Edgegrid API credentials to the EdgeDNS installation. The remote user in EdgeDNS must have CRUD access to +# Edge DNS Zones and Recordsets, e.g. DNS—Zone Record Management authorization + +# Report bugs to https://control.akamai.com/apps/support-ui/#/contact-support + +# Values to export: +# --EITHER-- +# *** TBD. NOT IMPLEMENTED YET *** +# specify Edgegrid credentials file and section +# AKAMAI_EDGERC= +# AKAMAI_EDGERC_SECTION="default" +## --OR-- +# specify indiviual credentials +# export AKAMAI_HOST = +# export AKAMAI_ACCESS_TOKEN = +# export AKAMAI_CLIENT_TOKEN = +# export AKAMAI_CLIENT_SECRET = + +ACME_EDGEDNS_VERSION="0.1.0" + +######## Public functions ##################### + +# Usage: dns_edgedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +# +dns_edgedns_add() { + fulldomain=$1 + txtvalue=$2 + _debug "ENTERING DNS_EDGEDNS_ADD" + _debug2 "fulldomain" "$fulldomain" + _debug2 "txtvalue" "$txtvalue" + + if ! _EDGEDNS_credentials; then + _err "$@" + return 1 + fi + if ! _EDGEDNS_getZoneInfo "$fulldomain"; then + _err "Invalid domain" + return 1 + fi + + _debug2 "Add: zone" "$zone" + acmeRecordURI=$(printf "%s/%s/names/%s/types/TXT" "$edge_endpoint" "$zone" "$fulldomain") + _debug3 "Add URL" "$acmeRecordURI" + # Get existing TXT record + _edge_result=$(_edgedns_rest GET "$acmeRecordURI") + _api_status="$?" + _debug3 "_edge_result" "$_edge_result" + if [ "$_api_status" -ne 0 ]; then + if [ "$curResult" = "FATAL" ]; then + _err "$(printf "Fatal error: acme API function call : %s" "$retVal")" + fi + if [ "$_edge_result" != "404" ]; then + _err "$(printf "Failure accessing Akamai Edge DNS API Server. Error: %s" "$_edge_result")" + return 1 + fi + fi + rdata="\"${txtvalue}\"" + record_op="POST" + if [ "$_api_status" -eq 0 ]; then + # record already exists. Get existing record data and update + record_op="PUT" + rdlist="${_edge_result#*\"rdata\":[}" + rdlist="${rdlist%%]*}" + rdlist=$(echo "$rdlist" | tr -d '"' | tr -d "\\\\") + _debug3 "existing TXT found" + _debug3 "record data" "$rdlist" + # value already there? + if _contains "$rdlist" "$txtvalue"; then + return 0 + fi + _txt_val="" + while [ "$_txt_val" != "$rdlist" ] && [ "${rdlist}" ]; do + _txt_val="${rdlist%%,*}" + rdlist="${rdlist#*,}" + rdata="${rdata},\"${_txt_val}\"" + done + fi + # Add the txtvalue TXT Record + body="{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":600, \"rdata\":"[${rdata}]"}" + _debug3 "Add body '${body}'" + _edge_result=$(_edgedns_rest "$record_op" "$acmeRecordURI" "$body") + _api_status="$?" + if [ "$_api_status" -eq 0 ]; then + _log "$(printf "Text value %s added to recordset %s" "$txtvalue" "$fulldomain")" + return 0 + else + _err "$(printf "error adding TXT record for validation. Error: %s" "$_edge_result")" + return 1 + fi +} + +# Usage: dns_edgedns_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to delete txt record +# +dns_edgedns_rm() { + fulldomain=$1 + txtvalue=$2 + _debug "ENTERING DNS_EDGEDNS_RM" + _debug2 "fulldomain" "$fulldomain" + _debug2 "txtvalue" "$txtvalue" + + if ! _EDGEDNS_credentials; then + _err "$@" + return 1 + fi + if ! _EDGEDNS_getZoneInfo "$fulldomain"; then + _err "Invalid domain" + return 1 + fi + _debug2 "RM: zone" "${zone}" + acmeRecordURI=$(printf "%s/%s/names/%s/types/TXT" "${edge_endpoint}" "$zone" "$fulldomain") + _debug3 "RM URL" "$acmeRecordURI" + # Get existing TXT record + _edge_result=$(_edgedns_rest GET "$acmeRecordURI") + _api_status="$?" + if [ "$_api_status" -ne 0 ]; then + if [ "$curResult" = "FATAL" ]; then + _err "$(printf "Fatal error: acme API function call : %s" "$retVal")" + fi + if [ "$_edge_result" != "404" ]; then + _err "$(printf "Failure accessing Akamai Edge DNS API Server. Error: %s" "$_edge_result")" + return 1 + fi + fi + _debug3 "_edge_result" "$_edge_result" + record_op="DELETE" + body="" + if [ "$_api_status" -eq 0 ]; then + # record already exists. Get existing record data and update + rdlist="${_edge_result#*\"rdata\":[}" + rdlist="${rdlist%%]*}" + rdlist=$(echo "$rdlist" | tr -d '"' | tr -d "\\\\") + _debug3 "rdlist" "$rdlist" + if [ -n "$rdlist" ]; then + record_op="PUT" + comma="" + rdata="" + _txt_val="" + while [ "$_txt_val" != "$rdlist" ] && [ "$rdlist" ]; do + _txt_val="${rdlist%%,*}" + rdlist="${rdlist#*,}" + _debug3 "_txt_val" "$_txt_val" + _debug3 "txtvalue" "$txtvalue" + if ! _contains "$_txt_val" "$txtvalue"; then + rdata="${rdata}${comma}\"${_txt_val}\"" + comma="," + fi + done + if [ -z "$rdata" ]; then + record_op="DELETE" + else + # Recreate the txtvalue TXT Record + body="{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":600, \"rdata\":"[${rdata}]"}" + _debug3 "body" "$body" + fi + fi + fi + _edge_result=$(_edgedns_rest "$record_op" "$acmeRecordURI" "$body") + _api_status="$?" + if [ "$_api_status" -eq 0 ]; then + _log "$(printf "Text value %s removed from recordset %s" "$txtvalue" "$fulldomain")" + return 0 + else + _err "$(printf "error removing TXT record for validation. Error: %s" "$_edge_result")" + return 1 + fi +} + +#################### Private functions below ################################## + +_EDGEDNS_credentials() { + _debug "GettingEdge DNS credentials" + _log "$(printf "ACME DNSAPI Edge DNS version %s" ${ACME_EDGEDNS_VERSION})" + args_missing=0 + if [ -z "$AKAMAI_ACCESS_TOKEN" ]; then + AKAMAI_ACCESS_TOKEN="" + AKAMAI_CLIENT_TOKEN="" + AKAMAI_HOST="" + AKAMAI_CLIENT_SECRET="" + _err "AKAMAI_ACCESS_TOKEN is missing" + args_missing=1 + fi + if [ -z "$AKAMAI_CLIENT_TOKEN" ]; then + AKAMAI_ACCESS_TOKEN="" + AKAMAI_CLIENT_TOKEN="" + AKAMAI_HOST="" + AKAMAI_CLIENT_SECRET="" + _err "AKAMAI_CLIENT_TOKEN is missing" + args_missing=1 + fi + if [ -z "$AKAMAI_HOST" ]; then + AKAMAI_ACCESS_TOKEN="" + AKAMAI_CLIENT_TOKEN="" + AKAMAI_HOST="" + AKAMAI_CLIENT_SECRET="" + _err "AKAMAI_HOST is missing" + args_missing=1 + fi + if [ -z "$AKAMAI_CLIENT_SECRET" ]; then + AKAMAI_ACCESS_TOKEN="" + AKAMAI_CLIENT_TOKEN="" + AKAMAI_HOST="" + AKAMAI_CLIENT_SECRET="" + _err "AKAMAI_CLIENT_SECRET is missing" + args_missing=1 + fi + + if [ "$args_missing" = 1 ]; then + _err "You have not properly specified the EdgeDNS Open Edgegrid API credentials. Please try again." + return 1 + else + _saveaccountconf_mutable AKAMAI_ACCESS_TOKEN "$AKAMAI_ACCESS_TOKEN" + _saveaccountconf_mutable AKAMAI_CLIENT_TOKEN "$AKAMAI_CLIENT_TOKEN" + _saveaccountconf_mutable AKAMAI_HOST "$AKAMAI_HOST" + _saveaccountconf_mutable AKAMAI_CLIENT_SECRET "$AKAMAI_CLIENT_SECRET" + # Set whether curl should use secure or insecure mode + fi + export HTTPS_INSECURE=0 # All Edgegrid API calls are secure + edge_endpoint=$(printf "https://%s/config-dns/v2/zones" "$AKAMAI_HOST") + _debug3 "Edge API Endpoint:" "$edge_endpoint" + +} + +_EDGEDNS_getZoneInfo() { + _debug "Getting Zoneinfo" + zoneEnd=false + curZone=$1 + while [ -n "$zoneEnd" ]; do + # we can strip the first part of the fulldomain, since its just the _acme-challenge string + curZone="${curZone#*.}" + # suffix . needed for zone -> domain.tld. + # create zone get url + get_zone_url=$(printf "%s/%s" "$edge_endpoint" "$curZone") + _debug3 "Zone Get: " "${get_zone_url}" + curResult=$(_edgedns_rest GET "$get_zone_url") + retVal=$? + if [ "$retVal" -ne 0 ]; then + if [ "$curResult" = "FATAL" ]; then + _err "$(printf "Fatal error: acme API function call : %s" "$retVal")" + fi + if [ "$curResult" != "404" ]; then + _err "$(printf "Managed zone validation failed. Error response: %s" "$retVal")" + return 1 + fi + fi + if _contains "$curResult" "\"zone\":"; then + _debug2 "Zone data" "${curResult}" + zone=$(echo "${curResult}" | _egrep_o "\"zone\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"") + _debug3 "Zone" "${zone}" + zoneEnd="" + return 0 + fi + + if [ "${curZone#*.}" != "$curZone" ]; then + _debug3 "$(printf "%s still contains a '.' - so we can check next higher level" "$curZone")" + else + zoneEnd=true + _err "Couldn't retrieve zone data." + return 1 + fi + done + _err "Failed to retrieve zone data." + return 2 +} + +_edgedns_headers="" + +_edgedns_rest() { + _debug "Handling API Request" + m=$1 + # Assume endpoint is complete path, including query args if applicable + ep=$2 + body_data=$3 + _edgedns_content_type="" + _request_url_path="$ep" + _request_body="$body_data" + _request_method="$m" + _edgedns_headers="" + tab="" + _edgedns_headers="${_edgedns_headers}${tab}Host: ${AKAMAI_HOST}" + tab="\t" + # Set in acme.sh _post/_get + #_edgedns_headers="${_edgedns_headers}${tab}User-Agent:ACME DNSAPI Edge DNS version ${ACME_EDGEDNS_VERSION}" + _edgedns_headers="${_edgedns_headers}${tab}Accept: application/json,*/*" + if [ "$m" != "GET" ] && [ "$m" != "DELETE" ]; then + _edgedns_content_type="application/json" + _debug3 "_request_body" "$_request_body" + _body_len=$(echo "$_request_body" | tr -d "\n\r" | awk '{print length}') + _edgedns_headers="${_edgedns_headers}${tab}Content-Length: ${_body_len}" + fi + _edgedns_make_auth_header + _edgedns_headers="${_edgedns_headers}${tab}Authorization: ${_signed_auth_header}" + _secure_debug2 "Made Auth Header" "$_signed_auth_header" + hdr_indx=1 + work_header="${_edgedns_headers}${tab}" + _debug3 "work_header" "$work_header" + while [ "$work_header" ]; do + entry="${work_header%%\\t*}" + work_header="${work_header#*\\t}" + export "$(printf "_H%s=%s" "$hdr_indx" "$entry")" + _debug2 "Request Header " "$entry" + hdr_indx=$((hdr_indx + 1)) + done + + # clear headers from previous request to avoid getting wrong http code on timeouts + : >"$HTTP_HEADER" + _debug2 "$ep" + if [ "$m" != "GET" ]; then + _debug3 "Method data" "$data" + # body url [needbase64] [POST|PUT|DELETE] [ContentType] + response=$(_post "$_request_body" "$ep" false "$m" "$_edgedns_content_type") + else + response=$(_get "$ep") + fi + _ret="$?" + if [ "$_ret" -ne 0 ]; then + _err "$(printf "acme.sh API function call failed. Error: %s" "$_ret")" + echo "FATAL" + return "$_ret" + fi + _debug2 "response" "${response}" + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" + _debug2 "http response code" "$_code" + if [ "$_code" = "200" ] || [ "$_code" = "201" ]; then + # All good + response="$(echo "${response}" | _normalizeJson)" + echo "$response" + return 0 + fi + + if [ "$_code" = "204" ]; then + # Success, no body + echo "$_code" + return 0 + fi + + if [ "$_code" = "400" ]; then + _err "Bad request presented" + _log "$(printf "Headers: %s" "$_edgedns_headers")" + _log "$(printf "Method: %s" "$_request_method")" + _log "$(printf "URL: %s" "$ep")" + _log "$(printf "Data: %s" "$data")" + fi + + if [ "$_code" = "403" ]; then + _err "access denied make sure your Edgegrid cedentials are correct." + fi + + echo "$_code" + return 1 +} + +_edgedns_eg_timestamp() { + _debug "Generating signature Timestamp" + _debug3 "Retriving ntp time" + _timeheaders="$(_get "https://www.ntp.org" "onlyheader")" + _debug3 "_timeheaders" "$_timeheaders" + _ntpdate="$(echo "$_timeheaders" | grep -i "Date:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")" + _debug3 "_ntpdate" "$_ntpdate" + _ntpdate="$(echo "${_ntpdate}" | sed -e 's/^[[:space:]]*//')" + _debug3 "_NTPDATE" "$_ntpdate" + _ntptime="$(echo "${_ntpdate}" | _head_n 1 | cut -d " " -f 5 | tr -d "\r\n")" + _debug3 "_ntptime" "$_ntptime" + _eg_timestamp=$(date -u "+%Y%m%dT") + _eg_timestamp="$(printf "%s%s+0000" "$_eg_timestamp" "$_ntptime")" + _debug "_eg_timestamp" "$_eg_timestamp" +} + +_edgedns_new_nonce() { + _debug "Generating Nonce" + _nonce=$(echo "EDGEDNS$(_time)" | _digest sha1 hex | cut -c 1-32) + _debug3 "_nonce" "$_nonce" +} + +_edgedns_make_auth_header() { + _debug "Constructing Auth Header" + _edgedns_new_nonce + _edgedns_eg_timestamp + # "Unsigned authorization header: 'EG1-HMAC-SHA256 client_token=block;access_token=block;timestamp=20200806T14:16:33+0000;nonce=72cde72c-82d9-4721-9854-2ba057929d67;'" + _auth_header="$(printf "EG1-HMAC-SHA256 client_token=%s;access_token=%s;timestamp=%s;nonce=%s;" "$AKAMAI_CLIENT_TOKEN" "$AKAMAI_ACCESS_TOKEN" "$_eg_timestamp" "$_nonce")" + _secure_debug2 "Unsigned Auth Header: " "$_auth_header" + + _edgedns_sign_request + _signed_auth_header="$(printf "%ssignature=%s" "$_auth_header" "$_signed_req")" + _secure_debug2 "Signed Auth Header: " "${_signed_auth_header}" +} + +_edgedns_sign_request() { + _debug2 "Signing http request" + _edgedns_make_data_to_sign "$_auth_header" + _secure_debug2 "Returned signed data" "$_mdata" + _edgedns_make_signing_key "$_eg_timestamp" + _edgedns_base64_hmac_sha256 "$_mdata" "$_signing_key" + _signed_req="$_hmac_out" + _secure_debug2 "Signed Request" "$_signed_req" +} + +_edgedns_make_signing_key() { + _debug2 "Creating sigining key" + ts=$1 + _edgedns_base64_hmac_sha256 "$ts" "$AKAMAI_CLIENT_SECRET" + _signing_key="$_hmac_out" + _secure_debug2 "Signing Key" "$_signing_key" + +} + +_edgedns_make_data_to_sign() { + _debug2 "Processing data to sign" + hdr=$1 + _secure_debug2 "hdr" "$hdr" + _edgedns_make_content_hash + path="$(echo "$_request_url_path" | tr -d "\n\r" | sed 's/https\?:\/\///')" + path="${path#*$AKAMAI_HOST}" + _debug "hier path" "$path" + # dont expose headers to sign so use MT string + _mdata="$(printf "%s\thttps\t%s\t%s\t%s\t%s\t%s" "$_request_method" "$AKAMAI_HOST" "$path" "" "$_hash" "$hdr")" + _secure_debug2 "Data to Sign" "$_mdata" +} + +_edgedns_make_content_hash() { + _debug2 "Generating content hash" + _hash="" + _debug2 "Request method" "${_request_method}" + if [ "$_request_method" != "POST" ] || [ -z "$_request_body" ]; then + return 0 + fi + _debug2 "Req body" "$_request_body" + _edgedns_base64_sha256 "$_request_body" + _hash="$_sha256_out" + _debug2 "Content hash" "$_hash" +} + +_edgedns_base64_hmac_sha256() { + _debug2 "Generating hmac" + data=$1 + key=$2 + encoded_data="$(echo "$data" | iconv -t utf-8)" + encoded_key="$(echo "$key" | iconv -t utf-8)" + _secure_debug2 "encoded data" "$encoded_data" + _secure_debug2 "encoded key" "$encoded_key" + + encoded_key_hex=$(printf "%s" "$encoded_key" | _hex_dump | tr -d ' ') + data_sig="$(echo "$encoded_data" | tr -d "\n\r" | _hmac sha256 "$encoded_key_hex" | _base64)" + + _secure_debug2 "data_sig:" "$data_sig" + _hmac_out="$(echo "$data_sig" | tr -d "\n\r" | iconv -f utf-8)" + _secure_debug2 "hmac" "$_hmac_out" +} + +_edgedns_base64_sha256() { + _debug2 "Creating sha256 digest" + trg=$1 + _secure_debug2 "digest data" "$trg" + digest="$(echo "$trg" | tr -d "\n\r" | _digest "sha256")" + _sha256_out="$(echo "$digest" | tr -d "\n\r" | iconv -f utf-8)" + _secure_debug2 "digest decode" "$_sha256_out" +} + +#_edgedns_parse_edgerc() { +# filepath=$1 +# section=$2 +#} diff --git a/dnsapi/dns_huaweicloud.sh b/dnsapi/dns_huaweicloud.sh new file mode 100644 index 00000000..74fec2a9 --- /dev/null +++ b/dnsapi/dns_huaweicloud.sh @@ -0,0 +1,255 @@ +#!/usr/bin/env sh + +# HUAWEICLOUD_Username +# HUAWEICLOUD_Password +# HUAWEICLOUD_ProjectID + +iam_api="https://iam.myhuaweicloud.com" +dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Used to add txt record +# +# Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/zh-cn_topic_0132421999.html +# + +dns_huaweicloud_add() { + fulldomain=$1 + txtvalue=$2 + + HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" + HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}" + HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}" + + # Check information + if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then + _err "Not enough information provided to dns_huaweicloud!" + return 1 + fi + + token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")" + _debug2 "${token}" + zoneid="$(_get_zoneid "${token}" "${fulldomain}")" + _debug "${zoneid}" + + _debug "Adding Record" + _add_record "${token}" "${fulldomain}" "${txtvalue}" + ret="$?" + if [ "${ret}" != "0" ]; then + _err "dns_huaweicloud: Error adding record." + return 1 + fi + + # Do saving work if all succeeded + _saveaccountconf_mutable HUAWEICLOUD_Username "${HUAWEICLOUD_Username}" + _saveaccountconf_mutable HUAWEICLOUD_Password "${HUAWEICLOUD_Password}" + _saveaccountconf_mutable HUAWEICLOUD_ProjectID "${HUAWEICLOUD_ProjectID}" + return 0 +} + +# Usage: fulldomain txtvalue +# Used to remove the txt record after validation +# +# Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/dns_api_64005.html +# + +dns_huaweicloud_rm() { + fulldomain=$1 + txtvalue=$2 + + HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}" + HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}" + HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}" + + # Check information + if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then + _err "Not enough information provided to dns_huaweicloud!" + return 1 + fi + + token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")" + _debug2 "${token}" + zoneid="$(_get_zoneid "${token}" "${fulldomain}")" + _debug "${zoneid}" + record_id="$(_get_recordset_id "${token}" "${fulldomain}" "${zoneid}")" + _debug "Record Set ID is: ${record_id}" + + # Remove all records + # Therotically HuaweiCloud does not allow more than one record set + # But remove them recurringly to increase robusty + while [ "${record_id}" != "0" ]; do + _debug "Removing Record" + _rm_record "${token}" "${zoneid}" "${record_id}" + record_id="$(_get_recordset_id "${token}" "${fulldomain}" "${zoneid}")" + done + return 0 +} + +################### Private functions below ################################## + +# _get_zoneid +# +# _token=$1 +# _domain_string=$2 +# +# printf "%s" "${_zoneid}" +_get_zoneid() { + _token=$1 + _domain_string=$2 + export _H1="X-Auth-Token: ${_token}" + + i=1 + while true; do + h=$(printf "%s" "${_domain_string}" | cut -d . -f $i-100) + if [ -z "$h" ]; then + #not valid + return 1 + fi + _debug "$h" + response=$(_get "${dns_api}/v2/zones?name=${h}") + + if _contains "${response}" "id"; then + _debug "Get Zone ID Success." + _zoneid=$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ") + printf "%s" "${_zoneid}" + return 0 + fi + + i=$(_math "$i" + 1) + done + return 1 +} + +_get_recordset_id() { + _token=$1 + _domain=$2 + _zoneid=$3 + export _H1="X-Auth-Token: ${_token}" + + response=$(_get "${dns_api}/v2/zones/${_zoneid}/recordsets?name=${_domain}") + if _contains "${response}" "id"; then + _id="$(echo "${response}" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | tr -d " ")" + printf "%s" "${_id}" + return 0 + fi + printf "%s" "0" + return 1 +} + +_add_record() { + _token=$1 + _domain=$2 + _txtvalue=$3 + + # Get Existing Records + export _H1="X-Auth-Token: ${_token}" + response=$(_get "${dns_api}/v2/zones/${zoneid}/recordsets?name=${_domain}") + + _debug2 "${response}" + _exist_record=$(echo "${response}" | _egrep_o '"records":[^]]*' | sed 's/\"records\"\:\[//g') + _debug "${_exist_record}" + + # Check if record exist + # Generate body data + if [ -z "${_exist_record}" ]; then + _post_body="{ + \"name\": \"${_domain}.\", + \"description\": \"ACME Challenge\", + \"type\": \"TXT\", + \"ttl\": 1, + \"records\": [ + \"\\\"${_txtvalue}\\\"\" + ] + }" + else + _post_body="{ + \"name\": \"${_domain}.\", + \"description\": \"ACME Challenge\", + \"type\": \"TXT\", + \"ttl\": 1, + \"records\": [ + ${_exist_record}, + \"\\\"${_txtvalue}\\\"\" + ] + }" + fi + + _record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")" + _debug "Record Set ID is: ${_record_id}" + + # Remove all records + while [ "${_record_id}" != "0" ]; do + _debug "Removing Record" + _rm_record "${_token}" "${zoneid}" "${_record_id}" + _record_id="$(_get_recordset_id "${_token}" "${_domain}" "${zoneid}")" + done + + # Add brand new records with all old and new records + export _H2="Content-Type: application/json" + export _H1="X-Auth-Token: ${_token}" + + _debug2 "${_post_body}" + _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets" >/dev/null + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" + if [ "$_code" != "202" ]; then + _err "dns_huaweicloud: http code ${_code}" + return 1 + fi + return 0 +} + +# _rm_record $token $zoneid $recordid +# assume ${dns_api} exist +# no output +# return 0 +_rm_record() { + _token=$1 + _zone_id=$2 + _record_id=$3 + + export _H2="Content-Type: application/json" + export _H1="X-Auth-Token: ${_token}" + + _post "" "${dns_api}/v2/zones/${_zone_id}/recordsets/${_record_id}" false "DELETE" >/dev/null + return $? +} + +_get_token() { + _username=$1 + _password=$2 + _project=$3 + + _debug "Getting Token" + body="{ + \"auth\": { + \"identity\": { + \"methods\": [ + \"password\" + ], + \"password\": { + \"user\": { + \"name\": \"${_username}\", + \"password\": \"${_password}\", + \"domain\": { + \"name\": \"${_username}\" + } + } + } + }, + \"scope\": { + \"project\": { + \"id\": \"${_project}\" + } + } + } + }" + export _H1="Content-Type: application/json;charset=utf8" + _post "${body}" "${iam_api}/v3/auth/tokens" >/dev/null + _code=$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n") + _token=$(grep "^X-Subject-Token" "$HTTP_HEADER" | cut -d " " -f 2-) + _debug2 "${_code}" + printf "%s" "${_token}" + return 0 +} diff --git a/dnsapi/dns_infomaniak.sh b/dnsapi/dns_infomaniak.sh new file mode 100755 index 00000000..765cf39d --- /dev/null +++ b/dnsapi/dns_infomaniak.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env sh + +############################################################################### +# Infomaniak API integration +# +# To use this API you need visit the API dashboard of your account +# once logged into https://manager.infomaniak.com add /api/dashboard to the URL +# +# Please report bugs to +# https://github.com/acmesh-official/acme.sh/issues/3188 +# +# Note: the URL looks like this: +# https://manager.infomaniak.com/v3//api/dashboard +# Then generate a token with the scope Domain +# this is given as an environment variable INFOMANIAK_API_TOKEN +############################################################################### + +# base variables + +DEFAULT_INFOMANIAK_API_URL="https://api.infomaniak.com" +DEFAULT_INFOMANIAK_TTL=300 + +######## Public functions ##################### + +#Usage: dns_infomaniak_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_infomaniak_add() { + + INFOMANIAK_API_TOKEN="${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}" + INFOMANIAK_API_URL="${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}" + INFOMANIAK_TTL="${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}" + + if [ -z "$INFOMANIAK_API_TOKEN" ]; then + INFOMANIAK_API_TOKEN="" + _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN" + return 1 + fi + + if [ -z "$INFOMANIAK_API_URL" ]; then + INFOMANIAK_API_URL="$DEFAULT_INFOMANIAK_API_URL" + fi + + if [ -z "$INFOMANIAK_TTL" ]; then + INFOMANIAK_TTL="$DEFAULT_INFOMANIAK_TTL" + fi + + #save the token to the account conf file. + _saveaccountconf_mutable INFOMANIAK_API_TOKEN "$INFOMANIAK_API_TOKEN" + + if [ "$INFOMANIAK_API_URL" != "$DEFAULT_INFOMANIAK_API_URL" ]; then + _saveaccountconf_mutable INFOMANIAK_API_URL "$INFOMANIAK_API_URL" + fi + + if [ "$INFOMANIAK_TTL" != "$DEFAULT_INFOMANIAK_TTL" ]; then + _saveaccountconf_mutable INFOMANIAK_TTL "$INFOMANIAK_TTL" + fi + + export _H1="Authorization: Bearer $INFOMANIAK_API_TOKEN" + export _H2="Content-Type: application/json" + + fulldomain="$1" + txtvalue="$2" + + _info "Infomaniak DNS API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + fqdn=${fulldomain#_acme-challenge.} + + # guess which base domain to add record to + zone_and_id=$(_find_zone "$fqdn") + if [ -z "$zone_and_id" ]; then + _err "cannot find zone to modify" + return 1 + fi + zone=${zone_and_id% *} + domain_id=${zone_and_id#* } + + # extract first part of domain + key=${fulldomain%.$zone} + + _debug "zone:$zone id:$domain_id key:$key" + + # payload + data="{\"type\": \"TXT\", \"source\": \"$key\", \"target\": \"$txtvalue\", \"ttl\": $INFOMANIAK_TTL}" + + # API call + response=$(_post "$data" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record") + if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then + _info "Record added" + _debug "Response: $response" + return 0 + fi + _err "could not create record" + _debug "Response: $response" + return 1 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_infomaniak_rm() { + + INFOMANIAK_API_TOKEN="${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}" + INFOMANIAK_API_URL="${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}" + INFOMANIAK_TTL="${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}" + + if [ -z "$INFOMANIAK_API_TOKEN" ]; then + INFOMANIAK_API_TOKEN="" + _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN" + return 1 + fi + + if [ -z "$INFOMANIAK_API_URL" ]; then + INFOMANIAK_API_URL="$DEFAULT_INFOMANIAK_API_URL" + fi + + if [ -z "$INFOMANIAK_TTL" ]; then + INFOMANIAK_TTL="$DEFAULT_INFOMANIAK_TTL" + fi + + #save the token to the account conf file. + _saveaccountconf_mutable INFOMANIAK_API_TOKEN "$INFOMANIAK_API_TOKEN" + + if [ "$INFOMANIAK_API_URL" != "$DEFAULT_INFOMANIAK_API_URL" ]; then + _saveaccountconf_mutable INFOMANIAK_API_URL "$INFOMANIAK_API_URL" + fi + + if [ "$INFOMANIAK_TTL" != "$DEFAULT_INFOMANIAK_TTL" ]; then + _saveaccountconf_mutable INFOMANIAK_TTL "$INFOMANIAK_TTL" + fi + + export _H1="Authorization: Bearer $INFOMANIAK_API_TOKEN" + export _H2="ContentType: application/json" + + fulldomain=$1 + txtvalue=$2 + _info "Infomaniak DNS API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + fqdn=${fulldomain#_acme-challenge.} + + # guess which base domain to add record to + zone_and_id=$(_find_zone "$fqdn") + if [ -z "$zone_and_id" ]; then + _err "cannot find zone to modify" + return 1 + fi + zone=${zone_and_id% *} + domain_id=${zone_and_id#* } + + # extract first part of domain + key=${fulldomain%.$zone} + + _debug "zone:$zone id:$domain_id key:$key" + + # find previous record + # shellcheck disable=SC1004 + record_id=$(_get "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record" | sed 's/.*"data":\[\(.*\)\]}/\1/; s/},{/}\ +{/g' | sed -n 's/.*"id":"*\([0-9]*\)"*.*"source_idn":"'"$fulldomain"'".*"target_idn":"'"$txtvalue"'".*/\1/p') + if [ -z "$record_id" ]; then + _err "could not find record to delete" + return 1 + fi + _debug "record_id: $record_id" + + # API call + response=$(_post "" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record/$record_id" "" DELETE) + if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then + _info "Record deleted" + return 0 + fi + _err "could not delete record" + return 1 +} + +#################### Private functions below ################################## + +_get_domain_id() { + domain="$1" + + # shellcheck disable=SC1004 + _get "${INFOMANIAK_API_URL}/1/product?service_name=domain&customer_name=$domain" | sed 's/.*"data":\[{\(.*\)}\]}/\1/; s/,/\ +/g' | sed -n 's/^"id":\(.*\)/\1/p' +} + +_find_zone() { + zone="$1" + + # find domain in list, removing . parts sequentialy + while _contains "$zone" '\.'; do + _debug "testing $zone" + id=$(_get_domain_id "$zone") + if [ -n "$id" ]; then + echo "$zone $id" + return + fi + zone=${zone#*.} + done +} diff --git a/dnsapi/dns_simply.sh b/dnsapi/dns_simply.sh new file mode 100644 index 00000000..d053dcf6 --- /dev/null +++ b/dnsapi/dns_simply.sh @@ -0,0 +1,247 @@ +#!/usr/bin/env sh + +# +#SIMPLY_AccountName="accountname" +# +#SIMPLY_ApiKey="apikey" +# +#SIMPLY_Api="https://api.simply.com/1/[ACCOUNTNAME]/[APIKEY]" + +SIMPLY_Api_Default="https://api.simply.com/1" + +######## Public functions ##################### +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_simply_add() { + fulldomain=$1 + txtvalue=$2 + + if ! _simply_load_config; then + return 1 + fi + + _simply_save_config + + _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 ! _simply_add_record "$_domain" "$_sub_domain" "$txtvalue"; then + _err "Could not add DNS record" + return 1 + fi + return 0 +} + +dns_simply_rm() { + fulldomain=$1 + txtvalue=$2 + + if ! _simply_load_config; then + return 1 + fi + + _simply_save_config + + _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 txtvalue "$txtvalue" + + _info "Getting all existing records" + + if ! _simply_get_all_records "$_domain"; then + _err "invalid domain" + return 1 + fi + + records=$(echo "$response" | tr '{' "\n" | grep 'record_id\|type\|data\|\name' | sed 's/\"record_id/;\"record_id/' | tr "\n" ' ' | tr -d ' ' | tr ';' ' ') + + nr_of_deleted_records=0 + _info "Fetching txt record" + + for record in $records; do + _debug record "$record" + + record_data=$(echo "$record" | cut -d "," -f 3 | sed 's/"//g' | grep "data" | cut -d ":" -f 2) + record_type=$(echo "$record" | cut -d "," -f 4 | sed 's/"//g' | grep "type" | cut -d ":" -f 2) + + _debug2 record_data "$record_data" + _debug2 record_type "$record_type" + + if [ "$record_data" = "$txtvalue" ] && [ "$record_type" = "TXT" ]; then + + record_id=$(echo "$record" | cut -d "," -f 1 | grep "record_id" | cut -d ":" -f 2) + + _info "Deleting record $record" + _debug2 record_id "$record_id" + + if [ "$record_id" -gt 0 ]; then + + if ! _simply_delete_record "$_domain" "$_sub_domain" "$record_id"; then + _err "Record with id $record_id could not be deleted" + return 1 + fi + + nr_of_deleted_records=1 + break + else + _err "Fetching record_id could not be done, this should not happen, exiting function. Failing record is $record" + break + fi + fi + + done + + if [ "$nr_of_deleted_records" -eq 0 ]; then + _err "No record deleted, the DNS record needs to be removed manually." + else + _info "Deleted $nr_of_deleted_records record" + fi + + return 0 +} + +#################### Private functions below ################################## + +_simply_load_config() { + SIMPLY_Api="${SIMPLY_Api:-$(_readaccountconf_mutable SIMPLY_Api)}" + SIMPLY_AccountName="${SIMPLY_AccountName:-$(_readaccountconf_mutable SIMPLY_AccountName)}" + SIMPLY_ApiKey="${SIMPLY_ApiKey:-$(_readaccountconf_mutable SIMPLY_ApiKey)}" + + if [ -z "$SIMPLY_Api" ]; then + SIMPLY_Api="$SIMPLY_Api_Default" + fi + + if [ -z "$SIMPLY_AccountName" ] || [ -z "$SIMPLY_ApiKey" ]; then + SIMPLY_AccountName="" + SIMPLY_ApiKey="" + + _err "A valid Simply API account and apikey not provided." + _err "Please provide a valid API user and try again." + + return 1 + fi + + return 0 +} + +_simply_save_config() { + if [ "$SIMPLY_Api" != "$SIMPLY_Api_Default" ]; then + _saveaccountconf_mutable SIMPLY_Api "$SIMPLY_Api" + fi + _saveaccountconf_mutable SIMPLY_AccountName "$SIMPLY_AccountName" + _saveaccountconf_mutable SIMPLY_ApiKey "$SIMPLY_ApiKey" +} + +_simply_get_all_records() { + domain=$1 + + if ! _simply_rest GET "my/products/$domain/dns/records"; then + return 1 + fi + + return 0 +} + +_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 ! _simply_rest GET "my/products/$h/dns"; then + return 1 + fi + + if _contains "$response" '"code":"NOT_FOUND"'; 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 +} + +_simply_add_record() { + domain=$1 + sub_domain=$2 + txtval=$3 + + data="{\"name\": \"$sub_domain\", \"type\":\"TXT\", \"data\": \"$txtval\", \"priority\":0, \"ttl\": 3600}" + + if ! _simply_rest POST "my/products/$domain/dns/records" "$data"; then + _err "Adding record not successfull!" + return 1 + fi + + return 0 +} + +_simply_delete_record() { + domain=$1 + sub_domain=$2 + record_id=$3 + + _debug record_id "Delete record with id $record_id" + + if ! _simply_rest DELETE "my/products/$domain/dns/records/$record_id"; then + _err "Deleting record not successfull!" + return 1 + fi + + return 0 +} + +_simply_rest() { + m=$1 + ep="$2" + data="$3" + + _debug2 data "$data" + _debug2 ep "$ep" + _debug2 m "$m" + + export _H1="Content-Type: application/json" + + if [ "$m" != "GET" ]; then + response="$(_post "$data" "$SIMPLY_Api/$SIMPLY_AccountName/$SIMPLY_ApiKey/$ep" "" "$m")" + else + response="$(_get "$SIMPLY_Api/$SIMPLY_AccountName/$SIMPLY_ApiKey/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + + _debug2 response "$response" + + if _contains "$response" "Invalid account authorization"; then + _err "It seems that your api key or accountnumber is not correct." + return 1 + fi + + return 0 +} diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh new file mode 100644 index 00000000..24b8dd68 --- /dev/null +++ b/dnsapi/dns_world4you.sh @@ -0,0 +1,191 @@ +#!/usr/bin/env sh + +# World4You - www.world4you.com +# Lorenz Stechauner, 2020 - https://www.github.com/NerLOR + +WORLD4YOU_API="https://my.world4you.com/en" +PAKETNR='' +TLD='' +RECORD='' + +################ Public functions ################ + +# Usage: dns_world4you_add +dns_world4you_add() { + fqdn="$1" + value="$2" + _info "Using world4you to add record" + _debug fulldomain "$fqdn" + _debug txtvalue "$value" + + _login + if [ "$?" != 0 ]; then + return 1 + fi + + export _H1="Cookie: W4YSESSID=$sessid" + form=$(_get "$WORLD4YOU_API/dashboard/paketuebersicht") + _get_paketnr "$fqdn" "$form" + paketnr="$PAKETNR" + if [ -z "$paketnr" ]; then + _err "Unable to parse paketnr" + return 3 + fi + _debug paketnr "$paketnr" + + export _H1="Cookie: W4YSESSID=$sessid" + form=$(_get "$WORLD4YOU_API/$paketnr/dns") + formiddp=$(echo "$form" | grep 'AddDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="AddDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/') + formidttl=$(echo "$form" | grep 'AddDnsRecordForm\[uniqueFormIdTTL\]' | sed 's/^.*name="AddDnsRecordForm\[uniqueFormIdTTL\]" value="\([^"]*\)".*$/\1/') + form_token=$(echo "$form" | grep 'AddDnsRecordForm\[_token\]' | sed 's/^.*name="AddDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/') + if [ -z "$formiddp" ]; then + _err "Unable to parse form" + return 3 + fi + + _resethttp + export ACME_HTTP_NO_REDIRECTS=1 + body="AddDnsRecordForm[name]=$RECORD&AddDnsRecordForm[dnsType][type]=TXT&\ +AddDnsRecordForm[value]=$value&AddDnsRecordForm[aktivPaket]=$paketnr&AddDnsRecordForm[uniqueFormIdDP]=$formiddp&\ +AddDnsRecordForm[uniqueFormIdTTL]=$formidttl&AddDnsRecordForm[_token]=$form_token" + _info "Adding record..." + ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded') + _resethttp + + if grep '302' >/dev/null <"$HTTP_HEADER"; then + res=$(_get "$WORLD4YOU_API/$paketnr/dns") + if _contains "$res" "successfully"; then + return 0 + else + msg=$(echo "$res" | tr '\n' '\t' | sed 's/.*

[^\t]*\t *\([^\t]*\)\t.*/\1/') + _err "Unable to add record: $msg" + return 1 + fi + else + _err "$(_head_n 1 <"$HTTP_HEADER")" + return 1 + fi +} + +# Usage: dns_world4you_rm +dns_world4you_rm() { + fqdn="$1" + value="$2" + _info "Using world4you to remove record" + _debug fulldomain "$fqdn" + _debug txtvalue "$value" + + _login + if [ "$?" != 0 ]; then + return 1 + fi + + export _H1="Cookie: W4YSESSID=$sessid" + form=$(_get "$WORLD4YOU_API/dashboard/paketuebersicht") + _get_paketnr "$fqdn" "$form" + paketnr="$PAKETNR" + if [ -z "$paketnr" ]; then + _err "Unable to parse paketnr" + return 3 + fi + _debug paketnr "$paketnr" + + form=$(_get "$WORLD4YOU_API/$paketnr/dns") + formiddp=$(echo "$form" | grep 'DeleteDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="DeleteDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/') + formidttl=$(echo "$form" | grep 'DeleteDnsRecordForm\[uniqueFormIdTTL\]' | sed 's/^.*name="DeleteDnsRecordForm\[uniqueFormIdTTL\]" value="\([^"]*\)".*$/\1/') + form_token=$(echo "$form" | grep 'DeleteDnsRecordForm\[_token\]' | sed 's/^.*name="DeleteDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/') + if [ -z "$formiddp" ]; then + _err "Unable to parse form" + return 3 + fi + + recordid=$(printf "TXT:%s.:\"%s\"" "$fqdn" "$value" | _base64) + _debug recordid "$recordid" + + _resethttp + export ACME_HTTP_NO_REDIRECTS=1 + body="DeleteDnsRecordForm[recordId]=$recordid&DeleteDnsRecordForm[aktivPaket]=$paketnr&\ +DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[uniqueFormIdTTL]=$formidttl&\ +DeleteDnsRecordForm[_token]=$form_token" + _info "Removing record..." + ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/deleteRecord" '' POST 'application/x-www-form-urlencoded') + _resethttp + + if grep '302' >/dev/null <"$HTTP_HEADER"; then + res=$(_get "$WORLD4YOU_API/$paketnr/dns") + if _contains "$res" "successfully"; then + return 0 + else + msg=$(echo "$res" | tr '\n' '\t' | sed 's/.*

[^\t]*\t *\([^\t]*\)\t.*/\1/') + _err "Unable to remove record: $msg" + return 1 + fi + else + _err "$(_head_n 1 <"$HTTP_HEADER")" + return 1 + fi +} + +################ Private functions ################ + +# Usage: _login +_login() { + WORLD4YOU_USERNAME="${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}" + WORLD4YOU_PASSWORD="${WORLD4YOU_PASSWORD:-$(_readaccountconf_mutable WORLD4YOU_PASSWORD)}" + + if [ -z "$WORLD4YOU_USERNAME" ] || [ -z "$WORLD4YOU_PASSWORD" ]; then + WORLD4YOU_USERNAME="" + WORLD4YOU_PASSWORD="" + _err "You didn't specify world4you username and password yet." + _err "Usage: export WORLD4YOU_USERNAME=" + _err "Usage: export WORLD4YOU_PASSWORD=" + return 1 + fi + + _saveaccountconf_mutable WORLD4YOU_USERNAME "$WORLD4YOU_USERNAME" + _saveaccountconf_mutable WORLD4YOU_PASSWORD "$WORLD4YOU_PASSWORD" + + _info "Logging in..." + + username="$WORLD4YOU_USERNAME" + password="$WORLD4YOU_PASSWORD" + csrf_token=$(_get "$WORLD4YOU_API/login" | grep '_csrf_token' | sed 's/^.*]*value=\"\([^"]*\)\".*$/\1/') + sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/') + + export _H1="Cookie: W4YSESSID=$sessid" + export _H2="X-Requested-With: XMLHttpRequest" + body="_username=$username&_password=$password&_csrf_token=$csrf_token" + ret=$(_post "$body" "$WORLD4YOU_API/login" '' POST 'application/x-www-form-urlencoded') + unset _H2 + _debug ret "$ret" + if _contains "$ret" "\"success\":true"; then + _info "Successfully logged in" + sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/') + else + _err "Unable to log in: $(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/')" + return 1 + fi +} + +# Usage _get_paketnr
+_get_paketnr() { + fqdn="$1" + form="$2" + + domains=$(echo "$form" | grep '^ *[A-Za-z0-9_\.-]*\.[A-Za-z0-9_-]*$' | sed 's/^\s*\(\S*\)$/\1/') + domain='' + for domain in $domains; do + if echo "$fqdn" | grep "$domain\$" >/dev/null; then + break + fi + domain='' + done + if [ -z "$domain" ]; then + return 1 + fi + + TLD="$domain" + RECORD=$(echo "$fqdn" | cut -c"1-$((${#fqdn} - ${#TLD} - 1))") + PAKETNR=$(echo "$form" | grep "data-textfilter=\" $domain " | _head_n 1 | sed 's/^.* \([0-9]*\) .*$/\1/') + return 0 +}