pax_global_header00006660000000000000000000000064147203236520014516gustar00rootroot0000000000000052 comment=5d6f1bd2d7d1dbb2ac880dbf59d3eee7a79fb1bb acme.sh-3.1.0/000077500000000000000000000000001472032365200130355ustar00rootroot00000000000000acme.sh-3.1.0/.github/000077500000000000000000000000001472032365200143755ustar00rootroot00000000000000acme.sh-3.1.0/.github/FUNDING.yml000066400000000000000000000011771472032365200162200ustar00rootroot00000000000000# These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: acmesh ko_fi: neilpang tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] acme.sh-3.1.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000014761472032365200171120ustar00rootroot00000000000000 Steps to reproduce ------------------ Debug log ----------------- ``` acme.sh --issue ..... --debug 2 ``` acme.sh-3.1.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000005431472032365200202000ustar00rootroot00000000000000acme.sh-3.1.0/.github/workflows/000077500000000000000000000000001472032365200164325ustar00rootroot00000000000000acme.sh-3.1.0/.github/workflows/DNS.yml000066400000000000000000000471231472032365200176100ustar00rootroot00000000000000name: DNS on: workflow_dispatch: push: paths: - 'dnsapi/*.sh' - '.github/workflows/DNS.yml' pull_request: branches: - 'dev' paths: - 'dnsapi/*.sh' - '.github/workflows/DNS.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: CheckToken: runs-on: ubuntu-latest outputs: hasToken: ${{ steps.step_one.outputs.hasToken }} steps: - name: Set the value id: step_one run: | if [ "${{secrets.TokenName1}}" ] ; then echo "::set-output name=hasToken::true" else echo "::set-output name=hasToken::false" fi - name: Check the value run: echo ${{ steps.step_one.outputs.hasToken }} Fail: runs-on: ubuntu-latest needs: CheckToken if: "contains(needs.CheckToken.outputs.hasToken, 'false')" steps: - name: "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test" run: | echo "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test" if [ "${{github.repository_owner}}" != "acmesh-official" ]; then false fi Docker: runs-on: ubuntu-latest needs: CheckToken if: "contains(needs.CheckToken.outputs.hasToken, 'true')" env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file run: | cd ../acmetest if [ "${{ secrets.TokenName1}}" ] ; then echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> docker.env fi if [ "${{ secrets.TokenName2}}" ] ; then echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> docker.env fi if [ "${{ secrets.TokenName3}}" ] ; then echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> docker.env fi if [ "${{ secrets.TokenName4}}" ] ; then echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> docker.env fi if [ "${{ secrets.TokenName5}}" ] ; then echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env fi - name: Run acmetest run: cd ../acmetest && ./rundocker.sh testall MacOS: runs-on: macos-latest needs: Docker env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Install tools run: brew install socat - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest 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 Windows: runs-on: windows-latest needs: MacOS env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - name: Set git to use LF run: | git config --global core.autocrlf false - uses: actions/checkout@v4 - name: Install cygwin base packages with chocolatey run: | choco config get cacheLocation choco install --no-progress cygwin shell: cmd - name: Install cygwin additional packages run: | C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://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: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest shell: bash 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 FreeBSD: runs-on: ubuntu-latest needs: Windows env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/freebsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: pkg install -y socat curl usesh: true copyback: false 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 OpenBSD: runs-on: ubuntu-latest needs: FreeBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/openbsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: pkg_add socat curl libiconv usesh: true copyback: false 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 NetBSD: runs-on: ubuntu-latest needs: OpenBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/netbsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | /usr/sbin/pkg_add curl socat usesh: true copyback: false 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 DragonFlyBSD: runs-on: ubuntu-latest needs: NetBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/dragonflybsd-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | pkg install -y curl socat libnghttp2 usesh: true copyback: false 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: ubuntu-latest needs: DragonFlyBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Solaris doesn't accept the expired ISRG X1 root TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/solaris-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' copyback: false prepare: pkgutil -y -i socat run: | pkg set-mediator -v -I default@1.1 openssl export PATH=/usr/gnu/bin:$PATH 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 Omnios: runs-on: ubuntu-latest needs: Solaris env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} CASE: le_test_dnsapi TEST_LOCAL: 1 DEBUG: ${{ secrets.DEBUG }} http_proxy: ${{ secrets.http_proxy }} https_proxy: ${{ secrets.https_proxy }} HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Omnios doesn't accept the expired ISRG X1 root TokenName1: ${{ secrets.TokenName1}} TokenName2: ${{ secrets.TokenName2}} TokenName3: ${{ secrets.TokenName3}} TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - uses: actions/checkout@v4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/omnios-vm@v1 with: envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' copyback: false prepare: pkg install socat 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 acme.sh-3.1.0/.github/workflows/DragonFlyBSD.yml000066400000000000000000000035601472032365200213770ustar00rootroot00000000000000name: DragonFlyBSD on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/DragonFlyBSD.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/DragonFlyBSD.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: DragonFlyBSD: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http port: 8080 - name: Set envs run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/dragonflybsd-vm@v1 with: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" prepare: | pkg install -y curl socat libnghttp2 usesh: true copyback: false run: | cd ../acmetest \ && ./letest.sh acme.sh-3.1.0/.github/workflows/FreeBSD.yml000066400000000000000000000041161472032365200203710ustar00rootroot00000000000000name: FreeBSD on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/FreeBSD.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/FreeBSD.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: FreeBSD: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) ACME_USE_WGET: 1 #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http port: 8080 - name: Set envs run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/freebsd-vm@v1 with: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" prepare: pkg install -y socat curl wget usesh: true copyback: false run: | cd ../acmetest \ && ./letest.sh acme.sh-3.1.0/.github/workflows/Linux.yml000066400000000000000000000021151472032365200202530ustar00rootroot00000000000000name: Linux on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/Linux.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/Linux.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Linux: strategy: matrix: os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3"] runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_PREFERRED_CHAIN: (STAGING) TEST_ACME_Server: "LetsEncrypt.org_test" steps: - uses: actions/checkout@v4 - name: Clone acmetest run: | cd .. \ && git clone --depth=1 https://github.com/acmesh-official/acmetest.git \ && cp -r acme.sh acmetest/ - name: Run acmetest run: | cd ../acmetest \ && ./rundocker.sh testplat ${{ matrix.os }} acme.sh-3.1.0/.github/workflows/MacOS.yml000066400000000000000000000027331472032365200201240ustar00rootroot00000000000000name: MacOS on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/MacOS.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/MacOS.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: MacOS: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: macos-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} steps: - uses: actions/checkout@v4 - name: Install tools run: brew install socat - name: Clone acmetest run: | cd .. \ && git clone --depth=1 https://github.com/acmesh-official/acmetest.git \ && cp -r acme.sh acmetest/ - name: Run acmetest run: | cd ../acmetest \ && sudo --preserve-env ./letest.sh acme.sh-3.1.0/.github/workflows/NetBSD.yml000066400000000000000000000035121472032365200202350ustar00rootroot00000000000000name: NetBSD on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/NetBSD.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/NetBSD.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: NetBSD: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http port: 8080 - name: Set envs run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/netbsd-vm@v1 with: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" prepare: | /usr/sbin/pkg_add curl socat usesh: true copyback: false run: | cd ../acmetest \ && ./letest.sh acme.sh-3.1.0/.github/workflows/Omnios.yml000066400000000000000000000037411472032365200204260ustar00rootroot00000000000000name: Omnios on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/Omnios.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/Omnios.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Omnios: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) ACME_USE_WGET: 1 #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http port: 8080 - name: Set envs run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/omnios-vm@v1 with: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" prepare: pkg install socat wget copyback: false run: | cd ../acmetest \ && ./letest.sh acme.sh-3.1.0/.github/workflows/OpenBSD.yml000066400000000000000000000041221472032365200204060ustar00rootroot00000000000000name: OpenBSD on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/OpenBSD.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/OpenBSD.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: OpenBSD: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) ACME_USE_WGET: 1 #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http port: 8080 - name: Set envs run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/openbsd-vm@v1 with: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" prepare: pkg_add socat curl wget libnghttp2 usesh: true copyback: false run: | cd ../acmetest \ && ./letest.sh acme.sh-3.1.0/.github/workflows/PebbleStrict.yml000066400000000000000000000041731472032365200215440ustar00rootroot00000000000000name: PebbleStrict on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/PebbleStrict.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/PebbleStrict.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: PebbleStrict: runs-on: ubuntu-latest env: TestingDomain: example.com TestingAltDomains: www.example.com TEST_ACME_Server: https://localhost:14000/dir HTTPS_INSECURE: 1 Le_HTTPPort: 5002 TEST_LOCAL: 1 TEST_CA: "Pebble Intermediate CA" steps: - uses: actions/checkout@v4 - name: Install tools run: sudo apt-get install -y socat - name: Run Pebble run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d - name: Set up Pebble run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest run: cd ../acmetest && ./letest.sh PebbleStrict_IPCert: runs-on: ubuntu-latest env: TestingDomain: 1.23.45.67 TEST_ACME_Server: https://localhost:14000/dir HTTPS_INSECURE: 1 Le_HTTPPort: 5002 Le_TLSPort: 5001 TEST_LOCAL: 1 TEST_CA: "Pebble Intermediate CA" TEST_IPCERT: 1 steps: - uses: actions/checkout@v4 - name: Install tools run: sudo apt-get install -y socat - name: Run Pebble run: | docker run --rm -itd --name=pebble \ -e PEBBLE_VA_ALWAYS_VALID=1 \ -p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest run: cd ../acmetest && ./letest.shacme.sh-3.1.0/.github/workflows/Solaris.yml000066400000000000000000000037551472032365200206030ustar00rootroot00000000000000name: Solaris on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/Solaris.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/Solaris.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Solaris: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) ACME_USE_WGET: 1 #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http port: 8080 - name: Set envs run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - uses: vmactions/solaris-vm@v1 with: envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" prepare: pkgutil -y -i socat curl wget copyback: false run: | cd ../acmetest \ && ./letest.sh acme.sh-3.1.0/.github/workflows/Ubuntu.yml000066400000000000000000000065341472032365200204470ustar00rootroot00000000000000name: Ubuntu on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/Ubuntu.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/Ubuntu.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Ubuntu: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) ACME_USE_WGET: 1 - TEST_ACME_Server: "ZeroSSL.com" CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" CA: "ZeroSSL RSA Domain Secure Site CA" CA_EMAIL: "githubtest@acme.sh" TEST_PREFERRED_CHAIN: "" - TEST_ACME_Server: "https://localhost:9000/acme/acme/directory" CA_ECDSA: "Smallstep Intermediate CA" CA: "Smallstep Intermediate CA" CA_EMAIL: "" TEST_PREFERRED_CHAIN: "" NO_REVOKE: 1 - TEST_ACME_Server: "https://localhost:9000/acme/acme/directory" CA_ECDSA: "Smallstep Intermediate CA" CA: "Smallstep Intermediate CA" CA_EMAIL: "" TEST_PREFERRED_CHAIN: "" NO_REVOKE: 1 TEST_IPCERT: 1 TestingDomain: "172.17.0.1" runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} NO_ECC_384: ${{ matrix.NO_ECC_384 }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} NO_REVOKE: ${{ matrix.NO_REVOKE }} TEST_IPCERT: ${{ matrix.TEST_IPCERT }} TestingDomain: ${{ matrix.TestingDomain }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - uses: actions/checkout@v4 - name: Install tools run: sudo apt-get install -y socat wget - name: Start StepCA if: ${{ matrix.TEST_ACME_Server=='https://localhost:9000/acme/acme/directory' }} run: | docker run --rm -d \ -p 9000:9000 \ -e "DOCKER_STEPCA_INIT_NAME=Smallstep" \ -e "DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)" \ -e "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true" \ -e "DOCKER_STEPCA_INIT_PASSWORD=test" \ --name stepca \ smallstep/step-ca:0.23.1 sleep 5 docker exec stepca bash -c "echo test >test" \ && docker exec stepca step ca provisioner add acme --type ACME --admin-subject step --admin-password-file=/home/step/test \ && docker exec stepca kill -1 1 \ && docker exec stepca cat /home/step/certs/root_ca.crt | sudo bash -c "cat - >>/etc/ssl/certs/ca-certificates.crt" - name: Clone acmetest run: | cd .. \ && git clone --depth=1 https://github.com/acmesh-official/acmetest.git \ && cp -r acme.sh acmetest/ - name: Run acmetest run: | cd ../acmetest \ && sudo --preserve-env ./letest.sh acme.sh-3.1.0/.github/workflows/Windows.yml000066400000000000000000000043521472032365200206130ustar00rootroot00000000000000name: Windows on: push: branches: - '*' paths: - '*.sh' - '.github/workflows/Windows.yml' pull_request: branches: - dev paths: - '*.sh' - '.github/workflows/Windows.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: Windows: strategy: matrix: include: - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" TEST_PREFERRED_CHAIN: (STAGING) #- TEST_ACME_Server: "ZeroSSL.com" # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: windows-latest env: TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} CA_ECDSA: ${{ matrix.CA_ECDSA }} CA: ${{ matrix.CA }} CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_LOCAL: 1 #The 80 port is used by Windows server, we have to use a custom port, tunnel will also use this port. Le_HTTPPort: 8888 TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} steps: - name: Set git to use LF run: | git config --global core.autocrlf false - uses: actions/checkout@v4 - name: Install cygwin base packages with chocolatey run: | choco config get cacheLocation choco install --no-progress cygwin shell: cmd - name: Install cygwin additional packages run: | C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,xxd shell: cmd - name: Set ENV shell: cmd run: | echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin;%PATH% >> %GITHUB_ENV% - name: Check ENV shell: cmd run: | echo "PATH=%PATH%" - name: Clone acmetest shell: cmd run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest shell: cmd run: cd ../acmetest && bash.exe -c ./letest.sh acme.sh-3.1.0/.github/workflows/dockerhub.yml000066400000000000000000000047031472032365200211270ustar00rootroot00000000000000 name: Build DockerHub on: push: branches: - '*' tags: - '*' paths: - '**.sh' - "Dockerfile" - '.github/workflows/dockerhub.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: DOCKER_IMAGE: neilpang/acme.sh jobs: CheckToken: runs-on: ubuntu-latest outputs: hasToken: ${{ steps.step_one.outputs.hasToken }} env: DOCKER_PASSWORD : ${{ secrets.DOCKER_PASSWORD }} steps: - name: Set the value id: step_one run: | if [ "$DOCKER_PASSWORD" ] ; then echo "hasToken=true" >>$GITHUB_OUTPUT else echo "hasToken=false" >>$GITHUB_OUTPUT fi - name: Check the value run: echo ${{ steps.step_one.outputs.hasToken }} build: runs-on: ubuntu-latest needs: CheckToken if: "contains(needs.CheckToken.outputs.hasToken, 'true')" steps: - name: checkout code uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Extract Docker metadata id: meta uses: docker/metadata-action@v5.5.1 with: images: ${DOCKER_IMAGE} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 - name: login to docker hub run: | echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - name: build and push the image run: | if [[ $GITHUB_REF == refs/tags/* ]]; then DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/} fi if [[ $GITHUB_REF == refs/heads/* ]]; then DOCKER_IMAGE_TAG=${GITHUB_REF#refs/heads/} if [[ $DOCKER_IMAGE_TAG == master ]]; then DOCKER_IMAGE_TAG=latest AUTO_UPGRADE=1 fi fi DOCKER_LABELS=() while read -r label; do DOCKER_LABELS+=(--label "${label}") done <<<"${DOCKER_METADATA_OUTPUT_LABELS}" docker buildx build \ --tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \ "${DOCKER_LABELS[@]}" \ --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,linux/ppc64le,linux/s390x . acme.sh-3.1.0/.github/workflows/issue.yml000066400000000000000000000012041472032365200203020ustar00rootroot00000000000000name: "Update issues" on: issues: types: [opened] jobs: comment: runs-on: ubuntu-latest steps: - uses: actions/github-script@v6 with: script: | github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: "Please upgrade to the latest code and try again first. Maybe it's already fixed. ```acme.sh --upgrade``` If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you." })acme.sh-3.1.0/.github/workflows/pr_dns.yml000066400000000000000000000020611472032365200204410ustar00rootroot00000000000000name: Check dns api on: pull_request_target: types: - opened paths: - 'dnsapi/*.sh' jobs: welcome: runs-on: ubuntu-latest steps: - uses: actions/github-script@v6 with: script: | await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `**Welcome** First thing: don't send PR to the master branch, please send to the dev branch instead. Please make sure you've read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test). Then reply on this message, otherwise, your code will not be reviewed or merged. We look forward to reviewing your Pull request shortly ✨ 注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试. ` }) acme.sh-3.1.0/.github/workflows/pr_notify.yml000066400000000000000000000015011472032365200211630ustar00rootroot00000000000000name: Check notify api on: pull_request_target: types: - opened branches: - 'dev' paths: - 'notify/*.sh' jobs: welcome: runs-on: ubuntu-latest steps: - uses: actions/github-script@v6 with: script: | await github.rest.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: `**Welcome** Please make sure you've read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify). Then reply on this message, otherwise, your code will not be reviewed or merged. We look forward to reviewing your Pull request shortly ✨ ` }) acme.sh-3.1.0/.github/workflows/shellcheck.yml000066400000000000000000000017251472032365200212670ustar00rootroot00000000000000name: Shellcheck on: push: branches: - '*' paths: - '**.sh' - '.github/workflows/shellcheck.yml' pull_request: branches: - dev paths: - '**.sh' - '.github/workflows/shellcheck.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: ShellCheck: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install Shellcheck run: sudo apt-get install -y shellcheck - name: DoShellcheck run: shellcheck -V && shellcheck -e SC2181 -e SC2089 **/*.sh && echo "shellcheck OK" shfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install shfmt run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt - name: shfmt run: ~/shfmt -l -w -i 2 . ; git diff --exit-code && echo "shfmt OK" acme.sh-3.1.0/Dockerfile000066400000000000000000000027701472032365200150350ustar00rootroot00000000000000FROM alpine:3.17 RUN apk --no-cache add -f \ openssl \ openssh-client \ coreutils \ bind-tools \ curl \ sed \ socat \ tzdata \ oath-toolkit-oathtool \ tar \ libidn \ jq \ cronie ENV LE_CONFIG_HOME /acme.sh ARG AUTO_UPGRADE=1 ENV AUTO_UPGRADE $AUTO_UPGRADE #Install COPY ./ /install_acme.sh/ RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/ RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null#> /proc/1/fd/1 2>/proc/1/fd/2#' | crontab - RUN for verb in help \ version \ install \ uninstall \ upgrade \ issue \ signcsr \ deploy \ install-cert \ renew \ renew-all \ revoke \ remove \ list \ info \ showcsr \ install-cronjob \ uninstall-cronjob \ cron \ toPkcs \ toPkcs8 \ update-account \ register-account \ create-account-key \ create-domain-key \ createCSR \ deactivate \ deactivate-account \ set-notify \ set-default-ca \ set-default-chain \ ; do \ printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \ ; done RUN printf "%b" '#!'"/usr/bin/env sh\n \ if [ \"\$1\" = \"daemon\" ]; then \n \ exec crond -n -s -m off \n \ else \n \ exec -- \"\$@\"\n \ fi\n" >/entry.sh && chmod +x /entry.sh VOLUME /acme.sh ENTRYPOINT ["/entry.sh"] CMD ["--help"] acme.sh-3.1.0/LICENSE.md000066400000000000000000001045151472032365200144470ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . acme.sh-3.1.0/README.md000066400000000000000000000536671472032365200143350ustar00rootroot00000000000000# An ACME Shell script: acme.sh [![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml) [![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml) [![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml) [![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml) [![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml) [![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml) [![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml) [![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml) [![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml) ![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg) ![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg) ![DockerHub](https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg) [![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Docker stars](https://img.shields.io/docker/stars/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") [![Docker pulls](https://img.shields.io/docker/pulls/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") - An ACME protocol client written purely in Shell (Unix shell) language. - Full ACME protocol implementation. - Support ECDSA certs - Support SAN and wildcard certs - Simple, powerful and very easy to use. You only need 3 minutes to learn it. - Bash, dash and sh compatible. - Purely written in Shell with no dependencies on python. - Just one script to issue, renew and install your certificates automatically. - DOES NOT require `root/sudoer` access. - Docker ready - IPv6 ready - Cron job notifications for renewal or error etc. It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates. Wiki: https://github.com/acmesh-official/acme.sh/wiki For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker) Twitter: [@neilpangxa](https://twitter.com/neilpangxa) # [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E) # Who: - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) - [ruby-china.org](https://ruby-china.org/topics/31983) - [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management) - [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) - [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html) - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) - [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) - [CentOS Web Panel](https://control-webpanel.com) - [lnmp.org](https://lnmp.org/) - [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials) # Tested OS | NO | Status| Platform| |----|-------|---------| |1|[![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)|Mac OSX |2|[![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)|Windows (cygwin with curl, openssl and crontab included) |3|[![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)|FreeBSD |4|[![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)|Solaris |5|[![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)| Ubuntu |6|NA|pfsense |7|[![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)|OpenBSD |8|[![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)|NetBSD |9|[![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)|DragonFlyBSD |10|[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios |11|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian |12|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|CentOS |13|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE |14|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl) |15|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux |16|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora |17|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux |18|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux |19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia |10|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux |11|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|ClearLinux |22|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 |23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) |24|[![](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) Check our [testing project](https://github.com/acmesh-official/acmetest): https://github.com/acmesh-official/acmetest # Supported CA - [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)(default) - Letsencrypt.org CA - [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA) - [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) - [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) - [Pebble strict Mode](https://github.com/letsencrypt/pebble) - Any other [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA # Supported modes - Webroot mode - Standalone mode - Standalone tls-alpn mode - Apache mode - Nginx mode - DNS mode - [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) - [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) # 1. How to install ### 1. Install online Check this project: https://github.com/acmesh-official/get.acme.sh ```bash curl https://get.acme.sh | sh -s email=my@example.com ``` Or: ```bash wget -O - https://get.acme.sh | sh -s email=my@example.com ``` ### 2. Or, Install from git Clone this project and launch installation: ```bash git clone https://github.com/acmesh-official/acme.sh.git cd ./acme.sh ./acme.sh --install -m my@example.com ``` You `don't have to be root` then, although `it is recommended`. Advanced Installation: https://github.com/acmesh-official/acme.sh/wiki/How-to-install The installer will perform 3 actions: 1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. All certs will be placed in this folder too. 2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. 3. Create daily cron job to check and renew the certs if needed. Cron entry example: ```bash 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null ``` After the installation, you must close the current terminal and reopen it to make the alias take effect. Ok, you are ready to issue certs now. Show help message: ```sh root@v1:~# acme.sh -h ``` # 2. Just issue a cert **Example 1:** Single domain. ```bash acme.sh --issue -d example.com -w /home/wwwroot/example.com ``` or: ```bash acme.sh --issue -d example.com -w /home/username/public_html ``` or: ```bash acme.sh --issue -d example.com -w /var/www/html ``` **Example 2:** Multiple domains in the same cert. ```bash acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com ``` The parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder. Second argument **"example.com"** is the main domain you want to issue the cert for. You must have at least one domain there. You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. The certs will be placed in `~/.acme.sh/example.com/` The certs will be renewed automatically every **60** days. More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert # 3. Install the cert to Apache/Nginx etc. After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers. You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future. **Apache** example: ```bash acme.sh --install-cert -d example.com \ --cert-file /path/to/certfile/in/apache/cert.pem \ --key-file /path/to/keyfile/in/apache/key.pem \ --fullchain-file /path/to/fullchain/certfile/apache/fullchain.pem \ --reloadcmd "service apache2 force-reload" ``` **Nginx** example: ```bash acme.sh --install-cert -d example.com \ --key-file /path/to/keyfile/in/nginx/key.pem \ --fullchain-file /path/to/fullchain/nginx/cert.pem \ --reloadcmd "service nginx force-reload" ``` Only the domain is required, all the other parameters are optional. The ownership and permission info of existing files are preserved. You can pre-create the files to define the ownership and permission. Install/copy the cert/key to the production Apache or Nginx path. The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. **Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.** # 4. Use Standalone server to issue cert **(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com ``` More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert # 5. Use Standalone ssl server to issue cert **(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com ``` More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert # 6. Use Apache mode **(requires you to be root/sudoer, since it is required to interact with Apache server)** If you are running a web server, it is recommended to use the `Webroot mode`. Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder. Just set string "apache" as the second argument and it will force use of apache plugin automatically. ```sh acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com ``` **This apache mode is only to issue the cert, it will not change your apache config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your apache server, don't worry.** More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert # 7. Use Nginx mode **(requires you to be root/sudoer, since it is required to interact with Nginx server)** If you are running a web server, it is recommended to use the `Webroot mode`. Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder. Just set string "nginx" as the second argument. It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version. So, the config is not changed. ```sh acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com ``` **This nginx mode is only to issue the cert, it will not change your nginx config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your nginx server, don't worry.** More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert # 8. Automatic DNS API integration If your DNS provider supports API access, we can use that API to automatically issue the certs. You don't have to do anything manually! ### Currently acme.sh supports most of the dns providers: https://github.com/acmesh-official/acme.sh/wiki/dnsapi # 9. Use DNS manual mode: See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first. If your dns provider doesn't support any api access, you can add the txt record by hand. ```bash acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com ``` You should get an output like below: ```sh Add the following txt record: Domain:_acme-challenge.example.com Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c Add the following txt record: Domain:_acme-challenge.www.example.com Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Please add those txt records to the domains. Waiting for the dns to take effect. ``` Then just rerun with `renew` argument: ```bash acme.sh --renew -d example.com ``` Ok, it's done. **Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.** **Please use dns api mode instead.** # 10. Issue ECC certificates Just set the `keylength` parameter with a prefix `ec-`. For example: ### Single domain ECC certificate ```bash acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 ``` ### SAN multi domain ECC certificate ```bash acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 ``` Please look at the `keylength` parameter above. Valid values are: 1. **ec-256 (prime256v1, "ECDSA P-256", which is the default key type)** 2. **ec-384 (secp384r1, "ECDSA P-384")** 3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** 4. **2048 (RSA2048)** 5. **3072 (RSA3072)** 6. **4096 (RSA4096)** # 11. 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 ``` # 12. 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. However, you can also force to renew a cert: ```sh acme.sh --renew -d example.com --force ``` or, for ECC cert: ```sh acme.sh --renew -d example.com --force --ecc ``` # 13. How to stop cert renewal To stop renewal of a cert, you can execute the following to remove the cert from the renewal list: ```sh acme.sh --remove -d example.com [--ecc] ``` 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` acme.sh is in constant development, so it's strongly recommended to use the latest code. You can update acme.sh to the latest code: ```sh acme.sh --upgrade ``` You can also enable auto upgrade: ```sh acme.sh --upgrade --auto-upgrade ``` Then **acme.sh** will be kept up to date automatically. Disable auto upgrade: ```sh acme.sh --upgrade --auto-upgrade 0 ``` # 15. Issue a cert from an existing CSR https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR # 16. Send notifications in cronjob https://github.com/acmesh-official/acme.sh/wiki/notify # 17. Under the Hood Speak ACME language using shell, directly to "Let's Encrypt". TODO: # 18. Acknowledgments 1. Acme-tiny: https://github.com/diafygi/acme-tiny 2. ACME protocol: https://github.com/ietf-wg-acme/acme ## Contributors ### Code Contributors This project exists thanks to all the people who contribute. ### Financial Contributors Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)] #### Individuals #### Organizations Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)] # 19. License & Others License is GPLv3 Please Star and Fork me. [Issues](https://github.com/acmesh-official/acme.sh/issues) and [pull requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome. # 20. Donate Your donation makes **acme.sh** better: 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list) acme.sh-3.1.0/acme.sh000077500000000000000000006717211472032365200143170ustar00rootroot00000000000000#!/usr/bin/env sh VER=3.1.0 PROJECT_NAME="acme.sh" PROJECT_ENTRY="acme.sh" PROJECT="https://github.com/acmesh-official/$PROJECT_NAME" DEFAULT_INSTALL_HOME="$HOME/.$PROJECT_NAME" _WINDOWS_SCHEDULER_NAME="$PROJECT_NAME.cron" _SCRIPT_="$0" _SUB_FOLDER_NOTIFY="notify" _SUB_FOLDER_DNSAPI="dnsapi" _SUB_FOLDER_DEPLOY="deploy" _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY" CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory" CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory" CA_BUYPASS="https://api.buypass.com/acme/directory" CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory" CA_ZEROSSL="https://acme.zerossl.com/v2/DV90" _ZERO_EAB_ENDPOINT="https://api.zerossl.com/acme/eab-credentials-email" CA_SSLCOM_RSA="https://acme.ssl.com/sslcom-dv-rsa" CA_SSLCOM_ECC="https://acme.ssl.com/sslcom-dv-ecc" CA_GOOGLE="https://dv.acme-v02.api.pki.goog/directory" CA_GOOGLE_TEST="https://dv.acme-v02.test-api.pki.goog/directory" DEFAULT_CA=$CA_ZEROSSL DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST CA_NAMES=" ZeroSSL.com,zerossl LetsEncrypt.org,letsencrypt LetsEncrypt.org_test,letsencrypt_test,letsencrypttest BuyPass.com,buypass BuyPass.com_test,buypass_test,buypasstest SSL.com,sslcom Google.com,google Google.com_test,googletest,google_test " CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST" DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)" DEFAULT_ACCOUNT_KEY_LENGTH=ec-256 DEFAULT_DOMAIN_KEY_LENGTH=ec-256 DEFAULT_OPENSSL_BIN="openssl" VTYPE_HTTP="http-01" VTYPE_DNS="dns-01" VTYPE_ALPN="tls-alpn-01" ID_TYPE_DNS="dns" ID_TYPE_IP="ip" LOCAL_ANY_ADDRESS="0.0.0.0" DEFAULT_RENEW=60 NO_VALUE="no" W_DNS="dns" W_ALPN="alpn" DNS_ALIAS_PREFIX="=" MODE_STATELESS="stateless" STATE_VERIFIED="verified_ok" NGINX="nginx:" NGINX_START="#ACME_NGINX_START" NGINX_END="#ACME_NGINX_END" BEGIN_CSR="-----BEGIN [NEW ]\{0,4\}CERTIFICATE REQUEST-----" END_CSR="-----END [NEW ]\{0,4\}CERTIFICATE REQUEST-----" BEGIN_CERT="-----BEGIN CERTIFICATE-----" END_CERT="-----END CERTIFICATE-----" CONTENT_TYPE_JSON="application/jose+json" RENEW_SKIP=2 CODE_DNS_MANUAL=3 B64CONF_START="__ACME_BASE64__START_" B64CONF_END="__ACME_BASE64__END_" ECC_SEP="_" ECC_SUFFIX="${ECC_SEP}ecc" LOG_LEVEL_1=1 LOG_LEVEL_2=2 LOG_LEVEL_3=3 DEFAULT_LOG_LEVEL="$LOG_LEVEL_2" DEBUG_LEVEL_1=1 DEBUG_LEVEL_2=2 DEBUG_LEVEL_3=3 DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_2 DEBUG_LEVEL_NONE=0 DOH_CLOUDFLARE=1 DOH_GOOGLE=2 DOH_ALI=3 DOH_DP=4 HIDDEN_VALUE="[hidden](please add '--output-insecure' to see this value)" SYSLOG_ERROR="user.error" SYSLOG_INFO="user.info" SYSLOG_DEBUG="user.debug" #error SYSLOG_LEVEL_ERROR=3 #info SYSLOG_LEVEL_INFO=6 #debug SYSLOG_LEVEL_DEBUG=7 #debug2 SYSLOG_LEVEL_DEBUG_2=8 #debug3 SYSLOG_LEVEL_DEBUG_3=9 SYSLOG_LEVEL_DEFAULT=$SYSLOG_LEVEL_ERROR #none SYSLOG_LEVEL_NONE=0 NOTIFY_LEVEL_DISABLE=0 NOTIFY_LEVEL_ERROR=1 NOTIFY_LEVEL_RENEW=2 NOTIFY_LEVEL_SKIP=3 NOTIFY_LEVEL_DEFAULT=$NOTIFY_LEVEL_RENEW NOTIFY_MODE_BULK=0 NOTIFY_MODE_CERT=1 NOTIFY_MODE_DEFAULT=$NOTIFY_MODE_BULK _BASE64_ENCODED_CFGS="Le_PreHook Le_PostHook Le_RenewHook Le_Preferred_Chain Le_ReloadCmd" _DEBUG_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh" _PREPARE_LINK="https://github.com/acmesh-official/acme.sh/wiki/Install-preparations" _STATELESS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode" _DNS_ALIAS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode" _DNS_MANUAL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode" _DNS_API_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnsapi" _NOTIFY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/notify" _SUDO_WIKI="https://github.com/acmesh-official/acme.sh/wiki/sudo" _REVOKE_WIKI="https://github.com/acmesh-official/acme.sh/wiki/revokecert" _ZEROSSL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA" _SSLCOM_WIKI="https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA" _SERVER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Server" _PREFERRED_CHAIN_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Preferred-Chain" _VALIDITY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Validity" _DNSCHECK_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnscheck" _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:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then printf '\33[1;32m%b\33[0m' "$1" return fi printf -- "%b" "$1" } __red() { if [ "${__INTERACTIVE}${ACME_NO_COLOR:-0}" = "10" -o "${ACME_FORCE_COLOR}" = "1" ]; then printf '\33[1;31m%b\33[0m' "$1" return fi printf -- "%b" "$1" } _printargs() { _exitstatus="$?" if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then printf -- "%s" "[$(date)] " fi if [ -z "$2" ]; then printf -- "%s" "$1" else printf -- "%s" "$1='$2'" fi printf "\n" # return the saved exit status return "$_exitstatus" } _dlg_versions() { echo "Diagnosis versions: " echo "openssl:$ACME_OPENSSL_BIN" if _exists "${ACME_OPENSSL_BIN:-openssl}"; then ${ACME_OPENSSL_BIN:-openssl} version 2>&1 else echo "$ACME_OPENSSL_BIN doesn't exist." fi echo "Apache:" if [ "$_APACHECTL" ] && _exists "$_APACHECTL"; then $_APACHECTL -V 2>&1 else echo "Apache doesn't exist." fi echo "nginx:" if _exists "nginx"; then nginx -V 2>&1 else echo "nginx doesn't exist." fi echo "socat:" if _exists "socat"; then socat -V 2>&1 else _debug "socat doesn't exist." fi } #class _syslog() { _exitstatus="$?" if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" = "$SYSLOG_LEVEL_NONE" ]; then return fi _logclass="$1" shift if [ -z "$__logger_i" ]; then if _contains "$(logger --help 2>&1)" "-i"; then __logger_i="logger -i" else __logger_i="logger" fi fi $__logger_i -t "$PROJECT_NAME" -p "$_logclass" "$(_printargs "$@")" >/dev/null 2>&1 return "$_exitstatus" } _log() { [ -z "$LOG_FILE" ] && return _printargs "$@" >>"$LOG_FILE" } _info() { _log "$@" if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_INFO" ]; then _syslog "$SYSLOG_INFO" "$@" fi _printargs "$@" } _err() { _syslog "$SYSLOG_ERROR" "$@" _log "$@" if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then printf -- "%s" "[$(date)] " >&2 fi if [ -z "$2" ]; then __red "$1" >&2 else __red "$1='$2'" >&2 fi printf "\n" >&2 return 1 } _usage() { __red "$@" >&2 printf "\n" >&2 } __debug_bash_helper() { # At this point only do for --debug 3 if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -lt "$DEBUG_LEVEL_3" ]; then return fi # Return extra debug info when running with bash, otherwise return empty # string. if [ -z "${BASH_VERSION}" ]; then return fi # We are a bash shell at this point, return the filename, function name, and # line number as a string _dbh_saveIFS=$IFS IFS=" " # Must use eval or syntax error happens under dash. The eval should use # single quotes as older versions of busybox had a bug with double quotes and # eval. # Use 'caller 1' as we want one level up the stack as we should be called # by one of the _debug* functions eval '_dbh_called=($(caller 1))' IFS=$_dbh_saveIFS eval '_dbh_file=${_dbh_called[2]}' if [ -n "${_script_home}" ]; then # Trim off the _script_home directory name eval '_dbh_file=${_dbh_file#$_script_home/}' fi eval '_dbh_function=${_dbh_called[1]}' eval '_dbh_lineno=${_dbh_called[0]}' printf "%-40s " "$_dbh_file:${_dbh_function}:${_dbh_lineno}" } _debug() { if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then _log "$@" fi if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG" ]; then _syslog "$SYSLOG_DEBUG" "$@" fi if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then _bash_debug=$(__debug_bash_helper) _printargs "${_bash_debug}$@" >&2 fi } #output the sensitive messages _secure_debug() { if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_1" ]; then if [ "$OUTPUT_INSECURE" = "1" ]; then _log "$@" else _log "$1" "$HIDDEN_VALUE" fi fi if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG" ]; then _syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE" fi if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_1" ]; then if [ "$OUTPUT_INSECURE" = "1" ]; then _printargs "$@" >&2 else _printargs "$1" "$HIDDEN_VALUE" >&2 fi fi } _debug2() { if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_2" ]; then _log "$@" fi if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_2" ]; then _syslog "$SYSLOG_DEBUG" "$@" fi if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then _bash_debug=$(__debug_bash_helper) _printargs "${_bash_debug}$@" >&2 fi } _secure_debug2() { if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_2" ]; then if [ "$OUTPUT_INSECURE" = "1" ]; then _log "$@" else _log "$1" "$HIDDEN_VALUE" fi fi if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_2" ]; then _syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE" fi if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then if [ "$OUTPUT_INSECURE" = "1" ]; then _printargs "$@" >&2 else _printargs "$1" "$HIDDEN_VALUE" >&2 fi fi } _debug3() { if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_3" ]; then _log "$@" fi if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_3" ]; then _syslog "$SYSLOG_DEBUG" "$@" fi if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then _bash_debug=$(__debug_bash_helper) _printargs "${_bash_debug}$@" >&2 fi } _secure_debug3() { if [ "${LOG_LEVEL:-$DEFAULT_LOG_LEVEL}" -ge "$LOG_LEVEL_3" ]; then if [ "$OUTPUT_INSECURE" = "1" ]; then _log "$@" else _log "$1" "$HIDDEN_VALUE" fi fi if [ "${SYS_LOG:-$SYSLOG_LEVEL_NONE}" -ge "$SYSLOG_LEVEL_DEBUG_3" ]; then _syslog "$SYSLOG_DEBUG" "$1" "$HIDDEN_VALUE" fi if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_3" ]; then if [ "$OUTPUT_INSECURE" = "1" ]; then _printargs "$@" >&2 else _printargs "$1" "$HIDDEN_VALUE" >&2 fi fi } _upper_case() { # shellcheck disable=SC2018,SC2019 tr '[a-z]' '[A-Z]' } _lower_case() { # shellcheck disable=SC2018,SC2019 tr '[A-Z]' '[a-z]' } _startswith() { _str="$1" _sub="$2" echo "$_str" | grep -- "^$_sub" >/dev/null 2>&1 } _endswith() { _str="$1" _sub="$2" echo "$_str" | grep -- "$_sub\$" >/dev/null 2>&1 } _contains() { _str="$1" _sub="$2" echo "$_str" | grep -- "$_sub" >/dev/null 2>&1 } _hasfield() { _str="$1" _field="$2" _sep="$3" if [ -z "$_field" ]; then _usage "Usage: str field [sep]" return 1 fi if [ -z "$_sep" ]; then _sep="," fi for f in $(echo "$_str" | tr "$_sep" ' '); do if [ "$f" = "$_field" ]; then _debug2 "'$_str' contains '$_field'" return 0 #contains ok fi done _debug2 "'$_str' does not contain '$_field'" return 1 #not contains } # str index [sep] _getfield() { _str="$1" _findex="$2" _sep="$3" if [ -z "$_findex" ]; then _usage "Usage: str field [sep]" return 1 fi if [ -z "$_sep" ]; then _sep="," fi _ffi="$_findex" while [ "$_ffi" -gt "0" ]; do _fv="$(echo "$_str" | cut -d "$_sep" -f "$_ffi")" if [ "$_fv" ]; then printf -- "%s" "$_fv" return 0 fi _ffi="$(_math "$_ffi" - 1)" done printf -- "%s" "$_str" } _exists() { cmd="$1" if [ -z "$cmd" ]; then _usage "Usage: _exists cmd" return 1 fi if eval type type >/dev/null 2>&1; then eval type "$cmd" >/dev/null 2>&1 elif command >/dev/null 2>&1; then command -v "$cmd" >/dev/null 2>&1 else which "$cmd" >/dev/null 2>&1 fi ret="$?" _debug3 "$cmd exists=$ret" return $ret } #a + b _math() { _m_opts="$@" printf "%s" "$(($_m_opts))" } _h_char_2_dec() { _ch=$1 case "${_ch}" in a | A) printf "10" ;; b | B) printf "11" ;; c | C) printf "12" ;; d | D) printf "13" ;; e | E) printf "14" ;; f | F) printf "15" ;; *) printf "%s" "$_ch" ;; esac } _URGLY_PRINTF="" if [ "$(printf '\x41')" != 'A' ]; then _URGLY_PRINTF=1 fi _ESCAPE_XARGS="" if _exists xargs && [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then _ESCAPE_XARGS=1 fi _h2b() { if _exists xxd; then if _contains "$(xxd --help 2>&1)" "assumes -c30"; then if xxd -r -p -c 9999 2>/dev/null; then return fi else if xxd -r -p 2>/dev/null; then return fi fi fi hex=$(cat) ic="" jc="" _debug2 _URGLY_PRINTF "$_URGLY_PRINTF" if [ -z "$_URGLY_PRINTF" ]; then if [ "$_ESCAPE_XARGS" ] && _exists xargs; then _debug2 "xargs" echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/g' | xargs printf else for h in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/ \1/g'); do if [ -z "$h" ]; then break fi printf "\x$h%s" done fi else for c in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\)/ \1/g'); do if [ -z "$ic" ]; then ic=$c continue fi jc=$c ic="$(_h_char_2_dec "$ic")" jc="$(_h_char_2_dec "$jc")" printf '\'"$(printf "%o" "$(_math "$ic" \* 16 + $jc)")""%s" ic="" jc="" done fi } _is_solaris() { _contains "${__OS__:=$(uname -a)}" "solaris" || _contains "${__OS__:=$(uname -a)}" "SunOS" } #_ascii_hex str #this can only process ascii chars, should only be used when od command is missing as a backup way. _ascii_hex() { _debug2 "Using _ascii_hex" _str="$1" _str_len=${#_str} _h_i=1 while [ "$_h_i" -le "$_str_len" ]; do _str_c="$(printf "%s" "$_str" | cut -c "$_h_i")" printf " %02x" "'$_str_c" _h_i="$(_math "$_h_i" + 1)" done } #stdin output hexstr splited by one space #input:"abc" #output: " 61 62 63" _hex_dump() { if _exists od; then od -A n -v -t x1 | tr -s " " | sed 's/ $//' | tr -d "\r\t\n" elif _exists hexdump; then _debug3 "using hexdump" hexdump -v -e '/1 ""' -e '/1 " %02x" ""' elif _exists xxd; then _debug3 "using xxd" xxd -ps -c 20 -i | sed "s/ 0x/ /g" | tr -d ",\n" | tr -s " " else _debug3 "using _ascii_hex" str=$(cat) _ascii_hex "$str" fi } #url encode, no-preserved chars #A B C D E F G H I J K L M N O P Q R S T U V W X Y Z #41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a #a b c d e f g h i j k l m n o p q r s t u v w x y z #61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a #0 1 2 3 4 5 6 7 8 9 - _ . ~ #30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e #_url_encode [upper-hex] the encoded hex will be upper-case if the argument upper-hex is followed #stdin stdout _url_encode() { _upper_hex=$1 _hex_str=$(_hex_dump) _debug3 "_url_encode" _debug3 "_hex_str" "$_hex_str" for _hex_code in $_hex_str; do #upper case case "${_hex_code}" in "41") printf "%s" "A" ;; "42") printf "%s" "B" ;; "43") printf "%s" "C" ;; "44") printf "%s" "D" ;; "45") printf "%s" "E" ;; "46") printf "%s" "F" ;; "47") printf "%s" "G" ;; "48") printf "%s" "H" ;; "49") printf "%s" "I" ;; "4a") printf "%s" "J" ;; "4b") printf "%s" "K" ;; "4c") printf "%s" "L" ;; "4d") printf "%s" "M" ;; "4e") printf "%s" "N" ;; "4f") printf "%s" "O" ;; "50") printf "%s" "P" ;; "51") printf "%s" "Q" ;; "52") printf "%s" "R" ;; "53") printf "%s" "S" ;; "54") printf "%s" "T" ;; "55") printf "%s" "U" ;; "56") printf "%s" "V" ;; "57") printf "%s" "W" ;; "58") printf "%s" "X" ;; "59") printf "%s" "Y" ;; "5a") printf "%s" "Z" ;; #lower case "61") printf "%s" "a" ;; "62") printf "%s" "b" ;; "63") printf "%s" "c" ;; "64") printf "%s" "d" ;; "65") printf "%s" "e" ;; "66") printf "%s" "f" ;; "67") printf "%s" "g" ;; "68") printf "%s" "h" ;; "69") printf "%s" "i" ;; "6a") printf "%s" "j" ;; "6b") printf "%s" "k" ;; "6c") printf "%s" "l" ;; "6d") printf "%s" "m" ;; "6e") printf "%s" "n" ;; "6f") printf "%s" "o" ;; "70") printf "%s" "p" ;; "71") printf "%s" "q" ;; "72") printf "%s" "r" ;; "73") printf "%s" "s" ;; "74") printf "%s" "t" ;; "75") printf "%s" "u" ;; "76") printf "%s" "v" ;; "77") printf "%s" "w" ;; "78") printf "%s" "x" ;; "79") printf "%s" "y" ;; "7a") printf "%s" "z" ;; #numbers "30") printf "%s" "0" ;; "31") printf "%s" "1" ;; "32") printf "%s" "2" ;; "33") printf "%s" "3" ;; "34") printf "%s" "4" ;; "35") printf "%s" "5" ;; "36") printf "%s" "6" ;; "37") printf "%s" "7" ;; "38") printf "%s" "8" ;; "39") printf "%s" "9" ;; "2d") printf "%s" "-" ;; "5f") printf "%s" "_" ;; "2e") printf "%s" "." ;; "7e") printf "%s" "~" ;; #other hex *) if [ "$_upper_hex" = "upper-hex" ]; then _hex_code=$(printf "%s" "$_hex_code" | _upper_case) fi printf '%%%s' "$_hex_code" ;; esac done } _json_encode() { _j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")" _debug3 "_json_encode" _debug3 "_j_str" "$_j_str" echo "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n" } #from: http:\/\/ to http:// _json_decode() { _j_str="$(sed 's#\\/#/#g')" _debug3 "_json_decode" _debug3 "_j_str" "$_j_str" echo "$_j_str" } #options file _sed_i() { options="$1" filename="$2" if [ -z "$filename" ]; then _usage "Usage:_sed_i options filename" return 1 fi _debug2 options "$options" if sed -h 2>&1 | grep "\-i\[SUFFIX]" >/dev/null 2>&1; then _debug "Using sed -i" sed -i "$options" "$filename" else _debug "No -i support in sed" text="$(cat "$filename")" echo "$text" | sed "$options" >"$filename" fi } if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then __USE_EGREP=1 else __USE_EGREP="" fi _egrep_o() { if [ "$__USE_EGREP" ]; then egrep -o -- "$1" 2>/dev/null else sed -n 's/.*\('"$1"'\).*/\1/p' fi } #Usage: file startline endline _getfile() { filename="$1" startline="$2" endline="$3" if [ -z "$endline" ]; then _usage "Usage: file startline endline" return 1 fi i="$(grep -n -- "$startline" "$filename" | cut -d : -f 1)" if [ -z "$i" ]; then _err "Cannot find start line: $startline" return 1 fi i="$(_math "$i" + 1)" _debug i "$i" j="$(grep -n -- "$endline" "$filename" | cut -d : -f 1)" if [ -z "$j" ]; then _err "Cannot find end line: $endline" return 1 fi j="$(_math "$j" - 1)" _debug j "$j" sed -n "$i,${j}p" "$filename" } #Usage: multiline _base64() { [ "" ] #urgly if [ "$1" ]; then _debug3 "base64 multiline:'$1'" ${ACME_OPENSSL_BIN:-openssl} base64 -e else _debug3 "base64 single line." ${ACME_OPENSSL_BIN:-openssl} base64 -e | tr -d '\r\n' fi } #Usage: multiline _dbase64() { if [ "$1" ]; then ${ACME_OPENSSL_BIN:-openssl} base64 -d else ${ACME_OPENSSL_BIN:-openssl} base64 -d -A fi } #file _checkcert() { _cf="$1" if [ "$DEBUG" ]; then ${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in "$_cf" else ${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in "$_cf" >/dev/null 2>&1 fi } #Usage: hashalg [outputhex] #Output Base64-encoded digest _digest() { alg="$1" if [ -z "$alg" ]; then _usage "Usage: _digest hashalg" return 1 fi outputhex="$2" if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then if [ "$outputhex" ]; then ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' ' else ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -binary | _base64 fi else _err "$alg is not supported yet" return 1 fi } #Usage: hashalg secret_hex [outputhex] #Output binary hmac _hmac() { alg="$1" secret_hex="$2" outputhex="$3" if [ -z "$secret_hex" ]; then _usage "Usage: _hmac hashalg secret [outputhex]" return 1 fi if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then if [ "$outputhex" ]; then (${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)") | cut -d = -f 2 | tr -d ' ' else ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary 2>/dev/null || ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hmac "$(printf "%s" "$secret_hex" | _h2b)" -binary fi else _err "$alg is not supported yet" return 1 fi } #Usage: keyfile hashalg #Output: Base64-encoded signature value _sign() { keyfile="$1" alg="$2" if [ -z "$alg" ]; then _usage "Usage: _sign keyfile hashalg" return 1 fi _sign_openssl="${ACME_OPENSSL_BIN:-openssl} dgst -sign $keyfile " if _isRSA "$keyfile" >/dev/null 2>&1; then $_sign_openssl -$alg | _base64 elif _isEcc "$keyfile" >/dev/null 2>&1; then if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then _err "Sign failed: $_sign_openssl" _err "Key file: $keyfile" _err "Key content: $(wc -l <"$keyfile") lines" return 1 fi _debug3 "_signedECText" "$_signedECText" _ec_r="$(echo "$_signedECText" | _head_n 2 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")" _ec_s="$(echo "$_signedECText" | _head_n 3 | _tail_n 1 | cut -d : -f 4 | tr -d "\r\n")" if [ "$__ECC_KEY_LEN" -eq "256" ]; then while [ "${#_ec_r}" -lt "64" ]; do _ec_r="0${_ec_r}" done while [ "${#_ec_s}" -lt "64" ]; do _ec_s="0${_ec_s}" done fi if [ "$__ECC_KEY_LEN" -eq "384" ]; then while [ "${#_ec_r}" -lt "96" ]; do _ec_r="0${_ec_r}" done while [ "${#_ec_s}" -lt "96" ]; do _ec_s="0${_ec_s}" done fi if [ "$__ECC_KEY_LEN" -eq "512" ]; then while [ "${#_ec_r}" -lt "132" ]; do _ec_r="0${_ec_r}" done while [ "${#_ec_s}" -lt "132" ]; do _ec_s="0${_ec_s}" done fi _debug3 "_ec_r" "$_ec_r" _debug3 "_ec_s" "$_ec_s" printf "%s" "$_ec_r$_ec_s" | _h2b | _base64 else _err "Unknown key file format." return 1 fi } #keylength or isEcc flag (empty str => not ecc) _isEccKey() { _length="$1" if [ -z "$_length" ]; then return 1 fi [ "$_length" != "1024" ] && [ "$_length" != "2048" ] && [ "$_length" != "3072" ] && [ "$_length" != "4096" ] && [ "$_length" != "8192" ] } # _createkey 2048|ec-256 file _createkey() { length="$1" f="$2" _debug2 "_createkey for file:$f" eccname="$length" if _startswith "$length" "ec-"; then length=$(printf "%s" "$length" | cut -d '-' -f 2-100) if [ "$length" = "256" ]; then eccname="prime256v1" fi if [ "$length" = "384" ]; then eccname="secp384r1" fi if [ "$length" = "521" ]; then eccname="secp521r1" fi fi if [ -z "$length" ]; then length=2048 fi _debug "Using length $length" if ! [ -e "$f" ]; then if ! touch "$f" >/dev/null 2>&1; then _f_path="$(dirname "$f")" _debug _f_path "$_f_path" if ! mkdir -p "$_f_path"; then _err "Cannot create path: $_f_path" return 1 fi fi if ! touch "$f" >/dev/null 2>&1; then return 1 fi chmod 600 "$f" fi if _isEccKey "$length"; then _debug "Using EC name: $eccname" if _opkey="$(${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -noout -genkey 2>/dev/null)"; then echo "$_opkey" >"$f" else _err "Error encountered for ECC key named $eccname" return 1 fi else _debug "Using RSA: $length" __traditional="" if _contains "$(${ACME_OPENSSL_BIN:-openssl} help genrsa 2>&1)" "-traditional"; then __traditional="-traditional" fi if _opkey="$(${ACME_OPENSSL_BIN:-openssl} genrsa $__traditional "$length" 2>/dev/null)"; then echo "$_opkey" >"$f" else _err "Error encountered for RSA key of length $length" return 1 fi fi if [ "$?" != "0" ]; then _err "Key creation error." return 1 fi } #domain _is_idn() { _is_idn_d="$1" _debug2 _is_idn_d "$_is_idn_d" _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '*.,-_') _debug2 _idn_temp "$_idn_temp" [ "$_idn_temp" ] } #aa.com #aa.com,bb.com,cc.com _idn() { __idn_d="$1" if ! _is_idn "$__idn_d"; then printf "%s" "$__idn_d" return 0 fi if _exists idn; then if _contains "$__idn_d" ','; then _i_first="1" for f in $(echo "$__idn_d" | tr ',' ' '); do [ -z "$f" ] && continue if [ -z "$_i_first" ]; then printf "%s" "," else _i_first="" fi idn --quiet "$f" | tr -d "\r\n" done else idn "$__idn_d" | tr -d "\r\n" fi else _err "Please install idn to process IDN names." fi } #_createcsr cn san_list keyfile csrfile conf acmeValidationv1 _createcsr() { _debug _createcsr domain="$1" domainlist="$2" csrkey="$3" csr="$4" csrconf="$5" acmeValidationv1="$6" _debug2 domain "$domain" _debug2 domainlist "$domainlist" _debug2 csrkey "$csrkey" _debug2 csr "$csr" _debug2 csrconf "$csrconf" printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]" >"$csrconf" if [ "$Le_ExtKeyUse" ]; then _savedomainconf Le_ExtKeyUse "$Le_ExtKeyUse" printf "\nextendedKeyUsage=$Le_ExtKeyUse\n" >>"$csrconf" else printf "\nextendedKeyUsage=serverAuth,clientAuth\n" >>"$csrconf" fi if [ "$acmeValidationv1" ]; then domainlist="$(_idn "$domainlist")" _debug2 domainlist "$domainlist" alt="" for dl in $(echo "$domainlist" | tr "," ' '); do if [ "$alt" ]; then alt="$alt,$(_getIdType "$dl" | _upper_case):$dl" else alt="$(_getIdType "$dl" | _upper_case):$dl" fi done printf -- "\nsubjectAltName=$alt" >>"$csrconf" elif [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then #single domain _info "Single domain" "$domain" printf -- "\nsubjectAltName=$(_getIdType "$domain" | _upper_case):$(_idn "$domain")" >>"$csrconf" else domainlist="$(_idn "$domainlist")" _debug2 domainlist "$domainlist" alt="$(_getIdType "$domain" | _upper_case):$(_idn "$domain")" for dl in $(echo "'$domainlist'" | sed "s/,/' '/g"); do dl=$(echo "$dl" | tr -d "'") alt="$alt,$(_getIdType "$dl" | _upper_case):$dl" done #multi _info "Multi domain" "$alt" printf -- "\nsubjectAltName=$alt" >>"$csrconf" fi if [ "$Le_OCSP_Staple" = "1" ]; then _savedomainconf Le_OCSP_Staple "$Le_OCSP_Staple" printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf" fi if [ "$acmeValidationv1" ]; then printf "\n1.3.6.1.5.5.7.1.31=critical,DER:04:20:${acmeValidationv1}" >>"${csrconf}" fi _csr_cn="$(_idn "$domain")" _debug2 _csr_cn "$_csr_cn" if _contains "$(uname -a)" "MINGW"; then if _isIP "$_csr_cn"; then ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//O=$PROJECT_NAME" -config "$csrconf" -out "$csr" else ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr" fi else if _isIP "$_csr_cn"; then ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/O=$PROJECT_NAME" -config "$csrconf" -out "$csr" else ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" fi fi } #_signcsr key csr conf cert _signcsr() { key="$1" csr="$2" conf="$3" cert="$4" _debug "_signcsr" _msg="$(${ACME_OPENSSL_BIN:-openssl} x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)" _ret="$?" _debug "$_msg" return $_ret } #_csrfile _readSubjectFromCSR() { _csrfile="$1" if [ -z "$_csrfile" ]; then _usage "_readSubjectFromCSR mycsr.csr" return 1 fi ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n' } #_csrfile #echo comma separated domain list _readSubjectAltNamesFromCSR() { _csrfile="$1" if [ -z "$_csrfile" ]; then _usage "_readSubjectAltNamesFromCSR mycsr.csr" return 1 fi _csrsubj="$(_readSubjectFromCSR "$_csrfile")" _debug _csrsubj "$_csrsubj" _dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')" _debug _dnsAltnames "$_dnsAltnames" if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then _debug "AltNames contains subject" _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')" _debug _excapedAlgnames "$_excapedAlgnames" _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')" _debug _escapedSubject "$_escapedSubject" _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" _debug _dnsAltnames "$_dnsAltnames" else _debug "AltNames doesn't contain subject" fi echo "$_dnsAltnames" | sed "s/DNS://g" } #_csrfile _readKeyLengthFromCSR() { _csrfile="$1" if [ -z "$_csrfile" ]; then _usage "_readKeyLengthFromCSR mycsr.csr" return 1 fi _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")" _debug2 _outcsr "$_outcsr" if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then _debug "ECC CSR" echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' ' else _debug "RSA CSR" _rkl="$(echo "$_outcsr" | tr "\t" " " | _egrep_o "^ *Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1)" if [ "$_rkl" ]; then echo "$_rkl" else echo "$_outcsr" | tr "\t" " " | _egrep_o "RSA Public.Key:.*" | cut -d '(' -f 2 | cut -d ' ' -f 1 fi fi } _ss() { _port="$1" if _exists "ss"; then _debug "Using: ss" ss -ntpl 2>/dev/null | grep ":$_port " return 0 fi if _exists "netstat"; then _debug "Using: netstat" if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then #for windows version netstat tool netstat -an -p tcp | grep "LISTENING" | grep ":$_port " else if netstat -help 2>&1 | grep "\-p protocol" >/dev/null; then netstat -an -p tcp | grep LISTEN | grep ":$_port " elif netstat -help 2>&1 | grep -- '-P protocol' >/dev/null; then #for solaris netstat -an -P tcp | grep "\.$_port " | grep "LISTEN" elif netstat -help 2>&1 | grep "\-p" >/dev/null; then #for full linux netstat -ntpl | grep ":$_port " else #for busybox (embedded linux; no pid support) netstat -ntl 2>/dev/null | grep ":$_port " fi fi return 0 fi return 1 } #outfile key cert cacert [password [name [caname]]] _toPkcs() { _cpfx="$1" _ckey="$2" _ccert="$3" _cca="$4" pfxPassword="$5" pfxName="$6" pfxCaname="$7" if [ "$pfxCaname" ]; then ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName" -caname "$pfxCaname" elif [ "$pfxName" ]; then ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" -name "$pfxName" elif [ "$pfxPassword" ]; then ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:$pfxPassword" else ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" fi if [ "$?" = "0" ]; then _savedomainconf "Le_PFXPassword" "$pfxPassword" fi } #domain [password] [isEcc] toPkcs() { domain="$1" pfxPassword="$2" if [ -z "$domain" ]; then _usage "Usage: $PROJECT_ENTRY --to-pkcs12 --domain [--password ] [--ecc]" return 1 fi _isEcc="$3" _initpath "$domain" "$_isEcc" _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$pfxPassword" if [ "$?" = "0" ]; then _info "Success, PFX has been exported to: $CERT_PFX_PATH" fi } #domain [isEcc] toPkcs8() { domain="$1" if [ -z "$domain" ]; then _usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain [--ecc]" return 1 fi _isEcc="$2" _initpath "$domain" "$_isEcc" ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH" if [ "$?" = "0" ]; then _info "Success, $CERT_PKCS8_PATH" fi } #[2048] createAccountKey() { _info "Creating account key" if [ -z "$1" ]; then _usage "Usage: $PROJECT_ENTRY --create-account-key [--accountkeylength ]" return fi length=$1 _create_account_key "$length" } _create_account_key() { length=$1 if [ -z "$length" ] || [ "$length" = "$NO_VALUE" ]; then _debug "Using default length $DEFAULT_ACCOUNT_KEY_LENGTH" length="$DEFAULT_ACCOUNT_KEY_LENGTH" fi _debug length "$length" _initpath mkdir -p "$CA_DIR" if [ -s "$ACCOUNT_KEY_PATH" ]; then _info "Account key exists, skipping" return 0 else #generate account key if _createkey "$length" "$ACCOUNT_KEY_PATH"; then _info "Account key creation OK." return 0 else _err "Account key creation error." return 1 fi fi } #domain [length] createDomainKey() { _info "Creating domain key" if [ -z "$1" ]; then _usage "Usage: $PROJECT_ENTRY --create-domain-key --domain [--keylength ]" return fi domain=$1 _cdl=$2 if [ -z "$_cdl" ]; then _debug "Using DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH" _cdl="$DEFAULT_DOMAIN_KEY_LENGTH" fi _initpath "$domain" "$_cdl" if [ ! -f "$CERT_KEY_PATH" ] || [ ! -s "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$_ACME_IS_RENEW" ]) || [ "$Le_ForceNewDomainKey" = "1" ]; then if _createkey "$_cdl" "$CERT_KEY_PATH"; then _savedomainconf Le_Keylength "$_cdl" _info "The domain key is here: $(__green $CERT_KEY_PATH)" return 0 else _err "Cannot create domain key" return 1 fi else if [ "$_ACME_IS_RENEW" ]; then _info "Domain key exists, skipping" return 0 else _err "Domain key exists, do you want to overwrite it?" _err "If so, add '--force' and try again." return 1 fi fi } # domain domainlist isEcc createCSR() { _info "Creating CSR" if [ -z "$1" ]; then _usage "Usage: $PROJECT_ENTRY --create-csr --domain [--domain ...] [--ecc]" return fi domain="$1" domainlist="$2" _isEcc="$3" _initpath "$domain" "$_isEcc" if [ -f "$CSR_PATH" ] && [ "$_ACME_IS_RENEW" ] && [ -z "$FORCE" ]; then _info "CSR exists, skipping" return fi if [ ! -f "$CERT_KEY_PATH" ]; then _err "This key file was not found: $CERT_KEY_PATH" _err "Please create it first." return 1 fi _createcsr "$domain" "$domainlist" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF" } _url_replace() { tr '/+' '_-' | tr -d '= ' } #base64 string _durl_replace_base64() { _l=$((${#1} % 4)) if [ $_l -eq 2 ]; then _s="$1"'==' elif [ $_l -eq 3 ]; then _s="$1"'=' else _s="$1" fi echo "$_s" | tr '_-' '/+' } _time2str() { #BSD if date -u -r "$1" -j "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then return fi #Linux if date -u --date=@"$1" "+%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then return fi #Omnios if date -u -r "$1" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then return fi #Solaris if printf "%(%Y-%m-%dT%H:%M:%SZ)T\n" $1 2>/dev/null; then return fi #Busybox if echo "$1" | awk '{ print strftime("%Y-%m-%dT%H:%M:%SZ", $0); }' 2>/dev/null; then return fi } _normalizeJson() { sed "s/\" *: *\([\"{\[]\)/\":\1/g" | sed "s/^ *\([^ ]\)/\1/" | tr -d "\r\n" } _stat() { #Linux if stat -c '%U:%G' "$1" 2>/dev/null; then return fi #BSD if stat -f '%Su:%Sg' "$1" 2>/dev/null; then return fi return 1 #error, 'stat' not found } #keyfile _isRSA() { keyfile=$1 if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text 2>&1 | grep "^publicExponent:" 2>&1 >/dev/null; then return 0 fi return 1 } #keyfile _isEcc() { keyfile=$1 if grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" 2>&1 >/dev/null; then return 0 fi return 1 } #keyfile _calcjwk() { keyfile="$1" if [ -z "$keyfile" ]; then _usage "Usage: _calcjwk keyfile" return 1 fi if [ "$JWK_HEADER" ] && [ "$__CACHED_JWK_KEY_FILE" = "$keyfile" ]; then _debug2 "Use cached jwk for file: $__CACHED_JWK_KEY_FILE" return 0 fi if _isRSA "$keyfile"; then _debug "RSA key" pub_exp=$(${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1) if [ "${#pub_exp}" = "5" ]; then pub_exp=0$pub_exp fi _debug3 pub_exp "$pub_exp" e=$(echo "$pub_exp" | _h2b | _base64) _debug3 e "$e" modulus=$(${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2) _debug3 modulus "$modulus" n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)" _debug3 n "$n" jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}' _debug3 jwk "$jwk" JWK_HEADER='{"alg": "RS256", "jwk": '$jwk'}' JWK_HEADERPLACE_PART1='{"nonce": "' JWK_HEADERPLACE_PART2='", "alg": "RS256"' elif _isEcc "$keyfile"; then _debug "EC key" crv="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")" _debug3 crv "$crv" __ECC_KEY_LEN=$(echo "$crv" | cut -d "-" -f 2) if [ "$__ECC_KEY_LEN" = "521" ]; then __ECC_KEY_LEN=512 fi _debug3 __ECC_KEY_LEN "$__ECC_KEY_LEN" if [ -z "$crv" ]; then _debug "Let's try ASN1 OID" crv_oid="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")" _debug3 crv_oid "$crv_oid" case "${crv_oid}" in "prime256v1") crv="P-256" __ECC_KEY_LEN=256 ;; "secp384r1") crv="P-384" __ECC_KEY_LEN=384 ;; "secp521r1") crv="P-521" __ECC_KEY_LEN=512 ;; *) _err "ECC oid: $crv_oid" return 1 ;; esac _debug3 crv "$crv" fi pubi="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)" pubi=$(_math "$pubi" + 1) _debug3 pubi "$pubi" pubj="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)" pubj=$(_math "$pubj" - 1) _debug3 pubj "$pubj" pubtext="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")" _debug3 pubtext "$pubtext" xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)" xlen=$(_math "$xlen" / 4) _debug3 xlen "$xlen" xend=$(_math "$xlen" + 1) x="$(printf "%s" "$pubtext" | cut -d : -f 2-"$xend")" _debug3 x "$x" x64="$(printf "%s" "$x" | tr -d : | _h2b | _base64 | _url_replace)" _debug3 x64 "$x64" xend=$(_math "$xend" + 1) y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-2048)" _debug3 y "$y" y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)" _debug3 y64 "$y64" jwk='{"crv": "'$crv'", "kty": "EC", "x": "'$x64'", "y": "'$y64'"}' _debug3 jwk "$jwk" JWK_HEADER='{"alg": "ES'$__ECC_KEY_LEN'", "jwk": '$jwk'}' JWK_HEADERPLACE_PART1='{"nonce": "' JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"' else _err "Only RSA or EC keys are supported. keyfile=$keyfile" _debug2 "$(cat "$keyfile")" return 1 fi _debug3 JWK_HEADER "$JWK_HEADER" __CACHED_JWK_KEY_FILE="$keyfile" } _time() { date -u "+%s" } #support 2 formats: # 2022-04-01 08:10:33 to 1648800633 #or 2022-04-01T08:10:33Z to 1648800633 _date2time() { #Linux if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return fi #Solaris if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return fi #Mac/BSD if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return fi #Omnios if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then return fi #Omnios if python3 -c "import datetime; print(int(datetime.datetime.strptime(\"$1\", \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" 2>/dev/null; then return fi _err "Cannot parse _date2time $1" return 1 } _utc_date() { date -u "+%Y-%m-%d %H:%M:%S" } _mktemp() { if _exists mktemp; then if mktemp 2>/dev/null; then return 0 elif _contains "$(mktemp 2>&1)" "-t prefix" && mktemp -t "$PROJECT_NAME" 2>/dev/null; then #for Mac osx return 0 fi fi if [ -d "/tmp" ]; then echo "/tmp/${PROJECT_NAME}wefADf24sf.$(_time).tmp" return 0 elif [ "$LE_TEMP_DIR" ] && mkdir -p "$LE_TEMP_DIR"; then echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp" return 0 fi _err "Cannot 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 HTTP_HEADER="$(_mktemp)" _debug2 HTTP_HEADER "$HTTP_HEADER" fi if [ "$__HTTP_INITIALIZED" ]; then if [ "$_ACME_CURL$_ACME_WGET" ]; then _debug2 "Http already initialized." return 0 fi fi if [ -z "$_ACME_CURL" ] && _exists "curl"; then _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 " fi if [ "$CA_PATH" ]; then _ACME_CURL="$_ACME_CURL --capath $CA_PATH " elif [ "$CA_BUNDLE" ]; then _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE " fi if _contains "$(curl --help 2>&1)" "--globoff" || _contains "$(curl --help curl 2>&1)" "--globoff"; then _ACME_CURL="$_ACME_CURL -g " fi #don't use --fail-with-body ##from curl 7.76: return fail on HTTP errors but keep the body #if _contains "$(curl --help http 2>&1)" "--fail-with-body"; then # _ACME_CURL="$_ACME_CURL --fail-with-body " #fi fi 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 if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--debug"; then _ACME_WGET="$_ACME_WGET -d " fi fi if [ "$CA_PATH" ]; then _ACME_WGET="$_ACME_WGET --ca-directory=$CA_PATH " elif [ "$CA_BUNDLE" ]; then _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE " fi #from wget 1.14: do not skip body on 404 error if _contains "$(wget --help 2>&1)" "--content-on-error"; then _ACME_WGET="$_ACME_WGET --content-on-error " fi fi __HTTP_INITIALIZED=1 } # body url [needbase64] [POST|PUT|DELETE] [ContentType] _post() { body="$1" _post_url="$2" needbase64="$3" httpmethod="$4" _postContentType="$5" if [ -z "$httpmethod" ]; then httpmethod="POST" fi _debug $httpmethod _debug "_post_url" "$_post_url" _debug2 "body" "$body" _debug2 "_postContentType" "$_postContentType" _inithttp if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then _CURL="$_ACME_CURL" if [ "$HTTPS_INSECURE" ]; then _CURL="$_CURL --insecure " fi if [ "$httpmethod" = "HEAD" ]; then _CURL="$_CURL -I " fi _debug "_CURL" "$_CURL" if [ "$needbase64" ]; then if [ "$body" ]; then 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 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" "$_post_url" | _base64)" else response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url" | _base64)" fi fi else if [ "$body" ]; then 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 else 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" "$_post_url")" else response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$_post_url")" fi fi fi _ret="$?" if [ "$_ret" != "0" ]; then _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret" if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _err "Here is the curl dump log:" _err "$(cat "$_CURL_DUMP")" fi fi elif [ "$_ACME_WGET" ]; then _WGET="$_ACME_WGET" if [ "$HTTPS_INSECURE" ]; then _WGET="$_WGET --no-check-certificate " fi if [ "$httpmethod" = "HEAD" ]; then _WGET="$_WGET --read-timeout=3.0 --tries=2 " fi _debug "_WGET" "$_WGET" if [ "$needbase64" ]; then if [ "$httpmethod" = "POST" ]; then 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 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 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 elif [ "$httpmethod" = "HEAD" ]; then if [ "$_postContentType" ]; then response="$($_WGET --spider -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 --spider -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 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="$?" if [ "$_ret" = "8" ]; then _ret=0 _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later." fi if [ "$_ret" != "0" ]; then _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret" fi if _contains "$_WGET" " -d "; then # Demultiplex wget debug output cat "$HTTP_HEADER" >&2 _sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER" fi # remove leading whitespaces from header to match curl format _sed_i 's/^ //g' "$HTTP_HEADER" else _ret="$?" _err "Neither curl nor wget have been found, cannot make $httpmethod request." fi _debug "_ret" "$_ret" printf "%s" "$response" return $_ret } # url getheader timeout _get() { _debug GET url="$1" onlyheader="$2" t="$3" _debug url "$url" _debug "timeout=$t" _inithttp if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then _CURL="$_ACME_CURL" if [ "$HTTPS_INSECURE" ]; then _CURL="$_CURL --insecure " fi if [ "$t" ]; then _CURL="$_CURL --connect-timeout $t" fi _debug "_CURL" "$_CURL" if [ "$onlyheader" ]; then $_CURL -I --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url" else $_CURL --user-agent "$USER_AGENT" -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" "$url" fi ret=$? if [ "$ret" != "0" ]; then _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret" if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _err "Here is the curl dump log:" _err "$(cat "$_CURL_DUMP")" fi fi elif [ "$_ACME_WGET" ]; then _WGET="$_ACME_WGET" if [ "$HTTPS_INSECURE" ]; then _WGET="$_WGET --no-check-certificate " fi if [ "$t" ]; then _WGET="$_WGET --timeout=$t" fi _debug "_WGET" "$_WGET" if [ "$onlyheader" ]; then _wget_out="$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)" if _contains "$_WGET" " -d "; then # Demultiplex wget debug output echo "$_wget_out" >&2 echo "$_wget_out" | sed '/^[^ ][^ ]/d; /^ *$/d; s/^ //g' - fi else $_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O - "$url" 2>"$HTTP_HEADER" if _contains "$_WGET" " -d "; then # Demultiplex wget debug output cat "$HTTP_HEADER" >&2 _sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER" fi # remove leading whitespaces from header to match curl format _sed_i 's/^ //g' "$HTTP_HEADER" fi ret=$? if [ "$ret" = "8" ]; then ret=0 _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later." fi if [ "$ret" != "0" ]; then _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret" fi else ret=$? _err "Neither curl nor wget have been found, cannot make GET request." fi _debug "ret" "$ret" return $ret } _head_n() { head -n "$1" } _tail_n() { if _is_solaris; then #fix for solaris tail -"$1" else tail -n "$1" fi } _tail_c() { tail -c "$1" 2>/dev/null || tail -"$1"c } # url payload needbase64 keyfile _send_signed_request() { url=$1 payload=$2 needbase64=$3 keyfile=$4 if [ -z "$keyfile" ]; then keyfile="$ACCOUNT_KEY_PATH" fi _debug "=======Sending Signed Request=======" _debug url "$url" _debug payload "$payload" if ! _calcjwk "$keyfile"; then return 1 fi __request_conent_type="$CONTENT_TYPE_JSON" payload64=$(printf "%s" "$payload" | _base64 | _url_replace) _debug3 payload64 "$payload64" MAX_REQUEST_RETRY_TIMES=20 _sleep_retry_sec=1 _request_retry_times=0 while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do _request_retry_times=$(_math "$_request_retry_times" + 1) _debug3 _request_retry_times "$_request_retry_times" if [ -z "$_CACHED_NONCE" ]; then _headers="" if [ "$ACME_NEW_NONCE" ]; then _debug2 "Get nonce with HEAD. ACME_NEW_NONCE" "$ACME_NEW_NONCE" nonceurl="$ACME_NEW_NONCE" if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type" >/dev/null; then _headers="$(cat "$HTTP_HEADER")" _debug2 _headers "$_headers" _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2 | cut -d , -f 1)" fi fi if [ -z "$_CACHED_NONCE" ]; then _debug2 "Get nonce with GET. ACME_DIRECTORY" "$ACME_DIRECTORY" nonceurl="$ACME_DIRECTORY" _headers="$(_get "$nonceurl" "onlyheader")" _debug2 _headers "$_headers" _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)" fi if [ -z "$_CACHED_NONCE" ] && [ "$ACME_NEW_NONCE" ]; then _debug2 "Get nonce with GET. ACME_NEW_NONCE" "$ACME_NEW_NONCE" nonceurl="$ACME_NEW_NONCE" _headers="$(_get "$nonceurl" "onlyheader")" _debug2 _headers "$_headers" _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)" fi if [ "$?" != "0" ]; then _err "Cannot connect to $nonceurl to get nonce." return 1 fi else _debug2 "Use _CACHED_NONCE" "$_CACHED_NONCE" 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 [ "$url" = "$ACME_NEW_ACCOUNT" ]; then protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' elif [ "$url" = "$ACME_REVOKE_CERT" ] && [ "$keyfile" != "$ACCOUNT_KEY_PATH" ]; then protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' else protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}' fi _debug3 protected "$protected" protected64="$(printf "%s" "$protected" | _base64 | _url_replace)" _debug3 protected64 "$protected64" if ! _sig_t="$(printf "%s" "$protected64.$payload64" | _sign "$keyfile" "sha256")"; then _err "Sign request failed." return 1 fi _debug3 _sig_t "$_sig_t" sig="$(printf "%s" "$_sig_t" | _url_replace)" _debug3 sig "$sig" body="{\"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" _debug3 body "$body" response="$(_post "$body" "$url" "$needbase64" "POST" "$__request_conent_type")" _CACHED_NONCE="" if [ "$?" != "0" ]; then _err "Cannot make POST request to $url" return 1 fi responseHeaders="$(cat "$HTTP_HEADER")" _debug2 responseHeaders "$responseHeaders" code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" _debug code "$code" _debug2 original "$response" if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then response="$(echo "$response" | _json_decode | _normalizeJson)" fi _debug2 response "$response" _CACHED_NONCE="$(echo "$responseHeaders" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2 | cut -d , -f 1)" if ! _startswith "$code" "2"; then _body="$response" if [ "$needbase64" ]; then _body="$(echo "$_body" | _dbase64 multiline)" _debug3 _body "$_body" fi _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') if [ "$code" = '503' ]; then _sleep_overload_retry_sec=$_retryafter if [ -z "$_sleep_overload_retry_sec" ]; then _sleep_overload_retry_sec=5 fi if [ $_sleep_overload_retry_sec -le 600 ]; then _info "It seems the CA server is currently overloaded, let's wait and retry. Sleeping for $_sleep_overload_retry_sec seconds." _sleep $_sleep_overload_retry_sec continue else _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore." fi fi if _contains "$_body" "JWS has invalid anti-replay nonce" || _contains "$_body" "JWS has an invalid anti-replay nonce"; then _info "It seems the CA server is busy now, let's wait and retry. Sleeping for $_sleep_retry_sec seconds." _CACHED_NONCE="" _sleep $_sleep_retry_sec continue fi if _contains "$_body" "The Replay Nonce is not recognized"; then _info "The replay nonce is not valid, let's get a new one. Sleeping for $_sleep_retry_sec seconds." _CACHED_NONCE="" _sleep $_sleep_retry_sec continue fi fi return 0 done _info "Giving up sending to CA server after $MAX_REQUEST_RETRY_TIMES retries." return 1 } #setopt "file" "opt" "=" "value" [";"] _setopt() { __conf="$1" __opt="$2" __sep="$3" __val="$4" __end="$5" if [ -z "$__opt" ]; then _usage usage: _setopt '"file" "opt" "=" "value" [";"]' return fi if [ ! -f "$__conf" ]; then touch "$__conf" fi if [ -n "$(_tail_c 1 <"$__conf")" ]; then echo >>"$__conf" fi if grep -n "^$__opt$__sep" "$__conf" >/dev/null; then _debug3 OK if _contains "$__val" "&"; then __val="$(echo "$__val" | sed 's/&/\\&/g')" fi if _contains "$__val" "|"; then __val="$(echo "$__val" | sed 's/|/\\|/g')" fi text="$(cat "$__conf")" printf -- "%s\n" "$text" | sed "s|^$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf" elif grep -n "^#$__opt$__sep" "$__conf" >/dev/null; then if _contains "$__val" "&"; then __val="$(echo "$__val" | sed 's/&/\\&/g')" fi if _contains "$__val" "|"; then __val="$(echo "$__val" | sed 's/|/\\|/g')" fi text="$(cat "$__conf")" printf -- "%s\n" "$text" | sed "s|^#$__opt$__sep.*$|$__opt$__sep$__val$__end|" >"$__conf" else _debug3 APP echo "$__opt$__sep$__val$__end" >>"$__conf" fi _debug3 "$(grep -n "^$__opt$__sep" "$__conf")" } #_save_conf file key value base64encode #save to conf _save_conf() { _s_c_f="$1" _sdkey="$2" _sdvalue="$3" _b64encode="$4" if [ "$_sdvalue" ] && [ "$_b64encode" ]; then _sdvalue="${B64CONF_START}$(printf "%s" "${_sdvalue}" | _base64)${B64CONF_END}" fi if [ "$_s_c_f" ]; then _setopt "$_s_c_f" "$_sdkey" "=" "'$_sdvalue'" else _err "Config file is empty, cannot save $_sdkey=$_sdvalue" fi } #_clear_conf file key _clear_conf() { _c_c_f="$1" _sdkey="$2" if [ "$_c_c_f" ]; then _conf_data="$(cat "$_c_c_f")" echo "$_conf_data" | sed "/^$_sdkey *=.*$/d" >"$_c_c_f" else _err "Config file is empty, cannot clear" fi } #_read_conf file key _read_conf() { _r_c_f="$1" _sdkey="$2" if [ -f "$_r_c_f" ]; then _sdv="$( eval "$(grep "^$_sdkey *=" "$_r_c_f")" eval "printf \"%s\" \"\$$_sdkey\"" )" if _startswith "$_sdv" "${B64CONF_START}" && _endswith "$_sdv" "${B64CONF_END}"; then _sdv="$(echo "$_sdv" | sed "s/${B64CONF_START}//" | sed "s/${B64CONF_END}//" | _dbase64)" fi printf "%s" "$_sdv" else _debug "Config file is empty, cannot read $_sdkey" fi } #_savedomainconf key value base64encode #save to domain.conf _savedomainconf() { _save_conf "$DOMAIN_CONF" "$@" } #_cleardomainconf key _cleardomainconf() { _clear_conf "$DOMAIN_CONF" "$1" } #_readdomainconf key _readdomainconf() { _read_conf "$DOMAIN_CONF" "$1" } #_migratedomainconf oldkey newkey base64encode _migratedomainconf() { _old_key="$1" _new_key="$2" _b64encode="$3" _old_value=$(_readdomainconf "$_old_key") _cleardomainconf "$_old_key" if [ -z "$_old_value" ]; then return 1 # migrated failed: old value is empty fi _new_value=$(_readdomainconf "$_new_key") if [ -n "$_new_value" ]; then _debug "Domain config new key exists, old key $_old_key='$_old_value' has been removed." return 1 # migrated failed: old value replaced by new value fi _savedomainconf "$_new_key" "$_old_value" "$_b64encode" _debug "Domain config $_old_key has been migrated to $_new_key." } #_migratedeployconf oldkey newkey base64encode _migratedeployconf() { _migratedomainconf "$1" "SAVED_$2" "$3" || _migratedomainconf "SAVED_$1" "SAVED_$2" "$3" # try only when oldkey itself is not found } #key value base64encode _savedeployconf() { _savedomainconf "SAVED_$1" "$2" "$3" #remove later _cleardomainconf "$1" } #key _getdeployconf() { _rac_key="$1" _rac_value="$(eval echo \$"$_rac_key")" if [ "$_rac_value" ]; then if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then _debug2 "trim quotation marks" eval $_rac_key=$_rac_value export $_rac_key fi return 0 # do nothing fi _saved="$(_readdomainconf "SAVED_$_rac_key")" eval $_rac_key=\$_saved export $_rac_key } #_saveaccountconf key value base64encode _saveaccountconf() { _save_conf "$ACCOUNT_CONF_PATH" "$@" } #key value base64encode _saveaccountconf_mutable() { _save_conf "$ACCOUNT_CONF_PATH" "SAVED_$1" "$2" "$3" #remove later _clearaccountconf "$1" } #key _readaccountconf() { _read_conf "$ACCOUNT_CONF_PATH" "$1" } #key _readaccountconf_mutable() { _rac_key="$1" _readaccountconf "SAVED_$_rac_key" } #_clearaccountconf key _clearaccountconf() { _clear_conf "$ACCOUNT_CONF_PATH" "$1" } #key _clearaccountconf_mutable() { _clearaccountconf "SAVED_$1" #remove later _clearaccountconf "$1" } #_savecaconf key value _savecaconf() { _save_conf "$CA_CONF" "$1" "$2" } #_readcaconf key _readcaconf() { _read_conf "$CA_CONF" "$1" } #_clearaccountconf key _clearcaconf() { _clear_conf "$CA_CONF" "$1" } # content localaddress _startserver() { content="$1" ncaddr="$2" _debug "content" "$content" _debug "ncaddr" "$ncaddr" _debug "startserver: $$" _debug Le_HTTPPort "$Le_HTTPPort" _debug Le_Listen_V4 "$Le_Listen_V4" _debug Le_Listen_V6 "$Le_Listen_V6" _NC="socat" if [ "$Le_Listen_V6" ]; then _NC="$_NC -6" else _NC="$_NC -4" fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then _NC="$_NC -d -d -v" fi SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork #Adding bind to local-address if [ "$ncaddr" ]; then SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}" fi _content_len="$(printf "%s" "$content" | wc -c)" _debug _content_len "$_content_len" _debug "_NC" "$_NC $SOCAT_OPTIONS" export _SOCAT_ERR="$(_mktemp)" $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \ echo 'HTTP/1.0 200 OK'; \ echo 'Content-Length\: $_content_len'; \ echo ''; \ printf '%s' '$content';" 2>"$_SOCAT_ERR" & serverproc="$!" if [ -f "$_SOCAT_ERR" ]; then if grep "Permission denied" "$_SOCAT_ERR" >/dev/null; then _err "socat: $(cat $_SOCAT_ERR)" _err "Can not listen for user: $(whoami)" _err "Maybe try with root again?" rm -f "$_SOCAT_ERR" return 1 fi fi } _stopserver() { pid="$1" _debug "pid" "$pid" if [ -z "$pid" ]; then rm -f "$_SOCAT_ERR" return fi kill $pid rm -f "$_SOCAT_ERR" } # sleep sec _sleep() { _sleep_sec="$1" if [ "$__INTERACTIVE" ]; then _sleep_c="$_sleep_sec" while [ "$_sleep_c" -ge "0" ]; do printf "\r \r" __green "$_sleep_c" _sleep_c="$(_math "$_sleep_c" - 1)" sleep 1 done printf "\r" else sleep "$_sleep_sec" fi } # _starttlsserver san_a san_b port content _ncaddr acmeValidationv1 _starttlsserver() { _info "Starting tls server." san_a="$1" san_b="$2" port="$3" content="$4" opaddr="$5" acmeValidationv1="$6" _debug san_a "$san_a" _debug san_b "$san_b" _debug port "$port" _debug acmeValidationv1 "$acmeValidationv1" #create key TLS_KEY if ! _createkey "2048" "$TLS_KEY"; then _err "Error creating TLS validation key." return 1 fi #create csr alt="$san_a" if [ "$san_b" ]; then alt="$alt,$san_b" fi if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$acmeValidationv1"; then _err "Error creating TLS validation CSR." return 1 fi #self signed if ! _signcsr "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$TLS_CERT"; then _err "Error creating TLS validation cert." return 1 fi __S_OPENSSL="${ACME_OPENSSL_BIN:-openssl} s_server -www -cert $TLS_CERT -key $TLS_KEY " if [ "$opaddr" ]; then __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port" else __S_OPENSSL="$__S_OPENSSL -accept $port" fi _debug Le_Listen_V4 "$Le_Listen_V4" _debug Le_Listen_V6 "$Le_Listen_V6" if [ "$Le_Listen_V4" ]; then __S_OPENSSL="$__S_OPENSSL -4" elif [ "$Le_Listen_V6" ]; then __S_OPENSSL="$__S_OPENSSL -6" fi if [ "$acmeValidationv1" ]; then __S_OPENSSL="$__S_OPENSSL -alpn acme-tls/1" fi _debug "$__S_OPENSSL" if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then $__S_OPENSSL -tlsextdebug & else $__S_OPENSSL >/dev/null 2>&1 & fi serverproc="$!" sleep 1 _debug serverproc "$serverproc" } #file _readlink() { _rf="$1" if ! readlink -f "$_rf" 2>/dev/null; then if _startswith "$_rf" "/"; then echo "$_rf" return 0 fi echo "$(pwd)/$_rf" | _conapath fi } _conapath() { sed "s#/\./#/#g" } __initHome() { if [ -z "$_SCRIPT_HOME" ]; then if _exists readlink && _exists dirname; then _debug "Let's find the script directory." _debug "_SCRIPT_" "$_SCRIPT_" _script="$(_readlink "$_SCRIPT_")" _debug "_script" "$_script" _script_home="$(dirname "$_script")" _debug "_script_home" "$_script_home" if [ -d "$_script_home" ]; then export _SCRIPT_HOME="$_script_home" else _err "It seems the script home is not correct: $_script_home" fi fi fi # if [ -z "$LE_WORKING_DIR" ]; then # if [ -f "$DEFAULT_INSTALL_HOME/account.conf" ]; then # _debug "It seems that $PROJECT_NAME is already installed in $DEFAULT_INSTALL_HOME" # LE_WORKING_DIR="$DEFAULT_INSTALL_HOME" # else # LE_WORKING_DIR="$_SCRIPT_HOME" # fi # fi if [ -z "$LE_WORKING_DIR" ]; then _debug "Using default home: $DEFAULT_INSTALL_HOME" LE_WORKING_DIR="$DEFAULT_INSTALL_HOME" fi export LE_WORKING_DIR if [ -z "$LE_CONFIG_HOME" ]; then LE_CONFIG_HOME="$LE_WORKING_DIR" fi _debug "Using config home: $LE_CONFIG_HOME" export LE_CONFIG_HOME _DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf" if [ -z "$ACCOUNT_CONF_PATH" ]; then if [ -f "$_DEFAULT_ACCOUNT_CONF_PATH" ]; then . "$_DEFAULT_ACCOUNT_CONF_PATH" fi fi if [ -z "$ACCOUNT_CONF_PATH" ]; then ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH" fi _debug3 ACCOUNT_CONF_PATH "$ACCOUNT_CONF_PATH" DEFAULT_LOG_FILE="$LE_CONFIG_HOME/$PROJECT_NAME.log" DEFAULT_CA_HOME="$LE_CONFIG_HOME/ca" if [ -z "$LE_TEMP_DIR" ]; then LE_TEMP_DIR="$LE_CONFIG_HOME/tmp" fi } _clearAPI() { ACME_NEW_ACCOUNT="" ACME_KEY_CHANGE="" ACME_NEW_AUTHZ="" ACME_NEW_ORDER="" ACME_REVOKE_CERT="" ACME_NEW_NONCE="" ACME_AGREEMENT="" } #server _initAPI() { _api_server="${1:-$ACME_DIRECTORY}" _debug "_init API for server: $_api_server" MAX_API_RETRY_TIMES=10 _sleep_retry_sec=10 _request_retry_times=0 while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do _request_retry_times=$(_math "$_request_retry_times" + 1) response=$(_get "$_api_server") if [ "$?" != "0" ]; then _debug2 "response" "$response" _info "Cannot init API for: $_api_server." _info "Sleeping for $_sleep_retry_sec seconds and retrying." _sleep "$_sleep_retry_sec" continue fi response=$(echo "$response" | _json_decode) _debug2 "response" "$response" ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_KEY_CHANGE ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_AUTHZ ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_ORDER ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_ACCOUNT ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_REVOKE_CERT ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_NEW_NONCE ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_AGREEMENT _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE" _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ" _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER" _debug "ACME_NEW_ACCOUNT" "$ACME_NEW_ACCOUNT" _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT" _debug "ACME_AGREEMENT" "$ACME_AGREEMENT" _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE" if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then return 0 fi _info "Sleeping for $_sleep_retry_sec seconds and retrying." _sleep "$_sleep_retry_sec" done if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then return 0 fi _err "Cannot init API for $_api_server" return 1 } _clearCA() { export CA_CONF= export ACCOUNT_KEY_PATH= export ACCOUNT_JSON_PATH= } #[domain] [keylength or isEcc flag] _initpath() { domain="$1" _ilength="$2" __initHome if [ -f "$ACCOUNT_CONF_PATH" ]; then . "$ACCOUNT_CONF_PATH" fi if [ "$_ACME_IN_CRON" ]; then if [ ! "$_USER_PATH_EXPORTED" ]; then _USER_PATH_EXPORTED=1 export PATH="$USER_PATH:$PATH" fi fi if [ -z "$CA_HOME" ]; then CA_HOME="$DEFAULT_CA_HOME" fi if [ -z "$ACME_DIRECTORY" ]; then if [ "$STAGE" ]; then ACME_DIRECTORY="$DEFAULT_STAGING_CA" _info "Using ACME_DIRECTORY: $ACME_DIRECTORY" else default_acme_server=$(_readaccountconf "DEFAULT_ACME_SERVER") _debug default_acme_server "$default_acme_server" if [ "$default_acme_server" ]; then ACME_DIRECTORY="$default_acme_server" else ACME_DIRECTORY="$DEFAULT_CA" fi fi fi _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" _ACME_SERVER_PATH="$(echo "$ACME_DIRECTORY" | cut -d : -f 2- | tr -s / | cut -d / -f 3-)" _debug2 "_ACME_SERVER_PATH" "$_ACME_SERVER_PATH" CA_DIR="$CA_HOME/$_ACME_SERVER_HOST/$_ACME_SERVER_PATH" _DEFAULT_CA_CONF="$CA_DIR/ca.conf" if [ -z "$CA_CONF" ]; then CA_CONF="$_DEFAULT_CA_CONF" fi _debug3 CA_CONF "$CA_CONF" _OLD_CADIR="$CA_HOME/$_ACME_SERVER_HOST" _OLD_ACCOUNT_KEY="$_OLD_CADIR/account.key" _OLD_ACCOUNT_JSON="$_OLD_CADIR/account.json" _OLD_CA_CONF="$_OLD_CADIR/ca.conf" _DEFAULT_ACCOUNT_KEY_PATH="$CA_DIR/account.key" _DEFAULT_ACCOUNT_JSON_PATH="$CA_DIR/account.json" if [ -z "$ACCOUNT_KEY_PATH" ]; then ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH" if [ -f "$_OLD_ACCOUNT_KEY" ] && ! [ -f "$ACCOUNT_KEY_PATH" ]; then mkdir -p "$CA_DIR" mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH" fi fi if [ -z "$ACCOUNT_JSON_PATH" ]; then ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH" if [ -f "$_OLD_ACCOUNT_JSON" ] && ! [ -f "$ACCOUNT_JSON_PATH" ]; then mkdir -p "$CA_DIR" mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH" fi fi if [ -f "$_OLD_CA_CONF" ] && ! [ -f "$CA_CONF" ]; then mkdir -p "$CA_DIR" mv "$_OLD_CA_CONF" "$CA_CONF" fi if [ -f "$CA_CONF" ]; then . "$CA_CONF" fi if [ -z "$ACME_DIR" ]; then ACME_DIR="/home/.acme" fi if [ -z "$APACHE_CONF_BACKUP_DIR" ]; then APACHE_CONF_BACKUP_DIR="$LE_CONFIG_HOME" fi if [ -z "$USER_AGENT" ]; then USER_AGENT="$DEFAULT_USER_AGENT" fi if [ -z "$HTTP_HEADER" ]; then HTTP_HEADER="$LE_CONFIG_HOME/http.header" fi _DEFAULT_CERT_HOME="$LE_CONFIG_HOME" if [ -z "$CERT_HOME" ]; then CERT_HOME="$_DEFAULT_CERT_HOME" fi if [ -z "$ACME_OPENSSL_BIN" ] || [ ! -f "$ACME_OPENSSL_BIN" ] || [ ! -x "$ACME_OPENSSL_BIN" ]; then ACME_OPENSSL_BIN="$DEFAULT_OPENSSL_BIN" fi if [ -z "$domain" ]; then return 0 fi if [ -z "$DOMAIN_PATH" ]; then domainhome="$CERT_HOME/$domain" domainhomeecc="$CERT_HOME/$domain$ECC_SUFFIX" DOMAIN_PATH="$domainhome" if _isEccKey "$_ilength"; then DOMAIN_PATH="$domainhomeecc" elif [ -z "$__SELECTED_RSA_KEY" ]; then if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; then _info "The domain '$domain' seems to already have an ECC cert, let's use it." DOMAIN_PATH="$domainhomeecc" fi fi _debug DOMAIN_PATH "$DOMAIN_PATH" export DOMAIN_PATH fi if [ -z "$DOMAIN_BACKUP_PATH" ]; then DOMAIN_BACKUP_PATH="$DOMAIN_PATH/backup" fi if [ -z "$DOMAIN_CONF" ]; then DOMAIN_CONF="$DOMAIN_PATH/$domain.conf" fi if [ -z "$DOMAIN_SSL_CONF" ]; then DOMAIN_SSL_CONF="$DOMAIN_PATH/$domain.csr.conf" fi if [ -z "$CSR_PATH" ]; then CSR_PATH="$DOMAIN_PATH/$domain.csr" fi if [ -z "$CERT_KEY_PATH" ]; then CERT_KEY_PATH="$DOMAIN_PATH/$domain.key" fi if [ -z "$CERT_PATH" ]; then CERT_PATH="$DOMAIN_PATH/$domain.cer" fi if [ -z "$CA_CERT_PATH" ]; then CA_CERT_PATH="$DOMAIN_PATH/ca.cer" fi if [ -z "$CERT_FULLCHAIN_PATH" ]; then CERT_FULLCHAIN_PATH="$DOMAIN_PATH/fullchain.cer" fi if [ -z "$CERT_PFX_PATH" ]; then CERT_PFX_PATH="$DOMAIN_PATH/$domain.pfx" fi if [ -z "$CERT_PKCS8_PATH" ]; then CERT_PKCS8_PATH="$DOMAIN_PATH/$domain.pkcs8" fi if [ -z "$TLS_CONF" ]; then TLS_CONF="$DOMAIN_PATH/tls.validation.conf" fi if [ -z "$TLS_CERT" ]; then TLS_CERT="$DOMAIN_PATH/tls.validation.cert" fi if [ -z "$TLS_KEY" ]; then TLS_KEY="$DOMAIN_PATH/tls.validation.key" fi if [ -z "$TLS_CSR" ]; then TLS_CSR="$DOMAIN_PATH/tls.validation.csr" fi } _apachePath() { _APACHECTL="apachectl" if ! _exists apachectl; then if _exists apache2ctl; then _APACHECTL="apache2ctl" else _err "'apachectl not found. It seems that Apache is not installed or you are not root.'" _err "Please use webroot mode to try again." return 1 fi fi if ! $_APACHECTL -V >/dev/null; then return 1 fi if [ "$APACHE_HTTPD_CONF" ]; then _saveaccountconf APACHE_HTTPD_CONF "$APACHE_HTTPD_CONF" httpdconf="$APACHE_HTTPD_CONF" httpdconfname="$(basename "$httpdconfname")" else httpdconfname="$($_APACHECTL -V | grep SERVER_CONFIG_FILE= | cut -d = -f 2 | tr -d '"')" _debug httpdconfname "$httpdconfname" if [ -z "$httpdconfname" ]; then _err "Cannot read Apache config file." return 1 fi if _startswith "$httpdconfname" '/'; then httpdconf="$httpdconfname" httpdconfname="$(basename "$httpdconfname")" else httpdroot="$($_APACHECTL -V | grep HTTPD_ROOT= | cut -d = -f 2 | tr -d '"')" _debug httpdroot "$httpdroot" httpdconf="$httpdroot/$httpdconfname" httpdconfname="$(basename "$httpdconfname")" fi fi _debug httpdconf "$httpdconf" _debug httpdconfname "$httpdconfname" if [ ! -f "$httpdconf" ]; then _err "Apache config file not found" "$httpdconf" return 1 fi return 0 } _restoreApache() { if [ -z "$usingApache" ]; then return 0 fi _initpath if ! _apachePath; then return 1 fi if [ ! -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" ]; then _debug "No config file to restore." return 0 fi cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf" _debug "Restored: $httpdconf." if ! $_APACHECTL -t; then _err "Sorry, there's been an error restoring the Apache config. Please ask for support on $PROJECT." return 1 fi _debug "Restored successfully." rm -f "$APACHE_CONF_BACKUP_DIR/$httpdconfname" return 0 } _setApache() { _initpath if ! _apachePath; then return 1 fi #test the conf first _info "Checking if there is an error in the Apache config file before starting." if ! $_APACHECTL -t >/dev/null; then _err "The Apache config file has errors, please fix them first then try again." _err "Don't worry, no changes to your system have been made." return 1 else _info "OK" fi #backup the conf _debug "Backing up Apache config file" "$httpdconf" if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then _err "Cannot backup Apache config file, aborting. Don't worry, the Apache config has not been changed." _err "This might be an $PROJECT_NAME bug, please open an issue on $PROJECT" return 1 fi _info "Config file $httpdconf has been backed up to $APACHE_CONF_BACKUP_DIR/$httpdconfname" _info "In case an error causes it to not be restored automatically, you can restore it yourself." _info "You do not need to do anything on success, as the backup file will automatically be deleted." #add alias apacheVer="$($_APACHECTL -V | grep "Server version:" | cut -d : -f 2 | cut -d " " -f 2 | cut -d '/' -f 2)" _debug "apacheVer" "$apacheVer" apacheMajor="$(echo "$apacheVer" | cut -d . -f 1)" apacheMinor="$(echo "$apacheVer" | cut -d . -f 2)" if [ "$apacheVer" ] && [ "$apacheMajor$apacheMinor" -ge "24" ]; then echo " Alias /.well-known/acme-challenge $ACME_DIR Require all granted " >>"$httpdconf" else echo " Alias /.well-known/acme-challenge $ACME_DIR Order allow,deny Allow from all " >>"$httpdconf" fi _msg="$($_APACHECTL -t 2>&1)" if [ "$?" != "0" ]; then _err "Sorry, an Apache config error has occurred" if _restoreApache; then _err "The Apache config file has been restored." else _err "Sorry, the Apache config file cannot be restored, please open an issue on $PROJECT." fi return 1 fi if [ ! -d "$ACME_DIR" ]; then mkdir -p "$ACME_DIR" chmod 755 "$ACME_DIR" fi if ! $_APACHECTL graceful; then _err "$_APACHECTL graceful error, please open an issue on $PROJECT." _restoreApache return 1 fi usingApache="1" return 0 } #find the real nginx conf file #backup #set the nginx conf #returns the real nginx conf file _setNginx() { _d="$1" _croot="$2" _thumbpt="$3" FOUND_REAL_NGINX_CONF="" FOUND_REAL_NGINX_CONF_LN="" BACKUP_NGINX_CONF="" _debug _croot "$_croot" _start_f="$(echo "$_croot" | cut -d : -f 2)" _debug _start_f "$_start_f" if [ -z "$_start_f" ]; then _debug "Finding config using the nginx command" if [ -z "$NGINX_CONF" ]; then if ! _exists "nginx"; then _err "nginx command not found." return 1 fi NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "\-\-conf-path=[^ ]* " | tr -d " ")" _debug NGINX_CONF "$NGINX_CONF" NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)" _debug NGINX_CONF "$NGINX_CONF" if [ -z "$NGINX_CONF" ]; then _err "Cannot find nginx config." NGINX_CONF="" return 1 fi if [ ! -f "$NGINX_CONF" ]; then _err "'$NGINX_CONF' doesn't exist." NGINX_CONF="" return 1 fi _debug "Found nginx config file: $NGINX_CONF" fi _start_f="$NGINX_CONF" fi _debug "Detecting nginx conf for $_d from: $_start_f" if ! _checkConf "$_d" "$_start_f"; then _err "Cannot find config file for domain $d" return 1 fi _info "Found config file: $FOUND_REAL_NGINX_CONF" _ln=$FOUND_REAL_NGINX_CONF_LN _debug "_ln" "$_ln" _lnn=$(_math $_ln + 1) _debug _lnn "$_lnn" _start_tag="$(sed -n "$_lnn,${_lnn}p" "$FOUND_REAL_NGINX_CONF")" _debug "_start_tag" "$_start_tag" if [ "$_start_tag" = "$NGINX_START" ]; then _info "The domain $_d is already configured, skipping" FOUND_REAL_NGINX_CONF="" return 0 fi mkdir -p "$DOMAIN_BACKUP_PATH" _backup_conf="$DOMAIN_BACKUP_PATH/$_d.nginx.conf" _debug _backup_conf "$_backup_conf" BACKUP_NGINX_CONF="$_backup_conf" _info "Backing $FOUND_REAL_NGINX_CONF up to $_backup_conf" if ! cp "$FOUND_REAL_NGINX_CONF" "$_backup_conf"; then _err "Backup error." FOUND_REAL_NGINX_CONF="" return 1 fi if ! _exists "nginx"; then _err "nginx command not found." return 1 fi _info "Checking the nginx config before setting up." if ! nginx -t >/dev/null 2>&1; then _err "It seems that the nginx config is not correct, cannot continue." return 1 fi _info "OK, setting up the nginx config file" if ! sed -n "1,${_ln}p" "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"; then cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" _err "Error writing nginx config. Restoring it to its original version." return 1 fi echo "$NGINX_START location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" { default_type text/plain; return 200 \"\$1.$_thumbpt\"; } #NGINX_START " >>"$FOUND_REAL_NGINX_CONF" if ! sed -n "${_lnn},99999p" "$_backup_conf" >>"$FOUND_REAL_NGINX_CONF"; then cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" _err "Error writing nginx config. Restoring it to its original version." return 1 fi _debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)" _info "nginx config has been written, let's check it again." if ! nginx -t >/dev/null 2>&1; then _err "There seems to be a problem with the nginx config, let's restore it to its original version." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 fi _info "Reloading nginx" if ! nginx -s reload >/dev/null 2>&1; then _err "There seems to be a problem with the nginx config, let's restore it to its original version." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 fi return 0 } #d , conf _checkConf() { _d="$1" _c_file="$2" _debug "Starting _checkConf from: $_c_file" if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then _debug "wildcard" for _w_f in $2; do if [ -f "$_w_f" ] && _checkConf "$1" "$_w_f"; then return 0 fi done #not found return 1 elif [ -f "$2" ]; then _debug "single" if _isRealNginxConf "$1" "$2"; then _debug "$2 found." FOUND_REAL_NGINX_CONF="$2" return 0 fi if cat "$2" | tr "\t" " " | grep "^ *include *.*;" >/dev/null; then _debug "Trying include files" for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do _debug "Checking included $included" if ! _startswith "$included" "/" && _exists dirname; then _relpath="$(dirname "$2")" _debug "_relpath" "$_relpath" included="$_relpath/$included" fi if _checkConf "$1" "$included"; then return 0 fi done fi return 1 else _debug "$2 not found." return 1 fi return 1 } #d , conf _isRealNginxConf() { _debug "_isRealNginxConf $1 $2" if [ -f "$2" ]; then for _fln in $(tr "\t" ' ' <"$2" | grep -n "^ *server_name.* $1" | cut -d : -f 1); do _debug _fln "$_fln" if [ "$_fln" ]; then _start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *" | grep -v server_name | _tail_n 1) _debug "_start" "$_start" _start_n=$(echo "$_start" | cut -d : -f 1) _start_nn=$(_math $_start_n + 1) _debug "_start_n" "$_start_n" _debug "_start_nn" "$_start_nn" _left="$(sed -n "${_start_nn},99999p" "$2")" _debug2 _left "$_left" _end="$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *" | grep -v server_name | _head_n 1)" _debug "_end" "$_end" if [ "$_end" ]; then _end_n=$(echo "$_end" | cut -d : -f 1) _debug "_end_n" "$_end_n" _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p") else _seg_n="$_left" fi _debug "_seg_n" "$_seg_n" _skip_ssl=1 for _listen_i in $(echo "$_seg_n" | tr "\t" ' ' | grep "^ *listen" | tr -d " "); do if [ "$_listen_i" ]; then if [ "$(echo "$_listen_i" | _egrep_o "listen.*ssl")" ]; then _debug2 "$_listen_i is ssl" else _debug2 "$_listen_i is plain text" _skip_ssl="" break fi fi done if [ "$_skip_ssl" = "1" ]; then _debug "ssl on, skip" else FOUND_REAL_NGINX_CONF_LN=$_fln _debug3 "found FOUND_REAL_NGINX_CONF_LN" "$FOUND_REAL_NGINX_CONF_LN" return 0 fi fi done fi return 1 } #restore all the nginx conf _restoreNginx() { if [ -z "$NGINX_RESTORE_VLIST" ]; then _debug "No need to restore nginx config, skipping." return fi _debug "_restoreNginx" _debug "NGINX_RESTORE_VLIST" "$NGINX_RESTORE_VLIST" for ng_entry in $(echo "$NGINX_RESTORE_VLIST" | tr "$dvsep" ' '); do _debug "ng_entry" "$ng_entry" _nd=$(echo "$ng_entry" | cut -d "$sep" -f 1) _ngconf=$(echo "$ng_entry" | cut -d "$sep" -f 2) _ngbackupconf=$(echo "$ng_entry" | cut -d "$sep" -f 3) _info "Restoring from $_ngbackupconf to $_ngconf" cat "$_ngbackupconf" >"$_ngconf" done _info "Reloading nginx" if ! nginx -s reload >/dev/null; then _err "An error occurred while reloading nginx, please open an issue on $PROJECT." return 1 fi return 0 } _clearup() { _stopserver "$serverproc" serverproc="" _restoreApache _restoreNginx _clearupdns if [ -z "$DEBUG" ]; then rm -f "$TLS_CONF" rm -f "$TLS_CERT" rm -f "$TLS_KEY" rm -f "$TLS_CSR" fi } _clearupdns() { _debug "_clearupdns" _debug "dns_entries" "$dns_entries" if [ -z "$dns_entries" ]; then _debug "Skipping dns." return fi _info "Removing DNS records." for entry in $dns_entries; do d=$(_getfield "$entry" 1) txtdomain=$(_getfield "$entry" 2) aliasDomain=$(_getfield "$entry" 3) _currentRoot=$(_getfield "$entry" 4) txt=$(_getfield "$entry" 5) d_api=$(_getfield "$entry" 6) _debug "d" "$d" _debug "txtdomain" "$txtdomain" _debug "aliasDomain" "$aliasDomain" _debug "_currentRoot" "$_currentRoot" _debug "txt" "$txt" _debug "d_api" "$d_api" if [ "$d_api" = "$txt" ]; then d_api="" fi if [ -z "$d_api" ]; then _info "Domain API file was not found: $d_api" continue fi if [ "$aliasDomain" ]; then txtdomain="$aliasDomain" fi ( if ! . "$d_api"; then _err "Error loading file $d_api. Please check your API file and try again." return 1 fi rmcommand="${_currentRoot}_rm" if ! _exists "$rmcommand"; then _err "It seems that your API file doesn't define $rmcommand" return 1 fi _info "Removing txt: $txt for domain: $txtdomain" if ! $rmcommand "$txtdomain" "$txt"; then _err "Error removing txt for domain: $txtdomain" return 1 fi _info "Successfully removed" ) done } # webroot removelevel tokenfile _clearupwebbroot() { __webroot="$1" if [ -z "$__webroot" ]; then _debug "No webroot specified, skipping" return 0 fi _rmpath="" if [ "$2" = '1' ]; then _rmpath="$__webroot/.well-known" elif [ "$2" = '2' ]; then _rmpath="$__webroot/.well-known/acme-challenge" elif [ "$2" = '3' ]; then _rmpath="$__webroot/.well-known/acme-challenge/$3" else _debug "Skipping for removelevel: $2" fi if [ "$_rmpath" ]; then if [ "$DEBUG" ]; then _debug "Debugging, not removing: $_rmpath" else rm -rf "$_rmpath" fi fi return 0 } _on_before_issue() { _chk_web_roots="$1" _chk_main_domain="$2" _chk_alt_domains="$3" _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 "Runing pre hook:'$_chk_pre_hook'" if ! ( export Le_Domain="$_chk_main_domain" export Le_Alt="$_chk_alt_domains" cd "$DOMAIN_PATH" && eval "$_chk_pre_hook" ); then _err "Error occurred when running pre hook." return 1 fi fi if _hasfield "$_chk_web_roots" "$NO_VALUE"; then if ! _exists "socat"; then _err "Please install socat tools first." return 1 fi fi _debug Le_LocalAddress "$_chk_local_addr" _index=1 _currentRoot="" _addrIndex=1 _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 "Checking for domain" "$d" _currentRoot="$(_getfield "$_chk_web_roots" $_index)" _debug "_currentRoot" "$_currentRoot" _index=$(_math $_index + 1) _checkport="" if [ "$_currentRoot" = "$NO_VALUE" ]; then _info "Standalone mode." if [ -z "$Le_HTTPPort" ]; then Le_HTTPPort=80 _cleardomainconf "Le_HTTPPort" else _savedomainconf "Le_HTTPPort" "$Le_HTTPPort" fi _checkport="$Le_HTTPPort" elif [ "$_currentRoot" = "$W_ALPN" ]; then _info "Standalone alpn mode." if [ -z "$Le_TLSPort" ]; then Le_TLSPort=443 else _savedomainconf "Le_TLSPort" "$Le_TLSPort" fi _checkport="$Le_TLSPort" fi if [ "$_checkport" ]; then _debug _checkport "$_checkport" _checkaddr="$(_getfield "$_chk_local_addr" $_addrIndex)" _debug _checkaddr "$_checkaddr" _addrIndex="$(_math $_addrIndex + 1)" _netprc="$(_ss "$_checkport" | grep "$_checkport")" netprc="$(echo "$_netprc" | grep "$_checkaddr")" if [ -z "$netprc" ]; then netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS:$_checkport")" fi if [ "$netprc" ]; then _err "$netprc" _err "tcp port $_checkport is already used by $(echo "$netprc" | cut -d : -f 4)" _err "Please stop it first" return 1 fi fi done if _hasfield "$_chk_web_roots" "apache"; then if ! _setApache; then _err "Error setting up Apache. Please open an issue on $PROJECT." return 1 fi else usingApache="" fi } _on_issue_err() { _chk_post_hook="$1" _chk_vlist="$2" _debug _on_issue_err if [ "$LOG_FILE" ]; then _err "Please check log file for more details: $LOG_FILE" else _err "Please add '--debug' or '--log' to see more information." _err "See: $_DEBUG_WIKI" fi #run the post hook if [ "$_chk_post_hook" ]; then _info "Running post hook: '$_chk_post_hook'" if ! ( cd "$DOMAIN_PATH" && eval "$_chk_post_hook" ); then _err "Error encountered while running post hook." return 1 fi fi #trigger the validation to flush the pending authz _debug2 "_chk_vlist" "$_chk_vlist" if [ "$_chk_vlist" ]; then ( _debug2 "start to deactivate authz" ventries=$(echo "$_chk_vlist" | tr "$dvsep" ' ') for ventry in $ventries; do d=$(echo "$ventry" | cut -d "$sep" -f 1) keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) uri=$(echo "$ventry" | cut -d "$sep" -f 3) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) __trigger_validation "$uri" "$keyauthorization" done ) fi if [ "$_ACME_IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "$W_DNS"; then _err "$_DNS_MANUAL_ERR" fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "0" ]; then _debug "$(_dlg_versions)" fi } _on_issue_success() { _chk_post_hook="$1" _chk_renew_hook="$2" _debug _on_issue_success #run the post hook if [ "$_chk_post_hook" ]; then _info "Running post hook:'$_chk_post_hook'" if ! ( export CERT_PATH export CERT_KEY_PATH export CA_CERT_PATH export CERT_FULLCHAIN_PATH export Le_Domain="$_main_domain" cd "$DOMAIN_PATH" && eval "$_chk_post_hook" ); then _err "Error encountered while running post hook." return 1 fi fi #run renew hook if [ "$_ACME_IS_RENEW" ] && [ "$_chk_renew_hook" ]; then _info "Running renew hook: '$_chk_renew_hook'" if ! ( export CERT_PATH export CERT_KEY_PATH export CA_CERT_PATH export CERT_FULLCHAIN_PATH export Le_Domain="$_main_domain" cd "$DOMAIN_PATH" && eval "$_chk_renew_hook" ); then _err "Error encountered while running renew hook." return 1 fi fi if _hasfield "$Le_Webroot" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then _err "$_DNS_MANUAL_WARN" fi } #account_key_length eab-kid eab-hmac-key registeraccount() { _account_key_length="$1" _eab_id="$2" _eab_hmac_key="$3" _initpath _regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key" } __calcAccountKeyHash() { [ -f "$ACCOUNT_KEY_PATH" ] && _digest sha256 <"$ACCOUNT_KEY_PATH" } __calc_account_thumbprint() { printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace } _getAccountEmail() { if [ "$ACCOUNT_EMAIL" ]; then echo "$ACCOUNT_EMAIL" return 0 fi if [ -z "$CA_EMAIL" ]; then CA_EMAIL="$(_readcaconf CA_EMAIL)" fi if [ "$CA_EMAIL" ]; then echo "$CA_EMAIL" return 0 fi _readaccountconf "ACCOUNT_EMAIL" } #keylength _regAccount() { _initpath _reg_length="$1" _eab_id="$2" _eab_hmac_key="$3" _debug3 _regAccount "$_regAccount" _initAPI mkdir -p "$CA_DIR" if [ ! -f "$ACCOUNT_KEY_PATH" ]; then if ! _create_account_key "$_reg_length"; then _err "Error creating account key." return 1 fi fi if ! _calcjwk "$ACCOUNT_KEY_PATH"; then return 1 fi if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then _savecaconf CA_EAB_KEY_ID "$_eab_id" _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" fi _eab_id=$(_readcaconf "CA_EAB_KEY_ID") _eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY") _secure_debug3 _eab_id "$_eab_id" _secure_debug3 _eab_hmac_key "$_eab_hmac_key" _email="$(_getAccountEmail)" if [ "$_email" ]; then _savecaconf "CA_EMAIL" "$_email" fi if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then _info "No EAB credentials found for ZeroSSL, let's obtain them" if [ -z "$_email" ]; then _info "$(__green "$PROJECT_NAME is using ZeroSSL as default CA now.")" _info "$(__green "Please update your account with an email address first.")" _info "$(__green "$PROJECT_ENTRY --register-account -m my@example.com")" _info "See: $(__green "$_ZEROSSL_WIKI")" return 1 fi _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT) if [ "$?" != "0" ]; then _debug2 "$_eabresp" _err "Cannot get EAB credentials from ZeroSSL." return 1 fi _secure_debug2 _eabresp "$_eabresp" _eab_id="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')" _secure_debug2 _eab_id "$_eab_id" if [ -z "$_eab_id" ]; then _err "Cannot resolve _eab_id" return 1 fi _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')" _secure_debug2 _eab_hmac_key "$_eab_hmac_key" if [ -z "$_eab_hmac_key" ]; then _err "Cannot resolve _eab_hmac_key" return 1 fi _savecaconf CA_EAB_KEY_ID "$_eab_id" _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" fi fi if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}" _debug3 eab_protected "$eab_protected" eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace) _debug3 eab_protected64 "$eab_protected64" eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace) _debug3 eab_payload64 "$eab_payload64" eab_sign_t="$eab_protected64.$eab_payload64" _debug3 eab_sign_t "$eab_sign_t" key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')" _debug3 key_hex "$key_hex" eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace) _debug3 eab_signature "$eab_signature" externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}" _debug3 externalBinding "$externalBinding" fi if [ "$_email" ]; then email_sg="\"contact\": [\"mailto:$_email\"], " fi regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}" _info "Registering account: $ACME_DIRECTORY" if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then _err "Error registering account: $response" return 1 fi _eabAlreadyBound="" if [ "$code" = "" ] || [ "$code" = '201' ]; then echo "$response" >"$ACCOUNT_JSON_PATH" _info "Registered" elif [ "$code" = '409' ] || [ "$code" = '200' ]; then _info "Already registered" elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then _info "EAB already registered" _eabAlreadyBound=1 else _err "Account registration error: $response" return 1 fi if [ -z "$_eabAlreadyBound" ]; then _debug2 responseHeaders "$responseHeaders" _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")" _debug "_accUri" "$_accUri" if [ -z "$_accUri" ]; then _err "Cannot find account id url." _err "$responseHeaders" return 1 fi _savecaconf "ACCOUNT_URL" "$_accUri" else ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)" fi export ACCOUNT_URL="$_accUri" CA_KEY_HASH="$(__calcAccountKeyHash)" _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH" _savecaconf CA_KEY_HASH "$CA_KEY_HASH" if [ "$code" = '403' ]; then _err "It seems that the account key has been deactivated, please use a new account key." return 1 fi ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)" _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT" } #implement updateaccount updateaccount() { _initpath if [ ! -f "$ACCOUNT_KEY_PATH" ]; then _err "Account key not found at: $ACCOUNT_KEY_PATH" return 1 fi _accUri=$(_readcaconf "ACCOUNT_URL") _debug _accUri "$_accUri" if [ -z "$_accUri" ]; then _err "The account URL is empty, please run '--update-account' first to update the account info, then try again." return 1 fi if ! _calcjwk "$ACCOUNT_KEY_PATH"; then return 1 fi _initAPI _email="$(_getAccountEmail)" if [ "$_email" ]; then updjson='{"contact": ["mailto:'$_email'"]}' else updjson='{"contact": []}' fi _send_signed_request "$_accUri" "$updjson" if [ "$code" = '200' ]; then echo "$response" >"$ACCOUNT_JSON_PATH" _info "Account update success for $_accUri." ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)" _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT" else _info "An error occurred and the account was not updated." return 1 fi } #Implement deactivate account deactivateaccount() { _initpath if [ ! -f "$ACCOUNT_KEY_PATH" ]; then _err "Account key not found at: $ACCOUNT_KEY_PATH" return 1 fi _accUri=$(_readcaconf "ACCOUNT_URL") _debug _accUri "$_accUri" if [ -z "$_accUri" ]; then _err "The account URL is empty, please run '--update-account' first to update the account info, then try again." return 1 fi if ! _calcjwk "$ACCOUNT_KEY_PATH"; then return 1 fi _initAPI _djson="{\"status\":\"deactivated\"}" if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then _info "Successfully deactivated account $_accUri." _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,') elif [ "$code" = "403" ]; then _info "The account is already deactivated." _accid=$(_getfield "$_accUri" "999" "/") else _err "Account deactivation failed for $_accUri." return 1 fi _debug "Account id: $_accid" if [ "$_accid" ]; then _deactivated_account_path="$CA_DIR/deactivated/$_accid" _debug _deactivated_account_path "$_deactivated_account_path" if mkdir -p "$_deactivated_account_path"; then _info "Moving deactivated account info to $_deactivated_account_path/" mv "$CA_CONF" "$_deactivated_account_path/" mv "$ACCOUNT_JSON_PATH" "$_deactivated_account_path/" mv "$ACCOUNT_KEY_PATH" "$_deactivated_account_path/" else _err "Cannot create dir: $_deactivated_account_path, try to remove the deactivated account key." rm -f "$CA_CONF" rm -f "$ACCOUNT_JSON_PATH" rm -f "$ACCOUNT_KEY_PATH" fi fi } # domain folder file _findHook() { _hookdomain="$1" _hookcat="$2" _hookname="$3" if [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname" ]; then d_api="$_SCRIPT_HOME/$_hookcat/$_hookname" elif [ -f "$_SCRIPT_HOME/$_hookcat/$_hookname.sh" ]; then d_api="$_SCRIPT_HOME/$_hookcat/$_hookname.sh" elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname" ]; then d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname" elif [ "$_hookdomain" ] && [ -f "$LE_WORKING_DIR/$_hookdomain/$_hookname.sh" ]; then d_api="$LE_WORKING_DIR/$_hookdomain/$_hookname.sh" elif [ -f "$LE_WORKING_DIR/$_hookname" ]; then d_api="$LE_WORKING_DIR/$_hookname" elif [ -f "$LE_WORKING_DIR/$_hookname.sh" ]; then d_api="$LE_WORKING_DIR/$_hookname.sh" elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname" ]; then d_api="$LE_WORKING_DIR/$_hookcat/$_hookname" elif [ -f "$LE_WORKING_DIR/$_hookcat/$_hookname.sh" ]; then d_api="$LE_WORKING_DIR/$_hookcat/$_hookname.sh" fi printf "%s" "$d_api" } #domain __get_domain_new_authz() { _gdnd="$1" _info "Getting new-authz for domain" "$_gdnd" _initAPI _Max_new_authz_retry_times=5 _authz_i=0 while [ "$_authz_i" -lt "$_Max_new_authz_retry_times" ]; do _debug "Trying new-authz, attempt number $_authz_i." if ! _send_signed_request "${ACME_NEW_AUTHZ}" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$(_idn "$_gdnd")\"}}"; then _err "Cannot get new authz for domain." return 1 fi if _contains "$response" "No registration exists matching provided key"; then _err "There has been an error, but it might now be resolved, please try again." _err "If you see this message for a second time, please report this as a bug: $(__green "$PROJECT")" _clearcaconf "CA_KEY_HASH" break fi if ! _contains "$response" "An error occurred while processing your request"; then _info "new-authz request successful." break fi _authz_i="$(_math "$_authz_i" + 1)" _info "The server is busy, sleeping for $_authz_i seconds and retrying." _sleep "$_authz_i" done if [ "$_authz_i" = "$_Max_new_authz_retry_times" ]; then _err "new-authz has been retried $_Max_new_authz_retry_times times, stopping." fi if [ "$code" ] && [ "$code" != '201' ]; then _err "new-authz error: $response" return 1 fi } #uri keyAuthorization __trigger_validation() { _debug2 "Trigger domain validation." _t_url="$1" _debug2 _t_url "$_t_url" _t_key_authz="$2" _debug2 _t_key_authz "$_t_key_authz" _t_vtype="$3" _debug2 _t_vtype "$_t_vtype" _send_signed_request "$_t_url" "{}" } #endpoint domain type _ns_lookup_impl() { _ns_ep="$1" _ns_domain="$2" _ns_type="$3" _debug2 "_ns_ep" "$_ns_ep" _debug2 "_ns_domain" "$_ns_domain" _debug2 "_ns_type" "$_ns_type" response="$(_H1="accept: application/dns-json" _get "$_ns_ep?name=$_ns_domain&type=$_ns_type")" _ret=$? _debug2 "response" "$response" if [ "$_ret" != "0" ]; then return $_ret fi _answers="$(echo "$response" | tr '{}' '<>' | _egrep_o '"Answer":\[[^]]*]' | tr '<>' '\n\n')" _debug2 "_answers" "$_answers" echo "$_answers" } #domain, type _ns_lookup_cf() { _cf_ld="$1" _cf_ld_type="$2" _cf_ep="https://cloudflare-dns.com/dns-query" _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" } #domain, type _ns_purge_cf() { _cf_d="$1" _cf_d_type="$2" _debug "Purging Cloudflare $_cf_d_type record for domain $_cf_d" _cf_purl="https://cloudflare-dns.com/api/v1/purge?domain=$_cf_d&type=$_cf_d_type" response="$(_post "" "$_cf_purl")" _debug2 response "$response" } #checks if cf server is available _ns_is_available_cf() { if _get "https://cloudflare-dns.com" "" 10 >/dev/null; then return 0 else return 1 fi } _ns_is_available_google() { if _get "https://dns.google" "" 10 >/dev/null; then return 0 else return 1 fi } #domain, type _ns_lookup_google() { _cf_ld="$1" _cf_ld_type="$2" _cf_ep="https://dns.google/resolve" _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" } _ns_is_available_ali() { if _get "https://dns.alidns.com" "" 10 >/dev/null; then return 0 else return 1 fi } #domain, type _ns_lookup_ali() { _cf_ld="$1" _cf_ld_type="$2" _cf_ep="https://dns.alidns.com/resolve" _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" } _ns_is_available_dp() { if _get "https://doh.pub" "" 10 >/dev/null; then return 0 else return 1 fi } #dnspod _ns_lookup_dp() { _cf_ld="$1" _cf_ld_type="$2" _cf_ep="https://doh.pub/dns-query" _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type" } _ns_select_doh() { if [ -z "$DOH_USE" ]; then _debug "Detecting DNS server first." if _ns_is_available_cf; then _debug "Using Cloudflare doh server" export DOH_USE=$DOH_CLOUDFLARE elif _ns_is_available_google; then _debug "Using Google DOH server" export DOH_USE=$DOH_GOOGLE elif _ns_is_available_ali; then _debug "Using Aliyun DOH server" export DOH_USE=$DOH_ALI elif _ns_is_available_dp; then _debug "Using DNS POD DOH server" export DOH_USE=$DOH_DP else _err "No DOH" fi fi } #domain, type _ns_lookup() { _ns_select_doh if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then _ns_lookup_cf "$@" elif [ "$DOH_USE" = "$DOH_GOOGLE" ]; then _ns_lookup_google "$@" elif [ "$DOH_USE" = "$DOH_ALI" ]; then _ns_lookup_ali "$@" elif [ "$DOH_USE" = "$DOH_DP" ]; then _ns_lookup_dp "$@" else _err "Unknown DOH provider: DOH_USE=$DOH_USE" fi } #txtdomain, alias, txt __check_txt() { _c_txtdomain="$1" _c_aliasdomain="$2" _c_txt="$3" _debug "_c_txtdomain" "$_c_txtdomain" _debug "_c_aliasdomain" "$_c_aliasdomain" _debug "_c_txt" "$_c_txt" _ns_select_doh _answers="$(_ns_lookup "$_c_aliasdomain" TXT)" _contains "$_answers" "$_c_txt" } #txtdomain __purge_txt() { _p_txtdomain="$1" _debug _p_txtdomain "$_p_txtdomain" if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then _ns_purge_cf "$_p_txtdomain" "TXT" else _debug "No purge API for this DOH API, just sleeping for 5 seconds" _sleep 5 fi } #wait and check each dns entries _check_dns_entries() { _success_txt="," _end_time="$(_time)" _end_time="$(_math "$_end_time" + 1200)" #let's check no more than 20 minutes. while [ "$(_time)" -le "$_end_time" ]; do _info "You can use '--dnssleep' to disable public dns checks." _info "See: $_DNSCHECK_WIKI" _left="" for entry in $dns_entries; do d=$(_getfield "$entry" 1) txtdomain=$(_getfield "$entry" 2) txtdomain=$(_idn "$txtdomain") aliasDomain=$(_getfield "$entry" 3) aliasDomain=$(_idn "$aliasDomain") txt=$(_getfield "$entry" 5) d_api=$(_getfield "$entry" 6) _debug "d" "$d" _debug "txtdomain" "$txtdomain" _debug "aliasDomain" "$aliasDomain" _debug "txt" "$txt" _debug "d_api" "$d_api" _info "Checking $d for $aliasDomain" if _contains "$_success_txt" ",$txt,"; then _info "Already succeeded, continuing." continue fi if __check_txt "$txtdomain" "$aliasDomain" "$txt"; then _info "Success for domain $d '$aliasDomain'." _success_txt="$_success_txt,$txt," continue fi _left=1 _info "Not valid yet, let's wait for 10 seconds then check the next one." __purge_txt "$txtdomain" if [ "$txtdomain" != "$aliasDomain" ]; then __purge_txt "$aliasDomain" fi _sleep 10 done if [ "$_left" ]; then _info "Let's wait for 10 seconds and check again". _sleep 10 else _info "All checks succeeded" return 0 fi done _info "Timed out waiting for DNS." return 1 } #file _get_chain_issuers() { _cfile="$1" if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 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 -i 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 else _cindex=1 for _startn in $(grep -n -- "$BEGIN_CERT" "$_cfile" | cut -d : -f 1); do _endn="$(grep -n -- "$END_CERT" "$_cfile" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)" _debug2 "_startn" "$_startn" _debug2 "_endn" "$_endn" if [ "$DEBUG" ]; then _debug2 "cert$_cindex" "$(sed -n "$_startn,${_endn}p" "$_cfile")" fi sed -n "$_startn,${_endn}p" "$_cfile" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 | sed "s/ *\(.*\)/\1/" _cindex=$(_math $_cindex + 1) done fi } # _get_chain_subjects() { _cfile="$1" if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 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 -i 'Subject:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 else _cindex=1 for _startn in $(grep -n -- "$BEGIN_CERT" "$_cfile" | cut -d : -f 1); do _endn="$(grep -n -- "$END_CERT" "$_cfile" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)" _debug2 "_startn" "$_startn" _debug2 "_endn" "$_endn" if [ "$DEBUG" ]; then _debug2 "cert$_cindex" "$(sed -n "$_startn,${_endn}p" "$_cfile")" fi sed -n "$_startn,${_endn}p" "$_cfile" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep -i 'Subject:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 | sed "s/ *\(.*\)/\1/" _cindex=$(_math $_cindex + 1) done fi } #cert issuer _match_issuer() { _cfile="$1" _missuer="$2" _fissuers="$(_get_chain_issuers $_cfile)" _debug2 _fissuers "$_fissuers" _rootissuer="$(echo "$_fissuers" | _lower_case | _tail_n 1)" _debug2 _rootissuer "$_rootissuer" _missuer="$(echo "$_missuer" | _lower_case)" _contains "$_rootissuer" "$_missuer" } #ip _isIPv4() { for seg in $(echo "$1" | tr '.' ' '); do _debug2 seg "$seg" if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then #not all number return 1 fi if [ $seg -ge 0 ] && [ $seg -lt 256 ]; then continue fi return 1 done return 0 } #ip6 _isIPv6() { _contains "$1" ":" } #ip _isIP() { _isIPv4 "$1" || _isIPv6 "$1" } #identifier _getIdType() { if _isIP "$1"; then echo "$ID_TYPE_IP" else echo "$ID_TYPE_DNS" fi } # beginTime dateTo # beginTime is full string format("2022-04-01T08:10:33Z"), beginTime can be empty, to use current time # dateTo can be ether in full string format("2022-04-01T08:10:33Z") or in delta format(+5d or +20h) _convertValidaty() { _beginTime="$1" _dateTo="$2" _debug2 "_beginTime" "$_beginTime" _debug2 "_dateTo" "$_dateTo" if _startswith "$_dateTo" "+"; then _v_begin=$(_time) if [ "$_beginTime" ]; then _v_begin="$(_date2time "$_beginTime")" fi _debug2 "_v_begin" "$_v_begin" if _endswith "$_dateTo" "h"; then _v_end=$(_math "$_v_begin + 60 * 60 * $(echo "$_dateTo" | tr -d '+h')") elif _endswith "$_dateTo" "d"; then _v_end=$(_math "$_v_begin + 60 * 60 * 24 * $(echo "$_dateTo" | tr -d '+d')") else _err "Unrecognized format for _dateTo: $_dateTo" return 1 fi _debug2 "_v_end" "$_v_end" _time2str "$_v_end" else if [ "$(_time)" -gt "$(_date2time "$_dateTo")" ]; then _err "The validity end date is in the past: _dateTo = $_dateTo" return 1 fi echo "$_dateTo" fi } #webroot, domain domainlist keylength issue() { if [ -z "$2" ]; then _usage "Usage: $PROJECT_ENTRY --issue --domain --webroot " return 1 fi if [ -z "$1" ]; then _usage "Please specify at least one validation method: '--webroot', '--standalone', '--apache', '--nginx' or '--dns' etc." return 1 fi _web_roots="$1" _main_domain="$2" _alt_domains="$3" if _contains "$_main_domain" ","; then _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" _real_ca="$7" _reload_cmd="$8" _real_fullchain="$9" _pre_hook="${10}" _post_hook="${11}" _renew_hook="${12}" _local_addr="${13}" _challenge_alias="${14}" _preferred_chain="${15}" _valid_from="${16}" _valid_to="${17}" if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" mkdir -p "$DOMAIN_PATH" elif ! _hasfield "$_web_roots" "$W_DNS"; then Le_OrderFinalize="" Le_LinkOrder="" Le_LinkCert="" fi if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then _err "$_DNS_MANUAL_ERROR" return 1 fi if [ -f "$DOMAIN_CONF" ]; then Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime) _debug Le_NextRenewTime "$Le_NextRenewTime" if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then _valid_to_saved=$(_readdomainconf Le_Valid_to) if [ "$_valid_to_saved" ] && ! _startswith "$_valid_to_saved" "+"; then _info "The domain is set to be valid to: $_valid_to_saved" _info "It cannot be renewed automatically" _info "See: $_VALIDITY_WIKI" return $RENEW_SKIP fi _saved_domain=$(_readdomainconf Le_Domain) _debug _saved_domain "$_saved_domain" _saved_alt=$(_readdomainconf Le_Alt) _debug _saved_alt "$_saved_alt" _normized_saved_domains="$(echo "$_saved_domain,$_saved_alt" | tr "," "\n" | sort | tr '\n' ',')" _debug _normized_saved_domains "$_normized_saved_domains" _normized_domains="$(echo "$_main_domain,$_alt_domains" | tr "," "\n" | sort | tr '\n' ',')" _debug _normized_domains "$_normized_domains" if [ "$_normized_saved_domains" = "$_normized_domains" ]; then _info "Domains not changed." _info "Skipping. Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")" _info "Add '$(__red '--force')' to force renewal." return $RENEW_SKIP else _info "Domains have changed." fi fi fi _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY" if ! _initAPI; then return 1 fi _savedomainconf "Le_Domain" "$_main_domain" _savedomainconf "Le_Alt" "$_alt_domains" _savedomainconf "Le_Webroot" "$_web_roots" _savedomainconf "Le_PreHook" "$_pre_hook" "base64" _savedomainconf "Le_PostHook" "$_post_hook" "base64" _savedomainconf "Le_RenewHook" "$_renew_hook" "base64" if [ "$_local_addr" ]; then _savedomainconf "Le_LocalAddress" "$_local_addr" else _cleardomainconf "Le_LocalAddress" fi if [ "$_challenge_alias" ]; then _savedomainconf "Le_ChallengeAlias" "$_challenge_alias" else _cleardomainconf "Le_ChallengeAlias" fi if [ "$_preferred_chain" ]; then _savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64" else _cleardomainconf "Le_Preferred_Chain" fi Le_API="$ACME_DIRECTORY" _savedomainconf "Le_API" "$Le_API" _info "Using CA: $ACME_DIRECTORY" if [ "$_alt_domains" = "$NO_VALUE" ]; then _alt_domains="" fi if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then _err "_on_before_issue." return 1 fi _saved_account_key_hash="$(_readcaconf "CA_KEY_HASH")" _debug2 _saved_account_key_hash "$_saved_account_key_hash" 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 fi else _debug "_saved_account_key_hash was not changed, skipping account registration." fi export Le_Next_Domain_Key="$CERT_KEY_PATH.next" if [ -f "$CSR_PATH" ] && [ ! -f "$CERT_KEY_PATH" ]; then _info "Signing from existing CSR." else # When renewing from an old version, the empty Le_Keylength means 2048. # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over # time but an empty value implies 2048 specifically. _key=$(_readdomainconf Le_Keylength) if [ -z "$_key" ]; then _key=2048 fi _debug "Read key length: $_key" if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then if [ "$Le_ForceNewDomainKey" = "1" ] && [ -f "$Le_Next_Domain_Key" ]; then _info "Using pre-generated key: $Le_Next_Domain_Key" cat "$Le_Next_Domain_Key" >"$CERT_KEY_PATH" echo "" >"$Le_Next_Domain_Key" else if ! createDomainKey "$_main_domain" "$_key_length"; then _err "Error creating domain key." _clearup _on_issue_err "$_post_hook" return 1 fi fi fi if [ "$Le_ForceNewDomainKey" ]; then _info "Generating next pre-generate key." if [ ! -e "$Le_Next_Domain_Key" ]; then touch "$Le_Next_Domain_Key" chmod 600 "$Le_Next_Domain_Key" fi if ! _createkey "$_key_length" "$Le_Next_Domain_Key"; then _err "Cannot pre-generate domain key" return 1 fi fi if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then _err "Error creating CSR." _clearup _on_issue_err "$_post_hook" return 1 fi fi _savedomainconf "Le_Keylength" "$_key_length" vlist="$Le_Vlist" _cleardomainconf "Le_Vlist" _debug "Getting domain auth token for each domain" sep='#' dvsep=',' if [ -z "$vlist" ]; then #make new order request _identifiers="{\"type\":\"$(_getIdType "$_main_domain")\",\"value\":\"$(_idn "$_main_domain")\"}" _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\":\"$(_getIdType "$d")\",\"value\":\"$(_idn "$d")\"}" done _debug2 _identifiers "$_identifiers" _notBefore="" _notAfter="" if [ "$_valid_from" ]; then _savedomainconf "Le_Valid_From" "$_valid_from" _debug2 "_valid_from" "$_valid_from" _notBefore="$(_convertValidaty "" "$_valid_from")" if [ "$?" != "0" ]; then _err "Cannot parse _valid_from: $_valid_from" return 1 fi if [ "$(_time)" -gt "$(_date2time "$_notBefore")" ]; then _notBefore="" fi else _cleardomainconf "Le_Valid_From" fi _debug2 _notBefore "$_notBefore" if [ "$_valid_to" ]; then _debug2 "_valid_to" "$_valid_to" _savedomainconf "Le_Valid_To" "$_valid_to" _notAfter="$(_convertValidaty "$_notBefore" "$_valid_to")" if [ "$?" != "0" ]; then _err "Cannot parse _valid_to: $_valid_to" return 1 fi else _cleardomainconf "Le_Valid_To" fi _debug2 "_notAfter" "$_notAfter" _newOrderObj="{\"identifiers\": [$_identifiers]" if [ "$_notBefore" ]; then _newOrderObj="$_newOrderObj,\"notBefore\": \"$_notBefore\"" fi if [ "$_notAfter" ]; then _newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\"" fi _debug "STEP 1, Ordering a Certificate" if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then _err "Error creating new order." _clearup _on_issue_err "$_post_hook" return 1 fi if _contains "$response" "invalid"; then if echo "$response" | _normalizeJson | grep '"status":"invalid"' >/dev/null 2>&1; then _err "Create new order with invalid status." _err "$response" _clearup _on_issue_err "$_post_hook" return 1 fi fi Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)" _debug Le_LinkOrder "$Le_LinkOrder" Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)" _debug Le_OrderFinalize "$Le_OrderFinalize" if [ -z "$Le_OrderFinalize" ]; then _err "Error creating new order. Le_OrderFinalize not found. $response" _clearup _on_issue_err "$_post_hook" return 1 fi #for dns manual mode _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize" _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" _debug2 _authorizations_seg "$_authorizations_seg" if [ -z "$_authorizations_seg" ]; then _err "_authorizations_seg not found." _clearup _on_issue_err "$_post_hook" return 1 fi _debug "STEP 2, Get the authorizations of each domain" #domain and authz map _authorizations_map="" for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do _debug2 "_authz_url" "$_authz_url" if ! _send_signed_request "$_authz_url"; then _err "Error getting authz." _err "_authorizations_seg" "$_authorizations_seg" _err "_authz_url" "$_authz_url" _err "$response" _clearup _on_issue_err "$_post_hook" return 1 fi response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" if echo "$response" | grep '"status":"invalid"' >/dev/null 2>&1; then _err "get authz objec with invalid status, please try again later." _err "_authorizations_seg" "$_authorizations_seg" _err "$response" _clearup _on_issue_err "$_post_hook" return 1 fi _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2- | tr -d ' "')" if _contains "$response" "\"wildcard\" *: *true"; then _d="*.$_d" fi _debug2 _d "$_d" _authorizations_map="$_d,$response#$_authz_url $_authorizations_map" done _debug2 _authorizations_map "$_authorizations_map" _index=0 _currentRoot="" _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)" _debug _w "$_w" if [ "$_w" ]; then _currentRoot="$_w" fi _debug "_currentRoot" "$_currentRoot" vtype="$VTYPE_HTTP" #todo, v2 wildcard force to use dns if _startswith "$_currentRoot" "$W_DNS"; then vtype="$VTYPE_DNS" fi if [ "$_currentRoot" = "$W_ALPN" ]; then vtype="$VTYPE_ALPN" fi _idn_d="$(_idn "$d")" _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")" _debug2 _candidates "$_candidates" if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then for _can in $_candidates; do if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then _candidates="$_can" break fi done fi response="$(echo "$_candidates" | sed "s/$_idn_d,//")" _debug2 "response" "$response" if [ -z "$response" ]; then _err "Error getting authz." _err "_authorizations_map" "$_authorizations_map" _clearup _on_issue_err "$_post_hook" return 1 fi _authz_url="$(echo "$_candidates" | sed "s/$_idn_d,//" | _egrep_o "#.*" | sed "s/^#//")" _debug _authz_url "$_authz_url" if [ -z "$thumbprint" ]; then thumbprint="$(__calc_account_thumbprint)" fi keyauthorization="" if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then _debug "$d is already valid." keyauthorization="$STATE_VERIFIED" _debug keyauthorization "$keyauthorization" fi entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$keyauthorization" -a -z "$entry" ]; then _err "Cannot get domain token entry $d for $vtype" _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')" if [ "$_supported_vtypes" ]; then _err "Supported validation types are: $_supported_vtypes, but you specified: $vtype" fi _clearup _on_issue_err "$_post_hook" return 1 fi if [ -z "$keyauthorization" ]; then token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')" _debug token "$token" if [ -z "$token" ]; then _err "Cannot get domain token $entry" _clearup _on_issue_err "$_post_hook" return 1 fi uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)" _debug uri "$uri" if [ -z "$uri" ]; then _err "Cannot get domain URI $entry" _clearup _on_issue_err "$_post_hook" return 1 fi keyauthorization="$token.$thumbprint" _debug keyauthorization "$keyauthorization" fi dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url" _debug dvlist "$dvlist" vlist="$vlist$dvlist$dvsep" done _debug vlist "$vlist" #add entry dns_entries="" dnsadded="" ventries=$(echo "$vlist" | tr "$dvsep" ' ') _alias_index=1 for ventry in $ventries; do d=$(echo "$ventry" | cut -d "$sep" -f 1) keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) _authz_url=$(echo "$ventry" | cut -d "$sep" -f 6) _debug d "$d" if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then _debug "$d has already been verified, skipping $vtype." _alias_index="$(_math "$_alias_index" + 1)" continue fi if [ "$vtype" = "$VTYPE_DNS" ]; then dnsadded='0' _dns_root_d="$d" if _startswith "$_dns_root_d" "*."; then _dns_root_d="$(echo "$_dns_root_d" | sed 's/*.//')" fi _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")" test "$_d_alias" = "$NO_VALUE" && _d_alias="" _alias_index="$(_math "$_alias_index" + 1)" _debug "_d_alias" "$_d_alias" if [ "$_d_alias" ]; then if _startswith "$_d_alias" "$DNS_ALIAS_PREFIX"; then txtdomain="$(echo "$_d_alias" | sed "s/$DNS_ALIAS_PREFIX//")" else txtdomain="_acme-challenge.$_d_alias" fi dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$txtdomain$dvsep$_currentRoot" else txtdomain="_acme-challenge.$_dns_root_d" dns_entry="${_dns_root_d}${dvsep}_acme-challenge.$_dns_root_d$dvsep$dvsep$_currentRoot" fi _debug txtdomain "$txtdomain" txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)" _debug txt "$txt" d_api="$(_findHook "$_dns_root_d" $_SUB_FOLDER_DNSAPI "$_currentRoot")" _debug d_api "$d_api" dns_entry="$dns_entry$dvsep$txt${dvsep}$d_api" _debug2 dns_entry "$dns_entry" if [ "$d_api" ]; then _debug "Found domain API file: $d_api" else if [ "$_currentRoot" != "$W_DNS" ]; then _err "Cannot 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")'")" _info "$(__red "Please make sure to prepend '_acme-challenge.' to your domain")" _info "$(__red "so that the resulting subdomain is: $txtdomain")" continue fi ( if ! . "$d_api"; then _err "Error loading file $d_api. Please check your API file and try again." return 1 fi addcommand="${_currentRoot}_add" if ! _exists "$addcommand"; then _err "It seems that your API file is incorrect. Make sure it has a function named: $addcommand" return 1 fi _info "Adding TXT value: $txt for domain: $txtdomain" if ! $addcommand "$txtdomain" "$txt"; then _err "Error adding TXT record to domain: $txtdomain" return 1 fi _info "The TXT record has been successfully added." ) if [ "$?" != "0" ]; then _on_issue_err "$_post_hook" "$vlist" _clearup return 1 fi dns_entries="$dns_entries$dns_entry " _debug2 "$dns_entries" dnsadded='1' fi done if [ "$dnsadded" = '0' ]; then _savedomainconf "Le_Vlist" "$vlist" _debug "DNS record not yet added. Will save to $DOMAIN_CONF and exit." _err "Please add the TXT records to the domains, and re-run with --renew." _on_issue_err "$_post_hook" _clearup # If asked to be in manual DNS mode, flag this exit with a separate # error so it can be distinguished from other failures. return $CODE_DNS_MANUAL fi fi if [ "$dns_entries" ]; then if [ -z "$Le_DNSSleep" ]; then _info "Let's check each DNS record now. Sleeping for 20 seconds first." _sleep 20 if ! _check_dns_entries; then _err "Error checking DNS." _on_issue_err "$_post_hook" _clearup return 1 fi else _savedomainconf "Le_DNSSleep" "$Le_DNSSleep" _info "Sleeping for $(__green $Le_DNSSleep) seconds to wait for the the TXT records to take effect" _sleep "$Le_DNSSleep" fi fi NGINX_RESTORE_VLIST="" _debug "OK, let's start verification" _ncIndex=1 ventries=$(echo "$vlist" | tr "$dvsep" ' ') for ventry in $ventries; do d=$(echo "$ventry" | cut -d "$sep" -f 1) keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) uri=$(echo "$ventry" | cut -d "$sep" -f 3) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) _authz_url=$(echo "$ventry" | cut -d "$sep" -f 6) if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then _info "$d is already verified, skipping $vtype." continue fi _info "Verifying: $d" _debug "d" "$d" _debug "keyauthorization" "$keyauthorization" _debug "uri" "$uri" _debug "_authz_url" "$_authz_url" removelevel="" token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)" _debug "_currentRoot" "$_currentRoot" if [ "$vtype" = "$VTYPE_HTTP" ]; then if [ "$_currentRoot" = "$NO_VALUE" ]; then _info "Standalone mode server" _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")" _ncIndex="$(_math $_ncIndex + 1)" _startserver "$keyauthorization" "$_ncaddr" if [ "$?" != "0" ]; then _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi sleep 1 _debug serverproc "$serverproc" elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then _info "Stateless mode for domain: $d" _sleep 1 elif _startswith "$_currentRoot" "$NGINX"; then _info "Nginx mode for domain: $d" #set up nginx server FOUND_REAL_NGINX_CONF="" BACKUP_NGINX_CONF="" if ! _setNginx "$d" "$_currentRoot" "$thumbprint"; then _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi if [ "$FOUND_REAL_NGINX_CONF" ]; then _realConf="$FOUND_REAL_NGINX_CONF" _backup="$BACKUP_NGINX_CONF" _debug _realConf "$_realConf" NGINX_RESTORE_VLIST="$d$sep$_realConf$sep$_backup$dvsep$NGINX_RESTORE_VLIST" fi _sleep 1 else if [ "$_currentRoot" = "apache" ]; then wellknown_path="$ACME_DIR" else wellknown_path="$_currentRoot/.well-known/acme-challenge" if [ ! -d "$_currentRoot/.well-known" ]; then removelevel='1' elif [ ! -d "$_currentRoot/.well-known/acme-challenge" ]; then removelevel='2' else removelevel='3' fi fi _debug wellknown_path "$wellknown_path" _debug "Writing token: $token to $wellknown_path/$token" mkdir -p "$wellknown_path" if ! printf "%s" "$keyauthorization" >"$wellknown_path/$token"; then _err "$d: Cannot write token to file: $wellknown_path/$token" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi if ! chmod a+r "$wellknown_path/$token"; then _debug "chmod failed, will just continue." fi fi elif [ "$vtype" = "$VTYPE_ALPN" ]; then acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")" _debug acmevalidationv1 "$acmevalidationv1" if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then _err "Error starting TLS server." _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi fi if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then _err "$d: Cannot get challenge: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi if [ "$code" ] && [ "$code" != '202' ]; then if [ "$code" = '200' ]; then _debug "Trigger validation code: $code" else _err "$d: Challenge error: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi fi waittimes=0 if [ -z "$MAX_RETRY_TIMES" ]; then MAX_RETRY_TIMES=30 fi _debug "Let's check the authz status" while true; do waittimes=$(_math "$waittimes" + 1) if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then _err "$d: Timeout" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi _debug2 original "$response" response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"') _debug2 status "$status" if _contains "$status" "invalid"; then error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')" _debug2 error "$error" errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)" _debug2 errordetail "$errordetail" if [ "$errordetail" ]; then _err "$d: Invalid status. Verification error details: $errordetail" else _err "$d: Invalid status, Verification error: $error" fi if [ "$DEBUG" ]; then if [ "$vtype" = "$VTYPE_HTTP" ]; then _debug "Debug: GET token URL." _get "http://$d/.well-known/acme-challenge/$token" "" 1 fi fi _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi if _contains "$status" "valid"; then _info "$(__green Success)" _stopserver "$serverproc" serverproc="" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" break fi if _contains "$status" "pending"; then _info "Pending. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)" elif _contains "$status" "processing"; then _info "Processing. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)" else _err "$d: Unknown status: $status. Verification error: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi _debug "Sleep 2 seconds before verifying again" _sleep 2 _debug "Checking" _send_signed_request "$_authz_url" if [ "$?" != "0" ]; then _err "$d: Invalid code. Verification error: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') _sleep_overload_retry_sec=$_retryafter if [ "$_sleep_overload_retry_sec" ]; then if [ $_sleep_overload_retry_sec -le 600 ]; then _sleep $_sleep_overload_retry_sec else _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore." _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi fi done done _clearup _info "Verification finished, beginning signing." der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)" _info "Let's finalize the order." _info "Le_OrderFinalize" "$Le_OrderFinalize" if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then _err "Signing failed." _on_issue_err "$_post_hook" return 1 fi if [ "$code" != "200" ]; then _err "Signing failed. Finalize code was not 200." _err "$response" _on_issue_err "$_post_hook" return 1 fi if [ -z "$Le_LinkOrder" ]; then Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n \t" | cut -d ":" -f 2-)" fi _savedomainconf "Le_LinkOrder" "$Le_LinkOrder" _link_cert_retry=0 _MAX_CERT_RETRY=30 while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do if _contains "$response" "\"status\":\"valid\""; then _debug "Order status is valid." Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)" _debug Le_LinkCert "$Le_LinkCert" if [ -z "$Le_LinkCert" ]; then _err "A signing error occurred: could not find Le_LinkCert" _err "$response" _on_issue_err "$_post_hook" return 1 fi break elif _contains "$response" "\"processing\""; then _info "Order status is 'processing', let's sleep and retry." _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') _debug "_retryafter" "$_retryafter" if [ "$_retryafter" ]; then _info "Sleeping for $_retryafter seconds then retrying" _sleep $_retryafter else _sleep 2 fi else _err "Signing error: wrong status" _err "$response" _on_issue_err "$_post_hook" return 1 fi #the order is processing, so we are going to poll order status if [ -z "$Le_LinkOrder" ]; then _err "Signing error: could not get order link location header" _err "responseHeaders" "$responseHeaders" _on_issue_err "$_post_hook" return 1 fi _info "Polling order status: $Le_LinkOrder" if ! _send_signed_request "$Le_LinkOrder"; then _err "Signing failed. Could not make POST request to Le_LinkOrder for cert: $Le_LinkOrder." _err "$response" _on_issue_err "$_post_hook" return 1 fi _link_cert_retry="$(_math $_link_cert_retry + 1)" done if [ -z "$Le_LinkCert" ]; then _err "Signing failed. Could not get Le_LinkCert, and stopped retrying after reaching the retry limit." _err "$response" _on_issue_err "$_post_hook" return 1 fi _info "Downloading cert." _info "Le_LinkCert" "$Le_LinkCert" if ! _send_signed_request "$Le_LinkCert"; then _err "Signing failed. Could not download cert: $Le_LinkCert." _err "$response" _on_issue_err "$_post_hook" return 1 fi echo "$response" >"$CERT_PATH" _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH" if [ -z "$_preferred_chain" ]; then _preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN) fi if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then if [ "$DEBUG" ]; then _debug "Default chain issuers: " "$(_get_chain_issuers "$CERT_FULLCHAIN_PATH")" fi if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)" _debug2 "rels" "$rels" for rel in $rels; do _info "Trying rel: $rel" if ! _send_signed_request "$rel"; then _err "Signing failed, could not download cert: $rel" _err "$response" continue fi _relcert="$CERT_PATH.alt" _relfullchain="$CERT_FULLCHAIN_PATH.alt" _relca="$CA_CERT_PATH.alt" echo "$response" >"$_relcert" _split_cert_chain "$_relcert" "$_relfullchain" "$_relca" if [ "$DEBUG" ]; then _debug "rel chain issuers: " "$(_get_chain_issuers "$_relfullchain")" fi if _match_issuer "$_relfullchain" "$_preferred_chain"; then _info "Matched issuer in: $rel" cat $_relcert >"$CERT_PATH" cat $_relfullchain >"$CERT_FULLCHAIN_PATH" cat $_relca >"$CA_CERT_PATH" rm -f "$_relcert" rm -f "$_relfullchain" rm -f "$_relca" break fi rm -f "$_relcert" rm -f "$_relfullchain" rm -f "$_relca" done fi fi _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 "Signing 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" _info "Your cert is in: $(__green "$CERT_PATH")" if [ -f "$CERT_KEY_PATH" ]; then _info "Your cert key is in: $(__green "$CERT_KEY_PATH")" fi if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then USER_PATH="$PATH" _saveaccountconf "USER_PATH" "$USER_PATH" fi fi [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in: $(__green "$CA_CERT_PATH")" [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full-chain cert is in: $(__green "$CERT_FULLCHAIN_PATH")" if [ "$Le_ForceNewDomainKey" ] && [ -e "$Le_Next_Domain_Key" ]; then _info "Your pre-generated key for future cert key changes is in: $(__green "$Le_Next_Domain_Key")" fi Le_CertCreateTime=$(_time) _savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime" Le_CertCreateTimeStr=$(_time2str "$Le_CertCreateTime") _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr" if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then Le_RenewalDays="$DEFAULT_RENEW" else _savedomainconf "Le_RenewalDays" "$Le_RenewalDays" fi if [ "$CA_BUNDLE" ]; then _saveaccountconf CA_BUNDLE "$CA_BUNDLE" else _clearaccountconf "CA_BUNDLE" fi if [ "$CA_PATH" ]; then _saveaccountconf CA_PATH "$CA_PATH" else _clearaccountconf "CA_PATH" fi if [ "$HTTPS_INSECURE" ]; then _saveaccountconf HTTPS_INSECURE "$HTTPS_INSECURE" else _clearaccountconf "HTTPS_INSECURE" fi if [ "$Le_Listen_V4" ]; then _savedomainconf "Le_Listen_V4" "$Le_Listen_V4" _cleardomainconf Le_Listen_V6 elif [ "$Le_Listen_V6" ]; then _savedomainconf "Le_Listen_V6" "$Le_Listen_V6" _cleardomainconf Le_Listen_V4 fi if [ "$Le_ForceNewDomainKey" = "1" ]; then _savedomainconf "Le_ForceNewDomainKey" "$Le_ForceNewDomainKey" else _cleardomainconf Le_ForceNewDomainKey fi if [ "$_notAfter" ]; then Le_NextRenewTime=$(_date2time "$_notAfter") Le_NextRenewTimeStr="$_notAfter" if [ "$_valid_to" ] && ! _startswith "$_valid_to" "+"; then _info "The domain is set to be valid until: $_valid_to" _info "It cannot be renewed automatically" _info "See: $_VALIDITY_WIKI" else _now=$(_time) _debug2 "_now" "$_now" _lifetime=$(_math $Le_NextRenewTime - $_now) _debug2 "_lifetime" "$_lifetime" if [ $_lifetime -gt 86400 ]; then #if lifetime is logner than one day, it will renew one day before Le_NextRenewTime=$(_math $Le_NextRenewTime - 86400) Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") else #if lifetime is less than 24 hours, it will renew one hour before Le_NextRenewTime=$(_math $Le_NextRenewTime - 3600) Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") fi fi else Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60) Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400) Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") fi _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr" _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime" #convert to pkcs12 if [ "$Le_PFXPassword" ]; then _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword" fi export CERT_PFX_PATH 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" "base64" _savedomainconf "Le_RealFullChainPath" "$_real_fullchain" 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 "Error calling hook." return 1 fi } #in_out_cert out_fullchain out_ca _split_cert_chain() { _certf="$1" _fullchainf="$2" _caf="$3" if [ "$(grep -- "$BEGIN_CERT" "$_certf" | wc -l)" -gt "1" ]; then _debug "Found cert chain" cat "$_certf" >"$_fullchainf" _end_n="$(grep -n -- "$END_CERT" "$_fullchainf" | _head_n 1 | cut -d : -f 1)" _debug _end_n "$_end_n" sed -n "1,${_end_n}p" "$_fullchainf" >"$_certf" _end_n="$(_math $_end_n + 1)" sed -n "${_end_n},9999p" "$_fullchainf" >"$_caf" fi } #domain [isEcc] [server] renew() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then _usage "Usage: $PROJECT_ENTRY --renew --domain [--ecc] [--server server]" return 1 fi _isEcc="$2" _renewServer="$3" _debug "_renewServer" "$_renewServer" _initpath "$Le_Domain" "$_isEcc" _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT} _info "$(__green "Renewing: '$Le_Domain'")" if [ ! -f "$DOMAIN_CONF" ]; then _info "'$Le_Domain' is not an issued domain, skipping." return $RENEW_SKIP fi if [ "$Le_RenewalDays" ]; then _savedomainconf Le_RenewalDays "$Le_RenewalDays" fi . "$DOMAIN_CONF" _debug Le_API "$Le_API" case "$Le_API" in "$CA_LETSENCRYPT_V2_TEST") _info "Switching back to $CA_LETSENCRYPT_V2" Le_API="$CA_LETSENCRYPT_V2" ;; "$CA_BUYPASS_TEST") _info "Switching back to $CA_BUYPASS" Le_API="$CA_BUYPASS" ;; "$CA_GOOGLE_TEST") _info "Switching back to $CA_GOOGLE" Le_API="$CA_GOOGLE" ;; esac if [ "$_server" ]; then Le_API="$_server" fi _info "Renewing using Le_API=$Le_API" _clearAPI _clearCA export ACME_DIRECTORY="$Le_API" #reload ca configs _debug2 "initpath again." _initpath "$Le_Domain" "$_isEcc" if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then _info "Skipping. Next renewal time is: $(__green "$Le_NextRenewTimeStr")" _info "Add '$(__red '--force')' to force renewal." if [ -z "$_ACME_IN_RENEWALL" ]; then if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then _send_notify "Renew $Le_Domain skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP" fi fi return "$RENEW_SKIP" fi if [ "$_ACME_IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then _info "Skipping invalid cert for: $Le_Domain" return $RENEW_SKIP fi _ACME_IS_RENEW="1" Le_ReloadCmd="$(_readdomainconf Le_ReloadCmd)" Le_PreHook="$(_readdomainconf Le_PreHook)" Le_PostHook="$(_readdomainconf Le_PostHook)" Le_RenewHook="$(_readdomainconf Le_RenewHook)" Le_Preferred_Chain="$(_readdomainconf Le_Preferred_Chain)" # When renewing from an old version, the empty Le_Keylength means 2048. # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over # time but an empty value implies 2048 specifically. Le_Keylength="$(_readdomainconf Le_Keylength)" if [ -z "$Le_Keylength" ]; then Le_Keylength=2048 fi issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" res="$?" if [ "$res" != "0" ]; then return "$res" fi if [ "$Le_DeployHook" ]; then _deploy "$Le_Domain" "$Le_DeployHook" res="$?" fi _ACME_IS_RENEW="" if [ -z "$_ACME_IN_RENEWALL" ]; then if [ "$res" = "0" ]; then if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0 fi else if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1 fi fi fi return "$res" } #renewAll [stopRenewOnError] [server] renewAll() { _initpath _clearCA _stopRenewOnError="$1" _debug "_stopRenewOnError" "$_stopRenewOnError" _server="$2" _debug "_server" "$_server" _ret="0" _success_msg="" _error_msg="" _skipped_msg="" _error_level=$NOTIFY_LEVEL_SKIP _notify_code=$RENEW_SKIP _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT} _debug "_set_level" "$_set_level" export _ACME_IN_RENEWALL=1 for di in "${CERT_HOME}"/*.*/; do _debug di "$di" if ! [ -d "$di" ]; then _debug "Not a directory, skipping: $di" continue fi d=$(basename "$di") _debug d "$d" ( if _endswith "$d" "$ECC_SUFFIX"; then _isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2) d=$(echo "$d" | cut -d "$ECC_SEP" -f 1) fi renew "$d" "$_isEcc" "$_server" ) rc="$?" _debug "Return code: $rc" if [ "$rc" = "0" ]; then if [ $_error_level -gt $NOTIFY_LEVEL_RENEW ]; then _error_level="$NOTIFY_LEVEL_RENEW" _notify_code=0 fi if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0 fi fi _success_msg="${_success_msg} $d " elif [ "$rc" = "$RENEW_SKIP" ]; then if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then _error_level="$NOTIFY_LEVEL_SKIP" _notify_code=$RENEW_SKIP fi if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then _send_notify "Renew $d skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP" fi fi _info "Skipped $d" _skipped_msg="${_skipped_msg} $d " else if [ $_error_level -gt $NOTIFY_LEVEL_ERROR ]; then _error_level="$NOTIFY_LEVEL_ERROR" _notify_code=1 fi if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1 fi fi _error_msg="${_error_msg} $d " if [ "$_stopRenewOnError" ]; then _err "Error renewing $d, stopping." _ret="$rc" break else _ret="$rc" _err "Error renewing $d." fi fi done _debug _error_level "$_error_level" _debug _set_level "$_set_level" if [ $_error_level -le $_set_level ]; then if [ -z "$NOTIFY_MODE" ] || [ "$NOTIFY_MODE" = "$NOTIFY_MODE_BULK" ]; then _msg_subject="Renew" if [ "$_error_msg" ]; then _msg_subject="${_msg_subject} Error" _msg_data="Errored certs: ${_error_msg} " fi if [ "$_success_msg" ]; then _msg_subject="${_msg_subject} Success" _msg_data="${_msg_data}Successful certs: ${_success_msg} " fi if [ "$_skipped_msg" ]; then _msg_subject="${_msg_subject} Skipped" _msg_data="${_msg_data}Skipped certs: ${_skipped_msg} " fi _send_notify "$_msg_subject" "$_msg_data" "$NOTIFY_HOOK" "$_notify_code" fi fi return "$_ret" } #csr webroot signcsr() { _csrfile="$1" _csrW="$2" if [ -z "$_csrfile" ] || [ -z "$_csrW" ]; then _usage "Usage: $PROJECT_ENTRY --sign-csr --csr --webroot " return 1 fi _real_cert="$3" _real_key="$4" _real_ca="$5" _reload_cmd="$6" _real_fullchain="$7" _pre_hook="${8}" _post_hook="${9}" _renew_hook="${10}" _local_addr="${11}" _challenge_alias="${12}" _preferred_chain="${13}" _csrsubj=$(_readSubjectFromCSR "$_csrfile") if [ "$?" != "0" ]; then _err "Cannot read subject from CSR: $_csrfile" return 1 fi _debug _csrsubj "$_csrsubj" if _contains "$_csrsubj" ' ' || ! _contains "$_csrsubj" '.'; then _info "It seems that the subject $_csrsubj is not a valid domain name. Dropping it." _csrsubj="" fi _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile") if [ "$?" != "0" ]; then _err "Cannot read domain list from CSR: $_csrfile" return 1 fi _debug "_csrdomainlist" "$_csrdomainlist" if [ -z "$_csrsubj" ]; then _csrsubj="$(_getfield "$_csrdomainlist" 1)" _debug _csrsubj "$_csrsubj" _csrdomainlist="$(echo "$_csrdomainlist" | cut -d , -f 2-)" _debug "_csrdomainlist" "$_csrdomainlist" fi if [ -z "$_csrsubj" ]; then _err "Cannot read subject from CSR: $_csrfile" return 1 fi _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile") if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then _err "Cannot read key length from CSR: $_csrfile" return 1 fi _initpath "$_csrsubj" "$_csrkeylength" mkdir -p "$DOMAIN_PATH" _info "Copying CSR to: $CSR_PATH" cp "$_csrfile" "$CSR_PATH" issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" "$_preferred_chain" } showcsr() { _csrfile="$1" _csrd="$2" if [ -z "$_csrfile" ] && [ -z "$_csrd" ]; then _usage "Usage: $PROJECT_ENTRY --show-csr --csr " return 1 fi _initpath _csrsubj=$(_readSubjectFromCSR "$_csrfile") if [ "$?" != "0" ]; then _err "Cannot read subject from CSR: $_csrfile" return 1 fi if [ -z "$_csrsubj" ]; then _info "The subject is empty" fi _info "Subject=$_csrsubj" _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile") if [ "$?" != "0" ]; then _err "Cannot read domain list from CSR: $_csrfile" return 1 fi _debug "_csrdomainlist" "$_csrdomainlist" _info "SubjectAltNames=$_csrdomainlist" _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile") if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then _err "Cannot read key length from CSR: $_csrfile" return 1 fi _info "KeyLength=$_csrkeylength" } #listraw domain list() { _raw="$1" _domain="$2" _initpath _sep="|" if [ "$_raw" ]; then if [ -z "$_domain" ]; then printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew" fi for di in "${CERT_HOME}"/*.*/; do d=$(basename "$di") _debug d "$d" ( if _endswith "$d" "$ECC_SUFFIX"; then _isEcc="ecc" d=$(echo "$d" | cut -d "$ECC_SEP" -f 1) fi DOMAIN_CONF="$di/$d.conf" if [ -f "$DOMAIN_CONF" ]; then . "$DOMAIN_CONF" _ca="$(_getCAShortName "$Le_API")" if [ -z "$_domain" ]; then printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr" else if [ "$_domain" = "$d" ]; then cat "$DOMAIN_CONF" fi fi fi ) done else if _exists column; then list "raw" "$_domain" | column -t -s "$_sep" else list "raw" "$_domain" | tr "$_sep" '\t' fi fi } _deploy() { _d="$1" _hooks="$2" for _d_api in $(echo "$_hooks" | tr ',' " "); do _deployApi="$(_findHook "$_d" $_SUB_FOLDER_DEPLOY "$_d_api")" if [ -z "$_deployApi" ]; then _err "The deploy hook $_d_api was not found." return 1 fi _debug _deployApi "$_deployApi" if ! ( if ! . "$_deployApi"; then _err "Error loading file $_deployApi. Please check your API file and try again." return 1 fi d_command="${_d_api}_deploy" if ! _exists "$d_command"; then _err "It seems that your API file is not correct. Make sure it has a function named: $d_command" return 1 fi if ! $d_command "$_d" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$CERT_FULLCHAIN_PATH"; then _err "Error deploying for domain: $_d" return 1 fi ); then _err "Error encountered while deploying." return 1 else _info "$(__green Success)" fi done } #domain hooks deploy() { _d="$1" _hooks="$2" _isEcc="$3" if [ -z "$_hooks" ]; then _usage "Usage: $PROJECT_ENTRY --deploy --domain --deploy-hook [--ecc] " return 1 fi _initpath "$_d" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then _err "The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install." _err "Cannot find path: '$DOMAIN_PATH'" return 1 fi _debug2 DOMAIN_CONF "$DOMAIN_CONF" . "$DOMAIN_CONF" _savedomainconf Le_DeployHook "$_hooks" _deploy "$_d" "$_hooks" } installcert() { _main_domain="$1" if [ -z "$_main_domain" ]; then _usage "Usage: $PROJECT_ENTRY --install-cert --domain [--ecc] [--cert-file ] [--key-file ] [--ca-file ] [ --reloadcmd ] [--fullchain-file ]" return 1 fi _real_cert="$2" _real_key="$3" _real_ca="$4" _reload_cmd="$5" _real_fullchain="$6" _isEcc="$7" _initpath "$_main_domain" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then _err "The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install." _err "Cannot find path: '$DOMAIN_PATH'" return 1 fi _savedomainconf "Le_RealCertPath" "$_real_cert" _savedomainconf "Le_RealCACertPath" "$_real_ca" _savedomainconf "Le_RealKeyPath" "$_real_key" _savedomainconf "Le_ReloadCmd" "$_reload_cmd" "base64" _savedomainconf "Le_RealFullChainPath" "$_real_fullchain" export Le_ForceNewDomainKey="$(_readdomainconf Le_ForceNewDomainKey)" export Le_Next_Domain_Key _installcert "$_main_domain" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd" } #domain cert key ca fullchain reloadcmd backup-prefix _installcert() { _main_domain="$1" _real_cert="$2" _real_key="$3" _real_ca="$4" _real_fullchain="$5" _reload_cmd="$6" _backup_prefix="$7" if [ "$_real_cert" = "$NO_VALUE" ]; then _real_cert="" fi if [ "$_real_key" = "$NO_VALUE" ]; then _real_key="" fi if [ "$_real_ca" = "$NO_VALUE" ]; then _real_ca="" fi if [ "$_reload_cmd" = "$NO_VALUE" ]; then _reload_cmd="" fi if [ "$_real_fullchain" = "$NO_VALUE" ]; then _real_fullchain="" fi _backup_path="$DOMAIN_BACKUP_PATH/$_backup_prefix" mkdir -p "$_backup_path" if [ "$_real_cert" ]; then _info "Installing cert to: $_real_cert" if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_cert" "$_backup_path/cert.bak" fi if [ "$CERT_PATH" != "$_real_cert" ]; then cat "$CERT_PATH" >"$_real_cert" || return 1 fi 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" || return 1 else if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_ca" "$_backup_path/ca.bak" fi if [ "$CA_CERT_PATH" != "$_real_ca" ]; then cat "$CA_CERT_PATH" >"$_real_ca" || return 1 fi fi fi if [ "$_real_key" ]; then _info "Installing key to: $_real_key" if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_key" "$_backup_path/key.bak" fi if [ "$CERT_KEY_PATH" != "$_real_key" ]; then if [ -f "$_real_key" ]; then cat "$CERT_KEY_PATH" >"$_real_key" || return 1 else touch "$_real_key" || return 1 chmod 600 "$_real_key" cat "$CERT_KEY_PATH" >"$_real_key" || return 1 fi fi fi if [ "$_real_fullchain" ]; then _info "Installing full chain to: $_real_fullchain" if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_fullchain" "$_backup_path/fullchain.bak" fi if [ "$_real_fullchain" != "$CERT_FULLCHAIN_PATH" ]; then cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1 fi fi if [ "$_reload_cmd" ]; then _info "Running reload cmd: $_reload_cmd" if ( export CERT_PATH export CERT_KEY_PATH export CA_CERT_PATH export CERT_FULLCHAIN_PATH export Le_Domain="$_main_domain" export Le_ForceNewDomainKey export Le_Next_Domain_Key cd "$DOMAIN_PATH" && eval "$_reload_cmd" ); then _info "$(__green "Reload successful")" else _err "Reload error for: $Le_Domain" fi fi } __read_password() { unset _pp prompt="Enter Password:" while IFS= read -p "$prompt" -r -s -n 1 char; do if [ "$char" = $'\0' ]; then break fi prompt='*' _pp="$_pp$char" done echo "$_pp" } _install_win_taskscheduler() { _lesh="$1" _centry="$2" _randomminute="$3" if ! _exists cygpath; then _err "cygpath not found" return 1 fi if ! _exists schtasks; then _err "schtasks.exe was not found, are you on Windows?" return 1 fi _winbash="$(cygpath -w $(which bash))" _debug _winbash "$_winbash" if [ -z "$_winbash" ]; then _err "Cannot find bash path" return 1 fi _myname="$(whoami)" _debug "_myname" "$_myname" if [ -z "$_myname" ]; then _err "Can not find own username" return 1 fi _debug "_lesh" "$_lesh" _info "To install the scheduler task to your Windows account, you must input your Windows password." _info "$PROJECT_NAME will not save your password." _info "Please input your Windows password for: $(__green "$_myname")" _password="$(__read_password)" #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null echo } _uninstall_win_taskscheduler() { if ! _exists schtasks; then _err "schtasks.exe was not found, are you on Windows?" return 1 fi if ! echo SCHTASKS /query /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null; then _debug "scheduler $_WINDOWS_SCHEDULER_NAME was not found." else _info "Removing $_WINDOWS_SCHEDULER_NAME" echo SCHTASKS /delete /f /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null fi } #confighome installcronjob() { _c_home="$1" _initpath _CRONTAB="crontab" if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]; then lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY" else _debug "_SCRIPT_" "$_SCRIPT_" _script="$(_readlink "$_SCRIPT_")" _debug _script "$_script" if [ -f "$_script" ]; then _info "Usinging the current script from: $_script" lesh="$_script" else _err "Cannot install cronjob, $PROJECT_ENTRY not found." return 1 fi fi if [ "$_c_home" ]; then _c_entry="--config-home \"$_c_home\" " fi _t=$(_time) random_minute=$(_math $_t % 60) random_hour=$(_math $_t / 60 % 24) if ! _exists "$_CRONTAB" && _exists "fcrontab"; then _CRONTAB="fcrontab" fi if ! _exists "$_CRONTAB"; then if _exists cygpath && _exists schtasks.exe; then _info "It seems you are on Windows, let's install the Windows scheduler task." if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute"; then _info "Successfully installed Windows scheduler task." return 0 else _err "Failed to install Windows scheduler task." return 1 fi fi _err "crontab/fcrontab doesn't exist, so we cannot install cron jobs." _err "Your certs will not be renewed automatically." _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' every day." return 1 fi _info "Installing cron job" if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then if _exists uname && uname -a | grep SunOS >/dev/null; then _CRONTAB_STDIN="$_CRONTAB --" else _CRONTAB_STDIN="$_CRONTAB -" fi $_CRONTAB -l | { cat echo "$random_minute $random_hour * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" } | $_CRONTAB_STDIN fi if [ "$?" != "0" ]; then _err "Failed to install cron job. You need to manually renew your certs." _err "Alternatively, you can add a cron job by yourself:" _err "$lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null" return 1 fi } uninstallcronjob() { _CRONTAB="crontab" if ! _exists "$_CRONTAB" && _exists "fcrontab"; then _CRONTAB="fcrontab" fi if ! _exists "$_CRONTAB"; then if _exists cygpath && _exists schtasks.exe; then _info "It seems you are on Windows, let's uninstall the Windows scheduler task." if _uninstall_win_taskscheduler; then _info "Successfully uninstalled Windows scheduler task." return 0 else _err "Failed to uninstall Windows scheduler task." return 1 fi fi return fi _info "Removing cron job" cr="$($_CRONTAB -l | grep "$PROJECT_ENTRY --cron")" if [ "$cr" ]; then if _exists uname && uname -a | grep SunOS >/dev/null; then $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB -- else $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB - fi LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 9 | tr -d '"')" _info LE_WORKING_DIR "$LE_WORKING_DIR" if _contains "$cr" "--config-home"; then LE_CONFIG_HOME="$(echo "$cr" | cut -d ' ' -f 11 | tr -d '"')" _debug LE_CONFIG_HOME "$LE_CONFIG_HOME" fi fi _initpath } #domain isECC revokeReason revoke() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then _usage "Usage: $PROJECT_ENTRY --revoke --domain [--ecc]" return 1 fi _isEcc="$2" _reason="$3" if [ -z "$_reason" ]; then _reason="0" fi _initpath "$Le_Domain" "$_isEcc" if [ ! -f "$DOMAIN_CONF" ]; then _err "$Le_Domain is not an issued domain, skipping." return 1 fi if [ ! -f "$CERT_PATH" ]; then _err "Cert for $Le_Domain $CERT_PATH was not found, skipping." return 1 fi . "$DOMAIN_CONF" _debug Le_API "$Le_API" if [ "$Le_API" ]; then if [ "$Le_API" != "$ACME_DIRECTORY" ]; then _clearAPI fi export ACME_DIRECTORY="$Le_API" #reload ca configs ACCOUNT_KEY_PATH="" ACCOUNT_JSON_PATH="" CA_CONF="" _debug3 "initpath again." _initpath "$Le_Domain" "$_isEcc" _initAPI fi cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _url_replace)" if [ -z "$cert" ]; then _err "Cert for $Le_Domain is empty, skipping." return 1 fi _initAPI data="{\"certificate\": \"$cert\",\"reason\":$_reason}" uri="${ACME_REVOKE_CERT}" _info "Trying account key first." if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then if [ -z "$response" ]; then _info "Successfully revoked." rm -f "$CERT_PATH" cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked" cat "$CSR_PATH" >"$CSR_PATH.revoked" return 0 else _err "Error revoking." _debug "$response" fi fi if [ -f "$CERT_KEY_PATH" ]; then _info "Trying domain key." if _send_signed_request "$uri" "$data" "" "$CERT_KEY_PATH"; then if [ -z "$response" ]; then _info "Successfully revoked." rm -f "$CERT_PATH" cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked" cat "$CSR_PATH" >"$CSR_PATH.revoked" return 0 else _err "Error revoking using domain key." _err "$response" fi fi else _info "Domain key file doesn't exist." fi return 1 } #domain ecc remove() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then _usage "Usage: $PROJECT_ENTRY --remove --domain [--ecc]" return 1 fi _isEcc="$2" _initpath "$Le_Domain" "$_isEcc" _removed_conf="$DOMAIN_CONF.removed" if [ ! -f "$DOMAIN_CONF" ]; then if [ -f "$_removed_conf" ]; then _err "$Le_Domain has already been removed. You can remove the folder by yourself: $DOMAIN_PATH" else _err "$Le_Domain is not an issued domain, skipping." fi return 1 fi if mv "$DOMAIN_CONF" "$_removed_conf"; then _info "$Le_Domain has been removed. The key and cert files are in $(__green $DOMAIN_PATH)" _info "You can remove them by yourself." return 0 else _err "Failed to remove $Le_Domain." return 1 fi } #domain vtype _deactivate() { _d_domain="$1" _d_type="$2" _initpath "$_d_domain" "$_d_type" . "$DOMAIN_CONF" _debug Le_API "$Le_API" if [ "$Le_API" ]; then if [ "$Le_API" != "$ACME_DIRECTORY" ]; then _clearAPI fi export ACME_DIRECTORY="$Le_API" #reload ca configs ACCOUNT_KEY_PATH="" ACCOUNT_JSON_PATH="" CA_CONF="" _debug3 "initpath again." _initpath "$Le_Domain" "$_d_type" _initAPI fi _identifiers="{\"type\":\"$(_getIdType "$_d_domain")\",\"value\":\"$_d_domain\"}" if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then _err "Cannot get new order for domain." return 1 fi _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" _debug2 _authorizations_seg "$_authorizations_seg" if [ -z "$_authorizations_seg" ]; then _err "_authorizations_seg not found." _clearup _on_issue_err "$_post_hook" return 1 fi authzUri="$_authorizations_seg" _debug2 "authzUri" "$authzUri" if ! _send_signed_request "$authzUri"; then _err "Error making GET request for authz." _err "_authorizations_seg" "$_authorizations_seg" _err "authzUri" "$authzUri" _clearup _on_issue_err "$_post_hook" return 1 fi response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" _URL_NAME="url" entries="$(echo "$response" | tr '][' '==' | _egrep_o "challenges\": *=[^=]*=" | tr '}{' '\n\n' | grep "\"status\": *\"valid\"")" if [ -z "$entries" ]; then _info "No valid entries found." if [ -z "$thumbprint" ]; then thumbprint="$(__calc_account_thumbprint)" fi _debug "Trigger validation." vtype="$(_getIdType "$_d_domain")" entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$entry" ]; then _err "$d: Cannot get domain token" return 1 fi token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')" _debug token "$token" uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*" | cut -d : -f 2,3 | tr -d '"')" _debug uri "$uri" keyauthorization="$token.$thumbprint" _debug keyauthorization "$keyauthorization" __trigger_validation "$uri" "$keyauthorization" fi _d_i=0 _d_max_retry=$(echo "$entries" | wc -l) while [ "$_d_i" -lt "$_d_max_retry" ]; do _info "Deactivating $_d_domain" _d_i="$(_math $_d_i + 1)" entry="$(echo "$entries" | sed -n "${_d_i}p")" _debug entry "$entry" if [ -z "$entry" ]; then _info "No more valid entries found." break fi _vtype="$(echo "$entry" | _egrep_o '"type": *"[^"]*"' | cut -d : -f 2 | tr -d '"')" _debug _vtype "$_vtype" _info "Found $_vtype" uri="$(echo "$entry" | _egrep_o "\"$_URL_NAME\":\"[^\"]*\"" | tr -d '" ' | cut -d : -f 2-)" _debug uri "$uri" if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then _info "Skipping $_vtype" continue fi _info "Deactivating $_vtype" _djson="{\"status\":\"deactivated\"}" if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then _info "Successfully deactivated $_vtype." else _err "Could not deactivate $_vtype." break fi done _debug "$_d_i" if [ "$_d_i" -eq "$_d_max_retry" ]; then _info "Successfully deactivated!" else _err "Deactivation failed." fi } deactivate() { _d_domain_list="$1" _d_type="$2" _initpath _initAPI _debug _d_domain_list "$_d_domain_list" if [ -z "$(echo $_d_domain_list | cut -d , -f 1)" ]; then _usage "Usage: $PROJECT_ENTRY --deactivate --domain [--domain ...]" return 1 fi for _d_dm in $(echo "$_d_domain_list" | tr ',' ' '); do if [ -z "$_d_dm" ] || [ "$_d_dm" = "$NO_VALUE" ]; then continue fi if ! _deactivate "$_d_dm" "$_d_type"; then return 1 fi done } # Detect profile file if not specified as environment variable _detect_profile() { if [ -n "$PROFILE" -a -f "$PROFILE" ]; then echo "$PROFILE" return fi DETECTED_PROFILE='' SHELLTYPE="$(basename "/$SHELL")" if [ "$SHELLTYPE" = "bash" ]; then if [ -f "$HOME/.bashrc" ]; then DETECTED_PROFILE="$HOME/.bashrc" elif [ -f "$HOME/.bash_profile" ]; then DETECTED_PROFILE="$HOME/.bash_profile" fi elif [ "$SHELLTYPE" = "zsh" ]; then DETECTED_PROFILE="$HOME/.zshrc" fi if [ -z "$DETECTED_PROFILE" ]; then if [ -f "$HOME/.profile" ]; then DETECTED_PROFILE="$HOME/.profile" elif [ -f "$HOME/.bashrc" ]; then DETECTED_PROFILE="$HOME/.bashrc" elif [ -f "$HOME/.bash_profile" ]; then DETECTED_PROFILE="$HOME/.bash_profile" elif [ -f "$HOME/.zshrc" ]; then DETECTED_PROFILE="$HOME/.zshrc" fi fi echo "$DETECTED_PROFILE" } _initconf() { _initpath if [ ! -f "$ACCOUNT_CONF_PATH" ]; then echo " #LOG_FILE=\"$DEFAULT_LOG_FILE\" #LOG_LEVEL=1 #AUTO_UPGRADE=\"1\" #NO_TIMESTAMP=1 " >"$ACCOUNT_CONF_PATH" fi } # nocron _precheck() { _nocron="$1" if ! _exists "curl" && ! _exists "wget"; then _err "Please install curl or wget first to enable access to HTTP resources." return 1 fi if [ -z "$_nocron" ]; then if ! _exists "crontab" && ! _exists "fcrontab"; then if _exists cygpath && _exists schtasks.exe; then _info "It seems you are on Windows, we will install the Windows scheduler task." else _err "It is recommended to install crontab first. Try to install 'cron', 'crontab', 'crontabs' or 'vixie-cron'." _err "We need to set a cron job to renew the certs automatically." _err "Otherwise, your certs will not be able to be renewed automatically." if [ -z "$FORCE" ]; then _err "Please add '--force' and try install again to go without crontab." _err "./$PROJECT_ENTRY --install --force" return 1 fi fi fi fi if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN" _err "We need openssl to generate keys." return 1 fi if ! _exists "socat"; then _err "It is recommended to install socat first." _err "We use socat for the standalone server, which is used for standalone mode." _err "If you don't want to use standalone mode, you may ignore this warning." fi return 0 } _setShebang() { _file="$1" _shebang="$2" if [ -z "$_shebang" ]; then _usage "Usage: file shebang" return 1 fi cp "$_file" "$_file.tmp" echo "$_shebang" >"$_file" sed -n 2,99999p "$_file.tmp" >>"$_file" rm -f "$_file.tmp" } #confighome _installalias() { _c_home="$1" _initpath _envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env" if [ "$_upgrading" ] && [ "$_upgrading" = "1" ]; then echo "$(cat "$_envfile")" | sed "s|^LE_WORKING_DIR.*$||" >"$_envfile" echo "$(cat "$_envfile")" | sed "s|^alias le.*$||" >"$_envfile" echo "$(cat "$_envfile")" | sed "s|^alias le.sh.*$||" >"$_envfile" fi if [ "$_c_home" ]; then _c_entry=" --config-home '$_c_home'" fi _setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\"" if [ "$_c_home" ]; then _setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\"" else _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile" fi _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _profile="$(_detect_profile)" if [ "$_profile" ]; then _debug "Found profile: $_profile" _info "Installing alias to '$_profile'" _setopt "$_profile" ". \"$_envfile\"" _info "Close and reopen your terminal to start using $PROJECT_NAME" else _info "No profile has been found, you will need to change your working directory to $LE_WORKING_DIR to use $PROJECT_NAME" fi #for csh _cshfile="$LE_WORKING_DIR/$PROJECT_ENTRY.csh" _csh_profile="$HOME/.cshrc" if [ -f "$_csh_profile" ]; then _info "Installing alias to '$_csh_profile'" _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\"" if [ "$_c_home" ]; then _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\"" else _sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile" fi _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_csh_profile" "source \"$_cshfile\"" fi #for tcsh _tcsh_profile="$HOME/.tcshrc" if [ -f "$_tcsh_profile" ]; then _info "Installing alias to '$_tcsh_profile'" _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\"" if [ "$_c_home" ]; then _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\"" fi _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_tcsh_profile" "source \"$_cshfile\"" fi } # nocron confighome noprofile accountemail install() { if [ -z "$LE_WORKING_DIR" ]; then LE_WORKING_DIR="$DEFAULT_INSTALL_HOME" fi _nocron="$1" _c_home="$2" _noprofile="$3" _accountemail="$4" if ! _initpath; then _err "Install failed." return 1 fi if [ "$_nocron" ]; then _debug "Skipping cron job installation" fi if [ "$_ACME_IN_CRON" != "1" ]; then if ! _precheck "$_nocron"; then _err "Pre-check failed, cannot install." return 1 fi fi if [ -z "$_c_home" ] && [ "$LE_CONFIG_HOME" != "$LE_WORKING_DIR" ]; then _info "Using config home: $LE_CONFIG_HOME" _c_home="$LE_CONFIG_HOME" fi #convert from le if [ -d "$HOME/.le" ]; then for envfile in "le.env" "le.sh.env"; do if [ -f "$HOME/.le/$envfile" ]; then if grep "le.sh" "$HOME/.le/$envfile" >/dev/null; then _upgrading="1" _info "You are upgrading from le.sh" _info "Renaming \"$HOME/.le\" to $LE_WORKING_DIR" mv "$HOME/.le" "$LE_WORKING_DIR" mv "$LE_WORKING_DIR/$envfile" "$LE_WORKING_DIR/$PROJECT_ENTRY.env" break fi fi done fi _info "Installing to $LE_WORKING_DIR" if [ ! -d "$LE_WORKING_DIR" ]; then if ! mkdir -p "$LE_WORKING_DIR"; then _err "Cannot create working dir: $LE_WORKING_DIR" return 1 fi chmod 700 "$LE_WORKING_DIR" fi if [ ! -d "$LE_CONFIG_HOME" ]; then if ! mkdir -p "$LE_CONFIG_HOME"; then _err "Cannot create config dir: $LE_CONFIG_HOME" return 1 fi chmod 700 "$LE_CONFIG_HOME" fi cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY" if [ "$?" != "0" ]; then _err "Installation failed, cannot copy $PROJECT_ENTRY" return 1 fi _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then _installalias "$_c_home" fi for subf in $_SUB_FOLDERS; do if [ -d "$subf" ]; then mkdir -p "$LE_WORKING_DIR/$subf" cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/ fi done if [ ! -f "$ACCOUNT_CONF_PATH" ]; then _initconf fi if [ "$_DEFAULT_ACCOUNT_CONF_PATH" != "$ACCOUNT_CONF_PATH" ]; then _setopt "$_DEFAULT_ACCOUNT_CONF_PATH" "ACCOUNT_CONF_PATH" "=" "\"$ACCOUNT_CONF_PATH\"" fi if [ "$_DEFAULT_CERT_HOME" != "$CERT_HOME" ]; then _saveaccountconf "CERT_HOME" "$CERT_HOME" fi if [ "$_DEFAULT_ACCOUNT_KEY_PATH" != "$ACCOUNT_KEY_PATH" ]; then _saveaccountconf "ACCOUNT_KEY_PATH" "$ACCOUNT_KEY_PATH" fi if [ -z "$_nocron" ]; then installcronjob "$_c_home" fi if [ -z "$NO_DETECT_SH" ]; then #Modify shebang if _exists bash; then _bash_path="$(bash -c "command -v bash 2>/dev/null")" if [ -z "$_bash_path" ]; then _bash_path="$(bash -c 'echo $SHELL')" fi fi if [ "$_bash_path" ]; then _info "bash has been found. Changing the shebang to use bash as preferred." _shebang='#!'"$_bash_path" _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang" for subf in $_SUB_FOLDERS; do if [ -d "$LE_WORKING_DIR/$subf" ]; then for _apifile in "$LE_WORKING_DIR/$subf/"*.sh; do _setShebang "$_apifile" "$_shebang" done fi done fi fi if [ "$_accountemail" ]; then _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail" fi _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)" _info OK } # nocron uninstall() { _nocron="$1" if [ -z "$_nocron" ]; then uninstallcronjob fi _initpath _uninstallalias rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\". You can remove them by yourself." } _uninstallalias() { _initpath _profile="$(_detect_profile)" if [ "$_profile" ]; then _info "Uninstalling alias from: '$_profile'" text="$(cat "$_profile")" echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.env\"$||" >"$_profile" fi _csh_profile="$HOME/.cshrc" if [ -f "$_csh_profile" ]; then _info "Uninstalling alias from: '$_csh_profile'" text="$(cat "$_csh_profile")" echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_csh_profile" fi _tcsh_profile="$HOME/.tcshrc" if [ -f "$_tcsh_profile" ]; then _info "Uninstalling alias from: '$_csh_profile'" text="$(cat "$_tcsh_profile")" echo "$text" | sed "s|^.*\"$LE_WORKING_DIR/$PROJECT_NAME.csh\"$||" >"$_tcsh_profile" fi } cron() { export _ACME_IN_CRON=1 _initpath _info "$(__green "===Starting cron===")" if [ "$AUTO_UPGRADE" = "1" ]; then export LE_WORKING_DIR ( if ! upgrade; then _err "Cron: Upgrade failed!" return 1 fi ) . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null if [ -t 1 ]; then __INTERACTIVE="1" fi _info "Automatically upgraded to: $VER" fi renewAll _ret="$?" _ACME_IN_CRON="" _info "$(__green "===End cron===")" exit $_ret } version() { echo "$PROJECT" echo "v$VER" } # subject content hooks code _send_notify() { _nsubject="$1" _ncontent="$2" _nhooks="$3" _nerror="$4" if [ "$NOTIFY_LEVEL" = "$NOTIFY_LEVEL_DISABLE" ]; then _debug "The NOTIFY_LEVEL is $NOTIFY_LEVEL, which means it's disabled, so will just return." return 0 fi if [ -z "$_nhooks" ]; then _debug "The NOTIFY_HOOK is empty, will just return." return 0 fi _nsource="$NOTIFY_SOURCE" if [ -z "$_nsource" ]; then _nsource="$(hostname)" fi _nsubject="$_nsubject by $_nsource" _send_err=0 for _n_hook in $(echo "$_nhooks" | tr ',' " "); do _n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")" _info "Sending via: $_n_hook" _debug "Found $_n_hook_file for $_n_hook" if [ -z "$_n_hook_file" ]; then _err "Cannot find the hook file for $_n_hook" continue fi if ! ( if ! . "$_n_hook_file"; then _err "Error loading file $_n_hook_file. Please check your API file and try again." return 1 fi d_command="${_n_hook}_send" if ! _exists "$d_command"; then _err "It seems that your API file is not correct. Make sure it has a function named: $d_command" return 1 fi if ! $d_command "$_nsubject" "$_ncontent" "$_nerror"; then _err "Error sending message using $d_command" return 1 fi return 0 ); then _err "Error setting $_n_hook_file." _send_err=1 else _info "$_n_hook $(__green Success)" fi done return $_send_err } # hook _set_notify_hook() { _nhooks="$1" _test_subject="Hello, this is a notification from $PROJECT_NAME" _test_content="If you receive this message, your notification works." _send_notify "$_test_subject" "$_test_content" "$_nhooks" 0 } #[hook] [level] [mode] setnotify() { _nhook="$1" _nlevel="$2" _nmode="$3" _nsource="$4" _initpath if [ -z "$_nhook$_nlevel$_nmode$_nsource" ]; then _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook ] [--notify-level <0|1|2|3>] [--notify-mode <0|1>] [--notify-source ]" _usage "$_NOTIFY_WIKI" return 1 fi if [ "$_nlevel" ]; then _info "Set notify level to: $_nlevel" export "NOTIFY_LEVEL=$_nlevel" _saveaccountconf "NOTIFY_LEVEL" "$NOTIFY_LEVEL" fi if [ "$_nmode" ]; then _info "Set notify mode to: $_nmode" export "NOTIFY_MODE=$_nmode" _saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE" fi if [ "$_nsource" ]; then _info "Set notify source to: $_nsource" export "NOTIFY_SOURCE=$_nsource" _saveaccountconf "NOTIFY_SOURCE" "$NOTIFY_SOURCE" fi if [ "$_nhook" ]; then _info "Set notify hook to: $_nhook" if [ "$_nhook" = "$NO_VALUE" ]; then _info "Clearing notify hook" _clearaccountconf "NOTIFY_HOOK" else if _set_notify_hook "$_nhook"; then export NOTIFY_HOOK="$_nhook" _saveaccountconf "NOTIFY_HOOK" "$NOTIFY_HOOK" return 0 else _err "Cannot set notify hook to: $_nhook" return 1 fi fi fi } showhelp() { _initpath version echo "Usage: $PROJECT_ENTRY ... [parameters ...] Commands: -h, --help Show this help message. -v, --version Show version info. --install Install $PROJECT_NAME to your system. --uninstall Uninstall $PROJECT_NAME, and uninstall the cron job. --upgrade Upgrade $PROJECT_NAME to the latest code from $PROJECT. --issue Issue a cert. --deploy Deploy the cert to your server. -i, --install-cert Install the issued cert to Apache/nginx or any other server. -r, --renew Renew a cert. --renew-all Renew all the certs. --revoke Revoke a cert. --remove Remove the cert from list of certs known to $PROJECT_NAME. --list List all the certs. --info Show the $PROJECT_NAME configs, or the configs for a domain with [-d domain] parameter. --to-pkcs12 Export the certificate and key to a pfx file. --to-pkcs8 Convert to pkcs8 format. --sign-csr Issue a cert from an existing csr. --show-csr Show the content of a csr. -ccr, --create-csr Create CSR, professional use. --create-domain-key Create an domain private key, professional use. --update-account Update account info. --register-account Register account key. --deactivate-account Deactivate the account. --create-account-key Create an account private key, professional use. --install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. --uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically. --cron Run cron job to renew all the certs. --set-notify Set the cron notification hook, level or mode. --deactivate Deactivate the domain authz, professional use. --set-default-ca Used with '--server', Set the default CA to use. See: $_SERVER_WIKI --set-default-chain Set the default preferred chain for a CA. See: $_PREFERRED_CHAIN_WIKI Parameters: -d, --domain Specifies a domain, used to issue, renew or revoke etc. --challenge-alias The challenge domain alias for DNS alias mode. See: $_DNS_ALIAS_WIKI --domain-alias The domain alias for DNS alias mode. See: $_DNS_ALIAS_WIKI --preferred-chain If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. If no match, the default offered chain will be used. (default: empty) See: $_PREFERRED_CHAIN_WIKI --valid-to Request the NotAfter field of the cert. See: $_VALIDITY_WIKI --valid-from Request the NotBefore field of the cert. See: $_VALIDITY_WIKI -f, --force Force install, force cert renewal or override sudo restrictions. --staging, --test Use staging server, for testing. --debug [0|1|2|3] Output debug info. Defaults to $DEBUG_LEVEL_DEFAULT if argument is omitted. --output-insecure Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for security. -w, --webroot Specifies the web root folder for web root mode. --standalone Use standalone mode. --alpn Use standalone alpn mode. --stateless Use stateless mode. See: $_STATELESS_WIKI --apache Use Apache mode. --dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted. See: $_DNS_API_WIKI --dnssleep The time in seconds to wait for all the txt records to propagate in dns api mode. It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically. -k, --keylength Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521. -ak, --accountkeylength Specifies the account key length: 2048, 3072, 4096 --log [file] Specifies the log file. Defaults to \"$DEFAULT_LOG_FILE\" if argument is omitted. --log-level <1|2> Specifies the log level, default is $DEFAULT_LOG_LEVEL. --syslog <0|3|6|7> Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug. --eab-kid Key Identifier for External Account Binding. --eab-hmac-key HMAC key for External Account Binding. These parameters are to install the cert to nginx/Apache or any other server after issue/renew a cert: --cert-file Path to copy the cert file to after issue/renew. --key-file Path to copy the key file to after issue/renew. --ca-file Path to copy the intermediate cert file to after issue/renew. --fullchain-file Path to copy the fullchain cert file to after issue/renew. --reloadcmd Command to execute after issue/renew to reload the server. --server ACME Directory Resource URI. (default: $DEFAULT_CA) See: $_SERVER_WIKI --accountconf Specifies a customized account config file. --home Specifies the home dir for $PROJECT_NAME. --cert-home Specifies the home dir to save all the certs, only valid for '--install' command. --config-home Specifies the home dir to save all the configurations. --useragent Specifies the user agent string. it will be saved for future use too. -m, --email Specifies the account email, only valid for the '--install' and '--update-account' command. --accountkey Specifies the account key path, only valid for the '--install' command. --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. --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. -se, --stop-renew-on-error Only valid for '--renew-all' command. Stop if one cert has error in renewal. --insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted. --ca-bundle Specifies the path to the CA certificate bundle to verify api server's certificate. --ca-path Specifies directory containing CA certificates in PEM format, used by wget or curl. --no-cron 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-profile Only valid for '--install' command, which means: do not install aliases to user profile. --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 use of the ECC cert. Only valid for '--install-cert', '--renew', '--remove ', '--revoke', '--deploy', '--to-pkcs8', '--to-pkcs12' and '--create-csr'. --csr Specifies the input csr. --pre-hook Command to be run before obtaining any certificates. --post-hook Command to be run after attempting to obtain/renew certificates. Runs regardless of whether obtain/renew succeeded or failed. --renew-hook Command to be run after each successfully renewed certificate. --deploy-hook The hook file to deploy cert --extended-key-usage Manually define the CSR extended key usage value. The default is serverAuth,clientAuth. --ocsp, --ocsp-must-staple Generate OCSP-Must-Staple extension. --always-force-new-domain-key Generate new domain key on renewal. Otherwise, the domain key is not changed by default. --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted. --listen-v4 Force standalone/tls server to listen at ipv4. --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 use of dns manual mode. See: $_DNS_MANUAL_WIKI -b, --branch Only valid for '--upgrade' command, specifies the branch name to upgrade to. --notify-level <0|1|2|3> Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT. 0: disabled, no notification will be sent. 1: send notifications only when there is an error. 2: send notifications when a cert is successfully renewed, or there is an error. 3: send notifications when a cert is skipped, renewed, or error. --notify-mode <0|1> Set notification mode. Default value is $NOTIFY_MODE_DEFAULT. 0: Bulk mode. Send all the domain's notifications in one message(mail). 1: Cert mode. Send a message for every single cert. --notify-hook Set the notify hook --notify-source Set the server name in the notification message --revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command. See: $_REVOKE_WIKI --password Add a password to exported pfx file. Use with --to-pkcs12. " } installOnline() { _info "Installing from online archive." _branch="$BRANCH" if [ -z "$_branch" ]; then _branch="master" fi target="$PROJECT/archive/$_branch.tar.gz" _info "Downloading $target" localname="$_branch.tar.gz" if ! _get "$target" >$localname; then _err "Download error." return 1 fi ( _info "Extracting $localname" if ! (tar xzf $localname || gtar xzf $localname); then _err "Extraction error." exit 1 fi cd "$PROJECT_NAME-$_branch" chmod +x $PROJECT_ENTRY if ./$PROJECT_ENTRY --install "$@"; then _info "Install success!" fi cd .. rm -rf "$PROJECT_NAME-$_branch" rm -f "$localname" ) } _getRepoHash() { _hash_path=$1 shift _hash_url="${PROJECT_API:-https://api.github.com/repos/acmesh-official}/$PROJECT_NAME/git/refs/$_hash_path" _get "$_hash_url" "" 30 | tr -d "\r\n" | tr '{},' '\n\n\n' | grep '"sha":' | cut -d '"' -f 4 } _getUpgradeHash() { _b="$BRANCH" if [ -z "$_b" ]; then _b="master" fi _hash=$(_getRepoHash "heads/$_b") if [ -z "$_hash" ]; then _hash=$(_getRepoHash "tags/$_b"); fi echo $_hash } upgrade() { if ( _initpath [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already up to date!" && exit 0 export LE_WORKING_DIR cd "$LE_WORKING_DIR" installOnline "--nocron" "--noprofile" ); then _info "Upgrade successful!" exit 0 else _err "Upgrade failed!" exit 1 fi } _processAccountConf() { if [ "$_useragent" ]; then _saveaccountconf "USER_AGENT" "$_useragent" elif [ "$USER_AGENT" ] && [ "$USER_AGENT" != "$DEFAULT_USER_AGENT" ]; then _saveaccountconf "USER_AGENT" "$USER_AGENT" fi if [ "$_openssl_bin" ]; then _saveaccountconf "ACME_OPENSSL_BIN" "$_openssl_bin" elif [ "$ACME_OPENSSL_BIN" ] && [ "$ACME_OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then _saveaccountconf "ACME_OPENSSL_BIN" "$ACME_OPENSSL_BIN" fi if [ "$_auto_upgrade" ]; then _saveaccountconf "AUTO_UPGRADE" "$_auto_upgrade" elif [ "$AUTO_UPGRADE" ]; then _saveaccountconf "AUTO_UPGRADE" "$AUTO_UPGRADE" fi if [ "$_use_wget" ]; then _saveaccountconf "ACME_USE_WGET" "$_use_wget" elif [ "$ACME_USE_WGET" ]; then _saveaccountconf "ACME_USE_WGET" "$ACME_USE_WGET" fi } _checkSudo() { if [ -z "$__INTERACTIVE" ]; then #don't check if it's not in an interactive shell return 0 fi if [ "$SUDO_GID" ] && [ "$SUDO_COMMAND" ] && [ "$SUDO_USER" ] && [ "$SUDO_UID" ]; then if [ "$SUDO_USER" = "root" ] && [ "$SUDO_UID" = "0" ]; then #it's root using sudo, no matter it's using sudo or not, just fine return 0 fi if [ -n "$SUDO_COMMAND" ]; then #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 return 1 fi return 0 } #server #keylength _selectServer() { _server="$1" _skeylength="$2" _server_lower="$(echo "$_server" | _lower_case)" _sindex=0 for snames in $CA_NAMES; do snames="$(echo "$snames" | _lower_case)" _sindex="$(_math $_sindex + 1)" _debug2 "_selectServer try snames" "$snames" for sname in $(echo "$snames" | tr ',' ' '); do if [ "$_server_lower" = "$sname" ]; then _debug2 "_selectServer match $sname" _serverdir="$(_getfield "$CA_SERVERS" $_sindex)" if [ "$_serverdir" = "$CA_SSLCOM_RSA" ] && _isEccKey "$_skeylength"; then _serverdir="$CA_SSLCOM_ECC" fi _debug "Selected server: $_serverdir" ACME_DIRECTORY="$_serverdir" export ACME_DIRECTORY return fi done done ACME_DIRECTORY="$_server" export ACME_DIRECTORY } #url _getCAShortName() { caurl="$1" if [ -z "$caurl" ]; then #use letsencrypt as default value if the Le_API is empty #this case can only come from the old upgrading. caurl="$CA_LETSENCRYPT_V2" fi if [ "$CA_SSLCOM_ECC" = "$caurl" ]; then caurl="$CA_SSLCOM_RSA" #just hack to get the short name fi caurl_lower="$(echo $caurl | _lower_case)" _sindex=0 for surl in $(echo "$CA_SERVERS" | _lower_case | tr , ' '); do _sindex="$(_math $_sindex + 1)" if [ "$caurl_lower" = "$surl" ]; then _nindex=0 for snames in $CA_NAMES; do _nindex="$(_math $_nindex + 1)" if [ $_nindex -ge $_sindex ]; then _getfield "$snames" 1 return fi done fi done echo "$caurl" } #set default ca to $ACME_DIRECTORY setdefaultca() { if [ -z "$ACME_DIRECTORY" ]; then _err "Please provide a --server parameter." return 1 fi _saveaccountconf "DEFAULT_ACME_SERVER" "$ACME_DIRECTORY" _info "Changed default CA to: $(__green "$ACME_DIRECTORY")" } #preferred-chain setdefaultchain() { _initpath _preferred_chain="$1" if [ -z "$_preferred_chain" ]; then _err "Please provide a value for '--preferred-chain'." return 1 fi mkdir -p "$CA_DIR" _savecaconf "DEFAULT_PREFERRED_CHAIN" "$_preferred_chain" } #domain ecc info() { _domain="$1" _ecc="$2" _initpath if [ -z "$_domain" ]; then _debug "Show global configs" echo "LE_WORKING_DIR=$LE_WORKING_DIR" echo "LE_CONFIG_HOME=$LE_CONFIG_HOME" cat "$ACCOUNT_CONF_PATH" else _debug "Show domain configs" ( _initpath "$_domain" "$_ecc" echo "DOMAIN_CONF=$DOMAIN_CONF" for seg in $(cat $DOMAIN_CONF | cut -d = -f 1); do echo "$seg=$(_readdomainconf "$seg")" done ) fi } _process() { _CMD="" _domain="" _altdomains="$NO_VALUE" _webroot="" _challenge_alias="" _keylength="$DEFAULT_DOMAIN_KEY_LENGTH" _accountkeylength="$DEFAULT_ACCOUNT_KEY_LENGTH" _cert_file="" _key_file="" _ca_file="" _fullchain_file="" _reloadcmd="" _password="" _accountconf="" _useragent="" _accountemail="" _accountkey="" _certhome="" _confighome="" _httpport="" _tlsport="" _dnssleep="" _listraw="" _stopRenewOnError="" #_insecure="" _ca_bundle="" _ca_path="" _nocron="" _noprofile="" _ecc="" _csr="" _pre_hook="" _post_hook="" _renew_hook="" _deploy_hook="" _logfile="" _log="" _local_address="" _log_level="" _auto_upgrade="" _listen_v4="" _listen_v6="" _openssl_bin="" _syslog="" _use_wget="" _server="" _notify_hook="" _notify_level="" _notify_mode="" _notify_source="" _revoke_reason="" _eab_kid="" _eab_hmac_key="" _preferred_chain="" _valid_from="" _valid_to="" while [ ${#} -gt 0 ]; do case "${1}" in --help | -h) showhelp return ;; --version | -v) version return ;; --install) _CMD="install" ;; --install-online) shift installOnline "$@" return ;; --uninstall) _CMD="uninstall" ;; --upgrade) _CMD="upgrade" ;; --issue) _CMD="issue" ;; --deploy) _CMD="deploy" ;; --sign-csr | --signcsr) _CMD="signcsr" ;; --show-csr | --showcsr) _CMD="showcsr" ;; -i | --install-cert | --installcert) _CMD="installcert" ;; --renew | -r) _CMD="renew" ;; --renew-all | --renewAll | --renewall) _CMD="renewAll" ;; --revoke) _CMD="revoke" ;; --remove) _CMD="remove" ;; --list) _CMD="list" ;; --info) _CMD="info" ;; --install-cronjob | --installcronjob) _CMD="installcronjob" ;; --uninstall-cronjob | --uninstallcronjob) _CMD="uninstallcronjob" ;; --cron) _CMD="cron" ;; --to-pkcs12 | --to-pkcs | --toPkcs) _CMD="toPkcs" ;; --to-pkcs8 | --toPkcs8) _CMD="toPkcs8" ;; --create-account-key | --createAccountKey | --createaccountkey | -cak) _CMD="createAccountKey" ;; --create-domain-key | --createDomainKey | --createdomainkey | -cdk) _CMD="createDomainKey" ;; -ccr | --create-csr | --createCSR | --createcsr) _CMD="createCSR" ;; --deactivate) _CMD="deactivate" ;; --update-account | --updateaccount) _CMD="updateaccount" ;; --register-account | --registeraccount) _CMD="registeraccount" ;; --deactivate-account) _CMD="deactivateaccount" ;; --set-notify) _CMD="setnotify" ;; --set-default-ca) _CMD="setdefaultca" ;; --set-default-chain) _CMD="setdefaultchain" ;; -d | --domain) _dvalue="$2" if [ "$_dvalue" ]; then if _startswith "$_dvalue" "-"; then _err "'$_dvalue' is not a valid domain for parameter '$1'" return 1 fi if _is_idn "$_dvalue" && ! _exists idn; then _err "It seems that $_dvalue is an IDN (Internationalized Domain Names), please install the 'idn' command first." return 1 fi if [ -z "$_domain" ]; then _domain="$_dvalue" else if [ "$_altdomains" = "$NO_VALUE" ]; then _altdomains="$_dvalue" else _altdomains="$_altdomains,$_dvalue" fi fi fi shift ;; -f | --force) FORCE="1" ;; --staging | --test) STAGE="1" ;; --server) _server="$2" shift ;; --debug) if [ -z "$2" ] || _startswith "$2" "-"; then DEBUG="$DEBUG_LEVEL_DEFAULT" else DEBUG="$2" shift fi ;; --output-insecure) export OUTPUT_INSECURE=1 ;; -w | --webroot) wvalue="$2" if [ -z "$_webroot" ]; then _webroot="$wvalue" else _webroot="$_webroot,$wvalue" fi shift ;; --challenge-alias) cvalue="$2" _challenge_alias="$_challenge_alias$cvalue," shift ;; --domain-alias) cvalue="$DNS_ALIAS_PREFIX$2" _challenge_alias="$_challenge_alias$cvalue," shift ;; --standalone) wvalue="$NO_VALUE" if [ -z "$_webroot" ]; then _webroot="$wvalue" else _webroot="$_webroot,$wvalue" fi ;; --alpn) wvalue="$W_ALPN" if [ -z "$_webroot" ]; then _webroot="$wvalue" else _webroot="$_webroot,$wvalue" fi ;; --stateless) wvalue="$MODE_STATELESS" if [ -z "$_webroot" ]; then _webroot="$wvalue" else _webroot="$_webroot,$wvalue" fi ;; --local-address) lvalue="$2" _local_address="$_local_address$lvalue," shift ;; --apache) wvalue="apache" if [ -z "$_webroot" ]; then _webroot="$wvalue" else _webroot="$_webroot,$wvalue" fi ;; --nginx) wvalue="$NGINX" if [ "$2" ] && ! _startswith "$2" "-"; then wvalue="$NGINX$2" shift fi if [ -z "$_webroot" ]; then _webroot="$wvalue" else _webroot="$_webroot,$wvalue" fi ;; --dns) wvalue="$W_DNS" if [ "$2" ] && ! _startswith "$2" "-"; then wvalue="$2" shift fi if [ -z "$_webroot" ]; then _webroot="$wvalue" else _webroot="$_webroot,$wvalue" fi ;; --dnssleep) _dnssleep="$2" Le_DNSSleep="$_dnssleep" shift ;; --keylength | -k) _keylength="$2" shift if [ "$_keylength" ] && ! _isEccKey "$_keylength"; then export __SELECTED_RSA_KEY=1 fi ;; -ak | --accountkeylength) _accountkeylength="$2" shift ;; --cert-file | --certpath) _cert_file="$2" shift ;; --key-file | --keypath) _key_file="$2" shift ;; --ca-file | --capath) _ca_file="$2" shift ;; --fullchain-file | --fullchainpath) _fullchain_file="$2" shift ;; --reloadcmd | --reloadCmd) _reloadcmd="$2" shift ;; --password) _password="$2" shift ;; --accountconf) _accountconf="$2" ACCOUNT_CONF_PATH="$_accountconf" shift ;; --home) export LE_WORKING_DIR="$(echo "$2" | sed 's|/$||')" shift ;; --cert-home | --certhome) _certhome="$2" export CERT_HOME="$_certhome" shift ;; --config-home) _confighome="$2" export LE_CONFIG_HOME="$_confighome" shift ;; --useragent) _useragent="$2" USER_AGENT="$_useragent" shift ;; -m | --email | --accountemail) _accountemail="$2" export ACCOUNT_EMAIL="$_accountemail" shift ;; --accountkey) _accountkey="$2" ACCOUNT_KEY_PATH="$_accountkey" shift ;; --days) _days="$2" Le_RenewalDays="$_days" shift ;; --valid-from) _valid_from="$2" shift ;; --valid-to) _valid_to="$2" shift ;; --httpport) _httpport="$2" Le_HTTPPort="$_httpport" shift ;; --tlsport) _tlsport="$2" Le_TLSPort="$_tlsport" shift ;; --listraw) _listraw="raw" ;; -se | --stop-renew-on-error | --stopRenewOnError | --stoprenewonerror) _stopRenewOnError="1" ;; --insecure) #_insecure="1" HTTPS_INSECURE="1" ;; --ca-bundle) _ca_bundle="$(_readlink "$2")" CA_BUNDLE="$_ca_bundle" shift ;; --ca-path) _ca_path="$2" CA_PATH="$_ca_path" shift ;; --no-cron | --nocron) _nocron="1" ;; --no-profile | --noprofile) _noprofile="1" ;; --no-color) export ACME_NO_COLOR=1 ;; --force-color) export ACME_FORCE_COLOR=1 ;; --ecc) _ecc="isEcc" ;; --csr) _csr="$2" shift ;; --pre-hook) _pre_hook="$2" shift ;; --post-hook) _post_hook="$2" shift ;; --renew-hook) _renew_hook="$2" shift ;; --deploy-hook) if [ -z "$2" ] || _startswith "$2" "-"; then _usage "Please specify a value for '--deploy-hook'" return 1 fi _deploy_hook="$_deploy_hook$2," shift ;; --extended-key-usage) Le_ExtKeyUse="$2" shift ;; --ocsp-must-staple | --ocsp) Le_OCSP_Staple="1" ;; --always-force-new-domain-key) if [ -z "$2" ] || _startswith "$2" "-"; then Le_ForceNewDomainKey=1 else Le_ForceNewDomainKey="$2" shift fi ;; --yes-I-know-dns-manual-mode-enough-go-ahead-please) export FORCE_DNS_MANUAL=1 ;; --log | --logfile) _log="1" _logfile="$2" if _startswith "$_logfile" '-'; then _logfile="" else shift fi LOG_FILE="$_logfile" if [ -z "$LOG_LEVEL" ]; then LOG_LEVEL="$DEFAULT_LOG_LEVEL" fi ;; --log-level) _log_level="$2" LOG_LEVEL="$_log_level" shift ;; --syslog) if ! _startswith "$2" '-'; then _syslog="$2" shift fi if [ -z "$_syslog" ]; then _syslog="$SYSLOG_LEVEL_DEFAULT" fi ;; --auto-upgrade) _auto_upgrade="$2" if [ -z "$_auto_upgrade" ] || _startswith "$_auto_upgrade" '-'; then _auto_upgrade="1" else shift fi AUTO_UPGRADE="$_auto_upgrade" ;; --listen-v4) _listen_v4="1" Le_Listen_V4="$_listen_v4" ;; --listen-v6) _listen_v6="1" Le_Listen_V6="$_listen_v6" ;; --openssl-bin) _openssl_bin="$2" ACME_OPENSSL_BIN="$_openssl_bin" shift ;; --use-wget) _use_wget="1" ACME_USE_WGET="1" ;; --branch | -b) export BRANCH="$2" shift ;; --notify-hook) _nhook="$2" if _startswith "$_nhook" "-"; then _err "'$_nhook' is not a hook name for '$1'" return 1 fi if [ "$_notify_hook" ]; then _notify_hook="$_notify_hook,$_nhook" else _notify_hook="$_nhook" fi shift ;; --notify-level) _nlevel="$2" if _startswith "$_nlevel" "-"; then _err "'$_nlevel' is not an integer for '$1'" return 1 fi _notify_level="$_nlevel" shift ;; --notify-mode) _nmode="$2" if _startswith "$_nmode" "-"; then _err "'$_nmode' is not an integer for '$1'" return 1 fi _notify_mode="$_nmode" shift ;; --notify-source) _nsource="$2" if _startswith "$_nsource" "-"; then _err "'$_nsource' is not a valid host name for '$1'" return 1 fi _notify_source="$_nsource" shift ;; --revoke-reason) _revoke_reason="$2" if _startswith "$_revoke_reason" "-"; then _err "'$_revoke_reason' is not an integer for '$1'" return 1 fi shift ;; --eab-kid) _eab_kid="$2" shift ;; --eab-hmac-key) _eab_hmac_key="$2" shift ;; --preferred-chain) _preferred_chain="$2" shift ;; *) _err "Unknown parameter: $1" return 1 ;; esac shift 1 done if [ "$_server" ]; then _selectServer "$_server" "${_ecc:-$_keylength}" _server="$ACME_DIRECTORY" fi if [ "${_CMD}" != "install" ]; then if [ "$__INTERACTIVE" ] && ! _checkSudo; then if [ -z "$FORCE" ]; then #Use "echo" here, instead of _info. it's too early echo "It seems that you are using sudo, please read this page first:" echo "$_SUDO_WIKI" return 1 fi fi __initHome if [ "$_log" ]; then if [ -z "$_logfile" ]; then _logfile="$DEFAULT_LOG_FILE" fi fi if [ "$_logfile" ]; then _saveaccountconf "LOG_FILE" "$_logfile" LOG_FILE="$_logfile" fi if [ "$_log_level" ]; then _saveaccountconf "LOG_LEVEL" "$_log_level" LOG_LEVEL="$_log_level" fi if [ "$_syslog" ]; then if _exists logger; then if [ "$_syslog" = "0" ]; then _clearaccountconf "SYS_LOG" else _saveaccountconf "SYS_LOG" "$_syslog" fi SYS_LOG="$_syslog" else _err "The 'logger' command was not found, cannot enable syslog." _clearaccountconf "SYS_LOG" SYS_LOG="" fi fi _processAccountConf fi _debug2 LE_WORKING_DIR "$LE_WORKING_DIR" if [ "$DEBUG" ]; then version if [ "$_server" ]; then _debug "Using server: $_server" fi fi _debug "Running cmd: ${_CMD}" case "${_CMD}" in install) install "$_nocron" "$_confighome" "$_noprofile" "$_accountemail" ;; uninstall) uninstall "$_nocron" ;; upgrade) upgrade ;; issue) issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" "$_valid_from" "$_valid_to" ;; deploy) deploy "$_domain" "$_deploy_hook" "$_ecc" ;; signcsr) signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" ;; showcsr) showcsr "$_csr" "$_domain" ;; installcert) installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc" ;; renew) renew "$_domain" "$_ecc" "$_server" ;; renewAll) renewAll "$_stopRenewOnError" "$_server" ;; revoke) revoke "$_domain" "$_ecc" "$_revoke_reason" ;; remove) remove "$_domain" "$_ecc" ;; deactivate) deactivate "$_domain,$_altdomains" ;; registeraccount) registeraccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key" ;; updateaccount) updateaccount ;; deactivateaccount) deactivateaccount ;; list) list "$_listraw" "$_domain" ;; info) info "$_domain" "$_ecc" ;; installcronjob) installcronjob "$_confighome" ;; uninstallcronjob) uninstallcronjob ;; cron) cron ;; toPkcs) toPkcs "$_domain" "$_password" "$_ecc" ;; toPkcs8) toPkcs8 "$_domain" "$_ecc" ;; createAccountKey) createAccountKey "$_accountkeylength" ;; createDomainKey) createDomainKey "$_domain" "$_keylength" ;; createCSR) createCSR "$_domain" "$_altdomains" "$_ecc" ;; setnotify) setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" "$_notify_source" ;; setdefaultca) setdefaultca ;; setdefaultchain) setdefaultchain "$_preferred_chain" ;; *) if [ "$_CMD" ]; then _err "Invalid command: $_CMD" fi showhelp return 1 ;; esac _ret="$?" if [ "$_ret" != "0" ]; then return $_ret fi if [ "${_CMD}" = "install" ]; then if [ "$_log" ]; then if [ -z "$LOG_FILE" ]; then LOG_FILE="$DEFAULT_LOG_FILE" fi _saveaccountconf "LOG_FILE" "$LOG_FILE" fi if [ "$_log_level" ]; then _saveaccountconf "LOG_LEVEL" "$_log_level" fi if [ "$_syslog" ]; then if _exists logger; then if [ "$_syslog" = "0" ]; then _clearaccountconf "SYS_LOG" else _saveaccountconf "SYS_LOG" "$_syslog" fi else _err "The 'logger' command was not found, cannot enable syslog." _clearaccountconf "SYS_LOG" SYS_LOG="" fi fi _processAccountConf fi } main() { [ -z "$1" ] && showhelp && return if _startswith "$1" '-'; then _process "$@"; else "$@"; fi } main "$@" acme.sh-3.1.0/deploy/000077500000000000000000000000001472032365200143315ustar00rootroot00000000000000acme.sh-3.1.0/deploy/README.md000066400000000000000000000001451472032365200156100ustar00rootroot00000000000000# Using deploy api deploy hook usage: https://github.com/acmesh-official/acme.sh/wiki/deployhooks acme.sh-3.1.0/deploy/ali_cdn.sh000066400000000000000000000052751472032365200162670ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034,SC2154 # Script to create certificate to Alibaba Cloud CDN # # Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun # # This deployment required following variables # export Ali_Key="ALIACCESSKEY" # export Ali_Secret="ALISECRETKEY" # The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi # # To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates # export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com" # If you have multiple CDN domains using the same certificate, just # export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com" # # For DCDN, see ali_dcdn deploy hook Ali_CDN_API="https://cdn.aliyuncs.com/" ali_cdn_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" # Load dnsapi/dns_ali.sh to reduce the duplicated codes # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)" # shellcheck source=/dev/null if ! . "$dnsapi_ali"; then _err "Error loading file $dnsapi_ali. Please check your API file and try again." return 1 fi _prepare_ali_credentials || return 1 _getdeployconf DEPLOY_ALI_CDN_DOMAIN if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then _savedeployconf DEPLOY_ALI_CDN_DOMAIN "$DEPLOY_ALI_CDN_DOMAIN" else DEPLOY_ALI_CDN_DOMAIN="$_cdomain" fi # read cert and key files and urlencode both _cert=$(_url_encode upper-hex <"$_cfullchain") _key=$(_url_encode upper-hex <"$_ckey") _debug2 _cert "$_cert" _debug2 _key "$_key" ## update domain ssl config for domain in $DEPLOY_ALI_CDN_DOMAIN; do _set_cdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key" if _ali_rest "Set CDN domain SSL certificate for $domain" "" POST; then _info "Domain $domain certificate has been deployed successfully" fi done return 0 } # domain pub pri _set_cdn_domain_ssl_certificate_query() { endpoint=$Ali_CDN_API query='' query=$query'AccessKeyId='$Ali_Key query=$query'&Action=SetCdnDomainSSLCertificate' query=$query'&CertType=upload' query=$query'&DomainName='$1 query=$query'&Format=json' query=$query'&SSLPri='$3 query=$query'&SSLProtocol=on' query=$query'&SSLPub='$2 query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' query=$query'&Timestamp='$(_timestamp) query=$query'&Version=2018-05-10' } acme.sh-3.1.0/deploy/ali_dcdn.sh000066400000000000000000000053301472032365200164230ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034,SC2154 # Script to create certificate to Alibaba Cloud DCDN # # Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun # # This deployment required following variables # export Ali_Key="ALIACCESSKEY" # export Ali_Secret="ALISECRETKEY" # The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi # # To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates # export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com" # If you have multiple CDN domains using the same certificate, just # export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com" # # For regular CDN, see ali_cdn deploy hook Ali_DCDN_API="https://dcdn.aliyuncs.com/" ali_dcdn_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" # Load dnsapi/dns_ali.sh to reduce the duplicated codes # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)" # shellcheck source=/dev/null if ! . "$dnsapi_ali"; then _err "Error loading file $dnsapi_ali. Please check your API file and try again." return 1 fi _prepare_ali_credentials || return 1 _getdeployconf DEPLOY_ALI_DCDN_DOMAIN if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then _savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN" else DEPLOY_ALI_DCDN_DOMAIN="$_cdomain" fi # read cert and key files and urlencode both _cert=$(_url_encode upper-hex <"$_cfullchain") _key=$(_url_encode upper-hex <"$_ckey") _debug2 _cert "$_cert" _debug2 _key "$_key" ## update domain ssl config for domain in $DEPLOY_ALI_DCDN_DOMAIN; do _set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key" if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then _info "Domain $domain certificate has been deployed successfully" fi done return 0 } # domain pub pri _set_dcdn_domain_ssl_certificate_query() { endpoint=$Ali_DCDN_API query='' query=$query'AccessKeyId='$Ali_Key query=$query'&Action=SetDcdnDomainSSLCertificate' query=$query'&CertType=upload' query=$query'&DomainName='$1 query=$query'&Format=json' query=$query'&SSLPri='$3 query=$query'&SSLProtocol=on' query=$query'&SSLPub='$2 query=$query'&SignatureMethod=HMAC-SHA1' query=$query"&SignatureNonce=$(_ali_nonce)" query=$query'&SignatureVersion=1.0' query=$query'&Timestamp='$(_timestamp) query=$query'&Version=2018-01-15' } acme.sh-3.1.0/deploy/apache.sh000066400000000000000000000010001472032365200160750ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to apache server. #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain apache_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" _err "Deploy cert to apache server, Not implemented yet" return 1 } acme.sh-3.1.0/deploy/cleverreach.sh000066400000000000000000000067001472032365200171530ustar00rootroot00000000000000#!/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" _rest_endpoint="https://rest.cleverreach.com" _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 _getdeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID 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}" _savedeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" _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" "$_rest_endpoint/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") _debug _subclient "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" if [ -n "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" ]; then _info "Obtaining token for sub-client ${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" export _H1="Authorization: Bearer ${_access_token}" _subclient_token_result="$(_get "$_rest_endpoint/v3/clients/$DEPLOY_CLEVERREACH_SUBCLIENT_ID/token")" _access_token=$(echo "$_subclient_token_result" | sed -n "s/\"//p") _debug _subclient_token_result "$_access_token" _info "Destroying parent token at CleverReach, as it not needed anymore" _destroy_result="$(_post "" "$_rest_endpoint/v3/oauth/token.json" "" "DELETE" "application/json")" _debug _destroy_result "$_destroy_result" fi _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" "$_rest_endpoint/v3/ssl" "" "POST" "application/json")" if [ -z "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" ]; then _info "Destroying token at CleverReach, as it not needed anymore" _destroy_result="$(_post "" "$_rest_endpoint/v3/oauth/token.json" "" "DELETE" "application/json")" _debug _destroy_result "$_destroy_result" fi 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 } acme.sh-3.1.0/deploy/consul.sh000066400000000000000000000061021472032365200161670ustar00rootroot00000000000000#!/usr/bin/env sh # Here is a script to deploy cert to hashicorp consul using curl # (https://www.consul.io/) # # it requires following environment variables: # # CONSUL_PREFIX - this contains the prefix path in consul # CONSUL_HTTP_ADDR - consul requires this to find your consul server # # additionally, you need to ensure that CONSUL_HTTP_TOKEN is available # to access the consul server #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain consul_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 CONSUL_PREFIX if [ -z "$CONSUL_PREFIX" ]; then _err "CONSUL_PREFIX needs to be defined (contains prefix path in vault)" return 1 fi _savedeployconf CONSUL_PREFIX "$CONSUL_PREFIX" _getdeployconf CONSUL_HTTP_ADDR if [ -z "$CONSUL_HTTP_ADDR" ]; then _err "CONSUL_HTTP_ADDR needs to be defined (contains consul connection address)" return 1 fi _savedeployconf CONSUL_HTTP_ADDR "$CONSUL_HTTP_ADDR" CONSUL_CMD=$(command -v consul) # force CLI, but the binary does not exist => error if [ -n "$USE_CLI" ] && [ -z "$CONSUL_CMD" ]; then _err "Cannot find the consul binary!" return 1 fi # use the CLI first if [ -n "$USE_CLI" ] || [ -n "$CONSUL_CMD" ]; then _info "Found consul binary, deploying with CLI" consul_deploy_cli "$CONSUL_CMD" "$CONSUL_PREFIX" else _info "Did not find consul binary, deploying with API" consul_deploy_api "$CONSUL_HTTP_ADDR" "$CONSUL_PREFIX" "$CONSUL_HTTP_TOKEN" fi } consul_deploy_api() { CONSUL_HTTP_ADDR="$1" CONSUL_PREFIX="$2" CONSUL_HTTP_TOKEN="$3" URL="$CONSUL_HTTP_ADDR/v1/kv/$CONSUL_PREFIX" export _H1="X-Consul-Token: $CONSUL_HTTP_TOKEN" if [ -n "$FABIO" ]; then _post "$(cat "$_cfullchain")" "$URL/${_cdomain}-cert.pem" '' "PUT" || return 1 _post "$(cat "$_ckey")" "$URL/${_cdomain}-key.pem" '' "PUT" || return 1 else _post "$(cat "$_ccert")" "$URL/${_cdomain}/cert.pem" '' "PUT" || return 1 _post "$(cat "$_ckey")" "$URL/${_cdomain}/cert.key" '' "PUT" || return 1 _post "$(cat "$_cca")" "$URL/${_cdomain}/chain.pem" '' "PUT" || return 1 _post "$(cat "$_cfullchain")" "$URL/${_cdomain}/fullchain.pem" '' "PUT" || return 1 fi } consul_deploy_cli() { CONSUL_CMD="$1" CONSUL_PREFIX="$2" if [ -n "$FABIO" ]; then $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-cert.pem" @"$_cfullchain" || return 1 $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-key.pem" @"$_ckey" || return 1 else $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 fi } acme.sh-3.1.0/deploy/cpanel_uapi.sh000066400000000000000000000170031472032365200171460ustar00rootroot00000000000000#!/usr/bin/env sh # 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. # # Configure DEPLOY_CPANEL_AUTO_<...> options to enable or restrict automatic # detection of deployment targets through UAPI (if not set, defaults below are used.) # - ENABLED : 'true' for multi-site / wildcard capability; otherwise single-site mode. # - NOMATCH : 'true' to allow deployment to sites that do not match the certificate. # - INCLUDE : Comma-separated list - sites must match this field. # - EXCLUDE : Comma-separated list - sites must NOT match this field. # INCLUDE/EXCLUDE both support non-lexical, glob-style matches using '*' # # 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 # export DEPLOY_CPANEL_AUTO_ENABLED='true' # export DEPLOY_CPANEL_AUTO_NOMATCH='false' # export DEPLOY_CPANEL_AUTO_INCLUDE='*' # export DEPLOY_CPANEL_AUTO_EXCLUDE='' ######## Public functions ##################### #domain keyfile certfile cafile fullchain cpanel_uapi_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" # re-declare vars inherited from acme.sh but not passed to make ShellCheck happy : "${Le_Alt:=""}" _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" if ! _exists uapi; then _err "The command uapi is not found." return 1 fi # declare useful constants uapi_error_response='status: 0' # read cert and key files and urlencode both _cert=$(_url_encode <"$_ccert") _key=$(_url_encode <"$_ckey") _debug2 _cert "$_cert" _debug2 _key "$_key" if [ "$(id -u)" = 0 ]; then _getdeployconf DEPLOY_CPANEL_USER # fallback to _readdomainconf for old installs if [ -z "${DEPLOY_CPANEL_USER:=$(_readdomainconf DEPLOY_CPANEL_USER)}" ]; then _err "It seems that you are root, please define the target user name: export DEPLOY_CPANEL_USER=username" return 1 fi _debug DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER" _savedeployconf DEPLOY_CPANEL_USER "$DEPLOY_CPANEL_USER" _uapi_user="$DEPLOY_CPANEL_USER" fi # Load all AUTO envars and set defaults - see above for usage __cpanel_initautoparam ENABLED 'true' __cpanel_initautoparam NOMATCH 'false' __cpanel_initautoparam INCLUDE '*' __cpanel_initautoparam EXCLUDE '' # Auto mode if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then # call API for site config _response=$(uapi DomainInfo list_domains) # exit if error in response if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then _err "Error in deploying certificate - cannot retrieve sitelist:" _err "\n$_response" return 1 fi # parse response to create site list sitelist=$(__cpanel_parse_response "$_response") _debug "UAPI sites found: $sitelist" # filter sitelist using configured domains # skip if NOMATCH is "true" if [ "$DEPLOY_CPANEL_AUTO_NOMATCH" = "true" ]; then _debug "DEPLOY_CPANEL_AUTO_NOMATCH is true" _info "UAPI nomatch mode is enabled - Will not validate sites are valid for the certificate" else _debug "DEPLOY_CPANEL_AUTO_NOMATCH is false" d="$(echo "${Le_Alt}," | sed -e "s/^$_cdomain,//" -e "s/,$_cdomain,/,/")" d="$(echo "$_cdomain,$d" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\[\^\.\]\*/g')" sitelist="$(echo "$sitelist" | grep -ix "$d")" _debug2 "Matched UAPI sites: $sitelist" fi # filter sites that do not match $DEPLOY_CPANEL_AUTO_INCLUDE _info "Applying sitelist filter DEPLOY_CPANEL_AUTO_INCLUDE: $DEPLOY_CPANEL_AUTO_INCLUDE" sitelist="$(echo "$sitelist" | grep -ix "$(echo "$DEPLOY_CPANEL_AUTO_INCLUDE" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\.\*/g')")" _debug2 "Remaining sites: $sitelist" # filter sites that match $DEPLOY_CPANEL_AUTO_EXCLUDE _info "Applying sitelist filter DEPLOY_CPANEL_AUTO_EXCLUDE: $DEPLOY_CPANEL_AUTO_EXCLUDE" sitelist="$(echo "$sitelist" | grep -vix "$(echo "$DEPLOY_CPANEL_AUTO_EXCLUDE" | tr ',' '\n' | sed -e 's/\./\\./g' -e 's/\*/\.\*/g')")" _debug2 "Remaining sites: $sitelist" # counter for success / failure check successes=0 if [ -n "$sitelist" ]; then sitetotal="$(echo "$sitelist" | wc -l)" _debug "$sitetotal sites to deploy" else sitetotal=0 _debug "No sites to deploy" fi # for each site: call uapi to publish cert and log result. Only return failure if all fail for site in $sitelist; do # call uapi to publish cert, check response for errors and log them. if [ -n "$_uapi_user" ]; then _response=$(uapi --user="$_uapi_user" SSL install_ssl domain="$site" cert="$_cert" key="$_key") else _response=$(uapi SSL install_ssl domain="$site" cert="$_cert" key="$_key") fi if [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then _err "Error in deploying certificate to $site:" _err "$_response" else successes=$((successes + 1)) _debug "$_response" _info "Succcessfully deployed to $site" fi done # Raise error if all updates fail if [ "$sitetotal" -gt 0 ] && [ "$successes" -eq 0 ]; then _err "Could not deploy to any of $sitetotal sites via UAPI" _debug "successes: $successes, sitetotal: $sitetotal" return 1 fi _info "Successfully deployed certificate to $successes of $sitetotal sites via UAPI" return 0 else # "classic" mode - will only try to deploy to the primary domain; will not check UAPI first if [ -n "$_uapi_user" ]; then _response=$(uapi --user="$_uapi_user" SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key") else _response=$(uapi SSL install_ssl domain="$_cdomain" cert="$_cert" key="$_key") fi if [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then _err "Error in deploying certificate:" _err "$_response" return 1 fi _debug response "$_response" _info "Certificate successfully deployed" return 0 fi } ######## Private functions ##################### # Internal utility to process YML from UAPI - looks at main_domain, sub_domains, addon domains and parked domains #[response] __cpanel_parse_response() { if [ $# -gt 0 ]; then resp="$*"; else resp="$(cat)"; fi echo "$resp" | sed -En \ -e 's/\r$//' \ -e 's/^( *)([_.[:alnum:]]+) *: *(.*)/\1,\2,\3/p' \ -e 's/^( *)- (.*)/\1,-,\2/p' | awk -F, '{ level = length($1)/2; section[level] = $2; for (i in section) {if (i > level) {delete section[i]}} if (length($3) > 0) { prefix=""; for (i=0; i < level; i++) { prefix = (prefix)(section[i])("/") } printf("%s%s=%s\n", prefix, $2, $3); } }' | sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' } # Load parameter by prefix+name - fallback to default if not set, and save to config #pname pdefault __cpanel_initautoparam() { pname="$1" pdefault="$2" pkey="DEPLOY_CPANEL_AUTO_$pname" _getdeployconf "$pkey" [ -n "$(eval echo "\"\$$pkey\"")" ] || eval "$pkey=\"$pdefault\"" _debug2 "$pkey" "$(eval echo "\"\$$pkey\"")" _savedeployconf "$pkey" "$(eval echo "\"\$$pkey\"")" } acme.sh-3.1.0/deploy/docker.sh000077500000000000000000000212661472032365200161460ustar00rootroot00000000000000#!/usr/bin/env sh #DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx" #DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem" #DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem" #DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem" #DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem" #DEPLOY_DOCKER_CONTAINER_RELOAD_CMD="service nginx force-reload" _DEPLOY_DOCKER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers" _DOCKER_HOST_DEFAULT="/var/run/docker.sock" docker_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _debug _cdomain "$_cdomain" _getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL _debug2 DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL" if [ -z "$DEPLOY_DOCKER_CONTAINER_LABEL" ]; then _err "The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container." _err "See: $_DEPLOY_DOCKER_WIKI" fi _savedeployconf DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL" if [ "$DOCKER_HOST" ]; then _saveaccountconf DOCKER_HOST "$DOCKER_HOST" fi if _exists docker && docker version | grep -i docker >/dev/null; then _info "Using docker command" export _USE_DOCKER_COMMAND=1 else export _USE_DOCKER_COMMAND= fi export _USE_UNIX_SOCKET= if [ -z "$_USE_DOCKER_COMMAND" ]; then export _USE_REST= if [ "$DOCKER_HOST" ]; then _debug "Try use docker host: $DOCKER_HOST" export _USE_REST=1 else export _DOCKER_SOCK="$_DOCKER_HOST_DEFAULT" _debug "Try use $_DOCKER_SOCK" if [ ! -e "$_DOCKER_SOCK" ] || [ ! -w "$_DOCKER_SOCK" ]; then _err "$_DOCKER_SOCK is not available" return 1 fi export _USE_UNIX_SOCKET=1 if ! _exists "curl"; then _err "Please install curl first." _err "We need curl to work." return 1 fi if ! _check_curl_version; then return 1 fi fi fi _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE _debug2 DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" fi _getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE _debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then _savedeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" fi _getdeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE _debug2 DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE" if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then _savedeployconf DEPLOY_DOCKER_CONTAINER_CA_FILE "$DEPLOY_DOCKER_CONTAINER_CA_FILE" fi _getdeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE _debug2 DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then _savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" fi _getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD _debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then _savedeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" "base64" fi _cid="$(_get_id "$DEPLOY_DOCKER_CONTAINER_LABEL")" _info "Container id: $_cid" if [ -z "$_cid" ]; then _err "can not find container id" return 1 fi if [ "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" ]; then if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then return 1 fi fi if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then if ! _docker_cp "$_cid" "$_ccert" "$DEPLOY_DOCKER_CONTAINER_CERT_FILE"; then return 1 fi fi if [ "$DEPLOY_DOCKER_CONTAINER_CA_FILE" ]; then if ! _docker_cp "$_cid" "$_cca" "$DEPLOY_DOCKER_CONTAINER_CA_FILE"; then return 1 fi fi if [ "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" ]; then if ! _docker_cp "$_cid" "$_cfullchain" "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE"; then return 1 fi fi if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then _info "Reloading: $DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" if ! _docker_exec "$_cid" "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"; then return 1 fi fi return 0 } #label _get_id() { _label="$1" if [ "$_USE_DOCKER_COMMAND" ]; then docker ps -f label="$_label" --format "{{.ID}}" elif [ "$_USE_REST" ]; then _err "Not implemented yet." return 1 elif [ "$_USE_UNIX_SOCKET" ]; then _req="{\"label\":[\"$_label\"]}" _debug2 _req "$_req" _req="$(printf "%s" "$_req" | _url_encode)" _debug2 _req "$_req" listjson="$(_curl_unix_sock "${_DOCKER_SOCK:-$_DOCKER_HOST_DEFAULT}" GET "/containers/json?filters=$_req")" _debug2 "listjson" "$listjson" echo "$listjson" | tr '{,' '\n' | grep -i '"id":' | _head_n 1 | cut -d '"' -f 4 else _err "Not implemented yet." return 1 fi } #id cmd _docker_exec() { _eargs="$*" _debug2 "_docker_exec $_eargs" _dcid="$1" shift if [ "$_USE_DOCKER_COMMAND" ]; then docker exec -i "$_dcid" sh -c "$*" elif [ "$_USE_REST" ]; then _err "Not implemented yet." return 1 elif [ "$_USE_UNIX_SOCKET" ]; then _cmd="$*" #_cmd="$(printf "%s" "$_cmd" | sed 's/ /","/g')" _debug2 _cmd "$_cmd" #create exec instance: cjson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/containers/$_dcid/exec" "{\"Cmd\": [\"sh\", \"-c\", \"$_cmd\"]}")" _debug2 cjson "$cjson" execid="$(echo "$cjson" | cut -d '"' -f 4)" _debug execid "$execid" ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")" _debug2 ejson "$ejson" if [ "$ejson" ]; then _err "$ejson" return 1 fi else _err "Not implemented yet." return 1 fi } #id from to _docker_cp() { _dcid="$1" _from="$2" _to="$3" _info "Copying file from $_from to $_to" _dir="$(dirname "$_to")" _debug2 _dir "$_dir" if ! _docker_exec "$_dcid" mkdir -p "$_dir"; then _err "Can not create dir: $_dir" return 1 fi if [ "$_USE_DOCKER_COMMAND" ]; then if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _docker_exec "$_dcid" tee "$_to" <"$_from" else _docker_exec "$_dcid" tee "$_to" <"$_from" >/dev/null fi if [ "$?" = "0" ]; then _info "Success" return 0 else _info "Error" return 1 fi elif [ "$_USE_REST" ]; then _err "Not implemented yet." return 1 elif [ "$_USE_UNIX_SOCKET" ]; then _frompath="$_from" if _startswith "$_frompath" '/'; then _frompath="$(echo "$_from" | cut -b 2-)" #remove the first '/' char fi _debug2 "_frompath" "$_frompath" _toname="$(basename "$_to")" _debug2 "_toname" "$_toname" _debug2 "_from" "$_from" if ! tar --transform="s,$(printf "%s" "$_frompath" | tr '*' .),$_toname," -cz "$_from" 2>/dev/null | _curl_unix_sock "$_DOCKER_SOCK" PUT "/containers/$_dcid/archive?noOverwriteDirNonDir=1&path=$(printf "%s" "$_dir" | _url_encode)" '@-' "Content-Type: application/octet-stream"; then _err "copy error" return 1 fi return 0 else _err "Not implemented yet." return 1 fi } #sock method endpoint data content-type _curl_unix_sock() { _socket="$1" _method="$2" _endpoint="$3" _data="$4" _ctype="$5" if [ -z "$_ctype" ]; then _ctype="Content-Type: application/json" fi _debug _data "$_data" _debug2 "url" "http://localhost$_endpoint" if [ "$_CURL_NO_HOST" ]; then _cux_url="http:$_endpoint" else _cux_url="http://localhost$_endpoint" fi if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then curl -vvv --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url" else curl --silent --unix-socket "$_socket" -X "$_method" --data-binary "$_data" --header "$_ctype" "$_cux_url" fi } _check_curl_version() { _cversion="$(curl -V | grep '^curl ' | cut -d ' ' -f 2)" _debug2 "_cversion" "$_cversion" _major="$(_getfield "$_cversion" 1 '.')" _debug2 "_major" "$_major" _minor="$(_getfield "$_cversion" 2 '.')" _debug2 "_minor" "$_minor" if [ "$_major" -ge "8" ]; then #ok return 0 fi if [ "$_major" = "7" ]; then if [ "$_minor" -lt "40" ]; then _err "curl v$_cversion doesn't support unit socket" _err "Please upgrade to curl 7.40 or later." return 1 fi if [ "$_minor" -lt "50" ]; then _debug "Use short host name" export _CURL_NO_HOST=1 else export _CURL_NO_HOST= fi return 0 else _err "curl v$_cversion doesn't support unit socket" _err "Please upgrade to curl 7.40 or later." return 1 fi } acme.sh-3.1.0/deploy/dovecot.sh000066400000000000000000000007441472032365200163350ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to dovecot server. #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain dovecot_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" _err "Not implemented yet" return 1 } acme.sh-3.1.0/deploy/exim4.sh000066400000000000000000000061731472032365200157220ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to exim4 server. #returns 0 means success, otherwise error. #DEPLOY_EXIM4_CONF="/etc/exim/exim.conf" #DEPLOY_EXIM4_RELOAD="service exim4 restart" ######## Public functions ##################### #domain keyfile certfile cafile fullchain exim4_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" _ssl_path="/etc/acme.sh/exim4" if ! mkdir -p "$_ssl_path"; then _err "Can not create folder:$_ssl_path" return 1 fi _info "Copying key and cert" _real_key="$_ssl_path/exim4.key" if ! cat "$_ckey" >"$_real_key"; then _err "Error: write key file to: $_real_key" return 1 fi _real_fullchain="$_ssl_path/exim4.pem" if ! cat "$_cfullchain" >"$_real_fullchain"; then _err "Error: write key file to: $_real_fullchain" return 1 fi DEFAULT_EXIM4_RELOAD="service exim4 restart" _reload="${DEPLOY_EXIM4_RELOAD:-$DEFAULT_EXIM4_RELOAD}" if [ -z "$IS_RENEW" ]; then DEFAULT_EXIM4_CONF="/etc/exim/exim.conf" if [ ! -f "$DEFAULT_EXIM4_CONF" ]; then DEFAULT_EXIM4_CONF="/etc/exim4/exim4.conf.template" fi _exim4_conf="${DEPLOY_EXIM4_CONF:-$DEFAULT_EXIM4_CONF}" _debug _exim4_conf "$_exim4_conf" if [ ! -f "$_exim4_conf" ]; then if [ -z "$DEPLOY_EXIM4_CONF" ]; then _err "exim4 conf is not found, please define DEPLOY_EXIM4_CONF" return 1 else _err "It seems that the specified exim4 conf is not valid, please check." return 1 fi fi if [ ! -w "$_exim4_conf" ]; then _err "The file $_exim4_conf is not writable, please change the permission." return 1 fi _backup_conf="$DOMAIN_BACKUP_PATH/exim4.conf.bak" _info "Backup $_exim4_conf to $_backup_conf" cp "$_exim4_conf" "$_backup_conf" _info "Modify exim4 conf: $_exim4_conf" if _setopt "$_exim4_conf" "tls_certificate" "=" "$_real_fullchain" && _setopt "$_exim4_conf" "tls_privatekey" "=" "$_real_key"; then _info "Set config success!" else _err "Config exim4 server error, please report bug to us." _info "Restoring exim4 conf" if cat "$_backup_conf" >"$_exim4_conf"; then _info "Restore conf success" eval "$_reload" else _err "Oops, error restore exim4 conf, please report bug to us." fi return 1 fi fi _info "Run reload: $_reload" if eval "$_reload"; then _info "Reload success!" if [ "$DEPLOY_EXIM4_CONF" ]; then _savedomainconf DEPLOY_EXIM4_CONF "$DEPLOY_EXIM4_CONF" else _cleardomainconf DEPLOY_EXIM4_CONF fi if [ "$DEPLOY_EXIM4_RELOAD" ]; then _savedomainconf DEPLOY_EXIM4_RELOAD "$DEPLOY_EXIM4_RELOAD" else _cleardomainconf DEPLOY_EXIM4_RELOAD fi return 0 else _err "Reload error, restoring" if cat "$_backup_conf" >"$_exim4_conf"; then _info "Restore conf success" eval "$_reload" else _err "Oops, error restore exim4 conf, please report bug to us." fi return 1 fi } acme.sh-3.1.0/deploy/fritzbox.sh000066400000000000000000000110631472032365200165350ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to an AVM FRITZ!Box router. #returns 0 means success, otherwise error. #DEPLOY_FRITZBOX_USERNAME="username" #DEPLOY_FRITZBOX_PASSWORD="password" #DEPLOY_FRITZBOX_URL="https://fritz.box" # Kudos to wikrie at Github for his FRITZ!Box update script: # https://gist.github.com/wikrie/f1d5747a714e0a34d0582981f7cb4cfb ######## Public functions ##################### #domain keyfile certfile cafile fullchain fritzbox_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" if ! _exists iconv; then if ! _exists uconv; then if ! _exists perl; then _err "iconv or uconv or perl not found" return 1 fi fi fi # Clear traces of incorrectly stored values _clearaccountconf DEPLOY_FRITZBOX_USERNAME _clearaccountconf DEPLOY_FRITZBOX_PASSWORD _clearaccountconf DEPLOY_FRITZBOX_URL # Read config from saved values or env _getdeployconf DEPLOY_FRITZBOX_USERNAME _getdeployconf DEPLOY_FRITZBOX_PASSWORD _getdeployconf DEPLOY_FRITZBOX_URL _debug DEPLOY_FRITZBOX_URL "$DEPLOY_FRITZBOX_URL" _debug DEPLOY_FRITZBOX_USERNAME "$DEPLOY_FRITZBOX_USERNAME" _secure_debug DEPLOY_FRITZBOX_PASSWORD "$DEPLOY_FRITZBOX_PASSWORD" if [ -z "$DEPLOY_FRITZBOX_USERNAME" ]; then _err "FRITZ!Box username is not found, please define DEPLOY_FRITZBOX_USERNAME." return 1 fi if [ -z "$DEPLOY_FRITZBOX_PASSWORD" ]; then _err "FRITZ!Box password is not found, please define DEPLOY_FRITZBOX_PASSWORD." return 1 fi if [ -z "$DEPLOY_FRITZBOX_URL" ]; then _err "FRITZ!Box url is not found, please define DEPLOY_FRITZBOX_URL." return 1 fi # Save current values _savedeployconf DEPLOY_FRITZBOX_USERNAME "$DEPLOY_FRITZBOX_USERNAME" _savedeployconf DEPLOY_FRITZBOX_PASSWORD "$DEPLOY_FRITZBOX_PASSWORD" _savedeployconf DEPLOY_FRITZBOX_URL "$DEPLOY_FRITZBOX_URL" # Do not check for a valid SSL certificate, because initially the cert is not valid, so it could not install the LE generated certificate export HTTPS_INSECURE=1 _info "Log in to the FRITZ!Box" _fritzbox_challenge="$(_get "${DEPLOY_FRITZBOX_URL}/login_sid.lua" | sed -e 's/^.*//' -e 's/<\/Challenge>.*$//')" if _exists iconv; then _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${DEPLOY_FRITZBOX_PASSWORD}" | iconv -f ASCII -t UTF16LE | _digest md5 hex)" elif _exists uconv; then _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${DEPLOY_FRITZBOX_PASSWORD}" | uconv -f ASCII -t UTF16LE | _digest md5 hex)" else _fritzbox_hash="$(printf "%s-%s" "${_fritzbox_challenge}" "${DEPLOY_FRITZBOX_PASSWORD}" | perl -p -e 'use Encode qw/encode/; print encode("UTF-16LE","$_"); $_="";' | _digest md5 hex)" fi _fritzbox_sid="$(_get "${DEPLOY_FRITZBOX_URL}/login_sid.lua?sid=0000000000000000&username=${DEPLOY_FRITZBOX_USERNAME}&response=${_fritzbox_challenge}-${_fritzbox_hash}" | sed -e 's/^.*//' -e 's/<\/SID>.*$//')" if [ -z "${_fritzbox_sid}" ] || [ "${_fritzbox_sid}" = "0000000000000000" ]; then _err "Logging in to the FRITZ!Box failed. Please check username, password and URL." return 1 fi _info "Generate form POST request" _post_request="$(_mktemp)" _post_boundary="---------------------------$(date +%Y%m%d%H%M%S)" # _CERTPASSWORD_ is unset because Let's Encrypt certificates don't have a password. But if they ever do, here's the place to use it! _CERTPASSWORD_= { printf -- "--" printf -- "%s\r\n" "${_post_boundary}" printf "Content-Disposition: form-data; name=\"sid\"\r\n\r\n%s\r\n" "${_fritzbox_sid}" printf -- "--" printf -- "%s\r\n" "${_post_boundary}" printf "Content-Disposition: form-data; name=\"BoxCertPassword\"\r\n\r\n%s\r\n" "${_CERTPASSWORD_}" printf -- "--" printf -- "%s\r\n" "${_post_boundary}" printf "Content-Disposition: form-data; name=\"BoxCertImportFile\"; filename=\"BoxCert.pem\"\r\n" printf "Content-Type: application/octet-stream\r\n\r\n" cat "${_ckey}" "${_cfullchain}" printf "\r\n" printf -- "--" printf -- "%s--" "${_post_boundary}" } >>"${_post_request}" _info "Upload certificate to the FRITZ!Box" export _H1="Content-type: multipart/form-data boundary=${_post_boundary}" _post "$(cat "${_post_request}")" "${DEPLOY_FRITZBOX_URL}/cgi-bin/firmwarecfg" | grep SSL retval=$? if [ $retval = 0 ]; then _info "Upload successful" else _err "Upload failed" fi rm "${_post_request}" return $retval } acme.sh-3.1.0/deploy/gcore_cdn.sh000066400000000000000000000112541472032365200166130ustar00rootroot00000000000000#!/usr/bin/env sh # Here is the script to deploy the cert to G-Core CDN service (https://gcore.com/) using the G-Core Labs API (https://apidocs.gcore.com/cdn). # Returns 0 when success. # # Written by temoffey # Public domain, 2019 # Update by DreamOfIce in 2023 #export DEPLOY_GCORE_CDN_USERNAME=myusername #export DEPLOY_GCORE_CDN_PASSWORD=mypassword ######## Public functions ##################### #domain keyfile certfile cafile fullchain gcore_cdn_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" _fullchain=$(tr '\r\n' '*#' <"$_cfullchain" | sed 's/*#/#/g;s/##/#/g;s/#/\\n/g') _key=$(tr '\r\n' '*#' <"$_ckey" | sed 's/*#/#/g;s/#/\\n/g') _debug _fullchain "$_fullchain" _debug _key "$_key" if [ -z "$DEPLOY_GCORE_CDN_USERNAME" ]; then if [ -z "$Le_Deploy_gcore_cdn_username" ]; then _err "Please define the target username: export DEPLOY_GCORE_CDN_USERNAME=username" return 1 fi else Le_Deploy_gcore_cdn_username="$DEPLOY_GCORE_CDN_USERNAME" _savedomainconf Le_Deploy_gcore_cdn_username "$Le_Deploy_gcore_cdn_username" fi if [ -z "$DEPLOY_GCORE_CDN_PASSWORD" ]; then if [ -z "$Le_Deploy_gcore_cdn_password" ]; then _err "Please define the target password: export DEPLOY_GCORE_CDN_PASSWORD=password" return 1 fi else Le_Deploy_gcore_cdn_password="$DEPLOY_GCORE_CDN_PASSWORD" _savedomainconf Le_Deploy_gcore_cdn_password "$Le_Deploy_gcore_cdn_password" fi _info "Get authorization token" _request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}" _debug _request "$_request" export _H1="Content-Type:application/json" _response=$(_post "$_request" "https://api.gcore.com/auth/jwt/login") _debug _response "$_response" _regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$" _debug _regex "$_regex" _token=$(echo "$_response" | sed -n "s/$_regex/\1/p") _debug _token "$_token" if [ -z "$_token" ]; then _err "Error G-Core Labs API authorization" return 1 fi _info "Find CDN resource with cname $_cdomain" export _H2="Authorization:Bearer $_token" _response=$(_get "https://api.gcore.com/cdn/resources") _debug _response "$_response" _regex="\"primary_resource\":null}," _debug _regex "$_regex" _response=$(echo "$_response" | sed "s/$_regex/$_regex\n/g") _debug _response "$_response" _regex="^.*\"cname\":\"$_cdomain\".*$" _debug _regex "$_regex" _resource=$(echo "$_response" | _egrep_o "$_regex") _debug _resource "$_resource" _regex=".*\"id\":\([0-9]*\).*$" _debug _regex "$_regex" _resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p") _debug _resourceId "$_resourceId" _regex=".*\"sslData\":\([0-9]*\).*$" _debug _regex "$_regex" _sslDataOld=$(echo "$_resource" | sed -n "s/$_regex/\1/p") _debug _sslDataOld "$_sslDataOld" _regex=".*\"originGroup\":\([0-9]*\).*$" _debug _regex "$_regex" _originGroup=$(echo "$_resource" | sed -n "s/$_regex/\1/p") _debug _originGroup "$_originGroup" if [ -z "$_resourceId" ] || [ -z "$_originGroup" ]; then _err "Not found CDN resource with cname $_cdomain" return 1 fi _info "Add new SSL certificate" _date=$(date "+%d.%m.%Y %H:%M:%S") _request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}" _debug _request "$_request" _response=$(_post "$_request" "https://api.gcore.com/cdn/sslData") _debug _response "$_response" _regex=".*\"id\":\([0-9]*\).*$" _debug _regex "$_regex" _sslDataAdd=$(echo "$_response" | sed -n "s/$_regex/\1/p") _debug _sslDataAdd "$_sslDataAdd" if [ -z "$_sslDataAdd" ]; then _err "Error new SSL certificate add" return 1 fi _info "Update CDN resource" _request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}" _debug _request "$_request" _response=$(_post "$_request" "https://api.gcore.com/cdn/resources/$_resourceId" '' "PUT") _debug _response "$_response" _regex=".*\"sslData\":\([0-9]*\).*$" _debug _regex "$_regex" _sslDataNew=$(echo "$_response" | sed -n "s/$_regex/\1/p") _debug _sslDataNew "$_sslDataNew" if [ "$_sslDataNew" != "$_sslDataAdd" ]; then _err "Error CDN resource update" return 1 fi if [ -z "$_sslDataOld" ] || [ "$_sslDataOld" = "null" ]; then _info "Not found old SSL certificate" else _info "Delete old SSL certificate" _response=$(_post '' "https://api.gcore.com/cdn/sslData/$_sslDataOld" '' "DELETE") _debug _response "$_response" fi _info "Certificate successfully deployed" return 0 } acme.sh-3.1.0/deploy/gitlab.sh000066400000000000000000000040421472032365200161270ustar00rootroot00000000000000#!/usr/bin/env sh # Script to deploy certificate to a Gitlab hosted page # The following variables exported from environment will be used. # If not set then values previously saved in domain.conf file are used. # All the variables are required # export GITLAB_TOKEN="xxxxxxx" # export GITLAB_PROJECT_ID=012345 # export GITLAB_DOMAIN="mydomain.com" gitlab_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" if [ -z "$GITLAB_TOKEN" ]; then if [ -z "$Le_Deploy_gitlab_token" ]; then _err "GITLAB_TOKEN not defined." return 1 fi else Le_Deploy_gitlab_token="$GITLAB_TOKEN" _savedomainconf Le_Deploy_gitlab_token "$Le_Deploy_gitlab_token" fi if [ -z "$GITLAB_PROJECT_ID" ]; then if [ -z "$Le_Deploy_gitlab_project_id" ]; then _err "GITLAB_PROJECT_ID not defined." return 1 fi else Le_Deploy_gitlab_project_id="$GITLAB_PROJECT_ID" _savedomainconf Le_Deploy_gitlab_project_id "$Le_Deploy_gitlab_project_id" fi if [ -z "$GITLAB_DOMAIN" ]; then if [ -z "$Le_Deploy_gitlab_domain" ]; then _err "GITLAB_DOMAIN not defined." return 1 fi else Le_Deploy_gitlab_domain="$GITLAB_DOMAIN" _savedomainconf Le_Deploy_gitlab_domain "$Le_Deploy_gitlab_domain" fi string_fullchain=$(_url_encode <"$_cfullchain") string_key=$(_url_encode <"$_ckey") body="certificate=$string_fullchain&key=$string_key" export _H1="PRIVATE-TOKEN: $Le_Deploy_gitlab_token" gitlab_url="https://gitlab.com/api/v4/projects/$Le_Deploy_gitlab_project_id/pages/domains/$Le_Deploy_gitlab_domain" _response=$(_post "$body" "$gitlab_url" 0 PUT | _dbase64 "multiline") error_response="error" if test "${_response#*"$error_response"}" != "$_response"; then _err "Error in deploying certificate:" _err "$_response" return 1 fi _debug response "$_response" _info "Certificate successfully deployed" return 0 } acme.sh-3.1.0/deploy/haproxy.sh000066400000000000000000000366461472032365200163760ustar00rootroot00000000000000#!/usr/bin/env sh # Script for acme.sh to deploy certificates to haproxy # # The following variables can be exported: # # export DEPLOY_HAPROXY_PEM_NAME="${domain}.pem" # # Defines the name of the PEM file. # Defaults to ".pem" # # export DEPLOY_HAPROXY_PEM_PATH="/etc/haproxy" # # Defines location of PEM file for HAProxy. # Defaults to /etc/haproxy # # export DEPLOY_HAPROXY_RELOAD="systemctl reload haproxy" # # OPTIONAL: Reload command used post deploy # This defaults to be a no-op (ie "true"). # It is strongly recommended to set this something that makes sense # for your distro. # # export DEPLOY_HAPROXY_ISSUER="no" # # OPTIONAL: Places CA file as "${DEPLOY_HAPROXY_PEM}.issuer" # Note: Required for OCSP stapling to work # # export DEPLOY_HAPROXY_BUNDLE="no" # # OPTIONAL: Deploy this certificate as part of a multi-cert bundle # This adds a suffix to the certificate based on the certificate type # eg RSA certificates will have .rsa as a suffix to the file name # HAProxy will load all certificates and provide one or the other # depending on client capabilities # Note: This functionality requires HAProxy was compiled against # a version of OpenSSL that supports this. # # export DEPLOY_HAPROXY_HOT_UPDATE="yes" # export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock" # # OPTIONAL: Deploy the certificate over the HAProxy stats socket without # needing to reload HAProxy. Default is "no". # # Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat # address format. # # export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock" # # OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE="yes" instead # of a stats socket, use this variable. ######## Public functions ##################### #domain keyfile certfile cafile fullchain haproxy_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _cmdpfx="" # Some defaults DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy" DEPLOY_HAPROXY_PEM_NAME_DEFAULT="${_cdomain}.pem" DEPLOY_HAPROXY_BUNDLE_DEFAULT="no" DEPLOY_HAPROXY_ISSUER_DEFAULT="no" DEPLOY_HAPROXY_RELOAD_DEFAULT="true" DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no" DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock" _debug _cdomain "${_cdomain}" _debug _ckey "${_ckey}" _debug _ccert "${_ccert}" _debug _cca "${_cca}" _debug _cfullchain "${_cfullchain}" # PEM_PATH is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}" _getdeployconf DEPLOY_HAPROXY_PEM_PATH _debug2 DEPLOY_HAPROXY_PEM_PATH "${DEPLOY_HAPROXY_PEM_PATH}" if [ -n "${DEPLOY_HAPROXY_PEM_PATH}" ]; then Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH}" _savedomainconf Le_Deploy_haproxy_pem_path "${Le_Deploy_haproxy_pem_path}" elif [ -z "${Le_Deploy_haproxy_pem_path}" ]; then Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}" fi # Ensure PEM_PATH exists if [ -d "${Le_Deploy_haproxy_pem_path}" ]; then _debug "PEM_PATH ${Le_Deploy_haproxy_pem_path} exists" else _err "PEM_PATH ${Le_Deploy_haproxy_pem_path} does not exist" return 1 fi # PEM_NAME is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}" _getdeployconf DEPLOY_HAPROXY_PEM_NAME _debug2 DEPLOY_HAPROXY_PEM_NAME "${DEPLOY_HAPROXY_PEM_NAME}" if [ -n "${DEPLOY_HAPROXY_PEM_NAME}" ]; then Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME}" _savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}" elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}" # We better not have '*' as the first character if [ "${Le_Deploy_haproxy_pem_name%%"${Le_Deploy_haproxy_pem_name#?}"}" = '*' ]; then # removes the first characters and add a _ instead Le_Deploy_haproxy_pem_name="_${Le_Deploy_haproxy_pem_name#?}" fi fi # BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}" _getdeployconf DEPLOY_HAPROXY_BUNDLE _debug2 DEPLOY_HAPROXY_BUNDLE "${DEPLOY_HAPROXY_BUNDLE}" if [ -n "${DEPLOY_HAPROXY_BUNDLE}" ]; then Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE}" _savedomainconf Le_Deploy_haproxy_bundle "${Le_Deploy_haproxy_bundle}" elif [ -z "${Le_Deploy_haproxy_bundle}" ]; then Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE_DEFAULT}" fi # ISSUER is optional. If not provided then assume "${DEPLOY_HAPROXY_ISSUER_DEFAULT}" _getdeployconf DEPLOY_HAPROXY_ISSUER _debug2 DEPLOY_HAPROXY_ISSUER "${DEPLOY_HAPROXY_ISSUER}" if [ -n "${DEPLOY_HAPROXY_ISSUER}" ]; then Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER}" _savedomainconf Le_Deploy_haproxy_issuer "${Le_Deploy_haproxy_issuer}" elif [ -z "${Le_Deploy_haproxy_issuer}" ]; then Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER_DEFAULT}" fi # RELOAD is optional. If not provided then assume "${DEPLOY_HAPROXY_RELOAD_DEFAULT}" _getdeployconf DEPLOY_HAPROXY_RELOAD _debug2 DEPLOY_HAPROXY_RELOAD "${DEPLOY_HAPROXY_RELOAD}" if [ -n "${DEPLOY_HAPROXY_RELOAD}" ]; then Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD}" _savedomainconf Le_Deploy_haproxy_reload "${Le_Deploy_haproxy_reload}" elif [ -z "${Le_Deploy_haproxy_reload}" ]; then Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}" fi # HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}" _getdeployconf DEPLOY_HAPROXY_HOT_UPDATE _debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}" if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}" _savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}" elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}" fi # STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}" _getdeployconf DEPLOY_HAPROXY_STATS_SOCKET _debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}" if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}" _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}" elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}" fi # MASTER_CLI is optional. No defaults are used. When the master CLI is used, # all commands are sent with a prefix. _getdeployconf DEPLOY_HAPROXY_MASTER_CLI _debug2 DEPLOY_HAPROXY_MASTER_CLI "${DEPLOY_HAPROXY_MASTER_CLI}" if [ -n "${DEPLOY_HAPROXY_MASTER_CLI}" ]; then Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_MASTER_CLI}" _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}" _cmdpfx="@1 " # command prefix used for master CLI only. fi # Set the suffix depending if we are creating a bundle or not if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then _info "Bundle creation requested" # Initialise $Le_Keylength if its not already set if [ -z "${Le_Keylength}" ]; then Le_Keylength="" fi if _isEccKey "${Le_Keylength}"; then _info "ECC key type detected" _suffix=".ecdsa" else _info "RSA key type detected" _suffix=".rsa" fi else _suffix="" fi _debug _suffix "${_suffix}" # Set variables for later _pem="${Le_Deploy_haproxy_pem_path}/${Le_Deploy_haproxy_pem_name}${_suffix}" _issuer="${_pem}.issuer" _ocsp="${_pem}.ocsp" _reload="${Le_Deploy_haproxy_reload}" _statssock="${Le_Deploy_haproxy_stats_socket}" _info "Deploying PEM file" # Create a temporary PEM file _temppem="$(_mktemp)" _debug _temppem "${_temppem}" cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}" _ret="$?" # Check that we could create the temporary file if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} returned during PEM file creation" [ -f "${_temppem}" ] && rm -f "${_temppem}" return ${_ret} fi # Move PEM file into place _info "Moving new certificate into place" _debug _pem "${_pem}" cat "${_temppem}" >"${_pem}" _ret=$? # Clean up temp file [ -f "${_temppem}" ] && rm -f "${_temppem}" # Deal with any failure of moving PEM file into place if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} returned while moving new certificate into place" return ${_ret} fi # Update .issuer file if requested if [ "${Le_Deploy_haproxy_issuer}" = "yes" ]; then _info "Updating .issuer file" _debug _issuer "${_issuer}" cat "${_cca}" >"${_issuer}" _ret="$?" if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} returned while copying issuer/CA certificate into place" return ${_ret} fi else [ -f "${_issuer}" ] && _err "Issuer file update not requested but .issuer file exists" fi # Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option if [ -z "${Le_OCSP_Staple}" ]; then Le_OCSP_Staple="0" fi if [ "${Le_OCSP_Staple}" = "1" ]; then _info "Updating OCSP stapling info" _debug _ocsp "${_ocsp}" _info "Extracting OCSP URL" _ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in "${_pem}") _debug _ocsp_url "${_ocsp_url}" # Only process OCSP if URL was present if [ "${_ocsp_url}" != "" ]; then # Extract the hostname from the OCSP URL _info "Extracting OCSP URL" _ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3) _debug _ocsp_host "${_ocsp_host}" # Only process the certificate if we have a .issuer file if [ -r "${_issuer}" ]; then # Check if issuer cert is also a root CA cert _subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10) _debug _subjectdn "${_subjectdn}" _issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10) _debug _issuerdn "${_issuerdn}" _info "Requesting OCSP response" # If the issuer is a CA cert then our command line has "-CAfile" added if [ "${_subjectdn}" = "${_issuerdn}" ]; then _cafile_argument="-CAfile \"${_issuer}\"" else _cafile_argument="" fi _debug _cafile_argument "${_cafile_argument}" # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2) _debug _openssl_version "${_openssl_version}" _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1) _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2) if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then _header_sep="=" else _header_sep=" " fi # Request the OCSP response from the issuer and store it _openssl_ocsp_cmd="${ACME_OPENSSL_BIN:-openssl} ocsp \ -issuer \"${_issuer}\" \ -cert \"${_pem}\" \ -url \"${_ocsp_url}\" \ -header Host${_header_sep}\"${_ocsp_host}\" \ -respout \"${_ocsp}\" \ -verify_other \"${_issuer}\" \ ${_cafile_argument} \ | grep -q \"${_pem}: good\"" _debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}" eval "${_openssl_ocsp_cmd}" _ret=$? else # Non fatal: No issuer file was present so no OCSP stapling file created _err "OCSP stapling in use but no .issuer file was present" fi else # Non fatal: No OCSP url was found int the certificate _err "OCSP update requested but no OCSP URL was found in certificate" fi # Non fatal: Check return code of openssl command if [ "${_ret}" != "0" ]; then _err "Updating OCSP stapling failed with return code ${_ret}" fi else # An OCSP file was already present but certificate did not have OCSP extension if [ -f "${_ocsp}" ]; then _err "OCSP was not requested but .ocsp file exists." # Could remove the file at this step, although HAProxy just ignores it in this case # rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file" fi fi if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then # set the socket name for messages if [ -n "${_cmdpfx}" ]; then _socketname="master CLI" else _socketname="stats socket" fi # Update certificate over HAProxy stats socket or master CLI. if _exists socat; then # look for the certificate on the stats socket, to chose between updating or creating one _socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'" _debug _socat_cert_cmd "${_socat_cert_cmd}" eval "${_socat_cert_cmd}" _ret=$? if [ "${_ret}" != "0" ]; then _newcert="1" _info "Creating new certificate '${_pem}' over HAProxy ${_socketname}." # certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate. _socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'" _debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}" eval "${_socat_crtlist_show_cmd}" _ret=$? if [ "${_ret}" != "0" ]; then _err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'" return "${_ret}" fi # create a new certificate _socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'" _debug _socat_new_cmd "${_socat_new_cmd}" eval "${_socat_new_cmd}" _ret=$? if [ "${_ret}" != "0" ]; then _err "Couldn't create '${_pem}' in haproxy" return "${_ret}" fi else _info "Update existing certificate '${_pem}' over HAProxy ${_socketname}." fi _socat_cert_set_cmd="echo -e '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -q 'Transaction created'" _debug _socat_cert_set_cmd "${_socat_cert_set_cmd}" eval "${_socat_cert_set_cmd}" _ret=$? if [ "${_ret}" != "0" ]; then _err "Can't update '${_pem}' in haproxy" return "${_ret}" fi _socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'" _debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}" eval "${_socat_cert_commit_cmd}" _ret=$? if [ "${_ret}" != "0" ]; then _err "Can't commit '${_pem}' in haproxy" return ${_ret} fi if [ "${_newcert}" = "1" ]; then # if this is a new certificate, it needs to be inserted into the crt-list` _socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'" _debug _socat_cert_add_cmd "${_socat_cert_add_cmd}" eval "${_socat_cert_add_cmd}" _ret=$? if [ "${_ret}" != "0" ]; then _err "Can't update '${_pem}' in haproxy" return "${_ret}" fi fi else _err "'socat' is not available, couldn't update over ${_socketname}" fi else # Reload HAProxy _debug _reload "${_reload}" eval "${_reload}" _ret=$? if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} during reload" return ${_ret} else _info "Reload successful" fi fi return 0 } acme.sh-3.1.0/deploy/keychain.sh000066400000000000000000000012351472032365200164610ustar00rootroot00000000000000#!/usr/bin/env sh ######## Public functions ##################### #domain keyfile certfile cafile fullchain keychain_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" /usr/bin/security import "$_ckey" -k "/Library/Keychains/System.keychain" /usr/bin/security import "$_ccert" -k "/Library/Keychains/System.keychain" /usr/bin/security import "$_cca" -k "/Library/Keychains/System.keychain" /usr/bin/security import "$_cfullchain" -k "/Library/Keychains/System.keychain" return 0 } acme.sh-3.1.0/deploy/kong.sh000077500000000000000000000055061472032365200156340ustar00rootroot00000000000000#!/usr/bin/env sh # If certificate already exists it will update only cert and key, not touching other parameters # If certificate doesn't exist it will only upload cert and key, and not set other parameters # Note that we deploy full chain # Written by Geoffroi Genot ######## Public functions ##################### #domain keyfile certfile cafile fullchain kong_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _info "Deploying certificate on Kong instance" if [ -z "$KONG_URL" ]; then _debug "KONG_URL Not set, using default http://localhost:8001" KONG_URL="http://localhost:8001" fi _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" #Get ssl_uuid linked to the domain ssl_uuid=$(_get "$KONG_URL/certificates/$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') if [ -z "$ssl_uuid" ]; then _debug "Unable to get Kong ssl_uuid for domain $_cdomain" _debug "Make sure that KONG_URL is correctly configured" _debug "Make sure that a Kong certificate match the sni" _debug "Kong url: $KONG_URL" _info "No existing certificate, creating..." #return 1 fi #Save kong url if it's succesful (First run case) _saveaccountconf KONG_URL "$KONG_URL" #Generate DEIM delim="-----MultipartDelimiter$(date "+%s%N")" nl="\015\012" #Set Header _H1="Content-Type: multipart/form-data; boundary=$delim" #Generate data for request (Multipart/form-data with mixed content) if [ -z "$ssl_uuid" ]; then #set sni to domain content="--$delim${nl}Content-Disposition: form-data; name=\"snis[]\"${nl}${nl}$_cdomain" fi #add key content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" #Add cert content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" #Close multipart content="$content${nl}--$delim--${nl}" #Convert CRLF content=$(printf %b "$content") #DEBUG _debug header "$_H1" _debug content "$content" #Check if sslcreated (if not => POST else => PATCH) if [ -z "$ssl_uuid" ]; then #Post certificate to Kong response=$(_post "$content" "$KONG_URL/certificates" "" "POST") else #patch response=$(_post "$content" "$KONG_URL/certificates/$ssl_uuid" "" "PATCH") fi if ! [ "$(echo "$response" | _egrep_o "created_at")" = "created_at" ]; then _err "An error occurred with cert upload. Check response:" _err "$response" return 1 fi _debug response "$response" _info "Certificate successfully deployed" } acme.sh-3.1.0/deploy/lighttpd.sh000066400000000000000000000236541472032365200165160ustar00rootroot00000000000000#!/usr/bin/env sh # Script for acme.sh to deploy certificates to lighttpd # # The following variables can be exported: # # export DEPLOY_LIGHTTPD_PEM_NAME="${domain}.pem" # # Defines the name of the PEM file. # Defaults to ".pem" # # export DEPLOY_LIGHTTPD_PEM_PATH="/etc/lighttpd" # # Defines location of PEM file for Lighttpd. # Defaults to /etc/lighttpd # # export DEPLOY_LIGHTTPD_RELOAD="systemctl reload lighttpd" # # OPTIONAL: Reload command used post deploy # This defaults to be a no-op (ie "true"). # It is strongly recommended to set this something that makes sense # for your distro. # # export DEPLOY_LIGHTTPD_ISSUER="yes" # # OPTIONAL: Places CA file as "${DEPLOY_LIGHTTPD_PEM}.issuer" # Note: Required for OCSP stapling to work # # export DEPLOY_LIGHTTPD_BUNDLE="no" # # OPTIONAL: Deploy this certificate as part of a multi-cert bundle # This adds a suffix to the certificate based on the certificate type # eg RSA certificates will have .rsa as a suffix to the file name # Lighttpd will load all certificates and provide one or the other # depending on client capabilities # Note: This functionality requires Lighttpd was compiled against # a version of OpenSSL that supports this. # ######## Public functions ##################### #domain keyfile certfile cafile fullchain lighttpd_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" # Some defaults DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT="/etc/lighttpd" DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT="${_cdomain}.pem" DEPLOY_LIGHTTPD_BUNDLE_DEFAULT="no" DEPLOY_LIGHTTPD_ISSUER_DEFAULT="yes" DEPLOY_LIGHTTPD_RELOAD_DEFAULT="true" _debug _cdomain "${_cdomain}" _debug _ckey "${_ckey}" _debug _ccert "${_ccert}" _debug _cca "${_cca}" _debug _cfullchain "${_cfullchain}" # PEM_PATH is optional. If not provided then assume "${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}" _getdeployconf DEPLOY_LIGHTTPD_PEM_PATH _debug2 DEPLOY_LIGHTTPD_PEM_PATH "${DEPLOY_LIGHTTPD_PEM_PATH}" if [ -n "${DEPLOY_LIGHTTPD_PEM_PATH}" ]; then Le_Deploy_lighttpd_pem_path="${DEPLOY_LIGHTTPD_PEM_PATH}" _savedomainconf Le_Deploy_lighttpd_pem_path "${Le_Deploy_lighttpd_pem_path}" elif [ -z "${Le_Deploy_lighttpd_pem_path}" ]; then Le_Deploy_lighttpd_pem_path="${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}" fi # Ensure PEM_PATH exists if [ -d "${Le_Deploy_lighttpd_pem_path}" ]; then _debug "PEM_PATH ${Le_Deploy_lighttpd_pem_path} exists" else _err "PEM_PATH ${Le_Deploy_lighttpd_pem_path} does not exist" return 1 fi # PEM_NAME is optional. If not provided then assume "${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}" _getdeployconf DEPLOY_LIGHTTPD_PEM_NAME _debug2 DEPLOY_LIGHTTPD_PEM_NAME "${DEPLOY_LIGHTTPD_PEM_NAME}" if [ -n "${DEPLOY_LIGHTTPD_PEM_NAME}" ]; then Le_Deploy_lighttpd_pem_name="${DEPLOY_LIGHTTPD_PEM_NAME}" _savedomainconf Le_Deploy_lighttpd_pem_name "${Le_Deploy_lighttpd_pem_name}" elif [ -z "${Le_Deploy_lighttpd_pem_name}" ]; then Le_Deploy_lighttpd_pem_name="${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}" fi # BUNDLE is optional. If not provided then assume "${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}" _getdeployconf DEPLOY_LIGHTTPD_BUNDLE _debug2 DEPLOY_LIGHTTPD_BUNDLE "${DEPLOY_LIGHTTPD_BUNDLE}" if [ -n "${DEPLOY_LIGHTTPD_BUNDLE}" ]; then Le_Deploy_lighttpd_bundle="${DEPLOY_LIGHTTPD_BUNDLE}" _savedomainconf Le_Deploy_lighttpd_bundle "${Le_Deploy_lighttpd_bundle}" elif [ -z "${Le_Deploy_lighttpd_bundle}" ]; then Le_Deploy_lighttpd_bundle="${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}" fi # ISSUER is optional. If not provided then assume "${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}" _getdeployconf DEPLOY_LIGHTTPD_ISSUER _debug2 DEPLOY_LIGHTTPD_ISSUER "${DEPLOY_LIGHTTPD_ISSUER}" if [ -n "${DEPLOY_LIGHTTPD_ISSUER}" ]; then Le_Deploy_lighttpd_issuer="${DEPLOY_LIGHTTPD_ISSUER}" _savedomainconf Le_Deploy_lighttpd_issuer "${Le_Deploy_lighttpd_issuer}" elif [ -z "${Le_Deploy_lighttpd_issuer}" ]; then Le_Deploy_lighttpd_issuer="${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}" fi # RELOAD is optional. If not provided then assume "${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}" _getdeployconf DEPLOY_LIGHTTPD_RELOAD _debug2 DEPLOY_LIGHTTPD_RELOAD "${DEPLOY_LIGHTTPD_RELOAD}" if [ -n "${DEPLOY_LIGHTTPD_RELOAD}" ]; then Le_Deploy_lighttpd_reload="${DEPLOY_LIGHTTPD_RELOAD}" _savedomainconf Le_Deploy_lighttpd_reload "${Le_Deploy_lighttpd_reload}" elif [ -z "${Le_Deploy_lighttpd_reload}" ]; then Le_Deploy_lighttpd_reload="${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}" fi # Set the suffix depending if we are creating a bundle or not if [ "${Le_Deploy_lighttpd_bundle}" = "yes" ]; then _info "Bundle creation requested" # Initialise $Le_Keylength if its not already set if [ -z "${Le_Keylength}" ]; then Le_Keylength="" fi if _isEccKey "${Le_Keylength}"; then _info "ECC key type detected" _suffix=".ecdsa" else _info "RSA key type detected" _suffix=".rsa" fi else _suffix="" fi _debug _suffix "${_suffix}" # Set variables for later _pem="${Le_Deploy_lighttpd_pem_path}/${Le_Deploy_lighttpd_pem_name}${_suffix}" _issuer="${_pem}.issuer" _ocsp="${_pem}.ocsp" _reload="${Le_Deploy_lighttpd_reload}" _info "Deploying PEM file" # Create a temporary PEM file _temppem="$(_mktemp)" _debug _temppem "${_temppem}" cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}" _ret="$?" # Check that we could create the temporary file if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} returned during PEM file creation" [ -f "${_temppem}" ] && rm -f "${_temppem}" return ${_ret} fi # Move PEM file into place _info "Moving new certificate into place" _debug _pem "${_pem}" cat "${_temppem}" >"${_pem}" _ret=$? # Clean up temp file [ -f "${_temppem}" ] && rm -f "${_temppem}" # Deal with any failure of moving PEM file into place if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} returned while moving new certificate into place" return ${_ret} fi # Update .issuer file if requested if [ "${Le_Deploy_lighttpd_issuer}" = "yes" ]; then _info "Updating .issuer file" _debug _issuer "${_issuer}" cat "${_cca}" >"${_issuer}" _ret="$?" if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} returned while copying issuer/CA certificate into place" return ${_ret} fi else [ -f "${_issuer}" ] && _err "Issuer file update not requested but .issuer file exists" fi # Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option if [ -z "${Le_OCSP_Staple}" ]; then Le_OCSP_Staple="0" fi if [ "${Le_OCSP_Staple}" = "1" ]; then _info "Updating OCSP stapling info" _debug _ocsp "${_ocsp}" _info "Extracting OCSP URL" _ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in "${_pem}") _debug _ocsp_url "${_ocsp_url}" # Only process OCSP if URL was present if [ "${_ocsp_url}" != "" ]; then # Extract the hostname from the OCSP URL _info "Extracting OCSP URL" _ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3) _debug _ocsp_host "${_ocsp_host}" # Only process the certificate if we have a .issuer file if [ -r "${_issuer}" ]; then # Check if issuer cert is also a root CA cert _subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10) _debug _subjectdn "${_subjectdn}" _issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10) _debug _issuerdn "${_issuerdn}" _info "Requesting OCSP response" # If the issuer is a CA cert then our command line has "-CAfile" added if [ "${_subjectdn}" = "${_issuerdn}" ]; then _cafile_argument="-CAfile \"${_issuer}\"" else _cafile_argument="" fi _debug _cafile_argument "${_cafile_argument}" # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2) _debug _openssl_version "${_openssl_version}" _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1) _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2) if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then _header_sep="=" else _header_sep=" " fi # Request the OCSP response from the issuer and store it _openssl_ocsp_cmd="${ACME_OPENSSL_BIN:-openssl} ocsp \ -issuer \"${_issuer}\" \ -cert \"${_pem}\" \ -url \"${_ocsp_url}\" \ -header Host${_header_sep}\"${_ocsp_host}\" \ -respout \"${_ocsp}\" \ -verify_other \"${_issuer}\" \ ${_cafile_argument} \ | grep -q \"${_pem}: good\"" _debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}" eval "${_openssl_ocsp_cmd}" _ret=$? else # Non fatal: No issuer file was present so no OCSP stapling file created _err "OCSP stapling in use but no .issuer file was present" fi else # Non fatal: No OCSP url was found int the certificate _err "OCSP update requested but no OCSP URL was found in certificate" fi # Non fatal: Check return code of openssl command if [ "${_ret}" != "0" ]; then _err "Updating OCSP stapling failed with return code ${_ret}" fi else # An OCSP file was already present but certificate did not have OCSP extension if [ -f "${_ocsp}" ]; then _err "OCSP was not requested but .ocsp file exists." # Could remove the file at this step, although Lighttpd just ignores it in this case # rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file" fi fi # Reload Lighttpd _debug _reload "${_reload}" eval "${_reload}" _ret=$? if [ "${_ret}" != "0" ]; then _err "Error code ${_ret} during reload" return ${_ret} else _info "Reload successful" fi return 0 } acme.sh-3.1.0/deploy/mailcow.sh000066400000000000000000000034471472032365200163300ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to mailcow. #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain mailcow_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_MAILCOW_PATH _getdeployconf DEPLOY_MAILCOW_RELOAD _debug DEPLOY_MAILCOW_PATH "$DEPLOY_MAILCOW_PATH" _debug DEPLOY_MAILCOW_RELOAD "$DEPLOY_MAILCOW_RELOAD" if [ -z "$DEPLOY_MAILCOW_PATH" ]; then _err "Mailcow path is not found, please define DEPLOY_MAILCOW_PATH." return 1 fi _savedeployconf DEPLOY_MAILCOW_PATH "$DEPLOY_MAILCOW_PATH" [ -n "$DEPLOY_MAILCOW_RELOAD" ] && _savedeployconf DEPLOY_MAILCOW_RELOAD "$DEPLOY_MAILCOW_RELOAD" _ssl_path="$DEPLOY_MAILCOW_PATH" if [ -f "$DEPLOY_MAILCOW_PATH/generate_config.sh" ]; then _ssl_path="$DEPLOY_MAILCOW_PATH/data/assets/ssl/" fi if [ ! -d "$_ssl_path" ]; then _err "Cannot find mailcow ssl path: $_ssl_path" return 1 fi _info "Copying key and cert" _real_key="$_ssl_path/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" if ! cat "$_cfullchain" >"$_real_fullchain"; then _err "Error: write cert file to: $_real_fullchain" return 1 fi DEFAULT_MAILCOW_RELOAD="docker restart \$(docker ps --quiet --filter name=nginx-mailcow --filter name=dovecot-mailcow --filter name=postfix-mailcow)" _reload="${DEPLOY_MAILCOW_RELOAD:-$DEFAULT_MAILCOW_RELOAD}" _info "Run reload: $_reload" if eval "$_reload"; then _info "Reload success!" fi return 0 } acme.sh-3.1.0/deploy/myapi.sh000077500000000000000000000011201472032365200160010ustar00rootroot00000000000000#!/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 myapi_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" _err "Not implemented yet" return 1 } acme.sh-3.1.0/deploy/mydevil.sh000077500000000000000000000027001472032365200163400ustar00rootroot00000000000000#!/usr/bin/env sh # MyDevil.net API (2019-02-03) # # MyDevil.net already supports automatic Let's Encrypt certificates, # except for wildcard domains. # # This script depends on `devil` command that MyDevil.net provides, # which means that it works only on server side. # # Author: Marcin Konicki # ######## Public functions ##################### # Usage: mydevil_deploy domain keyfile certfile cafile fullchain mydevil_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" ip="" _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" if ! _exists "devil"; then _err "Could not find 'devil' command." return 1 fi ip=$(mydevil_get_ip "$_cdomain") if [ -z "$ip" ]; then _err "Could not find IP for domain $_cdomain." return 1 fi # Delete old certificate first _info "Removing old certificate for $_cdomain at $ip" devil ssl www del "$ip" "$_cdomain" # Add new certificate _info "Adding new certificate for $_cdomain at $ip" devil ssl www add "$ip" "$_cfullchain" "$_ckey" "$_cdomain" || return 1 return 0 } #################### Private functions below ################################## # Usage: ip=$(mydevil_get_ip domain.com) # echo $ip mydevil_get_ip() { devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1 return 0 } acme.sh-3.1.0/deploy/mysqld.sh000066400000000000000000000010001472032365200161650ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to mysqld server. #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain mysqld_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" _err "deploy cert to mysqld server, Not implemented yet" return 1 } acme.sh-3.1.0/deploy/nginx.sh000066400000000000000000000007751472032365200160210ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to nginx server. #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain nginx_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" _err "deploy cert to nginx server, Not implemented yet" return 1 } acme.sh-3.1.0/deploy/openmediavault.sh000066400000000000000000000144011472032365200177020ustar00rootroot00000000000000#!/usr/bin/env sh # This deploy hook is tested on OpenMediaVault 5.x. It supports both local and remote deployment. # The way it works is that if a cert with the matching domain name is not found, it will firstly create a dummy cert to get its uuid, and then replace it with your cert. # # DEPLOY_OMV_WEBUI_ADMIN - This is OMV web gui admin account. Default value is admin. It's required as the user parameter (-u) for the omv-rpc command. # DEPLOY_OMV_HOST and DEPLOY_OMV_SSH_USER are optional. They are used for remote deployment through ssh (support public key authentication only). Per design, OMV web gui admin doesn't have ssh permission, so another account is needed for ssh. # # returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain openmediavault_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_OMV_WEBUI_ADMIN if [ -z "$DEPLOY_OMV_WEBUI_ADMIN" ]; then DEPLOY_OMV_WEBUI_ADMIN="admin" fi _savedeployconf DEPLOY_OMV_WEBUI_ADMIN "$DEPLOY_OMV_WEBUI_ADMIN" _getdeployconf DEPLOY_OMV_HOST _getdeployconf DEPLOY_OMV_SSH_USER if [ -n "$DEPLOY_OMV_HOST" ] && [ -n "$DEPLOY_OMV_SSH_USER" ]; then _info "[OMV deploy-hook] Deploy certificate remotely through ssh." _savedeployconf DEPLOY_OMV_HOST "$DEPLOY_OMV_HOST" _savedeployconf DEPLOY_OMV_SSH_USER "$DEPLOY_OMV_SSH_USER" else _info "[OMV deploy-hook] Deploy certificate locally." fi if [ -n "$DEPLOY_OMV_HOST" ] && [ -n "$DEPLOY_OMV_SSH_USER" ]; then _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'getList' '{\"start\": 0, \"limit\": -1}' | jq -r '.data[] | select(.name==\"/CN='$_cdomain'\") | .uuid'" # shellcheck disable=SC2029 _uuid=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command") _debug _command "$_command" if [ -z "$_uuid" ]; then _info "[OMV deploy-hook] Domain $_cdomain has no certificate in openmediavault, creating it!" _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'create' '{\"cn\": \"test.example.com\", \"size\": 4096, \"days\": 3650, \"c\": \"\", \"st\": \"\", \"l\": \"\", \"o\": \"\", \"ou\": \"\", \"email\": \"\"}' | jq -r '.uuid'" # shellcheck disable=SC2029 _uuid=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command") _debug _command "$_command" if [ -z "$_uuid" ]; then _err "[OMV deploy-hook] An error occured while creating the certificate" return 1 fi fi _info "[OMV deploy-hook] Domain $_cdomain has uuid: $_uuid" _fullchain=$(jq <"$_cfullchain" -aRs .) _key=$(jq <"$_ckey" -aRs .) _debug _fullchain "$_fullchain" _debug _key "$_key" _info "[OMV deploy-hook] Updating key and certificate in openmediavault" _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'set' '{\"uuid\":\"$_uuid\", \"certificate\":$_fullchain, \"privatekey\":$_key, \"comment\":\"acme.sh deployed $(date)\"}'" # shellcheck disable=SC2029 _result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command") _debug _command "$_command" _debug _result "$_result" _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'setSettings' \$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'getSettings' | jq -c '.sslcertificateref=\"$_uuid\"')" # shellcheck disable=SC2029 _result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command") _debug _command "$_command" _debug _result "$_result" _info "[OMV deploy-hook] Asking openmediavault to apply changes... (this could take some time, hang in there)" _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'Config' 'applyChanges' '{\"modules\":[], \"force\": false}'" # shellcheck disable=SC2029 _result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command") _debug _command "$_command" _debug _result "$_result" _info "[OMV deploy-hook] Asking nginx to reload" _command="nginx -s reload" # shellcheck disable=SC2029 _result=$(ssh "$DEPLOY_OMV_SSH_USER@$DEPLOY_OMV_HOST" "$_command") _debug _command "$_command" _debug _result "$_result" else # shellcheck disable=SC2086 _uuid=$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'getList' '{"start": 0, "limit": -1}' | jq -r '.data[] | select(.name=="/CN='$_cdomain'") | .uuid') if [ -z "$_uuid" ]; then _info "[OMV deploy-hook] Domain $_cdomain has no certificate in openmediavault, creating it!" # shellcheck disable=SC2086 _uuid=$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'create' '{"cn": "test.example.com", "size": 4096, "days": 3650, "c": "", "st": "", "l": "", "o": "", "ou": "", "email": ""}' | jq -r '.uuid') if [ -z "$_uuid" ]; then _err "[OMB deploy-hook] An error occured while creating the certificate" return 1 fi fi _info "[OMV deploy-hook] Domain $_cdomain has uuid: $_uuid" _fullchain=$(jq <"$_cfullchain" -aRs .) _key=$(jq <"$_ckey" -aRs .) _debug _fullchain "$_fullchain" _debug _key "$_key" _info "[OMV deploy-hook] Updating key and certificate in openmediavault" _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'CertificateMgmt' 'set' '{\"uuid\":\"$_uuid\", \"certificate\":$_fullchain, \"privatekey\":$_key, \"comment\":\"acme.sh deployed $(date)\"}'" _result=$(eval "$_command") _debug _command "$_command" _debug _result "$_result" _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'setSettings' \$(omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'WebGui' 'getSettings' | jq -c '.sslcertificateref=\"$_uuid\"')" _result=$(eval "$_command") _debug _command "$_command" _debug _result "$_result" _info "[OMV deploy-hook] Asking openmediavault to apply changes... (this could take some time, hang in there)" _command="omv-rpc -u $DEPLOY_OMV_WEBUI_ADMIN 'Config' 'applyChanges' '{\"modules\":[], \"force\": false}'" _result=$(eval "$_command") _debug _command "$_command" _debug _result "$_result" _info "[OMV deploy-hook] Asking nginx to reload" _command="nginx -s reload" _result=$(eval "$_command") _debug _command "$_command" _debug _result "$_result" fi return 0 } acme.sh-3.1.0/deploy/opensshd.sh000066400000000000000000000010061472032365200165050ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to opensshd server. #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain opensshd_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" _err "deploy cert to opensshd server, Not implemented yet" return 1 } acme.sh-3.1.0/deploy/openstack.sh000066400000000000000000000207731472032365200166650ustar00rootroot00000000000000#!/usr/bin/env sh # OpenStack Barbican deploy hook # # This requires you to have OpenStackClient and python-barbicanclient # installed. # # You will require Keystone V3 credentials loaded into your environment, which # could be either password or v3applicationcredential type. # # Author: Andy Botting openstack_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" if ! _exists openstack; then _err "OpenStack client not found" return 1 fi _openstack_credentials || return $? _info "Generate import pkcs12" _import_pkcs12="$(_mktemp)" if ! _openstack_to_pkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca"; then _err "Error creating pkcs12 certificate" return 1 fi _debug _import_pkcs12 "$_import_pkcs12" _base64_pkcs12=$(_base64 "multiline" <"$_import_pkcs12") secretHrefs=$(_openstack_get_secrets) _debug secretHrefs "$secretHrefs" _openstack_store_secret || return $? if [ -n "$secretHrefs" ]; then _info "Cleaning up existing secret" _openstack_delete_secrets || return $? fi _info "Certificate successfully deployed" return 0 } _openstack_store_secret() { if ! openstack secret store --name "$_cdomain." -t 'application/octet-stream' -e base64 --payload "$_base64_pkcs12"; then _err "Failed to create OpenStack secret" return 1 fi return } _openstack_delete_secrets() { echo "$secretHrefs" | while read -r secretHref; do _info "Deleting old secret $secretHref" if ! openstack secret delete "$secretHref"; then _err "Failed to delete OpenStack secret" return 1 fi done return } _openstack_get_secrets() { if ! secretHrefs=$(openstack secret list -f value --name "$_cdomain." | cut -d' ' -f1); then _err "Failed to list secrets" return 1 fi echo "$secretHrefs" } _openstack_to_pkcs() { # The existing _toPkcs command can't allow an empty password, due to sh # -z test, so copied here and forcing the empty password. _cpfx="$1" _ckey="$2" _ccert="$3" _cca="$4" ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:" } _openstack_credentials() { _debug "Check OpenStack credentials" # If we have OS_AUTH_URL already set in the environment, then assume we want # to use those, otherwise use stored credentials if [ -n "$OS_AUTH_URL" ]; then _debug "OS_AUTH_URL env var found, using environment" else _debug "OS_AUTH_URL not found, loading stored credentials" OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}" OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}" OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}" OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}" OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}" OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}" OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}" OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}" OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}" OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}" OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}" OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}" OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}" fi # Check each var and either save or clear it depending on whether its set. # The helps us clear out old vars in the case where a user may want # to switch between password and app creds _debug "OS_AUTH_URL" "$OS_AUTH_URL" if [ -n "$OS_AUTH_URL" ]; then export OS_AUTH_URL _saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL" else unset OS_AUTH_URL _clearaccountconf SAVED_OS_AUTH_URL fi _debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION" if [ -n "$OS_IDENTITY_API_VERSION" ]; then export OS_IDENTITY_API_VERSION _saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION" else unset OS_IDENTITY_API_VERSION _clearaccountconf SAVED_OS_IDENTITY_API_VERSION fi _debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE" if [ -n "$OS_AUTH_TYPE" ]; then export OS_AUTH_TYPE _saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE" else unset OS_AUTH_TYPE _clearaccountconf SAVED_OS_AUTH_TYPE fi _debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID" if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then export OS_APPLICATION_CREDENTIAL_ID _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID" else unset OS_APPLICATION_CREDENTIAL_ID _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID fi _secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET" if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then export OS_APPLICATION_CREDENTIAL_SECRET _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET" else unset OS_APPLICATION_CREDENTIAL_SECRET _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET fi _debug "OS_USERNAME" "$OS_USERNAME" if [ -n "$OS_USERNAME" ]; then export OS_USERNAME _saveaccountconf_mutable OS_USERNAME "$OS_USERNAME" else unset OS_USERNAME _clearaccountconf SAVED_OS_USERNAME fi _secure_debug "OS_PASSWORD" "$OS_PASSWORD" if [ -n "$OS_PASSWORD" ]; then export OS_PASSWORD _saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD" else unset OS_PASSWORD _clearaccountconf SAVED_OS_PASSWORD fi _debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME" if [ -n "$OS_PROJECT_NAME" ]; then export OS_PROJECT_NAME _saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME" else unset OS_PROJECT_NAME _clearaccountconf SAVED_OS_PROJECT_NAME fi _debug "OS_PROJECT_ID" "$OS_PROJECT_ID" if [ -n "$OS_PROJECT_ID" ]; then export OS_PROJECT_ID _saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID" else unset OS_PROJECT_ID _clearaccountconf SAVED_OS_PROJECT_ID fi _debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME" if [ -n "$OS_USER_DOMAIN_NAME" ]; then export OS_USER_DOMAIN_NAME _saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME" else unset OS_USER_DOMAIN_NAME _clearaccountconf SAVED_OS_USER_DOMAIN_NAME fi _debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID" if [ -n "$OS_USER_DOMAIN_ID" ]; then export OS_USER_DOMAIN_ID _saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID" else unset OS_USER_DOMAIN_ID _clearaccountconf SAVED_OS_USER_DOMAIN_ID fi _debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME" if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then export OS_PROJECT_DOMAIN_NAME _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME" else unset OS_PROJECT_DOMAIN_NAME _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME fi _debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID" if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then export OS_PROJECT_DOMAIN_ID _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID" else unset OS_PROJECT_DOMAIN_ID _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID fi if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then # Application Credential auth if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then _err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID" _err "and OS_APPLICATION_CREDENTIAL_SECRET must be set." _err "Please check your credentials and try again." return 1 fi else # Password auth if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then _err "OpenStack username or password not found." _err "Please check your credentials and try again." return 1 fi if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then _err "When using password authentication, OS_PROJECT_NAME or" _err "OS_PROJECT_ID must be set." _err "Please check your credentials and try again." return 1 fi fi return 0 } acme.sh-3.1.0/deploy/panos.sh000066400000000000000000000225661472032365200160200ustar00rootroot00000000000000#!/usr/bin/env sh # Script to deploy certificates to Palo Alto Networks PANOS via API # Note PANOS API KEY and IP address needs to be set prior to running. # The following variables exported from environment will be used. # If not set then values previously saved in domain.conf file are used. # # Firewall admin with superuser and IP address is required. # # REQURED: # export PANOS_HOST="" # export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role # export PANOS_PASS="" # # OPTIONAL # export PANOS_TEMPLATE="" #Template Name of panorama managed devices # # The script will automatically generate a new API key if # no key is found, or if a saved key has expired or is invalid. # This function is to parse the XML response from the firewall parse_response() { type=$2 if [ "$type" = 'keygen' ]; then status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g') if [ "$status" = "success" ]; then panos_key=$(echo "$1" | sed 's/^.*\(\)\(.*\)<\/key>.*/\2/g') _panos_key=$panos_key else message="PAN-OS Key could not be set." fi else status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g') message=$(echo "$1" | tr -d '\n' | sed 's/.*\(\|\|\)\([^<]*\).*/\2/g') _debug "Firewall message: $message" if [ "$type" = 'keytest' ] && [ "$status" != "success" ]; then _debug "**** API Key has EXPIRED or is INVALID ****" unset _panos_key fi fi return 0 } #This function is used to deploy to the firewall deployer() { content="" type=$1 # Types are keytest, keygen, cert, key, commit panos_url="https://$_panos_host/api/" #Test API Key by performing a lookup if [ "$type" = 'keytest' ]; then _debug "**** Testing saved API Key ****" _H1="Content-Type: application/x-www-form-urlencoded" # Get Version Info to test key content="type=version&key=$_panos_key" ## Exclude all scopes for the empty commit #_exclude_scope="excludeexcludeexclude" #content="type=commit&action=partial&key=$_panos_key&cmd=$_exclude_scopeacmekeytest" fi # Generate API Key if [ "$type" = 'keygen' ]; then _debug "**** Generating new API Key ****" _H1="Content-Type: application/x-www-form-urlencoded" content="type=keygen&user=$_panos_user&password=$_panos_pass" # content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}" fi # Deploy Cert or Key if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then _debug "**** Deploying $type ****" #Generate DELIM delim="-----MultipartDelimiter$(date "+%s%N")" nl="\015\012" #Set Header export _H1="Content-Type: multipart/form-data; boundary=$delim" if [ "$type" = 'cert' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi fi if [ "$type" = 'key' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cdomain.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" if [ "$_panos_template" ]; then content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" fi fi #Close multipart content="$content${nl}--$delim--${nl}${nl}" #Convert CRLF content=$(printf %b "$content") fi # Commit changes if [ "$type" = 'commit' ]; then _debug "**** Committing changes ****" export _H1="Content-Type: application/x-www-form-urlencoded" #Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution! if [ "$FORCE" ]; then _debug "Force switch detected. Committing ALL changes to the firewall." cmd=$(printf "%s" "$_panos_user" | _url_encode) else _exclude_scope="excludeexclude" cmd=$(printf "%s" "$_exclude_scope$_panos_user" | _url_encode) fi content="type=commit&action=partial&key=$_panos_key&cmd=$cmd" fi response=$(_post "$content" "$panos_url" "" "POST") parse_response "$response" "$type" # Saving response to variables response_status=$status _debug response_status "$response_status" if [ "$response_status" = "success" ]; then _debug "Successfully deployed $type" return 0 else _err "Deploy of type $type failed. Try deploying with --debug to troubleshoot." _debug "$message" return 1 fi } # This is the main function that will call the other functions to deploy everything. panos_deploy() { _cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename _ckey="$2" _cfullchain="$5" # VALID FILE CHECK if [ ! -f "$_ckey" ] || [ ! -f "$_cfullchain" ]; then _err "Unable to find a valid key and/or cert. If this is an ECDSA/ECC cert, use the --ecc flag when deploying." return 1 fi # PANOS_HOST if [ "$PANOS_HOST" ]; then _debug "Detected ENV variable PANOS_HOST. Saving to file." _savedeployconf PANOS_HOST "$PANOS_HOST" 1 else _debug "Attempting to load variable PANOS_HOST from file." _getdeployconf PANOS_HOST fi # PANOS USER if [ "$PANOS_USER" ]; then _debug "Detected ENV variable PANOS_USER. Saving to file." _savedeployconf PANOS_USER "$PANOS_USER" 1 else _debug "Attempting to load variable PANOS_USER from file." _getdeployconf PANOS_USER fi # PANOS_PASS if [ "$PANOS_PASS" ]; then _debug "Detected ENV variable PANOS_PASS. Saving to file." _savedeployconf PANOS_PASS "$PANOS_PASS" 1 else _debug "Attempting to load variable PANOS_PASS from file." _getdeployconf PANOS_PASS fi # PANOS_KEY _getdeployconf PANOS_KEY if [ "$PANOS_KEY" ]; then _debug "Detected saved key." _panos_key=$PANOS_KEY else _debug "No key detected" unset _panos_key fi # PANOS_TEMPLATE if [ "$PANOS_TEMPLATE" ]; then _debug "Detected ENV variable PANOS_TEMPLATE. Saving to file." _savedeployconf PANOS_TEMPLATE "$PANOS_TEMPLATE" 1 else _debug "Attempting to load variable PANOS_TEMPLATE from file." _getdeployconf PANOS_TEMPLATE fi #Store variables _panos_host=$PANOS_HOST _panos_user=$PANOS_USER _panos_pass=$PANOS_PASS _panos_template=$PANOS_TEMPLATE #Test API Key if found. If the key is invalid, the variable _panos_key will be unset. if [ "$_panos_host" ] && [ "$_panos_key" ]; then _debug "**** Testing API KEY ****" deployer keytest fi # Check for valid variables if [ -z "$_panos_host" ]; then _err "No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs." return 1 elif [ -z "$_panos_user" ]; then _err "No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs." return 1 elif [ -z "$_panos_pass" ]; then _err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs." return 1 else # Generate a new API key if no valid API key is found if [ -z "$_panos_key" ]; then _debug "**** Generating new PANOS API KEY ****" deployer keygen _savedeployconf PANOS_KEY "$_panos_key" 1 fi # Confirm that a valid key was generated if [ -z "$_panos_key" ]; then _err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again" return 1 else deployer cert deployer key deployer commit fi fi } acme.sh-3.1.0/deploy/peplink.sh000066400000000000000000000105371472032365200163350ustar00rootroot00000000000000#!/usr/bin/env sh # Script to deploy cert to Peplink Routers # # The following environment variables must be set: # # PEPLINK_Hostname - Peplink hostname # PEPLINK_Username - Peplink username to login # PEPLINK_Password - Peplink password to login # # The following environmental variables may be set if you don't like their # default values: # # PEPLINK_Certtype - Certificate type to target for replacement # defaults to "webadmin", can be one of: # * "chub" (ContentHub) # * "openvpn" (OpenVPN CA) # * "portal" (Captive Portal SSL) # * "webadmin" (Web Admin SSL) # * "webproxy" (Proxy Root CA) # * "wwan_ca" (Wi-Fi WAN CA) # * "wwan_client" (Wi-Fi WAN Client) # PEPLINK_Scheme - defaults to "https" # PEPLINK_Port - defaults to "443" # #returns 0 means success, otherwise error. ######## Public functions ##################### _peplink_get_cookie_data() { grep -i "\W$1=" | grep -i "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';' } #domain keyfile certfile cafile fullchain peplink_deploy() { _cdomain="$1" _ckey="$2" _cfullchain="$5" _debug _cdomain "$_cdomain" _debug _cfullchain "$_cfullchain" _debug _ckey "$_ckey" # Get Hostname, Username and Password, but don't save until we successfully authenticate _getdeployconf PEPLINK_Hostname _getdeployconf PEPLINK_Username _getdeployconf PEPLINK_Password if [ -z "${PEPLINK_Hostname:-}" ] || [ -z "${PEPLINK_Username:-}" ] || [ -z "${PEPLINK_Password:-}" ]; then _err "PEPLINK_Hostname & PEPLINK_Username & PEPLINK_Password must be set" return 1 fi _debug2 PEPLINK_Hostname "$PEPLINK_Hostname" _debug2 PEPLINK_Username "$PEPLINK_Username" _secure_debug2 PEPLINK_Password "$PEPLINK_Password" # Optional certificate type, scheme, and port for Peplink _getdeployconf PEPLINK_Certtype _getdeployconf PEPLINK_Scheme _getdeployconf PEPLINK_Port # Don't save the certificate type until we verify it exists and is supported _savedeployconf PEPLINK_Scheme "$PEPLINK_Scheme" _savedeployconf PEPLINK_Port "$PEPLINK_Port" # Default vaules for certificate type, scheme, and port [ -n "${PEPLINK_Certtype}" ] || PEPLINK_Certtype="webadmin" [ -n "${PEPLINK_Scheme}" ] || PEPLINK_Scheme="https" [ -n "${PEPLINK_Port}" ] || PEPLINK_Port="443" _debug2 PEPLINK_Certtype "$PEPLINK_Certtype" _debug2 PEPLINK_Scheme "$PEPLINK_Scheme" _debug2 PEPLINK_Port "$PEPLINK_Port" _base_url="$PEPLINK_Scheme://$PEPLINK_Hostname:$PEPLINK_Port" _debug _base_url "$_base_url" # Login, get the auth token from the cookie _info "Logging into $PEPLINK_Hostname:$PEPLINK_Port" encoded_username="$(printf "%s" "$PEPLINK_Username" | _url_encode)" encoded_password="$(printf "%s" "$PEPLINK_Password" | _url_encode)" response=$(_post "func=login&username=$encoded_username&password=$encoded_password" "$_base_url/cgi-bin/MANGA/api.cgi") auth_token=$(_peplink_get_cookie_data "bauth" <"$HTTP_HEADER") _debug3 response "$response" _debug auth_token "$auth_token" if [ -z "$auth_token" ]; then _err "Unable to authenticate to $PEPLINK_Hostname:$PEPLINK_Port using $PEPLINK_Scheme." _err "Check your username and password." return 1 fi _H1="Cookie: $auth_token" export _H1 _debug2 H1 "${_H1}" # Now that we know the hostnameusername and password are good, save them _savedeployconf PEPLINK_Hostname "$PEPLINK_Hostname" _savedeployconf PEPLINK_Username "$PEPLINK_Username" _savedeployconf PEPLINK_Password "$PEPLINK_Password" _info "Generate form POST request" encoded_key="$(_url_encode <"$_ckey")" encoded_fullchain="$(_url_encode <"$_cfullchain")" body="cert_type=$PEPLINK_Certtype&cert_uid=§ion=CERT_modify&key_pem=$encoded_key&key_pem_passphrase=&key_pem_passphrase_confirm=&cert_pem=$encoded_fullchain" _debug3 body "$body" _info "Upload $PEPLINK_Certtype certificate to the Peplink" response=$(_post "$body" "$_base_url/cgi-bin/MANGA/admin.cgi") _debug3 response "$response" if echo "$response" | grep 'Success' >/dev/null; then # We've verified this certificate type is valid, so save it _savedeployconf PEPLINK_Certtype "$PEPLINK_Certtype" _info "Certificate was updated" return 0 else _err "Unable to update certificate, error code $response" return 1 fi } acme.sh-3.1.0/deploy/proxmoxve.sh000066400000000000000000000113471472032365200167420ustar00rootroot00000000000000#!/usr/bin/env sh # Deploy certificates to a proxmox virtual environment node using the API. # # Environment variables that can be set are: # `DEPLOY_PROXMOXVE_SERVER`: The hostname of the proxmox ve node. Defaults to # _cdomain. # `DEPLOY_PROXMOXVE_SERVER_PORT`: The port number the management interface is on. # Defaults to 8006. # `DEPLOY_PROXMOXVE_NODE_NAME`: The name of the node we'll be connecting to. # Defaults to the host portion of the server # domain name. # `DEPLOY_PROXMOXVE_USER`: The user we'll connect as. Defaults to root. # `DEPLOY_PROXMOXVE_USER_REALM`: The authentication realm the user authenticates # with. Defaults to pam. # `DEPLOY_PROXMOXVE_API_TOKEN_NAME`: The name of the API token created for the # user account. Defaults to acme. # `DEPLOY_PROXMOXVE_API_TOKEN_KEY`: The API token. Required. proxmoxve_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _debug _cdomain "$_cdomain" _debug2 _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" # "Sane" defaults. _getdeployconf DEPLOY_PROXMOXVE_SERVER if [ -z "$DEPLOY_PROXMOXVE_SERVER" ]; then _target_hostname="$_cdomain" else _target_hostname="$DEPLOY_PROXMOXVE_SERVER" _savedeployconf DEPLOY_PROXMOXVE_SERVER "$DEPLOY_PROXMOXVE_SERVER" fi _debug2 DEPLOY_PROXMOXVE_SERVER "$_target_hostname" _getdeployconf DEPLOY_PROXMOXVE_SERVER_PORT if [ -z "$DEPLOY_PROXMOXVE_SERVER_PORT" ]; then _target_port="8006" else _target_port="$DEPLOY_PROXMOXVE_SERVER_PORT" _savedeployconf DEPLOY_PROXMOXVE_SERVER_PORT "$DEPLOY_PROXMOXVE_SERVER_PORT" fi _debug2 DEPLOY_PROXMOXVE_SERVER_PORT "$_target_port" _getdeployconf DEPLOY_PROXMOXVE_NODE_NAME if [ -z "$DEPLOY_PROXMOXVE_NODE_NAME" ]; then _node_name=$(echo "$_target_hostname" | cut -d. -f1) else _node_name="$DEPLOY_PROXMOXVE_NODE_NAME" _savedeployconf DEPLOY_PROXMOXVE_NODE_NAME "$DEPLOY_PROXMOXVE_NODE_NAME" fi _debug2 DEPLOY_PROXMOXVE_NODE_NAME "$_node_name" # Complete URL. _target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/${_node_name}/certificates/custom" _debug TARGET_URL "$_target_url" # More "sane" defaults. _getdeployconf DEPLOY_PROXMOXVE_USER if [ -z "$DEPLOY_PROXMOXVE_USER" ]; then _proxmoxve_user="root" else _proxmoxve_user="$DEPLOY_PROXMOXVE_USER" _savedeployconf DEPLOY_PROXMOXVE_USER "$DEPLOY_PROXMOXVE_USER" fi _debug2 DEPLOY_PROXMOXVE_USER "$_proxmoxve_user" _getdeployconf DEPLOY_PROXMOXVE_USER_REALM if [ -z "$DEPLOY_PROXMOXVE_USER_REALM" ]; then _proxmoxve_user_realm="pam" else _proxmoxve_user_realm="$DEPLOY_PROXMOXVE_USER_REALM" _savedeployconf DEPLOY_PROXMOXVE_USER_REALM "$DEPLOY_PROXMOXVE_USER_REALM" fi _debug2 DEPLOY_PROXMOXVE_USER_REALM "$_proxmoxve_user_realm" _getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_NAME" ]; then _proxmoxve_api_token_name="acme" else _proxmoxve_api_token_name="$DEPLOY_PROXMOXVE_API_TOKEN_NAME" _savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME "$DEPLOY_PROXMOXVE_API_TOKEN_NAME" fi _debug2 DEPLOY_PROXMOXVE_API_TOKEN_NAME "$_proxmoxve_api_token_name" # This is required. _getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_KEY" ]; then _err "API key not provided." return 1 else _proxmoxve_api_token_key="$DEPLOY_PROXMOXVE_API_TOKEN_KEY" _savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY "$DEPLOY_PROXMOXVE_API_TOKEN_KEY" fi _debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY "$_proxmoxve_api_token_key" # PVE API Token header value. Used in "Authorization: PVEAPIToken". _proxmoxve_header_api_token="${_proxmoxve_user}@${_proxmoxve_user_realm}!${_proxmoxve_api_token_name}=${_proxmoxve_api_token_key}" _debug2 "Auth Header" "$_proxmoxve_header_api_token" # Ugly. I hate putting heredocs inside functions because heredocs don't # account for whitespace correctly but it _does_ work and is several times # cleaner than anything else I had here. # # This dumps the json payload to a variable that should be passable to the # _psot function. _json_payload=$( cat < 6.41.3, but it is not guaranteed that it will be # true for future versions when upgrading. # # If the router have other certificates with the same name as the one # beeing deployed, then this script will remove those certificates. # # At the end of the script, the services that use those certificates # could be updated. Currently only the www-ssl service is beeing # updated, but more services could be added. # # For instance: # ```sh # export ROUTER_OS_ADDITIONAL_SERVICES="/ip service set api-ssl certificate=$_cdomain.cer_0" # ``` # # One optional thing to do as well is to create a script that updates # all the required services and run that script in a single command. # # To adopt parameters to `scp` and/or `ssh` set the optional # `ROUTER_OS_SSH_CMD` and `ROUTER_OS_SCP_CMD` variables accordingly, # see ssh(1) and scp(1) for parameters to those commands. # # Example: # ```ssh # export ROUTER_OS_SSH_CMD="ssh -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts" # export ROUTER_OS_SCP_CMD="scp -i /acme.sh/.ssh/router.example.com -o UserKnownHostsFile=/acme.sh/.ssh/known_hosts" # ```` # # returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain routeros_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _err_code=0 _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" _getdeployconf ROUTER_OS_HOST if [ -z "$ROUTER_OS_HOST" ]; then _debug "Using _cdomain as ROUTER_OS_HOST, please set if not correct." ROUTER_OS_HOST="$_cdomain" fi _getdeployconf ROUTER_OS_USERNAME if [ -z "$ROUTER_OS_USERNAME" ]; then _err "Need to set the env variable ROUTER_OS_USERNAME" return 1 fi _getdeployconf ROUTER_OS_PORT if [ -z "$ROUTER_OS_PORT" ]; then _debug "Using default port 22 as ROUTER_OS_PORT, please set if not correct." ROUTER_OS_PORT=22 fi _getdeployconf ROUTER_OS_SSH_CMD if [ -z "$ROUTER_OS_SSH_CMD" ]; then _debug "Use default ssh setup." ROUTER_OS_SSH_CMD="ssh -p $ROUTER_OS_PORT" fi _getdeployconf ROUTER_OS_SCP_CMD if [ -z "$ROUTER_OS_SCP_CMD" ]; then _debug "USe default scp setup." ROUTER_OS_SCP_CMD="scp -P $ROUTER_OS_PORT" fi _getdeployconf ROUTER_OS_ADDITIONAL_SERVICES if [ -z "$ROUTER_OS_ADDITIONAL_SERVICES" ]; then _debug "Not enabling additional services" ROUTER_OS_ADDITIONAL_SERVICES="" fi _savedeployconf ROUTER_OS_HOST "$ROUTER_OS_HOST" _savedeployconf ROUTER_OS_USERNAME "$ROUTER_OS_USERNAME" _savedeployconf ROUTER_OS_PORT "$ROUTER_OS_PORT" _savedeployconf ROUTER_OS_SSH_CMD "$ROUTER_OS_SSH_CMD" _savedeployconf ROUTER_OS_SCP_CMD "$ROUTER_OS_SCP_CMD" _savedeployconf ROUTER_OS_ADDITIONAL_SERVICES "$ROUTER_OS_ADDITIONAL_SERVICES" # push key to routeros if ! _scp_certificate "$_ckey" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.key"; then return $_err_code fi # push certificate chain to routeros if ! _scp_certificate "$_cfullchain" "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST:$_cdomain.cer"; then return $_err_code fi DEPLOY_SCRIPT_CMD=":do {/system script remove \"LECertDeploy-$_cdomain\" } on-error={ }; \ /system script add name=\"LECertDeploy-$_cdomain\" owner=$ROUTER_OS_USERNAME \ comment=\"generated by routeros deploy script in acme.sh\" \ source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\ \n/certificate remove [ find name=$_cdomain.cer_1 ];\ \n/certificate remove [ find name=$_cdomain.cer_2 ];\ \ndelay 1;\ \n/certificate import file-name=$_cdomain.cer passphrase=\\\"\\\";\ \n/certificate import file-name=$_cdomain.key passphrase=\\\"\\\";\ \ndelay 1;\ \n:do {/file remove $_cdomain.cer; } on-error={ }\ \n:do {/file remove $_cdomain.key; } on-error={ }\ \ndelay 2;\ \n/ip service set www-ssl certificate=$_cdomain.cer_0;\ \n$ROUTER_OS_ADDITIONAL_SERVICES;\ \n\" " if ! _ssh_remote_cmd "$DEPLOY_SCRIPT_CMD"; then return $_err_code fi if ! _ssh_remote_cmd "/system script run \"LECertDeploy-$_cdomain\""; then return $_err_code fi if ! _ssh_remote_cmd "/system script remove \"LECertDeploy-$_cdomain\""; then return $_err_code fi return 0 } # inspired by deploy/ssh.sh _ssh_remote_cmd() { _cmd="$1" _secure_debug "Remote commands to execute: $_cmd" _info "Submitting sequence of commands to routeros" # quotations in bash cmd below intended. Squash travis spellcheck error # shellcheck disable=SC2029 $ROUTER_OS_SSH_CMD "$ROUTER_OS_USERNAME@$ROUTER_OS_HOST" "$_cmd" _err_code="$?" if [ "$_err_code" != "0" ]; then _err "Error code $_err_code returned from routeros" fi return $_err_code } _scp_certificate() { _src="$1" _dst="$2" _secure_debug "scp '$_src' to '$_dst'" _info "Push key '$_src' to routeros" $ROUTER_OS_SCP_CMD "$_src" "$_dst" _err_code="$?" if [ "$_err_code" != "0" ]; then _err "Error code $_err_code returned from scp" fi return $_err_code } acme.sh-3.1.0/deploy/ssh.sh000066400000000000000000000365101472032365200154670ustar00rootroot00000000000000#!/usr/bin/env sh # Script to deploy certificates to remote server by SSH # Note that SSH must be able to login to remote host without a password... # SSH Keys must have been exchanged with the remote host. Validate and # test that you can login to USER@SERVER from the host running acme.sh before # using this script. # # The following variables exported from environment will be used. # If not set then values previously saved in domain.conf file are used. # # Only a username is required. All others are optional. # # The following examples are for QNAP NAS running QTS 4.2 # export DEPLOY_SSH_CMD="" # defaults to "ssh -T" # export DEPLOY_SSH_USER="admin" # required # export DEPLOY_SSH_SERVER="host1 host2:8022 192.168.0.1:9022" # defaults to domain name, support multiple servers with optional port # export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem" # export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem" # export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem" # export DEPLOY_SSH_FULLCHAIN="" # export DEPLOY_SSH_REMOTE_CMD="/etc/init.d/stunnel.sh restart" # export DEPLOY_SSH_BACKUP="" # yes or no, default to yes or previously saved value # export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy # export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value # export DEPLOY_SSH_USE_SCP="" yes or no, default to no # export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q" # ######## Public functions ##################### #domain keyfile certfile cafile fullchain ssh_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _cfullchain="$5" _deploy_ssh_servers="" _debug _cdomain "$_cdomain" _debug _ckey "$_ckey" _debug _ccert "$_ccert" _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" # USER is required to login by SSH to remote host. _migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER _getdeployconf DEPLOY_SSH_USER _debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER" if [ -z "$DEPLOY_SSH_USER" ]; then _err "DEPLOY_SSH_USER not defined." return 1 fi _savedeployconf DEPLOY_SSH_USER "$DEPLOY_SSH_USER" # SERVER is optional. If not provided then use _cdomain _migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER _getdeployconf DEPLOY_SSH_SERVER _debug2 DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER" if [ -z "$DEPLOY_SSH_SERVER" ]; then DEPLOY_SSH_SERVER="$_cdomain" fi _savedeployconf DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER" # CMD is optional. If not provided then use ssh _migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD _getdeployconf DEPLOY_SSH_CMD _debug2 DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD" if [ -z "$DEPLOY_SSH_CMD" ]; then DEPLOY_SSH_CMD="ssh -T" fi _savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD" # BACKUP is optional. If not provided then default to previously saved value or yes. _migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP _getdeployconf DEPLOY_SSH_BACKUP _debug2 DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP" if [ -z "$DEPLOY_SSH_BACKUP" ]; then DEPLOY_SSH_BACKUP="yes" fi _savedeployconf DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP" # BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy _migratedeployconf Le_Deploy_ssh_backup_path DEPLOY_SSH_BACKUP_PATH _getdeployconf DEPLOY_SSH_BACKUP_PATH _debug2 DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH" if [ -z "$DEPLOY_SSH_BACKUP_PATH" ]; then DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" fi _savedeployconf DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH" # MULTI_CALL is optional. If not provided then default to previously saved # value (which may be undefined... equivalent to "no"). _migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL _getdeployconf DEPLOY_SSH_MULTI_CALL _debug2 DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL" if [ -z "$DEPLOY_SSH_MULTI_CALL" ]; then DEPLOY_SSH_MULTI_CALL="no" fi _savedeployconf DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL" # KEYFILE is optional. # If provided then private key will be copied to provided filename. _migratedeployconf Le_Deploy_ssh_keyfile DEPLOY_SSH_KEYFILE _getdeployconf DEPLOY_SSH_KEYFILE _debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE" if [ -n "$DEPLOY_SSH_KEYFILE" ]; then _savedeployconf DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE" fi # CERTFILE is optional. # If provided then certificate will be copied or appended to provided filename. _migratedeployconf Le_Deploy_ssh_certfile DEPLOY_SSH_CERTFILE _getdeployconf DEPLOY_SSH_CERTFILE _debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE" if [ -n "$DEPLOY_SSH_CERTFILE" ]; then _savedeployconf DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE" fi # CAFILE is optional. # If provided then CA intermediate certificate will be copied or appended to provided filename. _migratedeployconf Le_Deploy_ssh_cafile DEPLOY_SSH_CAFILE _getdeployconf DEPLOY_SSH_CAFILE _debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE" if [ -n "$DEPLOY_SSH_CAFILE" ]; then _savedeployconf DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE" fi # FULLCHAIN is optional. # If provided then fullchain certificate will be copied or appended to provided filename. _migratedeployconf Le_Deploy_ssh_fullchain DEPLOY_SSH_FULLCHAIN _getdeployconf DEPLOY_SSH_FULLCHAIN _debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN" if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then _savedeployconf DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN" fi # REMOTE_CMD is optional. # If provided then this command will be executed on remote host. _migratedeployconf Le_Deploy_ssh_remote_cmd DEPLOY_SSH_REMOTE_CMD _getdeployconf DEPLOY_SSH_REMOTE_CMD _debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD" if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then _savedeployconf DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD" fi # USE_SCP is optional. If not provided then default to previously saved # value (which may be undefined... equivalent to "no"). _getdeployconf DEPLOY_SSH_USE_SCP _debug2 DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP" if [ -z "$DEPLOY_SSH_USE_SCP" ]; then DEPLOY_SSH_USE_SCP="no" fi _savedeployconf DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP" # SCP_CMD is optional. If not provided then use scp _getdeployconf DEPLOY_SSH_SCP_CMD _debug2 DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD" if [ -z "$DEPLOY_SSH_SCP_CMD" ]; then DEPLOY_SSH_SCP_CMD="scp -q" fi _savedeployconf DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD" if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then DEPLOY_SSH_MULTI_CALL="yes" _info "Using scp as alternate method for copying files. Multicall Mode is implicit" elif [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then _info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host" else _info "Required commands batched and sent in single call to remote host" fi _deploy_ssh_servers="$DEPLOY_SSH_SERVER" for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do _ssh_deploy done } _ssh_deploy() { _err_code=0 _cmdstr="" _backupprefix="" _backupdir="" _local_cert_file="" _local_ca_file="" _local_full_file="" case $DEPLOY_SSH_SERVER in *:*) _host=${DEPLOY_SSH_SERVER%:*} _port=${DEPLOY_SSH_SERVER##*:} ;; *) _host=$DEPLOY_SSH_SERVER _port= ;; esac _info "Deploy certificates to remote server $DEPLOY_SSH_USER@$_host:$_port" if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then _backupprefix="$DEPLOY_SSH_BACKUP_PATH/$_cdomain-backup" _backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')" # run cleanup on the backup directory, erase all older # than 180 days (15552000 seconds). _cmdstr="{ now=\"\$(date -u +%s)\"; for fn in $_backupprefix*; \ do if [ -d \"\$fn\" ] && [ \"\$(expr \$now - \$(date -ur \$fn +%s) )\" -ge \"15552000\" ]; \ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; done; }; $_cmdstr" # Alternate version of above... _cmdstr="find $_backupprefix* -type d -mtime +180 2>/dev/null | xargs rm -rf; $_cmdstr" # Create our backup directory for overwritten cert files. _cmdstr="mkdir -p $_backupdir; $_cmdstr" _info "Backup of old certificate files will be placed in remote directory $_backupdir" _info "Backup directories erased after 180 days." if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi if [ -n "$DEPLOY_SSH_KEYFILE" ]; then if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then # backup file we are about to overwrite. _cmdstr="$_cmdstr cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null;" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi # copy new key into file. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then # scp the file if ! _scp_remote_cmd "$_ckey" "$DEPLOY_SSH_KEYFILE"; then return $_err_code fi else # ssh echo to the file _cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;" _info "will copy private key to remote file $DEPLOY_SSH_KEYFILE" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi fi if [ -n "$DEPLOY_SSH_CERTFILE" ]; then _pipe=">" if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then # if filename is same as previous file then append. _pipe=">>" elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then # backup file we are about to overwrite. _cmdstr="$_cmdstr cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null;" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi # copy new certificate into file. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then # scp the file _local_cert_file=$(_mktemp) if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then cat "$_ckey" >>"$_local_cert_file" fi cat "$_ccert" >>"$_local_cert_file" if ! _scp_remote_cmd "$_local_cert_file" "$DEPLOY_SSH_CERTFILE"; then return $_err_code fi else # ssh echo to the file _cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE;" _info "will copy certificate to remote file $DEPLOY_SSH_CERTFILE" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi fi if [ -n "$DEPLOY_SSH_CAFILE" ]; then _pipe=">" if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ] || [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then # if filename is same as previous file then append. _pipe=">>" elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then # backup file we are about to overwrite. _cmdstr="$_cmdstr cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null;" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi # copy new certificate into file. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then # scp the file _local_ca_file=$(_mktemp) if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ]; then cat "$_ckey" >>"$_local_ca_file" fi if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then cat "$_ccert" >>"$_local_ca_file" fi cat "$_cca" >>"$_local_ca_file" if ! _scp_remote_cmd "$_local_ca_file" "$DEPLOY_SSH_CAFILE"; then return $_err_code fi else # ssh echo to the file _cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE;" _info "will copy CA file to remote file $DEPLOY_SSH_CAFILE" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi fi if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then _pipe=">" if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ] || [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ] || [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then # if filename is same as previous file then append. _pipe=">>" elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then # backup file we are about to overwrite. _cmdstr="$_cmdstr cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null;" if [ "$DEPLOY_SSH_FULLCHAIN" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi # copy new certificate into file. if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then # scp the file _local_full_file=$(_mktemp) if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ]; then cat "$_ckey" >>"$_local_full_file" fi if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ]; then cat "$_ccert" >>"$_local_full_file" fi if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then cat "$_cca" >>"$_local_full_file" fi cat "$_cfullchain" >>"$_local_full_file" if ! _scp_remote_cmd "$_local_full_file" "$DEPLOY_SSH_FULLCHAIN"; then return $_err_code fi else # ssh echo to the file _cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN;" _info "will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi fi # cleanup local files if any if [ -f "$_local_cert_file" ]; then rm -f "$_local_cert_file" fi if [ -f "$_local_ca_file" ]; then rm -f "$_local_ca_file" fi if [ -f "$_local_full_file" ]; then rm -f "$_local_full_file" fi if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then _cmdstr="$_cmdstr $DEPLOY_SSH_REMOTE_CMD;" _info "Will execute remote command $DEPLOY_SSH_REMOTE_CMD" if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi _cmdstr="" fi fi # if commands not all sent in multiple calls then all commands sent in a single SSH call now... if [ -n "$_cmdstr" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi fi # cleanup in case all is ok return 0 } #cmd _ssh_remote_cmd() { _cmd="$1" _ssh_cmd="$DEPLOY_SSH_CMD" if [ -n "$_port" ]; then _ssh_cmd="$_ssh_cmd -p $_port" fi _secure_debug "Remote commands to execute: $_cmd" _info "Submitting sequence of commands to remote server by $_ssh_cmd" # quotations in bash cmd below intended. Squash travis spellcheck error # shellcheck disable=SC2029 $_ssh_cmd "$DEPLOY_SSH_USER@$_host" sh -c "'$_cmd'" _err_code="$?" if [ "$_err_code" != "0" ]; then _err "Error code $_err_code returned from ssh" fi return $_err_code } # cmd scp _scp_remote_cmd() { _src=$1 _dest=$2 _scp_cmd="$DEPLOY_SSH_SCP_CMD" if [ -n "$_port" ]; then _scp_cmd="$_scp_cmd -P $_port" fi _secure_debug "Remote copy source $_src to destination $_dest" _info "Submitting secure copy by $_scp_cmd" $_scp_cmd "$_src" "$DEPLOY_SSH_USER"@"$_host":"$_dest" _err_code="$?" if [ "$_err_code" != "0" ]; then _err "Error code $_err_code returned from scp" fi return $_err_code } acme.sh-3.1.0/deploy/strongswan.sh000066400000000000000000000051741472032365200171010ustar00rootroot00000000000000#!/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 strongswan_deploy() { _cdomain="${1}" _ckey="${2}" _ccert="${3}" _cca="${4}" _cfullchain="${5}" _info "Using strongswan" if _exists ipsec; then _ipsec=ipsec elif _exists strongswan; then _ipsec=strongswan fi if _exists swanctl; then _swanctl=swanctl fi # For legacy stroke mode if [ -n "${_ipsec}" ]; then _info "${_ipsec} command detected" _confdir=$(${_ipsec} --confdir) if [ -z "${_confdir}" ]; then _err "no strongswan --confdir is detected" return 1 fi _info _confdir "${_confdir}" __deploy_cert "$@" "stroke" "${_confdir}" ${_ipsec} reload fi # For modern vici mode if [ -n "${_swanctl}" ]; then _info "${_swanctl} command detected" for _dir in /usr/local/etc/swanctl /etc/swanctl /etc/strongswan/swanctl; do if [ -d ${_dir} ]; then _confdir=${_dir} _info _confdir "${_confdir}" break fi done if [ -z "${_confdir}" ]; then _err "no swanctl config dir is found" return 1 fi __deploy_cert "$@" "vici" "${_confdir}" ${_swanctl} --load-creds fi if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then _err "no strongswan or ipsec command is detected" _err "no swanctl is detected" return 1 fi } #################### Private functions below ################################## __deploy_cert() { _cdomain="${1}" _ckey="${2}" _ccert="${3}" _cca="${4}" _cfullchain="${5}" _swan_mode="${6}" _confdir="${7}" _debug _cdomain "${_cdomain}" _debug _ckey "${_ckey}" _debug _ccert "${_ccert}" _debug _cca "${_cca}" _debug _cfullchain "${_cfullchain}" _debug _swan_mode "${_swan_mode}" _debug _confdir "${_confdir}" if [ "${_swan_mode}" = "vici" ]; then _dir_private="private" _dir_cert="x509" _dir_ca="x509ca" elif [ "${_swan_mode}" = "stroke" ]; then _dir_private="ipsec.d/private" _dir_cert="ipsec.d/certs" _dir_ca="ipsec.d/cacerts" else _err "unknown StrongSwan mode ${_swan_mode}" return 1 fi cat "${_ckey}" >"${_confdir}/${_dir_private}/$(basename "${_ckey}")" cat "${_ccert}" >"${_confdir}/${_dir_cert}/$(basename "${_ccert}")" cat "${_cca}" >"${_confdir}/${_dir_ca}/$(basename "${_cca}")" if [ "${_swan_mode}" = "stroke" ]; then cat "${_cfullchain}" >"${_confdir}/${_dir_ca}/$(basename "${_cfullchain}")" fi } acme.sh-3.1.0/deploy/synology_dsm.sh000066400000000000000000000503741472032365200174240ustar00rootroot00000000000000#!/bin/bash ################################################################################ # ACME.sh 3rd party deploy plugin for Synology DSM ################################################################################ # Authors: Brian Hartvigsen (creator), https://github.com/tresni # Martin Arndt (contributor), https://troublezone.net/ # Updated: 2023-07-03 # Issues: https://github.com/acmesh-official/acme.sh/issues/2727 ################################################################################ # Usage (shown values are the examples): # 1. Set required environment variables: # - use automatically created temp admin user to authenticate # export SYNO_USE_TEMP_ADMIN=1 # - or provide your own admin user credential to authenticate # 1. export SYNO_USERNAME="adminUser" # 2. export SYNO_PASSWORD="adminPassword" # 2. Set optional environment variables # - common optional variables # - export SYNO_SCHEME="http" - defaults to "http" # - export SYNO_HOSTNAME="localhost" - defaults to "localhost" # - export SYNO_PORT="5000" - defaults to "5000" # - export SYNO_CREATE=1 - to allow creating the cert if it doesn't exist # - export SYNO_CERTIFICATE="" - to replace a specific cert by its # description # - temp admin optional variables # - export SYNO_LOCAL_HOSTNAME=1 - if set to 1, force to treat hostname is # targeting current local machine (since # this method only locally supported) # - exsiting admin 2FA-OTP optional variables # - export SYNO_OTP_CODE="XXXXXX" - if set, script won't require to # interactive input the OTP code # - export SYNO_DEVICE_NAME="CertRenewal" - if set, script won't require to # interactive input the device name # - export SYNO_DEVICE_ID="" - (deprecated, auth with OTP code instead) # required for omitting 2FA-OTP # 3. Run command: # acme.sh --deploy --deploy-hook synology_dsm -d example.com ################################################################################ # Dependencies: # - curl # - synouser & synogroup & synosetkeyvalue (Required for SYNO_USE_TEMP_ADMIN=1) ################################################################################ # Return value: # 0 means success, otherwise error. ################################################################################ ########## Public functions #################################################### #domain keyfile certfile cafile fullchain synology_dsm_deploy() { _cdomain="$1" _ckey="$2" _ccert="$3" _cca="$4" _debug _cdomain "$_cdomain" # Get username and password, but don't save until we authenticated successfully _migratedeployconf SYNO_Username SYNO_USERNAME _migratedeployconf SYNO_Password SYNO_PASSWORD _migratedeployconf SYNO_Device_ID SYNO_DEVICE_ID _migratedeployconf SYNO_Device_Name SYNO_DEVICE_NAME _getdeployconf SYNO_USERNAME _getdeployconf SYNO_PASSWORD _getdeployconf SYNO_DEVICE_ID _getdeployconf SYNO_DEVICE_NAME # Prepare to use temp admin if SYNO_USE_TEMP_ADMIN is set _getdeployconf SYNO_USE_TEMP_ADMIN _check2cleardeployconfexp SYNO_USE_TEMP_ADMIN _debug2 SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN" if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then _err "Missing required tools to creat temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead." _err "Notice: temp admin user authorization method only supports local deployment on DSM." return 1 fi if synouser --help 2>&1 | grep -q 'Permission denied'; then _err "For creating temp admin user, the deploy script must be run as root." return 1 fi [ -n "$SYNO_USERNAME" ] || _savedeployconf SYNO_USERNAME "" [ -n "$SYNO_PASSWORD" ] || _savedeployconf SYNO_PASSWORD "" _debug "Setting temp admin user credential..." SYNO_USERNAME=sc-acmesh-tmp SYNO_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16) # Set 2FA-OTP settings to empty consider they won't be needed. SYNO_DEVICE_ID= SYNO_DEVICE_NAME= SYNO_OTP_CODE= else _debug2 SYNO_USERNAME "$SYNO_USERNAME" _secure_debug2 SYNO_PASSWORD "$SYNO_PASSWORD" _debug2 SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME" _secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID" fi if [ -z "$SYNO_USERNAME" ] || [ -z "$SYNO_PASSWORD" ]; then _err "You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_USERNAME and SYNO_PASSWORD." return 1 fi # Optional scheme, hostname and port for Synology DSM _migratedeployconf SYNO_Scheme SYNO_SCHEME _migratedeployconf SYNO_Hostname SYNO_HOSTNAME _migratedeployconf SYNO_Port SYNO_PORT _getdeployconf SYNO_SCHEME _getdeployconf SYNO_HOSTNAME _getdeployconf SYNO_PORT # Default values for scheme, hostname and port # Defaulting to localhost and http, because it's localhost… [ -n "$SYNO_SCHEME" ] || SYNO_SCHEME=http [ -n "$SYNO_HOSTNAME" ] || SYNO_HOSTNAME=localhost [ -n "$SYNO_PORT" ] || SYNO_PORT=5000 _savedeployconf SYNO_SCHEME "$SYNO_SCHEME" _savedeployconf SYNO_HOSTNAME "$SYNO_HOSTNAME" _savedeployconf SYNO_PORT "$SYNO_PORT" _debug2 SYNO_SCHEME "$SYNO_SCHEME" _debug2 SYNO_HOSTNAME "$SYNO_HOSTNAME" _debug2 SYNO_PORT "$SYNO_PORT" # Get the certificate description, but don't save it until we verify it's real _migratedeployconf SYNO_Certificate SYNO_CERTIFICATE "base64" _getdeployconf SYNO_CERTIFICATE _check2cleardeployconfexp SYNO_CERTIFICATE _debug SYNO_CERTIFICATE "${SYNO_CERTIFICATE:-}" # shellcheck disable=SC1003 # We are not trying to escape a single quote if printf "%s" "$SYNO_CERTIFICATE" | grep '\\'; then _err "Do not use a backslash (\) in your certificate description" return 1 fi _debug "Getting API version..." _base_url="$SYNO_SCHEME://$SYNO_HOSTNAME:$SYNO_PORT" _debug _base_url "$_base_url" response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth") api_path=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"path" *: *"\([^"]*\)".*/\1/p') api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p') _debug3 response "$response" _debug3 api_path "$api_path" _debug3 api_version "$api_version" # Login, get the session ID and SynoToken from JSON _info "Logging into $SYNO_HOSTNAME:$SYNO_PORT..." encoded_username="$(printf "%s" "$SYNO_USERNAME" | _url_encode)" encoded_password="$(printf "%s" "$SYNO_PASSWORD" | _url_encode)" # ## START ## - DEPRECATED, for backward compatibility _getdeployconf SYNO_TOTP_SECRET if [ -n "$SYNO_TOTP_SECRET" ]; then _info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!" _info " See synology_dsm.sh script or ACME.sh Wiki page for details:" _info " https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide" if ! _exists oathtool; then _err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET" return 1 fi DEPRECATED_otp_code="$(oathtool --base32 --totp "$SYNO_TOTP_SECRET" 2>/dev/null)" if [ -z "$SYNO_DEVICE_ID" ]; then _getdeployconf SYNO_DID [ -n "$SYNO_DID" ] || SYNO_DEVICE_ID="$SYNO_DID" fi if [ -n "$SYNO_DEVICE_ID" ]; then _H1="Cookie: did=$SYNO_DEVICE_ID" export _H1 _debug3 H1 "${_H1}" fi response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DEVICE_ID" "$_base_url/webapi/$api_path?enable_syno_token=yes") _debug3 response "$response" # ## END ## - DEPRECATED, for backward compatibility # If SYNO_DEVICE_ID or SYNO_OTP_CODE is set, we treat current account enabled 2FA-OTP. # Notice that if SYNO_USE_TEMP_ADMIN=1, both variables will be unset else if [ -n "$SYNO_DEVICE_ID" ] || [ -n "$SYNO_OTP_CODE" ]; then response='{"error":{"code":403}}' # Assume the current account disabled 2FA-OTP, try to log in right away. else if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then _getdeployconf SYNO_LOCAL_HOSTNAME _debug SYNO_LOCAL_HOSTNAME "${SYNO_LOCAL_HOSTNAME:-}" if [ "$SYNO_LOCAL_HOSTNAME" != "1" ] && [ "$SYNO_LOCAL_HOSTNAME" == "$SYNO_HOSTNAME" ]; then if [ "$SYNO_HOSTNAME" != "localhost" ] && [ "$SYNO_HOSTNAME" != "127.0.0.1" ]; then _err "SYNO_USE_TEMP_ADMIN=1 only support local deployment, though if you are sure that the hostname $SYNO_HOSTNAME is targeting to your **current local machine**, execute 'export SYNO_LOCAL_HOSTNAME=1' then rerun." return 1 fi fi _debug "Creating temp admin user in Synology DSM..." if synogroup --help | grep -q '\-\-memberadd '; then _temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD" synogroup --memberadd administrators "$SYNO_USERNAME" >/dev/null elif synogroup --help | grep -q '\-\-member '; then # For supporting DSM 6.x which only has `--member` parameter. cur_admins=$(synogroup --get administrators | awk -F '[][]' '/Group Members/,0{if(NF>1)printf "%s ", $2}') if [ -n "$cur_admins" ]; then _temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD" _secure_debug3 admin_users "$cur_admins$SYNO_USERNAME" # shellcheck disable=SC2086 synogroup --member administrators $cur_admins $SYNO_USERNAME >/dev/null else _err "The tool synogroup may be broken, please set SYNO_USERNAME and SYNO_PASSWORD instead." return 1 fi else _err "Unsupported synogroup tool detected, please set SYNO_USERNAME and SYNO_PASSWORD instead." return 1 fi # havig a workaround to temporary disable enforce 2FA-OTP, will restore # it soon (after a single request), though if any accident occurs like # unexpected interruption, this setting can be easily reverted manually. otp_enforce_option=$(synogetkeyvalue /etc/synoinfo.conf otp_enforce_option) if [ -n "$otp_enforce_option" ] && [ "${otp_enforce_option:-"none"}" != "none" ]; then synosetkeyvalue /etc/synoinfo.conf otp_enforce_option none _info "Enforcing 2FA-OTP has been disabled to complete temp admin authentication." _info "Notice: it will be restored soon, if not, you can restore it manually via Control Panel." _info "previous_otp_enforce_option" "$otp_enforce_option" else otp_enforce_option="" fi fi response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes") if [ -n "$SYNO_USE_TEMP_ADMIN" ] && [ -n "$otp_enforce_option" ]; then synosetkeyvalue /etc/synoinfo.conf otp_enforce_option "$otp_enforce_option" _info "Restored previous enforce 2FA-OTP option." fi _debug3 response "$response" fi fi error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') _debug2 error_code "$error_code" # Account has 2FA-OTP enabled, since error 403 reported. # https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf if [ "$error_code" == "403" ]; then if [ -z "$SYNO_DEVICE_NAME" ]; then printf "Enter device name or leave empty for default (CertRenewal): " read -r SYNO_DEVICE_NAME [ -n "$SYNO_DEVICE_NAME" ] || SYNO_DEVICE_NAME="CertRenewal" fi if [ -n "$SYNO_DEVICE_ID" ]; then # Omit OTP code with SYNO_DEVICE_ID. response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_DEVICE_NAME&device_id=$SYNO_DEVICE_ID") _secure_debug3 response "$response" else # Require the OTP code if still unset. if [ -z "$SYNO_OTP_CODE" ]; then printf "Enter OTP code for user '%s': " "$SYNO_USERNAME" read -r SYNO_OTP_CODE fi _secure_debug SYNO_OTP_CODE "${SYNO_OTP_CODE:-}" if [ -z "$SYNO_OTP_CODE" ]; then response='{"error":{"code":404}}' else response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_DEVICE_NAME&otp_code=$SYNO_OTP_CODE") _secure_debug3 response "$response" id_property='device_id' [ "${api_version}" -gt '6' ] || id_property='did' SYNO_DEVICE_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p') _secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID" fi fi error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') _debug2 error_code "$error_code" fi if [ -n "$error_code" ]; then if [ "$error_code" == "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then _cleardeployconf SYNO_DEVICE_ID _err "Failed to authenticate with SYNO_DEVICE_ID (may expired or invalid), please try again in a new terminal window." elif [ "$error_code" == "404" ]; then _err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window." elif [ "$error_code" == "406" ]; then if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then _err "Failed with unexcepted error, please report this by providing full log with '--debug 3'." else _err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue." fi elif [ "$error_code" == "400" ]; then _err "Failed to authenticate, no such account or incorrect password." elif [ "$error_code" == "401" ]; then _err "Failed to authenticate with a non-existent account." elif [ "$error_code" == "408" ] || [ "$error_code" == "409" ] || [ "$error_code" == "410" ]; then _err "Failed to authenticate, the account password has expired or must be changed." else _err "Failed to authenticate with error: $error_code." fi _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 1 fi sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p') token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p') _debug "Session ID" "$sid" _debug SynoToken "$token" if [ -z "$sid" ] || [ -z "$token" ]; then # Still can't get necessary info even got no errors, may Synology have API updated? _err "Unable to authenticate to $_base_url, you may report this by providing full log with '--debug 3'." _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 1 fi _H1="X-SYNO-TOKEN: $token" export _H1 _debug2 H1 "${_H1}" # Now that we know the username and password are good, save them if not in temp admin mode. if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then _cleardeployconf SYNO_USERNAME _cleardeployconf SYNO_PASSWORD _cleardeployconf SYNO_DEVICE_ID _cleardeployconf SYNO_DEVICE_NAME _savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN" _savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_HOSTNAME" else _savedeployconf SYNO_USERNAME "$SYNO_USERNAME" _savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD" _savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID" _savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME" fi _info "Getting certificates in Synology DSM..." response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi") _debug3 response "$response" escaped_certificate="$(printf "%s" "$SYNO_CERTIFICATE" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')" _debug escaped_certificate "$escaped_certificate" id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p") _debug2 id "$id" error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -o '[0-9]*') _debug2 error_code "$error_code" if [ -n "$error_code" ]; then if [ "$error_code" -eq 105 ]; then _err "Current user is not administrator and does not have sufficient permission for deploying." else _err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more." fi _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 1 fi _migratedeployconf SYNO_Create SYNO_CREATE _getdeployconf SYNO_CREATE _debug2 SYNO_CREATE "$SYNO_CREATE" if [ -z "$id" ] && [ -z "$SYNO_CREATE" ]; then _err "Unable to find certificate: $SYNO_CERTIFICATE and $SYNO_CREATE is not set." _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 1 fi # We've verified this certificate description is a thing, so save it _savedeployconf SYNO_CERTIFICATE "$SYNO_CERTIFICATE" "base64" _info "Generating form POST request..." nl="\0015\0012" delim="--------------------------$(_utc_date | tr -d -- '-: ')" content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_CERTIFICATE}" if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then _debug2 default "This is the default certificate" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true" else _debug2 default "This is NOT the default certificate" fi content="$content${nl}--$delim--${nl}" content="$(printf "%b_" "$content")" content="${content%_}" # protect trailing \n _info "Upload certificate to the Synology DSM." response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid" "" "POST" "multipart/form-data; boundary=${delim}") _debug3 response "$response" if ! echo "$response" | grep '"error":' >/dev/null; then if echo "$response" | grep '"restart_httpd":true' >/dev/null; then _info "Restart HTTP services succeeded." else _info "Restart HTTP services failed." fi _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" _logout return 0 else _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" _err "Unable to update certificate, got error response: $response." _logout return 1 fi } #################### Private functions below ################################## _logout() { # Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables) response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid") _debug3 response "$response" } _temp_admin_create() { _username="$1" _password="$2" synouser --del "$_username" >/dev/null 2>/dev/null synouser --add "$_username" "$_password" "" 0 "scruelt@hotmail.com" 0 >/dev/null } _temp_admin_cleanup() { _flag=$1 _username=$2 if [ -n "${_flag}" ]; then _debug "Cleanuping temp admin info..." synouser --del "$_username" >/dev/null fi } #_cleardeployconf key _cleardeployconf() { _cleardomainconf "SAVED_$1" } # key _check2cleardeployconfexp() { _key="$1" _clear_key="CLEAR_$_key" # Clear saved settings if explicitly requested if [ -n "$(eval echo \$"$_clear_key")" ]; then _debug2 "$_key: value cleared from config, exported value will be ignored." _cleardeployconf "$_key" eval "$_key"= export "$_key"= eval SAVED_"$_key"= export SAVED_"$_key"= fi } acme.sh-3.1.0/deploy/truenas.sh000066400000000000000000000300751472032365200163530ustar00rootroot00000000000000#!/usr/bin/env sh # Here is a scipt to deploy the cert to your TrueNAS using the REST API. # https://www.truenas.com/docs/hub/additional-topics/api/rest_api.html # # Written by Frank Plass github@f-plass.de # https://github.com/danb35/deploy-freenas/blob/master/deploy_freenas.py # Thanks to danb35 for your template! # # Following environment variables must be set: # # export DEPLOY_TRUENAS_APIKEY="" # # The following environmental variables may be set if you don't like their # default values: # # DEPLOY_TRUENAS_HOSTNAME - defaults to localhost # DEPLOY_TRUENAS_SCHEME - defaults to http, set alternatively to https # #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain truenas_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_TRUENAS_APIKEY if [ -z "$DEPLOY_TRUENAS_APIKEY" ]; then _err "TrueNAS API key not found, please set the DEPLOY_TRUENAS_APIKEY environment variable." return 1 fi _secure_debug2 DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" # Optional hostname, scheme for TrueNAS _getdeployconf DEPLOY_TRUENAS_HOSTNAME _getdeployconf DEPLOY_TRUENAS_SCHEME # default values for hostname and scheme [ -n "${DEPLOY_TRUENAS_HOSTNAME}" ] || DEPLOY_TRUENAS_HOSTNAME="localhost" [ -n "${DEPLOY_TRUENAS_SCHEME}" ] || DEPLOY_TRUENAS_SCHEME="http" _debug2 DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME" _debug2 DEPLOY_TRUENAS_SCHEME "$DEPLOY_TRUENAS_SCHEME" _api_url="$DEPLOY_TRUENAS_SCHEME://$DEPLOY_TRUENAS_HOSTNAME/api/v2.0" _debug _api_url "$_api_url" _H1="Authorization: Bearer $DEPLOY_TRUENAS_APIKEY" _secure_debug3 _H1 "$_H1" _info "Testing Connection TrueNAS" _response=$(_get "$_api_url/system/state") _info "TrueNAS system state: $_response." _info "Getting TrueNAS version" _response=$(_get "$_api_url/system/version") if echo "$_response" | grep -q "SCALE"; then _truenas_os=$(echo "$_response" | cut -d '-' -f 2) _truenas_version=$(echo "$_response" | cut -d '-' -f 3 | tr -d '"' | cut -d '.' -f 1,2) else _truenas_os="unknown" _truenas_version="unknown" fi _info "Detected TrueNAS system os: $_truenas_os" _info "Detected TrueNAS system version: $_truenas_version" if [ -z "$_response" ]; then _err "Unable to authenticate to $_api_url." _err 'Check your connection settings are correct, e.g.' _err 'DEPLOY_TRUENAS_HOSTNAME="192.168.x.y" or DEPLOY_TRUENAS_HOSTNAME="truenas.example.com".' _err 'DEPLOY_TRUENAS_SCHEME="https" or DEPLOY_TRUENAS_SCHEME="http".' _err "Verify your TrueNAS API key is valid and set correctly, e.g. DEPLOY_TRUENAS_APIKEY=xxxx...." return 1 fi _savedeployconf DEPLOY_TRUENAS_APIKEY "$DEPLOY_TRUENAS_APIKEY" _savedeployconf DEPLOY_TRUENAS_HOSTNAME "$DEPLOY_TRUENAS_HOSTNAME" _savedeployconf DEPLOY_TRUENAS_SCHEME "$DEPLOY_TRUENAS_SCHEME" _info "Getting current active certificate from TrueNAS" _response=$(_get "$_api_url/system/general") _active_cert_id=$(echo "$_response" | grep -B2 '"name":' | grep 'id' | tr -d -- '"id: ,') _active_cert_name=$(echo "$_response" | grep '"name":' | sed -n 's/.*: "\(.\{1,\}\)",$/\1/p') _param_httpsredirect=$(echo "$_response" | grep '"ui_httpsredirect":' | sed -n 's/.*": \(.\{1,\}\),$/\1/p') _debug Active_UI_Certificate_ID "$_active_cert_id" _debug Active_UI_Certificate_Name "$_active_cert_name" _debug Active_UI_http_redirect "$_param_httpsredirect" if [ "$DEPLOY_TRUENAS_SCHEME" = "http" ] && [ "$_param_httpsredirect" = "true" ]; then _info "HTTP->HTTPS redirection is enabled" _info "Setting DEPLOY_TRUENAS_SCHEME to 'https'" DEPLOY_TRUENAS_SCHEME="https" _api_url="$DEPLOY_TRUENAS_SCHEME://$DEPLOY_TRUENAS_HOSTNAME/api/v2.0" _savedeployconf DEPLOY_TRUENAS_SCHEME "$DEPLOY_TRUENAS_SCHEME" fi _info "Uploading new certificate to TrueNAS" _certname="Letsencrypt_$(_utc_date | tr ' ' '_' | tr -d -- ':')" _debug3 _certname "$_certname" _certData="{\"create_type\": \"CERTIFICATE_CREATE_IMPORTED\", \"name\": \"${_certname}\", \"certificate\": \"$(_json_encode <"$_cfullchain")\", \"privatekey\": \"$(_json_encode <"$_ckey")\"}" _add_cert_result="$(_post "$_certData" "$_api_url/certificate" "" "POST" "application/json")" _debug3 _add_cert_result "$_add_cert_result" _info "Fetching list of installed certificates" _cert_list=$(_get "$_api_url/system/general/ui_certificate_choices") _cert_id=$(echo "$_cert_list" | grep "$_certname" | sed -n 's/.*"\([0-9]\{1,\}\)".*$/\1/p') _debug3 _cert_id "$_cert_id" _info "Current activate certificate ID: $_cert_id" _activateData="{\"ui_certificate\": \"${_cert_id}\"}" _activate_result="$(_post "$_activateData" "$_api_url/system/general" "" "PUT" "application/json")" _debug3 _activate_result "$_activate_result" _truenas_version_23_10="23.10" _truenas_version_24_10="24.10" _check_version=$(printf "%s\n%s" "$_truenas_version_23_10" "$_truenas_version" | sort -V | head -n 1) if [ "$_truenas_os" != "SCALE" ] || [ "$_check_version" != "$_truenas_version_23_10" ]; then _info "Checking if WebDAV certificate is the same as the TrueNAS web UI" _webdav_list=$(_get "$_api_url/webdav") _webdav_cert_id=$(echo "$_webdav_list" | grep '"certssl":' | tr -d -- '"certsl: ,') if [ "$_webdav_cert_id" = "$_active_cert_id" ]; then _info "Updating the WebDAV certificate" _debug _webdav_cert_id "$_webdav_cert_id" _webdav_data="{\"certssl\": \"${_cert_id}\"}" _activate_webdav_cert="$(_post "$_webdav_data" "$_api_url/webdav" "" "PUT" "application/json")" _webdav_new_cert_id=$(echo "$_activate_webdav_cert" | _json_decode | grep '"certssl":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p') if [ "$_webdav_new_cert_id" -eq "$_cert_id" ]; then _info "WebDAV certificate updated successfully" else _err "Unable to set WebDAV certificate" _debug3 _activate_webdav_cert "$_activate_webdav_cert" _debug3 _webdav_new_cert_id "$_webdav_new_cert_id" return 1 fi _debug3 _webdav_new_cert_id "$_webdav_new_cert_id" else _info "WebDAV certificate is not configured or is not the same as TrueNAS web UI" fi _info "Checking if S3 certificate is the same as the TrueNAS web UI" _s3_list=$(_get "$_api_url/s3") _s3_cert_id=$(echo "$_s3_list" | grep '"certificate":' | tr -d -- '"certifa:_ ,') if [ "$_s3_cert_id" = "$_active_cert_id" ]; then _info "Updating the S3 certificate" _debug _s3_cert_id "$_s3_cert_id" _s3_data="{\"certificate\": \"${_cert_id}\"}" _activate_s3_cert="$(_post "$_s3_data" "$_api_url/s3" "" "PUT" "application/json")" _s3_new_cert_id=$(echo "$_activate_s3_cert" | _json_decode | grep '"certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p') if [ "$_s3_new_cert_id" -eq "$_cert_id" ]; then _info "S3 certificate updated successfully" else _err "Unable to set S3 certificate" _debug3 _activate_s3_cert "$_activate_s3_cert" _debug3 _s3_new_cert_id "$_s3_new_cert_id" return 1 fi _debug3 _activate_s3_cert "$_activate_s3_cert" else _info "S3 certificate is not configured or is not the same as TrueNAS web UI" fi fi if [ "$_truenas_os" = "SCALE" ]; then _check_version=$(printf "%s\n%s" "$_truenas_version_24_10" "$_truenas_version" | sort -V | head -n 1) if [ "$_check_version" != "$_truenas_version_24_10" ]; then _info "Checking if any chart release Apps is using the same certificate as TrueNAS web UI. Tool 'jq' is required" if _exists jq; then _info "Query all chart release" _release_list=$(_get "$_api_url/chart/release") _related_name_list=$(printf "%s" "$_release_list" | jq -r "[.[] | {name,certId: .config.ingress?.main.tls[]?.scaleCert} | select(.certId==$_active_cert_id) | .name ] | unique") _release_length=$(printf "%s" "$_related_name_list" | jq -r "length") _info "Found $_release_length related chart release in list: $_related_name_list" for i in $(seq 0 $((_release_length - 1))); do _release_name=$(echo "$_related_name_list" | jq -r ".[$i]") _info "Updating certificate from $_active_cert_id to $_cert_id for chart release: $_release_name" #Read the chart release configuration _chart_config=$(printf "%s" "$_release_list" | jq -r ".[] | select(.name==\"$_release_name\")") #Replace the old certificate id with the new one in path .config.ingress.main.tls[].scaleCert. Then update .config.ingress _updated_chart_config=$(printf "%s" "$_chart_config" | jq "(.config.ingress?.main.tls[]? | select(.scaleCert==$_active_cert_id) | .scaleCert ) |= $_cert_id | .config.ingress ") _update_chart_result="$(_post "{\"values\" : { \"ingress\" : $_updated_chart_config } }" "$_api_url/chart/release/id/$_release_name" "" "PUT" "application/json")" _debug3 _update_chart_result "$_update_chart_result" done else _info "Tool 'jq' does not exists, skip chart release checking" fi else _info "Checking if any app is using the same certificate as TrueNAS web UI. Tool 'jq' is required" if _exists jq; then _info "Query all apps" _app_list=$(_get "$_api_url/app") _app_id_list=$(printf "%s" "$_app_list" | jq -r '.[].name') _app_length=$(echo "$_app_id_list" | wc -l) _info "Found $_app_length apps" _info "Checking for each app if an update is needed" for i in $(seq 1 "$_app_length"); do _app_id=$(echo "$_app_id_list" | sed -n "${i}p") _app_config="$(_post "\"$_app_id\"" "$_api_url/app/config" "" "POST" "application/json")" # Check if the app use the same certificate TrueNAS web UI _app_active_cert_config=$(echo "$_app_config" | _json_decode | jq -r ".ix_certificates[\"$_active_cert_id\"]") if [ "$_app_active_cert_config" != "null" ]; then _info "Updating certificate from $_active_cert_id to $_cert_id for app: $_app_id" #Replace the old certificate id with the new one in path _update_app_result="$(_post "{\"values\" : { \"network\": { \"certificate_id\": $_cert_id } } }" "$_api_url/app/id/$_app_id" "" "PUT" "application/json")" _debug3 _update_app_result "$_update_app_result" fi done else _info "Tool 'jq' does not exists, skip app checking" fi fi fi _info "Checking if FTP certificate is the same as the TrueNAS web UI" _ftp_list=$(_get "$_api_url/ftp") _ftp_cert_id=$(echo "$_ftp_list" | grep '"ssltls_certificate":' | tr -d -- '"certislfa:_ ,') if [ "$_ftp_cert_id" = "$_active_cert_id" ]; then _info "Updating the FTP certificate" _debug _ftp_cert_id "$_ftp_cert_id" _ftp_data="{\"ssltls_certificate\": \"${_cert_id}\"}" _activate_ftp_cert="$(_post "$_ftp_data" "$_api_url/ftp" "" "PUT" "application/json")" _ftp_new_cert_id=$(echo "$_activate_ftp_cert" | _json_decode | grep '"ssltls_certificate":' | sed -n 's/.*: \([0-9]\{1,\}\),\{0,1\}$/\1/p') if [ "$_ftp_new_cert_id" -eq "$_cert_id" ]; then _info "FTP certificate updated successfully" else _err "Unable to set FTP certificate" _debug3 _activate_ftp_cert "$_activate_ftp_cert" _debug3 _ftp_new_cert_id "$_ftp_new_cert_id" return 1 fi _debug3 _activate_ftp_cert "$_activate_ftp_cert" else _info "FTP certificate is not configured or is not the same as TrueNAS web UI" fi _info "Deleting old certificate" _delete_result="$(_post "" "$_api_url/certificate/id/$_active_cert_id" "" "DELETE" "application/json")" _debug3 _delete_result "$_delete_result" _info "Reloading TrueNAS web UI" _restart_UI=$(_get "$_api_url/system/general/ui_restart") _debug2 _restart_UI "$_restart_UI" if [ -n "$_add_cert_result" ] && [ -n "$_activate_result" ]; then return 0 else _err "Certificate update was not succesful, please try again with --debug" return 1 fi } acme.sh-3.1.0/deploy/unifi.sh000066400000000000000000000264521472032365200160100ustar00rootroot00000000000000#!/usr/bin/env sh # Here is a script to deploy cert on a Unifi Controller or Cloud Key device. # It supports: # - self-hosted Unifi Controller # - Unifi Cloud Key (Gen1/2/2+) # - Unifi Cloud Key running UnifiOS (v2.0.0+, Gen2/2+ only) # - Unifi Dream Machine # This has not been tested on other "all-in-one" devices such as # UDM Pro or Unifi Express. # # OS Version v2.0.0+ # Network Application version 7.0.0+ # OS version ~3.1 removed java and keytool from the UnifiOS. # Using PKCS12 format keystore appears to work fine. # # Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3359 #returns 0 means success, otherwise error. # The deploy-hook automatically detects standard Unifi installations # for each of the supported environments. Most users should not need # to set any of these variables, but if you are running a self-hosted # Controller with custom locations, set these as necessary before running # the deploy hook. (Defaults shown below.) # # Settings for Unifi Controller: # Location of Java keystore or unifi.keystore.jks file: #DEPLOY_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore" # Keystore password (built into Unifi Controller, not a user-set password): #DEPLOY_UNIFI_KEYPASS="aircontrolenterprise" # Command to restart Unifi Controller: #DEPLOY_UNIFI_RELOAD="service unifi restart" # # Settings for Unifi Cloud Key Gen1 (nginx admin pages): # Directory where cloudkey.crt and cloudkey.key live: #DEPLOY_UNIFI_CLOUDKEY_CERTDIR="/etc/ssl/private" # Command to restart maintenance pages and Controller # (same setting as above, default is updated when running on Cloud Key Gen1): #DEPLOY_UNIFI_RELOAD="service nginx restart && service unifi restart" # # Settings for UnifiOS (Cloud Key Gen2): # Directory where unifi-core.crt and unifi-core.key live: #DEPLOY_UNIFI_CORE_CONFIG="/data/unifi-core/config/" # Command to restart unifi-core: #DEPLOY_UNIFI_RELOAD="systemctl restart unifi-core" # # At least one of DEPLOY_UNIFI_KEYSTORE, DEPLOY_UNIFI_CLOUDKEY_CERTDIR, # or DEPLOY_UNIFI_CORE_CONFIG must exist to receive the deployed certs. ######## Public functions ##################### #domain keyfile certfile cafile fullchain unifi_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_UNIFI_KEYSTORE _getdeployconf DEPLOY_UNIFI_KEYPASS _getdeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR _getdeployconf DEPLOY_UNIFI_CORE_CONFIG _getdeployconf DEPLOY_UNIFI_RELOAD _debug2 DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE" _debug2 DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS" _debug2 DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR" _debug2 DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG" _debug2 DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD" # Space-separated list of environments detected and installed: _services_updated="" # Default reload commands accumulated as we auto-detect environments: _reload_cmd="" # Unifi Controller environment (self hosted or any Cloud Key) -- # auto-detect by file /usr/lib/unifi/data/keystore _unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-/usr/lib/unifi/data/keystore}" if [ -f "$_unifi_keystore" ]; then _debug _unifi_keystore "$_unifi_keystore" if ! _exists keytool; then _do_keytool=0 _info "Installing certificate for Unifi Controller (PKCS12 keystore)." else _do_keytool=1 _info "Installing certificate for Unifi Controller (Java keystore)" fi if [ ! -w "$_unifi_keystore" ]; then _err "The file $_unifi_keystore is not writable, please change the permission." return 1 fi _unifi_keypass="${DEPLOY_UNIFI_KEYPASS:-aircontrolenterprise}" _debug "Generate import pkcs12" _import_pkcs12="$(_mktemp)" _debug "_toPkcs $_import_pkcs12 $_ckey $_ccert $_cca $_unifi_keypass unifi root" _toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root # shellcheck disable=SC2181 if [ "$?" != "0" ]; then _err "Error generating pkcs12. Please re-run with --debug and report a bug." return 1 fi # Save the existing keystore in case something goes wrong. mv -f "${_unifi_keystore}" "${_unifi_keystore}"_original _info "Previous keystore saved to ${_unifi_keystore}_original." if [ "$_do_keytool" -eq 1 ]; then _debug "Import into keystore: $_unifi_keystore" if keytool -importkeystore \ -deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \ -srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \ -alias unifi -noprompt; then _debug "Import keystore success!" else _err "Error importing into Unifi Java keystore." _err "Please re-run with --debug and report a bug." _info "Restoring original keystore." mv -f "${_unifi_keystore}"_original "${_unifi_keystore}" rm "$_import_pkcs12" return 1 fi else _debug "Copying new keystore to $_unifi_keystore" cp -f "$_import_pkcs12" "$_unifi_keystore" fi # Update unifi service for certificate cipher compatibility if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \ -in "$_import_pkcs12" \ -password pass:aircontrolenterprise \ -nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \ -noout | grep -i "signature" | grep -iq ecdsa >/dev/null 2>&1; then cp -f /usr/lib/unifi/data/system.properties /usr/lib/unifi/data/system.properties_original _info "Updating system configuration for cipher compatibility." _info "Saved original system config to /usr/lib/unifi/data/system.properties_original" sed -i '/unifi\.https\.ciphers/d' /usr/lib/unifi/data/system.properties echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>/usr/lib/unifi/data/system.properties sed -i '/unifi\.https\.sslEnabledProtocols/d' /usr/lib/unifi/data/system.properties echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>/usr/lib/unifi/data/system.properties _info "System configuration updated." fi rm "$_import_pkcs12" # Restarting unifi-core will bring up unifi, doing it out of order results in # a certificate error, and breaks wifiman. # Restart if we aren't doing unifi-core, otherwise stop for later restart. if systemctl -q is-active unifi; then if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi" else _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl stop unifi" fi fi _services_updated="${_services_updated} unifi" _info "Install Unifi Controller certificate success!" elif [ "$DEPLOY_UNIFI_KEYSTORE" ]; then _err "The specified DEPLOY_UNIFI_KEYSTORE='$DEPLOY_UNIFI_KEYSTORE' is not valid, please check." return 1 fi # Cloud Key environment (non-UnifiOS -- nginx serves admin pages) -- # auto-detect by file /etc/ssl/private/cloudkey.key: _cloudkey_certdir="${DEPLOY_UNIFI_CLOUDKEY_CERTDIR:-/etc/ssl/private}" if [ -f "${_cloudkey_certdir}/cloudkey.key" ]; then _info "Installing certificate for Cloud Key Gen1 (nginx admin pages)" _debug _cloudkey_certdir "$_cloudkey_certdir" if [ ! -w "$_cloudkey_certdir" ]; then _err "The directory $_cloudkey_certdir is not writable; please check permissions." return 1 fi # Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks. # Normally /usr/lib/unifi/data/keystore is a symlink there (so the keystore was # updated above), but if not, we don't know how to handle this installation: if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'" return 1 fi cat "$_cfullchain" >"${_cloudkey_certdir}/cloudkey.crt" cat "$_ckey" >"${_cloudkey_certdir}/cloudkey.key" (cd "$_cloudkey_certdir" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks) if systemctl -q is-active nginx; then _reload_cmd="${_reload_cmd:+$_reload_cmd && }service nginx restart" fi _info "Install Cloud Key Gen1 certificate success!" _services_updated="${_services_updated} nginx" elif [ "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR" ]; then _err "The specified DEPLOY_UNIFI_CLOUDKEY_CERTDIR='$DEPLOY_UNIFI_CLOUDKEY_CERTDIR' is not valid, please check." return 1 fi # UnifiOS environment -- auto-detect by /data/unifi-core/config/unifi-core.key: _unifi_core_config="${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}" if [ -f "${_unifi_core_config}/unifi-core.key" ]; then _info "Installing certificate for UnifiOS" _debug _unifi_core_config "$_unifi_core_config" if [ ! -w "$_unifi_core_config" ]; then _err "The directory $_unifi_core_config is not writable; please check permissions." return 1 fi # Save the existing certs in case something goes wrong. cp -f "${_unifi_core_config}"/unifi-core.crt "${_unifi_core_config}"/unifi-core_original.crt cp -f "${_unifi_core_config}"/unifi-core.key "${_unifi_core_config}"/unifi-core_original.key _info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt/key." cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt" cat "$_ckey" >"${_unifi_core_config}/unifi-core.key" if systemctl -q is-active unifi-core; then _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi-core" fi _info "Install UnifiOS certificate success!" _services_updated="${_services_updated} unifi-core" elif [ "$DEPLOY_UNIFI_CORE_CONFIG" ]; then _err "The specified DEPLOY_UNIFI_CORE_CONFIG='$DEPLOY_UNIFI_CORE_CONFIG' is not valid, please check." return 1 fi if [ -z "$_services_updated" ]; then # None of the Unifi environments were auto-detected, so no deployment has occurred # (and none of DEPLOY_UNIFI_{KEYSTORE,CLOUDKEY_CERTDIR,CORE_CONFIG} were set). _err "Unable to detect Unifi environment in standard location." _err "(This deploy hook must be run on the Unifi device, not a remote machine.)" _err "For non-standard Unifi installations, set DEPLOY_UNIFI_KEYSTORE," _err "DEPLOY_UNIFI_CLOUDKEY_CERTDIR, and/or DEPLOY_UNIFI_CORE_CONFIG as appropriate." return 1 fi _reload_cmd="${DEPLOY_UNIFI_RELOAD:-$_reload_cmd}" if [ -z "$_reload_cmd" ]; then _err "Certificates were installed for services:${_services_updated}," _err "but none appear to be active. Please set DEPLOY_UNIFI_RELOAD" _err "to a command that will restart the necessary services." return 1 fi _info "Reload services (this may take some time): $_reload_cmd" if eval "$_reload_cmd"; then _info "Reload success!" else _err "Reload error" return 1 fi # Successful, so save all (non-default) config: _savedeployconf DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE" _savedeployconf DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS" _savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR" _savedeployconf DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG" _savedeployconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD" return 0 } acme.sh-3.1.0/deploy/vault.sh000066400000000000000000000112711472032365200160220ustar00rootroot00000000000000#!/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 # VAULT_SAVE_TOKEN - set to anything if you want to save the token # VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying # VAULT_KV_V2 - set to anything if you are using v2 of the kv engine # # 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" _getdeployconf VAULT_SAVE_TOKEN _savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN" _getdeployconf VAULT_RENEW_TOKEN _savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN" _getdeployconf VAULT_KV_V2 _savedeployconf VAULT_KV_V2 "$VAULT_KV_V2" _getdeployconf VAULT_TOKEN if [ -z "$VAULT_TOKEN" ]; then _err "VAULT_TOKEN needs to be defined" return 1 fi if [ -n "$VAULT_SAVE_TOKEN" ]; then _savedeployconf VAULT_TOKEN "$VAULT_TOKEN" fi _migratedeployconf FABIO VAULT_FABIO_MODE # JSON does not allow multiline strings. # So replacing new-lines with "\n" here _ckey=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$2") _ccert=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$3") _cca=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$4") _cfullchain=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$5") export _H1="X-Vault-Token: $VAULT_TOKEN" if [ -n "$VAULT_RENEW_TOKEN" ]; then URL="$VAULT_ADDR/v1/auth/token/renew-self" _info "Renew the Vault token to default TTL" if ! _post "" "$URL" >/dev/null; then _err "Failed to renew the Vault token" return 1 fi fi URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain" if [ -n "$VAULT_FABIO_MODE" ]; then _info "Writing certificate and key to $URL in Fabio mode" if [ -n "$VAULT_KV_V2" ]; then _post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL" >/dev/null || return 1 else _post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" >/dev/null || return 1 fi else if [ -n "$VAULT_KV_V2" ]; then _info "Writing certificate to $URL/cert.pem" _post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem" >/dev/null || return 1 _info "Writing key to $URL/cert.key" _post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key" >/dev/null || return 1 _info "Writing CA certificate to $URL/ca.pem" _post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem" >/dev/null || return 1 _info "Writing full-chain certificate to $URL/fullchain.pem" _post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem" >/dev/null || return 1 else _info "Writing certificate to $URL/cert.pem" _post "{\"value\": \"$_ccert\"}" "$URL/cert.pem" >/dev/null || return 1 _info "Writing key to $URL/cert.key" _post "{\"value\": \"$_ckey\"}" "$URL/cert.key" >/dev/null || return 1 _info "Writing CA certificate to $URL/ca.pem" _post "{\"value\": \"$_cca\"}" "$URL/ca.pem" >/dev/null || return 1 _info "Writing full-chain certificate to $URL/fullchain.pem" _post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem" >/dev/null || return 1 fi # To make it compatible with the wrong ca path `chain.pem` which was used in former versions if _contains "$(_get "$URL/chain.pem")" "-----BEGIN CERTIFICATE-----"; then _err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning" _info "Updating CA certificate to $URL/chain.pem for backward compatibility" if [ -n "$VAULT_KV_V2" ]; then _post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem" >/dev/null || return 1 else _post "{\"value\": \"$_cca\"}" "$URL/chain.pem" >/dev/null || return 1 fi fi fi } acme.sh-3.1.0/deploy/vault_cli.sh000066400000000000000000000071161472032365200166540ustar00rootroot00000000000000#!/usr/bin/env sh # Here is a script to deploy cert to hashicorp vault # (https://www.vaultproject.io/) # # it requires the vault binary to be available in PATH, and the following # environment variables: # # VAULT_PREFIX - this contains the prefix path in vault # VAULT_ADDR - vault requires this to find your vault server # VAULT_SAVE_TOKEN - set to anything if you want to save the token # VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying # # additionally, you need to ensure that VAULT_TOKEN is avialable or # `vault auth` has applied the appropriate authorization for the vault binary # to access the vault server #returns 0 means success, otherwise error. ######## Public functions ##################### #domain keyfile certfile cafile fullchain vault_cli_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" _getdeployconf VAULT_SAVE_TOKEN _savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN" _getdeployconf VAULT_RENEW_TOKEN _savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN" _getdeployconf VAULT_TOKEN if [ -z "$VAULT_TOKEN" ]; then _err "VAULT_TOKEN needs to be defined" return 1 fi if [ -n "$VAULT_SAVE_TOKEN" ]; then _savedeployconf VAULT_TOKEN "$VAULT_TOKEN" fi _migratedeployconf FABIO VAULT_FABIO_MODE VAULT_CMD=$(command -v vault) if [ ! $? ]; then _err "cannot find vault binary!" return 1 fi if [ -n "$VAULT_RENEW_TOKEN" ]; then _info "Renew the Vault token to default TTL" if ! $VAULT_CMD token renew; then _err "Failed to renew the Vault token" return 1 fi fi if [ -n "$VAULT_FABIO_MODE" ]; then _info "Writing certificate and key to ${VAULT_PREFIX}/${_cdomain} in Fabio mode" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1 else _info "Writing certificate to ${VAULT_PREFIX}/${_cdomain}/cert.pem" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 _info "Writing key to ${VAULT_PREFIX}/${_cdomain}/cert.key" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 _info "Writing CA certificate to ${VAULT_PREFIX}/${_cdomain}/ca.pem" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/ca.pem" value=@"$_cca" || return 1 _info "Writing full-chain certificate to ${VAULT_PREFIX}/${_cdomain}/fullchain.pem" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 # To make it compatible with the wrong ca path `chain.pem` which was used in former versions if $VAULT_CMD kv get "${VAULT_PREFIX}/${_cdomain}/chain.pem" >/dev/null; then _err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning" _info "Updating CA certificate to ${VAULT_PREFIX}/${_cdomain}/chain.pem for backward compatibility" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 fi fi } acme.sh-3.1.0/deploy/vsftpd.sh000066400000000000000000000061201472032365200161720ustar00rootroot00000000000000#!/usr/bin/env sh #Here is a script to deploy cert to vsftpd server. #returns 0 means success, otherwise error. #DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf" #DEPLOY_VSFTPD_RELOAD="service vsftpd restart" ######## Public functions ##################### #domain keyfile certfile cafile fullchain vsftpd_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" _ssl_path="/etc/acme.sh/vsftpd" if ! mkdir -p "$_ssl_path"; then _err "Can not create folder:$_ssl_path" return 1 fi _info "Copying key and cert" _real_key="$_ssl_path/vsftpd.key" if ! cat "$_ckey" >"$_real_key"; then _err "Error: write key file to: $_real_key" return 1 fi _real_fullchain="$_ssl_path/vsftpd.chain.pem" if ! cat "$_cfullchain" >"$_real_fullchain"; then _err "Error: write key file to: $_real_fullchain" return 1 fi DEFAULT_VSFTPD_RELOAD="service vsftpd restart" _reload="${DEPLOY_VSFTPD_RELOAD:-$DEFAULT_VSFTPD_RELOAD}" if [ -z "$IS_RENEW" ]; then DEFAULT_VSFTPD_CONF="/etc/vsftpd.conf" _vsftpd_conf="${DEPLOY_VSFTPD_CONF:-$DEFAULT_VSFTPD_CONF}" if [ ! -f "$_vsftpd_conf" ]; then if [ -z "$DEPLOY_VSFTPD_CONF" ]; then _err "vsftpd conf is not found, please define DEPLOY_VSFTPD_CONF" return 1 else _err "It seems that the specified vsftpd conf is not valid, please check." return 1 fi fi if [ ! -w "$_vsftpd_conf" ]; then _err "The file $_vsftpd_conf is not writable, please change the permission." return 1 fi _backup_conf="$DOMAIN_BACKUP_PATH/vsftpd.conf.bak" _info "Backup $_vsftpd_conf to $_backup_conf" cp "$_vsftpd_conf" "$_backup_conf" _info "Modify vsftpd conf: $_vsftpd_conf" if _setopt "$_vsftpd_conf" "rsa_cert_file" "=" "$_real_fullchain" && _setopt "$_vsftpd_conf" "rsa_private_key_file" "=" "$_real_key" && _setopt "$_vsftpd_conf" "ssl_enable" "=" "YES"; then _info "Set config success!" else _err "Config vsftpd server error, please report bug to us." _info "Restoring vsftpd conf" if cat "$_backup_conf" >"$_vsftpd_conf"; then _info "Restore conf success" eval "$_reload" else _err "Oops, error restore vsftpd conf, please report bug to us." fi return 1 fi fi _info "Run reload: $_reload" if eval "$_reload"; then _info "Reload success!" if [ "$DEPLOY_VSFTPD_CONF" ]; then _savedomainconf DEPLOY_VSFTPD_CONF "$DEPLOY_VSFTPD_CONF" else _cleardomainconf DEPLOY_VSFTPD_CONF fi if [ "$DEPLOY_VSFTPD_RELOAD" ]; then _savedomainconf DEPLOY_VSFTPD_RELOAD "$DEPLOY_VSFTPD_RELOAD" else _cleardomainconf DEPLOY_VSFTPD_RELOAD fi return 0 else _err "Reload error, restoring" if cat "$_backup_conf" >"$_vsftpd_conf"; then _info "Restore conf success" eval "$_reload" else _err "Oops, error restore vsftpd conf, please report bug to us." fi return 1 fi } acme.sh-3.1.0/dnsapi/000077500000000000000000000000001472032365200143135ustar00rootroot00000000000000acme.sh-3.1.0/dnsapi/README.md000066400000000000000000000001361472032365200155720ustar00rootroot00000000000000# How to use DNS API DNS api usage: https://github.com/acmesh-official/acme.sh/wiki/dnsapi acme.sh-3.1.0/dnsapi/dns_1984hosting.sh000077500000000000000000000210511472032365200175160ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_1984hosting_info='1984.hosting Domains: 1984.is Site: 1984.hosting Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_1984hosting Options: One984HOSTING_Username Username One984HOSTING_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2851 Author: Adrian Fedoreanu ' ######## Public functions ##################### # Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Add a text record. dns_1984hosting_add() { fulldomain=$1 txtvalue=$2 _info "Add TXT record using 1984Hosting." _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" if ! _1984hosting_login; then _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file." return 1 fi _debug "First detect the root zone." if ! _get_root "$fulldomain"; then _err "Invalid domain '$fulldomain'." return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Add TXT record $fulldomain with value '$txtvalue'." value="$(printf '%s' "$txtvalue" | _url_encode)" url="https://1984.hosting/domains/entry/" postdata="entry=new" postdata="$postdata&type=TXT" postdata="$postdata&ttl=900" postdata="$postdata&zone=$_domain" postdata="$postdata&host=$_sub_domain" postdata="$postdata&rdata=%22$value%22" _debug2 postdata "$postdata" _authpost "$postdata" "$url" 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" "html>"; 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 # Remove the txt record after validation. dns_1984hosting_rm() { fulldomain=$1 txtvalue=$2 _info "Delete TXT record using 1984Hosting." _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" if ! _1984hosting_login; then _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file." return 1 fi _debug "First detect the root zone." if ! _get_root "$fulldomain"; then _err "Invalid domain '$fulldomain'." return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Delete $fulldomain TXT record." url="https://1984.hosting/domains" if ! _get_zone_id "$url" "$_domain"; then _err "Invalid zone '$_domain'." return 1 fi _htmlget "$url/$_zone_id" "$txtvalue" entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')" _debug2 entry_id "$entry_id" if [ -z "$entry_id" ]; then _err "Error getting TXT entry_id for $1." return 1 fi _authpost "entry=$entry_id" "$url/delentry/" if ! _contains "$_response" '"ok": true'; then _err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post." return 1 fi _info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting." return 0 } #################### Private functions below ################################## _1984hosting_login() { if ! _check_credentials; then return 1; fi if _check_cookies; then _debug "Already logged in." return 0 fi _debug "Login to 1984Hosting as user $One984HOSTING_Username." username=$(printf '%s' "$One984HOSTING_Username" | _url_encode) password=$(printf '%s' "$One984HOSTING_Password" | _url_encode) url="https://1984.hosting/api/auth/" _get "https://1984.hosting/accounts/login/" | grep "csrfmiddlewaretoken" csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')" sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')" if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then _err "One or more cookies are empty: '$csrftoken', '$sessionid'." return 1 fi export _H1="Cookie: $csrftoken; $sessionid" export _H2="Referer: https://1984.hosting/accounts/login/" csrf_header=$(echo "$csrftoken" | sed 's/csrftoken=//' | _head_n 1) export _H3="X-CSRFToken: $csrf_header" response="$(_post "username=$username&password=$password&otpkey=" $url)" response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" if _contains "$response" '"loggedin": true'; then One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')" One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')" export One984HOSTING_SESSIONID_COOKIE export One984HOSTING_CSRFTOKEN_COOKIE _saveaccountconf_mutable One984HOSTING_Username "$One984HOSTING_Username" _saveaccountconf_mutable One984HOSTING_Password "$One984HOSTING_Password" _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE" _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE" return 0 fi return 1 } _check_credentials() { One984HOSTING_Username="${One984HOSTING_Username:-$(_readaccountconf_mutable One984HOSTING_Username)}" One984HOSTING_Password="${One984HOSTING_Password:-$(_readaccountconf_mutable One984HOSTING_Password)}" if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then One984HOSTING_Username="" One984HOSTING_Password="" _clearaccountconf_mutable One984HOSTING_Username _clearaccountconf_mutable One984HOSTING_Password _err "You haven't specified 1984Hosting username or password yet." _err "Please export as One984HOSTING_Username / One984HOSTING_Password and try again." return 1 fi return 0 } _check_cookies() { One984HOSTING_SESSIONID_COOKIE="${One984HOSTING_SESSIONID_COOKIE:-$(_readaccountconf_mutable One984HOSTING_SESSIONID_COOKIE)}" One984HOSTING_CSRFTOKEN_COOKIE="${One984HOSTING_CSRFTOKEN_COOKIE:-$(_readaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE)}" if [ -z "$One984HOSTING_SESSIONID_COOKIE" ] || [ -z "$One984HOSTING_CSRFTOKEN_COOKIE" ]; then _debug "No cached cookie(s) found." return 1 fi _authget "https://1984.hosting/api/auth/" if _contains "$_response" '"ok": true'; then _debug "Cached cookies still valid." return 0 fi _debug "Cached cookies no longer valid. Clearing cookies." One984HOSTING_SESSIONID_COOKIE="" One984HOSTING_CSRFTOKEN_COOKIE="" _clearaccountconf_mutable One984HOSTING_SESSIONID_COOKIE _clearaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE return 1 } # _acme-challenge.www.domain.com # Returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain="$1" i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) # not valid if [ -z "$h" ]; then return 1 fi _authget "https://1984.hosting/domains/zonestatus/$h/?cached=no" if _contains "$_response" '"ok": true'; 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 } # Usage: _get_zone_id url domain.com # Returns zone id for domain.com _get_zone_id() { url=$1 domain=$2 _htmlget "$url" "$domain" _zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+' | _head_n 1)" _debug2 _zone_id "$_zone_id" if [ -z "$_zone_id" ]; then _err "Error getting _zone_id for $2." return 1 fi return 0 } # Add extra headers to request _authget() { export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE" _response=$(_get "$1" | _normalizeJson) _debug2 _response "$_response" } # Truncate huge HTML response _htmlget() { export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE" _response=$(_get "$1" | grep "$2") if _contains "$_response" "@$2"; then _response=$(echo "$_response" | grep -v "[@]" | _head_n 1) fi _debug2 _response "$_response" } # Add extra headers to request _authpost() { url="https://1984.hosting/domains" _get_zone_id "$url" "$_domain" csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")" export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE" export _H2="Referer: https://1984.hosting/domains/$_zone_id" export _H3="X-CSRFToken: $csrf_header" _response="$(_post "$1" "$2" | _normalizeJson)" _debug2 _response "$_response" } acme.sh-3.1.0/dnsapi/dns_acmedns.sh000077500000000000000000000071771472032365200171440ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_acmedns_info='acme-dns Server API The acme-dns is a limited DNS server with RESTful API to handle ACME DNS challenges. Site: github.com/joohoi/acme-dns Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_acmedns Options: ACMEDNS_USERNAME Username. Optional. ACMEDNS_PASSWORD Password. Optional. ACMEDNS_SUBDOMAIN Subdomain. Optional. ACMEDNS_BASE_URL API endpoint. Default: "https://auth.acme-dns.io". Issues: github.com/dampfklon/acme.sh Author: Wolfgang Ebner, Sven Neubuaer ' ######## Public functions ##################### #Usage: dns_acmedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record dns_acmedns_add() { fulldomain=$1 txtvalue=$2 _info "Using acme-dns" _debug "fulldomain $fulldomain" _debug "txtvalue $txtvalue" #for compatiblity from account conf ACMEDNS_USERNAME="${ACMEDNS_USERNAME:-$(_readaccountconf_mutable ACMEDNS_USERNAME)}" _clearaccountconf_mutable ACMEDNS_USERNAME ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readaccountconf_mutable ACMEDNS_PASSWORD)}" _clearaccountconf_mutable ACMEDNS_PASSWORD ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readaccountconf_mutable ACMEDNS_SUBDOMAIN)}" _clearaccountconf_mutable ACMEDNS_SUBDOMAIN ACMEDNS_BASE_URL="${ACMEDNS_BASE_URL:-$(_readdomainconf ACMEDNS_BASE_URL)}" ACMEDNS_USERNAME="${ACMEDNS_USERNAME:-$(_readdomainconf ACMEDNS_USERNAME)}" ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readdomainconf ACMEDNS_PASSWORD)}" ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readdomainconf ACMEDNS_SUBDOMAIN)}" if [ "$ACMEDNS_BASE_URL" = "" ]; then ACMEDNS_BASE_URL="https://auth.acme-dns.io" fi ACMEDNS_UPDATE_URL="$ACMEDNS_BASE_URL/update" ACMEDNS_REGISTER_URL="$ACMEDNS_BASE_URL/register" if [ -z "$ACMEDNS_USERNAME" ] || [ -z "$ACMEDNS_PASSWORD" ]; then response="$(_post "" "$ACMEDNS_REGISTER_URL" "" "POST")" _debug response "$response" ACMEDNS_USERNAME=$(echo "$response" | sed -n 's/^{.*\"username\":[ ]*\"\([^\"]*\)\".*}/\1/p') _debug "received username: $ACMEDNS_USERNAME" ACMEDNS_PASSWORD=$(echo "$response" | sed -n 's/^{.*\"password\":[ ]*\"\([^\"]*\)\".*}/\1/p') _debug "received password: $ACMEDNS_PASSWORD" ACMEDNS_SUBDOMAIN=$(echo "$response" | sed -n 's/^{.*\"subdomain\":[ ]*\"\([^\"]*\)\".*}/\1/p') _debug "received subdomain: $ACMEDNS_SUBDOMAIN" ACMEDNS_FULLDOMAIN=$(echo "$response" | sed -n 's/^{.*\"fulldomain\":[ ]*\"\([^\"]*\)\".*}/\1/p') _info "##########################################################" _info "# Create $fulldomain CNAME $ACMEDNS_FULLDOMAIN DNS entry #" _info "##########################################################" _info "Press enter to continue... " read -r _ fi _savedomainconf ACMEDNS_BASE_URL "$ACMEDNS_BASE_URL" _savedomainconf ACMEDNS_USERNAME "$ACMEDNS_USERNAME" _savedomainconf ACMEDNS_PASSWORD "$ACMEDNS_PASSWORD" _savedomainconf 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 ################################## acme.sh-3.1.0/dnsapi/dns_acmeproxy.sh000077500000000000000000000062331472032365200175310ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_acmeproxy_info='AcmeProxy Server API AcmeProxy can be used to as a single host in your network to request certificates through a DNS API. Clients can connect with the one AcmeProxy host so you do not need to store DNS API credentials on every single host. Site: github.com/mdbraber/acmeproxy Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_acmeproxy Options: ACMEPROXY_ENDPOINT API Endpoint ACMEPROXY_USERNAME Username ACMEPROXY_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/2251 Author: Maarten den Braber ' dns_acmeproxy_add() { fulldomain="${1}" txtvalue="${2}" action="present" _debug "Calling: _acmeproxy_request() '${fulldomain}' '${txtvalue}' '${action}'" _acmeproxy_request "$fulldomain" "$txtvalue" "$action" } dns_acmeproxy_rm() { fulldomain="${1}" txtvalue="${2}" action="cleanup" _debug "Calling: _acmeproxy_request() '${fulldomain}' '${txtvalue}' '${action}'" _acmeproxy_request "$fulldomain" "$txtvalue" "$action" } _acmeproxy_request() { ## Nothing to see here, just some housekeeping fulldomain=$1 txtvalue=$2 action=$3 _info "Using acmeproxy" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" ACMEPROXY_ENDPOINT="${ACMEPROXY_ENDPOINT:-$(_readaccountconf_mutable ACMEPROXY_ENDPOINT)}" ACMEPROXY_USERNAME="${ACMEPROXY_USERNAME:-$(_readaccountconf_mutable ACMEPROXY_USERNAME)}" ACMEPROXY_PASSWORD="${ACMEPROXY_PASSWORD:-$(_readaccountconf_mutable ACMEPROXY_PASSWORD)}" ## Check for the endpoint if [ -z "$ACMEPROXY_ENDPOINT" ]; then ACMEPROXY_ENDPOINT="" _err "You didn't specify the endpoint" _err "Please set them via 'export ACMEPROXY_ENDPOINT=https://ip:port' and try again." return 1 fi ## Save the credentials to the account file _saveaccountconf_mutable ACMEPROXY_ENDPOINT "$ACMEPROXY_ENDPOINT" _saveaccountconf_mutable ACMEPROXY_USERNAME "$ACMEPROXY_USERNAME" _saveaccountconf_mutable ACMEPROXY_PASSWORD "$ACMEPROXY_PASSWORD" if [ -z "$ACMEPROXY_USERNAME" ] || [ -z "$ACMEPROXY_PASSWORD" ]; then _info "ACMEPROXY_USERNAME and/or ACMEPROXY_PASSWORD not set - using without client authentication! Make sure you're using server authentication (e.g. IP-based)" export _H1="Accept: application/json" export _H2="Content-Type: application/json" else ## Base64 encode the credentials credentials=$(printf "%b" "$ACMEPROXY_USERNAME:$ACMEPROXY_PASSWORD" | _base64) ## Construct the HTTP Authorization header export _H1="Authorization: Basic $credentials" export _H2="Accept: application/json" export _H3="Content-Type: application/json" fi ## Add the challenge record to the acmeproxy grid member response="$(_post "{\"fqdn\": \"$fulldomain.\", \"value\": \"$txtvalue\"}" "$ACMEPROXY_ENDPOINT/$action" "" "POST")" ## Let's see if we get something intelligible back from the unit if echo "$response" | grep "\"$txtvalue\"" >/dev/null; then _info "Successfully updated the txt record" return 0 else _err "Error encountered during record addition" _err "$response" return 1 fi } #################### Private functions below ################################## acme.sh-3.1.0/dnsapi/dns_active24.sh000077500000000000000000000063661472032365200171520ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_active24_info='Active24.com Site: Active24.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24 Options: ACTIVE24_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2059 Author: Milan Pála ' ACTIVE24_Api="https://api.active24.com" ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record dns_active24_add() { fulldomain=$1 txtvalue=$2 _active24_init _info "Adding txt record" if _active24_rest POST "dns/$_domain/txt/v1" "{\"name\":\"$_sub_domain\",\"text\":\"$txtvalue\",\"ttl\":0}"; then if _contains "$response" "errors"; then _err "Add txt record error." return 1 else _info "Added, OK" return 0 fi fi _err "Add txt record error." return 1 } # Usage: fulldomain txtvalue # Used to remove the txt record after validation dns_active24_rm() { fulldomain=$1 txtvalue=$2 _active24_init _debug "Getting txt records" _active24_rest GET "dns/$_domain/records/v1" if _contains "$response" "errors"; then _err "Error" return 1 fi hash_ids=$(echo "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o "hashId\":\"[^\"]+" | cut -c10-) for hash_id in $hash_ids; do _debug "Removing hash_id" "$hash_id" if _active24_rest DELETE "dns/$_domain/$hash_id/v1" ""; then if _contains "$response" "errors"; then _err "Unable to remove txt record." return 1 else _info "Removed txt record." return 0 fi fi done _err "No txt records found." return 1 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 if ! _active24_rest GET "dns/domains/v1"; then return 1 fi i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug "h" "$h" if [ -z "$h" ]; then #not valid return 1 fi if _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 return 1 } _active24_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Authorization: Bearer $ACTIVE24_Token" if [ "$m" != "GET" ]; then _debug "data" "$data" response="$(_post "$data" "$ACTIVE24_Api/$ep" "" "$m" "application/json")" else response="$(_get "$ACTIVE24_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } _active24_init() { ACTIVE24_Token="${ACTIVE24_Token:-$(_readaccountconf_mutable ACTIVE24_Token)}" if [ -z "$ACTIVE24_Token" ]; then ACTIVE24_Token="" _err "You didn't specify a Active24 api token yet." _err "Please create the token and try again." return 1 fi _saveaccountconf_mutable ACTIVE24_Token "$ACTIVE24_Token" _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" } acme.sh-3.1.0/dnsapi/dns_ad.sh000077500000000000000000000067231472032365200161120ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ad_info='AlwaysData.com Site: AlwaysData.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ad Options: AD_API_KEY API Key Issues: github.com/acmesh-official/acme.sh/pull/503 Author: Paul Koppen ' AD_API_URL="https://$AD_API_KEY:@api.alwaysdata.com/v1" ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_ad_add() { fulldomain=$1 txtvalue=$2 if [ -z "$AD_API_KEY" ]; then AD_API_KEY="" _err "You didn't specify the AD api key yet." _err "Please create you key and try again." return 1 fi _saveaccountconf AD_API_KEY "$AD_API_KEY" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _ad_tmpl_json="{\"domain\":$_domain_id,\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\"}" if _ad_rest POST "record/" "$_ad_tmpl_json" && [ -z "$response" ]; then _info "txt record updated success." return 0 fi return 1 } #fulldomain txtvalue dns_ad_rm() { fulldomain=$1 txtvalue=$2 _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _ad_rest GET "record/?domain=$_domain_id&name=$_sub_domain" if [ -n "$response" ]; then record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) _debug record_id "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if _ad_rest DELETE "record/$record_id/" && [ -z "$response" ]; then _info "txt record deleted success." return 0 fi _debug response "$response" return 1 fi return 1 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=12345 _get_root() { domain=$1 i=2 p=1 if _ad_rest GET "domain/"; then response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")" if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done fi return 1 } #method uri qstr data _ad_rest() { mtd="$1" ep="$2" data="$3" _debug mtd "$mtd" _debug ep "$ep" export _H1="Accept: application/json" export _H2="Content-Type: application/json" if [ "$mtd" != "GET" ]; then # both POST and DELETE. _debug data "$data" response="$(_post "$data" "$AD_API_URL/$ep" "" "$mtd")" else response="$(_get "$AD_API_URL/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_ali.sh000077500000000000000000000131561472032365200162710ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ali_info='AlibabaCloud.com Domains: Aliyun.com Site: AlibabaCloud.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ali Options: Ali_Key API Key Ali_Secret API Secret ' # NOTICE: # This file is referenced by Alibaba Cloud Services deploy hooks # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 # Be careful when modifying this file, especially when making breaking changes for common functions Ali_DNS_API="https://alidns.aliyuncs.com/" #Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_ali_add() { fulldomain=$1 txtvalue=$2 _prepare_ali_credentials || return 1 _debug "First detect the root zone" if ! _get_root "$fulldomain"; then return 1 fi _debug "Add record" _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record" } dns_ali_rm() { fulldomain=$1 txtvalue=$2 Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then return 1 fi _clean } #################### Alibaba Cloud common functions below #################### _prepare_ali_credentials() { Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then Ali_Key="" Ali_Secret="" _err "You don't specify aliyun api key and secret yet." return 1 fi #save the api key and secret to the account conf file. _saveaccountconf_mutable Ali_Key "$Ali_Key" _saveaccountconf_mutable Ali_Secret "$Ali_Secret" } # act ign mtd _ali_rest() { act="$1" ign="$2" mtd="${3:-GET}" signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _url_encode upper-hex)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64) signature=$(printf "%s" "$signature" | _url_encode upper-hex) url="$endpoint?Signature=$signature" if [ "$mtd" = "GET" ]; then url="$url&$query" response="$(_get "$url")" else response="$(_post "$query" "$url" "" "$mtd" "application/x-www-form-urlencoded")" fi _ret="$?" _debug2 response "$response" if [ "$_ret" != "0" ]; then _err "Error <$act>" return 1 fi if [ -z "$ign" ]; then message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" if [ "$message" ]; then _err "$message" return 1 fi fi } _ali_nonce() { #_head_n 1 /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 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi _anx_rest GET "zone.json/${h}" 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 } acme.sh-3.1.0/dnsapi/dns_artfiles.sh000066400000000000000000000121001472032365200173160ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_artfiles_info='ArtFiles.de Site: ArtFiles.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_artfiles Options: AF_API_USERNAME API Username AF_API_PASSWORD API Password Issues: github.com/acmesh-official/acme.sh/issues/4718 Author: Martin Arndt ' ########## API configuration ################################################### AF_API_SUCCESS='status":"OK' AF_URL_DCP='https://dcp.c.artfiles.de/api/' AF_URL_DNS=${AF_URL_DCP}'dns/{*}_dns.html?domain=' AF_URL_DOMAINS=${AF_URL_DCP}'domain/get_domains.html' ########## Public functions #################################################### # Adds a new TXT record for given ACME challenge value & domain. # Usage: dns_artfiles_add _acme-challenge.www.example.com "ACME challenge value" dns_artfiles_add() { domain="$1" txtValue="$2" _info 'Using ArtFiles.de DNS addition API…' _debug 'Domain' "$domain" _debug 'txtValue' "$txtValue" _set_credentials _saveaccountconf_mutable 'AF_API_USERNAME' "$AF_API_USERNAME" _saveaccountconf_mutable 'AF_API_PASSWORD' "$AF_API_PASSWORD" _set_headers _get_zone "$domain" _dns 'GET' if ! _contains "$response" 'TXT'; then _err 'Retrieving TXT records failed.' return 1 fi _clean_records _dns 'SET' "$(printf -- '%s\n_acme-challenge "%s"' "$response" "$txtValue")" if ! _contains "$response" "$AF_API_SUCCESS"; then _err 'Adding ACME challenge value failed.' return 1 fi } # Removes the existing TXT record for given ACME challenge value & domain. # Usage: dns_artfiles_rm _acme-challenge.www.example.com "ACME challenge value" dns_artfiles_rm() { domain="$1" txtValue="$2" _info 'Using ArtFiles.de DNS removal API…' _debug 'Domain' "$domain" _debug 'txtValue' "$txtValue" _set_credentials _set_headers _get_zone "$domain" if ! _dns 'GET'; then return 1 fi if ! _contains "$response" "$txtValue"; then _err 'Retrieved TXT records are missing given ACME challenge value.' return 1 fi _clean_records response="$(printf -- '%s' "$response" | sed '/_acme-challenge "'"$txtValue"'"/d')" _dns 'SET' "$response" if ! _contains "$response" "$AF_API_SUCCESS"; then _err 'Removing ACME challenge value failed.' return 1 fi } ########## Private functions ################################################### # Cleans awful TXT records response of ArtFiles's API & pretty prints it. # Usage: _clean_records _clean_records() { _info 'Cleaning TXT records…' # Extract TXT part, strip trailing quote sign (ACME.sh API guidelines forbid # usage of SED's GNU extensions, hence couldn't omit it via regex), strip '\' # from '\"' & turn '\n' into real LF characters. # Yup, awful API to use - but that's all we got to get this working, so… ;) _debug2 'Raw ' "$response" response="$(printf -- '%s' "$response" | sed 's/^.*TXT":"\([^}]*\).*$/\1/;s/,".*$//;s/.$//;s/\\"/"/g;s/\\n/\n/g')" _debug2 'Clean' "$response" } # Executes an HTTP GET or POST request for getting or setting DNS records, # containing given payload upon POST. # Usage: _dns [GET | SET] [payload] _dns() { _info 'Executing HTTP request…' action="$1" payload="$(printf -- '%s' "$2" | _url_encode)" url="$(printf -- '%s%s' "$AF_URL_DNS" "$domain" | sed 's/{\*}/'"$(printf -- '%s' "$action" | _lower_case)"'/')" if [ "$action" = 'SET' ]; then _debug2 'Payload' "$payload" response="$(_post '' "$url&TXT=$payload" '' 'POST' 'application/x-www-form-urlencoded')" else response="$(_get "$url" '' 10)" fi if ! _contains "$response" "$AF_API_SUCCESS"; then _err "DNS API error: $response" return 1 fi _debug 'Response' "$response" return 0 } # Gets the root domain zone for given domain. # Usage: _get_zone _acme-challenge.www.example.com _get_zone() { fqdn="$1" domains="$(_get "$AF_URL_DOMAINS" '' 10)" _info 'Getting domain zone…' _debug2 'FQDN' "$fqdn" _debug2 'Domains' "$domains" while _contains "$fqdn" "."; do if _contains "$domains" "$fqdn"; then domain="$fqdn" _info "Found root domain zone: $domain" break else fqdn="${fqdn#*.}" _debug2 'FQDN' "$fqdn" fi done if [ "$domain" = "$fqdn" ]; then return 0 fi _err 'Couldn'\''t find root domain zone.' return 1 } # Sets the credentials for accessing ArtFiles's API # Usage: _set_credentials _set_credentials() { _info 'Setting credentials…' AF_API_USERNAME="${AF_API_USERNAME:-$(_readaccountconf_mutable AF_API_USERNAME)}" AF_API_PASSWORD="${AF_API_PASSWORD:-$(_readaccountconf_mutable AF_API_PASSWORD)}" if [ -z "$AF_API_USERNAME" ] || [ -z "$AF_API_PASSWORD" ]; then _err 'Missing ArtFiles.de username and/or password.' _err 'Please ensure both are set via export command & try again.' return 1 fi } # Adds the HTTP Authorization & Content-Type headers to a follow-up request. # Usage: _set_headers _set_headers() { _info 'Setting headers…' encoded="$(printf -- '%s:%s' "$AF_API_USERNAME" "$AF_API_PASSWORD" | _base64)" export _H1="Authorization: Basic $encoded" export _H2='Content-Type: application/json' } acme.sh-3.1.0/dnsapi/dns_arvan.sh000066400000000000000000000101641472032365200166240ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_arvan_info='ArvanCloud.ir Site: ArvanCloud.ir Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_arvan Options: Arvan_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2796 Author: Vahid Fardi ' ARVAN_API_URL="https://napi.arvancloud.ir/cdn/4.0/domains" ######## Public functions ##################### #Usage: dns_arvan_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_arvan_add() { fulldomain=$1 txtvalue=$2 _info "Using Arvan" Arvan_Token="${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}" if [ -z "$Arvan_Token" ]; then _err "You didn't specify \"Arvan_Token\" token yet." _err "You can get yours from here https://npanel.arvancloud.ir/profile/api-keys" return 1 fi #save the api token to the account conf file. _saveaccountconf_mutable Arvan_Token "$Arvan_Token" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if _arvan_rest POST "$_domain/dns-records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":{\"text\":\"$txtvalue\"},\"ttl\":120}"; then if _contains "$response" "$txtvalue"; then _info "response id is $response" _info "Added, OK" return 0 elif _contains "$response" "Record Data is duplicate"; then _info "Already exists, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 0 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_arvan_rm() { fulldomain=$1 txtvalue=$2 _info "Using Arvan" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" Arvan_Token="${Arvan_Token:-$(_readaccountconf_mutable Arvan_Token)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _arvan_rest GET "${_domain}/dns-records" if ! printf "%s" "$response" | grep \"current_page\":1 >/dev/null; then _err "Error on Arvan Api" _err "Please create a github issue with debbug log" return 1 fi _record_id=$(echo "$response" | _egrep_o ".\"id\":\"[^\"]*\",\"type\":\"txt\",\"name\":\"_acme-challenge\",\"value\":{\"text\":\"$txtvalue\"}" | cut -d : -f 2 | cut -d , -f 1 | tr -d \") if ! _arvan_rest "DELETE" "${_domain}/dns-records/${_record_id}"; then _err "Error on Arvan Api" return 1 fi _debug "$response" _contains "$response" 'dns record deleted' return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _arvan_rest GET "$h"; then return 1 fi if _contains "$response" "\"domain\":\"$h\""; then _domain_id=$(echo "$response" | cut -d : -f 3 | cut -d , -f 1 | tr -d \") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _arvan_rest() { mtd="$1" ep="$2" data="$3" token_trimmed=$(echo "$Arvan_Token" | tr -d '"') export _H1="Authorization: $token_trimmed" if [ "$mtd" = "DELETE" ]; then #DELETE Request shouldn't have Content-Type _debug data "$data" response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")" elif [ "$mtd" = "POST" ]; then export _H2="Content-Type: application/json" export _H3="Accept: application/json" _debug data "$data" response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")" else response="$(_get "$ARVAN_API_URL/$ep$data")" fi return 0 } acme.sh-3.1.0/dnsapi/dns_aurora.sh000066400000000000000000000115151472032365200170070ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_aurora_info='versio.nl AuroraDNS Domains: pcextreme.nl Site: versio.nl Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_aurora Options: AURORA_Key API Key AURORA_Secret API Secret Issues: github.com/acmesh-official/acme.sh/issues/3459 Author: Jasper Zonneveld ' AURORA_Api="https://api.auroradns.eu" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_aurora_add() { fulldomain=$1 txtvalue=$2 AURORA_Key="${AURORA_Key:-$(_readaccountconf_mutable AURORA_Key)}" AURORA_Secret="${AURORA_Secret:-$(_readaccountconf_mutable AURORA_Secret)}" if [ -z "$AURORA_Key" ] || [ -z "$AURORA_Secret" ]; then AURORA_Key="" AURORA_Secret="" _err "You didn't specify an Aurora api key and secret yet." _err "You can get yours from here https://cp.pcextreme.nl/auroradns/users." return 1 fi #save the api key and secret to the account conf file. _saveaccountconf_mutable AURORA_Key "$AURORA_Key" _saveaccountconf_mutable AURORA_Secret "$AURORA_Secret" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if _aurora_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":300}"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 elif _contains "$response" "RecordExistsError"; then _info "Already exists, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_aurora_rm() { fulldomain=$1 txtvalue=$2 AURORA_Key="${AURORA_Key:-$(_readaccountconf_mutable AURORA_Key)}" AURORA_Secret="${AURORA_Secret:-$(_readaccountconf_mutable AURORA_Secret)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting records" _aurora_rest GET "zones/${_domain_id}/records" if ! _contains "$response" "$txtvalue"; then _info "Don't need to remove." else records=$(echo "$response" | _normalizeJson | tr -d "[]" | sed "s/},{/}|{/g" | tr "|" "\n") if [ "$(echo "$records" | wc -l)" -le 2 ]; then _err "Can not parse records." return 1 fi record_id=$(echo "$records" | grep "\"type\": *\"TXT\"" | grep "\"name\": *\"$_sub_domain\"" | grep "\"content\": *\"$txtvalue\"" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _aurora_rest DELETE "zones/$_domain_id/records/$record_id"; then _err "Delete record error." return 1 fi fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _aurora_rest GET "zones/$h"; then return 1 fi if _contains "$response" "\"name\": \"$h\""; then _domain_id=$(echo "$response" | _normalizeJson | tr -d "{}" | tr "," "\n" | grep "\"id\": *\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _aurora_rest() { m=$1 ep="$2" data="$3" _debug "$ep" key_trimmed=$(echo "$AURORA_Key" | tr -d '"') secret_trimmed=$(echo "$AURORA_Secret" | tr -d '"') timestamp=$(date -u +"%Y%m%dT%H%M%SZ") signature=$(printf "%s/%s%s" "$m" "$ep" "$timestamp" | _hmac sha256 "$(printf "%s" "$secret_trimmed" | _hex_dump | tr -d " ")" | _base64) authorization=$(printf "AuroraDNSv1 %s" "$(printf "%s:%s" "$key_trimmed" "$signature" | _base64)") export _H1="Content-Type: application/json; charset=UTF-8" export _H2="X-AuroraDNS-Date: $timestamp" export _H3="Authorization: $authorization" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$AURORA_Api/$ep" "" "$m")" else response="$(_get "$AURORA_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_autodns.sh000066400000000000000000000134511472032365200171740ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_autodns_info='InternetX autoDNS InternetX autoDNS XML API Site: InternetX.com/autodns/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_autodns Options: AUTODNS_USER Username AUTODNS_PASSWORD Password AUTODNS_CONTEXT Context Author: ' AUTODNS_API="https://gateway.autodns.com" # Arguments: # txtdomain # txt dns_autodns_add() { fulldomain="$1" txtvalue="$2" AUTODNS_USER="${AUTODNS_USER:-$(_readaccountconf_mutable AUTODNS_USER)}" AUTODNS_PASSWORD="${AUTODNS_PASSWORD:-$(_readaccountconf_mutable AUTODNS_PASSWORD)}" AUTODNS_CONTEXT="${AUTODNS_CONTEXT:-$(_readaccountconf_mutable AUTODNS_CONTEXT)}" if [ -z "$AUTODNS_USER" ] || [ -z "$AUTODNS_CONTEXT" ] || [ -z "$AUTODNS_PASSWORD" ]; then _err "You don't specify autodns user, password and context." return 1 fi _saveaccountconf_mutable AUTODNS_USER "$AUTODNS_USER" _saveaccountconf_mutable AUTODNS_PASSWORD "$AUTODNS_PASSWORD" _saveaccountconf_mutable AUTODNS_CONTEXT "$AUTODNS_CONTEXT" _debug "First detect the root zone" if ! _get_autodns_zone "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _zone "$_zone" _debug _system_ns "$_system_ns" _info "Adding TXT record" autodns_response="$(_autodns_zone_update "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")" if [ "$?" -eq "0" ]; then _info "Added, OK" return 0 fi return 1 } # Arguments: # txtdomain # txt dns_autodns_rm() { fulldomain="$1" txtvalue="$2" AUTODNS_USER="${AUTODNS_USER:-$(_readaccountconf_mutable AUTODNS_USER)}" AUTODNS_PASSWORD="${AUTODNS_PASSWORD:-$(_readaccountconf_mutable AUTODNS_PASSWORD)}" AUTODNS_CONTEXT="${AUTODNS_CONTEXT:-$(_readaccountconf_mutable AUTODNS_CONTEXT)}" if [ -z "$AUTODNS_USER" ] || [ -z "$AUTODNS_CONTEXT" ] || [ -z "$AUTODNS_PASSWORD" ]; then _err "You don't specify autodns user, password and context." return 1 fi _debug "First detect the root zone" if ! _get_autodns_zone "$fulldomain"; then _err "zone not found" return 1 fi _debug _sub_domain "$_sub_domain" _debug _zone "$_zone" _debug _system_ns "$_system_ns" _info "Delete TXT record" autodns_response="$(_autodns_zone_cleanup "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")" if [ "$?" -eq "0" ]; then _info "Deleted, OK" return 0 fi return 1 } #################### Private functions below ################################## # Arguments: # fulldomain # Returns: # _sub_domain=_acme-challenge.www # _zone=domain.com # _system_ns _get_autodns_zone() { domain="$1" i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then # not valid return 1 fi autodns_response="$(_autodns_zone_inquire "$h")" if [ "$?" -ne "0" ]; then _err "invalid domain" return 1 fi if _contains "$autodns_response" "1" >/dev/null; then _zone="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" _system_ns="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _build_request_auth_xml() { printf " %s %s %s " "$AUTODNS_USER" "$AUTODNS_PASSWORD" "$AUTODNS_CONTEXT" } # Arguments: # zone _build_zone_inquire_xml() { printf " %s 0205 1 1 name eq %s " "$(_build_request_auth_xml)" "$1" } # Arguments: # zone # subdomain # txtvalue # system_ns _build_zone_update_xml() { printf " %s 0202001 %s 600 TXT %s %s %s " "$(_build_request_auth_xml)" "$2" "$3" "$1" "$4" } # Arguments: # zone _autodns_zone_inquire() { request_data="$(_build_zone_inquire_xml "$1")" autodns_response="$(_autodns_api_call "$request_data")" ret="$?" printf "%s" "$autodns_response" return "$ret" } # Arguments: # zone # subdomain # txtvalue # system_ns _autodns_zone_update() { request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")" autodns_response="$(_autodns_api_call "$request_data")" ret="$?" printf "%s" "$autodns_response" return "$ret" } # Arguments: # zone # subdomain # txtvalue # system_ns _autodns_zone_cleanup() { request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")" # replace 'rr_add>' with 'rr_rem>' in request_data request_data="$(printf -- "%s" "$request_data" | sed 's/rr_add>/rr_rem>/g')" autodns_response="$(_autodns_api_call "$request_data")" ret="$?" printf "%s" "$autodns_response" return "$ret" } # Arguments: # request_data _autodns_api_call() { request_data="$1" _debug request_data "$request_data" autodns_response="$(_post "$request_data" "$AUTODNS_API")" ret="$?" _debug autodns_response "$autodns_response" if [ "$ret" -ne "0" ]; then _err "error" return 1 fi if _contains "$autodns_response" "success" >/dev/null; then _info "success" printf "%s" "$autodns_response" return 0 fi return 1 } acme.sh-3.1.0/dnsapi/dns_aws.sh000077500000000000000000000304421472032365200163130ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_aws_info='Amazon AWS Route53 domain API Site: docs.aws.amazon.com/route53/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_aws Options: AWS_ACCESS_KEY_ID API Key ID AWS_SECRET_ACCESS_KEY API Secret ' # All `_sleep` commands are included to avoid Route53 throttling, see # https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests AWS_HOST="route53.amazonaws.com" AWS_URL="https://$AWS_HOST" AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API" ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_aws_add() { fulldomain=$1 txtvalue=$2 AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" AWS_DNS_SLOWRATE="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}" if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then _use_container_role || _use_instance_role fi if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then AWS_ACCESS_KEY_ID="" AWS_SECRET_ACCESS_KEY="" _err "You haven't specified the aws route53 api key id and and api key secret yet." _err "Please create your key and try again. see $(__green $AWS_WIKI)" return 1 fi #save for future use, unless using a role which will be fetched as needed if [ -z "$_using_role" ]; then _saveaccountconf_mutable AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" _saveaccountconf_mutable AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" _saveaccountconf_mutable AWS_DNS_SLOWRATE "$AWS_DNS_SLOWRATE" fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" _sleep 1 return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Getting existing records for $fulldomain" if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then _sleep 1 return 1 fi if _contains "$response" "$fulldomain."; then _resource_record="$(echo "$response" | sed 's//"/g' | tr '"' "\n" | grep "$fulldomain." | _egrep_o "" | sed "s///" | sed "s###")" _debug "_resource_record" "$_resource_record" else _debug "single new add" fi if [ "$_resource_record" ] && _contains "$response" "$txtvalue"; then _info "The TXT record already exists. Skipping." _sleep 1 return 0 fi _debug "Adding records" _aws_tmpl_xml="UPSERT$fulldomainTXT300$_resource_record\"$txtvalue\"" if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then _info "TXT record updated successfully." if [ -n "$AWS_DNS_SLOWRATE" ]; then _info "Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds" _sleep "$AWS_DNS_SLOWRATE" else _sleep 1 fi return 0 fi _sleep 1 return 1 } #fulldomain txtvalue dns_aws_rm() { fulldomain=$1 txtvalue=$2 AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-$(_readaccountconf_mutable AWS_ACCESS_KEY_ID)}" AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable AWS_SECRET_ACCESS_KEY)}" AWS_DNS_SLOWRATE="${AWS_DNS_SLOWRATE:-$(_readaccountconf_mutable AWS_DNS_SLOWRATE)}" if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then _use_container_role || _use_instance_role fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" _sleep 1 return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Getting existing records for $fulldomain" if ! aws_rest GET "2013-04-01$_domain_id/rrset" "name=$fulldomain&type=TXT"; then _sleep 1 return 1 fi if _contains "$response" "$fulldomain."; then _resource_record="$(echo "$response" | sed 's//"/g' | tr '"' "\n" | grep "$fulldomain." | _egrep_o "" | sed "s///" | sed "s###")" _debug "_resource_record" "$_resource_record" else _debug "no records exist, skip" _sleep 1 return 0 fi _aws_tmpl_xml="DELETE$_resource_record$fulldomain.TXT300" if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then _info "TXT record deleted successfully." if [ -n "$AWS_DNS_SLOWRATE" ]; then _info "Slow rate activated: sleeping for $AWS_DNS_SLOWRATE seconds" _sleep "$AWS_DNS_SLOWRATE" else _sleep 1 fi return 0 fi _sleep 1 return 1 } #################### Private functions below ################################## _get_root() { domain=$1 i=1 p=1 # iterate over names (a.b.c.d -> b.c.d -> c.d -> d) while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100 | sed 's/\./\\./g') _debug "Checking domain: $h" if [ -z "$h" ]; then _error "invalid domain" return 1 fi # iterate over paginated result for list_hosted_zones aws_rest GET "2013-04-01/hostedzone" while true; do if _contains "$response" "$h."; then hostedzone="$(echo "$response" | tr -d '\n' | sed 's//#&/g' | tr '#' '\n' | _egrep_o "[^<]*<.Id>$h.<.Name>.*false<.PrivateZone>.*<.HostedZone>")" _debug hostedzone "$hostedzone" if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o ".*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi _err "Can't find domain with id: $h" return 1 fi fi if _contains "$response" "true" && _contains "$response" ""; then _debug "IsTruncated" _nextMarker="$(echo "$response" | _egrep_o ".*" | cut -d '>' -f 2 | cut -d '<' -f 1)" _debug "NextMarker" "$_nextMarker" else break fi _debug "Checking domain: $h - Next Page " aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker" done p=$i i=$(_math "$i" + 1) done return 1 } _use_container_role() { # automatically set if running inside ECS if [ -z "$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" ]; then _debug "No ECS environment variable detected" return 1 fi _use_metadata "169.254.170.2$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" } _use_instance_role() { _instance_role_name_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/" if _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 401; then _debug "Using IMDSv2" _token_url="http://169.254.169.254/latest/api/token" export _H1="X-aws-ec2-metadata-token-ttl-seconds: 21600" _token="$(_post "" "$_token_url" "" "PUT")" _secure_debug3 "_token" "$_token" if [ -z "$_token" ]; then _debug "Unable to fetch IMDSv2 token from instance metadata" return 1 fi export _H1="X-aws-ec2-metadata-token: $_token" fi if ! _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 200; then _debug "Unable to fetch IAM role from instance metadata" return 1 fi _instance_role_name=$(_get "$_instance_role_name_url" "" 1) _debug "_instance_role_name" "$_instance_role_name" _use_metadata "$_instance_role_name_url$_instance_role_name" "$_token" } _use_metadata() { export _H1="X-aws-ec2-metadata-token: $2" _aws_creds="$( _get "$1" "" 1 | _normalizeJson | tr '{,}' '\n' | while read -r _line; do _key="$(echo "${_line%%:*}" | tr -d '\"')" _value="${_line#*:}" _debug3 "_key" "$_key" _secure_debug3 "_value" "$_value" case "$_key" in AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;; SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;; Token) echo "AWS_SESSION_TOKEN=$_value" ;; esac done | paste -sd' ' - )" _secure_debug "_aws_creds" "$_aws_creds" if [ -z "$_aws_creds" ]; then return 1 fi eval "$_aws_creds" _using_role=true } #method uri qstr data aws_rest() { mtd="$1" ep="$2" qsr="$3" data="$4" _debug mtd "$mtd" _debug ep "$ep" _debug qsr "$qsr" _debug data "$data" CanonicalURI="/$ep" _debug2 CanonicalURI "$CanonicalURI" CanonicalQueryString="$qsr" _debug2 CanonicalQueryString "$CanonicalQueryString" RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")" _debug2 RequestDate "$RequestDate" #RequestDate="20161120T141056Z" ############## export _H1="x-amz-date: $RequestDate" aws_host="$AWS_HOST" CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n" SignedHeaders="host;x-amz-date" if [ -n "$AWS_SESSION_TOKEN" ]; then export _H3="x-amz-security-token: $AWS_SESSION_TOKEN" CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n" SignedHeaders="${SignedHeaders};x-amz-security-token" fi _debug2 CanonicalHeaders "$CanonicalHeaders" _debug2 SignedHeaders "$SignedHeaders" RequestPayload="$data" _debug2 RequestPayload "$RequestPayload" Hash="sha256" CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)" _debug2 CanonicalRequest "$CanonicalRequest" HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)" _debug2 HashedCanonicalRequest "$HashedCanonicalRequest" Algorithm="AWS4-HMAC-SHA256" _debug2 Algorithm "$Algorithm" RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)" _debug2 RequestDateOnly "$RequestDateOnly" Region="us-east-1" Service="route53" CredentialScope="$RequestDateOnly/$Region/$Service/aws4_request" _debug2 CredentialScope "$CredentialScope" StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest" _debug2 StringToSign "$StringToSign" kSecret="AWS4$AWS_SECRET_ACCESS_KEY" #kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################ _secure_debug2 kSecret "$kSecret" kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")" _secure_debug2 kSecretH "$kSecretH" kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)" _debug2 kDateH "$kDateH" kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)" _debug2 kRegionH "$kRegionH" kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)" _debug2 kServiceH "$kServiceH" kSigningH="$(printf "%s" "aws4_request" | _hmac "$Hash" "$kServiceH" hex)" _debug2 kSigningH "$kSigningH" signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)" _debug2 signature "$signature" Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature" _debug2 Authorization "$Authorization" _H2="Authorization: $Authorization" _debug _H2 "$_H2" url="$AWS_URL/$ep" if [ "$qsr" ]; then url="$AWS_URL/$ep?$qsr" fi if [ "$mtd" = "GET" ]; then response="$(_get "$url")" else response="$(_post "$data" "$url")" fi _ret="$?" _debug2 response "$response" if [ "$_ret" = "0" ]; then if _contains "$response" "/dev/null; then _azion_token=$(echo "$response" | _egrep_o "\"token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") export AZION_Token="$_azion_token" else _err "Failed to generate Azion token" return 1 fi } _azion_rest() { _method=$1 _uri="$2" _data="$3" if [ -z "$AZION_Token" ]; then _get_token fi _debug2 token "$AZION_Token" export _H1="Accept: application/json; version=3" export _H2="Content-Type: application/json" export _H3="Authorization: token $AZION_Token" if [ "$_method" != "GET" ]; then _debug _data "$_data" response="$(_post "$_data" "$AZION_Api/$_uri" "" "$_method")" else response="$(_get "$AZION_Api/$_uri")" fi _debug2 response "$response" if [ "$?" != "0" ]; then _err "error $_method $_uri $_data" return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_azure.sh000066400000000000000000000377621472032365200166600ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_azure_info='Azure Site: Azure.microsoft.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_azure Options: AZUREDNS_SUBSCRIPTIONID Subscription ID AZUREDNS_TENANTID Tenant ID AZUREDNS_APPID App ID. App ID of the service principal AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. "true"/"false" AZUREDNS_BEARERTOKEN Optional Bearer Token. Used instead of service principal credentials or managed identity ' wiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record # # Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/create-or-update?view=rest-dns-2018-05-01&tabs=HTTP # dns_azure_add() { fulldomain=$1 txtvalue=$2 AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}" if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Subscription ID" return 1 fi #save subscription id to account conf file. _saveaccountconf_mutable AZUREDNS_SUBSCRIPTIONID "$AZUREDNS_SUBSCRIPTIONID" AZUREDNS_MANAGEDIDENTITY="${AZUREDNS_MANAGEDIDENTITY:-$(_readaccountconf_mutable AZUREDNS_MANAGEDIDENTITY)}" if [ "$AZUREDNS_MANAGEDIDENTITY" = true ]; then _info "Using Azure managed identity" #save managed identity as preferred authentication method, clear service principal credentials from conf file. _saveaccountconf_mutable AZUREDNS_MANAGEDIDENTITY "$AZUREDNS_MANAGEDIDENTITY" _saveaccountconf_mutable AZUREDNS_TENANTID "" _saveaccountconf_mutable AZUREDNS_APPID "" _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "" _saveaccountconf_mutable AZUREDNS_BEARERTOKEN "" else _info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token" AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}" AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}" AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}" AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}" if [ -z "$AZUREDNS_BEARERTOKEN" ]; then if [ -z "$AZUREDNS_TENANTID" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Tenant ID " return 1 fi if [ -z "$AZUREDNS_APPID" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure App ID" return 1 fi if [ -z "$AZUREDNS_CLIENTSECRET" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Client Secret" return 1 fi else _info "Using provided bearer token" fi #save account details to account conf file, don't opt in for azure manages identity check. _saveaccountconf_mutable AZUREDNS_MANAGEDIDENTITY "false" _saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID" _saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID" _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET" _saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$AZUREDNS_BEARERTOKEN" fi if [ -z "$AZUREDNS_BEARERTOKEN" ]; then accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") else accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g") fi if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" _debug "$acmeRecordURI" # Get existing TXT record _azure_rest GET "$acmeRecordURI" "" "$accesstoken" 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 "[]\"")" _debug "existing TXT found" _debug "$vlist" 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 fi _diff="$(_math "$timestamp - $existingts")" _debug "existing txt age: $_diff" # only use recently added records and discard if older than 2 hours because they are probably orphaned if [ "$_diff" -lt 7200 ]; then _debug "existing txt value: $vlist" for v in $vlist; do values="$values ,{\"value\":[\"$v\"]}" done fi fi # Add the txtvalue TXT Record body="{\"properties\":{\"metadata\":{\"acmetscheck\":\"$timestamp\"},\"TTL\":10, \"TXTRecords\":[$values]}}" _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 fi } # Usage: fulldomain txtvalue # Used to remove the txt record after validation # # Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/delete?view=rest-dns-2018-05-01&tabs=HTTP # dns_azure_rm() { fulldomain=$1 txtvalue=$2 AZUREDNS_SUBSCRIPTIONID="${AZUREDNS_SUBSCRIPTIONID:-$(_readaccountconf_mutable AZUREDNS_SUBSCRIPTIONID)}" if [ -z "$AZUREDNS_SUBSCRIPTIONID" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Subscription ID " return 1 fi AZUREDNS_MANAGEDIDENTITY="${AZUREDNS_MANAGEDIDENTITY:-$(_readaccountconf_mutable AZUREDNS_MANAGEDIDENTITY)}" if [ "$AZUREDNS_MANAGEDIDENTITY" = true ]; then _info "Using Azure managed identity" else _info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token" AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}" AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}" AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}" AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}" if [ -z "$AZUREDNS_BEARERTOKEN" ]; then if [ -z "$AZUREDNS_TENANTID" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Tenant ID " return 1 fi if [ -z "$AZUREDNS_APPID" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure App ID" return 1 fi if [ -z "$AZUREDNS_CLIENTSECRET" ]; then AZUREDNS_SUBSCRIPTIONID="" AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Client Secret" return 1 fi else _info "Using provided bearer token" fi fi if [ -z "$AZUREDNS_BEARERTOKEN" ]; then accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") else accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g") fi if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" acmeRecordURI="https://management.azure.com$(printf '%s' "$_domain_id" | sed 's/\\//g')/TXT/$_sub_domain?api-version=2017-09-01" _debug "$acmeRecordURI" # Get existing TXT record _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")" values="" comma="" for v in $vlist; do values="$values$comma{\"value\":[\"$v\"]}" comma="," done if [ -z "$values" ]; then # No values left remove record _debug "removing validation record completely $acmeRecordURI" _azure_rest DELETE "$acmeRecordURI" "" "$accesstoken" if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then _info "validation record removed" else _err "error removing validation record ($_code)" return 1 fi else # Remove only txtvalue from the TXT Record body="{\"properties\":{\"metadata\":{\"acmetscheck\":\"$timestamp\"},\"TTL\":10, \"TXTRecords\":[$values]}}" _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 fi fi fi } ################### Private functions below ################################## _azure_rest() { m=$1 ep="$2" data="$3" accesstoken="$4" MAX_REQUEST_RETRY_TIMES=5 _request_retry_times=0 while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do _debug3 _request_retry_times "$_request_retry_times" export _H1="authorization: Bearer $accesstoken" export _H2="accept: application/json" export _H3="Content-Type: application/json" # clear headers from previous request to avoid getting wrong http code on timeouts : >"$HTTP_HEADER" _debug "$ep" if [ "$m" != "GET" ]; then _secure_debug2 "data $data" response="$(_post "$data" "$ep" "" "$m")" else response="$(_get "$ep")" fi _ret="$?" _secure_debug2 "response $response" _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 _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "0" _err "Access denied. Invalid access token. Make sure your Azure settings are correct. See: $wiki" return 1 fi # See https://learn.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes 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" continue fi break done if [ "$_request_retry_times" = "$MAX_REQUEST_RETRY_TIMES" ]; then _err "Error Azure REST called was retried $MAX_REQUEST_RETRY_TIMES times." _err "Calling $ep failed." return 1 fi response="$(echo "$response" | _normalizeJson)" return 0 } ## Ref: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#request-an-access-token _azure_getaccess_token() { managedIdentity=$1 tenantID=$2 clientID=$3 clientSecret=$4 accesstoken="${AZUREDNS_ACCESSTOKEN:-$(_readaccountconf_mutable AZUREDNS_ACCESSTOKEN)}" expires_on="${AZUREDNS_TOKENVALIDTO:-$(_readaccountconf_mutable AZUREDNS_TOKENVALIDTO)}" # can we reuse the bearer token? if [ -n "$accesstoken" ] && [ -n "$expires_on" ]; then if [ "$(_time)" -lt "$expires_on" ]; then # brearer token is still valid - reuse it _debug "reusing bearer token" printf "%s" "$accesstoken" return 0 else _debug "bearer token expired" fi fi _debug "getting new bearer token" if [ "$managedIdentity" = true ]; then # https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http export _H1="Metadata: true" response="$(_get http://169.254.169.254/metadata/identity/oauth2/token\?api-version=2018-02-01\&resource=https://management.azure.com/)" response="$(echo "$response" | _normalizeJson)" accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") else export _H1="accept: application/json" export _H2="Content-Type: application/x-www-form-urlencoded" 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 \") expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") fi if [ -z "$accesstoken" ]; then _err "No acccess token received. Check your Azure settings. See: $wiki" return 1 fi if [ "$_ret" != "0" ]; then _err "error $response" return 1 fi _saveaccountconf_mutable AZUREDNS_ACCESSTOKEN "$accesstoken" _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "$expires_on" printf "%s" "$accesstoken" return 0 } _get_root() { domain=$1 subscriptionId=$2 accesstoken=$3 i=1 p=1 ## Ref: https://learn.microsoft.com/en-us/rest/api/dns/zones/list?view=rest-dns-2018-05-01&tabs=HTTP ## returns up to 100 zones in one response. Handling more results is not implemented ## (ZoneListResult with continuation token for the next page of results) ## ## TODO: handle more than 100 results, as per: ## https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-dns-limits ## The new limit is 250 Public DNS zones per subscription, while the old limit was only 100 ## _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?\$top=500&api-version=2017-09-01" "" "$accesstoken" # Find matching domain name in Json response while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug2 "Checking domain: $h" if [ -z "$h" ]; then #not valid _err "Invalid domain" return 1 fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(echo "$response" | _egrep_o "\\{\"id\":\"[^\"]*\\/$h\"" | head -n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ]; then if [ "$i" = 1 ]; then #create the record at the domain apex (@) if only the domain name was provided as --domain-alias _sub_domain="@" else _sub_domain=$(echo "$domain" | cut -d . -f 1-"$p") fi _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } acme.sh-3.1.0/dnsapi/dns_bookmyname.sh000066400000000000000000000062141472032365200176570ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_bookmyname_info='BookMyName.com Site: BookMyName.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bookmyname Options: BOOKMYNAME_USERNAME Username BOOKMYNAME_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/3209 Author: Neilpang ' ######## Public functions ##################### # BookMyName urls: # https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=add&value="XXXXXXXX"' # https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=remove&value="XXXXXXXX"' # Output: #good: update done, cid 123456, domain id 456789, type txt, ip XXXXXXXX #good: remove done 1, cid 123456, domain id 456789, ttl 300, type txt, ip XXXXXXXX # Be careful, BMN DNS servers can be slow to pick up changes; using dnssleep is thus advised. # Usage: # export BOOKMYNAME_USERNAME="ABCDE-FREE" # export BOOKMYNAME_PASSWORD="MyPassword" # /usr/local/ssl/acme.sh/acme.sh --dns dns_bookmyname --dnssleep 600 --issue -d domain.tld #Usage: dns_bookmyname_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_bookmyname_add() { fulldomain=$1 txtvalue=$2 _info "Using bookmyname" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}" BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}" if [ -z "$BOOKMYNAME_USERNAME" ] || [ -z "$BOOKMYNAME_PASSWORD" ]; then BOOKMYNAME_USERNAME="" BOOKMYNAME_PASSWORD="" _err "You didn't specify BookMyName username and password yet." _err "Please specify them and try again." return 1 fi #save the credentials to the account conf file. _saveaccountconf_mutable BOOKMYNAME_USERNAME "$BOOKMYNAME_USERNAME" _saveaccountconf_mutable BOOKMYNAME_PASSWORD "$BOOKMYNAME_PASSWORD" uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/" data="?hostname=${fulldomain}&type=TXT&ttl=300&do=add&value=${txtvalue}" result="$(_get "${uri}${data}")" _debug "Result: $result" if ! _startswith "$result" 'good: update done, cid '; then _err "Can't add $fulldomain" return 1 fi } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_bookmyname_rm() { fulldomain=$1 txtvalue=$2 _info "Using bookmyname" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}" BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}" uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/" data="?hostname=${fulldomain}&type=TXT&ttl=300&do=remove&value=${txtvalue}" result="$(_get "${uri}${data}")" _debug "Result: $result" if ! _startswith "$result" 'good: remove done 1, cid '; then _info "Can't remove $fulldomain" fi } #################### Private functions below ################################## acme.sh-3.1.0/dnsapi/dns_bunny.sh000066400000000000000000000172231472032365200166530ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_bunny_info='Bunny.net Site: Bunny.net/dns/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bunny Options: BUNNY_API_KEY API Key Issues: github.com/acmesh-official/acme.sh/issues/4296 Author: ' ##################### Public functions ##################### ## Create the text record for validation. ## Usage: fulldomain txtvalue ## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" dns_bunny_add() { fulldomain="$(echo "$1" | _lower_case)" txtvalue=$2 BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}" # Check if API Key is set if [ -z "$BUNNY_API_KEY" ]; then BUNNY_API_KEY="" _err "You did not specify Bunny.net API key." _err "Please export BUNNY_API_KEY and try again." return 1 fi _info "Using Bunny.net dns validation - add record" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" ## save the env vars (key and domain split location) for later automated use _saveaccountconf_mutable BUNNY_API_KEY "$BUNNY_API_KEY" ## split the domain for Bunny API if ! _get_base_domain "$fulldomain"; then _err "domain not found in your account for addition" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug _domain_id "$_domain_id" ## Set the header with our post type and auth key export _H1="Accept: application/json" export _H2="AccessKey: $BUNNY_API_KEY" export _H3="Content-Type: application/json" PURL="https://api.bunny.net/dnszone/$_domain_id/records" PBODY='{"Id":'$_domain_id',"Type":3,"Name":"'$_sub_domain'","Value":"'$txtvalue'","ttl":120}' _debug PURL "$PURL" _debug PBODY "$PBODY" ## the create request - POST ## args: BODY, URL, [need64, httpmethod] response="$(_post "$PBODY" "$PURL" "" "PUT")" ## check response if [ "$?" != "0" ]; then _err "error in response: $response" return 1 fi _debug2 response "$response" ## finished correctly return 0 } ## Remove the txt record after validation. ## Usage: fulldomain txtvalue ## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" dns_bunny_rm() { fulldomain="$(echo "$1" | _lower_case)" txtvalue=$2 BUNNY_API_KEY="${BUNNY_API_KEY:-$(_readaccountconf_mutable BUNNY_API_KEY)}" # Check if API Key Exists if [ -z "$BUNNY_API_KEY" ]; then BUNNY_API_KEY="" _err "You did not specify Bunny.net API key." _err "Please export BUNNY_API_KEY and try again." return 1 fi _info "Using Bunny.net dns validation - remove record" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" ## split the domain for Bunny API if ! _get_base_domain "$fulldomain"; then _err "Domain not found in your account for TXT record removal" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug _domain_id "$_domain_id" ## Set the header with our post type and key auth key export _H1="Accept: application/json" export _H2="AccessKey: $BUNNY_API_KEY" ## get URL for the list of DNS records GURL="https://api.bunny.net/dnszone/$_domain_id" ## 1) Get the domain/zone records ## the fetch request - GET ## args: URL, [onlyheader, timeout] domain_list="$(_get "$GURL")" ## check response if [ "$?" != "0" ]; then _err "error in domain_list response: $domain_list" return 1 fi _debug2 domain_list "$domain_list" ## 2) search through records ## check for what we are looking for: "Type":3,"Value":"$txtvalue","Name":"$_sub_domain" record="$(echo "$domain_list" | _egrep_o "\"Id\"\s*\:\s*\"*[0-9]+\"*,\s*\"Type\"[^}]*\"Value\"\s*\:\s*\"$txtvalue\"[^}]*\"Name\"\s*\:\s*\"$_sub_domain\"")" if [ -n "$record" ]; then ## We found records rec_ids="$(echo "$record" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" _debug rec_ids "$rec_ids" if [ -n "$rec_ids" ]; then echo "$rec_ids" | while IFS= read -r rec_id; do ## delete the record ## delete URL for removing the one we dont want DURL="https://api.bunny.net/dnszone/$_domain_id/records/$rec_id" ## the removal request - DELETE ## args: BODY, URL, [need64, httpmethod] response="$(_post "" "$DURL" "" "DELETE")" ## check response (sort of) if [ "$?" != "0" ]; then _err "error in remove response: $response" return 1 fi _debug2 response "$response" done fi fi ## finished correctly return 0 } ##################### Private functions below ##################### ## Split the domain provided into the "base domain" and the "start prefix". ## This function searches for the longest subdomain in your account ## for the full domain given and splits it into the base domain (zone) ## and the prefix/record to be added/removed ## USAGE: fulldomain ## EG: "_acme-challenge.two.three.four.domain.com" ## returns ## _sub_domain="_acme-challenge.two" ## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists ## _domain_id=234 ## if only "domain.com" exists it will return ## _sub_domain="_acme-challenge.two.three.four" ## _domain="domain.com" ## _domain_id=234 _get_base_domain() { # args fulldomain="$(echo "$1" | _lower_case)" _debug fulldomain "$fulldomain" # domain max legal length = 253 MAX_DOM=255 page=1 ## get a list of domains for the account to check thru ## Set the headers export _H1="Accept: application/json" export _H2="AccessKey: $BUNNY_API_KEY" _debug BUNNY_API_KEY "$BUNNY_API_KEY" ## get URL for the list of domains ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} DOMURL="https://api.bunny.net/dnszone" ## while we dont have a matching domain we keep going while [ -z "$found" ]; do ## get the domain list (current page) domain_list="$(_get "$DOMURL")" ## check response if [ "$?" != "0" ]; then _err "error in domain_list response: $domain_list" return 1 fi _debug2 domain_list "$domain_list" i=1 while [ "$i" -gt 0 ]; do ## get next longest domain _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") ## check we got something back from our cut (or are we at the end) if [ -z "$_domain" ]; then break fi ## we got part of a domain back - grep it out found="$(echo "$domain_list" | _egrep_o "\"Id\"\s*:\s*\"*[0-9]+\"*,\s*\"Domain\"\s*\:\s*\"$_domain\"")" ## check if it exists if [ -n "$found" ]; then ## exists - exit loop returning the parts sub_point=$(_math "$i" - 1) _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") _domain_id="$(echo "$found" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" _debug _domain_id "$_domain_id" _debug _domain "$_domain" _debug _sub_domain "$_sub_domain" found="" return 0 fi ## increment cut point $i i=$(_math "$i" + 1) done if [ -z "$found" ]; then page=$(_math "$page" + 1) nextpage="https://api.bunny.net/dnszone?page=$page" ## Find the next page if we don't have a match. hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")" if [ -z "$hasnextpage" ]; then _err "No record and no nextpage in Bunny.net domain search." found="" return 1 fi _debug2 nextpage "$nextpage" DOMURL="$nextpage" fi done ## We went through the entire domain zone list and didn't find one that matched. ## If we ever get here, something is broken in the code... _err "Domain not found in Bunny.net account, but we should never get here!" found="" return 1 } acme.sh-3.1.0/dnsapi/dns_cf.sh000077500000000000000000000160601472032365200161110ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_cf_info='CloudFlare Site: CloudFlare.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cf Options: CF_Key API Key CF_Email Your account email OptionsAlt: CF_Token API Token CF_Account_ID Account ID CF_Zone_ID Zone ID. Optional. ' CF_Api="https://api.cloudflare.com/client/v4" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_cf_add() { fulldomain=$1 txtvalue=$2 CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}" CF_Account_ID="${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}" CF_Zone_ID="${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}" CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}" CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}" if [ "$CF_Token" ]; then if [ "$CF_Zone_ID" ]; then _savedomainconf CF_Token "$CF_Token" _savedomainconf CF_Account_ID "$CF_Account_ID" _savedomainconf CF_Zone_ID "$CF_Zone_ID" else _saveaccountconf_mutable CF_Token "$CF_Token" _saveaccountconf_mutable CF_Account_ID "$CF_Account_ID" _clearaccountconf_mutable CF_Zone_ID _clearaccountconf CF_Zone_ID fi else if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then CF_Key="" CF_Email="" _err "You didn't specify a Cloudflare api key and email yet." _err "You can get yours from here https://dash.cloudflare.com/profile." return 1 fi if ! _contains "$CF_Email" "@"; then _err "It seems that the CF_Email=$CF_Email is not a valid email address." _err "Please check and retry." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable CF_Key "$CF_Key" _saveaccountconf_mutable CF_Email "$CF_Email" _clearaccountconf_mutable CF_Token _clearaccountconf_mutable CF_Account_ID _clearaccountconf_mutable CF_Zone_ID _clearaccountconf CF_Token _clearaccountconf CF_Account_ID _clearaccountconf CF_Zone_ID fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain" if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then _err "Error" return 1 fi # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so # we can not use updating anymore. # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) # _debug count "$count" # if [ "$count" = "0" ]; then _info "Adding record" if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 elif _contains "$response" "The record already exists"; then _info "Already exists, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_cf_rm() { fulldomain=$1 txtvalue=$2 CF_Token="${CF_Token:-$(_readaccountconf_mutable CF_Token)}" CF_Account_ID="${CF_Account_ID:-$(_readaccountconf_mutable CF_Account_ID)}" CF_Zone_ID="${CF_Zone_ID:-$(_readaccountconf_mutable CF_Zone_ID)}" CF_Key="${CF_Key:-$(_readaccountconf_mutable CF_Key)}" CF_Email="${CF_Email:-$(_readaccountconf_mutable CF_Email)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain&content=$txtvalue" if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then _err "Error: $response" return 1 fi count=$(echo "$response" | _egrep_o "\"count\": *[^,]*" | cut -d : -f 2 | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else record_id=$(echo "$response" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _cf_rest DELETE "zones/$_domain_id/dns_records/$record_id"; then _err "Delete record error." return 1 fi echo "$response" | tr -d " " | grep \"success\":true >/dev/null fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=1 p=1 # Use Zone ID directly if provided if [ "$CF_Zone_ID" ]; then if ! _cf_rest GET "zones/$CF_Zone_ID"; then return 1 else if echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then _domain=$(echo "$response" | _egrep_o "\"name\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") if [ "$_domain" ]; then _cutlength=$((${#domain} - ${#_domain} - 1)) _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength") _domain_id=$CF_Zone_ID return 0 else return 1 fi else return 1 fi fi fi 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 [ "$CF_Account_ID" ]; then if ! _cf_rest GET "zones?name=$h&account.id=$CF_Account_ID"; then return 1 fi else if ! _cf_rest GET "zones?name=$h"; then return 1 fi fi if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_count":1'; then _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _cf_rest() { m=$1 ep="$2" data="$3" _debug "$ep" email_trimmed=$(echo "$CF_Email" | tr -d '"') key_trimmed=$(echo "$CF_Key" | tr -d '"') token_trimmed=$(echo "$CF_Token" | tr -d '"') export _H1="Content-Type: application/json" if [ "$token_trimmed" ]; then export _H2="Authorization: Bearer $token_trimmed" else export _H2="X-Auth-Email: $email_trimmed" export _H3="X-Auth-Key: $key_trimmed" fi if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$CF_Api/$ep" "" "$m")" else response="$(_get "$CF_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_clouddns.sh000077500000000000000000000147401472032365200173370ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_clouddns_info='vshosting.cz CloudDNS Site: github.com/vshosting/clouddns Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_clouddns Options: CLOUDDNS_EMAIL Email CLOUDDNS_PASSWORD Password CLOUDDNS_CLIENT_ID Client ID Issues: github.com/acmesh-official/acme.sh/issues/2699 Author: Radek Sprta ' CLOUDDNS_API='https://admin.vshosting.cloud/clouddns' CLOUDDNS_LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login' ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_clouddns_add() { fulldomain=$1 txtvalue=$2 _debug "fulldomain" "$fulldomain" CLOUDDNS_CLIENT_ID="${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}" CLOUDDNS_EMAIL="${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}" CLOUDDNS_PASSWORD="${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}" if [ -z "$CLOUDDNS_PASSWORD" ] || [ -z "$CLOUDDNS_EMAIL" ] || [ -z "$CLOUDDNS_CLIENT_ID" ]; then CLOUDDNS_CLIENT_ID="" CLOUDDNS_EMAIL="" CLOUDDNS_PASSWORD="" _err "You didn't specify a CloudDNS password, email and client ID yet." return 1 fi if ! _contains "$CLOUDDNS_EMAIL" "@"; then _err "It seems that the CLOUDDNS_EMAIL=$CLOUDDNS_EMAIL is not a valid email address." _err "Please check and retry." return 1 fi # Save CloudDNS client id, email and password to config file _saveaccountconf_mutable CLOUDDNS_CLIENT_ID "$CLOUDDNS_CLIENT_ID" _saveaccountconf_mutable CLOUDDNS_EMAIL "$CLOUDDNS_EMAIL" _saveaccountconf_mutable CLOUDDNS_PASSWORD "$CLOUDDNS_PASSWORD" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # Add TXT record data="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"value\":\"$txtvalue\",\"domainId\":\"$_domain_id\"}" if _clouddns_api POST "record-txt" "$data"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" elif _contains "$response" '"code":4136'; then _info "Already exists, OK" else _err "Add TXT record error." return 1 fi fi _debug "Publishing record changes" _clouddns_api PUT "domain/$_domain_id/publish" "{\"soaTtl\":300}" } # Usage: rm _acme-challenge.www.domain.com dns_clouddns_rm() { fulldomain=$1 _debug "fulldomain" "$fulldomain" CLOUDDNS_CLIENT_ID="${CLOUDDNS_CLIENT_ID:-$(_readaccountconf_mutable CLOUDDNS_CLIENT_ID)}" CLOUDDNS_EMAIL="${CLOUDDNS_EMAIL:-$(_readaccountconf_mutable CLOUDDNS_EMAIL)}" CLOUDDNS_PASSWORD="${CLOUDDNS_PASSWORD:-$(_readaccountconf_mutable CLOUDDNS_PASSWORD)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # Get record ID _clouddns_api GET "domain/$_domain_id" if _contains "$response" "lastDomainRecordList"; then re="\"lastDomainRecordList\".*\"id\":\"([^\"}]*)\"[^}]*\"name\":\"$fulldomain.\"," _last_domains=$(echo "$response" | _egrep_o "$re") re2="\"id\":\"([^\"}]*)\"[^}]*\"name\":\"$fulldomain.\"," _record_id=$(echo "$_last_domains" | _egrep_o "$re2" | _head_n 1 | cut -d : -f 2 | cut -d , -f 1 | tr -d "\"") _debug _record_id "$_record_id" else _err "Could not retrieve record ID" return 1 fi _info "Removing record" if _clouddns_api DELETE "record/$_record_id"; then if _contains "$response" "\"error\":"; then _err "Could not remove record" return 1 fi fi _debug "Publishing record changes" _clouddns_api PUT "domain/$_domain_id/publish" "{\"soaTtl\":300}" } #################### Private functions below ################################## # Usage: _get_root _acme-challenge.www.domain.com # Returns: # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 # Get domain root data="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$CLOUDDNS_CLIENT_ID\"}]}" _clouddns_api "POST" "domain/search" "$data" domain_slice="$domain" while [ -z "$domain_root" ]; do if _contains "$response" "\"domainName\":\"$domain_slice\.\""; then domain_root="$domain_slice" _debug domain_root "$domain_root" fi domain_slice="$(echo "$domain_slice" | cut -d . -f 2-)" done # Get domain id data="{\"search\": [{\"name\": \"clientId\", \"operator\": \"eq\", \"value\": \"$CLOUDDNS_CLIENT_ID\"}, \ {\"name\": \"domainName\", \"operator\": \"eq\", \"value\": \"$domain_root.\"}]}" _clouddns_api "POST" "domain/search" "$data" if _contains "$response" "\"id\":\""; then re='domainType\":\"[^\"]*\",\"id\":\"([^\"]*)\",' # Match domain id _domain_id=$(echo "$response" | _egrep_o "$re" | _head_n 1 | cut -d : -f 3 | tr -d "\",") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | sed "s/.$domain_root//") _domain="$domain_root" return 0 fi _err 'Domain name not found on your CloudDNS account' return 1 fi return 1 } # Usage: _clouddns_api GET domain/search '{"data": "value"}' # Returns: # response='{"message": "api response"}' _clouddns_api() { method=$1 endpoint="$2" data="$3" _debug endpoint "$endpoint" if [ -z "$CLOUDDNS_TOKEN" ]; then _clouddns_login fi _debug CLOUDDNS_TOKEN "$CLOUDDNS_TOKEN" export _H1="Content-Type: application/json" export _H2="Authorization: Bearer $CLOUDDNS_TOKEN" if [ "$method" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$CLOUDDNS_API/$endpoint" "" "$method" | tr -d '\t\r\n ')" else response="$(_get "$CLOUDDNS_API/$endpoint" | tr -d '\t\r\n ')" fi # shellcheck disable=SC2181 if [ "$?" != "0" ]; then _err "Error $endpoint" return 1 fi _debug2 response "$response" return 0 } # Returns: # CLOUDDNS_TOKEN=dslfje2rj23l _clouddns_login() { login_data="{\"email\": \"$CLOUDDNS_EMAIL\", \"password\": \"$CLOUDDNS_PASSWORD\"}" response="$(_post "$login_data" "$CLOUDDNS_LOGIN_API" "" "POST" "Content-Type: application/json")" if _contains "$response" "\"accessToken\":\""; then CLOUDDNS_TOKEN=$(echo "$response" | _egrep_o "\"accessToken\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") export CLOUDDNS_TOKEN else echo 'Could not get CloudDNS access token; check your credentials' return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_cloudns.sh000077500000000000000000000134251472032365200171720ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_cloudns_info='ClouDNS.net Site: ClouDNS.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cloudns Options: CLOUDNS_AUTH_ID Regular auth ID CLOUDNS_SUB_AUTH_ID Sub auth ID CLOUDNS_AUTH_PASSWORD Auth Password Author: Boyan Peychev ' CLOUDNS_API="https://api.cloudns.net" DOMAIN_TYPE= DOMAIN_MASTER= ######## Public functions ##################### #Usage: dns_cloudns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_cloudns_add() { _info "Using cloudns" if ! _dns_cloudns_init_check; then return 1 fi zone="$(_dns_cloudns_get_zone_name "$1")" if [ -z "$zone" ]; then _err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup." return 1 fi host="$(echo "$1" | sed "s/\.$zone\$//")" record=$2 _debug zone "$zone" _debug host "$host" _debug record "$record" _info "Adding the TXT record for $1" _dns_cloudns_http_api_call "dns/add-record.json" "domain-name=$zone&record-type=TXT&host=$host&record=$record&ttl=60" if ! _contains "$response" "\"status\":\"Success\""; then _err "Record cannot be added." return 1 fi _info "Added." return 0 } #Usage: dns_cloudns_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_cloudns_rm() { _info "Using cloudns" if ! _dns_cloudns_init_check; then return 1 fi if [ -z "$zone" ]; then zone="$(_dns_cloudns_get_zone_name "$1")" if [ -z "$zone" ]; then _err "Missing DNS zone at ClouDNS. Please log into your control panel and create the required DNS zone for the initial setup." return 1 fi fi host="$(echo "$1" | sed "s/\.$zone\$//")" record=$2 _dns_cloudns_get_zone_info "$zone" _debug "Type" "$DOMAIN_TYPE" _debug "Cloud Master" "$DOMAIN_MASTER" if _contains "$DOMAIN_TYPE" "cloud"; then zone=$DOMAIN_MASTER fi _debug "ZONE" "$zone" _dns_cloudns_http_api_call "dns/records.json" "domain-name=$zone&host=$host&type=TXT" if ! _contains "$response" "\"id\":"; then return 1 fi for i in $(echo "$response" | tr '{' "\n" | grep -- "$record"); do record_id=$(echo "$i" | tr ',' "\n" | grep -E '^"id"' | sed -re 's/^\"id\"\:\"([0-9]+)\"$/\1/g') if [ -n "$record_id" ]; then _debug zone "$zone" _debug host "$host" _debug record "$record" _debug record_id "$record_id" _info "Deleting the TXT record for $1" _dns_cloudns_http_api_call "dns/delete-record.json" "domain-name=$zone&record-id=$record_id" if ! _contains "$response" "\"status\":\"Success\""; then _err "The TXT record for $1 cannot be deleted." else _info "Deleted." fi fi done return 0 } #################### Private functions below ################################## _dns_cloudns_init_check() { if [ -n "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then return 0 fi CLOUDNS_AUTH_ID="${CLOUDNS_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_AUTH_ID)}" CLOUDNS_SUB_AUTH_ID="${CLOUDNS_SUB_AUTH_ID:-$(_readaccountconf_mutable CLOUDNS_SUB_AUTH_ID)}" CLOUDNS_AUTH_PASSWORD="${CLOUDNS_AUTH_PASSWORD:-$(_readaccountconf_mutable CLOUDNS_AUTH_PASSWORD)}" if [ -z "$CLOUDNS_AUTH_ID$CLOUDNS_SUB_AUTH_ID" ] || [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then CLOUDNS_AUTH_ID="" CLOUDNS_SUB_AUTH_ID="" CLOUDNS_AUTH_PASSWORD="" _err "You don't specify cloudns api id and password yet." _err "Please create you id and password and try again." return 1 fi if [ -z "$CLOUDNS_AUTH_ID" ] && [ -z "$CLOUDNS_SUB_AUTH_ID" ]; then _err "CLOUDNS_AUTH_ID or CLOUDNS_SUB_AUTH_ID is not configured" return 1 fi if [ -z "$CLOUDNS_AUTH_PASSWORD" ]; then _err "CLOUDNS_AUTH_PASSWORD is not configured" return 1 fi _dns_cloudns_http_api_call "dns/login.json" "" if ! _contains "$response" "\"status\":\"Success\""; then _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials." return 1 fi # save the api id and password to the account conf file. _saveaccountconf_mutable CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" _saveaccountconf_mutable CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID" _saveaccountconf_mutable CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" CLOUDNS_INIT_CHECK_COMPLETED=1 return 0 } _dns_cloudns_get_zone_info() { zone=$1 _dns_cloudns_http_api_call "dns/get-zone-info.json" "domain-name=$zone" if ! _contains "$response" "\"status\":\"Failed\""; then DOMAIN_TYPE=$(echo "$response" | _egrep_o '"type":"[^"]*"' | cut -d : -f 2 | tr -d '"') if _contains "$DOMAIN_TYPE" "cloud"; then DOMAIN_MASTER=$(echo "$response" | _egrep_o '"cloud-master":"[^"]*"' | cut -d : -f 2 | tr -d '"') fi fi return 0 } _dns_cloudns_get_zone_name() { i=2 while true; do zoneForCheck=$(printf "%s" "$1" | cut -d . -f "$i"-100) if [ -z "$zoneForCheck" ]; then return 1 fi _debug zoneForCheck "$zoneForCheck" _dns_cloudns_http_api_call "dns/get-zone-info.json" "domain-name=$zoneForCheck" if ! _contains "$response" "\"status\":\"Failed\""; then echo "$zoneForCheck" return 0 fi i=$(_math "$i" + 1) done return 1 } _dns_cloudns_http_api_call() { method=$1 _debug CLOUDNS_AUTH_ID "$CLOUDNS_AUTH_ID" _debug CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID" _debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" if [ -n "$CLOUDNS_SUB_AUTH_ID" ]; then auth_user="sub-auth-id=$CLOUDNS_SUB_AUTH_ID" else auth_user="auth-id=$CLOUDNS_AUTH_ID" fi if [ -z "$2" ]; then data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD" else data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2" fi response="$(_get "$CLOUDNS_API/$method?$data")" _debug response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_cn.sh000066400000000000000000000075301472032365200161200ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_cn_info='Core-Networks.de Site: beta.api.Core-Networks.de/doc/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cn Options: CN_User User CN_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2142 Author: 5ll, francis ' CN_API="https://beta.api.core-networks.de" ######## Public functions ##################### dns_cn_add() { fulldomain=$1 txtvalue=$2 if ! _cn_login; then _err "login failed" return 1 fi _debug "First detect the root zone" if ! _cn_get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug "_sub_domain $_sub_domain" _debug "_domain $_domain" _info "Adding record" curData="{\"name\":\"$_sub_domain\",\"ttl\":120,\"type\":\"TXT\",\"data\":\"$txtvalue\"}" curResult="$(_post "${curData}" "${CN_API}/dnszones/${_domain}/records/")" _debug "curData $curData" _debug "curResult $curResult" if _contains "$curResult" ""; then _info "Added, OK" if ! _cn_commit; then _err "commiting changes failed" return 1 fi return 0 else _err "Add txt record error." _debug "curData is $curData" _debug "curResult is $curResult" _err "error adding text record, response was $curResult" return 1 fi } dns_cn_rm() { fulldomain=$1 txtvalue=$2 if ! _cn_login; then _err "login failed" return 1 fi _debug "First detect the root zone" if ! _cn_get_root "$fulldomain"; then _err "invalid domain" return 1 fi _info "Deleting record" curData="{\"name\":\"$_sub_domain\",\"data\":\"$txtvalue\"}" curResult="$(_post "${curData}" "${CN_API}/dnszones/${_domain}/records/delete")" _debug curData is "$curData" _info "commiting changes" if ! _cn_commit; then _err "commiting changes failed" return 1 fi _info "Deletet txt record" return 0 } ################### Private functions below ################################## _cn_login() { CN_User="${CN_User:-$(_readaccountconf_mutable CN_User)}" CN_Password="${CN_Password:-$(_readaccountconf_mutable CN_Password)}" if [ -z "$CN_User" ] || [ -z "$CN_Password" ]; then CN_User="" CN_Password="" _err "You must export variables: CN_User and CN_Password" return 1 fi #save the config variables to the account conf file. _saveaccountconf_mutable CN_User "$CN_User" _saveaccountconf_mutable CN_Password "$CN_Password" _info "Getting an AUTH-Token" curData="{\"login\":\"${CN_User}\",\"password\":\"${CN_Password}\"}" curResult="$(_post "${curData}" "${CN_API}/auth/token")" _debug "Calling _CN_login: '${curData}' '${CN_API}/auth/token'" if _contains "${curResult}" '"token":"'; then authToken=$(echo "${curResult}" | cut -d ":" -f2 | cut -d "," -f1 | sed 's/^.\(.*\).$/\1/') export _H1="Authorization: Bearer $authToken" _info "Successfully acquired AUTH-Token" _debug "AUTH-Token: '${authToken}'" _debug "_H1 '${_H1}'" else _err "Couldn't acquire an AUTH-Token" return 1 fi } # Commit changes _cn_commit() { _info "Commiting changes" _post "" "${CN_API}/dnszones/$h/records/commit" } _cn_get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" _debug _H1 "${_H1}" if [ -z "$h" ]; then #not valid return 1 fi _cn_zonelist="$(_get ${CN_API}/dnszones/)" _debug _cn_zonelist "${_cn_zonelist}" if [ "$?" != "0" ]; then _err "something went wrong while getting the zone list" return 1 fi if _contains "$_cn_zonelist" "\"name\":\"$h\"" >/dev/null; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 else _debug "Zonelist does not contain domain - iterating " fi p=$i i=$(_math "$i" + 1) done _err "Zonelist does not contain domain - exiting" return 1 } acme.sh-3.1.0/dnsapi/dns_conoha.sh000077500000000000000000000204651472032365200167740ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_conoha_info='ConoHa.jp Domains: ConoHa.io Site: ConoHa.jp Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_conoha Options: CONOHA_Username Username CONOHA_Password Password CONOHA_TenantId TenantId CONOHA_IdentityServiceApi Identity Service API. E.g. "https://identity.xxxx.conoha.io/v2.0" ' CONOHA_DNS_EP_PREFIX_REGEXP="https://dns-service\." ######## Public functions ##################### #Usage: dns_conoha_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_conoha_add() { fulldomain=$1 txtvalue=$2 _info "Using conoha" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug "Check uesrname and password" CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}" CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}" CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}" CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}" if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then CONOHA_Username="" CONOHA_Password="" CONOHA_TenantId="" CONOHA_IdentityServiceApi="" _err "You didn't specify a conoha api username and password yet." _err "Please create the user and try again." return 1 fi _saveaccountconf_mutable CONOHA_Username "$CONOHA_Username" _saveaccountconf_mutable CONOHA_Password "$CONOHA_Password" _saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId" _saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi" if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then accesstoken="$(printf "%s" "$token" | sed -n 1p)" CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)" else return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" body="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"data\":\"$txtvalue\",\"ttl\":60}" if _conoha_rest POST "$CONOHA_Api/v1/domains/$_domain_id/records" "$body" "$accesstoken"; then if _contains "$response" '"data":"'"$txtvalue"'"'; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_conoha_rm() { fulldomain=$1 txtvalue=$2 _info "Using conoha" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug "Check uesrname and password" CONOHA_Username="${CONOHA_Username:-$(_readaccountconf_mutable CONOHA_Username)}" CONOHA_Password="${CONOHA_Password:-$(_readaccountconf_mutable CONOHA_Password)}" CONOHA_TenantId="${CONOHA_TenantId:-$(_readaccountconf_mutable CONOHA_TenantId)}" CONOHA_IdentityServiceApi="${CONOHA_IdentityServiceApi:-$(_readaccountconf_mutable CONOHA_IdentityServiceApi)}" if [ -z "$CONOHA_Username" ] || [ -z "$CONOHA_Password" ] || [ -z "$CONOHA_TenantId" ] || [ -z "$CONOHA_IdentityServiceApi" ]; then CONOHA_Username="" CONOHA_Password="" CONOHA_TenantId="" CONOHA_IdentityServiceApi="" _err "You didn't specify a conoha api username and password yet." _err "Please create the user and try again." return 1 fi _saveaccountconf_mutable CONOHA_Username "$CONOHA_Username" _saveaccountconf_mutable CONOHA_Password "$CONOHA_Password" _saveaccountconf_mutable CONOHA_TenantId "$CONOHA_TenantId" _saveaccountconf_mutable CONOHA_IdentityServiceApi "$CONOHA_IdentityServiceApi" if token="$(_conoha_get_accesstoken "$CONOHA_IdentityServiceApi/tokens" "$CONOHA_Username" "$CONOHA_Password" "$CONOHA_TenantId")"; then accesstoken="$(printf "%s" "$token" | sed -n 1p)" CONOHA_Api="$(printf "%s" "$token" | sed -n 2p)" else return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain" "$CONOHA_Api" "$accesstoken"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" if ! _conoha_rest GET "$CONOHA_Api/v1/domains/$_domain_id/records" "" "$accesstoken"; then _err "Error" return 1 fi record_id=$(printf "%s" "$response" | _egrep_o '{[^}]*}' | grep '"type":"TXT"' | grep "\"data\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi _debug record_id "$record_id" _info "Removing the txt record" if ! _conoha_rest DELETE "$CONOHA_Api/v1/domains/$_domain_id/records/$record_id" "" "$accesstoken"; then _err "Delete record error." return 1 fi return 0 } #################### Private functions below ################################## _conoha_rest() { m="$1" ep="$2" data="$3" accesstoken="$4" export _H1="Accept: application/json" export _H2="Content-Type: application/json" if [ -n "$accesstoken" ]; then export _H3="X-Auth-Token: $accesstoken" fi _debug "$ep" if [ "$m" != "GET" ]; then _secure_debug2 data "$data" response="$(_post "$data" "$ep" "" "$m")" else response="$(_get "$ep")" fi _ret="$?" _secure_debug2 response "$response" if [ "$_ret" != "0" ]; then _err "error $ep" return 1 fi response="$(printf "%s" "$response" | _normalizeJson)" return 0 } _conoha_get_accesstoken() { ep="$1" username="$2" password="$3" tenantId="$4" accesstoken="$(_readaccountconf_mutable conoha_accesstoken)" expires="$(_readaccountconf_mutable conoha_tokenvalidto)" CONOHA_Api="$(_readaccountconf_mutable conoha_dns_ep)" # can we reuse the access token? if [ -n "$accesstoken" ] && [ -n "$expires" ] && [ -n "$CONOHA_Api" ]; then utc_date="$(_utc_date | sed "s/ /T/")" if expr "$utc_date" "<" "$expires" >/dev/null; then # access token is still valid - reuse it _debug "reusing access token" printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api" return 0 else _debug "access token expired" fi fi _debug "getting new access token" body="$(printf '{"auth":{"passwordCredentials":{"username":"%s","password":"%s"},"tenantId":"%s"}}' "$username" "$password" "$tenantId")" if ! _conoha_rest POST "$ep" "$body" ""; then _err error "$response" return 1 fi accesstoken=$(printf "%s" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") expires=$(printf "%s" "$response" | _egrep_o "\"expires\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2-4 | tr -d \" | tr -d Z) #expect UTC if [ -z "$accesstoken" ] || [ -z "$expires" ]; then _err "no acccess token received. Check your Conoha settings see $WIKI" return 1 fi _saveaccountconf_mutable conoha_accesstoken "$accesstoken" _saveaccountconf_mutable conoha_tokenvalidto "$expires" CONOHA_Api=$(printf "%s" "$response" | _egrep_o 'publicURL":"'"$CONOHA_DNS_EP_PREFIX_REGEXP"'[^"]*"' | _head_n 1 | cut -d : -f 2-3 | tr -d \") if [ -z "$CONOHA_Api" ]; then _err "failed to get conoha dns endpoint url" return 1 fi _saveaccountconf_mutable conoha_dns_ep "$CONOHA_Api" printf "%s\n%s\n" "$accesstoken" "$CONOHA_Api" return 0 } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain="$1" ep="$2" accesstoken="$3" i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100). _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _conoha_rest GET "$ep/v1/domains?name=$h" "" "$accesstoken"; then return 1 fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } acme.sh-3.1.0/dnsapi/dns_constellix.sh000066400000000000000000000135601472032365200177040ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_constellix_info='Constellix.com Site: Constellix.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_constellix Options: CONSTELLIX_Key API Key CONSTELLIX_Secret API Secret Issues: github.com/acmesh-official/acme.sh/issues/2724 Author: Wout Decre ' CONSTELLIX_Api="https://api.dns.constellix.com/v1" ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record dns_constellix_add() { fulldomain=$1 txtvalue=$2 CONSTELLIX_Key="${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}" CONSTELLIX_Secret="${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}" if [ -z "$CONSTELLIX_Key" ] || [ -z "$CONSTELLIX_Secret" ]; then _err "You did not specify the Contellix API key and secret yet." return 1 fi _saveaccountconf_mutable CONSTELLIX_Key "$CONSTELLIX_Key" _saveaccountconf_mutable CONSTELLIX_Secret "$CONSTELLIX_Secret" if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi # The TXT record might already exist when working with wildcard certificates. In that case, update the record by adding the new value. _debug "Search TXT record" if _constellix_rest GET "domains/${_domain_id}/records/TXT/search?exact=${_sub_domain}"; then if printf -- "%s" "$response" | grep "{\"errors\":\[\"Requested record was not found\"\]}" >/dev/null; then _info "Adding TXT record" if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"add\":true,\"set\":{\"name\":\"${_sub_domain}\",\"ttl\":60,\"roundRobin\":[{\"value\":\"${txtvalue}\"}]}}]"; then if printf -- "%s" "$response" | grep "{\"success\":\"1 record(s) added, 0 record(s) updated, 0 record(s) deleted\"}" >/dev/null; then _info "Added" return 0 else _err "Error adding TXT record" fi fi else _record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]*" | cut -d ':' -f 2) if _constellix_rest GET "domains/${_domain_id}/records/TXT/${_record_id}"; then _new_rr_values=$(printf "%s\n" "$response" | _egrep_o '"roundRobin":\[[^]]*\]' | sed "s/\]$/,{\"value\":\"${txtvalue}\"}]/") _debug _new_rr_values "$_new_rr_values" _info "Updating TXT record" if _constellix_rest PUT "domains/${_domain_id}/records/TXT/${_record_id}" "{\"name\":\"${_sub_domain}\",\"ttl\":60,${_new_rr_values}}"; then if printf -- "%s" "$response" | grep "{\"success\":\"Record.*updated successfully\"}" >/dev/null; then _info "Updated" return 0 elif printf -- "%s" "$response" | grep "{\"errors\":\[\"Contents are identical\"\]}" >/dev/null; then _info "Already exists, no need to update" return 0 else _err "Error updating TXT record" fi fi fi fi fi return 1 } # Usage: fulldomain txtvalue # Used to remove the txt record after validation dns_constellix_rm() { fulldomain=$1 txtvalue=$2 CONSTELLIX_Key="${CONSTELLIX_Key:-$(_readaccountconf_mutable CONSTELLIX_Key)}" CONSTELLIX_Secret="${CONSTELLIX_Secret:-$(_readaccountconf_mutable CONSTELLIX_Secret)}" if [ -z "$CONSTELLIX_Key" ] || [ -z "$CONSTELLIX_Secret" ]; then _err "You did not specify the Contellix API key and secret yet." return 1 fi if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi # The TXT record might have been removed already when working with some wildcard certificates. _debug "Search TXT record" if _constellix_rest GET "domains/${_domain_id}/records/TXT/search?exact=${_sub_domain}"; then if printf -- "%s" "$response" | grep "{\"errors\":\[\"Requested record was not found\"\]}" >/dev/null; then _info "Removed" return 0 else _info "Removing TXT record" if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"delete\":true,\"filter\":{\"field\":\"name\",\"op\":\"eq\",\"value\":\"${_sub_domain}\"}}]"; then if printf -- "%s" "$response" | grep "{\"success\":\"0 record(s) added, 0 record(s) updated, 1 record(s) deleted\"}" >/dev/null; then _info "Removed" return 0 else _err "Error removing TXT record" fi fi fi fi return 1 } #################### Private functions below ################################## _get_root() { domain=$1 i=2 p=1 _debug "Detecting root zone" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then return 1 fi if ! _constellix_rest GET "domains/search?exact=$h"; then return 1 fi if _contains "$response" "\"name\":\"$h\""; then _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]*" | cut -d ':' -f 2) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-"$p") _domain="$h" _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _constellix_rest() { m=$1 ep="$2" data="$3" _debug "$ep" rdate=$(date +"%s")"000" hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64) export _H1="x-cnsdns-apiKey: $CONSTELLIX_Key" export _H2="x-cnsdns-requestDate: $rdate" export _H3="x-cnsdns-hmac: $hmac" export _H4="Accept: application/json" export _H5="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$CONSTELLIX_Api/$ep" "" "$m")" else response="$(_get "$CONSTELLIX_Api/$ep")" fi if [ "$?" != "0" ]; then _err "Error $ep" return 1 fi _debug response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_cpanel.sh000077500000000000000000000117711472032365200167670ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_cpanel_info='cPanel Server API Manage DNS via cPanel Dashboard. Site: cPanel.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_cpanel Options: cPanel_Username Username cPanel_Apitoken API Token cPanel_Hostname Server URL. E.g. "https://hostname:port" Issues: github.com/acmesh-official/acme.sh/issues/3732 Author: Bjarne Saltbaek ' ######## Public functions ##################### # Used to add txt record dns_cpanel_add() { fulldomain=$1 txtvalue=$2 _info "Adding TXT record to cPanel based system" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug cPanel_Username "$cPanel_Username" _debug cPanel_Apitoken "$cPanel_Apitoken" _debug cPanel_Hostname "$cPanel_Hostname" if ! _cpanel_login; then _err "cPanel Login failed for user $cPanel_Username. Check $HTTP_HEADER file" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "No matching root domain for $fulldomain found" return 1 fi # adding entry _info "Adding the entry" stripped_fulldomain=$(echo "$fulldomain" | sed "s/.$_domain//") _debug "Adding $stripped_fulldomain to $_domain zone" _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1" if _successful_update; then return 0; fi _err "Couldn't create entry!" return 1 } # Usage: fulldomain txtvalue # Used to remove the txt record after validation dns_cpanel_rm() { fulldomain=$1 txtvalue=$2 _info "Using cPanel based system" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" if ! _cpanel_login; then _err "cPanel Login failed for user $cPanel_Username. Check $HTTP_HEADER file" return 1 fi if ! _get_root; then _err "No matching root domain for $fulldomain found" return 1 fi _findentry "$fulldomain" "$txtvalue" if [ -z "$_id" ]; then _info "Entry doesn't exist, nothing to delete" return 0 fi _debug "Deleting record..." _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=remove_zone_record&domain=$_domain&line=$_id" # removing entry _debug "_result is: $_result" if _successful_update; then return 0; fi _err "Couldn't delete entry!" return 1 } #################### Private functions below ################################## _checkcredentials() { cPanel_Username="${cPanel_Username:-$(_readaccountconf_mutable cPanel_Username)}" cPanel_Apitoken="${cPanel_Apitoken:-$(_readaccountconf_mutable cPanel_Apitoken)}" cPanel_Hostname="${cPanel_Hostname:-$(_readaccountconf_mutable cPanel_Hostname)}" if [ -z "$cPanel_Username" ] || [ -z "$cPanel_Apitoken" ] || [ -z "$cPanel_Hostname" ]; then cPanel_Username="" cPanel_Apitoken="" cPanel_Hostname="" _err "You haven't specified cPanel username, apitoken and hostname yet." _err "Please add credentials and try again." return 1 fi #save the credentials to the account conf file. _saveaccountconf_mutable cPanel_Username "$cPanel_Username" _saveaccountconf_mutable cPanel_Apitoken "$cPanel_Apitoken" _saveaccountconf_mutable cPanel_Hostname "$cPanel_Hostname" return 0 } _cpanel_login() { if ! _checkcredentials; then return 1; fi if ! _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=CustInfo&cpanel_jsonapi_func=displaycontactinfo"; then _err "cPanel login failed for user $cPanel_Username." return 1 fi return 0 } _myget() { #Adds auth header to request export _H1="Authorization: cpanel $cPanel_Username:$cPanel_Apitoken" _result=$(_get "$cPanel_Hostname/$1") } _get_root() { _myget 'json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzones' _domains=$(echo "$_result" | _egrep_o '"[a-z0-9\.\-]*":\["; cPanel first' | cut -d':' -f1 | sed 's/"//g' | sed 's/{//g') _debug "_result is: $_result" _debug "_domains is: $_domains" if [ -z "$_domains" ]; then _err "Primary domain list not found!" return 1 fi for _domain in $_domains; do _debug "Checking if $fulldomain ends with $_domain" if (_endswith "$fulldomain" "$_domain"); then _debug "Root domain: $_domain" return 0 fi done return 1 } _successful_update() { if (echo "$_result" | _egrep_o 'data":\[[^]]*]' | grep -q '"newserial":null'); then return 1; fi return 0 } _findentry() { _debug "In _findentry" #returns id of dns entry, if it exists _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&domain=$_domain" _id=$(echo "$_result" | sed -e "s/},{/},\n{/g" | grep "$fulldomain" | grep "$txtvalue" | _egrep_o 'line":[0-9]+' | cut -d ':' -f 2) _debug "_result is: $_result" _debug "fulldomain. is $fulldomain." _debug "txtvalue is $txtvalue" _debug "_id is: $_id" if [ -n "$_id" ]; then _debug "Entry found with _id=$_id" return 0 fi return 1 } acme.sh-3.1.0/dnsapi/dns_curanet.sh000066400000000000000000000114471472032365200171630ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_curanet_info='Curanet.dk Domains: scannet.dk wannafind.dk dandomain.dk Site: Curanet.dk Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_curanet Options: CURANET_AUTHCLIENTID Auth ClientID. Requires scope dns CURANET_AUTHSECRET Auth Secret Issues: github.com/acmesh-official/acme.sh/issues/3933 Author: Peter L. Hansen ' CURANET_REST_URL="https://api.curanet.dk/dns/v1/Domains" CURANET_AUTH_URL="https://apiauth.dk.team.blue/auth/realms/Curanet/protocol/openid-connect/token" CURANET_ACCESS_TOKEN="" ######## Public functions ##################### #Usage: dns_curanet_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_curanet_add() { fulldomain=$1 txtvalue=$2 _info "Using curanet" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" CURANET_AUTHCLIENTID="${CURANET_AUTHCLIENTID:-$(_readaccountconf_mutable CURANET_AUTHCLIENTID)}" CURANET_AUTHSECRET="${CURANET_AUTHSECRET:-$(_readaccountconf_mutable CURANET_AUTHSECRET)}" if [ -z "$CURANET_AUTHCLIENTID" ] || [ -z "$CURANET_AUTHSECRET" ]; then CURANET_AUTHCLIENTID="" CURANET_AUTHSECRET="" _err "You don't specify curanet api client and secret." _err "Please create your auth info and try again." return 1 fi #save the credentials to the account conf file. _saveaccountconf_mutable CURANET_AUTHCLIENTID "$CURANET_AUTHCLIENTID" _saveaccountconf_mutable CURANET_AUTHSECRET "$CURANET_AUTHSECRET" if ! _get_token; then _err "Unable to get token" return 1 fi if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi export _H1="Content-Type: application/json-patch+json" export _H2="Accept: application/json" export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" data="{\"name\": \"$fulldomain\",\"type\": \"TXT\",\"ttl\": 60,\"priority\": 0,\"data\": \"$txtvalue\"}" response="$(_post "$data" "$CURANET_REST_URL/${_domain}/Records" "" "")" if _contains "$response" "$txtvalue"; then _debug "TXT record added OK" else _err "Unable to add TXT record" return 1 fi return 0 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_curanet_rm() { fulldomain=$1 txtvalue=$2 _info "Using curanet" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" CURANET_AUTHCLIENTID="${CURANET_AUTHCLIENTID:-$(_readaccountconf_mutable CURANET_AUTHCLIENTID)}" CURANET_AUTHSECRET="${CURANET_AUTHSECRET:-$(_readaccountconf_mutable CURANET_AUTHSECRET)}" if ! _get_token; then _err "Unable to get token" return 1 fi if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi _debug "Getting current record list to identify TXT to delete" export _H1="Content-Type: application/json" export _H2="Accept: application/json" export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" response="$(_get "$CURANET_REST_URL/${_domain}/Records" "" "")" if ! _contains "$response" "$txtvalue"; then _err "Unable to delete record (does not contain $txtvalue )" return 1 fi recordid=$(echo "$response" | _egrep_o "{\"id\":[0-9]+,\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":60,\"priority\":0,\"data\":\"..$txtvalue" | _egrep_o "id\":[0-9]+" | cut -c 5-) if [ -z "$recordid" ]; then _err "Unable to get recordid" _debug "regex {\"id\":[0-9]+,\"name\":\"$fulldomain\",\"type\":\"TXT\",\"ttl\":60,\"priority\":0,\"data\":\"..$txtvalue" _debug "response $response" return 1 fi _debug "Deleting recordID $recordid" response="$(_post "" "$CURANET_REST_URL/${_domain}/Records/$recordid" "" "DELETE")" return 0 } #################### Private functions below ################################## _get_token() { response="$(_post "grant_type=client_credentials&client_id=$CURANET_AUTHCLIENTID&client_secret=$CURANET_AUTHSECRET&scope=dns" "$CURANET_AUTH_URL" "" "")" if ! _contains "$response" "access_token"; then _err "Unable get access token" return 1 fi CURANET_ACCESS_TOKEN=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]+" | cut -c 17-) if [ -z "$CURANET_ACCESS_TOKEN" ]; then _err "Unable to get token" return 1 fi return 0 } #_acme-challenge.www.domain.com #returns # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi export _H1="Content-Type: application/json" export _H2="Accept: application/json" export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" response="$(_get "$CURANET_REST_URL/$h/Records" "" "")" if [ ! "$(echo "$response" | _egrep_o "Entity not found")" ]; then _domain=$h return 0 fi i=$(_math "$i" + 1) done return 1 } acme.sh-3.1.0/dnsapi/dns_cyon.sh000066400000000000000000000231431472032365200164660ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_cyon_info='cyon.ch Site: cyon.ch Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cyon Options: CY_Username Username CY_Password API Token CY_OTP_Secret OTP token. Only required if using 2FA Issues: github.com/noplanman/cyon-api/issues Author: Armando Lüscher ' dns_cyon_add() { _cyon_load_credentials && _cyon_load_parameters "$@" && _cyon_print_header "add" && _cyon_login && _cyon_change_domain_env && _cyon_add_txt && _cyon_logout } dns_cyon_rm() { _cyon_load_credentials && _cyon_load_parameters "$@" && _cyon_print_header "delete" && _cyon_login && _cyon_change_domain_env && _cyon_delete_txt && _cyon_logout } ######################### ### PRIVATE FUNCTIONS ### ######################### _cyon_load_credentials() { # Convert loaded password to/from base64 as needed. if [ "${CY_Password_B64}" ]; then CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64)" elif [ "${CY_Password}" ]; then CY_Password_B64="$(printf "%s" "${CY_Password}" | _base64)" fi if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then # Dummy entries to satisfy script checker. CY_Username="" CY_Password="" CY_OTP_Secret="" _err "" _err "You haven't set your cyon.ch login credentials yet." _err "Please set the required cyon environment variables." _err "" return 1 fi # Save the login credentials to the account.conf file. _debug "Save credentials to account.conf" _saveaccountconf CY_Username "${CY_Username}" _saveaccountconf CY_Password_B64 "$CY_Password_B64" if [ -n "${CY_OTP_Secret}" ]; then _saveaccountconf CY_OTP_Secret "$CY_OTP_Secret" else _clearaccountconf CY_OTP_Secret fi } _cyon_is_idn() { _idn_temp="$(printf "%s" "${1}" | tr -d "0-9a-zA-Z.,-_")" _idn_temp2="$(printf "%s" "${1}" | grep -o "xn--")" [ "$_idn_temp" ] || [ "$_idn_temp2" ] } _cyon_load_parameters() { # Read the required parameters to add the TXT entry. # shellcheck disable=SC2018,SC2019 fulldomain="$(printf "%s" "${1}" | tr "A-Z" "a-z")" fulldomain_idn="${fulldomain}" # Special case for IDNs, as cyon needs a domain environment change, # which uses the "pretty" instead of the punycode version. if _cyon_is_idn "${fulldomain}"; then if ! _exists idn; then _err "Please install idn to process IDN names." _err "" return 1 fi fulldomain="$(idn -u "${fulldomain}")" fulldomain_idn="$(idn -a "${fulldomain}")" fi _debug fulldomain "${fulldomain}" _debug fulldomain_idn "${fulldomain_idn}" txtvalue="${2}" _debug txtvalue "${txtvalue}" # This header is required for curl calls. _H1="X-Requested-With: XMLHttpRequest" export _H1 } _cyon_print_header() { if [ "${1}" = "add" ]; then _info "" _info "+---------------------------------------------+" _info "| Adding DNS TXT entry to your cyon.ch domain |" _info "+---------------------------------------------+" _info "" _info " * Full Domain: ${fulldomain}" _info " * TXT Value: ${txtvalue}" _info "" elif [ "${1}" = "delete" ]; then _info "" _info "+-------------------------------------------------+" _info "| Deleting DNS TXT entry from your cyon.ch domain |" _info "+-------------------------------------------------+" _info "" _info " * Full Domain: ${fulldomain}" _info "" fi } _cyon_get_cookie_header() { printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')" } _cyon_login() { _info " - Logging in..." username_encoded="$(printf "%s" "${CY_Username}" | _url_encode)" password_encoded="$(printf "%s" "${CY_Password}" | _url_encode)" login_url="https://my.cyon.ch/auth/index/dologin-async" login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")" login_response="$(_post "$login_data" "$login_url")" _debug login_response "${login_response}" # Bail if login fails. if [ "$(printf "%s" "${login_response}" | _cyon_get_response_success)" != "success" ]; then _err " $(printf "%s" "${login_response}" | _cyon_get_response_message)" _err "" return 1 fi _info " success" # NECESSARY!! Load the main page after login, to get the new cookie. _H2="$(_cyon_get_cookie_header)" export _H2 _get "https://my.cyon.ch/" >/dev/null # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. # 2FA authentication with OTP? if [ -n "${CY_OTP_Secret}" ]; then _info " - Authorising with OTP code..." if ! _exists oathtool; then _err "Please install oathtool to use 2 Factor Authentication." _err "" return 1 fi # Get OTP code with the defined secret. otp_code="$(oathtool --base32 --totp "${CY_OTP_Secret}" 2>/dev/null)" login_otp_url="https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" login_otp_data="totpcode=${otp_code}&pathname=%2F&rememberme=0" login_otp_response="$(_post "$login_otp_data" "$login_otp_url")" _debug login_otp_response "${login_otp_response}" # Bail if OTP authentication fails. if [ "$(printf "%s" "${login_otp_response}" | _cyon_get_response_success)" != "success" ]; then _err " $(printf "%s" "${login_otp_response}" | _cyon_get_response_message)" _err "" return 1 fi _info " success" fi _info "" } _cyon_logout() { _info " - Logging out..." _get "https://my.cyon.ch/auth/index/dologout" >/dev/null _info " success" _info "" } _cyon_change_domain_env() { _info " - Changing domain environment..." # Get the "example.com" part of the full domain name. domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')" _debug "Changing domain environment to ${domain_env}" gloo_item_key="$(_get "https://my.cyon.ch/domain/" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")" _debug gloo_item_key "${gloo_item_key}" domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}" domain_env_response="$(_get "${domain_env_url}")" _debug domain_env_response "${domain_env_response}" if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)" # Bail if domain environment change fails. if [ "${domain_env_success}" != "true" ]; then _err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)" _err "" return 1 fi _info " success" _info "" } _cyon_add_txt() { _info " - Adding DNS TXT entry..." add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async" add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}" add_txt_response="$(_post "$add_txt_data" "$add_txt_url")" _debug add_txt_response "${add_txt_response}" if ! _cyon_check_if_2fa_missed "${add_txt_response}"; then return 1; fi add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)" add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)" # Bail if adding TXT entry fails. if [ "${add_txt_status}" != "true" ]; then _err " ${add_txt_message}" _err "" return 1 fi _info " success (TXT|${fulldomain_idn}.|${txtvalue})" _info "" } _cyon_delete_txt() { _info " - Deleting DNS TXT entry..." list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async" list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')" _debug list_txt_response "${list_txt_response}" if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi # Find and delete all acme challenge entries for the $fulldomain. _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')" printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)" dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)" if [ "${dns_type}" != "TXT" ] || [ "${dns_domain}" != "${fulldomain_idn}." ]; then continue fi hash_encoded="$(printf "%s" "${_hash}" | _url_encode)" identifier_encoded="$(printf "%s" "${_identifier}" | _url_encode)" delete_txt_url="https://my.cyon.ch/domain/dnseditor/delete-record-async" delete_txt_data="$(printf "%s" "hash=${hash_encoded}&identifier=${identifier_encoded}")" delete_txt_response="$(_post "$delete_txt_data" "$delete_txt_url")" _debug delete_txt_response "${delete_txt_response}" if ! _cyon_check_if_2fa_missed "${delete_txt_response}"; then return 1; fi delete_txt_message="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_message)" delete_txt_status="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_status)" # Skip if deleting TXT entry fails. if [ "${delete_txt_status}" != "true" ]; then _err " ${delete_txt_message} (${_identifier})" else _info " success (${_identifier})" fi done _info " done" _info "" } _cyon_get_response_message() { _egrep_o '"message":"[^"]*"' | cut -d : -f 2 | tr -d '"' } _cyon_get_response_status() { _egrep_o '"status":\w*' | cut -d : -f 2 } _cyon_get_response_success() { _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' } _cyon_check_if_2fa_missed() { # Did we miss the 2FA? if test "${1#*multi_factor_form}" != "${1}"; then _err " Missed OTP authentication!" _err "" return 1 fi } acme.sh-3.1.0/dnsapi/dns_da.sh000077500000000000000000000115771472032365200161150ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_da_info='DirectAdmin Server API Site: DirectAdmin.com/api.php Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_da Options: DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443" DA_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept Issues: github.com/TigerP/acme.sh/issues ' ######## 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 } acme.sh-3.1.0/dnsapi/dns_ddnss.sh000066400000000000000000000070221472032365200166270ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ddnss_info='DDNSS.de Site: DDNSS.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss Options: DDNSS_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2230 Author: RaidenII, helbgd, mod242 ' DDNSS_DNS_API="https://ddnss.de/upd.php" ######## Public functions ##################### #Usage: dns_ddnss_add _acme-challenge.domain.ddnss.de "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_ddnss_add() { fulldomain=$1 txtvalue=$2 DDNSS_Token="${DDNSS_Token:-$(_readaccountconf_mutable DDNSS_Token)}" if [ -z "$DDNSS_Token" ]; then _err "You must export variable: DDNSS_Token" _err "The token for your DDNSS account is necessary." _err "You can look it up in your DDNSS account." return 1 fi # Now save the credentials. _saveaccountconf_mutable DDNSS_Token "$DDNSS_Token" # Unfortunately, DDNSS does not seems to support lookup domain through API # So I assume your credentials (which are your domain and token) are correct # If something goes wrong, we will get a KO response from DDNSS if ! _ddnss_get_domain; then return 1 fi # Now add the TXT record to DDNSS DNS _info "Trying to add TXT record" if _ddnss_rest GET "key=$DDNSS_Token&host=$_ddnss_domain&txtm=1&txt=$txtvalue"; then if [ "$response" = "Updated 1 hostname." ]; then _info "TXT record has been successfully added to your DDNSS domain." _info "Note that all subdomains under this domain uses the same TXT record." return 0 else _err "Errors happened during adding the TXT record, response=$response" return 1 fi else _err "Errors happened during adding the TXT record." return 1 fi } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_ddnss_rm() { fulldomain=$1 txtvalue=$2 DDNSS_Token="${DDNSS_Token:-$(_readaccountconf_mutable DDNSS_Token)}" if [ -z "$DDNSS_Token" ]; then _err "You must export variable: DDNSS_Token" _err "The token for your DDNSS account is necessary." _err "You can look it up in your DDNSS account." return 1 fi if ! _ddnss_get_domain; then return 1 fi # Now remove the TXT record from DDNS DNS _info "Trying to remove TXT record" if _ddnss_rest GET "key=$DDNSS_Token&host=$_ddnss_domain&txtm=2"; then if [ "$response" = "Updated 1 hostname." ]; then _info "TXT record has been successfully removed from your DDNSS domain." return 0 else _err "Errors happened during removing the TXT record, response=$response" return 1 fi else _err "Errors happened during removing the TXT record." return 1 fi } #################### Private functions below ################################## #fulldomain=_acme-challenge.domain.ddnss.de #returns # _ddnss_domain=domain _ddnss_get_domain() { # We'll extract the domain/username from full domain _ddnss_domain="$(echo "$fulldomain" | _lower_case | _egrep_o '[.][^.][^.]*[.](ddnss|dyn-ip24|dyndns|dyn|dyndns1|home-webserver|myhome-server|dynip)\..*' | cut -d . -f 2-)" if [ -z "$_ddnss_domain" ]; then _err "Error extracting the domain." return 1 fi return 0 } #Usage: method URI _ddnss_rest() { method=$1 param="$2" _debug param "$param" url="$DDNSS_DNS_API?$param" _debug url "$url" # DDNSS uses GET to update domain info if [ "$method" = "GET" ]; then response="$(_get "$url" | sed 's/<[a-zA-Z\/][^>]*>//g' | tr -s "\n" | _tail_n 1)" else _err "Unsupported method" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_desec.sh000066400000000000000000000121471472032365200166030ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_desec_info='deSEC.io Site: desec.readthedocs.io/en/latest/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_desec Options: DDNSS_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2180 Author: Zheng Qian ' REST_API="https://desec.io/api/v1/domains" ######## Public functions ##################### #Usage: dns_desec_add _acme-challenge.foobar.dedyn.io "d41d8cd98f00b204e9800998ecf8427e" dns_desec_add() { fulldomain=$1 txtvalue=$2 _info "Using desec.io api" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" DEDYN_TOKEN="${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}" if [ -z "$DEDYN_TOKEN" ]; then DEDYN_TOKEN="" _err "You did not specify DEDYN_TOKEN yet." _err "Please create your key and try again." _err "e.g." _err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e" return 1 fi #save the api token to the account conf file. _saveaccountconf_mutable DEDYN_TOKEN "$DEDYN_TOKEN" _debug "First detect the root zone" if ! _get_root "$fulldomain" "$REST_API/"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # Get existing TXT record _debug "Getting txt records" txtvalues="\"\\\"$txtvalue\\\"\"" _desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/" if [ "$_code" = "200" ]; then oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")" _debug "existing TXT found" _debug oldtxtvalues "$oldtxtvalues" if [ -n "$oldtxtvalues" ]; then for oldtxtvalue in $oldtxtvalues; do txtvalues="$txtvalues, \"\\\"$oldtxtvalue\\\"\"" done fi fi _debug txtvalues "$txtvalues" _info "Adding record" body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":3600}]" if _desec_rest PUT "$REST_API/$_domain/rrsets/" "$body"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_desec_rm() { fulldomain=$1 txtvalue=$2 _info "Using desec.io api" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" DEDYN_TOKEN="${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}" if [ -z "$DEDYN_TOKEN" ]; then DEDYN_TOKEN="" _err "You did not specify DEDYN_TOKEN yet." _err "Please create your key and try again." _err "e.g." _err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain" "$REST_API/"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # Get existing TXT record _debug "Getting txt records" txtvalues="" _desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/" if [ "$_code" = "200" ]; then oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")" _debug "existing TXT found" _debug oldtxtvalues "$oldtxtvalues" if [ -n "$oldtxtvalues" ]; then for oldtxtvalue in $oldtxtvalues; do if [ "$txtvalue" != "$oldtxtvalue" ]; then txtvalues="$txtvalues, \"\\\"$oldtxtvalue\\\"\"" fi done fi fi txtvalues="$(echo "$txtvalues" | cut -c3-)" _debug txtvalues "$txtvalues" _info "Deleting record" body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":3600}]" _desec_rest PUT "$REST_API/$_domain/rrsets/" "$body" if [ "$_code" = "200" ]; then _info "Deleted, OK" return 0 fi _err "Delete txt record error." return 1 } #################### Private functions below ################################## _desec_rest() { m="$1" ep="$2" data="$3" export _H1="Authorization: Token $DEDYN_TOKEN" export _H2="Accept: application/json" export _H3="Content-Type: application/json" if [ "$m" != "GET" ]; then _secure_debug2 data "$data" response="$(_post "$data" "$ep" "" "$m")" else response="$(_get "$ep")" fi _ret="$?" _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" _debug "http response code $_code" _secure_debug2 response "$response" if [ "$_ret" != "0" ]; then _err "error $ep" return 1 fi response="$(printf "%s" "$response" | _normalizeJson)" return 0 } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain="$1" ep="$2" i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _desec_rest GET "$ep"; then return 1 fi if _contains "$response" "\"name\":\"$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 return 1 } acme.sh-3.1.0/dnsapi/dns_df.sh000066400000000000000000000032461472032365200161110ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_df_info='DynDnsFree.de Domains: dynup.de Site: DynDnsFree.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_df Options: DF_user Username DF_password Password Issues: github.com/acmesh-official/acme.sh/issues/2897 Author: Thilo Gass ' dyndnsfree_api="https://dynup.de/acme.php" dns_df_add() { fulldomain=$1 txt_value=$2 _info "Using DNS-01 dyndnsfree.de hook" DF_user="${DF_user:-$(_readaccountconf_mutable DF_user)}" DF_password="${DF_password:-$(_readaccountconf_mutable DF_password)}" if [ -z "$DF_user" ] || [ -z "$DF_password" ]; then DF_user="" DF_password="" _err "No auth details provided. Please set user credentials using the \$DF_user and \$DF_password environment variables." return 1 fi #save the api user and password to the account conf file. _debug "Save user and password" _saveaccountconf_mutable DF_user "$DF_user" _saveaccountconf_mutable DF_password "$DF_password" domain="$(printf "%s" "$fulldomain" | cut -d"." -f2-)" get="$dyndnsfree_api?username=$DF_user&password=$DF_password&hostname=$domain&add_hostname=$fulldomain&txt=$txt_value" if ! erg="$(_get "$get")"; then _err "error Adding $fulldomain TXT: $txt_value" return 1 fi if _contains "$erg" "success"; then _info "Success, TXT Added, OK" else _err "error Adding $fulldomain TXT: $txt_value erg: $erg" return 1 fi _debug "ok Auto $fulldomain TXT: $txt_value erg: $erg" return 0 } dns_df_rm() { fulldomain=$1 txtvalue=$2 _info "TXT enrty in $fulldomain is deleted automatically" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" } acme.sh-3.1.0/dnsapi/dns_dgon.sh000077500000000000000000000171721472032365200164550ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dgon_info='DigitalOcean.com Site: DigitalOcean.com/help/api/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dgon Options: DO_API_KEY API Key Author: ' ##################### Public functions ##################### ## Create the text record for validation. ## Usage: fulldomain txtvalue ## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" 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 Exists 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_mutable DO_API_KEY "$DO_API_KEY" ## split the domain for DO API if ! _get_base_domain "$fulldomain"; then _err "domain not found in your account for addition" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" ## Set the header with our post type and key auth key 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'","ttl":120}' _debug PURL "$PURL" _debug PBODY "$PBODY" ## the create request - post ## args: BODY, URL, [need64, httpmethod] response="$(_post "$PBODY" "$PURL")" ## check response if [ "$?" != "0" ]; then _err "error in response: $response" return 1 fi _debug2 response "$response" ## finished correctly return 0 } ## Remove the txt record after validation. ## Usage: fulldomain txtvalue ## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" 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 Exists 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" ## split the domain for DO API if ! _get_base_domain "$fulldomain"; then _err "domain not found in your account for removal" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" ## Set the header with our post type and key auth key export _H1="Content-Type: application/json" export _H2="Authorization: Bearer $DO_API_KEY" ## get URL for the list of domains ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} GURL="https://api.digitalocean.com/v2/domains/$_domain/records" ## Get all the matching records while true; do ## 1) get the URL ## the create request - get ## args: URL, [onlyheader, timeout] domain_list="$(_get "$GURL")" ## check response if [ "$?" != "0" ]; then _err "error in domain_list response: $domain_list" return 1 fi _debug2 domain_list "$domain_list" ## 2) find records ## check for what we are looking for: "type":"A","name":"$_sub_domain" record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" if [ -n "$record" ]; then ## we found records rec_ids="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" _debug rec_ids "$rec_ids" if [ -n "$rec_ids" ]; then echo "$rec_ids" | while IFS= read -r rec_id; do ## delete the record ## delete URL for removing the one we dont want DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" ## the create request - delete ## args: BODY, URL, [need64, httpmethod] response="$(_post "" "$DURL" "" "DELETE")" ## check response (sort of) if [ "$?" != "0" ]; then _err "error in remove response: $response" return 1 fi _debug2 response "$response" done fi fi ## 3) find the next page nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=[0-9]+")" if [ -z "$nextpage" ]; then break fi _debug2 nextpage "$nextpage" GURL="$nextpage" done ## finished correctly return 0 } ##################### Private functions below ##################### ## Split the domain provided into the "bade domain" and the "start prefix". ## This function searches for the longest subdomain in your account ## for the full domain given and splits it into the base domain (zone) ## and the prefix/record to be added/removed ## USAGE: fulldomain ## EG: "_acme-challenge.two.three.four.domain.com" ## returns ## _sub_domain="_acme-challenge.two" ## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists ## if only "domain.com" exists it will return ## _sub_domain="_acme-challenge.two.three.four" ## _domain="domain.com" _get_base_domain() { # args fulldomain="$(echo "$1" | _lower_case)" _debug fulldomain "$fulldomain" # domain max legal length = 253 MAX_DOM=255 ## get a list of domains for the account to check thru ## Set the headers export _H1="Content-Type: application/json" export _H2="Authorization: Bearer $DO_API_KEY" _debug DO_API_KEY "$DO_API_KEY" ## get URL for the list of domains ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} DOMURL="https://api.digitalocean.com/v2/domains" found="" ## while we dont have a matching domain we keep going while [ -z "$found" ]; do ## get the domain list (current page) domain_list="$(_get "$DOMURL")" ## check response if [ "$?" != "0" ]; then _err "error in domain_list response: $domain_list" return 1 fi _debug2 domain_list "$domain_list" i=1 while [ "$i" -gt 0 ]; do ## get next longest domain _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") ## check we got something back from our cut (or are we at the end) if [ -z "$_domain" ]; then break fi ## we got part of a domain back - grep it out found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" ## check if it exists if [ -n "$found" ]; then ## exists - exit loop returning the parts sub_point=$(_math "$i" - 1) _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") _debug _domain "$_domain" _debug _sub_domain "$_sub_domain" return 0 fi ## increment cut point $i i=$(_math "$i" + 1) done if [ -z "$found" ]; 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\=[0-9]+")" if [ -z "$nextpage" ]; then _err "no record and no nextpage in digital ocean DNS removal" return 1 fi _debug2 nextpage "$nextpage" DOMURL="$nextpage" fi done ## we went through the entire domain zone list and dint find one that matched ## doesnt look like we can add in the record _err "domain not found in DigitalOcean account, but we should never get here" return 1 } acme.sh-3.1.0/dnsapi/dns_dnsexit.sh000066400000000000000000000105151472032365200171730ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dnsexit_info='DNSExit.com Site: DNSExit.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsexit Options: DNSEXIT_API_KEY API Key DNSEXIT_AUTH_USER Username DNSEXIT_AUTH_PASS Password Issues: github.com/acmesh-official/acme.sh/issues/4719 Author: Samuel Jimenez ' DNSEXIT_API_URL="https://api.dnsexit.com/dns/" DNSEXIT_HOSTS_URL="https://update.dnsexit.com/ipupdate/hosts.jsp" ######## Public functions ##################### #Usage: dns_dnsexit_add _acme-challenge.*.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dnsexit_add() { fulldomain=$1 txtvalue=$2 _info "Using DNSExit.com" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug 'Load account auth' if ! get_account_info; then return 1 fi _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"add\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":0,\"overwrite\":false}}"; then _err "$response" return 1 fi _debug2 _response "$response" return 0 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_dnsexit_rm() { fulldomain=$1 txtvalue=$2 _info "Using DNSExit.com" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug 'Load account auth' if ! get_account_info; then return 1 fi _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then _err "$response" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" if ! _dnsexit_rest "{\"domain\":\"$_domain\",\"delete\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"}}"; then _err "$response" return 1 fi _debug2 _response "$response" return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 while true; do _domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$_domain" if [ -z "$_domain" ]; then return 1 fi _debug login "$DNSEXIT_AUTH_USER" _debug password "$DNSEXIT_AUTH_PASS" _debug domain "$_domain" _dnsexit_http "login=$DNSEXIT_AUTH_USER&password=$DNSEXIT_AUTH_PASS&domain=$_domain" if _contains "$response" "0=$_domain"; then _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")" return 0 else _debug "Go to next level of $_domain" fi i=$(_math "$i" + 1) done return 1 } _dnsexit_rest() { m=POST ep="" data="$1" _debug _dnsexit_rest "$ep" _debug data "$data" api_key_trimmed=$(echo "$DNSEXIT_API_KEY" | tr -d '"') export _H1="apikey: $api_key_trimmed" export _H2='Content-Type: application/json' if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$DNSEXIT_API_URL/$ep" "" "$m")" else response="$(_get "$DNSEXIT_API_URL/$ep")" fi if [ "$?" != "0" ]; then _err "Error $ep" return 1 fi _debug2 response "$response" return 0 } _dnsexit_http() { m=GET param="$1" _debug param "$param" _debug get "$DNSEXIT_HOSTS_URL?$param" response="$(_get "$DNSEXIT_HOSTS_URL?$param")" _debug response "$response" if [ "$?" != "0" ]; then _err "Error $param" return 1 fi _debug2 response "$response" return 0 } get_account_info() { DNSEXIT_API_KEY="${DNSEXIT_API_KEY:-$(_readaccountconf_mutable DNSEXIT_API_KEY)}" if test -z "$DNSEXIT_API_KEY"; then DNSEXIT_API_KEY='' _err 'DNSEXIT_API_KEY was not exported' return 1 fi _saveaccountconf_mutable DNSEXIT_API_KEY "$DNSEXIT_API_KEY" DNSEXIT_AUTH_USER="${DNSEXIT_AUTH_USER:-$(_readaccountconf_mutable DNSEXIT_AUTH_USER)}" if test -z "$DNSEXIT_AUTH_USER"; then DNSEXIT_AUTH_USER="" _err 'DNSEXIT_AUTH_USER was not exported' return 1 fi _saveaccountconf_mutable DNSEXIT_AUTH_USER "$DNSEXIT_AUTH_USER" DNSEXIT_AUTH_PASS="${DNSEXIT_AUTH_PASS:-$(_readaccountconf_mutable DNSEXIT_AUTH_PASS)}" if test -z "$DNSEXIT_AUTH_PASS"; then DNSEXIT_AUTH_PASS="" _err 'DNSEXIT_AUTH_PASS was not exported' return 1 fi _saveaccountconf_mutable DNSEXIT_AUTH_PASS "$DNSEXIT_AUTH_PASS" return 0 } acme.sh-3.1.0/dnsapi/dns_dnshome.sh000077500000000000000000000046721472032365200171640ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dnshome_info='dnsHome.de Site: dnsHome.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnshome Options: DNSHOME_Subdomain Subdomain DNSHOME_SubdomainPassword Subdomain Password Issues: github.com/acmesh-official/acme.sh/issues/3819 Author: dnsHome.de https://github.com/dnsHome-de ' # Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record dns_dnshome_add() { txtvalue=$2 DNSHOME_Subdomain="${DNSHOME_Subdomain:-$(_readdomainconf DNSHOME_Subdomain)}" DNSHOME_SubdomainPassword="${DNSHOME_SubdomainPassword:-$(_readdomainconf DNSHOME_SubdomainPassword)}" if [ -z "$DNSHOME_Subdomain" ] || [ -z "$DNSHOME_SubdomainPassword" ]; then DNSHOME_Subdomain="" DNSHOME_SubdomainPassword="" _err "Please specify/export your dnsHome.de Subdomain and Password" return 1 fi #save the credentials to the account conf file. _savedomainconf DNSHOME_Subdomain "$DNSHOME_Subdomain" _savedomainconf DNSHOME_SubdomainPassword "$DNSHOME_SubdomainPassword" DNSHOME_Api="https://$DNSHOME_Subdomain:$DNSHOME_SubdomainPassword@www.dnshome.de/dyndns.php" _DNSHOME_rest POST "acme=add&txt=$txtvalue" if ! echo "$response" | grep 'successfully' >/dev/null; then _err "Error" _err "$response" return 1 fi return 0 } # Usage: txtvalue # Used to remove the txt record after validation dns_dnshome_rm() { txtvalue=$2 DNSHOME_Subdomain="${DNSHOME_Subdomain:-$(_readdomainconf DNSHOME_Subdomain)}" DNSHOME_SubdomainPassword="${DNSHOME_SubdomainPassword:-$(_readdomainconf DNSHOME_SubdomainPassword)}" DNSHOME_Api="https://$DNSHOME_Subdomain:$DNSHOME_SubdomainPassword@www.dnshome.de/dyndns.php" if [ -z "$DNSHOME_Subdomain" ] || [ -z "$DNSHOME_SubdomainPassword" ]; then DNSHOME_Subdomain="" DNSHOME_SubdomainPassword="" _err "Please specify/export your dnsHome.de Subdomain and Password" return 1 fi _DNSHOME_rest POST "acme=rm&txt=$txtvalue" if ! echo "$response" | grep 'successfully' >/dev/null; then _err "Error" _err "$response" return 1 fi return 0 } #################### Private functions below ################################## _DNSHOME_rest() { method=$1 data="$2" _debug "$data" _debug data "$data" response="$(_post "$data" "$DNSHOME_Api" "" "$method")" if [ "$?" != "0" ]; then _err "error $data" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_dnsimple.sh000066400000000000000000000111701472032365200173260ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dnsimple_info='DNSimple.com Site: DNSimple.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsimple Options: DNSimple_OAUTH_TOKEN OAuth Token Issues: github.com/pho3nixf1re/acme.sh/issues ' DNSimple_API="https://api.dnsimple.com/v2" ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dnsimple_add() { fulldomain=$1 txtvalue=$2 if [ -z "$DNSimple_OAUTH_TOKEN" ]; then DNSimple_OAUTH_TOKEN="" _err "You have not set the dnsimple oauth token yet." _err "Please visit https://dnsimple.com/user to generate it." return 1 fi # save the oauth token for later _saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN" if ! _get_account_id; then _err "failed to retrive account id" return 1 fi if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _get_records "$_account_id" "$_domain" "$_sub_domain" _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." } # fulldomain dns_dnsimple_rm() { fulldomain=$1 if ! _get_account_id; then _err "failed to retrive account id" return 1 fi if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _get_records "$_account_id" "$_domain" "$_sub_domain" _extract_record_id "$_records" "$_sub_domain" if [ "$_record_id" ]; then 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 } #################### Private functions bellow ################################## # _acme-challenge.www.domain.com # returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 previous=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then # not valid return 1 fi if ! _dnsimple_rest GET "$_account_id/zones/$h"; then return 1 fi if _contains "$response" 'not found'; then _debug "$h not found" else _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$previous") _domain="$h" _debug _domain "$_domain" _debug _sub_domain "$_sub_domain" return 0 fi previous="$i" i=$(_math "$i" + 1) done return 1 } # returns _account_id _get_account_id() { _debug "retrive account id" if ! _dnsimple_rest GET "whoami"; then return 1 fi if _contains "$response" "\"account\":null"; then _err "no account associated with this token" return 1 fi if _contains "$response" "timeout"; then _err "timeout retrieving account id" return 1 fi _account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1) _debug _account_id "$_account_id" return 0 } # returns # _records # _records_count _get_records() { account_id=$1 domain=$2 sub_domain=$3 _debug "fetching txt records" _dnsimple_rest GET "$account_id/zones/$domain/records?per_page=5000&sort=id:desc" if ! _contains "$response" "\"id\":"; then _err "failed to retrieve records" return 1 fi _records_count=$(printf "%s" "$response" | _egrep_o "\"name\":\"$sub_domain\"" | wc -l | _egrep_o "[0-9]+") _records=$response _debug _records_count "$_records_count" } # returns _record_id _extract_record_id() { _record_id=$(printf "%s" "$_records" | _egrep_o "\"id\":[^,]*,\"zone_id\":\"[^,]*\",\"parent_id\":null,\"name\":\"$_sub_domain\"" | cut -d: -f2 | cut -d, -f1) _debug "_record_id" "$_record_id" } # returns response _dnsimple_rest() { method=$1 path="$2" data="$3" request_url="$DNSimple_API/$path" _debug "$path" export _H1="Accept: application/json" export _H2="Authorization: Bearer $DNSimple_OAUTH_TOKEN" if [ "$data" ] || [ "$method" = "DELETE" ]; then _H1="Content-Type: application/json" _debug data "$data" response="$(_post "$data" "$request_url" "" "$method")" else response="$(_get "$request_url" "" "" "$method")" fi if [ "$?" != "0" ]; then _err "error $request_url" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_dnsservices.sh000077500000000000000000000225431472032365200200540ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dnsservices_info='DNS.Services Site: DNS.Services Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsservices Options: DnsServices_Username Username DnsServices_Password Password Issues: github.com/acmesh-official/acme.sh/issues/4152 Author: Bjarke Bruun ' DNSServices_API=https://dns.services/api ######## Public functions ##################### #Usage: dns_dnsservices_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dnsservices_add() { fulldomain="$1" txtvalue="$2" _info "Using dns.services to create ACME DNS challenge" _debug2 add_fulldomain "$fulldomain" _debug2 add_txtvalue "$txtvalue" # Read username/password from environment or .acme.sh/accounts.conf DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}" DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}" if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then DnsServices_Username="" DnsServices_Password="" _err "You didn't specify dns.services api username and password yet." _err "Set environment variables DnsServices_Username and DnsServices_Password" return 1 fi # Setup GET/POST/DELETE headers _setup_headers #save the credentials to the account conf file. _saveaccountconf_mutable DnsServices_Username "$DnsServices_Username" _saveaccountconf_mutable DnsServices_Password "$DnsServices_Password" if ! _contains "$DnsServices_Username" "@"; then _err "It seems that the username variable DnsServices_Username has not been set/left blank" _err "or is not a valid email. Please correct and try again." return 1 fi if ! _get_root "${fulldomain}"; then _err "Invalid domain ${fulldomain}" return 1 fi if ! createRecord "$fulldomain" "${txtvalue}"; then _err "Error creating TXT record in domain $fulldomain in $rootZoneName" return 1 fi _debug2 challenge-created "Created $fulldomain" return 0 } #Usage: fulldomain txtvalue #Description: Remove the txt record after validation. dns_dnsservices_rm() { fulldomain="$1" txtvalue="$2" _info "Using dns.services to remove DNS record $fulldomain TXT $txtvalue" _debug rm_fulldomain "$fulldomain" _debug rm_txtvalue "$txtvalue" # Read username/password from environment or .acme.sh/accounts.conf DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}" DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}" if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then DnsServices_Username="" DnsServices_Password="" _err "You didn't specify dns.services api username and password yet." _err "Set environment variables DnsServices_Username and DnsServices_Password" return 1 fi # Setup GET/POST/DELETE headers _setup_headers if ! _get_root "${fulldomain}"; then _err "Invalid domain ${fulldomain}" return 1 fi _debug2 rm_rootDomainInfo "found root domain $rootZoneName for $fulldomain" if ! deleteRecord "${fulldomain}" "${txtvalue}"; then _err "Error removing record: $fulldomain TXT ${txtvalue}" return 1 fi return 0 } #################### Private functions below ################################## _setup_headers() { # Set up API Headers for _get() and _post() # The _add or _rm must have been called before to work if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then _err "Could not setup BASIC authentication headers, they are missing" return 1 fi DnsServiceCredentials="$(printf "%s" "$DnsServices_Username:$DnsServices_Password" | _base64)" export _H1="Authorization: Basic $DnsServiceCredentials" export _H2="Content-Type: application/json" # Just return if headers are set return 0 } _get_root() { domain="$1" _debug2 _get_root "Get the root domain of ${domain} for DNS API" # Setup _get() and _post() headers #_setup_headers result=$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/dns") result2="$(printf "%s\n" "$result" | tr '[' '\n' | grep '"name"')" result3="$(printf "%s\n" "$result2" | tr '}' '\n' | grep '"name"' | sed "s,^\,,,g" | sed "s,$,},g")" useResult="" _debug2 _get_root "Got the following root domain(s) $result" _debug2 _get_root "- JSON: $result" if [ "$(printf "%s\n" "$result" | tr '}' '\n' | grep -c '"name"')" -gt "1" ]; then checkMultiZones="true" _debug2 _get_root "- multiple zones found" else checkMultiZones="false" _debug2 _get_root "- single zone found" fi # Find/isolate the root zone to work with in createRecord() and deleteRecord() rootZone="" if [ "$checkMultiZones" = "true" ]; then #rootZone=$(for x in $(printf "%s" "${result3}" | tr ',' '\n' | sed -n 's/.*"name":"\(.*\)",.*/\1/p'); do if [ "$(echo "$domain" | grep "$x")" != "" ]; then echo "$x"; fi; done) rootZone=$(for x in $(printf "%s\n" "${result3}" | tr ',' '\n' | grep name | cut -d'"' -f4); do if [ "$(echo "$domain" | grep "$x")" != "" ]; then echo "$x"; fi; done) if [ "$rootZone" != "" ]; then _debug2 _rootZone "- root zone for $domain is $rootZone" else _err "Could not find root zone for $domain, is it correctly typed?" return 1 fi else rootZone=$(echo "$result" | tr '}' '\n' | _egrep_o '"name":"[^"]*' | cut -d'"' -f4) _debug2 _get_root "- only found 1 domain in API: $rootZone" fi if [ -z "$rootZone" ]; then _err "Could not find root domain for $domain - is it correctly typed?" return 1 fi # Make sure we use the correct API zone data useResult="$(printf "%s\n" "${result3}" tr ',' '\n' | grep "$rootZone")" _debug2 _useResult "useResult=$useResult" # Setup variables used by other functions to communicate with DNS.Services API #zoneInfo=$(printf "%s\n" "$useResult" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"name":")([^"]*)"(.*)$,\2,g') zoneInfo=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep '"name"' | cut -d'"' -f4) rootZoneName="$rootZone" subDomainName="$(printf "%s\n" "$domain" | sed "s,\.$rootZone,,g")" subDomainNameClean="$(printf "%s\n" "$domain" | sed "s,_acme-challenge.,,g")" rootZoneDomainID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep domain_id | cut -d'"' -f4) rootZoneServiceID=$(printf "%s\n" "$useResult" | tr ',' '\n' | grep service_id | cut -d'"' -f4) _debug2 _zoneInfo "Zone info from API : $zoneInfo" _debug2 _get_root "Root zone name : $rootZoneName" _debug2 _get_root "Root zone domain ID : $rootZoneDomainID" _debug2 _get_root "Root zone service ID: $rootZoneServiceID" _debug2 _get_root "Sub domain : $subDomainName" _debug _get_root "Found valid root domain $rootZone for $subDomainNameClean" return 0 } createRecord() { fulldomain="$1" txtvalue="$2" # Get root domain information - needed for DNS.Services API communication if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then _get_root "$fulldomain" fi if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then _err "Something happend - could not get the API zone information" return 1 fi _debug2 createRecord "CNAME TXT value is: $txtvalue" # Prepare data to send to API data="{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\", \"ttl\":\"10\"}" _debug2 createRecord "data to API: $data" result=$(_post "$data" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records" "" "POST") _debug2 createRecord "result from API: $result" if [ "$(echo "$result" | _egrep_o "\"success\":true")" = "" ]; then _err "Failed to create TXT record $fulldomain with content $txtvalue in zone $rootZoneName" _err "$result" return 1 fi _info "Record \"$fulldomain TXT $txtvalue\" has been created" return 0 } deleteRecord() { fulldomain="$1" txtvalue="$2" _log deleteRecord "Deleting $fulldomain TXT $txtvalue record" if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then _get_root "$fulldomain" fi result="$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID")" #recordInfo="$(echo "$result" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}")" #recordID="$(echo "$recordInfo" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"id":")([^"]*)"(.*)$,\2,g')" recordID="$(printf "%s\n" "$result" | tr '}' '\n' | grep -- "$txtvalue" | tr ',' '\n' | grep '"id"' | cut -d'"' -f4)" _debug2 _recordID "recordID used for deletion of record: $recordID" if [ -z "$recordID" ]; then _info "Record $fulldomain TXT $txtvalue not found or already deleted" return 0 else _debug2 deleteRecord "Found recordID=$recordID" fi _debug2 deleteRecord "DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" _log "curl DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" result="$(_H1="$_H1" _H2="$_H2" _post "" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" "" "DELETE")" _debug2 deleteRecord "API Delete result \"$result\"" _log "curl API Delete result \"$result\"" # Return OK regardless return 0 } acme.sh-3.1.0/dnsapi/dns_doapi.sh000077500000000000000000000034041472032365200166130ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_doapi_info='Domain-Offensive do.de Official LetsEncrypt API for do.de / Domain-Offensive. This API is also available to private customers/individuals. Site: do.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_doapi Options: DO_LETOKEN LetsEncrypt Token Issues: github.com/acmesh-official/acme.sh/issues/2057 ' DO_API="https://my.do.de/api/letsencrypt" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_doapi_add() { fulldomain=$1 txtvalue=$2 DO_LETOKEN="${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}" if [ -z "$DO_LETOKEN" ]; then DO_LETOKEN="" _err "You didn't configure a do.de API token yet." _err "Please set DO_LETOKEN and try again." return 1 fi _saveaccountconf_mutable DO_LETOKEN "$DO_LETOKEN" _info "Adding TXT record to ${fulldomain}" response="$(_get "$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&value=${txtvalue}")" if _contains "${response}" 'success'; then return 0 fi _err "Could not create resource record, check logs" _err "${response}" return 1 } dns_doapi_rm() { fulldomain=$1 DO_LETOKEN="${DO_LETOKEN:-$(_readaccountconf_mutable DO_LETOKEN)}" if [ -z "$DO_LETOKEN" ]; then DO_LETOKEN="" _err "You didn't configure a do.de API token yet." _err "Please set DO_LETOKEN and try again." return 1 fi _saveaccountconf_mutable DO_LETOKEN "$DO_LETOKEN" _info "Deleting resource record $fulldomain" response="$(_get "$DO_API?token=$DO_LETOKEN&domain=${fulldomain}&action=delete")" if _contains "${response}" 'success'; then return 0 fi _err "Could not delete resource record, check logs" _err "${response}" return 1 } acme.sh-3.1.0/dnsapi/dns_domeneshop.sh000066400000000000000000000104701472032365200176560ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_domeneshop_info='DomeneShop.no Site: DomeneShop.no Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_domeneshop Options: DOMENESHOP_Token Token DOMENESHOP_Secret Secret Issues: github.com/acmesh-official/acme.sh/issues/2457 ' DOMENESHOP_Api_Endpoint="https://api.domeneshop.no/v0" ##################### Public functions ##################### # Usage: dns_domeneshop_add # Example: dns_domeneshop_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_domeneshop_add() { fulldomain=$1 txtvalue=$2 # Get token and secret DOMENESHOP_Token="${DOMENESHOP_Token:-$(_readaccountconf_mutable DOMENESHOP_Token)}" DOMENESHOP_Secret="${DOMENESHOP_Secret:-$(_readaccountconf_mutable DOMENESHOP_Secret)}" if [ -z "$DOMENESHOP_Token" ] || [ -z "$DOMENESHOP_Secret" ]; then DOMENESHOP_Token="" DOMENESHOP_Secret="" _err "You need to spesify a Domeneshop/Domainnameshop API Token and Secret." return 1 fi # Save the api token and secret. _saveaccountconf_mutable DOMENESHOP_Token "$DOMENESHOP_Token" _saveaccountconf_mutable DOMENESHOP_Secret "$DOMENESHOP_Secret" # Get the domain name id if ! _get_domainid "$fulldomain"; then _err "Did not find domainname" return 1 fi # Create record _domeneshop_rest POST "domains/$_domainid/dns" "{\"type\":\"TXT\",\"host\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"ttl\":120}" } # Usage: dns_domeneshop_rm # Example: dns_domeneshop_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_domeneshop_rm() { fulldomain=$1 txtvalue=$2 # Get token and secret DOMENESHOP_Token="${DOMENESHOP_Token:-$(_readaccountconf_mutable DOMENESHOP_Token)}" DOMENESHOP_Secret="${DOMENESHOP_Secret:-$(_readaccountconf_mutable DOMENESHOP_Secret)}" if [ -z "$DOMENESHOP_Token" ] || [ -z "$DOMENESHOP_Secret" ]; then DOMENESHOP_Token="" DOMENESHOP_Secret="" _err "You need to spesify a Domeneshop/Domainnameshop API Token and Secret." return 1 fi # Get the domain name id if ! _get_domainid "$fulldomain"; then _err "Did not find domainname" return 1 fi # Find record if ! _get_recordid "$_domainid" "$_sub_domain" "$txtvalue"; then _err "Did not find dns record" return 1 fi # Remove record _domeneshop_rest DELETE "domains/$_domainid/dns/$_recordid" } ##################### Private functions ##################### _get_domainid() { domain=$1 # Get domains _domeneshop_rest GET "domains" if ! _contains "$response" "\"id\":"; then _err "failed to get domain names" return 1 fi i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug "h" "$h" if [ -z "$h" ]; then #not valid return 1 fi if _contains "$response" "\"$h\"" >/dev/null; then # We have found the domain name. _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h _domainid=$(printf "%s" "$response" | _egrep_o "[^{]*\"domain\":\"$_domain\"[^}]*" | _egrep_o "\"id\":[0-9]+" | cut -d : -f 2) return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _get_recordid() { domainid=$1 subdomain=$2 txtvalue=$3 # Get all dns records for the domainname _domeneshop_rest GET "domains/$domainid/dns" if ! _contains "$response" "\"id\":"; then _debug "No records in dns" return 1 fi if ! _contains "$response" "\"host\":\"$subdomain\""; then _debug "Record does not exist" return 1 fi # Get the id of the record in question _recordid=$(printf "%s" "$response" | _egrep_o "[^{]*\"host\":\"$subdomain\"[^}]*" | _egrep_o "[^{]*\"data\":\"$txtvalue\"[^}]*" | _egrep_o "\"id\":[0-9]+" | cut -d : -f 2) if [ -z "$_recordid" ]; then return 1 fi return 0 } _domeneshop_rest() { method=$1 endpoint=$2 data=$3 credentials=$(printf "%b" "$DOMENESHOP_Token:$DOMENESHOP_Secret" | _base64) export _H1="Authorization: Basic $credentials" export _H2="Content-Type: application/json" if [ "$method" != "GET" ]; then response="$(_post "$data" "$DOMENESHOP_Api_Endpoint/$endpoint" "" "$method")" else response="$(_get "$DOMENESHOP_Api_Endpoint/$endpoint")" fi if [ "$?" != "0" ]; then _err "error $endpoint" return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_dp.sh000077500000000000000000000075111472032365200161250ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dp_info='DNSPod.cn Site: DNSPod.cn Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dp Options: DP_Id Id DP_Key Key ' REST_API="https://dnsapi.cn" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dp_add() { fulldomain=$1 txtvalue=$2 DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}" DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}" if [ -z "$DP_Id" ] || [ -z "$DP_Key" ]; then DP_Id="" DP_Key="" _err "You don't specify dnspod api key and key id yet." _err "Please create you key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable DP_Id "$DP_Id" _saveaccountconf_mutable DP_Key "$DP_Key" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi add_record "$_domain" "$_sub_domain" "$txtvalue" } #fulldomain txtvalue dns_dp_rm() { fulldomain=$1 txtvalue=$2 DP_Id="${DP_Id:-$(_readaccountconf_mutable DP_Id)}" DP_Key="${DP_Key:-$(_readaccountconf_mutable DP_Key)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain"; then _err "Record.Lis error." return 1 fi if _contains "$response" 'No records'; then _info "Don't need to remove." return 0 fi record_id=$(echo "$response" | tr "{" "\n" | grep -- "$txtvalue" | grep '^"id"' | cut -d : -f 2 | cut -d '"' -f 2) _debug record_id "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id." return 1 fi if ! _rest POST "Record.Remove" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&record_id=$record_id"; then _err "Record.Remove error." return 1 fi _contains "$response" "successful" } #add the txt record. #usage: root sub txtvalue add_record() { root=$1 sub=$2 txtvalue=$3 fulldomain="$sub.$root" _info "Adding record" if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=%E9%BB%98%E8%AE%A4"; then return 1 fi _contains "$response" "successful" || _contains "$response" "Domain record already exists" } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain=$h"; then return 1 fi if _contains "$response" "successful"; then _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _debug _sub_domain "$_sub_domain" _domain="$h" _debug _domain "$_domain" return 0 fi return 1 fi p="$i" i=$(_math "$i" + 1) done return 1 } #Usage: method URI data _rest() { m="$1" ep="$2" data="$3" _debug "$ep" url="$REST_API/$ep" _debug url "$url" if [ "$m" = "GET" ]; then response="$(_get "$url" | tr -d '\r')" else _debug2 data "$data" response="$(_post "$data" "$url" | tr -d '\r')" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_dpi.sh000077500000000000000000000075451472032365200163050ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dpi_info='DNSPod.com Site: DNSPod.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dpi Options: DPI_Id Id DPI_Key Key ' REST_API="https://api.dnspod.com" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dpi_add() { fulldomain=$1 txtvalue=$2 DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}" DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}" if [ -z "$DPI_Id" ] || [ -z "$DPI_Key" ]; then DPI_Id="" DPI_Key="" _err "You don't specify dnspod api key and key id yet." _err "Please create you key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable DPI_Id "$DPI_Id" _saveaccountconf_mutable DPI_Key "$DPI_Key" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi add_record "$_domain" "$_sub_domain" "$txtvalue" } #fulldomain txtvalue dns_dpi_rm() { fulldomain=$1 txtvalue=$2 DPI_Id="${DPI_Id:-$(_readaccountconf_mutable DPI_Id)}" DPI_Key="${DPI_Key:-$(_readaccountconf_mutable DPI_Key)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi if ! _rest POST "Record.List" "login_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then _err "Record.Lis error." return 1 fi if _contains "$response" 'No records'; then _info "Don't need to remove." return 0 fi record_id=$(echo "$response" | tr "{" "\n" | grep -- "$txtvalue" | grep '^"id"' | cut -d : -f 2 | cut -d '"' -f 2) _debug record_id "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id." return 1 fi if ! _rest POST "Record.Remove" "login_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then _err "Record.Remove error." return 1 fi _contains "$response" "Operation successful" } #add the txt record. #usage: root sub txtvalue add_record() { root=$1 sub=$2 txtvalue=$3 fulldomain="$sub.$root" _info "Adding record" if ! _rest POST "Record.Create" "login_token=$DPI_Id,$DPI_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=default"; then return 1 fi _contains "$response" "Operation successful" || _contains "$response" "Domain record already exists" } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi if ! _rest POST "Domain.Info" "login_token=$DPI_Id,$DPI_Key&format=json&domain=$h"; then return 1 fi 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 _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _debug _sub_domain "$_sub_domain" _domain="$h" _debug _domain "$_domain" return 0 fi return 1 fi p="$i" i=$(_math "$i" + 1) done return 1 } #Usage: method URI data _rest() { m="$1" ep="$2" data="$3" _debug "$ep" url="$REST_API/$ep" _debug url "$url" if [ "$m" = "GET" ]; then response="$(_get "$url" | tr -d '\r')" else _debug2 data "$data" response="$(_post "$data" "$url" | tr -d '\r')" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_dreamhost.sh000066400000000000000000000044161472032365200175060ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dreamhost_info='DreamHost.com Site: DreamHost.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dreamhost Options: DH_API_KEY API Key Issues: github.com/RhinoLance/acme.sh Author: RhinoLance ' DH_API_ENDPOINT="https://api.dreamhost.com/" querystring="" ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dreamhost_add() { fulldomain=$1 txtvalue=$2 if ! validate "$fulldomain" "$txtvalue"; then return 1 fi querystring="key=$DH_API_KEY&cmd=dns-add_record&record=$fulldomain&type=TXT&value=$txtvalue" if ! submit "$querystring"; then return 1 fi return 0 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_dreamhost_rm() { fulldomain=$1 txtvalue=$2 if ! validate "$fulldomain" "$txtvalue"; then return 1 fi querystring="key=$DH_API_KEY&cmd=dns-remove_record&record=$fulldomain&type=TXT&value=$txtvalue" if ! submit "$querystring"; then return 1 fi return 0 } #################### Private functions below ################################## #send the command to the api endpoint. submit() { querystring=$1 url="$DH_API_ENDPOINT?$querystring" _debug url "$url" if ! response="$(_get "$url")"; then _err "Error <$1>" return 1 fi if [ -z "$2" ]; then message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" if [ -n "$message" ]; then _err "$message" return 1 fi fi _debug response "$response" return 0 } #check that we have a valid API Key validate() { fulldomain=$1 txtvalue=$2 _info "Using dreamhost" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" #retrieve the API key from the environment variable if it exists, otherwise look for a saved key. DH_API_KEY="${DH_API_KEY:-$(_readaccountconf_mutable DH_API_KEY)}" if [ -z "$DH_API_KEY" ]; then DH_API_KEY="" _err "You didn't specify the DreamHost api key yet (export DH_API_KEY=\"\")" _err "Please login to your control panel, create a key and try again." return 1 fi #save the api key to the account conf file. _saveaccountconf_mutable DH_API_KEY "$DH_API_KEY" } acme.sh-3.1.0/dnsapi/dns_duckdns.sh000077500000000000000000000075351472032365200171630ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_duckdns_info='DuckDNS.org Site: www.DuckDNS.org Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns Options: DuckDNS_Token API Token Author: RaidenII ' DuckDNS_API="https://www.duckdns.org/update" ######## Public functions ###################### #Usage: dns_duckdns_add _acme-challenge.domain.duckdns.org "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_duckdns_add() { fulldomain=$1 txtvalue=$2 DuckDNS_Token="${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}" if [ -z "$DuckDNS_Token" ]; then _err "You must export variable: DuckDNS_Token" _err "The token for your DuckDNS account is necessary." _err "You can look it up in your DuckDNS account." return 1 fi # Now save the credentials. _saveaccountconf_mutable DuckDNS_Token "$DuckDNS_Token" # Unfortunately, DuckDNS does not seems to support lookup domain through API # So I assume your credentials (which are your domain and token) are correct # If something goes wrong, we will get a KO response from DuckDNS if ! _duckdns_get_domain; then return 1 fi # Now add the TXT record to DuckDNS _info "Trying to add TXT record" if _duckdns_rest GET "domains=$_duckdns_domain&token=$DuckDNS_Token&txt=$txtvalue"; then if [ "$response" = "OK" ]; then _info "TXT record has been successfully added to your DuckDNS domain." _info "Note that all subdomains under this domain uses the same TXT record." return 0 else _err "Errors happened during adding the TXT record, response=$response" return 1 fi else _err "Errors happened during adding the TXT record." return 1 fi } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_duckdns_rm() { fulldomain=$1 txtvalue=$2 DuckDNS_Token="${DuckDNS_Token:-$(_readaccountconf_mutable DuckDNS_Token)}" if [ -z "$DuckDNS_Token" ]; then _err "You must export variable: DuckDNS_Token" _err "The token for your DuckDNS account is necessary." _err "You can look it up in your DuckDNS account." return 1 fi if ! _duckdns_get_domain; then return 1 fi # Now remove the TXT record from DuckDNS _info "Trying to remove TXT record" if _duckdns_rest GET "domains=$_duckdns_domain&token=$DuckDNS_Token&txt=&clear=true"; then if [ "$response" = "OK" ]; then _info "TXT record has been successfully removed from your DuckDNS domain." return 0 else _err "Errors happened during removing the TXT record, response=$response" return 1 fi else _err "Errors happened during removing the TXT record." return 1 fi } #################### Private functions below ################################## # fulldomain may be 'domain.duckdns.org' (if using --domain-alias) or '_acme-challenge.domain.duckdns.org' # either way, return 'domain'. (duckdns does not allow further subdomains and restricts domains to [a-z0-9-].) _duckdns_get_domain() { # We'll extract the domain/username from full domain _duckdns_domain="$(printf "%s" "$fulldomain" | _lower_case | _egrep_o '^(_acme-challenge\.)?([a-z0-9-]+\.)+duckdns\.org' | sed -n 's/^\([^.]\{1,\}\.\)*\([a-z0-9-]\{1,\}\)\.duckdns\.org$/\2/p;')" if [ -z "$_duckdns_domain" ]; then _err "Error extracting the domain." return 1 fi return 0 } #Usage: method URI _duckdns_rest() { method=$1 param="$2" _debug param "$param" url="$DuckDNS_API?$param" if [ -n "$DEBUG" ] && [ "$DEBUG" -gt 0 ]; then url="$url&verbose=true" fi _debug url "$url" # DuckDNS uses GET to update domain info if [ "$method" = "GET" ]; then response="$(_get "$url")" _debug2 response "$response" if [ -n "$DEBUG" ] && [ "$DEBUG" -gt 0 ] && _contains "$response" "UPDATED" && _contains "$response" "OK"; then response="OK" fi else _err "Unsupported method" return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_durabledns.sh000066400000000000000000000115311472032365200176370ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_durabledns_info='DurableDNS.com Site: DurableDNS.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_durabledns Options: DD_API_User API User DD_API_Key API Key Issues: github.com/acmesh-official/acme.sh/issues/2281 ' _DD_BASE="https://durabledns.com/services/dns" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_durabledns_add() { fulldomain=$1 txtvalue=$2 DD_API_User="${DD_API_User:-$(_readaccountconf_mutable DD_API_User)}" DD_API_Key="${DD_API_Key:-$(_readaccountconf_mutable DD_API_Key)}" if [ -z "$DD_API_User" ] || [ -z "$DD_API_Key" ]; then DD_API_User="" DD_API_Key="" _err "You didn't specify a durabledns api user or key yet." _err "You can get yours from here https://durabledns.com/dashboard/index.php" return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable DD_API_User "$DD_API_User" _saveaccountconf_mutable DD_API_Key "$DD_API_Key" _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" _dd_soap createRecord string zonename "$_domain." string name "$_sub_domain" string type "TXT" string data "$txtvalue" int aux 0 int ttl 10 string ddns_enabled N _contains "$response" "createRecordResponse" } dns_durabledns_rm() { fulldomain=$1 txtvalue=$2 DD_API_User="${DD_API_User:-$(_readaccountconf_mutable DD_API_User)}" DD_API_Key="${DD_API_Key:-$(_readaccountconf_mutable DD_API_Key)}" if [ -z "$DD_API_User" ] || [ -z "$DD_API_Key" ]; then DD_API_User="" DD_API_Key="" _err "You didn't specify a durabledns api user or key yet." _err "You can get yours from here https://durabledns.com/dashboard/index.php" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Find record id" if ! _dd_soap listRecords string zonename "$_domain."; then _err "can not listRecords" return 1 fi subtxt="$(echo "$txtvalue" | cut -c 1-30)" record="$(echo "$response" | sed 's//#/g' | tr '#' '\n' | grep ">$subtxt")" _debug record "$record" if [ -z "$record" ]; then _err "can not find record for txtvalue" "$txtvalue" _err "$response" return 1 fi recordid="$(echo "$record" | _egrep_o '[0-9]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" _debug recordid "$recordid" if [ -z "$recordid" ]; then _err "can not find record id" return 1 fi if ! _dd_soap deleteRecord string zonename "$_domain." int id "$recordid"; then _err "delete error" return 1 fi _contains "$response" "Success" } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 if ! _dd_soap "listZones"; then return 1 fi i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if _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 } #method _dd_soap() { _method="$1" shift _urn="${_method}wsdl" # put the parameters to xml body=" $DD_API_User $DD_API_Key " while [ "$1" ]; do _t="$1" shift _k="$1" shift _v="$1" shift body="$body<$_k xsi:type=\"xsd:$_t\">$_v" done body="$body" _debug2 "SOAP request ${body}" # build SOAP XML _xml=' '"$body"' ' _debug2 _xml "$_xml" # set SOAP headers _action="SOAPAction: \"urn:$_urn#$_method\"" _debug2 "_action" "$_action" export _H1="$_action" export _H2="Content-Type: text/xml; charset=utf-8" _url="$_DD_BASE/$_method.php" _debug "_url" "$_url" if ! response="$(_post "${_xml}" "${_url}")"; then _err "Error <$1>" return 1 fi _debug2 "response" "$response" response="$(echo "$response" | tr -d "\r\n" | _egrep_o ":${_method}Response .*:${_method}Response><")" _debug2 "response" "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_dyn.sh000066400000000000000000000175231472032365200163150ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dyn_info='Dyn.com Domains: dynect.net Site: Dyn.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dyn Options: DYN_Customer Customer DYN_Username API Username DYN_Password Secret Author: Gerd Naschenweng ' # Dyn Managed DNS API # https://help.dyn.com/dns-api-knowledge-base/ # # It is recommended to add a "Dyn Managed DNS" user specific for API access. # The "Zones & Records Permissions" required by this script are: # -- # RecordAdd # RecordUpdate # RecordDelete # RecordGet # ZoneGet # ZoneAddNode # ZoneRemoveNode # ZonePublish # -- DYN_API="https://api.dynect.net/REST" #REST_API ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "Challenge-code" dns_dyn_add() { fulldomain="$1" txtvalue="$2" DYN_Customer="${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}" DYN_Username="${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}" DYN_Password="${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}" if [ -z "$DYN_Customer" ] || [ -z "$DYN_Username" ] || [ -z "$DYN_Password" ]; then DYN_Customer="" DYN_Username="" DYN_Password="" _err "You must export variables: DYN_Customer, DYN_Username and DYN_Password" return 1 fi #save the config variables to the account conf file. _saveaccountconf_mutable DYN_Customer "$DYN_Customer" _saveaccountconf_mutable DYN_Username "$DYN_Username" _saveaccountconf_mutable DYN_Password "$DYN_Password" if ! _dyn_get_authtoken; then return 1 fi if [ -z "$_dyn_authtoken" ]; then _dyn_end_session return 1 fi if ! _dyn_get_zone; then _dyn_end_session return 1 fi if ! _dyn_add_record; then _dyn_end_session return 1 fi if ! _dyn_publish_zone; then _dyn_end_session return 1 fi _dyn_end_session return 0 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_dyn_rm() { fulldomain="$1" txtvalue="$2" DYN_Customer="${DYN_Customer:-$(_readaccountconf_mutable DYN_Customer)}" DYN_Username="${DYN_Username:-$(_readaccountconf_mutable DYN_Username)}" DYN_Password="${DYN_Password:-$(_readaccountconf_mutable DYN_Password)}" if [ -z "$DYN_Customer" ] || [ -z "$DYN_Username" ] || [ -z "$DYN_Password" ]; then DYN_Customer="" DYN_Username="" DYN_Password="" _err "You must export variables: DYN_Customer, DYN_Username and DYN_Password" return 1 fi if ! _dyn_get_authtoken; then return 1 fi if [ -z "$_dyn_authtoken" ]; then _dyn_end_session return 1 fi if ! _dyn_get_zone; then _dyn_end_session return 1 fi if ! _dyn_get_record_id; then _dyn_end_session return 1 fi if [ -z "$_dyn_record_id" ]; then _dyn_end_session return 1 fi if ! _dyn_rm_record; then _dyn_end_session return 1 fi if ! _dyn_publish_zone; then _dyn_end_session return 1 fi _dyn_end_session return 0 } #################### Private functions below ################################## #get Auth-Token _dyn_get_authtoken() { _info "Start Dyn API Session" data="{\"customer_name\":\"$DYN_Customer\", \"user_name\":\"$DYN_Username\", \"password\":\"$DYN_Password\"}" dyn_url="$DYN_API/Session/" method="POST" _debug data "$data" _debug dyn_url "$dyn_url" export _H1="Content-Type: application/json" response="$(_post "$data" "$dyn_url" "" "$method")" sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" _debug response "$response" _debug sessionstatus "$sessionstatus" if [ "$sessionstatus" = "success" ]; then _dyn_authtoken="$(printf "%s\n" "$response" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')" _info "Token received" _debug _dyn_authtoken "$_dyn_authtoken" return 0 fi _dyn_authtoken="" _err "get token failed" return 1 } #fulldomain=_acme-challenge.www.domain.com #returns # _dyn_zone=domain.com _dyn_get_zone() { i=2 while true; do domain="$(printf "%s" "$fulldomain" | cut -d . -f "$i-100")" if [ -z "$domain" ]; then break fi dyn_url="$DYN_API/Zone/$domain/" export _H1="Auth-Token: $_dyn_authtoken" export _H2="Content-Type: application/json" response="$(_get "$dyn_url" "" "")" sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" _debug dyn_url "$dyn_url" _debug response "$response" _debug sessionstatus "$sessionstatus" if [ "$sessionstatus" = "success" ]; then _dyn_zone="$domain" return 0 fi i=$(_math "$i" + 1) done _dyn_zone="" _err "get zone failed" return 1 } #add TXT record _dyn_add_record() { _info "Adding TXT record" data="{\"rdata\":{\"txtdata\":\"$txtvalue\"},\"ttl\":\"300\"}" dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/" method="POST" export _H1="Auth-Token: $_dyn_authtoken" export _H2="Content-Type: application/json" response="$(_post "$data" "$dyn_url" "" "$method")" sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" _debug response "$response" _debug sessionstatus "$sessionstatus" if [ "$sessionstatus" = "success" ]; then _info "TXT Record successfully added" return 0 fi _err "add TXT record failed" return 1 } #publish the zone _dyn_publish_zone() { _info "Publishing zone" data="{\"publish\":\"true\"}" dyn_url="$DYN_API/Zone/$_dyn_zone/" method="PUT" export _H1="Auth-Token: $_dyn_authtoken" export _H2="Content-Type: application/json" response="$(_post "$data" "$dyn_url" "" "$method")" sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" _debug response "$response" _debug sessionstatus "$sessionstatus" if [ "$sessionstatus" = "success" ]; then _info "Zone published" return 0 fi _err "publish zone failed" return 1 } #get record_id of TXT record so we can delete the record _dyn_get_record_id() { _info "Getting record_id of TXT record" dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/" export _H1="Auth-Token: $_dyn_authtoken" export _H2="Content-Type: application/json" response="$(_get "$dyn_url" "" "")" sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" _debug response "$response" _debug sessionstatus "$sessionstatus" if [ "$sessionstatus" = "success" ]; then _dyn_record_id="$(printf "%s\n" "$response" | _egrep_o "\"data\" *: *\[\"/REST/TXTRecord/$_dyn_zone/$fulldomain/[^\"]*" | _head_n 1 | sed "s#^\"data\" *: *\[\"/REST/TXTRecord/$_dyn_zone/$fulldomain/##")" _debug _dyn_record_id "$_dyn_record_id" return 0 fi _dyn_record_id="" _err "getting record_id failed" return 1 } #delete TXT record _dyn_rm_record() { _info "Deleting TXT record" dyn_url="$DYN_API/TXTRecord/$_dyn_zone/$fulldomain/$_dyn_record_id/" method="DELETE" _debug dyn_url "$dyn_url" export _H1="Auth-Token: $_dyn_authtoken" export _H2="Content-Type: application/json" response="$(_post "" "$dyn_url" "" "$method")" sessionstatus="$(printf "%s\n" "$response" | _egrep_o '"status" *: *"[^"]*' | _head_n 1 | sed 's#^"status" *: *"##')" _debug response "$response" _debug sessionstatus "$sessionstatus" if [ "$sessionstatus" = "success" ]; then _info "TXT record successfully deleted" return 0 fi _err "delete TXT record failed" return 1 } #logout _dyn_end_session() { _info "End Dyn API Session" dyn_url="$DYN_API/Session/" method="DELETE" _debug dyn_url "$dyn_url" export _H1="Auth-Token: $_dyn_authtoken" export _H2="Content-Type: application/json" response="$(_post "" "$dyn_url" "" "$method")" _debug response "$response" _dyn_authtoken="" return 0 } acme.sh-3.1.0/dnsapi/dns_dynu.sh000066400000000000000000000123231472032365200164730ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dynu_info='Dynu.com Site: Dynu.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dynu Options: Dynu_ClientId Client ID Dynu_Secret Secret Issues: github.com/shar0119/acme.sh Author: Dynu Systems Inc ' #Token Dynu_Token="" # #Endpoint Dynu_EndPoint="https://api.dynu.com/v2" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dynu_add() { fulldomain=$1 txtvalue=$2 if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then Dynu_ClientId="" Dynu_Secret="" _err "Dynu client id and secret is not specified." _err "Please create you API client id and secret and try again." return 1 fi #save the client id and secret to the account conf file. _saveaccountconf Dynu_ClientId "$Dynu_ClientId" _saveaccountconf Dynu_Secret "$Dynu_Secret" if [ -z "$Dynu_Token" ]; then _info "Getting Dynu token." if ! _dynu_authentication; then _err "Can not get token." fi fi _debug "Detect root zone" if ! _get_root "$fulldomain"; then _err "Invalid domain." return 1 fi _debug _node "$_node" _debug _domain_name "$_domain_name" _info "Creating TXT record." if ! _dynu_rest POST "dns/$dnsId/record" "{\"domainId\":\"$dnsId\",\"nodeName\":\"$_node\",\"recordType\":\"TXT\",\"textData\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then return 1 fi if ! _contains "$response" "200"; then _err "Could not add TXT record." return 1 fi return 0 } #Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dynu_rm() { fulldomain=$1 txtvalue=$2 if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then Dynu_ClientId="" Dynu_Secret="" _err "Dynu client id and secret is not specified." _err "Please create you API client id and secret and try again." return 1 fi #save the client id and secret to the account conf file. _saveaccountconf Dynu_ClientId "$Dynu_ClientId" _saveaccountconf Dynu_Secret "$Dynu_Secret" if [ -z "$Dynu_Token" ]; then _info "Getting Dynu token." if ! _dynu_authentication; then _err "Can not get token." fi fi _debug "Detect root zone." if ! _get_root "$fulldomain"; then _err "Invalid domain." return 1 fi _debug _node "$_node" _debug _domain_name "$_domain_name" _info "Checking for TXT record." if ! _get_recordid "$fulldomain" "$txtvalue"; then _err "Could not get TXT record id." return 1 fi if [ "$_dns_record_id" = "" ]; then _err "TXT record not found." return 1 fi _info "Removing TXT record." if ! _delete_txt_record "$_dns_record_id"; then _err "Could not remove TXT record $_dns_record_id." fi return 0 } ######## Private functions below ################################## #_acme-challenge.www.domain.com #returns # _node=_acme-challenge.www # _domain_name=domain.com _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _dynu_rest GET "dns/getroot/$h"; then return 1 fi if _contains "$response" "\"domainName\":\"$h\"" >/dev/null; then dnsId=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 2 | cut -d : -f 2) _domain_name=$h _node=$(printf "%s" "$domain" | cut -d . -f 1-"$p") return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _get_recordid() { fulldomain=$1 txtvalue=$2 if ! _dynu_rest GET "dns/$dnsId/record"; then return 1 fi if ! _contains "$response" "$txtvalue"; then _dns_record_id=0 return 0 fi _dns_record_id=$(printf "%s" "$response" | sed -e 's/[^{]*\({[^}]*}\)[^{]*/\1\n/g' | grep "\"textData\":\"$txtvalue\"" | sed -e 's/.*"id":\([^,]*\).*/\1/') return 0 } _delete_txt_record() { _dns_record_id=$1 if ! _dynu_rest DELETE "dns/$dnsId/record/$_dns_record_id"; then return 1 fi if ! _contains "$response" "200"; then return 1 fi return 0 } _dynu_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Authorization: Bearer $Dynu_Token" export _H2="Content-Type: application/json" if [ "$data" ] || [ "$m" = "DELETE" ]; then _debug data "$data" response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")" else _info "Getting $Dynu_EndPoint/$ep" response="$(_get "$Dynu_EndPoint/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } _dynu_authentication() { realm="$(printf "%s" "$Dynu_ClientId:$Dynu_Secret" | _base64)" export _H1="Authorization: Basic $realm" export _H2="Content-Type: application/json" response="$(_get "$Dynu_EndPoint/oauth2/token")" if [ "$?" != "0" ]; then _err "Authentication failed." return 1 fi if _contains "$response" "Authentication Exception"; then _err "Authentication failed." return 1 fi if _contains "$response" "access_token"; then Dynu_Token=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2) fi if _contains "$Dynu_Token" "null"; then Dynu_Token="" fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_dynv6.sh000066400000000000000000000215151472032365200165650ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_dynv6_info='DynV6.com Site: DynV6.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dynv6 Options: DYNV6_TOKEN REST API token. Get from https://DynV6.com/keys OptionsAlt: KEY Path to SSH private key file. E.g. "/root/.ssh/dynv6" Issues: github.com/acmesh-official/acme.sh/issues/2702 Author: StefanAbl ' dynv6_api="https://dynv6.com/api/v2" ######## Public functions ##################### # Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide #Usage: dns_dynv6_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dynv6_add() { fulldomain="$(echo "$1" | _lower_case)" txtvalue="$2" _info "Using dynv6 api" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _get_authentication if [ "$dynv6_token" ]; then _dns_dynv6_add_http return $? else _info "using key file $dynv6_keyfile" _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)" if ! _get_domain "$fulldomain" "$_your_hosts"; then _err "Host not found on your account" return 1 fi _debug "found host on your account" returnval="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts \""$_host"\" records set \""$_record"\" txt data \""$txtvalue"\")" _debug "Dynv6 returned this after record was added: $returnval" if _contains "$returnval" "created"; then return 0 elif _contains "$returnval" "updated"; then return 0 else _err "Something went wrong! it does not seem like the record was added successfully" return 1 fi fi } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_dynv6_rm() { fulldomain="$(echo "$1" | _lower_case)" txtvalue="$2" _info "Using dynv6 API" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _get_authentication if [ "$dynv6_token" ]; then _dns_dynv6_rm_http return $? else _info "using key file $dynv6_keyfile" _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)" if ! _get_domain "$fulldomain" "$_your_hosts"; then _err "Host not found on your account" return 1 fi _debug "found host on your account" _info "$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts "\"$_host\"" records del "\"$_record\"" txt)" return 0 fi } #################### Private functions below ################################## #Usage: No Input required #returns #dynv6_keyfile the path to the new key file that has been generated _generate_new_key() { dynv6_keyfile="$(eval echo ~"$USER")/.ssh/dynv6" _info "Path to key file used: $dynv6_keyfile" if [ ! -f "$dynv6_keyfile" ] && [ ! -f "$dynv6_keyfile.pub" ]; then _debug "generating key in $dynv6_keyfile and $dynv6_keyfile.pub" ssh-keygen -f "$dynv6_keyfile" -t ssh-ed25519 -N '' else _err "There is already a file in $dynv6_keyfile or $dynv6_keyfile.pub" return 1 fi } #Usage: _acme-challenge.www.example.dynv6.net "$_your_hosts" #where _your_hosts is the output of ssh -i ~/.ssh/dynv6.pub api@dynv6.com hosts #returns #_host= example.dynv6.net #_record=_acme-challenge.www #aborts if not a valid domain _get_domain() { #_your_hosts="$(ssh -i ~/.ssh/dynv6.pub api@dynv6.com hosts)" _full_domain="$1" _your_hosts="$2" _your_hosts="$(echo "$_your_hosts" | awk '/\./ {print $1}')" for l in $_your_hosts; do #echo "host: $l" if test "${_full_domain#*"$l"}" != "$_full_domain"; then _record=${_full_domain%."$l"} _host=$l _debug "The host is $_host and the record $_record" return 0 fi done _err "Either their is no such host on your dnyv6 account or it cannot be accessed with this key" return 1 } # Usage: No input required #returns #dynv6_keyfile path to the key that will be used _get_authentication() { dynv6_token="${DYNV6_TOKEN:-$(_readaccountconf_mutable dynv6_token)}" if [ "$dynv6_token" ]; then _debug "Found HTTP Token. Going to use the HTTP API and not the SSH API" if [ "$DYNV6_TOKEN" ]; then _saveaccountconf_mutable dynv6_token "$dynv6_token" fi else _debug "no HTTP token found. Looking for an SSH key" dynv6_keyfile="${dynv6_keyfile:-$(_readaccountconf_mutable dynv6_keyfile)}" _debug "Your key is $dynv6_keyfile" if [ -z "$dynv6_keyfile" ]; then if [ -z "$KEY" ]; then _err "You did not specify a key to use with dynv6" _info "Creating new dynv6 API key to add to dynv6.com" _generate_new_key _info "Please add this key to dynv6.com $(cat "$dynv6_keyfile.pub")" _info "Hit Enter to continue" read -r _ #save the credentials to the account conf file. else dynv6_keyfile="$KEY" fi _saveaccountconf_mutable dynv6_keyfile "$dynv6_keyfile" fi fi } _dns_dynv6_add_http() { _debug "Got HTTP token form _get_authentication method. Going to use the HTTP API" if ! _get_zone_id "$fulldomain"; then _err "Could not find a matching zone for $fulldomain. Maybe your HTTP Token is not authorized to access the zone" return 1 fi _get_zone_name "$_zone_id" record=${fulldomain%%."$_zone_name"} _set_record TXT "$record" "$txtvalue" if _contains "$response" "$txtvalue"; then _info "Successfully added record" return 0 else _err "Something went wrong while adding the record" return 1 fi } _dns_dynv6_rm_http() { _debug "Got HTTP token form _get_authentication method. Going to use the HTTP API" if ! _get_zone_id "$fulldomain"; then _err "Could not find a matching zone for $fulldomain. Maybe your HTTP Token is not authorized to access the zone" return 1 fi _get_zone_name "$_zone_id" record=${fulldomain%%."$_zone_name"} _get_record_id "$_zone_id" "$record" "$txtvalue" _del_record "$_zone_id" "$_record_id" if [ -z "$response" ]; then _info "Successfully deleted record" return 0 else _err "Something went wrong while deleting the record" return 1 fi } #get the zoneid for a specifc record or zone #usage: _get_zone_id §record #where $record is the record to get the id for #returns _zone_id the id of the zone _get_zone_id() { record="$1" _debug "getting zone id for $record" _dynv6_rest GET zones zones="$(echo "$response" | tr '}' '\n' | tr ',' '\n' | grep name | sed 's/\[//g' | tr -d '{' | tr -d '"')" #echo $zones selected="" for z in $zones; do z="${z#name:}" _debug zone: "$z" if _contains "$record" "$z"; then _debug "$z found in $record" selected="$z" fi done if [ -z "$selected" ]; then _err "no zone found" return 1 fi zone_id="$(echo "$response" | tr '}' '\n' | grep "$selected" | tr ',' '\n' | grep '"id":' | tr -d '"')" _zone_id="${zone_id#id:}" _debug "zone id: $_zone_id" } _get_zone_name() { _zone_id="$1" _dynv6_rest GET zones/"$_zone_id" _zone_name="$(echo "$response" | tr ',' '\n' | tr -d '{' | grep name | tr -d '"')" _zone_name="${_zone_name#name:}" } #usaage _get_record_id $zone_id $record # where zone_id is thevalue returned by _get_zone_id # and record ist in the form _acme.www for an fqdn of _acme.www.example.com # returns _record_id _get_record_id() { _zone_id="$1" record="$2" value="$3" _dynv6_rest GET "zones/$_zone_id/records" if ! _get_record_id_from_response "$response"; then _err "no such record $record found in zone $_zone_id" return 1 fi } _get_record_id_from_response() { response="$1" _record_id="$(echo "$response" | tr '}' '\n' | grep "\"name\":\"$record\"" | grep "\"data\":\"$value\"" | tr ',' '\n' | grep id | tr -d '"' | tr -d 'id:')" #_record_id="${_record_id#id:}" if [ -z "$_record_id" ]; then _err "no such record: $record found in zone $_zone_id" return 1 fi _debug "record id: $_record_id" return 0 } #usage: _set_record TXT _acme_challenge.www longvalue 12345678 #zone id is optional can also be set as vairable bevor calling this method _set_record() { type="$1" record="$2" value="$3" if [ "$4" ]; then _zone_id="$4" fi data="{\"name\": \"$record\", \"data\": \"$value\", \"type\": \"$type\"}" #data='{ "name": "acme.test.thorn.dynv6.net", "type": "A", "data": "192.168.0.1"}' echo "$data" #"{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}" _dynv6_rest POST "zones/$_zone_id/records" "$data" } _del_record() { _zone_id=$1 _record_id=$2 _dynv6_rest DELETE zones/"$_zone_id"/records/"$_record_id" } _dynv6_rest() { m=$1 #method GET,POST,DELETE or PUT ep="$2" #the endpoint data="$3" _debug "$ep" token_trimmed=$(echo "$dynv6_token" | tr -d '"') export _H1="Authorization: Bearer $token_trimmed" export _H2="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$dynv6_api/$ep" "" "$m")" else response="$(_get "$dynv6_api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_easydns.sh000066400000000000000000000106141472032365200171630ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_easydns_info='easyDNS.net Site: easyDNS.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_easydns Options: EASYDNS_Token API Token EASYDNS_Key API Key Issues: github.com/acmesh-official/acme.sh/issues/2647 Author: Neilpang, wurzelpanzer ' # API Documentation: https://sandbox.rest.easydns.net:3001/ #################### Public functions ################# #EASYDNS_Key="xxxxxxxxxxxxxxxxxxxxxxxx" #EASYDNS_Token="xxxxxxxxxxxxxxxxxxxxxxxx" EASYDNS_Api="https://rest.easydns.net" #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_easydns_add() { fulldomain=$1 txtvalue=$2 EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}" EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}" if [ -z "$EASYDNS_Token" ] || [ -z "$EASYDNS_Key" ]; then _err "You didn't specify an easydns.net token or api key. Signup at https://cp.easydns.com/manage/security/api/signup.php" return 1 else _saveaccountconf_mutable EASYDNS_Token "$EASYDNS_Token" _saveaccountconf_mutable EASYDNS_Key "$EASYDNS_Key" fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _EASYDNS_rest GET "zones/records/all/${_domain}/search/${_sub_domain}" if ! printf "%s" "$response" | grep \"status\":200 >/dev/null; then _err "Error" return 1 fi _info "Adding record" if _EASYDNS_rest PUT "zones/records/add/$_domain/TXT" "{\"host\":\"$_sub_domain\",\"rdata\":\"$txtvalue\"}"; then if _contains "$response" "\"status\":201"; then _info "Added, OK" return 0 elif _contains "$response" "Record already exists"; then _info "Already exists, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } dns_easydns_rm() { fulldomain=$1 txtvalue=$2 EASYDNS_Token="${EASYDNS_Token:-$(_readaccountconf_mutable EASYDNS_Token)}" EASYDNS_Key="${EASYDNS_Key:-$(_readaccountconf_mutable EASYDNS_Key)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _EASYDNS_rest GET "zones/records/all/${_domain}/search/${_sub_domain}" if ! printf "%s" "$response" | grep \"status\":200 >/dev/null; then _err "Error" return 1 fi count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _EASYDNS_rest DELETE "zones/records/$_domain/$record_id"; then _err "Delete record error." return 1 fi _contains "$response" "\"status\":200" fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _EASYDNS_rest GET "zones/records/all/$h"; then return 1 fi if _contains "$response" "\"status\":200"; 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 } _EASYDNS_rest() { m=$1 ep="$2" data="$3" _debug "$ep" basicauth=$(printf "%s" "$EASYDNS_Token":"$EASYDNS_Key" | _base64) export _H1="accept: application/json" if [ "$basicauth" ]; then export _H2="Authorization: Basic $basicauth" fi if [ "$m" != "GET" ]; then export _H3="Content-Type: application/json" _debug data "$data" response="$(_post "$data" "$EASYDNS_Api/$ep" "" "$m")" else response="$(_get "$EASYDNS_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_edgedns.sh000077500000000000000000000365311472032365200171370ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_edgedns_info='Akamai.com Edge DNS Site: techdocs.Akamai.com/edge-dns/reference/edge-dns-api Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgedns Options: Specify individual credentials AKAMAI_HOST Host AKAMAI_ACCESS_TOKEN Access token AKAMAI_CLIENT_TOKEN Client token AKAMAI_CLIENT_SECRET Client secret Issues: github.com/acmesh-official/acme.sh/issues/3157 ' # 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 # *** TBD. NOT IMPLEMENTED YET *** # Specify Edgegrid credentials file and section. # AKAMAI_EDGERC Edge RC. Full file path # AKAMAI_EDGERC_SECTION Edge RC Section. E.g. "default" 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 AKAMAI_ACCESS_TOKEN="${AKAMAI_ACCESS_TOKEN:-$(_readaccountconf_mutable AKAMAI_ACCESS_TOKEN)}" 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 AKAMAI_CLIENT_TOKEN="${AKAMAI_CLIENT_TOKEN:-$(_readaccountconf_mutable AKAMAI_CLIENT_TOKEN)}" 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 AKAMAI_HOST="${AKAMAI_HOST:-$(_readaccountconf_mutable AKAMAI_HOST)}" 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 AKAMAI_CLIENT_SECRET="${AKAMAI_CLIENT_SECRET:-$(_readaccountconf_mutable AKAMAI_CLIENT_SECRET)}" 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 #} acme.sh-3.1.0/dnsapi/dns_euserv.sh000066400000000000000000000223331472032365200170270ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_euserv_info='EUserv.com Domains: EUserv.eu Site: EUserv.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_euserv Options: EUSERV_Username Username EUSERV_Password Password Author: Michael Brueckner ' EUSERV_Api="https://api.euserv.net" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_euserv_add() { fulldomain="$(echo "$1" | _lower_case)" txtvalue=$2 EUSERV_Username="${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}" EUSERV_Password="${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}" if [ -z "$EUSERV_Username" ] || [ -z "$EUSERV_Password" ]; then EUSERV_Username="" EUSERV_Password="" _err "You don't specify euserv user and password yet." _err "Please create your key and try again." return 1 fi #save the user and email to the account conf file. _saveaccountconf_mutable EUSERV_Username "$EUSERV_Username" _saveaccountconf_mutable EUSERV_Password "$EUSERV_Password" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug "_sub_domain" "$_sub_domain" _debug "_domain" "$_domain" _info "Adding record" if ! _euserv_add_record "$_domain" "$_sub_domain" "$txtvalue"; then return 1 fi } #fulldomain txtvalue dns_euserv_rm() { fulldomain="$(echo "$1" | _lower_case)" txtvalue=$2 EUSERV_Username="${EUSERV_Username:-$(_readaccountconf_mutable EUSERV_Username)}" EUSERV_Password="${EUSERV_Password:-$(_readaccountconf_mutable EUSERV_Password)}" if [ -z "$EUSERV_Username" ] || [ -z "$EUSERV_Password" ]; then EUSERV_Username="" EUSERV_Password="" _err "You don't specify euserv user and password yet." _err "Please create your key and try again." return 1 fi #save the user and email to the account conf file. _saveaccountconf_mutable EUSERV_Username "$EUSERV_Username" _saveaccountconf_mutable EUSERV_Password "$EUSERV_Password" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug "_sub_domain" "$_sub_domain" _debug "_domain" "$_domain" _debug "Getting txt records" xml_content=$(printf ' domain.dns_get_active_records login %s password %s domain_id %s ' "$EUSERV_Username" "$EUSERV_Password" "$_euserv_domain_id") export _H1="Content-Type: text/xml" response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")" if ! _contains "$response" "status100"; then _err "Error could not get txt records" _debug "xml_content" "$xml_content" _debug "response" "$response" return 1 fi if ! echo "$response" | grep '>dns_record_content<.*>'"$txtvalue"'<' >/dev/null; then _info "Do not need to delete record" else # find XML block where txtvalue is in. The record_id is allways prior this line! _endLine=$(echo "$response" | grep -n '>dns_record_content<.*>'"$txtvalue"'<' | cut -d ':' -f 1) # record_id is the last Tag with a number before the row _endLine, identified by _record_id=$(echo "$response" | sed -n '1,'"$_endLine"'p' | grep '' | _tail_n 1 | sed 's/.*\([0-9]*\)<\/name>.*/\1/') _info "Deleting record" _euserv_delete_record "$_record_id" fi } #################### Private functions below ################################## _get_root() { domain=$1 _debug "get root" # Just to read the domain_orders once domain=$1 i=2 p=1 if ! _euserv_get_domain_orders; then return 1 fi # Get saved response with domain_orders response="$_euserv_domain_orders" while true; do h=$(echo "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if _contains "$response" "$h"; then _sub_domain=$(echo "$domain" | cut -d . -f 1-"$p") _domain="$h" if ! _euserv_get_domain_id "$_domain"; then _err "invalid domain" return 1 fi return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _euserv_get_domain_orders() { # returns: _euserv_domain_orders _debug "get domain_orders" xml_content=$(printf ' domain.get_domain_orders login %s password %s ' "$EUSERV_Username" "$EUSERV_Password") export _H1="Content-Type: text/xml" response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")" if ! _contains "$response" "status100"; then _err "Error could not get domain orders" _debug "xml_content" "$xml_content" _debug "response" "$response" return 1 fi # save response to reduce API calls _euserv_domain_orders="$response" return 0 } _euserv_get_domain_id() { # returns: _euserv_domain_id domain=$1 _debug "get domain_id" # find line where the domain name is within the $response _startLine=$(echo "$_euserv_domain_orders" | grep -n '>domain_name<.*>'"$domain"'<' | cut -d ':' -f 1) # next occurency of domain_id after the domain_name is the correct one _euserv_domain_id=$(echo "$_euserv_domain_orders" | sed -n "$_startLine"',$p' | grep '>domain_id<' | _head_n 1 | sed 's/.*\([0-9]*\)<\/i4>.*/\1/') if [ -z "$_euserv_domain_id" ]; then _err "Could not find domain_id for domain $domain" _debug "_euserv_domain_orders" "$_euserv_domain_orders" return 1 fi return 0 } _euserv_delete_record() { record_id=$1 xml_content=$(printf ' domain.dns_delete_record login %s password %s dns_record_id %s ' "$EUSERV_Username" "$EUSERV_Password" "$record_id") export _H1="Content-Type: text/xml" response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")" if ! _contains "$response" "status100"; then _err "Error deleting record" _debug "xml_content" "$xml_content" _debug "response" "$response" return 1 fi return 0 } _euserv_add_record() { domain=$1 sub_domain=$2 txtval=$3 xml_content=$(printf ' domain.dns_create_record login %s password %s domain_id %s dns_record_subdomain %s dns_record_type TXT dns_record_value %s dns_record_ttl 300 ' "$EUSERV_Username" "$EUSERV_Password" "$_euserv_domain_id" "$sub_domain" "$txtval") export _H1="Content-Type: text/xml" response="$(_post "$xml_content" "$EUSERV_Api" "" "POST")" if ! _contains "$response" "status100"; then _err "Error could not create record" _debug "xml_content" "$xml_content" _debug "response" "$response" return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_exoscale.sh000077500000000000000000000106341472032365200173250ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_exoscale_info='Exoscale.com Site: Exoscale.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_exoscale Options: EXOSCALE_API_KEY API Key EXOSCALE_SECRET_KEY API Secret key ' EXOSCALE_API=https://api.exoscale.com/dns/v1 ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record dns_exoscale_add() { fulldomain=$1 txtvalue=$2 if ! _checkAuth; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 fi fi _err "Add txt record error." return 1 } # Usage: fulldomain txtvalue # Used to remove the txt record after validation dns_exoscale_rm() { fulldomain=$1 txtvalue=$2 if ! _checkAuth; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token" if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then _record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") fi if [ -z "$_record_id" ]; then _err "Can not get record id to remove." return 1 fi _debug "Deleting record $_record_id" if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then _err "Delete record error." return 1 fi return 0 } #################### Private functions below ################################## _checkAuth() { EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}" EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}" if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then EXOSCALE_API_KEY="" EXOSCALE_SECRET_KEY="" _err "You don't specify Exoscale application key and application secret yet." _err "Please create you key and try again." return 1 fi _saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY" _saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY" return 0 } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg # _domain_token=sdjkglgdfewsdfg _get_root() { if ! _exoscale_rest GET "domains"; then return 1 fi domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") _domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_token" ] && [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } # returns response _exoscale_rest() { method=$1 path="$2" data="$3" token="$4" request_url="$EXOSCALE_API/$path" _debug "$path" export _H1="Accept: application/json" if [ "$token" ]; then export _H2="X-DNS-Domain-Token: $token" else export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY" fi if [ "$data" ] || [ "$method" = "DELETE" ]; then export _H3="Content-Type: application/json" _debug data "$data" response="$(_post "$data" "$request_url" "" "$method")" else response="$(_get "$request_url" "" "" "$method")" fi if [ "$?" != "0" ]; then _err "error $request_url" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_fornex.sh000066400000000000000000000064531472032365200170240ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_fornex_info='Fornex.com Site: Fornex.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_fornex Options: FORNEX_API_KEY API Key Issues: github.com/acmesh-official/acme.sh/issues/3998 Author: Timur Umarov ' FORNEX_API_URL="https://fornex.com/api" ######## Public functions ##################### #Usage: dns_fornex_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_fornex_add() { fulldomain=$1 txtvalue=$2 if ! _Fornex_API; then return 1 fi if ! _get_root "$fulldomain"; then _err "Unable to determine root domain" return 1 else _debug _domain "$_domain" fi _info "Adding record" if _rest POST "dns/domain/$_domain/entry_set/" "{\"host\" : \"${fulldomain}\" , \"type\" : \"TXT\" , \"value\" : \"${txtvalue}\" , \"ttl\" : null}"; then _debug _response "$response" _info "Added, OK" return 0 fi _err "Add txt record error." return 1 } #Usage: dns_fornex_rm _acme-challenge.www.domain.com dns_fornex_rm() { fulldomain=$1 txtvalue=$2 if ! _Fornex_API; then return 1 fi if ! _get_root "$fulldomain"; then _err "Unable to determine root domain" return 1 else _debug _domain "$_domain" fi _debug "Getting txt records" _rest GET "dns/domain/$_domain/entry_set?type=TXT&q=$fulldomain" if ! _contains "$response" "$txtvalue"; then _err "Txt record not found" return 1 fi _record_id="$(echo "$response" | _egrep_o "\{[^\{]*\"value\"*:*\"$txtvalue\"[^\}]*\}" | sed -n -e 's#.*"id":\([0-9]*\).*#\1#p')" _debug "_record_id" "$_record_id" if [ -z "$_record_id" ]; then _err "can not find _record_id" return 1 fi if ! _rest DELETE "dns/domain/$_domain/entry_set/$_record_id/"; then _err "Delete record error." return 1 fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _rest GET "dns/domain/"; then return 1 fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain=$h return 0 else _debug "$h not found" fi i=$(_math "$i" + 1) done return 1 } _Fornex_API() { FORNEX_API_KEY="${FORNEX_API_KEY:-$(_readaccountconf_mutable FORNEX_API_KEY)}" if [ -z "$FORNEX_API_KEY" ]; then FORNEX_API_KEY="" _err "You didn't specify the Fornex API key yet." _err "Please create your key and try again." return 1 fi _saveaccountconf_mutable FORNEX_API_KEY "$FORNEX_API_KEY" } #method method action data _rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Authorization: Api-Key $FORNEX_API_KEY" export _H2="Content-Type: application/json" export _H3="Accept: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$FORNEX_API_URL/$ep" "" "$m")" else response="$(_get "$FORNEX_API_URL/$ep" | _normalizeJson)" fi _ret="$?" if [ "$_ret" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_freedns.sh000077500000000000000000000300621472032365200171450ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_freedns_info='FreeDNS Site: FreeDNS.afraid.org Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_freedns Options: FREEDNS_User Username FREEDNS_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2305 Author: David Kerr ' ######## Public functions ##################### # Export FreeDNS userid and password in following variables... # FREEDNS_User=username # FREEDNS_Password=password # login cookie is saved in acme account config file so userid / pw # need to be set only when changed. #Usage: dns_freedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_freedns_add() { fulldomain="$1" txtvalue="$2" _info "Add TXT record using FreeDNS" _debug "fulldomain: $fulldomain" _debug "txtvalue: $txtvalue" if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then FREEDNS_User="" FREEDNS_Password="" if [ -z "$FREEDNS_COOKIE" ]; then _err "You did not specify the FreeDNS username and password yet." _err "Please export as FREEDNS_User / FREEDNS_Password and try again." return 1 fi using_cached_cookies="true" else FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")" if [ -z "$FREEDNS_COOKIE" ]; then return 1 fi using_cached_cookies="false" fi _debug "FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)" _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE" # We may have to cycle through the domain name to find the # TLD that we own... i=1 wmax="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" while [ "$i" -lt "$wmax" ]; do # split our full domain name into two parts... sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")" i="$(_math "$i" + 1)" top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)" _debug "sub_domain: $sub_domain" _debug "top_domain: $top_domain" DNSdomainid="$(_freedns_domain_id "$top_domain")" if [ "$?" = "0" ]; then _info "Domain $top_domain found at FreeDNS, domain_id $DNSdomainid" break else _info "Domain $top_domain not found at FreeDNS, try with next level of TLD" fi done if [ -z "$DNSdomainid" ]; then # If domain ID is empty then something went wrong (top level # domain not found at FreeDNS). _err "Domain $top_domain not found at FreeDNS" return 1 fi # 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 #Remove the txt record after validation. dns_freedns_rm() { fulldomain="$1" txtvalue="$2" _info "Delete TXT record using FreeDNS" _debug "fulldomain: $fulldomain" _debug "txtvalue: $txtvalue" # Need to read cookie from conf file again in case new value set # during login to FreeDNS when TXT record was created. FREEDNS_COOKIE="$(_readaccountconf "FREEDNS_COOKIE")" _debug "FreeDNS login cookies: $FREEDNS_COOKIE" TXTdataid="$(_freedns_data_id "$fulldomain" "TXT")" if [ "$?" != "0" ]; then _info "Cannot delete TXT record for $fulldomain, record does not exist at FreeDNS" return 1 fi _debug "Data ID's found, $TXTdataid" # now we have one (or more) TXT record data ID's. Load the page # for that record and search for the record txt value. If match # then we can delete it. lines="$(echo "$TXTdataid" | wc -l)" _debug "Found $lines TXT data records for $fulldomain" i=0 while [ "$i" -lt "$lines" ]; do i="$(_math "$i" + 1)" dataid="$(echo "$TXTdataid" | sed -n "${i}p")" _debug "$dataid" htmlpage="$(_freedns_retrieve_data_page "$FREEDNS_COOKIE" "$dataid")" if [ "$?" != "0" ]; then if [ "$using_cached_cookies" = "true" ]; then _err "Has your FreeDNS username and password changed? If so..." _err "Please export as FREEDNS_User / FREEDNS_Password and try again." fi return 1 fi echo "$htmlpage" | grep "value=\""$txtvalue"\"" >/dev/null if [ "$?" = "0" ]; then # Found a match... delete the record and return _info "Deleting TXT record for $fulldomain, $txtvalue" _freedns_delete_txt_record "$FREEDNS_COOKIE" "$dataid" return $? fi done # If we get this far we did not find a match # Not necessarily an error, but log anyway. _info "Cannot delete TXT record for $fulldomain, $txtvalue. Does not exist at FreeDNS" return 0 } #################### Private functions below ################################## # usage: _freedns_login username password # print string "cookie=value" etc. # returns 0 success _freedns_login() { export _H1="Accept-Language:en-US" username="$1" password="$2" url="https://freedns.afraid.org/zc.php?step=2" _debug "Login to FreeDNS as user $username" htmlpage="$(_post "username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" "$url")" if [ "$?" != "0" ]; then _err "FreeDNS login failed for user $username bad RC from _post" return 1 fi cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)" # if cookies is not empty then logon successful if [ -z "$cookies" ]; then _debug3 "htmlpage: $htmlpage" _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file" return 1 fi printf "%s" "$cookies" return 0 } # usage _freedns_retrieve_subdomain_page login_cookies # echo page retrieved (html) # returns 0 success _freedns_retrieve_subdomain_page() { export _H1="Cookie:$1" export _H2="Accept-Language:en-US" url="https://freedns.afraid.org/subdomain/" _debug "Retrieve subdomain page from FreeDNS" htmlpage="$(_get "$url")" if [ "$?" != "0" ]; then _err "FreeDNS retrieve subdomains failed bad RC from _get" return 1 elif [ -z "$htmlpage" ]; then _err "FreeDNS returned empty subdomain page" return 1 fi _debug3 "htmlpage: $htmlpage" printf "%s" "$htmlpage" return 0 } # usage _freedns_retrieve_data_page login_cookies data_id # echo page retrieved (html) # returns 0 success _freedns_retrieve_data_page() { export _H1="Cookie:$1" export _H2="Accept-Language:en-US" data_id="$2" url="https://freedns.afraid.org/subdomain/edit.php?data_id=$2" _debug "Retrieve data page for ID $data_id from FreeDNS" htmlpage="$(_get "$url")" if [ "$?" != "0" ]; then _err "FreeDNS retrieve data page failed bad RC from _get" return 1 elif [ -z "$htmlpage" ]; then _err "FreeDNS returned empty data page" return 1 fi _debug3 "htmlpage: $htmlpage" printf "%s" "$htmlpage" return 0 } # usage _freedns_add_txt_record login_cookies domain_id subdomain value # returns 0 success _freedns_add_txt_record() { export _H1="Cookie:$1" export _H2="Accept-Language:en-US" domain_id="$2" subdomain="$3" value="$(printf '%s' "$4" | _url_encode)" 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")" if [ "$?" != "0" ]; then _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" return 1 elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then _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 _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 _debug3 "htmlpage: $htmlpage" _info "Added acme challenge TXT record for $fulldomain at FreeDNS" return 0 } # usage _freedns_delete_txt_record login_cookies data_id # returns 0 success _freedns_delete_txt_record() { export _H1="Cookie:$1" export _H2="Accept-Language:en-US" data_id="$2" url="https://freedns.afraid.org/subdomain/delete2.php" htmlheader="$(_get "$url?data_id%5B%5D=$data_id&submit=delete+selected" "onlyheader")" if [ "$?" != "0" ]; then _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" return 1 elif ! _contains "$htmlheader" "200 OK"; then _debug2 "htmlheader: $htmlheader" _err "FreeDNS failed to delete TXT record $data_id" return 1 fi _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS" return 0 } # usage _freedns_domain_id domain_name # echo the domain_id if found # return 0 success _freedns_domain_id() { # Start by escaping the dots in the domain name search_domain="$(echo "$1" | sed 's/\./\\./g')" # 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 # returns the correct subdomain page. So, we will try twice to # load the page and obtain our domain ID 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 _err "Has your FreeDNS username and password changed? If so..." _err "Please export as FREEDNS_User / FREEDNS_Password and try again." fi return 1 fi domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' | grep "$search_domain\|$search_domain(.*)" | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' | cut -d = -f 2)" # The above beauty extracts domain ID from the html page... # strip out all blank space and new lines. Then insert newlines # before each table row # search for the domain within each row (which may or may not have # a text string in brackets (.*) after it. # And finally extract the domain ID. if [ -n "$domain_id" ]; then printf "%s" "$domain_id" return 0 fi _debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)" done _debug "Domain $search_domain not found after retry" return 1 } # usage _freedns_data_id domain_name record_type # echo the data_id(s) if found # return 0 success _freedns_data_id() { # Start by escaping the dots in the domain name search_domain="$(echo "$1" | sed 's/\./\\./g')" record_type="$2" # 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 # returns the correct subdomain page. So, we will try twice to # load the page and obtain our domain ID 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 _err "Has your FreeDNS username and password changed? If so..." _err "Please export as FREEDNS_User / FREEDNS_Password and try again." fi return 1 fi data_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' | grep "$record_type" | grep "$search_domain" | sed -n 's/.*\(edit\.php?data_id=[0-9a-zA-Z]*\).*/\1/p' | cut -d = -f 2)" # The above beauty extracts data ID from the html page... # strip out all blank space and new lines. Then insert newlines # before each table row # search for the record type withing each row (e.g. TXT) # search for the domain within each row (which is within a # anchor. And finally extract the domain ID. if [ -n "$data_id" ]; then printf "%s" "$data_id" return 0 fi _debug "Domain $search_domain not found. Retry loading subdomain page ($attempts attempts remaining)" done _debug "Domain $search_domain not found after retry" return 1 } acme.sh-3.1.0/dnsapi/dns_gandi_livedns.sh000066400000000000000000000124021472032365200203200ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_gandi_livedns_info='Gandi.net LiveDNS Site: Gandi.net/domain/dns Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gandi_livedns Options: GANDI_LIVEDNS_KEY API Key Issues: github.com/fcrozat/acme.sh Author: Frédéric Crozat , Dominik Röttsches ' # Gandi LiveDNS v5 API # https://api.gandi.net/docs/livedns/ # https://api.gandi.net/docs/authentication/ for token + apikey (deprecated) authentication # currently under beta ######## Public functions ##################### GANDI_LIVEDNS_API="https://api.gandi.net/v5/livedns" #Usage: dns_gandi_livedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_gandi_livedns_add() { fulldomain=$1 txtvalue=$2 if [ -z "$GANDI_LIVEDNS_KEY" ] && [ -z "$GANDI_LIVEDNS_TOKEN" ]; then _err "No Token or API key (deprecated) specified for Gandi LiveDNS." _err "Create your token or key and export it as GANDI_LIVEDNS_KEY or GANDI_LIVEDNS_TOKEN respectively" return 1 fi # Keep only one secret in configuration if [ -n "$GANDI_LIVEDNS_TOKEN" ]; then _saveaccountconf GANDI_LIVEDNS_TOKEN "$GANDI_LIVEDNS_TOKEN" _clearaccountconf GANDI_LIVEDNS_KEY elif [ -n "$GANDI_LIVEDNS_KEY" ]; then _saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" _clearaccountconf GANDI_LIVEDNS_TOKEN fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug domain "$_domain" _debug sub_domain "$_sub_domain" _dns_gandi_append_record "$_domain" "$_sub_domain" "$txtvalue" } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_gandi_livedns_rm() { fulldomain=$1 txtvalue=$2 _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug fulldomain "$fulldomain" _debug domain "$_domain" _debug sub_domain "$_sub_domain" _debug txtvalue "$txtvalue" if ! _dns_gandi_existing_rrset_values "$_domain" "$_sub_domain"; then return 1 fi _new_rrset_values=$(echo "$_rrset_values" | sed "s/...$txtvalue...//g") # Cleanup dangling commata. _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, ,/ ,/g") _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/, *\]/\]/g") _new_rrset_values=$(echo "$_new_rrset_values" | sed "s/\[ *,/\[/g") _debug "New rrset_values" "$_new_rrset_values" _gandi_livedns_rest PUT \ "domains/$_domain/records/$_sub_domain/TXT" \ "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" && _contains "$response" '{"message":"DNS Record Created"}' && _info "Removing record $(__green "success")" } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _gandi_livedns_rest GET "domains/$h"; then return 1 fi if _contains "$response" '"code": 401'; then _err "$response" return 1 elif _contains "$response" '"code": 404'; 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 } _dns_gandi_append_record() { domain=$1 sub_domain=$2 txtvalue=$3 if _dns_gandi_existing_rrset_values "$domain" "$sub_domain"; then _debug "Appending new value" _rrset_values=$(echo "$_rrset_values" | sed "s/\"]/\",\"$txtvalue\"]/") else _debug "Creating new record" "$_rrset_values" _rrset_values="[\"$txtvalue\"]" fi _debug new_rrset_values "$_rrset_values" _gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \ "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" && _contains "$response" '{"message":"DNS Record Created"}' && _info "Adding record $(__green "success")" } _dns_gandi_existing_rrset_values() { domain=$1 sub_domain=$2 if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then return 1 fi if ! _contains "$response" '"rrset_type":"TXT"'; then _debug "Does not have a _acme-challenge TXT record yet." return 1 fi if _contains "$response" '"rrset_values":\[\]'; then _debug "Empty rrset_values for TXT record, no previous TXT record." return 1 fi _debug "Already has TXT record." _rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' | _egrep_o '\[".*\"]') return 0 } _gandi_livedns_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Content-Type: application/json" if [ -n "$GANDI_LIVEDNS_TOKEN" ]; then export _H2="Authorization: Bearer $GANDI_LIVEDNS_TOKEN" else export _H2="Authorization: Apikey $GANDI_LIVEDNS_KEY" fi if [ "$m" = "GET" ]; then response="$(_get "$GANDI_LIVEDNS_API/$ep")" else _debug data "$data" response="$(_post "$data" "$GANDI_LIVEDNS_API/$ep" "" "$m")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_gcloud.sh000077500000000000000000000121131472032365200167710ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_gcloud_info='Google Cloud DNS Site: Cloud.Google.com/dns Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcloud Options: CLOUDSDK_ACTIVE_CONFIG_NAME Active config name. E.g. "default" Author: Janos Lenart ' ######## Public functions ##################### # Usage: dns_gcloud_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_gcloud_add() { fulldomain=$1 txtvalue=$2 _info "Using gcloud" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _dns_gcloud_find_zone || return $? # Add an extra RR _dns_gcloud_start_tr || return $? _dns_gcloud_get_rrdatas || return $? echo "$rrdatas" | _dns_gcloud_remove_rrs || return $? printf "%s\n%s\n" "$rrdatas" "\"$txtvalue\"" | grep -v '^$' | _dns_gcloud_add_rrs || return $? _dns_gcloud_execute_tr || return $? _info "$fulldomain record added" } # Usage: dns_gcloud_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Remove the txt record after validation. dns_gcloud_rm() { fulldomain=$1 txtvalue=$2 _info "Using gcloud" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _dns_gcloud_find_zone || return $? # Remove one RR _dns_gcloud_start_tr || return $? _dns_gcloud_get_rrdatas || return $? echo "$rrdatas" | _dns_gcloud_remove_rrs || return $? echo "$rrdatas" | grep -F -v -- "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $? _dns_gcloud_execute_tr || return $? _info "$fulldomain record removed" } #################### Private functions below ################################## _dns_gcloud_start_tr() { if ! trd=$(mktemp -d); then _err "_dns_gcloud_start_tr: failed to create temporary directory" return 1 fi tr="$trd/tr.yaml" _debug tr "$tr" if ! gcloud dns record-sets transaction start \ --transaction-file="$tr" \ --zone="$managedZone"; then rm -r "$trd" _err "_dns_gcloud_start_tr: failed to execute transaction" return 1 fi } _dns_gcloud_execute_tr() { if ! gcloud dns record-sets transaction execute \ --transaction-file="$tr" \ --zone="$managedZone"; then _debug tr "$(cat "$tr")" rm -r "$trd" _err "_dns_gcloud_execute_tr: failed to execute transaction" return 1 fi rm -r "$trd" for i in $(seq 1 120); do if gcloud dns record-sets changes list \ --zone="$managedZone" \ --filter='status != done' | grep -q '^.*'; then _info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..." sleep 5 else return 0 fi done _err "_dns_gcloud_execute_tr: transaction is still pending after 10 minutes" rm -r "$trd" return 1 } _dns_gcloud_remove_rrs() { if ! xargs -r gcloud dns record-sets transaction remove \ --name="$fulldomain." \ --ttl="$ttl" \ --type=TXT \ --zone="$managedZone" \ --transaction-file="$tr" --; then _debug tr "$(cat "$tr")" rm -r "$trd" _err "_dns_gcloud_remove_rrs: failed to remove RRs" return 1 fi } _dns_gcloud_add_rrs() { ttl=60 if ! xargs -r gcloud dns record-sets transaction add \ --name="$fulldomain." \ --ttl="$ttl" \ --type=TXT \ --zone="$managedZone" \ --transaction-file="$tr" --; then _debug tr "$(cat "$tr")" rm -r "$trd" _err "_dns_gcloud_add_rrs: failed to add RRs" return 1 fi } _dns_gcloud_find_zone() { # Prepare a filter that matches zones that are suiteable for this entry. # For example, _acme-challenge.something.domain.com might need to go into something.domain.com or domain.com; # this function finds the longest postfix that has a managed zone. part="$fulldomain" filter="dnsName=( " while [ "$part" != "" ]; do filter="$filter$part. " part="$(echo "$part" | sed 's/[^.]*\.*//')" done filter="$filter) AND visibility=public" _debug filter "$filter" # List domains and find the zone with the deepest sub-domain (in case of some levels of delegation) if ! match=$(gcloud dns managed-zones list \ --format="value(name, dnsName)" \ --filter="$filter" | while read -r dnsName name; do printf "%s\t%s\t%s\n" "$(echo "$name" | awk -F"." '{print NF-1}')" "$dnsName" "$name" done | sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?" return 1 fi dnsName=$(echo "$match" | cut -f2) _debug dnsName "$dnsName" managedZone=$(echo "$match" | cut -f1) _debug managedZone "$managedZone" } _dns_gcloud_get_rrdatas() { if ! rrdatas=$(gcloud dns record-sets list \ --zone="$managedZone" \ --name="$fulldomain." \ --type=TXT \ --format="value(ttl,rrdatas)"); then _err "_dns_gcloud_get_rrdatas: Failed to list record-sets" rm -r "$trd" return 1 fi ttl=$(echo "$rrdatas" | cut -f1) # starting with version 353.0.0 gcloud seems to # separate records with a semicolon instead of commas # see also https://cloud.google.com/sdk/docs/release-notes#35300_2021-08-17 rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/"[,;]"/"\n"/g') } acme.sh-3.1.0/dnsapi/dns_gcore.sh000077500000000000000000000121051472032365200166140ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_gcore_info='Gcore.com Site: Gcore.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcore Options: GCORE_Key API Key Issues: github.com/acmesh-official/acme.sh/issues/4460 ' GCORE_Api="https://api.gcore.com/dns/v2" GCORE_Doc="https://api.gcore.com/docs/dns" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_gcore_add() { fulldomain=$1 txtvalue=$2 GCORE_Key="${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}" if [ -z "$GCORE_Key" ]; then GCORE_Key="" _err "You didn't specify a Gcore api key yet." _err "You can get yours from here $GCORE_Doc" return 1 fi #save the api key to the account conf file. _saveaccountconf_mutable GCORE_Key "$GCORE_Key" "base64" _debug "First detect the zone name" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _zone_name "$_zone_name" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _gcore_rest GET "zones/$_zone_name/$fulldomain/TXT" payload="" if echo "$response" | grep "record is not found" >/dev/null; then _info "Record doesn't exists" payload="{\"resource_records\":[{\"content\":[\"$txtvalue\"],\"enabled\":true}],\"ttl\":120}" elif echo "$response" | grep "$txtvalue" >/dev/null; then _info "Already exists, OK" return 0 elif echo "$response" | tr -d " " | grep \"name\":\""$fulldomain"\",\"type\":\"TXT\" >/dev/null; then _info "Record with mismatch txtvalue, try update it" payload=$(echo "$response" | tr -d " " | sed 's/"updated_at":[0-9]\+,//g' | sed 's/"meta":{}}]}/"meta":{}},{"content":['\""$txtvalue"\"'],"enabled":true}]}/') fi # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so # we can not use updating anymore. # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) # _debug count "$count" # if [ "$count" = "0" ]; then _info "Adding record" if _gcore_rest PUT "zones/$_zone_name/$fulldomain/TXT" "$payload"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 elif _contains "$response" "rrset is already exists"; then _info "Already exists, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_gcore_rm() { fulldomain=$1 txtvalue=$2 GCORE_Key="${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _zone_name "$_zone_name" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _gcore_rest GET "zones/$_zone_name/$fulldomain/TXT" if echo "$response" | grep "record is not found" >/dev/null; then _info "No such txt recrod" return 0 fi if ! echo "$response" | tr -d " " | grep \"name\":\""$fulldomain"\",\"type\":\"TXT\" >/dev/null; then _err "Error: $response" return 1 fi if ! echo "$response" | tr -d " " | grep \""$txtvalue"\" >/dev/null; then _info "No such txt recrod" return 0 fi count="$(echo "$response" | grep -o "content" | wc -l)" if [ "$count" = "1" ]; then if ! _gcore_rest DELETE "zones/$_zone_name/$fulldomain/TXT"; then _err "Delete record error. $response" return 1 fi return 0 fi payload="$(echo "$response" | tr -d " " | sed 's/"updated_at":[0-9]\+,//g' | sed 's/{"id":[0-9]\+,"content":\["'"$txtvalue"'"\],"enabled":true,"meta":{}}//' | sed 's/\[,/\[/' | sed 's/,,/,/' | sed 's/,\]/\]/')" if ! _gcore_rest PUT "zones/$_zone_name/$fulldomain/TXT" "$payload"; then _err "Delete record error. $response" fi } #################### Private functions below ################################## #_acme-challenge.sub.domain.com #returns # _sub_domain=_acme-challenge.sub or _acme-challenge # _domain=domain.com # _zone_name=domain.com or sub.domain.com _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _gcore_rest GET "zones/$h"; then return 1 fi if _contains "$response" "\"name\":\"$h\""; then _zone_name=$h if [ "$_zone_name" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _gcore_rest() { m=$1 ep="$2" data="$3" _debug "$ep" key_trimmed=$(echo "$GCORE_Key" | tr -d '"') export _H1="Content-Type: application/json" export _H2="Authorization: APIKey $key_trimmed" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$GCORE_Api/$ep" "" "$m")" else response="$(_get "$GCORE_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_gd.sh000077500000000000000000000121571472032365200161160ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_gd_info='GoDaddy.com Site: GoDaddy.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gd Options: GD_Key API Key GD_Secret API Secret ' GD_Api="https://api.godaddy.com/v1" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_gd_add() { fulldomain=$1 txtvalue=$2 GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}" GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}" if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then GD_Key="" GD_Secret="" _err "You didn't specify godaddy api key and secret yet." _err "Please create your key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable GD_Key "$GD_Key" _saveaccountconf_mutable GD_Secret "$GD_Secret" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting existing records" if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then return 1 fi if _contains "$response" "$txtvalue"; then _info "This record already exists, skipping" return 0 fi _add_data="{\"data\":\"$txtvalue\"}" for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do _debug2 t "$t" # ignore empty (previously removed) records, to prevent useless _acme-challenge TXT entries if [ "$t" ] && [ "$t" != '""' ]; then _add_data="$_add_data,{\"data\":$t}" fi done _debug2 _add_data "$_add_data" _info "Adding record" if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then _debug "Checking updated records of '${fulldomain}'" if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then _err "Validating TXT record for '${fulldomain}' with rest error [$?]." "$response" return 1 fi if ! _contains "$response" "$txtvalue"; then _err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!" return 1 fi else _err "Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set." return 1 fi _sleep 10 _info "Added TXT record '${txtvalue}' for '${fulldomain}'." return 0 } #fulldomain dns_gd_rm() { fulldomain=$1 txtvalue=$2 GD_Key="${GD_Key:-$(_readaccountconf_mutable GD_Key)}" GD_Secret="${GD_Secret:-$(_readaccountconf_mutable GD_Secret)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting existing records" if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then return 1 fi if ! _contains "$response" "$txtvalue"; then _info "The record does not exist, skip" return 0 fi _add_data="" for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do _debug2 t "$t" if [ "$t" ] && [ "$t" != "\"$txtvalue\"" ]; then if [ "$_add_data" ]; then _add_data="$_add_data,{\"data\":$t}" else _add_data="{\"data\":$t}" fi fi done if [ -z "$_add_data" ]; then # delete empty record _debug "Delete last record for '${fulldomain}'" if ! _gd_rest DELETE "domains/$_domain/records/TXT/$_sub_domain"; then _err "Cannot delete empty TXT record for '$fulldomain'" return 1 fi else # remove specific TXT value, keeping other entries _debug2 _add_data "$_add_data" if ! _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then _err "Cannot update TXT record for '$fulldomain'" return 1 fi fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi if ! _gd_rest GET "domains/$h"; 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 } _gd_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Authorization: sso-key $GD_Key:$GD_Secret" export _H2="Content-Type: application/json" if [ "$data" ] || [ "$m" = "DELETE" ]; then _debug "data ($m): " "$data" response="$(_post "$data" "$GD_Api/$ep" "" "$m")" else response="$(_get "$GD_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error on rest call ($m): $ep" 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 } acme.sh-3.1.0/dnsapi/dns_geoscaling.sh000077500000000000000000000177251472032365200176450ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_geoscaling_info='GeoScaling.com Site: GeoScaling.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_geoscaling Options: GEOSCALING_Username Username. This is usually NOT an email address GEOSCALING_Password Password ' #-- dns_geoscaling_add() - Add TXT record -------------------------------------- # Usage: dns_geoscaling_add _acme-challenge.subdomain.domain.com "XyZ123..." dns_geoscaling_add() { full_domain=$1 txt_value=$2 _info "Using DNS-01 Geoscaling DNS2 hook" GEOSCALING_Username="${GEOSCALING_Username:-$(_readaccountconf_mutable GEOSCALING_Username)}" GEOSCALING_Password="${GEOSCALING_Password:-$(_readaccountconf_mutable GEOSCALING_Password)}" if [ -z "$GEOSCALING_Username" ] || [ -z "$GEOSCALING_Password" ]; then GEOSCALING_Username= GEOSCALING_Password= _err "No auth details provided. Please set user credentials using the \$GEOSCALING_Username and \$GEOSCALING_Password environment variables." return 1 fi _saveaccountconf_mutable GEOSCALING_Username "${GEOSCALING_Username}" _saveaccountconf_mutable GEOSCALING_Password "${GEOSCALING_Password}" # Fills in the $zone_id and $zone_name find_zone "${full_domain}" || return 1 _debug "Zone id '${zone_id}' will be used." # We're logged in here # we should add ${full_domain} minus the trailing ${zone_name} prefix=$(echo "${full_domain}" | sed "s|\\.${zone_name}\$||") body="id=${zone_id}&name=${prefix}&type=TXT&content=${txt_value}&ttl=300&prio=0" do_post "$body" "https://www.geoscaling.com/dns2/ajax/add_record.php" exit_code="$?" if [ "${exit_code}" -eq 0 ]; then _info "TXT record added successfully." else _err "Couldn't add the TXT record." fi do_logout return "${exit_code}" } #-- dns_geoscaling_rm() - Remove TXT record ------------------------------------ # Usage: dns_geoscaling_rm _acme-challenge.subdomain.domain.com "XyZ123..." dns_geoscaling_rm() { full_domain=$1 txt_value=$2 _info "Cleaning up after DNS-01 Geoscaling DNS2 hook" GEOSCALING_Username="${GEOSCALING_Username:-$(_readaccountconf_mutable GEOSCALING_Username)}" GEOSCALING_Password="${GEOSCALING_Password:-$(_readaccountconf_mutable GEOSCALING_Password)}" if [ -z "$GEOSCALING_Username" ] || [ -z "$GEOSCALING_Password" ]; then GEOSCALING_Username= GEOSCALING_Password= _err "No auth details provided. Please set user credentials using the \$GEOSCALING_Username and \$GEOSCALING_Password environment variables." return 1 fi _saveaccountconf_mutable GEOSCALING_Username "${GEOSCALING_Username}" _saveaccountconf_mutable GEOSCALING_Password "${GEOSCALING_Password}" # fills in the $zone_id find_zone "${full_domain}" || return 1 _debug "Zone id '${zone_id}' will be used." # Here we're logged in # Find the record id to clean # get the domain response=$(do_get "https://www.geoscaling.com/dns2/index.php?module=domain&id=${zone_id}") _debug2 "response" "$response" table="$(echo "${response}" | tr -d '\n' | sed 's|.*
Basic Records
.*||')" _debug2 table "${table}" names=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|||; s|.*>||') ids=$(echo "${table}" | _egrep_o 'id="[0-9]+\.name">[^<]*' | sed 's|\.name">.*||; s|id="||') types=$(echo "${table}" | _egrep_o 'id="[0-9]+\.type">[^<]*' | sed 's|||; s|.*>||') values=$(echo "${table}" | _egrep_o 'id="[0-9]+\.content">[^<]*' | sed 's|||; s|.*>||') _debug2 names "${names}" _debug2 ids "${ids}" _debug2 types "${types}" _debug2 values "${values}" # look for line whose name is ${full_domain}, whose type is TXT, and whose value is ${txt_value} line_num="$(echo "${values}" | grep -F -n -- "${txt_value}" | _head_n 1 | cut -d ':' -f 1)" _debug2 line_num "${line_num}" found_id= if [ -n "$line_num" ]; then type=$(echo "${types}" | sed -n "${line_num}p") name=$(echo "${names}" | sed -n "${line_num}p") id=$(echo "${ids}" | sed -n "${line_num}p") _debug2 type "$type" _debug2 name "$name" _debug2 id "$id" _debug2 full_domain "$full_domain" if [ "${type}" = "TXT" ] && [ "${name}" = "${full_domain}" ]; then found_id=${id} fi fi if [ "${found_id}" = "" ]; then _err "Can not find record id." return 0 fi # Remove the record body="id=${zone_id}&record_id=${found_id}" response=$(do_post "$body" "https://www.geoscaling.com/dns2/ajax/delete_record.php") exit_code="$?" if [ "$exit_code" -eq 0 ]; then _info "Record removed successfully." else _err "Could not clean (remove) up the record. Please go to Geoscaling administration interface and clean it by hand." fi do_logout return "${exit_code}" } ########################## PRIVATE FUNCTIONS ########################### do_get() { _url=$1 export _H1="Cookie: $geoscaling_phpsessid_cookie" _get "${_url}" } do_post() { _body=$1 _url=$2 export _H1="Cookie: $geoscaling_phpsessid_cookie" _post "${_body}" "${_url}" } do_login() { _info "Logging in..." username_encoded="$(printf "%s" "${GEOSCALING_Username}" | _url_encode)" password_encoded="$(printf "%s" "${GEOSCALING_Password}" | _url_encode)" body="username=${username_encoded}&password=${password_encoded}" response=$(_post "$body" "https://www.geoscaling.com/dns2/index.php?module=auth") _debug2 response "${response}" #retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | _egrep_o '[0-9]+$') retcode=$(grep '^HTTP[^ ]*' "${HTTP_HEADER}" | _head_n 1 | cut -d ' ' -f 2) if [ "$retcode" != "302" ]; then _err "Geoscaling login failed for user ${GEOSCALING_Username}. Check ${HTTP_HEADER} file" return 1 fi geoscaling_phpsessid_cookie="$(grep -i '^set-cookie:' "${HTTP_HEADER}" | _egrep_o 'PHPSESSID=[^;]*;' | tr -d ';')" return 0 } do_logout() { _info "Logging out." response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=auth")" _debug2 response "$response" return 0 } find_zone() { domain="$1" # do login do_login || return 1 # get zones response="$(do_get "https://www.geoscaling.com/dns2/index.php?module=domains")" table="$(echo "${response}" | tr -d '\n' | sed 's|.*
Your domains
.*||')" _debug2 table "${table}" zone_names="$(echo "${table}" | _egrep_o '[^<]*' | sed 's|||;s|||')" _debug2 _matches "${zone_names}" # Zone names and zone IDs are in same order zone_ids=$(echo "${table}" | _egrep_o '' | sed 's|.*id=||;s|. .*||') _debug2 "These are the zones on this Geoscaling account:" _debug2 "zone_names" "${zone_names}" _debug2 "And these are their respective IDs:" _debug2 "zone_ids" "${zone_ids}" if [ -z "${zone_names}" ] || [ -z "${zone_ids}" ]; then _err "Can not get zone names or IDs." return 1 fi # Walk through all possible zone names strip_counter=1 while true; do attempted_zone=$(echo "${domain}" | cut -d . -f "${strip_counter}"-) # All possible zone names have been tried if [ -z "${attempted_zone}" ]; then _err "No zone for domain '${domain}' found." return 1 fi _debug "Looking for zone '${attempted_zone}'" line_num="$(echo "${zone_names}" | grep -n "^${attempted_zone}\$" | _head_n 1 | cut -d : -f 1)" _debug2 line_num "${line_num}" if [ "$line_num" ]; then zone_id=$(echo "${zone_ids}" | sed -n "${line_num}p") zone_name=$(echo "${zone_names}" | sed -n "${line_num}p") if [ -z "${zone_id}" ]; then _err "Can not find zone id." return 1 fi _debug "Found relevant zone '${attempted_zone}' with id '${zone_id}' - will be used for domain '${domain}'." return 0 fi _debug "Zone '${attempted_zone}' doesn't exist, let's try a less specific zone." strip_counter=$(_math "${strip_counter}" + 1) done } # vim: et:ts=2:sw=2: acme.sh-3.1.0/dnsapi/dns_googledomains.sh000077500000000000000000000107631472032365200203540ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_googledomains_info='Google Domains Site: Domains.Google.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_googledomains Options: GOOGLEDOMAINS_ACCESS_TOKEN API Access Token GOOGLEDOMAINS_ZONE Zone Issues: github.com/acmesh-official/acme.sh/issues/4545 Author: Alex Leigh ' GOOGLEDOMAINS_API="https://acmedns.googleapis.com/v1/acmeChallengeSets" ######## Public functions ######## #Usage: dns_googledomains_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_googledomains_add() { fulldomain=$1 txtvalue=$2 _info "Invoking Google Domains ACME DNS API." if ! _dns_googledomains_setup; then return 1 fi zone="$(_dns_googledomains_get_zone "$fulldomain")" if [ -z "$zone" ]; then _err "Could not find a Google Domains-managed zone containing the requested domain." return 1 fi _debug zone "$zone" _debug txtvalue "$txtvalue" _info "Adding TXT record for $fulldomain." if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToAdd\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then if _contains "$response" "$txtvalue"; then _info "TXT record added." return 0 else _err "Error adding TXT record." return 1 fi fi _err "Error adding TXT record." return 1 } #Usage: dns_googledomains_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_googledomains_rm() { fulldomain=$1 txtvalue=$2 _info "Invoking Google Domains ACME DNS API." if ! _dns_googledomains_setup; then return 1 fi zone="$(_dns_googledomains_get_zone "$fulldomain")" if [ -z "$zone" ]; then _err "Could not find a Google Domains-managed domain based on request." return 1 fi _debug zone "$zone" _debug txtvalue "$txtvalue" _info "Removing TXT record for $fulldomain." if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToRemove\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then if _contains "$response" "$txtvalue"; then _err "Error removing TXT record." return 1 else _info "TXT record removed." return 0 fi fi _err "Error removing TXT record." return 1 } ######## Private functions ######## _dns_googledomains_setup() { if [ -n "$GOOGLEDOMAINS_SETUP_COMPLETED" ]; then return 0 fi GOOGLEDOMAINS_ACCESS_TOKEN="${GOOGLEDOMAINS_ACCESS_TOKEN:-$(_readaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN)}" GOOGLEDOMAINS_ZONE="${GOOGLEDOMAINS_ZONE:-$(_readaccountconf_mutable GOOGLEDOMAINS_ZONE)}" if [ -z "$GOOGLEDOMAINS_ACCESS_TOKEN" ]; then GOOGLEDOMAINS_ACCESS_TOKEN="" _err "Google Domains access token was not specified." _err "Please visit Google Domains Security settings to provision an ACME DNS API access token." return 1 fi if [ "$GOOGLEDOMAINS_ZONE" ]; then _savedomainconf GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN" _savedomainconf GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE" else _saveaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN" _clearaccountconf_mutable GOOGLEDOMAINS_ZONE _clearaccountconf GOOGLEDOMAINS_ZONE fi _debug GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN" _debug GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE" GOOGLEDOMAINS_SETUP_COMPLETED=1 return 0 } _dns_googledomains_get_zone() { domain=$1 # Use zone directly if provided if [ "$GOOGLEDOMAINS_ZONE" ]; then if ! _dns_googledomains_api "$GOOGLEDOMAINS_ZONE"; then return 1 fi echo "$GOOGLEDOMAINS_ZONE" return 0 fi i=2 while true; do curr=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug curr "$curr" if [ -z "$curr" ]; then return 1 fi if _dns_googledomains_api "$curr"; then echo "$curr" return 0 fi i=$(_math "$i" + 1) done return 1 } _dns_googledomains_api() { zone=$1 apimethod=$2 data="$3" if [ -z "$data" ]; then response="$(_get "$GOOGLEDOMAINS_API/$zone$apimethod")" else _debug data "$data" export _H1="Content-Type: application/json" response="$(_post "$data" "$GOOGLEDOMAINS_API/$zone$apimethod")" fi _debug response "$response" if [ "$?" != "0" ]; then _err "Error" return 1 fi if _contains "$response" "\"error\": {"; then return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_he.sh000077500000000000000000000145641472032365200161240ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_he_info='Hurricane Electric HE.net Site: dns.he.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_he Options: HE_Username Username HE_Password Password Issues: github.com/angel333/acme.sh/issues/ Author: Ondrej Simek ' #-- dns_he_add() - Add TXT record -------------------------------------- # Usage: dns_he_add _acme-challenge.subdomain.domain.com "XyZ123..." dns_he_add() { _full_domain=$1 _txt_value=$2 _info "Using DNS-01 Hurricane Electric hook" HE_Username="${HE_Username:-$(_readaccountconf_mutable HE_Username)}" HE_Password="${HE_Password:-$(_readaccountconf_mutable HE_Password)}" if [ -z "$HE_Username" ] || [ -z "$HE_Password" ]; then HE_Username= HE_Password= _err "No auth details provided. Please set user credentials using the \$HE_Username and \$HE_Password environment variables." return 1 fi _saveaccountconf_mutable HE_Username "$HE_Username" _saveaccountconf_mutable HE_Password "$HE_Password" # Fills in the $_zone_id _find_zone "$_full_domain" || return 1 _debug "Zone id \"$_zone_id\" will be used." 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" body="$body&hosted_dns_zoneid=$_zone_id" body="$body&hosted_dns_recordid=" body="$body&hosted_dns_editzone=1" body="$body&Priority=" body="$body&Name=$_full_domain" body="$body&Content=$_txt_value" body="$body&TTL=300" body="$body&hosted_dns_editrecord=Submit" response="$(_post "$body" "https://dns.he.net/")" exit_code="$?" if [ "$exit_code" -eq 0 ]; then _info "TXT record added successfully." else _err "Couldn't add the TXT record." fi _debug2 response "$response" return "$exit_code" } #-- dns_he_rm() - Remove TXT record ------------------------------------ # Usage: dns_he_rm _acme-challenge.subdomain.domain.com "XyZ123..." dns_he_rm() { _full_domain=$1 _txt_value=$2 _info "Cleaning up after DNS-01 Hurricane Electric hook" HE_Username="${HE_Username:-$(_readaccountconf_mutable HE_Username)}" HE_Password="${HE_Password:-$(_readaccountconf_mutable HE_Password)}" # fills in the $_zone_id _find_zone "$_full_domain" || return 1 _debug "Zone id \"$_zone_id\" will be used." # Find the record id to clean 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=" response="$(_post "$body" "https://dns.he.net/")" _debug2 "response" "$response" if ! _contains "$response" "$_txt_value"; then _debug "The txt record is not found, just skip" return 0 fi _record_id="$(echo "$response" | tr -d "#" | sed "s/Successfully removed record.
' \ >/dev/null exit_code="$?" if [ "$exit_code" -eq 0 ]; then _info "Record removed successfully." else _err "Could not clean (remove) up the record. Please go to HE administration interface and clean it by hand." return "$exit_code" fi } ########################## PRIVATE FUNCTIONS ########################### _find_zone() { _domain="$1" 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//dev/null else _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets/${_record_id}" false "PUT" >/dev/null fi _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 _domain_name=$3 _debug "Getting Token" body="{ \"auth\": { \"identity\": { \"methods\": [ \"password\" ], \"password\": { \"user\": { \"name\": \"${_username}\", \"password\": \"${_password}\", \"domain\": { \"name\": \"${_domain_name}\" } } } }, \"scope\": { \"project\": { \"name\": \"ap-southeast-1\" } } } }" 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-) _secure_debug "${_code}" printf "%s" "${_token}" return 0 } acme.sh-3.1.0/dnsapi/dns_infoblox.sh000066400000000000000000000075651472032365200173500ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_infoblox_info='Infoblox.com Site: Infoblox.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_infoblox Options: Infoblox_Creds Credentials. E.g. "username:password" Infoblox_Server Server hostname. IP or FQDN of infoblox appliance Issues: github.com/jasonkeller/acme.sh Author: Jason Keller, Elijah Tenai ' dns_infoblox_add() { ## Nothing to see here, just some housekeeping fulldomain=$1 txtvalue=$2 _info "Using Infoblox API" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" ## Check for the credentials if [ -z "$Infoblox_Creds" ] || [ -z "$Infoblox_Server" ]; then Infoblox_Creds="" Infoblox_Server="" _err "You didn't specify the Infoblox credentials or server (Infoblox_Creds; Infoblox_Server)." _err "Please set them via EXPORT Infoblox_Creds=username:password or EXPORT Infoblox_server=ip/hostname and try again." return 1 fi if [ -z "$Infoblox_View" ]; then _info "No Infoblox_View set, using fallback value 'default'" Infoblox_View="default" fi ## Save the credentials to the account file _saveaccountconf Infoblox_Creds "$Infoblox_Creds" _saveaccountconf Infoblox_Server "$Infoblox_Server" _saveaccountconf Infoblox_View "$Infoblox_View" ## URLencode Infoblox View to deal with e.g. spaces Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode) ## Base64 encode the credentials Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64) ## Construct the HTTP Authorization header export _H1="Accept-Language:en-US" export _H2="Authorization: Basic $Infoblox_CredsEncoded" ## Construct the request URL baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}" ## Add the challenge record to the Infoblox grid member result="$(_post "" "$baseurlnObject" "" "POST")" ## Let's see if we get something intelligible back from the unit if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then _info "Successfully created the txt record" return 0 else _err "Error encountered during record addition" _err "$result" return 1 fi } dns_infoblox_rm() { ## Nothing to see here, just some housekeeping fulldomain=$1 txtvalue=$2 _info "Using Infoblox API" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" ## URLencode Infoblox View to deal with e.g. spaces Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode) ## Base64 encode the credentials Infoblox_CredsEncoded="$(printf "%b" "$Infoblox_Creds" | _base64)" ## Construct the HTTP Authorization header export _H1="Accept-Language:en-US" export _H2="Authorization: Basic $Infoblox_CredsEncoded" ## Does the record exist? Let's check. baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}&_return_type=xml-pretty" result="$(_get "$baseurlnObject")" ## Let's see if we get something intelligible back from the grid if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then ## Extract the object reference objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef" ## Delete them! All the stale records! rmResult="$(_post "" "$objRmUrl" "" "DELETE")" ## Let's see if that worked if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then _info "Successfully deleted $objRef" return 0 else _err "Error occurred during txt record delete" _err "$rmResult" return 1 fi else _err "Record to delete didn't match an existing record" _err "$result" return 1 fi } #################### Private functions below ################################## acme.sh-3.1.0/dnsapi/dns_infomaniak.sh000077500000000000000000000135661472032365200176450ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_infomaniak_info='Infomaniak.com Site: Infomaniak.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_infomaniak Options: INFOMANIAK_API_TOKEN API Token Issues: github.com/acmesh-official/acme.sh/issues/3188 ' # 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 # # 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 } acme.sh-3.1.0/dnsapi/dns_internetbs.sh000077500000000000000000000123531472032365200176770ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_internetbs_info='InternetBS.net Site: InternetBS.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_internetbs Options: INTERNETBS_API_KEY API Key INTERNETBS_API_PASSWORD API Password Issues: github.com/acmesh-official/acme.sh/issues/2261 Author: Ne-Lexa ' INTERNETBS_API_URL="https://api.internet.bs" ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_internetbs_add() { fulldomain=$1 txtvalue=$2 INTERNETBS_API_KEY="${INTERNETBS_API_KEY:-$(_readaccountconf_mutable INTERNETBS_API_KEY)}" INTERNETBS_API_PASSWORD="${INTERNETBS_API_PASSWORD:-$(_readaccountconf_mutable INTERNETBS_API_PASSWORD)}" if [ -z "$INTERNETBS_API_KEY" ] || [ -z "$INTERNETBS_API_PASSWORD" ]; then INTERNETBS_API_KEY="" INTERNETBS_API_PASSWORD="" _err "You didn't specify the INTERNET.BS api key and password yet." _err "Please create you key and try again." return 1 fi _saveaccountconf_mutable INTERNETBS_API_KEY "$INTERNETBS_API_KEY" _saveaccountconf_mutable INTERNETBS_API_PASSWORD "$INTERNETBS_API_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" # https://testapi.internet.bs/Domain/DnsRecord/Add?ApiKey=testapi&Password=testpass&FullRecordName=w3.test-api-domain7.net&Type=CNAME&Value=www.internet.bs%&ResponseFormat=json if _internetbs_rest POST "Domain/DnsRecord/Add" "FullRecordName=${_sub_domain}.${_domain}&Type=TXT&Value=${txtvalue}&ResponseFormat=json"; then if ! _contains "$response" "\"status\":\"SUCCESS\""; then _err "ERROR add TXT record" _err "$response" return 1 fi _info "txt record add success." return 0 fi return 1 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_internetbs_rm() { fulldomain=$1 txtvalue=$2 INTERNETBS_API_KEY="${INTERNETBS_API_KEY:-$(_readaccountconf_mutable INTERNETBS_API_KEY)}" INTERNETBS_API_PASSWORD="${INTERNETBS_API_PASSWORD:-$(_readaccountconf_mutable INTERNETBS_API_PASSWORD)}" if [ -z "$INTERNETBS_API_KEY" ] || [ -z "$INTERNETBS_API_PASSWORD" ]; then INTERNETBS_API_KEY="" INTERNETBS_API_PASSWORD="" _err "You didn't specify the INTERNET.BS api key and password yet." _err "Please create you key and try again." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" # https://testapi.internet.bs/Domain/DnsRecord/List?ApiKey=testapi&Password=testpass&Domain=test-api-domain7.net&FilterType=CNAME&ResponseFormat=json _internetbs_rest POST "Domain/DnsRecord/List" "Domain=$_domain&FilterType=TXT&ResponseFormat=json" if ! _contains "$response" "\"status\":\"SUCCESS\""; then _err "ERROR list dns records" _err "$response" return 1 fi if _contains "$response" "\name\":\"${_sub_domain}.${_domain}\""; then _info "txt record find." # https://testapi.internet.bs/Domain/DnsRecord/Remove?ApiKey=testapi&Password=testpass&FullRecordName=www.test-api-domain7.net&Type=cname&ResponseFormat=json _internetbs_rest POST "Domain/DnsRecord/Remove" "FullRecordName=${_sub_domain}.${_domain}&Type=TXT&ResponseFormat=json" if ! _contains "$response" "\"status\":\"SUCCESS\""; then _err "ERROR remove dns record" _err "$response" return 1 fi _info "txt record deleted success." return 0 fi return 1 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=12345 _get_root() { domain=$1 i=2 p=1 # https://testapi.internet.bs/Domain/List?ApiKey=testapi&Password=testpass&CompactList=yes&ResponseFormat=json if _internetbs_rest POST "Domain/List" "CompactList=yes&ResponseFormat=json"; then if ! _contains "$response" "\"status\":\"SUCCESS\""; then _err "ERROR fetch domain list" _err "$response" return 1 fi while true; do h=$(printf "%s" "$domain" | cut -d . -f "${i}"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if _contains "$response" "\"$h\""; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"${p}") _domain=${h} return 0 fi p=${i} i=$(_math "$i" + 1) done fi return 1 } #Usage: method URI data _internetbs_rest() { m="$1" ep="$2" data="$3" url="${INTERNETBS_API_URL}/${ep}" _debug url "$url" apiKey="$(printf "%s" "${INTERNETBS_API_KEY}" | _url_encode)" password="$(printf "%s" "${INTERNETBS_API_PASSWORD}" | _url_encode)" if [ "$m" = "GET" ]; then response="$(_get "${url}?ApiKey=${apiKey}&Password=${password}&${data}" | tr -d '\r')" else _debug2 data "$data" response="$(_post "$data" "${url}?ApiKey=${apiKey}&Password=${password}" | tr -d '\r')" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_inwx.sh000077500000000000000000000234151472032365200165100ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_inwx_info='INWX.de Site: INWX.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx Options: INWX_User Username INWX_Password Password ' # Dependencies: # ------------- # - oathtool (When using 2 Factor Authentication) INWX_Api="https://api.domrobot.com/xmlrpc/" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_inwx_add() { fulldomain=$1 txtvalue=$2 INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}" INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}" INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}" if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then INWX_User="" INWX_Password="" _err "You don't specify inwx 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 INWX_User "$INWX_User" _saveaccountconf_mutable INWX_Password "$INWX_Password" _saveaccountconf_mutable INWX_Shared_Secret "$INWX_Shared_Secret" if ! _inwx_login; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" _inwx_add_record "$_domain" "$_sub_domain" "$txtvalue" } #fulldomain txtvalue dns_inwx_rm() { fulldomain=$1 txtvalue=$2 INWX_User="${INWX_User:-$(_readaccountconf_mutable INWX_User)}" INWX_Password="${INWX_Password:-$(_readaccountconf_mutable INWX_Password)}" INWX_Shared_Secret="${INWX_Shared_Secret:-$(_readaccountconf_mutable INWX_Shared_Secret)}" if [ -z "$INWX_User" ] || [ -z "$INWX_Password" ]; then INWX_User="" INWX_Password="" _err "You don't specify inwx user and password yet." _err "Please create you key and try again." return 1 fi if ! _inwx_login; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" xml_content=$(printf ' nameserver.info domain %s type TXT name %s ' "$_domain" "$_sub_domain") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if ! _contains "$response" "Command completed successfully"; then _err "Error could not get txt records" return 1 fi if ! printf "%s" "$response" | grep "count" >/dev/null; then _info "Do not need to delete record" else _record_id=$(printf '%s' "$response" | _egrep_o '.*(record){1}(.*)([0-9]+){1}' | _egrep_o 'id<\/name>[0-9]+' | _egrep_o '[0-9]+') _info "Deleting record" _inwx_delete_record "$_record_id" fi } #################### Private functions below ################################## _inwx_check_cookie() { INWX_Cookie="${INWX_Cookie:-$(_readaccountconf_mutable INWX_Cookie)}" if [ -z "$INWX_Cookie" ]; then _debug "No cached cookie found" return 1 fi _H1="$INWX_Cookie" export _H1 xml_content=$(printf ' account.info ') response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if _contains "$response" "code1000"; then _debug "Cached cookie still valid" return 0 fi _debug "Cached cookie no longer valid" _H1="" export _H1 INWX_Cookie="" _saveaccountconf_mutable INWX_Cookie "$INWX_Cookie" return 1 } _htmlEscape() { _s="$1" _s=$(echo "$_s" | sed "s/&/&/g") _s=$(echo "$_s" | sed "s//\>/g") _s=$(echo "$_s" | sed 's/"/\"/g') printf -- %s "$_s" } _inwx_login() { if _inwx_check_cookie; then _debug "Already logged in" return 0 fi XML_PASS=$(_htmlEscape "$INWX_Password") xml_content=$(printf ' account.login user %s pass %s ' "$INWX_User" "$XML_PASS") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep -i "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')") _H1=$INWX_Cookie export _H1 export INWX_Cookie _saveaccountconf_mutable INWX_Cookie "$INWX_Cookie" if ! _contains "$response" "code1000"; then _err "INWX API: Authentication error (username/password correct?)" return 1 fi #https://github.com/inwx/php-client/blob/master/INWX/Domrobot.php#L71 if _contains "$response" "tfaGOOGLE-AUTH"; then if [ -z "$INWX_Shared_Secret" ]; then _err "INWX API: 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")" if ! _contains "$response" "code1000"; then _err "INWX API: Mobile TAN not correct." return 1 fi fi } _get_root() { domain=$1 _debug "get root" domain=$1 i=2 p=1 xml_content=' nameserver.list pagelimit 9999 ' response="$(_post "$xml_content" "$INWX_Api" "" "POST")" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if _contains "$response" "$h"; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _inwx_delete_record() { record_id=$1 xml_content=$(printf ' nameserver.deleteRecord id %s ' "$record_id") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then _err "Error" return 1 fi return 0 } _inwx_update_record() { record_id=$1 txtval=$2 xml_content=$(printf ' nameserver.updateRecord content %s id %s ' "$txtval" "$record_id") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then _err "Error" return 1 fi return 0 } _inwx_add_record() { domain=$1 sub_domain=$2 txtval=$3 xml_content=$(printf ' nameserver.createRecord domain %s type TXT content %s name %s ' "$domain" "$txtval" "$sub_domain") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if ! printf "%s" "$response" | grep "Command completed successfully" >/dev/null; then _err "Error" return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_ionos.sh000077500000000000000000000101051472032365200166420ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ionos_info='IONOS.de Site: IONOS.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ionos Options: IONOS_PREFIX Prefix IONOS_SECRET Secret Issues: github.com/acmesh-official/acme.sh/issues/3379 ' IONOS_API="https://api.hosting.ionos.com/dns" IONOS_ROUTE_ZONES="/v1/zones" IONOS_TXT_TTL=60 # minimum accepted by API IONOS_TXT_PRIO=10 dns_ionos_add() { fulldomain=$1 txtvalue=$2 if ! _ionos_init; then return 1 fi _body="[{\"name\":\"$_sub_domain.$_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":$IONOS_TXT_TTL,\"prio\":$IONOS_TXT_PRIO,\"disabled\":false}]" if _ionos_rest POST "$IONOS_ROUTE_ZONES/$_zone_id/records" "$_body" && [ "$_code" = "201" ]; then _info "TXT record has been created successfully." return 0 fi return 1 } dns_ionos_rm() { fulldomain=$1 txtvalue=$2 if ! _ionos_init; then return 1 fi if ! _ionos_get_record "$fulldomain" "$_zone_id" "$txtvalue"; then _err "Could not find _acme-challenge TXT record." return 1 fi if _ionos_rest DELETE "$IONOS_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ "$_code" = "200" ]; then _info "TXT record has been deleted successfully." return 0 fi return 1 } _ionos_init() { IONOS_PREFIX="${IONOS_PREFIX:-$(_readaccountconf_mutable IONOS_PREFIX)}" IONOS_SECRET="${IONOS_SECRET:-$(_readaccountconf_mutable IONOS_SECRET)}" if [ -z "$IONOS_PREFIX" ] || [ -z "$IONOS_SECRET" ]; then _err "You didn't specify an IONOS api prefix and secret yet." _err "Read https://beta.developer.hosting.ionos.de/docs/getstarted to learn how to get a prefix and secret." _err "" _err "Then set them before calling acme.sh:" _err "\$ export IONOS_PREFIX=\"...\"" _err "\$ export IONOS_SECRET=\"...\"" _err "\$ acme.sh --issue -d ... --dns dns_ionos" return 1 fi _saveaccountconf_mutable IONOS_PREFIX "$IONOS_PREFIX" _saveaccountconf_mutable IONOS_SECRET "$IONOS_SECRET" if ! _get_root "$fulldomain"; then _err "Cannot find this domain in your IONOS account." return 1 fi } _get_root() { domain=$1 i=1 p=1 if _ionos_rest GET "$IONOS_ROUTE_ZONES"; then _response="$(echo "$_response" | tr -d "\n")" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then return 1 fi _zone="$(echo "$_response" | _egrep_o "\"name\":\"$h\".*\}")" if [ "$_zone" ]; then _zone_id=$(printf "%s\n" "$_zone" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"') if [ "$_zone_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done fi return 1 } _ionos_get_record() { fulldomain=$1 zone_id=$2 txtrecord=$3 if _ionos_rest GET "$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT"; then _response="$(echo "$_response" | tr -d "\n")" _record="$(echo "$_response" | _egrep_o "\"name\":\"$fulldomain\"[^\}]*\"type\":\"TXT\"[^\}]*\"content\":\"\\\\\"$txtrecord\\\\\"\".*\}")" if [ "$_record" ]; then _record_id=$(printf "%s\n" "$_record" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"') return 0 fi fi return 1 } _ionos_rest() { method="$1" route="$2" data="$3" IONOS_API_KEY="$(printf "%s.%s" "$IONOS_PREFIX" "$IONOS_SECRET")" export _H1="X-API-Key: $IONOS_API_KEY" # clear headers : >"$HTTP_HEADER" if [ "$method" != "GET" ]; then export _H2="Accept: application/json" export _H3="Content-Type: application/json" _response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")" else export _H2="Accept: */*" export _H3= _response="$(_get "$IONOS_API$route")" fi _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" if [ "$?" != "0" ]; then _err "Error $route: $_response" return 1 fi _debug2 "_response" "$_response" _debug2 "_code" "$_code" return 0 } acme.sh-3.1.0/dnsapi/dns_ionos_cloud.sh000066400000000000000000000101111472032365200200220ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ionos_cloud_info='IONOS Cloud DNS Site: ionos.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ionos_cloud Options: IONOS_TOKEN API Token. Issues: github.com/acmesh-official/acme.sh/issues/5243 ' # Supports IONOS Cloud DNS API v1.15.4 IONOS_CLOUD_API="https://dns.de-fra.ionos.com" IONOS_CLOUD_ROUTE_ZONES="/zones" dns_ionos_cloud_add() { fulldomain=$1 txtvalue=$2 if ! _ionos_init; then return 1 fi _record_name=$(printf "%s" "$fulldomain" | cut -d . -f 1) _body="{\"properties\":{\"name\":\"$_record_name\", \"type\":\"TXT\", \"content\":\"$txtvalue\"}}" if _ionos_cloud_rest POST "$IONOS_CLOUD_ROUTE_ZONES/$_zone_id/records" "$_body" && [ "$_code" = "202" ]; then _info "TXT record has been created successfully." return 0 fi return 1 } dns_ionos_cloud_rm() { fulldomain=$1 txtvalue=$2 if ! _ionos_init; then return 1 fi if ! _ionos_cloud_get_record "$_zone_id" "$txtvalue" "$fulldomain"; then _err "Could not find _acme-challenge TXT record." return 1 fi if _ionos_cloud_rest DELETE "$IONOS_CLOUD_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ "$_code" = "202" ]; then _info "TXT record has been deleted successfully." return 0 fi return 1 } _ionos_init() { IONOS_TOKEN="${IONOS_TOKEN:-$(_readaccountconf_mutable IONOS_TOKEN)}" if [ -z "$IONOS_TOKEN" ]; then _err "You didn't specify an IONOS token yet." _err "Read https://api.ionos.com/docs/authentication/v1/#tag/tokens/operation/tokensGenerate to learn how to get a token." _err "You need to set it before calling acme.sh:" _err "\$ export IONOS_TOKEN=\"...\"" _err "\$ acme.sh --issue -d ... --dns dns_ionos_cloud" return 1 fi _saveaccountconf_mutable IONOS_TOKEN "$IONOS_TOKEN" if ! _get_cloud_zone "$fulldomain"; then _err "Cannot find zone $zone in your IONOS account." return 1 fi return 0 } _get_cloud_zone() { domain=$1 zone=$(printf "%s" "$domain" | cut -d . -f 2-) if _ionos_cloud_rest GET "$IONOS_CLOUD_ROUTE_ZONES?filter.zoneName=$zone"; then _response="$(echo "$_response" | tr -d "\n")" _zone_list_items=$(echo "$_response" | _egrep_o "\"items\":.*") _zone_id=$(printf "%s\n" "$_zone_list_items" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"') if [ "$_zone_id" ]; then return 0 fi fi return 1 } _ionos_cloud_get_record() { zone_id=$1 txtrecord=$2 # this is to transform the domain to lower case fulldomain=$(printf "%s" "$3" | _lower_case) # this is to transform record name to lower case # IONOS Cloud API transforms all record names to lower case _record_name=$(printf "%s" "$fulldomain" | cut -d . -f 1 | _lower_case) if _ionos_cloud_rest GET "$IONOS_CLOUD_ROUTE_ZONES/$zone_id/records"; then _response="$(echo "$_response" | tr -d "\n")" pattern="\{\"id\":\"[a-fA-F0-9\-]*\",\"type\":\"record\",\"href\":\"/zones/$zone_id/records/[a-fA-F0-9\-]*\",\"metadata\":\{\"createdDate\":\"[A-Z0-9\:\.\-]*\",\"lastModifiedDate\":\"[A-Z0-9\:\.\-]*\",\"fqdn\":\"$fulldomain\",\"state\":\"AVAILABLE\",\"zoneId\":\"$zone_id\"\},\"properties\":\{\"content\":\"$txtrecord\",\"enabled\":true,\"name\":\"$_record_name\",\"priority\":[0-9]*,\"ttl\":[0-9]*,\"type\":\"TXT\"\}\}" _record="$(echo "$_response" | _egrep_o "$pattern")" if [ "$_record" ]; then _record_id=$(printf "%s\n" "$_record" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"') return 0 fi fi return 1 } _ionos_cloud_rest() { method="$1" route="$2" data="$3" export _H1="Authorization: Bearer $IONOS_TOKEN" # clear headers : >"$HTTP_HEADER" if [ "$method" != "GET" ]; then _response="$(_post "$data" "$IONOS_CLOUD_API$route" "" "$method" "application/json")" else _response="$(_get "$IONOS_CLOUD_API$route")" fi _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" if [ "$?" != "0" ]; then _err "Error $route: $_response" return 1 fi _debug2 "_response" "$_response" _debug2 "_code" "$_code" return 0 } acme.sh-3.1.0/dnsapi/dns_ipv64.sh000077500000000000000000000102041472032365200164630ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ipv64_info='IPv64.net Site: IPv64.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ipv64 Options: IPv64_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/4419 Author: Roman Lumetsberger ' IPv64_API="https://ipv64.net/api" ######## Public functions ###################### #Usage: dns_ipv64_add _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_ipv64_add() { fulldomain=$1 txtvalue=$2 IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}" if [ -z "$IPv64_Token" ]; then _err "You must export variable: IPv64_Token" _err "The API Key for your IPv64 account is necessary." _err "You can look it up in your IPv64 account." return 1 fi # Now save the credentials. _saveaccountconf_mutable IPv64_Token "$IPv64_Token" if ! _get_root "$fulldomain"; then _err "invalid domain" "$fulldomain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # convert to lower case _domain="$(echo "$_domain" | _lower_case)" _sub_domain="$(echo "$_sub_domain" | _lower_case)" # Now add the TXT record _info "Trying to add TXT record" if _ipv64_rest "POST" "add_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then _info "TXT record has been successfully added." return 0 else _err "Errors happened during adding the TXT record, response=$_response" return 1 fi } #Usage: fulldomain txtvalue #Usage: dns_ipv64_rm _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" #Remove the txt record after validation. dns_ipv64_rm() { fulldomain=$1 txtvalue=$2 IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}" if [ -z "$IPv64_Token" ]; then _err "You must export variable: IPv64_Token" _err "The API Key for your IPv64 account is necessary." _err "You can look it up in your IPv64 account." return 1 fi if ! _get_root "$fulldomain"; then _err "invalid domain" "$fulldomain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # convert to lower case _domain="$(echo "$_domain" | _lower_case)" _sub_domain="$(echo "$_sub_domain" | _lower_case)" # Now delete the TXT record _info "Trying to delete TXT record" if _ipv64_rest "DELETE" "del_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then _info "TXT record has been successfully deleted." return 0 else _err "Errors happened during deleting the TXT record, response=$_response" return 1 fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain="$1" i=1 p=1 _ipv64_get "get_domains" domain_data=$_response while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi #if _contains "$domain_data" "\""$h"\"\:"; then if _contains "$domain_data" "\"""$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 } #send get request to api # $1 has to set the api-function _ipv64_get() { url="$IPv64_API?$1" export _H1="Authorization: Bearer $IPv64_Token" _response=$(_get "$url") _response="$(echo "$_response" | _normalizeJson)" if _contains "$_response" "429 Too Many Requests"; then _info "API throttled, sleeping to reset the limit" _sleep 10 _response=$(_get "$url") _response="$(echo "$_response" | _normalizeJson)" fi } _ipv64_rest() { url="$IPv64_API" export _H1="Authorization: Bearer $IPv64_Token" export _H2="Content-Type: application/x-www-form-urlencoded" _response=$(_post "$2" "$url" "" "$1") if _contains "$_response" "429 Too Many Requests"; then _info "API throttled, sleeping to reset the limit" _sleep 10 _response=$(_post "$2" "$url" "" "$1") fi if ! _contains "$_response" "\"info\":\"success\""; then return 1 fi _debug2 response "$_response" return 0 } acme.sh-3.1.0/dnsapi/dns_ispconfig.sh000077500000000000000000000166431472032365200175110ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ispconfig_info='ISPConfig Server API Site: ISPConfig.org Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ispconfig Options: ISPC_User Remote User ISPC_Password Remote Password ISPC_Api API URL. E.g. "https://ispc.domain.tld:8080/remote/json.php" ISPC_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept ' # ISPConfig 3.1 API # User must provide login data and URL to the ISPConfig installation incl. port. # The remote user in ISPConfig must have access to: # - DNS txt Functions # - DNS zone functions # - Client functions ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_ispconfig_add() { fulldomain="${1}" txtvalue="${2}" _debug "Calling: dns_ispconfig_add() '${fulldomain}' '${txtvalue}'" _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt } #Usage: dns_myapi_rm _acme-challenge.www.domain.com dns_ispconfig_rm() { fulldomain="${1}" _debug "Calling: dns_ispconfig_rm() '${fulldomain}'" _ISPC_credentials && _ISPC_login && _ISPC_rmTxt } #################### Private functions below ################################## _ISPC_credentials() { ISPC_User="${ISPC_User:-$(_readaccountconf_mutable ISPC_User)}" ISPC_Password="${ISPC_Password:-$(_readaccountconf_mutable ISPC_Password)}" ISPC_Api="${ISPC_Api:-$(_readaccountconf_mutable ISPC_Api)}" ISPC_Api_Insecure="${ISPC_Api_Insecure:-$(_readaccountconf_mutable ISPC_Api_Insecure)}" if [ -z "${ISPC_User}" ] || [ -z "${ISPC_Password}" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then ISPC_User="" ISPC_Password="" ISPC_Api="" ISPC_Api_Insecure="" _err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again." return 1 else _saveaccountconf_mutable ISPC_User "${ISPC_User}" _saveaccountconf_mutable ISPC_Password "${ISPC_Password}" _saveaccountconf_mutable ISPC_Api "${ISPC_Api}" _saveaccountconf_mutable ISPC_Api_Insecure "${ISPC_Api_Insecure}" # Set whether curl should use secure or insecure mode export HTTPS_INSECURE="${ISPC_Api_Insecure}" fi } _ISPC_login() { _info "Getting Session ID" curData="{\"username\":\"${ISPC_User}\",\"password\":\"${ISPC_Password}\",\"client_login\":false}" curResult="$(_post "${curData}" "${ISPC_Api}?login")" _debug "Calling _ISPC_login: '${curData}' '${ISPC_Api}?login'" _debug "Result of _ISPC_login: '$curResult'" if _contains "${curResult}" '"code":"ok"'; then sessionID=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _info "Retrieved Session ID." _debug "Session ID: '${sessionID}'" else _err "Couldn't retrieve the Session ID." return 1 fi } _ISPC_getZoneInfo() { _info "Getting Zoneinfo" zoneEnd=false curZone="${fulldomain}" while [ "${zoneEnd}" = false ]; do # we can strip the first part of the fulldomain, since it's just the _acme-challenge string curZone="${curZone#*.}" # suffix . needed for zone -> domain.tld. curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}" curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")" _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?dns_zone_get'" _debug "Result of _ISPC_getZoneInfo: '$curResult'" if _contains "${curResult}" '"id":"'; then zoneFound=true zoneEnd=true _info "Retrieved zone data." _debug "Zone data: '${curResult}'" fi if [ "${curZone#*.}" != "$curZone" ]; then _debug2 "$curZone still contains a '.' - so we can check next higher level" else zoneEnd=true _err "Couldn't retrieve zone data." return 1 fi done if [ "${zoneFound}" ]; then server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Server ID: '${server_id}'" case "${server_id}" in '' | *[!0-9]*) _err "Server ID is not numeric." return 1 ;; *) _info "Retrieved Server ID" ;; esac zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Zone: '${zone}'" case "${zone}" in '' | *[!0-9]*) _err "Zone ID is not numeric." return 1 ;; *) _info "Retrieved Zone ID" ;; esac sys_userid=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "SYS User ID: '${sys_userid}'" case "${sys_userid}" in '' | *[!0-9]*) _err "SYS User ID is not numeric." return 1 ;; *) _info "Retrieved SYS User ID." ;; esac zoneFound="" zoneEnd="" fi # Need to get client_id as it is different from sys_userid curData="{\"session_id\":\"${sessionID}\",\"sys_userid\":\"${sys_userid}\"}" curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")" _debug "Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'" _debug "Result of _ISPC_ClientGetID: '$curResult'" client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | tr -d '{}') _debug "Client ID: '${client_id}'" case "${client_id}" in '' | *[!0-9]*) _err "Client ID is not numeric." return 1 ;; *) _info "Retrieved Client ID." ;; esac } _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}},\"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'" record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Record ID: '${record_id}'" case "${record_id}" in '' | *[!0-9]*) _err "Couldn't add ACME Challenge TXT record to zone." return 1 ;; *) _info "Added ACME Challenge TXT record to zone." ;; esac } _ISPC_rmTxt() { # Need to get the record ID. curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"name\":\"${fulldomain}.\",\"type\":\"TXT\"}}" curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_get")" _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_get'" _debug "Result of _ISPC_rmTxt: '$curResult'" if _contains "${curResult}" '"code":"ok"'; then record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Record ID: '${record_id}'" case "${record_id}" in '' | *[!0-9]*) _err "Record ID is not numeric." return 1 ;; *) unset IFS _info "Retrieved 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'" if _contains "${curResult}" '"code":"ok"'; then _info "Removed ACME Challenge TXT record from zone." else _err "Couldn't remove ACME Challenge TXT record from zone." return 1 fi ;; esac fi } acme.sh-3.1.0/dnsapi/dns_jd.sh000066400000000000000000000206571472032365200161220ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_jd_info='jdcloud.com Site: jdcloud.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_jd Options: JD_ACCESS_KEY_ID Access key ID JD_ACCESS_KEY_SECRET Access key secret JD_REGION Region. E.g. "cn-north-1" Issues: github.com/acmesh-official/acme.sh/issues/2388 ' _JD_ACCOUNT="https://uc.jdcloud.com/account/accesskey" _JD_PROD="clouddnsservice" _JD_API="jdcloud-api.com" _JD_API_VERSION="v1" _JD_DEFAULT_REGION="cn-north-1" _JD_HOST="$_JD_PROD.$_JD_API" ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_jd_add() { fulldomain=$1 txtvalue=$2 JD_ACCESS_KEY_ID="${JD_ACCESS_KEY_ID:-$(_readaccountconf_mutable JD_ACCESS_KEY_ID)}" JD_ACCESS_KEY_SECRET="${JD_ACCESS_KEY_SECRET:-$(_readaccountconf_mutable JD_ACCESS_KEY_SECRET)}" JD_REGION="${JD_REGION:-$(_readaccountconf_mutable JD_REGION)}" if [ -z "$JD_ACCESS_KEY_ID" ] || [ -z "$JD_ACCESS_KEY_SECRET" ]; then JD_ACCESS_KEY_ID="" JD_ACCESS_KEY_SECRET="" _err "You haven't specifed the jdcloud api key id or api key secret yet." _err "Please create your key and try again. see $(__green $_JD_ACCOUNT)" return 1 fi _saveaccountconf_mutable JD_ACCESS_KEY_ID "$JD_ACCESS_KEY_ID" _saveaccountconf_mutable JD_ACCESS_KEY_SECRET "$JD_ACCESS_KEY_SECRET" if [ -z "$JD_REGION" ]; then _debug "Using default region: $_JD_DEFAULT_REGION" JD_REGION="$_JD_DEFAULT_REGION" else _saveaccountconf_mutable JD_REGION "$JD_REGION" fi _JD_BASE_URI="$_JD_API_VERSION/regions/$JD_REGION" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" #_debug "Getting getViewTree" _debug "Adding records" _addrr="{\"req\":{\"hostRecord\":\"$_sub_domain\",\"hostValue\":\"$txtvalue\",\"ttl\":300,\"type\":\"TXT\",\"viewValue\":-1},\"regionId\":\"$JD_REGION\",\"domainId\":\"$_domain_id\"}" #_addrr='{"req":{"hostRecord":"xx","hostValue":"\"value4\"","jcloudRes":false,"mxPriority":null,"port":null,"ttl":300,"type":"TXT","weight":null,"viewValue":-1},"regionId":"cn-north-1","domainId":"8824"}' if jd_rest POST "domain/$_domain_id/RRAdd" "" "$_addrr"; then _rid="$(echo "$response" | tr '{},' '\n' | grep '"id":' | cut -d : -f 2)" if [ -z "$_rid" ]; then _err "Can not find record id from the result." return 1 fi _info "TXT record added successfully." _srid="$(_readdomainconf "JD_CLOUD_RIDS")" if [ "$_srid" ]; then _rid="$_srid,$_rid" fi _savedomainconf "JD_CLOUD_RIDS" "$_rid" return 0 fi return 1 } dns_jd_rm() { fulldomain=$1 txtvalue=$2 JD_ACCESS_KEY_ID="${JD_ACCESS_KEY_ID:-$(_readaccountconf_mutable JD_ACCESS_KEY_ID)}" JD_ACCESS_KEY_SECRET="${JD_ACCESS_KEY_SECRET:-$(_readaccountconf_mutable JD_ACCESS_KEY_SECRET)}" JD_REGION="${JD_REGION:-$(_readaccountconf_mutable JD_REGION)}" if [ -z "$JD_REGION" ]; then _debug "Using default region: $_JD_DEFAULT_REGION" JD_REGION="$_JD_DEFAULT_REGION" fi _JD_BASE_URI="$_JD_API_VERSION/regions/$JD_REGION" _info "Getting existing records for $fulldomain" _srid="$(_readdomainconf "JD_CLOUD_RIDS")" _debug _srid "$_srid" if [ -z "$_srid" ]; then _err "Not rid skip" return 0 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _cleardomainconf JD_CLOUD_RIDS _aws_tmpl_xml="{\"ids\":[$_srid],\"action\":\"del\",\"regionId\":\"$JD_REGION\",\"domainId\":\"$_domain_id\"}" if jd_rest POST "domain/$_domain_id/RROperate" "" "$_aws_tmpl_xml" && _contains "$response" "\"code\":\"OK\""; then _info "TXT record deleted successfully." return 0 fi return 1 } #################### Private functions below ################################## _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug2 "Checking domain: $h" if ! jd_rest GET "domain"; then _err "error get domain list" return 1 fi if [ -z "$h" ]; then #not valid _err "Invalid domain" return 1 fi if _contains "$response" "\"domainName\":\"$h\""; then hostedzone="$(echo "$response" | tr '{}' '\n' | grep "\"domainName\":\"$h\"")" _debug hostedzone "$hostedzone" if [ "$hostedzone" ]; then _domain_id="$(echo "$hostedzone" | tr ',' '\n' | grep "\"id\":" | cut -d : -f 2)" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi fi _err "Can't find domain with id: $h" return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } #method uri qstr data jd_rest() { mtd="$1" ep="$2" qsr="$3" data="$4" _debug mtd "$mtd" _debug ep "$ep" _debug qsr "$qsr" _debug data "$data" CanonicalURI="/$_JD_BASE_URI/$ep" _debug2 CanonicalURI "$CanonicalURI" CanonicalQueryString="$qsr" _debug2 CanonicalQueryString "$CanonicalQueryString" RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")" #RequestDate="20190713T082155Z" ###################################################### _debug2 RequestDate "$RequestDate" export _H1="X-Jdcloud-Date: $RequestDate" RequestNonce="2bd0852a-8bae-4087-b2d5-$(_time)" #RequestNonce="894baff5-72d4-4244-883a-7b2eb51e7fbe" ################################# _debug2 RequestNonce "$RequestNonce" export _H2="X-Jdcloud-Nonce: $RequestNonce" if [ "$data" ]; then CanonicalHeaders="content-type:application/json\n" SignedHeaders="content-type;" else CanonicalHeaders="" SignedHeaders="" fi CanonicalHeaders="${CanonicalHeaders}host:$_JD_HOST\nx-jdcloud-date:$RequestDate\nx-jdcloud-nonce:$RequestNonce\n" SignedHeaders="${SignedHeaders}host;x-jdcloud-date;x-jdcloud-nonce" _debug2 CanonicalHeaders "$CanonicalHeaders" _debug2 SignedHeaders "$SignedHeaders" Hash="sha256" RequestPayload="$data" _debug2 RequestPayload "$RequestPayload" RequestPayloadHash="$(printf "%s" "$RequestPayload" | _digest "$Hash" hex | _lower_case)" _debug2 RequestPayloadHash "$RequestPayloadHash" CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$RequestPayloadHash" _debug2 CanonicalRequest "$CanonicalRequest" HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)" _debug2 HashedCanonicalRequest "$HashedCanonicalRequest" Algorithm="JDCLOUD2-HMAC-SHA256" _debug2 Algorithm "$Algorithm" RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)" _debug2 RequestDateOnly "$RequestDateOnly" Region="$JD_REGION" Service="$_JD_PROD" CredentialScope="$RequestDateOnly/$Region/$Service/jdcloud2_request" _debug2 CredentialScope "$CredentialScope" StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest" _debug2 StringToSign "$StringToSign" kSecret="JDCLOUD2$JD_ACCESS_KEY_SECRET" _secure_debug2 kSecret "$kSecret" kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")" _secure_debug2 kSecretH "$kSecretH" kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)" _debug2 kDateH "$kDateH" kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)" _debug2 kRegionH "$kRegionH" kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)" _debug2 kServiceH "$kServiceH" kSigningH="$(printf "%s" "jdcloud2_request" | _hmac "$Hash" "$kServiceH" hex)" _debug2 kSigningH "$kSigningH" signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)" _debug2 signature "$signature" Authorization="$Algorithm Credential=$JD_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature" _debug2 Authorization "$Authorization" _H3="Authorization: $Authorization" _debug _H3 "$_H3" url="https://$_JD_HOST$CanonicalURI" if [ "$qsr" ]; then url="https://$_JD_HOST$CanonicalURI?$qsr" fi if [ "$mtd" = "GET" ]; then response="$(_get "$url")" else response="$(_post "$data" "$url" "" "$mtd" "application/json")" fi _ret="$?" _debug2 response "$response" if [ "$_ret" = "0" ]; then if _contains "$response" "\"error\""; then _err "Response error:$response" return 1 fi fi return "$_ret" } acme.sh-3.1.0/dnsapi/dns_joker.sh000066400000000000000000000060571472032365200166350ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_joker_info='Joker.com Site: Joker.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_joker Options: JOKER_USERNAME Username JOKER_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/2840 Author: ' JOKER_API="https://svc.joker.com/nic/replace" ######## Public functions ##################### #Usage: dns_joker_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_joker_add() { fulldomain=$1 txtvalue=$2 JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}" JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}" if [ -z "$JOKER_USERNAME" ] || [ -z "$JOKER_PASSWORD" ]; then _err "No Joker.com username and password specified." return 1 fi _saveaccountconf_mutable JOKER_USERNAME "$JOKER_USERNAME" _saveaccountconf_mutable JOKER_PASSWORD "$JOKER_PASSWORD" if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi _info "Adding TXT record" if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=$txtvalue"; then if _startswith "$response" "OK"; then _info "Added, OK" return 0 fi fi _err "Error adding TXT record." return 1 } #fulldomain txtvalue dns_joker_rm() { fulldomain=$1 txtvalue=$2 JOKER_USERNAME="${JOKER_USERNAME:-$(_readaccountconf_mutable JOKER_USERNAME)}" JOKER_PASSWORD="${JOKER_PASSWORD:-$(_readaccountconf_mutable JOKER_PASSWORD)}" if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi _info "Removing TXT record" # TXT record is removed by setting its value to empty. if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value="; then if _startswith "$response" "OK"; then _info "Removed, OK" return 0 fi fi _err "Error removing TXT record." return 1 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { fulldomain=$1 i=1 while true; do h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then return 1 fi # Try to remove a test record. With correct root domain, username and password this will return "OK: ..." regardless # of record in question existing or not. if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$h&label=jokerTXTUpdateTest&type=TXT&value="; then if _startswith "$response" "OK"; then _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")" _domain=$h return 0 fi fi i=$(_math "$i" + 1) done _debug "Root domain not found" return 1 } _joker_rest() { data="$1" _debug data "$data" if ! response="$(_post "$data" "$JOKER_API" "" "POST")"; then _err "Error POSTing" return 1 fi _debug response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_kappernet.sh000066400000000000000000000112101472032365200174770ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_kappernet_info='kapper.net Site: kapper.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_kappernet Options: KAPPERNETDNS_Key API Key KAPPERNETDNS_Secret API Secret Issues: github.com/acmesh-official/acme.sh/issues/2977 ' ############################################################################### # called with # fullhostname: something.example.com # txtvalue: someacmegenerated string dns_kappernet_add() { fullhostname=$1 txtvalue=$2 KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}" KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}" KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret" if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then _err "Please specify your kapper.net api key and secret." _err "If you have not received yours - send your mail to" _err "support@kapper.net to get your key and secret." return 1 fi #store the api key and email to the account conf file. _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key" _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret" _debug "Checking Domain ..." if ! _get_root "$fullhostname"; then _err "invalid domain" return 1 fi _debug _sub_domain "SUBDOMAIN: $_sub_domain" _debug _domain "DOMAIN: $_domain" _info "Trying to add TXT DNS Record" data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D" if _kappernet_api GET "action=new&subject=$_domain&data=$data"; then if _contains "$response" "{\"OK\":true"; then _info "Waiting 1 second for DNS to spread the new record" _sleep 1 return 0 else _err "Error creating a TXT DNS Record: $fullhostname TXT $txtvalue" _err "Error Message: $response" return 1 fi fi _err "Failed creating TXT Record" } ############################################################################### # called with # fullhostname: something.example.com dns_kappernet_rm() { fullhostname=$1 txtvalue=$2 KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}" KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}" KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret" if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then _err "Please specify your kapper.net api key and secret." _err "If you have not received yours - send your mail to" _err "support@kapper.net to get your key and secret." return 1 fi #store the api key and email to the account conf file. _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key" _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret" _info "Trying to remove the TXT Record: $fullhostname containing $txtvalue" data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D" if _kappernet_api GET "action=del&subject=$fullhostname&data=$data"; then if _contains "$response" "{\"OK\":true"; then return 0 else _err "Error deleting DNS Record: $fullhostname containing $txtvalue" _err "Problem: $response" return 1 fi fi _err "Problem deleting TXT DNS record" } #################### Private functions below ################################## # called with hostname # e.g._acme-challenge.www.domain.com returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi if ! _kappernet_api GET "action=list&subject=$h"; then return 1 fi if _contains "$response" '"OK":false'; then _debug "$h not found" else _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi p="$i" i=$(_math "$i" + 1) done return 1 } ################################################################################ # calls the kapper.net DNS Panel API # with # method # param _kappernet_api() { method=$1 param="$2" _debug param "PARAMETER=$param" url="$KAPPERNETDNS_Api&$param" _debug url "URL=$url" if [ "$method" = "GET" ]; then response="$(_get "$url")" else _err "Unsupported method or missing Secret/Key" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_kas.sh000077500000000000000000000265271472032365200163100ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_kas_info='All-inkl Kas Server Site: kas.all-inkl.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_kas Options: KAS_Login API login name KAS_Authtype API auth type. Default: "plain" KAS_Authdata API auth data Issues: github.com/acmesh-official/acme.sh/issues/2715 Author: squared GmbH , Martin Kammerlander , Marc-Oliver Lange ' ######################################################################## KAS_Api_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasApi.wsdl")" KAS_Api="$(echo "$KAS_Api_GET" | tr -d ' ' | grep -i "//g")" _info "[KAS] -> API URL $KAS_Api" KAS_Auth_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasAuth.wsdl")" KAS_Auth="$(echo "$KAS_Auth_GET" | tr -d ' ' | grep -i "//g")" _info "[KAS] -> AUTH URL $KAS_Auth" KAS_default_ratelimit=5 # TODO - Every response delivers a ratelimit (seconds) where KASAPI is blocking a request. ######## Public functions ##################### dns_kas_add() { _fulldomain=$1 _txtvalue=$2 _info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook" _info "[KAS] -> Check and Save Props" _check_and_save _info "[KAS] -> Adding $_fulldomain DNS TXT entry on all-inkl.com/Kasserver" _info "[KAS] -> Retriving Credential Token" _get_credential_token _info "[KAS] -> Checking Zone and Record_Name" _get_zone_and_record_name "$_fulldomain" _info "[KAS] -> Checking for existing Record entries" _get_record_id # If there is a record_id, delete the entry if [ -n "$_record_id" ]; then _info "[KAS] -> Existing records found. Now deleting old entries" for i in $_record_id; do _delete_RecordByID "$i" done else _info "[KAS] -> No record found." fi _info "[KAS] -> Creating TXT DNS record" action="add_dns_settings" kasReqParam="\"record_name\":\"$_record_name\"" kasReqParam="$kasReqParam,\"record_type\":\"TXT\"" kasReqParam="$kasReqParam,\"record_data\":\"$_txtvalue\"" kasReqParam="$kasReqParam,\"record_aux\":\"0\"" kasReqParam="$kasReqParam,\"zone_host\":\"$_zone\"" response="$(_callAPI "$action" "$kasReqParam")" _debug2 "[KAS] -> Response" "$response" if [ -z "$response" ]; then _info "[KAS] -> Response was empty, please check manually." return 1 elif _contains "$response" ""; then faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")" case "${faultstring}" in "record_already_exists") _info "[KAS] -> The record already exists, which must not be a problem. Please check manually." ;; *) _err "[KAS] -> An error =>$faultstring<= occurred, please check manually." return 1 ;; esac elif ! _contains "$response" "ReturnStringTRUE"; then _err "[KAS] -> An unknown error occurred, please check manually." return 1 fi return 0 } dns_kas_rm() { _fulldomain=$1 _txtvalue=$2 _info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook" _info "[KAS] -> Check and Save Props" _check_and_save _info "[KAS] -> Cleaning up after All-inkl/Kasserver hook" _info "[KAS] -> Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver" _info "[KAS] -> Retriving Credential Token" _get_credential_token _info "[KAS] -> Checking Zone and Record_Name" _get_zone_and_record_name "$_fulldomain" _info "[KAS] -> Getting Record ID" _get_record_id _info "[KAS] -> Removing entries with ID: $_record_id" # If there is a record_id, delete the entry if [ -n "$_record_id" ]; then for i in $_record_id; do _delete_RecordByID "$i" done else # Cannot delete or unkown error _info "[KAS] -> No record_id found that can be deleted. Please check manually." fi return 0 } ########################## PRIVATE FUNCTIONS ########################### # Delete Record ID _delete_RecordByID() { recId=$1 action="delete_dns_settings" kasReqParam="\"record_id\":\"$recId\"" response="$(_callAPI "$action" "$kasReqParam")" _debug2 "[KAS] -> Response" "$response" if [ -z "$response" ]; then _info "[KAS] -> Response was empty, please check manually." return 1 elif _contains "$response" ""; then faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")" case "${faultstring}" in "record_id_not_found") _info "[KAS] -> The record was not found, which perhaps is not a problem. Please check manually." ;; *) _err "[KAS] -> An error =>$faultstring<= occurred, please check manually." return 1 ;; esac elif ! _contains "$response" "ReturnStringTRUE"; then _err "[KAS] -> An unknown error occurred, please check manually." return 1 fi } # Checks for the ENV variables and saves them _check_and_save() { KAS_Login="${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}" KAS_Authtype="${KAS_Authtype:-$(_readaccountconf_mutable KAS_Authtype)}" KAS_Authdata="${KAS_Authdata:-$(_readaccountconf_mutable KAS_Authdata)}" if [ -z "$KAS_Login" ] || [ -z "$KAS_Authtype" ] || [ -z "$KAS_Authdata" ]; then KAS_Login= KAS_Authtype= KAS_Authdata= _err "[KAS] -> No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables." return 1 fi _saveaccountconf_mutable KAS_Login "$KAS_Login" _saveaccountconf_mutable KAS_Authtype "$KAS_Authtype" _saveaccountconf_mutable KAS_Authdata "$KAS_Authdata" return 0 } # Gets back the base domain/zone and record name. # See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide _get_zone_and_record_name() { action="get_domains" response="$(_callAPI "$action")" _debug2 "[KAS] -> Response" "$response" if [ -z "$response" ]; then _info "[KAS] -> Response was empty, please check manually." return 1 elif _contains "$response" ""; then faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")" _err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually." return 1 fi zonen="$(echo "$response" | sed 's//\n/g' | sed -r 's/(.*domain_name<\/key>)(.*)(<\/value.*)/\2/' | sed '/^ Zone:" "$_zone" _debug "[KAS] -> Domain:" "$domain" _debug "[KAS] -> Record_Name:" "$_record_name" return 0 } # Retrieve the DNS record ID _get_record_id() { action="get_dns_settings" kasReqParam="\"zone_host\":\"$_zone\"" response="$(_callAPI "$action" "$kasReqParam")" _debug2 "[KAS] -> Response" "$response" if [ -z "$response" ]; then _info "[KAS] -> Response was empty, please check manually." return 1 elif _contains "$response" ""; then faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")" _err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually." return 1 fi _record_id="$(echo "$response" | tr -d '\n\r' | sed "s//\n/g" | grep -i "$_record_name" | grep -i ">TXT<" | sed "s/record_id<\/key>/=>/g" | grep -i "$_txtvalue" | sed "s/<\/value><\/item>/\n/g" | grep "=>" | sed "s/=>//g")" _debug "[KAS] -> Record Id: " "$_record_id" return 0 } # Retrieve credential token _get_credential_token() { baseParamAuth="\"kas_login\":\"$KAS_Login\"" baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"$KAS_Authtype\"" baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$KAS_Authdata\"" baseParamAuth="$baseParamAuth,\"session_lifetime\":600" baseParamAuth="$baseParamAuth,\"session_update_lifetime\":\"Y\"" data='{' data="$data$baseParamAuth}" _debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API." _sleep $KAS_default_ratelimit contentType="text/xml" export _H1="SOAPAction: urn:xmethodsKasApiAuthentication#KasAuth" response="$(_post "$data" "$KAS_Auth" "" "POST" "$contentType")" _debug2 "[KAS] -> Response" "$response" if [ -z "$response" ]; then _info "[KAS] -> Response was empty, please check manually." return 1 elif _contains "$response" ""; then faultstring="$(echo "$response" | tr -d '\n\r' | sed "s//\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")" _err "[KAS] -> Could not retrieve login token or antoher error =>$faultstring<= occurred, please check manually." return 1 fi _credential_token="$(echo "$response" | tr '\n' ' ' | sed 's/.*return xsi:type="xsd:string">\(.*\)<\/return>/\1/' | sed 's/<\/ns1:KasAuthResponse\(.*\)Envelope>.*//')" _debug "[KAS] -> Credential Token: " "$_credential_token" return 0 } _callAPI() { kasaction=$1 kasReqParams=$2 baseParamAuth="\"kas_login\":\"$KAS_Login\"" baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"session\"" baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$_credential_token\"" data='{' data="$data$baseParamAuth,\"kas_action\":\"$kasaction\"" if [ -n "$kasReqParams" ]; then data="$data,\"KasRequestParams\":{$kasReqParams}" fi data="$data}" _debug2 "[KAS] -> Request" "$data" _debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API." _sleep $KAS_default_ratelimit contentType="text/xml" export _H1="SOAPAction: urn:xmethodsKasApi#KasApi" response="$(_post "$data" "$KAS_Api" "" "POST" "$contentType")" _debug2 "[KAS] -> Response" "$response" echo "$response" } acme.sh-3.1.0/dnsapi/dns_kinghost.sh000066400000000000000000000060751472032365200173510ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_kinghost_info='King.host Domains: KingHost.net KingHost.com.br Site: King.host Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_kinghost Options: KINGHOST_Username Username KINGHOST_Password Password Author: Felipe Keller Braz ' # KingHost API support # # https://api.kinghost.net/doc/ # 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 exist #We are creating a new txt record here, so we expect the "ok" status if ! echo "$response" | grep '"status":"ok"' >/dev/null; then _err "Error" _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 } acme.sh-3.1.0/dnsapi/dns_knot.sh000066400000000000000000000043661472032365200164770ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_knot_info='Knot Server knsupdate Site: www.knot-dns.cz/docs/2.5/html/man_knsupdate.html Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_knot Options: KNOT_SERVER Server hostname. Default: "localhost". KNOT_KEY File path to TSIG key ' # See also dns_nsupdate.sh ######## Public functions ##################### #Usage: dns_knot_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_knot_add() { fulldomain=$1 txtvalue=$2 _checkKey || return 1 [ -n "${KNOT_SERVER}" ] || KNOT_SERVER="localhost" # save the dns server and key to the account.conf file. _saveaccountconf KNOT_SERVER "${KNOT_SERVER}" _saveaccountconf KNOT_KEY "${KNOT_KEY}" if ! _get_root "$fulldomain"; then _err "Domain does not exist." return 1 fi _info "Adding ${fulldomain}. 60 TXT \"${txtvalue}\"" knsupdate < removeSubdomain %s %s %s %s ' "$LOOPIA_User" "$Encoded_Password" "$_domain" "$_sub_domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" "OK"; then err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/') _err "Error could not get txt records: $err_response" return 1 fi } #################### Private functions below ################################## _loopia_load_config() { LOOPIA_Api="${LOOPIA_Api:-$(_readaccountconf_mutable LOOPIA_Api)}" LOOPIA_User="${LOOPIA_User:-$(_readaccountconf_mutable LOOPIA_User)}" LOOPIA_Password="${LOOPIA_Password:-$(_readaccountconf_mutable LOOPIA_Password)}" if [ -z "$LOOPIA_Api" ]; then LOOPIA_Api="$LOOPIA_Api_Default" fi if [ -z "$LOOPIA_User" ] || [ -z "$LOOPIA_Password" ]; then LOOPIA_User="" LOOPIA_Password="" _err "A valid Loopia API user and password not provided." _err "Please provide a valid API user and try again." return 1 fi if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then _err "Password contains a quotation mark or double quotation marks and this is not supported by dns_loopia.sh" return 1 fi Encoded_Password=$(_xml_encode "$LOOPIA_Password") return 0 } _loopia_save_config() { if [ "$LOOPIA_Api" != "$LOOPIA_Api_Default" ]; then _saveaccountconf_mutable LOOPIA_Api "$LOOPIA_Api" fi _saveaccountconf_mutable LOOPIA_User "$LOOPIA_User" _saveaccountconf_mutable LOOPIA_Password "$LOOPIA_Password" } _loopia_get_records() { domain=$1 sub_domain=$2 xml_content=$(printf ' getZoneRecords %s %s %s %s ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" ""; then err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/') _err "Error: $err_response" return 1 fi return 0 } _get_root() { domain=$1 _debug "get root" domain=$1 i=2 p=1 xml_content=$(printf ' getDomains %s %s ' "$LOOPIA_User" "$Encoded_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_add_record() { domain=$1 sub_domain=$2 txtval=$3 xml_content=$(printf ' addZoneRecord %s %s %s %s type TXT priority 0 ttl 300 rdata %s ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain" "$txtval") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" "OK"; then err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/') _err "Error: $err_response" return 1 fi return 0 } _sub_domain_exists() { domain=$1 sub_domain=$2 xml_content=$(printf ' getSubdomains %s %s %s ' "$LOOPIA_User" "$Encoded_Password" "$domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if _contains "$response" "$sub_domain"; then return 0 fi return 1 } _loopia_add_sub_domain() { domain=$1 sub_domain=$2 if _sub_domain_exists "$domain" "$sub_domain"; then return 0 fi xml_content=$(printf ' addSubdomain %s %s %s %s ' "$LOOPIA_User" "$Encoded_Password" "$domain" "$sub_domain") response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" if ! _contains "$response" "OK"; then err_response=$(echo "$response" | sed 's/.*\(.*\)<\/string>.*/\1/') _err "Error: $err_response" return 1 fi return 0 } _xml_encode() { encoded_string=$1 encoded_string=$(echo "$encoded_string" | sed 's/&/\&/') encoded_string=$(echo "$encoded_string" | sed 's//\>/') printf "%s" "$encoded_string" } acme.sh-3.1.0/dnsapi/dns_lua.sh000077500000000000000000000100061472032365200162740ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_lua_info='LuaDNS.com Domains: LuaDNS.net Site: LuaDNS.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_lua Options: LUA_Key API key LUA_Email Email Author: ' LUA_Api="https://api.luadns.com/v1" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_lua_add() { fulldomain=$1 txtvalue=$2 LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}" LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}" LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) if [ -z "$LUA_Key" ] || [ -z "$LUA_Email" ]; then LUA_Key="" LUA_Email="" _err "You don't specify luadns api key and email 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 LUA_Key "$LUA_Key" _saveaccountconf_mutable LUA_Email "$LUA_Email" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then if _contains "$response" "$fulldomain"; then _info "Added" #todo: check if the record takes effect return 0 else _err "Add txt record error." return 1 fi fi } #fulldomain dns_lua_rm() { fulldomain=$1 txtvalue=$2 LUA_Key="${LUA_Key:-$(_readaccountconf_mutable LUA_Key)}" LUA_Email="${LUA_Email:-$(_readaccountconf_mutable LUA_Email)}" LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _LUA_rest GET "zones/${_domain_id}/records" count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | _head_n 1 | cut -d: -f2 | cut -d, -f1) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _LUA_rest DELETE "/zones/$_domain_id/records/$record_id"; then _err "Delete record error." return 1 fi _contains "$response" "$record_id" fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 if ! _LUA_rest GET "zones"; then return 1 fi 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 _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1) _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _LUA_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Accept: application/json" export _H2="Authorization: Basic $LUA_auth" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$LUA_Api/$ep" "" "$m")" else response="$(_get "$LUA_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_maradns.sh000077500000000000000000000044571472032365200171550ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_maradns_info='MaraDNS Server Site: MaraDNS.samiam.org Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_maradns Options: MARA_ZONE_FILE Zone file path. E.g. "/etc/maradns/db.domain.com" MARA_DUENDE_PID_PATH Duende PID Path. E.g. "/run/maradns/etc_maradns_mararc.pid" Issues: github.com/acmesh-official/acme.sh/issues/2072 ' #Usage: dns_maradns_add _acme-challenge.www.domain.com "token" dns_maradns_add() { fulldomain="$1" txtvalue="$2" MARA_ZONE_FILE="${MARA_ZONE_FILE:-$(_readaccountconf_mutable MARA_ZONE_FILE)}" MARA_DUENDE_PID_PATH="${MARA_DUENDE_PID_PATH:-$(_readaccountconf_mutable MARA_DUENDE_PID_PATH)}" _check_zone_file "$MARA_ZONE_FILE" || return 1 _check_duende_pid_path "$MARA_DUENDE_PID_PATH" || return 1 _saveaccountconf_mutable MARA_ZONE_FILE "$MARA_ZONE_FILE" _saveaccountconf_mutable MARA_DUENDE_PID_PATH "$MARA_DUENDE_PID_PATH" printf "%s. TXT '%s' ~\n" "$fulldomain" "$txtvalue" >>"$MARA_ZONE_FILE" _reload_maradns "$MARA_DUENDE_PID_PATH" || return 1 } #Usage: dns_maradns_rm _acme-challenge.www.domain.com "token" dns_maradns_rm() { fulldomain="$1" txtvalue="$2" MARA_ZONE_FILE="${MARA_ZONE_FILE:-$(_readaccountconf_mutable MARA_ZONE_FILE)}" MARA_DUENDE_PID_PATH="${MARA_DUENDE_PID_PATH:-$(_readaccountconf_mutable MARA_DUENDE_PID_PATH)}" _check_zone_file "$MARA_ZONE_FILE" || return 1 _check_duende_pid_path "$MARA_DUENDE_PID_PATH" || return 1 _saveaccountconf_mutable MARA_ZONE_FILE "$MARA_ZONE_FILE" _saveaccountconf_mutable MARA_DUENDE_PID_PATH "$MARA_DUENDE_PID_PATH" _sed_i "/^$fulldomain.\+TXT '$txtvalue' ~/d" "$MARA_ZONE_FILE" _reload_maradns "$MARA_DUENDE_PID_PATH" || return 1 } _check_zone_file() { zonefile="$1" if [ -z "$zonefile" ]; then _err "MARA_ZONE_FILE not passed!" return 1 elif [ ! -w "$zonefile" ]; then _err "MARA_ZONE_FILE not writable: $zonefile" return 1 fi } _check_duende_pid_path() { pidpath="$1" if [ -z "$pidpath" ]; then _err "MARA_DUENDE_PID_PATH not passed!" return 1 fi if [ ! -r "$pidpath" ]; then _err "MARA_DUENDE_PID_PATH not readable: $pidpath" return 1 fi } _reload_maradns() { pidpath="$1" kill -s HUP -- "$(cat "$pidpath")" if [ $? -ne 0 ]; then _err "Unable to reload MaraDNS, kill returned" return 1 fi } acme.sh-3.1.0/dnsapi/dns_me.sh000066400000000000000000000077271472032365200161310ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_me_info='DnsMadeEasy.com Site: DnsMadeEasy.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_me Options: ME_Key API Key ME_Secret API Secret Author: ' ME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_me_add() { fulldomain=$1 txtvalue=$2 if [ -z "$ME_Key" ] || [ -z "$ME_Secret" ]; then ME_Key="" ME_Secret="" _err "You didn't specify DNSMadeEasy api key and secret yet." _err "Please create you key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf ME_Key "$ME_Key" _saveaccountconf ME_Secret "$ME_Secret" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _me_rest GET "${_domain_id}/records?recordName=$_sub_domain&type=TXT" if ! _contains "$response" "\"totalRecords\":"; then _err "Error" return 1 fi _info "Adding record" if _me_rest POST "$_domain_id/records/" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"; then if printf -- "%s" "$response" | grep \"id\": >/dev/null; then _info "Added" #todo: check if the record takes effect return 0 else _err "Add txt record error." return 1 fi fi } #fulldomain dns_me_rm() { fulldomain=$1 txtvalue=$2 _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _me_rest GET "${_domain_id}/records?recordName=$_sub_domain&type=TXT" count=$(printf "%s\n" "$response" | _egrep_o "\"totalRecords\":[^,]*" | cut -d : -f 2) _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else record_id=$(printf "%s\n" "$response" | _egrep_o ",\"value\":\"..$txtvalue..\",\"id\":[^,]*" | cut -d : -f 3 | head -n 1) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _me_rest DELETE "$_domain_id/records/$record_id"; then _err "Delete record error." return 1 fi _contains "$response" '' fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=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 ! _me_rest GET "name?domainname=$h"; then return 1 fi if _contains "$response" "\"name\":\"$h\""; then _domain_id=$(printf "%s\n" "$response" | sed 's/^{//; s/}$//; s/{.*}//' | sed -r 's/^.*"id":([0-9]+).*$/\1/') if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _me_rest() { m=$1 ep="$2" data="$3" _debug "$ep" cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z") hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) export _H1="x-dnsme-apiKey: $ME_Key" export _H2="x-dnsme-requestDate: $cdate" export _H3="x-dnsme-hmac: $hmac" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$ME_Api/$ep" "" "$m")" else response="$(_get "$ME_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_miab.sh000066400000000000000000000133171472032365200164300ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_miab_info='Mail-in-a-Box Site: MailInaBox.email Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_miab Options: MIAB_Username Admin username MIAB_Password Admin password MIAB_Server Server hostname. FQDN of your_MIAB Server Issues: github.com/acmesh-official/acme.sh/issues/2550 Author: Darven Dissek, William Gertz ' ######## Public functions ##################### #Usage: dns_miab_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_miab_add() { fulldomain=$1 txtvalue=$2 _info "Using miab challenge add" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" #retrieve MIAB environemt vars if ! _retrieve_miab_env; then return 1 fi #check domain and seperate into domain and host if ! _get_root "$fulldomain"; then _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}" return 1 fi _debug2 _sub_domain "$_sub_domain" _debug2 _domain "$_domain" #add the challenge record _api_path="custom/${fulldomain}/txt" _miab_rest "$txtvalue" "$_api_path" "POST" #check if result was good if _contains "$response" "updated DNS"; then _info "Successfully created the txt record" return 0 else _err "Error encountered during record add" _err "$response" return 1 fi } #Usage: dns_miab_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_miab_rm() { fulldomain=$1 txtvalue=$2 _info "Using miab challenge delete" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" #retrieve MIAB environemt vars if ! _retrieve_miab_env; then return 1 fi #check domain and seperate into doamin and host if ! _get_root "$fulldomain"; then _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}" return 1 fi _debug2 _sub_domain "$_sub_domain" _debug2 _domain "$_domain" #Remove the challenge record _api_path="custom/${fulldomain}/txt" _miab_rest "$txtvalue" "$_api_path" "DELETE" #check if result was good if _contains "$response" "updated DNS"; then _info "Successfully removed the txt record" return 0 else _err "Error encountered during record remove" _err "$response" return 1 fi } #################### Private functions below ################################## # #Usage: _get_root _acme-challenge.www.domain.com #Returns: # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { _passed_domain=$1 _debug _passed_domain "$_passed_domain" _i=2 _p=1 #get the zones hosed on MIAB server, must be a json stream _miab_rest "" "zones" "GET" if ! _is_json "$response"; then _err "ERROR fetching domain list" _err "$response" return 1 fi #cycle through the passed domain seperating out a test domain discarding # the subdomain by marching thorugh the dots while true; do _test_domain=$(printf "%s" "$_passed_domain" | cut -d . -f "${_i}"-100) _debug _test_domain "$_test_domain" if [ -z "$_test_domain" ]; then return 1 fi #report found if the test domain is in the json response and # report the subdomain if _contains "$response" "\"$_test_domain\""; then _sub_domain=$(printf "%s" "$_passed_domain" | cut -d . -f 1-"${_p}") _domain=${_test_domain} return 0 fi #cycle to the next dot in the passed domain _p=${_i} _i=$(_math "$_i" + 1) done return 1 } #Usage: _retrieve_miab_env #Returns (from store or environment variables): # MIAB_Username # MIAB_Password # MIAB_Server #retrieve MIAB environment variables, report errors and quit if problems _retrieve_miab_env() { MIAB_Username="${MIAB_Username:-$(_readaccountconf_mutable MIAB_Username)}" MIAB_Password="${MIAB_Password:-$(_readaccountconf_mutable MIAB_Password)}" MIAB_Server="${MIAB_Server:-$(_readaccountconf_mutable MIAB_Server)}" #debug log the environmental variables _debug MIAB_Username "$MIAB_Username" _debug MIAB_Password "$MIAB_Password" _debug MIAB_Server "$MIAB_Server" #check if MIAB environemt vars set and quit if not if [ -z "$MIAB_Username" ] || [ -z "$MIAB_Password" ] || [ -z "$MIAB_Server" ]; then _err "You didn't specify one or more of MIAB_Username, MIAB_Password or MIAB_Server." _err "Please check these environment variables and try again." return 1 fi #save the credentials to the account conf file. _saveaccountconf_mutable MIAB_Username "$MIAB_Username" _saveaccountconf_mutable MIAB_Password "$MIAB_Password" _saveaccountconf_mutable MIAB_Server "$MIAB_Server" return 0 } #Useage: _miab_rest "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" "custom/_acme-challenge.www.domain.com/txt "POST" #Returns: "updated DNS: domain.com" #rest interface MIAB dns _miab_rest() { _data="$1" _api_path="$2" _httpmethod="$3" #encode username and password for basic authentication _credentials="$(printf "%s" "$MIAB_Username:$MIAB_Password" | _base64)" export _H1="Authorization: Basic $_credentials" _url="https://${MIAB_Server}/admin/dns/${_api_path}" _debug2 _data "$_data" _debug _api_path "$_api_path" _debug2 _url "$_url" _debug2 _credentails "$_credentials" _debug _httpmethod "$_httpmethod" if [ "$_httpmethod" = "GET" ]; then response="$(_get "$_url")" else response="$(_post "$_data" "$_url" "" "$_httpmethod")" fi _retcode="$?" if [ "$_retcode" != "0" ]; then _err "MIAB REST authentication failed on $_httpmethod" return 1 fi _debug response "$response" return 0 } #Usage: _is_json "\[\n "mydomain.com"\n]" #Reurns "\[\n "mydomain.com"\n]" #returns the string if it begins and ends with square braces _is_json() { _str="$(echo "$1" | _normalizeJson)" echo "$_str" | grep '^\[.*\]$' >/dev/null 2>&1 } acme.sh-3.1.0/dnsapi/dns_misaka.sh000077500000000000000000000076451472032365200167770ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_misaka_info='Misaka.io Site: Misaka.io Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_misaka Options: Misaka_Key API Key Author: ' Misaka_Api="https://dnsapi.misaka.io/dns" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_misaka_add() { fulldomain=$1 txtvalue=$2 if [ -z "$Misaka_Key" ]; then Misaka_Key="" _err "You didn't specify misaka.io dns api key yet." _err "Please create you key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf Misaka_Key "$Misaka_Key" _debug "checking root zone [$fulldomain]" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}" if ! _contains "$response" "\"results\":"; then _err "Error" return 1 fi count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Adding record" if _misaka_rest POST "zones/${_domain}/recordsets/${_sub_domain}/TXT" "{\"records\":[{\"value\":\"\\\"$txtvalue\\\"\"}],\"filters\":[],\"ttl\":1}"; then _debug response "$response" if _contains "$response" "$_sub_domain"; then _info "Added" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." else _info "Updating record" _misaka_rest PUT "zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true" "{\"records\": [{\"value\": \"\\\"$txtvalue\\\"\"}],\"ttl\":1}" if [ "$?" = "0" ] && _contains "$response" "$_sub_domain"; then _info "Updated!" #todo: check if the record takes effect return 0 fi _err "Update error" return 1 fi } #fulldomain dns_misaka_rm() { fulldomain=$1 txtvalue=$2 _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _misaka_rest GET "zones/${_domain}/recordsets?search=${_sub_domain}" count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else if ! _misaka_rest DELETE "zones/${_domain}/recordsets/${_sub_domain}/TXT"; then _err "Delete record error." return 1 fi _contains "$response" "" fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 if ! _misaka_rest GET "zones?limit=1000"; then return 1 fi 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 } _misaka_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Content-Type: application/json" export _H2="User-Agent: acme.sh/$VER misaka-dns-acmesh/20191213" export _H3="Authorization: Token $Misaka_Key" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$Misaka_Api/$ep" "" "$m")" else response="$(_get "$Misaka_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_myapi.sh000077500000000000000000000025051472032365200166370ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_myapi_info='Custom API Example A sample custom DNS API script. Domains: example.com Site: github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns Options: MYAPI_Token API Token. Get API Token from https://example.com/api/. Optional. Issues: github.com/acmesh-official/acme.sh Author: Neil Pang ' #This file name is "dns_myapi.sh" #So, here must be a method dns_myapi_add() #Which will be called by acme.sh to add the txt record to your api system. #returns 0 means success, otherwise error. ######## Public functions ##################### # Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_myapi_add() { fulldomain=$1 txtvalue=$2 _info "Using myapi" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _err "Not implemented!" return 1 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_myapi_rm() { fulldomain=$1 txtvalue=$2 _info "Using myapi" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" } #################### Private functions below ################################## acme.sh-3.1.0/dnsapi/dns_mydevil.sh000077500000000000000000000054741472032365200172010ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_mydevil_info='MyDevil.net MyDevil.net already supports automatic Lets Encrypt certificates, except for wildcard domains. This script depends on devil command that MyDevil.net provides, which means that it works only on server side. Site: MyDevil.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydevil Issues: github.com/acmesh-official/acme.sh/issues/2079 Author: Marcin Konicki ' ######## Public functions ##################### #Usage: dns_mydevil_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_mydevil_add() { fulldomain=$1 txtvalue=$2 domain="" if ! _exists "devil"; then _err "Could not find 'devil' command." return 1 fi _info "Using mydevil" domain=$(mydevil_get_domain "$fulldomain") if [ -z "$domain" ]; then _err "Invalid domain name: could not find root domain of $fulldomain." return 1 fi # No need to check if record name exists, `devil` always adds new record. # In worst case scenario, we end up with multiple identical records. _info "Adding $fulldomain record for domain $domain" if devil dns add "$domain" "$fulldomain" TXT "$txtvalue"; then _info "Successfully added TXT record, ready for validation." return 0 else _err "Unable to add DNS record." return 1 fi } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_mydevil_rm() { fulldomain=$1 txtvalue=$2 domain="" if ! _exists "devil"; then _err "Could not find 'devil' command." return 1 fi _info "Using mydevil" domain=$(mydevil_get_domain "$fulldomain") if [ -z "$domain" ]; then _err "Invalid domain name: could not find root domain of $fulldomain." return 1 fi # catch one or more numbers num='[0-9][0-9]*' # catch one or more whitespace w=$(printf '[\t ][\t ]*') # catch anything, except newline any='.*' # filter to make sure we do not delete other records validRecords="^${num}${w}${fulldomain}${w}TXT${w}${any}${txtvalue}$" for id in $(devil dns list "$domain" | tail -n+2 | grep "${validRecords}" | cut -w -s -f 1); do _info "Removing record $id from domain $domain" echo "y" | devil dns del "$domain" "$id" || _err "Could not remove DNS record." done } #################### Private functions below ################################## # Usage: domain=$(mydevil_get_domain "_acme-challenge.www.domain.com" || _err "Invalid domain name") # echo $domain mydevil_get_domain() { fulldomain=$1 domain="" for domain in $(devil dns list | cut -w -s -f 1 | tail -n+2); do _debug "Checking domain: $domain" if _endswith "$fulldomain" "$domain"; then _debug "Fulldomain '$fulldomain' matches '$domain'" printf -- "%s" "$domain" return 0 fi done return 1 } acme.sh-3.1.0/dnsapi/dns_mydnsjp.sh000077500000000000000000000115511472032365200172050ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_mydnsjp_info='MyDNS.JP Site: MyDNS.JP Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydnsjp Options: MYDNSJP_MasterID Master ID MYDNSJP_Password Password Author: epgdatacapbon ' ######## Public functions ##################### # Export MyDNS.JP MasterID and Password in following variables... # MYDNSJP_MasterID=MasterID # MYDNSJP_Password=Password MYDNSJP_API="https://www.mydns.jp" #Usage: dns_mydnsjp_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_mydnsjp_add() { fulldomain=$1 txtvalue=$2 _info "Using mydnsjp" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" # Load the credentials from the account conf file MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}" MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}" if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then MYDNSJP_MasterID="" MYDNSJP_Password="" _err "You don't specify mydnsjp api MasterID and Password yet." _err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again." return 1 fi # Save the credentials to the account conf file _saveaccountconf_mutable MYDNSJP_MasterID "$MYDNSJP_MasterID" _saveaccountconf_mutable MYDNSJP_Password "$MYDNSJP_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" if _mydnsjp_api "REGIST" "$_domain" "$txtvalue"; then if printf -- "%s" "$response" | grep "OK." >/dev/null; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_mydnsjp_rm() { fulldomain=$1 txtvalue=$2 _info "Removing TXT record" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" # Load the credentials from the account conf file MYDNSJP_MasterID="${MYDNSJP_MasterID:-$(_readaccountconf_mutable MYDNSJP_MasterID)}" MYDNSJP_Password="${MYDNSJP_Password:-$(_readaccountconf_mutable MYDNSJP_Password)}" if [ -z "$MYDNSJP_MasterID" ] || [ -z "$MYDNSJP_Password" ]; then MYDNSJP_MasterID="" MYDNSJP_Password="" _err "You don't specify mydnsjp api MasterID and Password yet." _err "Please export as MYDNSJP_MasterID / MYDNSJP_Password and try again." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" if _mydnsjp_api "DELETE" "$_domain" "$txtvalue"; then if printf -- "%s" "$response" | grep "OK." >/dev/null; then _info "Deleted, OK" return 0 else _err "Delete txt record error." return 1 fi fi _err "Delete txt record error." return 1 } #################### Private functions below ################################## # _acme-challenge.www.domain.com # returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { fulldomain=$1 i=2 p=1 # Get the root domain _mydnsjp_retrieve_domain if [ "$?" != "0" ]; then # not valid return 1 fi while true; do _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) if [ -z "$_domain" ]; then # not valid return 1 fi if [ "$_domain" = "$_root_domain" ]; then _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$p") return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } # Retrieve the root domain # returns 0 success _mydnsjp_retrieve_domain() { _debug "Login to MyDNS.JP" response="$(_post "MENU=100&masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/members/")" cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)" # If cookies is not empty then logon successful if [ -z "$cookie" ]; then _err "Fail to get a cookie." return 1 fi _root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/') _debug _root_domain "$_root_domain" if [ -z "$_root_domain" ]; then _err "Fail to get the root domain." return 1 fi return 0 } _mydnsjp_api() { cmd=$1 domain=$2 txtvalue=$3 # Base64 encode the credentials credentials=$(printf "%s:%s" "$MYDNSJP_MasterID" "$MYDNSJP_Password" | _base64) # Construct the HTTP Authorization header export _H1="Content-Type: application/x-www-form-urlencoded" export _H2="Authorization: Basic ${credentials}" response="$(_post "CERTBOT_DOMAIN=$domain&CERTBOT_VALIDATION=$txtvalue&EDIT_CMD=$cmd" "$MYDNSJP_API/directedit.html")" if [ "$?" != "0" ]; then _err "error $domain" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_mythic_beasts.sh000077500000000000000000000155071472032365200203640ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_mythic_beasts_info='Mythic-Beasts.com Site: Mythic-Beasts.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mythic_beasts Options: MB_AK API Key MB_AS API Secret Issues: github.com/acmesh-official/acme.sh/issues/3848 ' # Mythic Beasts is a long-standing UK service provider using standards-based OAuth2 authentication # To test: ./acme.sh --dns dns_mythic_beasts --test --debug 1 --output-insecure --issue --domain domain.com # Cannot retest once cert is issued # OAuth2 tokens only valid for 300 seconds so we do not store # NOTE: This will remove all TXT records matching the fulldomain, not just the added ones (_acme-challenge.www.domain.com) # Test OAuth2 credentials #MB_AK="aaaaaaaaaaaaaaaa" #MB_AS="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" # URLs MB_API='https://api.mythic-beasts.com/dns/v2/zones' MB_AUTH='https://auth.mythic-beasts.com/login' ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_mythic_beasts_add() { fulldomain=$1 txtvalue=$2 _info "MYTHIC BEASTS Adding record $fulldomain = $txtvalue" if ! _initAuth; then return 1 fi if ! _get_root "$fulldomain"; then return 1 fi # method path body_data if _mb_rest POST "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then if _contains "$response" "1 records added"; then _info "Added, verifying..." # Max 120 seconds to publish for i in $(seq 1 6); do # Retry on error if ! _mb_rest GET "$_domain/records/$_sub_domain/TXT?verify"; then _sleep 20 else _info "Record published!" return 0 fi done else _err "\n$response" fi fi _err "Add txt record error." return 1 } #Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_mythic_beasts_rm() { fulldomain=$1 txtvalue=$2 _info "MYTHIC BEASTS Removing record $fulldomain = $txtvalue" if ! _initAuth; then return 1 fi if ! _get_root "$fulldomain"; then return 1 fi # method path body_data if _mb_rest DELETE "$_domain/records/$_sub_domain/TXT" "$txtvalue"; then _info "Record removed" return 0 fi _err "Remove txt record error." return 1 } #################### Private functions below ################################## #Possible formats: # _acme-challenge.www.example.com # _acme-challenge.example.com # _acme-challenge.example.co.uk # _acme-challenge.www.example.co.uk # _acme-challenge.sub1.sub2.www.example.co.uk # sub1.sub2.example.co.uk # example.com # example.co.uk #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 p=1 _debug "Detect the root zone" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then _err "Domain exhausted" return 1 fi # Use the status errors to find the domain, continue on 403 Access denied # method path body_data _mb_rest GET "$h/records" ret="$?" if [ "$ret" -eq 0 ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" return 0 elif [ "$ret" -eq 1 ]; then return 1 fi p=$i i=$(_math "$i" + 1) if [ "$i" -gt 50 ]; then break fi done _err "Domain too long" return 1 } _initAuth() { MB_AK="${MB_AK:-$(_readaccountconf_mutable MB_AK)}" MB_AS="${MB_AS:-$(_readaccountconf_mutable MB_AS)}" if [ -z "$MB_AK" ] || [ -z "$MB_AS" ]; then MB_AK="" MB_AS="" _err "Please specify an OAuth2 Key & Secret" return 1 fi _saveaccountconf_mutable MB_AK "$MB_AK" _saveaccountconf_mutable MB_AS "$MB_AS" if ! _oauth2; then return 1 fi _info "Checking authentication" _secure_debug access_token "$MB_TK" _sleep 1 # GET a list of zones # method path body_data if ! _mb_rest GET ""; then _err "The token is invalid" return 1 fi _info "Token OK" return 0 } # Github appears to use an outbound proxy for requests which means subsequent requests may not have the same # source IP. The standard Mythic Beasts OAuth2 tokens are tied to an IP, meaning github test requests fail # authentication. This is a work around using an undocumented MB API to obtain a token not tied to an # IP just for the github tests. _oauth2() { if [ "$GITHUB_ACTIONS" = "true" ]; then _oauth2_github else _oauth2_std fi return $? } _oauth2_std() { # HTTP Basic Authentication _H1="Authorization: Basic $(echo "$MB_AK:$MB_AS" | _base64)" _H2="Accepts: application/json" export _H1 _H2 body="grant_type=client_credentials" _info "Getting OAuth2 token..." # body url [needbase64] [POST|PUT|DELETE] [ContentType] response="$(_post "$body" "$MB_AUTH" "" "POST" "application/x-www-form-urlencoded")" if _contains "$response" "\"token_type\":\"bearer\""; then MB_TK="$(echo "$response" | _egrep_o "access_token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')" if [ -z "$MB_TK" ]; then _err "Unable to get access_token" _err "\n$response" return 1 fi else _err "OAuth2 token_type not Bearer" _err "\n$response" return 1 fi _debug2 response "$response" return 0 } _oauth2_github() { _H1="Accepts: application/json" export _H1 body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}" _info "Getting Floating token..." # body url [needbase64] [POST|PUT|DELETE] [ContentType] response="$(_post "$body" "$MB_AUTH" "" "POST" "application/json")" MB_TK="$(echo "$response" | _egrep_o "\"token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')" if [ -z "$MB_TK" ]; then _err "Unable to get token" _err "\n$response" return 1 fi _debug2 response "$response" return 0 } # method path body_data _mb_rest() { # URL encoded body for single API operations m="$1" ep="$2" data="$3" if [ -z "$ep" ]; then _mb_url="$MB_API" else _mb_url="$MB_API/$ep" fi _H1="Authorization: Bearer $MB_TK" _H2="Accepts: application/json" export _H1 _H2 if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then # body url [needbase64] [POST|PUT|DELETE] [ContentType] response="$(_post "data=$data" "$_mb_url" "" "$m" "application/x-www-form-urlencoded")" else response="$(_get "$_mb_url")" fi if [ "$?" != "0" ]; then _err "Request error" return 1 fi header="$(cat "$HTTP_HEADER")" status="$(echo "$header" | _egrep_o "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n")" code="$(echo "$status" | _egrep_o "^[0-9]*")" if [ "$code" -ge 400 ] || _contains "$response" "\"error\"" || _contains "$response" "invalid_client"; then _err "error $status" _err "\n$response" _debug "\n$header" return 2 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_namecheap.sh000077500000000000000000000233571472032365200174510ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_namecheap_info='NameCheap.com Site: NameCheap.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecheap Options: NAMECHEAP_API_KEY API Key NAMECHEAP_USERNAME Username NAMECHEAP_SOURCEIP Source IP Issues: github.com/acmesh-official/acme.sh/issues/2107 ' # Namecheap API # https://www.namecheap.com/support/api/intro.aspx # Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise. ######## Public functions ##################### NAMECHEAP_API="https://api.namecheap.com/xml.response" #Usage: dns_namecheap_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_namecheap_add() { fulldomain=$1 txtvalue=$2 if ! _namecheap_check_config; then _err "$error" return 1 fi if ! _namecheap_set_publicip; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug domain "$_domain" _debug sub_domain "$_sub_domain" _set_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue" } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_namecheap_rm() { fulldomain=$1 txtvalue=$2 if ! _namecheap_set_publicip; then return 1 fi if ! _namecheap_check_config; then _err "$error" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug domain "$_domain" _debug sub_domain "$_sub_domain" _del_namecheap_TXT "$_domain" "$_sub_domain" "$txtvalue" } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { fulldomain=$1 if ! _get_root_by_getList "$fulldomain"; then _debug "Failed domain lookup via domains.getList api call. Trying domain lookup via domains.dns.getHosts api." # The above "getList" api will only return hosts *owned* by the calling user. However, if the calling # user is not the owner, but still has administrative rights, we must query the getHosts api directly. # See this comment and the official namecheap response: https://disq.us/p/1q6v9x9 if ! _get_root_by_getHosts "$fulldomain"; then return 1 fi fi return 0 } _get_root_by_getList() { domain=$1 if ! _namecheap_post "namecheap.domains.getList"; then _err "$error" return 1 fi i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _contains "$h" "\\."; then #not valid return 1 fi if ! _contains "$response" "$h"; then _debug "$h not found" else _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi p="$i" i=$(_math "$i" + 1) done return 1 } _get_root_by_getHosts() { i=100 p=99 while [ "$p" -ne 0 ]; do h=$(printf "%s" "$1" | cut -d . -f "$i"-100) if [ -n "$h" ]; then if _contains "$h" "\\."; then _debug h "$h" if _namecheap_set_tld_sld "$h"; then _sub_domain=$(printf "%s" "$1" | cut -d . -f 1-"$p") _domain="$h" return 0 else _debug "$h not found" fi fi fi i="$p" p=$(_math "$p" - 1) done return 1 } _namecheap_set_publicip() { if [ -z "$NAMECHEAP_SOURCEIP" ]; then _err "No Source IP specified for Namecheap API." _err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP" return 1 else _saveaccountconf NAMECHEAP_SOURCEIP "$NAMECHEAP_SOURCEIP" _debug sourceip "$NAMECHEAP_SOURCEIP" ip=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}') addr=$(echo "$NAMECHEAP_SOURCEIP" | _egrep_o '(http|https):\/\/.*') _debug2 ip "$ip" _debug2 addr "$addr" if [ -n "$ip" ]; then _publicip="$ip" elif [ -n "$addr" ]; then _publicip=$(_get "$addr") else _err "No Source IP specified for Namecheap API." _err "Use your public ip address or an url to retrieve it (e.g. https://ifconfig.co/ip) and export it as NAMECHEAP_SOURCEIP" return 1 fi fi _debug publicip "$_publicip" return 0 } _namecheap_post() { command=$1 data="ApiUser=${NAMECHEAP_USERNAME}&ApiKey=${NAMECHEAP_API_KEY}&ClientIp=${_publicip}&UserName=${NAMECHEAP_USERNAME}&Command=${command}" _debug2 "_namecheap_post data" "$data" response="$(_post "$data" "$NAMECHEAP_API" "" "POST")" _debug2 response "$response" if _contains "$response" "Status=\"ERROR\"" >/dev/null; then error=$(echo "$response" | _egrep_o ">.*<\\/Error>" | cut -d '<' -f 1 | tr -d '>') _err "error $error" return 1 fi return 0 } _namecheap_parse_host() { _host=$1 _debug _host "$_host" _hostid=$(echo "$_host" | _egrep_o ' HostId="[^"]*' | cut -d '"' -f 2) _hostname=$(echo "$_host" | _egrep_o ' Name="[^"]*' | cut -d '"' -f 2) _hosttype=$(echo "$_host" | _egrep_o ' Type="[^"]*' | cut -d '"' -f 2) _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2 | _xml_decode) _hostmxpref=$(echo "$_host" | _egrep_o ' MXPref="[^"]*' | cut -d '"' -f 2) _hostttl=$(echo "$_host" | _egrep_o ' TTL="[^"]*' | cut -d '"' -f 2) _debug hostid "$_hostid" _debug hostname "$_hostname" _debug hosttype "$_hosttype" _debug hostaddress "$_hostaddress" _debug hostmxpref "$_hostmxpref" _debug hostttl "$_hostttl" } _namecheap_check_config() { if [ -z "$NAMECHEAP_API_KEY" ]; then _err "No API key specified for Namecheap API." _err "Create your key and export it as NAMECHEAP_API_KEY" return 1 fi if [ -z "$NAMECHEAP_USERNAME" ]; then _err "No username key specified for Namecheap API." _err "Create your key and export it as NAMECHEAP_USERNAME" return 1 fi _saveaccountconf NAMECHEAP_API_KEY "$NAMECHEAP_API_KEY" _saveaccountconf NAMECHEAP_USERNAME "$NAMECHEAP_USERNAME" return 0 } _set_namecheap_TXT() { subdomain=$2 txt=$3 if ! _namecheap_set_tld_sld "$1"; then return 1 fi request="namecheap.domains.dns.getHosts&SLD=${_sld}&TLD=${_tld}" if ! _namecheap_post "$request"; then _err "$error" return 1 fi hosts=$(echo "$response" | _egrep_o ']*') _debug hosts "$hosts" if [ -z "$hosts" ]; then _err "Hosts not found" return 1 fi _namecheap_reset_hostList while read -r host; do if _contains "$host" "]*') _debug hosts "$hosts" if [ -z "$hosts" ]; then _err "Hosts not found" return 1 fi _namecheap_reset_hostList found=0 while read -r host; do if _contains "$host" "300") if [ "$retcode" ]; then _info "Successfully added TXT record, ready for validation." return 0 else _err "Unable to add the DNS record." return 1 fi fi } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_namesilo_rm() { fulldomain=$1 txtvalue=$2 if ! _get_root "$fulldomain"; then _err "Unable to find domain specified." return 1 fi # Get the record id. if _namesilo_rest GET "dnsListRecords?version=1&type=xml&key=$Namesilo_Key&domain=$_domain"; then retcode=$(printf "%s\n" "$response" | _egrep_o "300") if [ "$retcode" ]; then _record_id=$(echo "$response" | _egrep_o "([^<]*)TXT$fulldomain" | _egrep_o "([^<]*)" | sed -r "s/([^<]*)<\/record_id>/\1/" | tail -n 1) _debug _record_id "$_record_id" if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." else _info "Empty record id, it seems no such record." return 0 fi else _err "Unable to retrieve the record id." return 1 fi fi # Remove the DNS record using record id. if _namesilo_rest GET "dnsDeleteRecord?version=1&type=xml&key=$Namesilo_Key&domain=$_domain&rrid=$_record_id"; then retcode=$(printf "%s\n" "$response" | _egrep_o "300") if [ "$retcode" ]; then _info "Successfully removed the TXT record." return 0 else _err "Unable to remove the DNS record." return 1 fi fi } #################### Private functions below ################################## # _acme-challenge.www.domain.com # returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 p=1 if ! _namesilo_rest GET "listDomains?version=1&type=xml&key=$Namesilo_Key"; then return 1 fi # Need to exclude the last field (tld) numfields=$(echo "$domain" | _egrep_o "\." | wc -l) while [ "$i" -le "$numfields" ]; do host=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug host "$host" if [ -z "$host" ]; then return 1 fi if _contains "$response" ">$host"; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$host" return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _namesilo_rest() { method=$1 param=$2 data=$3 if [ "$method" != "GET" ]; then response="$(_post "$data" "$Namesilo_API/$param" "" "$method")" else response="$(_get "$Namesilo_API/$param")" fi if [ "$?" != "0" ]; then _err "error $param" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_nanelo.sh000066400000000000000000000036301472032365200167710ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_nanelo_info='Nanelo.com Site: Nanelo.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nanelo Options: NANELO_TOKEN API Token Issues: github.com/acmesh-official/acme.sh/issues/4519 ' NANELO_API="https://api.nanelo.com/v1/" ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_nanelo_add() { fulldomain=$1 txtvalue=$2 NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}" if [ -z "$NANELO_TOKEN" ]; then NANELO_TOKEN="" _err "You didn't configure a Nanelo API Key yet." _err "Please set NANELO_TOKEN and try again." _err "Login to Nanelo.com and go to Settings > API Keys to get a Key" return 1 fi _saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN" _info "Adding TXT record to ${fulldomain}" response="$(_get "$NANELO_API$NANELO_TOKEN/dns/addrecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")" if _contains "${response}" 'success'; then return 0 fi _err "Could not create resource record, please check the logs" _err "${response}" return 1 } dns_nanelo_rm() { fulldomain=$1 txtvalue=$2 NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}" if [ -z "$NANELO_TOKEN" ]; then NANELO_TOKEN="" _err "You didn't configure a Nanelo API Key yet." _err "Please set NANELO_TOKEN and try again." _err "Login to Nanelo.com and go to Settings > API Keys to get a Key" return 1 fi _saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN" _info "Deleting resource record $fulldomain" response="$(_get "$NANELO_API$NANELO_TOKEN/dns/deleterecord?type=TXT&ttl=60&name=${fulldomain}&value=${txtvalue}")" if _contains "${response}" 'success'; then return 0 fi _err "Could not delete resource record, please check the logs" _err "${response}" return 1 } acme.sh-3.1.0/dnsapi/dns_nederhost.sh000077500000000000000000000062211472032365200175120ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_nederhost_info='NederHost.nl Site: NederHost.nl Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nederhost Options: NederHost_Key API Key Issues: github.com/acmesh-official/acme.sh/issues/2089 ' NederHost_Api="https://api.nederhost.nl/dns/v1" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_nederhost_add() { fulldomain=$1 txtvalue=$2 NederHost_Key="${NederHost_Key:-$(_readaccountconf_mutable NederHost_Key)}" if [ -z "$NederHost_Key" ]; then NederHost_Key="" _err "You didn't specify a NederHost api key." _err "You can get yours from https://www.nederhost.nl/mijn_nederhost" return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable NederHost_Key "$NederHost_Key" _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 _nederhost_rest PATCH "zones/$_domain/records/$fulldomain/TXT" "[{\"content\":\"$txtvalue\",\"ttl\":60}]"; then if _contains "$response" "$fulldomain"; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_nederhost_rm() { fulldomain=$1 txtvalue=$2 NederHost_Key="${NederHost_Key:-$(_readaccountconf_mutable NederHost_Key)}" if [ -z "$NederHost_Key" ]; then NederHost_Key="" _err "You didn't specify a NederHost api key." _err "You can get yours from https://www.nederhost.nl/mijn_nederhost" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Removing txt record" _nederhost_rest DELETE "zones/${_domain}/records/$fulldomain/TXT?content=$txtvalue" } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 p=1 while true; do _domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _debug _domain "$_domain" if [ -z "$_domain" ]; then #not valid return 1 fi if _nederhost_rest GET "zones/${_domain}"; then if [ "${_code}" = "204" ]; then return 0 fi else return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _nederhost_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Authorization: Bearer $NederHost_Key" export _H2="Content-Type: application/json" _debug data "$data" response="$(_post "$data" "$NederHost_Api/$ep" "" "$m")" _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" _debug "http response code $_code" if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_neodigit.sh000066400000000000000000000107141472032365200173200ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_neodigit_info='Neodigit.net Site: Neodigit.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_neodigit Options: NEODIGIT_API_TOKEN API Token Author: Adrian Almenar ' NEODIGIT_API_URL="https://api.neodigit.net/v1" # ######## Public functions ##################### # Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_neodigit_add() { fulldomain=$1 txtvalue=$2 NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" if [ -z "$NEODIGIT_API_TOKEN" ]; then NEODIGIT_API_TOKEN="" _err "You haven't specified a Token api key." _err "Please create the key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable NEODIGIT_API_TOKEN "$NEODIGIT_API_TOKEN" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug domain "$_domain" _debug sub_domain "$_sub_domain" _debug "Getting txt records" _neo_rest GET "dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain" _debug _code "$_code" if [ "$_code" != "200" ]; then _err "error retrieving data!" return 1 fi _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _debug domain "$_domain" _debug sub_domain "$_sub_domain" _info "Adding record" if _neo_rest POST "dns/zones/$_domain_id/records" "{\"record\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":60}}"; then if printf -- "%s" "$response" | grep "$_sub_domain" >/dev/null; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_neodigit_rm() { fulldomain=$1 txtvalue=$2 NEODIGIT_API_TOKEN="${NEODIGIT_API_TOKEN:-$(_readaccountconf_mutable NEODIGIT_API_TOKEN)}" if [ -z "$NEODIGIT_API_TOKEN" ]; then NEODIGIT_API_TOKEN="" _err "You haven't specified a Token api key." _err "Please create the key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable NEODIGIT_API_TOKEN "$NEODIGIT_API_TOKEN" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _neo_rest GET "dns/zones/${_domain_id}/records?type=TXT&name=$fulldomain&content=$txtvalue" if [ "$_code" != "200" ]; then _err "error retrieving data!" return 1 fi record_id=$(echo "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _neo_rest DELETE "dns/zones/$_domain_id/records/$record_id"; then _err "Delete record error." return 1 fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=dasfdsafsadg5ythd _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _neo_rest GET "dns/zones?name=$h"; then return 1 fi _debug p "$p" if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(echo "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _neo_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="X-TCPanel-Token: $NEODIGIT_API_TOKEN" export _H2="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$NEODIGIT_API_URL/$ep" "" "$m")" else response="$(_get "$NEODIGIT_API_URL/$ep")" fi _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_netcup.sh000066400000000000000000000114741472032365200170200ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_netcup_info='netcup.eu Domains: netcup.de netcup.net Site: netcup.eu/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_netcup Options: NC_Apikey API Key NC_Apipw API Password NC_CID Customer Number Author: linux-insideDE ' NC_Apikey="${NC_Apikey:-$(_readaccountconf_mutable NC_Apikey)}" NC_Apipw="${NC_Apipw:-$(_readaccountconf_mutable NC_Apipw)}" NC_CID="${NC_CID:-$(_readaccountconf_mutable NC_CID)}" end="https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" client="" dns_netcup_add() { _debug NC_Apikey "$NC_Apikey" login if [ "$NC_Apikey" = "" ] || [ "$NC_Apipw" = "" ] || [ "$NC_CID" = "" ]; then _err "No Credentials given" return 1 fi _saveaccountconf_mutable NC_Apikey "$NC_Apikey" _saveaccountconf_mutable NC_Apipw "$NC_Apipw" _saveaccountconf_mutable NC_CID "$NC_CID" fulldomain=$1 txtvalue=$2 domain="" exit=$(echo "$fulldomain" | tr -dc '.' | wc -c) exit=$(_math "$exit" + 1) i=$exit while [ "$exit" -gt 0 ] do tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit") if [ "$(_math "$i" - "$exit")" -eq 0 ]; then domain="$tmp" else domain="$tmp.$domain" fi if [ "$(_math "$i" - "$exit")" -ge 1 ]; then msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"false\", \"state\": \"yes\"} ]}}}" "$end" "" "POST") _debug "$msg" if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then _err "$msg" return 1 else break fi fi fi exit=$(_math "$exit" - 1) done logout } dns_netcup_rm() { login fulldomain=$1 txtvalue=$2 domain="" exit=$(echo "$fulldomain" | tr -dc '.' | wc -c) exit=$(_math "$exit" + 1) i=$exit rec="" while [ "$exit" -gt 0 ] do tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit") if [ "$(_math "$i" - "$exit")" -eq 0 ]; then domain="$tmp" else domain="$tmp.$domain" fi if [ "$(_math "$i" - "$exit")" -ge 1 ]; then msg=$(_post "{\"action\": \"infoDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\", \"domainname\": \"$domain\"}}" "$end" "" "POST") rec=$(echo "$msg" | sed 's/\[//g' | sed 's/\]//g' | sed 's/{\"serverrequestid\".*\"dnsrecords\"://g' | sed 's/},{/};{/g' | sed 's/{//g' | sed 's/}//g') _debug "$msg" if [ "$(_getfield "$msg" "5" | sed 's/"statuscode"://g')" != 5028 ]; then if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then _err "$msg" return 1 else break fi fi fi exit=$(_math "$exit" - 1) done ida=0000 idv=0001 ids=0000000000 i=1 while [ "$i" -ne 0 ] do specrec=$(_getfield "$rec" "$i" ";") idv="$ida" ida=$(_getfield "$specrec" "1" "," | sed 's/\"id\":\"//g' | sed 's/\"//g') txtv=$(_getfield "$specrec" "5" "," | sed 's/\"destination\":\"//g' | sed 's/\"//g') i=$(_math "$i" + 1) if [ "$txtvalue" = "$txtv" ]; then i=0 ids="$ida" fi if [ "$ida" = "$idv" ]; then i=0 fi done msg=$(_post "{\"action\": \"updateDnsRecords\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\",\"clientrequestid\": \"$client\" , \"domainname\": \"$domain\", \"dnsrecordset\": { \"dnsrecords\": [ {\"id\": \"$ids\", \"hostname\": \"$fulldomain.\", \"type\": \"TXT\", \"priority\": \"\", \"destination\": \"$txtvalue\", \"deleterecord\": \"TRUE\", \"state\": \"yes\"} ]}}}" "$end" "" "POST") _debug "$msg" if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then _err "$msg" return 1 fi logout } login() { tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST") sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4) _debug "$tmp" if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then _err "$tmp" return 1 fi } logout() { tmp=$(_post "{\"action\": \"logout\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST") _debug "$tmp" if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then _err "$tmp" return 1 fi } acme.sh-3.1.0/dnsapi/dns_netlify.sh000066400000000000000000000104431472032365200171670ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_netlify_info='Netlify.com Site: Netlify.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_netlify Options: NETLIFY_ACCESS_TOKEN API Token Issues: github.com/acmesh-official/acme.sh/issues/3088 ' NETLIFY_HOST="api.netlify.com/api/v1/" NETLIFY_URL="https://$NETLIFY_HOST" ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_netlify_add() { fulldomain=$1 txtvalue=$2 NETLIFY_ACCESS_TOKEN="${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}" if [ -z "$NETLIFY_ACCESS_TOKEN" ]; then NETLIFY_ACCESS_TOKEN="" _err "Please specify your Netlify Access Token and try again." return 1 else _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN" fi _info "Using Netlify" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" dnsRecordURI="dns_zones/$_domain_id/dns_records" body="{\"type\":\"TXT\", \"hostname\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"ttl\":\"10\"}" _netlify_rest POST "$dnsRecordURI" "$body" "$NETLIFY_ACCESS_TOKEN" _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then _info "validation value added" return 0 else _err "error adding validation value ($_code)" return 1 fi } #Usage: dns_myapi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" #Remove the txt record after validation. dns_netlify_rm() { _info "Using Netlify" txtdomain="$1" txt="$2" _debug txtdomain "$txtdomain" _debug txt "$txt" NETLIFY_ACCESS_TOKEN="${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}" if ! _get_root "$txtdomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" dnsRecordURI="dns_zones/$_domain_id/dns_records" _netlify_rest GET "$dnsRecordURI" "" "$NETLIFY_ACCESS_TOKEN" _record_id=$(echo "$response" | _egrep_o "\"type\":\"TXT\",[^\}]*\"value\":\"$txt\"" | head -n 1 | _egrep_o "\"id\":\"[^\"\}]*\"" | cut -d : -f 2 | tr -d \") _debug _record_id "$_record_id" if [ "$_record_id" ]; then _netlify_rest DELETE "$dnsRecordURI/$_record_id" "" "$NETLIFY_ACCESS_TOKEN" _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then _info "validation value removed" return 0 else _err "error removing validation value ($_code)" return 1 fi fi return 1 } #################### Private functions below ################################## _get_root() { domain=$1 accesstoken=$2 i=1 p=1 _netlify_rest GET "dns_zones" "" "$accesstoken" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug2 "Checking domain: $h" if [ -z "$h" ]; then #not valid _err "Invalid domain" return 1 fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(echo "$response" | _egrep_o "\"[^\"]*\",\"name\":\"$h\"" | cut -d , -f 1 | tr -d \") if [ "$_domain_id" ]; then if [ "$i" = 1 ]; then #create the record at the domain apex (@) if only the domain name was provided as --domain-alias _sub_domain="@" else _sub_domain=$(echo "$domain" | cut -d . -f 1-"$p") fi _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _netlify_rest() { m=$1 ep="$2" data="$3" _debug "$ep" token_trimmed=$(echo "$NETLIFY_ACCESS_TOKEN" | tr -d '"') export _H1="Content-Type: application/json" export _H2="Authorization: Bearer $token_trimmed" : >"$HTTP_HEADER" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$NETLIFY_URL$ep" "" "$m")" else response="$(_get "$NETLIFY_URL$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_nic.sh000066400000000000000000000131701472032365200162660ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_nic_info='nic.ru Site: nic.ru Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nic Options: NIC_ClientID Client ID NIC_ClientSecret Client Secret NIC_Username Username NIC_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2547 ' NIC_Api="https://api.nic.ru" dns_nic_add() { fulldomain="${1}" txtvalue="${2}" if ! _nic_get_authtoken save; then _err "get NIC auth token failed" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug _service "$_service" _info "Adding record" if ! _nic_rest PUT "services/$_service/zones/$_domain/records" "$_sub_domainTXT$txtvalue"; then _err "Add TXT record error" return 1 fi if ! _nic_rest POST "services/$_service/zones/$_domain/commit" ""; then return 1 fi _info "Added, OK" } dns_nic_rm() { fulldomain="${1}" txtvalue="${2}" if ! _nic_get_authtoken; then _err "get NIC auth token failed" return 1 fi if ! _get_root "$fulldomain"; then _err "Invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug _service "$_service" if ! _nic_rest GET "services/$_service/zones/$_domain/records"; then _err "Get records error" return 1 fi _domain_id=$(printf "%s" "$response" | grep "$_sub_domain" | grep -- "$txtvalue" | sed -r "s/.*"; then error=$(printf "%s" "$response" | grep "error code" | sed -r "s/.*(.*)<\/error>/\1/g") _err "Error: $error" return 1 fi if ! _contains "$response" "success"; then return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_njalla.sh000066400000000000000000000113131472032365200167530ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_njalla_info='Njalla Site: Njal.la Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_njalla Options: NJALLA_Token API Token Issues: github.com/acmesh-official/acme.sh/issues/2913 ' NJALLA_Api="https://njal.la/api/1/" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_njalla_add() { fulldomain=$1 txtvalue=$2 NJALLA_Token="${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}" if [ "$NJALLA_Token" ]; then _saveaccountconf_mutable NJALLA_Token "$NJALLA_Token" else NJALLA_Token="" _err "You didn't specify a Njalla api token yet." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so # we can not use updating anymore. # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) # _debug count "$count" # if [ "$count" = "0" ]; then _info "Adding record" if _njalla_rest "{\"method\":\"add-record\",\"params\":{\"domain\":\"$_domain\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}}"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_njalla_rm() { fulldomain=$1 txtvalue=$2 NJALLA_Token="${NJALLA_Token:-$(_readaccountconf_mutable NJALLA_Token)}" if [ "$NJALLA_Token" ]; then _saveaccountconf_mutable NJALLA_Token "$NJALLA_Token" else NJALLA_Token="" _err "You didn't specify a Njalla api token yet." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting records for domain" if ! _njalla_rest "{\"method\":\"list-records\",\"params\":{\"domain\":\"${_domain}\"}}"; then return 1 fi if ! echo "$response" | tr -d " " | grep "\"id\":" >/dev/null; then _err "Error: $response" return 1 fi records=$(echo "$response" | _egrep_o "\"records\":\s?\[(.*)\]\}" | _egrep_o "\[.*\]" | _egrep_o "\{[^\{\}]*\"id\":[^\{\}]*\}") count=$(echo "$records" | wc -l) _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else echo "$records" | while read -r record; do record_name=$(echo "$record" | _egrep_o "\"name\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") record_content=$(echo "$record" | _egrep_o "\"content\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") record_id=$(echo "$record" | _egrep_o "\"id\":\s?[0-9]+" | cut -d : -f 2 | tr -d " " | tr -d \") if [ "$_sub_domain" = "$record_name" ]; then if [ "$txtvalue" = "$record_content" ]; then _debug "record_id" "$record_id" if ! _njalla_rest "{\"method\":\"remove-record\",\"params\":{\"domain\":\"${_domain}\",\"id\":${record_id}}}"; then _err "Delete record error." return 1 fi echo "$response" | tr -d " " | grep "\"result\"" >/dev/null fi fi done fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _njalla_rest "{\"method\":\"get-domain\",\"params\":{\"domain\":\"${h}\"}}"; then return 1 fi if _contains "$response" "\"$h\""; then _domain_returned=$(echo "$response" | _egrep_o "\{\"name\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ") if [ "$_domain_returned" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _njalla_rest() { data="$1" token_trimmed=$(echo "$NJALLA_Token" | tr -d '"') export _H1="Content-Type: application/json" export _H2="Accept: application/json" export _H3="Authorization: Njalla $token_trimmed" _debug data "$data" response="$(_post "$data" "$NJALLA_Api" "" "POST")" if [ "$?" != "0" ]; then _err "error $data" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_nm.sh000066400000000000000000000042741472032365200161340ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_nm_info='NameMaster.de Site: NameMaster.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nm Options: NM_user API Username NM_sha256 API Password as SHA256 hash Author: Thilo Gass ' #-- dns_nm_add() - Add TXT record -------------------------------------- # Usage: dns_nm_add _acme-challenge.subdomain.domain.com "XyZ123..." namemaster_api="https://namemaster.de/api/api.php" dns_nm_add() { fulldomain=$1 txt_value=$2 _info "Using DNS-01 namemaster hook" NM_user="${NM_user:-$(_readaccountconf_mutable NM_user)}" NM_sha256="${NM_sha256:-$(_readaccountconf_mutable NM_sha256)}" if [ -z "$NM_user" ] || [ -z "$NM_sha256" ]; then NM_user="" NM_sha256="" _err "No auth details provided. Please set user credentials using the \$NM_user and \$NM_sha256 environment variables." return 1 fi #save the api user and sha256 password to the account conf file. _debug "Save user and hash" _saveaccountconf_mutable NM_user "$NM_user" _saveaccountconf_mutable NM_sha256 "$NM_sha256" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" "$fulldomain" return 1 fi _info "die Zone lautet:" "$zone" get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Antwort=csv&Typ=ACME&zone=$zone&hostname=$fulldomain&TXT=$txt_value&Action=Auto&Lifetime=3600" if ! erg="$(_get "$get")"; then _err "error Adding $fulldomain TXT: $txt_value" return 1 fi if _contains "$erg" "Success"; then _info "Success, TXT Added, OK" else _err "error Adding $fulldomain TXT: $txt_value erg: $erg" return 1 fi _debug "ok Auto $fulldomain TXT: $txt_value erg: $erg" return 0 } dns_nm_rm() { fulldomain=$1 txtvalue=$2 _info "TXT enrty in $fulldomain is deleted automatically" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" } _get_root() { domain=$1 get="$namemaster_api?User=$NM_user&Password=$NM_sha256&Typ=acme&hostname=$domain&Action=getzone&antwort=csv" if ! zone="$(_get "$get")"; then _err "error getting Zone" return 1 else if _contains "$zone" "hostname not found"; then return 1 fi fi } acme.sh-3.1.0/dnsapi/dns_nsd.sh000066400000000000000000000034131472032365200163000ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_nsd_info='NLnetLabs NSD Server Site: github.com/NLnetLabs/nsd Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#nsd Options: Nsd_ZoneFile Zone File path. E.g. "/etc/nsd/zones/example.com.zone" Nsd_Command Command. E.g. "sudo nsd-control reload" Issues: github.com/acmesh-official/acme.sh/issues/2245 ' # args: fulldomain txtvalue dns_nsd_add() { fulldomain=$1 txtvalue=$2 ttlvalue=300 Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}" Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}" # Arg checks if [ -z "$Nsd_ZoneFile" ] || [ -z "$Nsd_Command" ]; then Nsd_ZoneFile="" Nsd_Command="" _err "Specify ENV vars Nsd_ZoneFile and Nsd_Command" return 1 fi if [ ! -f "$Nsd_ZoneFile" ]; then Nsd_ZoneFile="" Nsd_Command="" _err "No such file: $Nsd_ZoneFile" return 1 fi _savedomainconf Nsd_ZoneFile "$Nsd_ZoneFile" _savedomainconf Nsd_Command "$Nsd_Command" echo "$fulldomain. $ttlvalue IN TXT \"$txtvalue\"" >>"$Nsd_ZoneFile" _info "Added TXT record for $fulldomain" _debug "Running $Nsd_Command" if eval "$Nsd_Command"; then _info "Successfully updated the zone" return 0 else _err "Problem updating the zone" return 1 fi } # args: fulldomain txtvalue dns_nsd_rm() { fulldomain=$1 txtvalue=$2 ttlvalue=300 Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}" Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}" _sed_i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile" _info "Removed TXT record for $fulldomain" _debug "Running $Nsd_Command" if eval "$Nsd_Command"; then _info "Successfully reloaded NSD " return 0 else _err "Problem reloading NSD" return 1 fi } acme.sh-3.1.0/dnsapi/dns_nsone.sh000066400000000000000000000077571472032365200166550ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_nsone_info='ns1.com Domains: ns1.net Site: ns1.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nsone Options: NS1_Key API Key Author: ' NS1_Api="https://api.nsone.net/v1" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_nsone_add() { fulldomain=$1 txtvalue=$2 if [ -z "$NS1_Key" ]; then NS1_Key="" _err "You didn't specify nsone dns api key yet." _err "Please create you key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf NS1_Key "$NS1_Key" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _nsone_rest GET "zones/${_domain}" if ! _contains "$response" "\"records\":"; then _err "Error" return 1 fi count=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Adding record" if _nsone_rest PUT "zones/$_domain/$fulldomain/TXT" "{\"answers\":[{\"answer\":[\"$txtvalue\"]}],\"type\":\"TXT\",\"domain\":\"$fulldomain\",\"zone\":\"$_domain\",\"ttl\":0}"; then if _contains "$response" "$fulldomain"; then _info "Added" #todo: check if the record takes effect return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." else _info "Updating record" prev_txt=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",\"short_answers\":\[\"[^,]*\]" | _head_n 1 | cut -d: -f3 | cut -d, -f1) _debug "prev_txt" "$prev_txt" _nsone_rest POST "zones/$_domain/$fulldomain/TXT" "{\"answers\": [{\"answer\": [\"$txtvalue\"]},{\"answer\": $prev_txt}],\"type\": \"TXT\",\"domain\":\"$fulldomain\",\"zone\": \"$_domain\",\"ttl\":0}" if [ "$?" = "0" ] && _contains "$response" "$fulldomain"; then _info "Updated!" #todo: check if the record takes effect return 0 fi _err "Update error" return 1 fi } #fulldomain dns_nsone_rm() { fulldomain=$1 txtvalue=$2 _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _nsone_rest GET "zones/${_domain}/$fulldomain/TXT" count=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",.*\"type\":\"TXT\"" | wc -l | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else if ! _nsone_rest DELETE "zones/${_domain}/$fulldomain/TXT"; then _err "Delete record error." return 1 fi _contains "$response" "" fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 if ! _nsone_rest GET "zones"; then return 1 fi 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" "\"zone\":\"$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 } _nsone_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Accept: application/json" export _H2="X-NSONE-Key: $NS1_Key" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$NS1_Api/$ep" "" "$m")" else response="$(_get "$NS1_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_nsupdate.sh000077500000000000000000000100121472032365200173330ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_nsupdate_info='nsupdate RFC 2136 DynDNS client Site: bind9.readthedocs.io/en/v9.18.19/manpages.html#nsupdate-dynamic-dns-update-utility Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nsupdate Options: NSUPDATE_SERVER Server hostname. Default: "localhost". NSUPDATE_SERVER_PORT Server port. Default: "53". NSUPDATE_KEY File path to TSIG key. NSUPDATE_ZONE Domain zone to update. Optional. ' ######## Public functions ##################### #Usage: dns_nsupdate_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_nsupdate_add() { fulldomain=$1 txtvalue=$2 NSUPDATE_SERVER="${NSUPDATE_SERVER:-$(_readaccountconf_mutable NSUPDATE_SERVER)}" NSUPDATE_SERVER_PORT="${NSUPDATE_SERVER_PORT:-$(_readaccountconf_mutable NSUPDATE_SERVER_PORT)}" NSUPDATE_KEY="${NSUPDATE_KEY:-$(_readaccountconf_mutable NSUPDATE_KEY)}" NSUPDATE_ZONE="${NSUPDATE_ZONE:-$(_readaccountconf_mutable NSUPDATE_ZONE)}" NSUPDATE_OPT="${NSUPDATE_OPT:-$(_readaccountconf_mutable NSUPDATE_OPT)}" _checkKeyFile || return 1 # save the dns server and key to the account conf file. _saveaccountconf_mutable NSUPDATE_SERVER "${NSUPDATE_SERVER}" _saveaccountconf_mutable NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}" _saveaccountconf_mutable NSUPDATE_KEY "${NSUPDATE_KEY}" _saveaccountconf_mutable NSUPDATE_ZONE "${NSUPDATE_ZONE}" _saveaccountconf_mutable NSUPDATE_OPT "${NSUPDATE_OPT}" [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost" [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53 [ -n "${NSUPDATE_OPT}" ] || NSUPDATE_OPT="" _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D" if [ -z "${NSUPDATE_ZONE}" ]; then #shellcheck disable=SC2086 nsupdate -k "${NSUPDATE_KEY}" $nsdebug $NSUPDATE_OPT </dev/null | ${ACME_OPENSSL_BIN:-openssl} md5 -c | cut -d = -f 2 | tr -d ' ' } _signed_request() { _sig_method="$1" _sig_target="$2" _sig_body="$3" _return_field="$4" _key_fingerprint=$(_fingerprint "$OCI_CLI_KEY") _sig_host="dns.$OCI_CLI_REGION.oraclecloud.com" _sig_keyId="$OCI_CLI_TENANCY/$OCI_CLI_USER/$_key_fingerprint" _sig_alg="rsa-sha256" _sig_version="1" _sig_now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")" _request_method=$(printf %s "$_sig_method" | _lower_case) _curl_method=$(printf %s "$_sig_method" | _upper_case) _request_target="(request-target): $_request_method $_sig_target" _date_header="date: $_sig_now" _host_header="host: $_sig_host" _string_to_sign="$_request_target\n$_date_header\n$_host_header" _sig_headers="(request-target) date host" if [ "$_sig_body" ]; then _secure_debug3 _sig_body "$_sig_body" _sig_body_sha256="x-content-sha256: $(printf %s "$_sig_body" | _digest sha256)" _sig_body_type="content-type: application/json" _sig_body_length="content-length: ${#_sig_body}" _string_to_sign="$_string_to_sign\n$_sig_body_sha256\n$_sig_body_type\n$_sig_body_length" _sig_headers="$_sig_headers x-content-sha256 content-type content-length" fi _tmp_file=$(_mktemp) if [ -f "$_tmp_file" ]; then printf '%s' "$OCI_CLI_KEY" >"$_tmp_file" _signature=$(printf '%b' "$_string_to_sign" | _sign "$_tmp_file" sha256 | tr -d '\r\n') rm -f "$_tmp_file" fi _signed_header="Authorization: Signature version=\"$_sig_version\",keyId=\"$_sig_keyId\",algorithm=\"$_sig_alg\",headers=\"$_sig_headers\",signature=\"$_signature\"" _secure_debug3 _signed_header "$_signed_header" if [ "$_curl_method" = "GET" ]; then export _H1="$_date_header" export _H2="$_signed_header" _response="$(_get "https://${_sig_host}${_sig_target}")" elif [ "$_curl_method" = "PATCH" ]; then export _H1="$_date_header" # shellcheck disable=SC2090 export _H2="$_sig_body_sha256" export _H3="$_sig_body_type" export _H4="$_sig_body_length" export _H5="$_signed_header" _response="$(_post "$_sig_body" "https://${_sig_host}${_sig_target}" "" "PATCH")" else _err "Unable to process method: $_curl_method." fi _ret="$?" if [ "$_return_field" ]; then _response="$(echo "$_response" | sed 's/\\\"//g'))" _return=$(echo "${_response}" | _egrep_o "\"$_return_field\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"") else _return="$_response" fi printf "%s" "$_return" return $_ret } # file key [section] _readini() { _file="$1" _key="$2" _section="${3:-DEFAULT}" _start_n=$(grep -n '\['"$_section"']' "$_file" | cut -d : -f 1) _debug3 _start_n "$_start_n" if [ -z "$_start_n" ]; then _err "Can not find section: $_section" return 1 fi _start_nn=$(_math "$_start_n" + 1) _debug3 "_start_nn" "$_start_nn" _left="$(sed -n "${_start_nn},99999p" "$_file")" _debug3 _left "$_left" _end="$(echo "$_left" | grep -n "^\[" | _head_n 1)" _debug3 "_end" "$_end" if [ "$_end" ]; then _end_n=$(echo "$_end" | cut -d : -f 1) _debug3 "_end_n" "$_end_n" _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p") else _seg_n="$_left" fi _debug3 "_seg_n" "$_seg_n" _lineini="$(echo "$_seg_n" | grep "^ *$_key *= *")" _inivalue="$(printf "%b" "$(eval "echo $_lineini | sed \"s/^ *${_key} *= *//g\"")")" _debug2 _inivalue "$_inivalue" echo "$_inivalue" } acme.sh-3.1.0/dnsapi/dns_omglol.sh000066400000000000000000000245171472032365200170150ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_omglol_info='omg.lol Site: omg.lol Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_omglol Options: OMG_ApiKey API Key from omg.lol. This is accessible from the bottom of the account page at https://home.omg.lol/account OMG_Address This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard Issues: github.com/acmesh-official/acme.sh/issues/5299 Author: @Kholin ' # See API Docs https://api.omg.lol/ ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_omglol_add() { fulldomain=$1 txtvalue=$2 OMG_ApiKey="${OMG_ApiKey:-$(_readaccountconf_mutable OMG_ApiKey)}" OMG_Address="${OMG_Address:-$(_readaccountconf_mutable OMG_Address)}" # As omg.lol includes a leading @ for their addresses, pre-strip this before save OMG_Address="$(echo "$OMG_Address" | tr -d '@')" _saveaccountconf_mutable OMG_ApiKey "$OMG_ApiKey" _saveaccountconf_mutable OMG_Address "$OMG_Address" _info "Using omg.lol." _debug "Function" "dns_omglol_add()" _debug "Full Domain Name" "$fulldomain" _debug "txt Record Value" "$txtvalue" _secure_debug "omg.lol API key" "$OMG_ApiKey" _debug "omg.lol Address" "$OMG_Address" omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" if [ ! $? ]; then return 1 fi dnsName=$(_getDnsRecordName "$fulldomain" "$OMG_Address") authHeader="$(_createAuthHeader "$OMG_ApiKey")" _debug2 "dns_omglol_add(): Address" "$dnsName" omg_add "$OMG_Address" "$authHeader" "$dnsName" "$txtvalue" } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_omglol_rm() { fulldomain=$1 txtvalue=$2 OMG_ApiKey="${OMG_ApiKey:-$(_readaccountconf_mutable OMG_ApiKey)}" OMG_Address="${OMG_Address:-$(_readaccountconf_mutable OMG_Address)}" # As omg.lol includes a leading @ for their addresses, strip this in case provided OMG_Address="$(echo "$OMG_Address" | tr -d '@')" _info "Using omg.lol" _debug "Function" "dns_omglol_rm()" _debug "Full Domain Name" "$fulldomain" _debug "txt Record Value" "$txtvalue" _secure_debug "omg.lol API key" "$OMG_ApiKey" _debug "omg.lol Address" "$OMG_Address" omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" if [ ! $? ]; then return 1 fi dnsName=$(_getDnsRecordName "$fulldomain" "$OMG_Address") authHeader="$(_createAuthHeader "$OMG_ApiKey")" omg_delete "$OMG_Address" "$authHeader" "$dnsName" "$txtvalue" } #################### Private functions below ################################## # Check that the minimum requirements are present. Close ungracefully if not omg_validate() { omg_apikey=$1 omg_address=$2 fulldomain=$3 _debug2 "Function" "dns_validate()" _secure_debug2 "omg.lol API key" "$omg_apikey" _debug2 "omg.lol Address" "$omg_address" _debug2 "Full Domain Name" "$fulldomain" if [ "" = "$omg_address" ]; then _err "omg.lol base address not provided. Exiting" return 1 fi if [ "" = "$omg_apikey" ]; then _err "omg.lol API key not provided. Exiting" return 1 fi _endswith "$fulldomain" "omg.lol" if [ ! $? ]; then _err "Domain name requested is not under omg.lol" return 1 fi _endswith "$fulldomain" "$omg_address.omg.lol" if [ ! $? ]; then _err "Domain name is not a subdomain of provided omg.lol address $omg_address" return 1 fi _debug "Required environment parameters are all present" } # Add (or modify) an entry for a new ACME query omg_add() { address=$1 authHeader=$2 dnsName=$3 txtvalue=$4 _info "Creating DNS entry for $dnsName" _debug2 "omg_add()" _debug2 "omg.lol Address: " "$address" _secure_debug2 "omg.lol authorization header: " "$authHeader" _debug2 "Full Domain name:" "$dnsName.$address.omg.lol" _debug2 "TXT value to set:" "$txtvalue" export _H1="$authHeader" endpoint="https://api.omg.lol/address/$address/dns" _debug2 "Endpoint" "$endpoint" payload='{"type": "TXT", "name":"'"$dnsName"'", "data":"'"$txtvalue"'", "ttl":30}' _debug2 "Payload" "$payload" response=$(_post "$payload" "$endpoint" "" "POST" "application/json") omg_validate_add "$response" "$dnsName.$address" "$txtvalue" } omg_validate_add() { response=$1 name=$2 content=$3 _debug "Validating DNS record addition" _debug2 "omg_validate_add()" _debug2 "Response" "$response" _debug2 "DNS Name" "$name" _debug2 "DNS value" "$content" _jsonResponseCheck "$response" "success" "true" if [ "1" = "$?" ]; then _err "Response did not report success" return 1 fi _jsonResponseCheck "$response" "message" "Your DNS record was created successfully." if [ "1" = "$?" ]; then _err "Response message did not indicate DNS record was successfully created" return 1 fi _jsonResponseCheck "$response" "name" "$name" if [ "1" = "$?" ]; then _err "Response DNS Name did not match the response received" return 1 fi _jsonResponseCheck "$response" "content" "$content" if [ "1" = "$?" ]; then _err "Response DNS Name did not match the response received" return 1 fi _info "Record Created successfully" return 0 } omg_getRecords() { address=$1 authHeader=$2 dnsName=$3 txtValue=$4 _debug2 "omg_getRecords()" _debug2 "omg.lol Address: " "$address" _secure_debug2 "omg.lol Auth Header: " "$authHeader" _debug2 "omg.lol DNS name:" "$dnsName" _debug2 "txt Value" "$txtValue" export _H1="$authHeader" endpoint="https://api.omg.lol/address/$address/dns" _debug2 "Endpoint" "$endpoint" payload=$(_get "$endpoint") _debug2 "Received Payload:" "$payload" # Reformat the JSON to be more parseable recordID=$(echo "$payload" | _stripWhitespace) recordID=$(echo "$recordID" | _exposeJsonArray) # Now find the one with the right value, and caputre its ID recordID=$(echo "$recordID" | grep -- "$txtValue" | grep -i -- "$dnsName.$address") _getJsonElement "$recordID" "id" } omg_delete() { address=$1 authHeader=$2 dnsName=$3 txtValue=$4 _info "Deleting DNS entry for $dnsName with value $txtValue" _debug2 "omg_delete()" _debug2 "omg.lol Address: " "$address" _secure_debug2 "omg.lol Auth Header: " "$authHeader" _debug2 "Full Domain name:" "$dnsName.$address.omg.lol" _debug2 "txt Value" "$txtValue" record=$(omg_getRecords "$address" "$authHeader" "$dnsName" "$txtvalue") if [ "" = "$record" ]; then _err "DNS record $address not found!" return 1 fi endpoint="https://api.omg.lol/address/$address/dns/$record" _debug2 "Endpoint" "$endpoint" export _H1="$authHeader" output=$(_post "" "$endpoint" "" "DELETE") _debug2 "Response" "$output" omg_validate_delete "$output" } # Validate the response on request to delete. # Confirm status is success and message indicates deletion was successful. # Input: Response - HTTP response received from delete request omg_validate_delete() { response=$1 _info "Validating DNS record deletion" _debug2 "omg_validate_delete()" _debug2 "Response" "$response" _jsonResponseCheck "$output" "success" "true" if [ "1" = "$?" ]; then _err "Response did not report success" return 1 fi _jsonResponseCheck "$output" "message" "OK, your DNS record has been deleted." if [ "1" = "$?" ]; then _err "Response message did not indicate DNS record was successfully deleted" return 1 fi _info "Record deleted successfully" return 0 } ########## Utility Functions ##################################### # All utility functions only log at debug3 _jsonResponseCheck() { response=$1 field=$2 correct=$3 correct=$(echo "$correct" | _lower_case) _debug3 "jsonResponseCheck()" _debug3 "Response to parse" "$response" _debug3 "Field to get response from" "$field" _debug3 "What is the correct response" "$correct" responseValue=$(_jsonGetLastResponse "$response" "$field") if [ "$responseValue" != "$correct" ]; then _debug3 "Expected: $correct" _debug3 "Actual: $responseValue" return 1 else _debug3 "Matched: $responseValue" fi return 0 } _jsonGetLastResponse() { response=$1 field=$2 _debug3 "jsonGetLastResponse()" _debug3 "Response provided" "$response" _debug3 "Field to get responses for" "$field" responseValue=$(echo "$response" | grep -- "\"$field\"" | cut -f2 -d":") _debug3 "Response lines found:" "$responseValue" responseValue=$(echo "$responseValue" | sed 's/^ //g' | sed 's/^"//g' | sed 's/\\"//g') responseValue=$(echo "$responseValue" | sed 's/,$//g' | sed 's/"$//g') responseValue=$(echo "$responseValue" | _lower_case) _debug3 "Responses found" "$responseValue" _debug3 "Response Selected" "$(echo "$responseValue" | tail -1)" echo "$responseValue" | tail -1 } _stripWhitespace() { tr -d '\n' | tr -d '\r' | tr -d '\t' | sed -r 's/ +/ /g' | sed 's/\\"//g' } _exposeJsonArray() { sed -r 's/.*\[//g' | tr '}' '|' | tr '{' '|' | sed 's/|, |/|/g' | tr '|' '\n' } _getJsonElement() { content=$1 field=$2 _debug3 "_getJsonElement()" _debug3 "Input JSON element" "$content" _debug3 "JSON element to isolate" "$field" # With a single JSON entry to parse, convert commas to newlines puts each element on # its own line - which then allows us to just grep teh name, remove the key, and # isolate the value output=$(echo "$content" | tr ',' '\n' | grep -- "\"$field\":" | sed 's/.*: //g') _debug3 "String before unquoting: $output" _unquoteString "$output" } _createAuthHeader() { apikey=$1 _debug3 "_createAuthHeader()" _secure_debug3 "Provided API Key" "$apikey" authheader="Authorization: Bearer $apikey" _secure_debug3 "Authorization Header" "$authheader" echo "$authheader" } _getDnsRecordName() { fqdn=$1 address=$2 _debug3 "_getDnsRecordName()" _debug3 "FQDN" "$fqdn" _debug3 "omg.lol Address" "$address" echo "$fqdn" | sed 's/\.omg\.lol//g' | sed 's/\.'"$address"'$//g' } _unquoteString() { output=$1 quotes=0 _debug3 "_unquoteString()" _debug3 "Possibly quoted string" "$output" _startswith "$output" "\"" if [ $? ]; then quotes=$((quotes + 1)) fi _endswith "$output" "\"" if [ $? ]; then quotes=$((quotes + 1)) fi _debug3 "Original String: $output" _debug3 "Quotes found: $quotes" if [ $((quotes)) -gt 1 ]; then output=$(echo "$output" | sed 's/^"//g' | sed 's/"$//g') _debug3 "Quotes removed: $output" fi echo "$output" } acme.sh-3.1.0/dnsapi/dns_one.sh000066400000000000000000000140061472032365200162750ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_one_info='one.com Site: one.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_one Options: ONECOM_User Username ONECOM_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2103 ' dns_one_add() { fulldomain=$1 txtvalue=$2 if ! _dns_one_login; then _err "login failed" return 1 fi _debug "detect the root domain" if ! _get_root "$fulldomain"; then _err "root domain not found" return 1 fi subdomain="${_sub_domain}" maindomain=${_domain} _debug subdomain "$subdomain" _debug maindomain "$maindomain" #Check if the TXT exists _dns_one_getrecord "TXT" "$subdomain" "$txtvalue" if [ -n "$id" ]; then _info "$(__green "Txt record with the same value found. Skip adding.")" return 0 fi _dns_one_addrecord "TXT" "$subdomain" "$txtvalue" if [ -z "$id" ]; then _err "Add TXT record error." return 1 else _info "$(__green "Added, OK ($id)")" return 0 fi } dns_one_rm() { fulldomain=$1 txtvalue=$2 if ! _dns_one_login; then _err "login failed" return 1 fi _debug "detect the root domain" if ! _get_root "$fulldomain"; then _err "root domain not found" return 1 fi subdomain="${_sub_domain}" maindomain=${_domain} _debug subdomain "$subdomain" _debug maindomain "$maindomain" #Check if the TXT exists _dns_one_getrecord "TXT" "$subdomain" "$txtvalue" if [ -z "$id" ]; then _err "Txt record not found." return 1 fi # delete entry if _dns_one_delrecord "$id"; then _info "$(__green Removed, OK)" return 0 else _err "Removing txt record error." return 1 fi } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain="$1" i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi response="$(_get "https://www.one.com/admin/api/domains/$h/dns/custom_records")" if ! _contains "$response" "CRMRST_000302"; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi p=$i i=$(_math "$i" + 1) done _err "Unable to parse this domain" return 1 } _dns_one_login() { # get credentials ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}" ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}" if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then ONECOM_User="" ONECOM_Password="" _err "You didn't specify a one.com username and password yet." _err "Please create the key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable ONECOM_User "$ONECOM_User" _saveaccountconf_mutable ONECOM_Password "$ONECOM_Password" # Login with user and password postdata="loginDomain=true" postdata="$postdata&displayUsername=$ONECOM_User" postdata="$postdata&username=$ONECOM_User" postdata="$postdata&targetDomain=" postdata="$postdata&password1=$ONECOM_Password" postdata="$postdata&loginTarget=" #_debug postdata "$postdata" response="$(_post "$postdata" "https://www.one.com/admin/login.do" "" "POST" "application/x-www-form-urlencoded")" #_debug response "$response" # Get SessionID JSESSIONID="$(grep "OneSIDCrmAdmin" "$HTTP_HEADER" | grep "^[Ss]et-[Cc]ookie:" | _head_n 1 | _egrep_o 'OneSIDCrmAdmin=[^;]*;' | tr -d ';')" _debug jsessionid "$JSESSIONID" if [ -z "$JSESSIONID" ]; then _err "error sessionid cookie not found" return 1 fi export _H1="Cookie: ${JSESSIONID}" return 0 } _dns_one_getrecord() { type="$1" name="$2" value="$3" if [ -z "$type" ]; then type="TXT" fi if [ -z "$name" ]; then _err "Record name is empty." return 1 fi response="$(_get "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records")" response="$(echo "$response" | _normalizeJson)" _debug response "$response" if [ -z "${value}" ]; then id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"[^\"]*\",\"priority\":0,\"ttl\":600}.*/\1/p") response=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"[^\"]*\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"\([^\"]*\)\",\"priority\":0,\"ttl\":600}.*/\1/p") else id=$(printf -- "%s" "$response" | sed -n "s/.*{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"${name}\",\"type\":\"${type}\",\"content\":\"${value}\",\"priority\":0,\"ttl\":600}.*/\1/p") fi if [ -z "$id" ]; then return 1 fi return 0 } _dns_one_addrecord() { type="$1" name="$2" value="$3" if [ -z "$type" ]; then type="TXT" fi if [ -z "$name" ]; then _err "Record name is empty." return 1 fi postdata="{\"type\":\"dns_custom_records\",\"attributes\":{\"priority\":0,\"ttl\":600,\"type\":\"${type}\",\"prefix\":\"${name}\",\"content\":\"${value}\"}}" _debug postdata "$postdata" response="$(_post "$postdata" "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records" "" "POST" "application/json")" response="$(echo "$response" | _normalizeJson)" _debug response "$response" id=$(echo "$response" | sed -n "s/{\"result\":{\"data\":{\"type\":\"dns_custom_records\",\"id\":\"\([^\"]*\)\",\"attributes\":{\"prefix\":\"$subdomain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"priority\":0,\"ttl\":600}}},\"metadata\":null}/\1/p") if [ -z "$id" ]; then return 1 else return 0 fi } _dns_one_delrecord() { id="$1" if [ -z "$id" ]; then return 1 fi response="$(_post "" "https://www.one.com/admin/api/domains/$maindomain/dns/custom_records/$id" "" "DELETE" "application/json")" response="$(echo "$response" | _normalizeJson)" _debug response "$response" if [ "$response" = '{"result":null,"metadata":null}' ]; then return 0 else return 1 fi } acme.sh-3.1.0/dnsapi/dns_online.sh000077500000000000000000000132101472032365200167770ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_online_info='online.net Domains: scaleway.com Site: online.net Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_online Options: ONLINE_API_KEY API Key Issues: github.com/acmesh-official/acme.sh/issues/2093 ' # Online API # https://console.online.net/en/api/ ######## Public functions ##################### ONLINE_API="https://api.online.net/api/v1" #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_online_add() { fulldomain=$1 txtvalue=$2 if ! _online_check_config; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug _real_dns_version "$_real_dns_version" _info "Creating temporary zone version" _online_create_temporary_zone_version _info "Enabling temporary zone version" _online_enable_zone "$_temporary_dns_version" _info "Adding record" _online_create_TXT_record "$_real_dns_version" "$_sub_domain" "$txtvalue" _info "Disabling temporary version" _online_enable_zone "$_real_dns_version" _info "Destroying temporary version" _online_destroy_zone "$_temporary_dns_version" _info "Record added." return 0 } #fulldomain dns_online_rm() { fulldomain=$1 txtvalue=$2 if ! _online_check_config; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug _real_dns_version "$_real_dns_version" _debug "Getting txt records" if ! _online_rest GET "domain/$_domain/version/active"; then return 1 fi rid=$(echo "$response" | _egrep_o "\"id\":[0-9]+,\"name\":\"$_sub_domain\",\"data\":\"\\\u0022$txtvalue\\\u0022\"" | cut -d ':' -f 2 | cut -d ',' -f 1) _debug rid "$rid" if [ -z "$rid" ]; then return 1 fi _info "Creating temporary zone version" _online_create_temporary_zone_version _info "Enabling temporary zone version" _online_enable_zone "$_temporary_dns_version" _info "Removing DNS record" _online_rest DELETE "domain/$_domain/version/$_real_dns_version/zone/$rid" _info "Disabling temporary version" _online_enable_zone "$_real_dns_version" _info "Destroying temporary version" _online_destroy_zone "$_temporary_dns_version" return 0 } #################### Private functions below ################################## _online_check_config() { ONLINE_API_KEY="${ONLINE_API_KEY:-$(_readaccountconf_mutable ONLINE_API_KEY)}" if [ -z "$ONLINE_API_KEY" ]; then _err "No API key specified for Online API." _err "Create your key and export it as ONLINE_API_KEY" return 1 fi if ! _online_rest GET "domain/"; then _err "Invalid API key specified for Online API." return 1 fi _saveaccountconf_mutable ONLINE_API_KEY "$ONLINE_API_KEY" return 0 } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi _online_rest GET "domain/$h/version/active" if ! _contains "$response" "Domain not found" >/dev/null; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" _real_dns_version=$(echo "$response" | _egrep_o '"uuid_ref":.*' | cut -d ':' -f 2 | cut -d '"' -f 2) return 0 fi p=$i i=$(_math "$i" + 1) done _err "Unable to retrive DNS zone matching this domain" return 1 } # this function create a temporary zone version # as online.net does not allow updating an active version _online_create_temporary_zone_version() { _online_rest POST "domain/$_domain/version" "name=acme.sh" if [ "$?" != "0" ]; then return 1 fi _temporary_dns_version=$(echo "$response" | _egrep_o '"uuid_ref":.*' | cut -d ':' -f 2 | cut -d '"' -f 2) # Creating a dummy record in this temporary version, because online.net doesn't accept enabling an empty version _online_create_TXT_record "$_temporary_dns_version" "dummy.acme.sh" "dummy" return 0 } _online_destroy_zone() { version_id=$1 _online_rest DELETE "domain/$_domain/version/$version_id" if [ "$?" != "0" ]; then return 1 fi return 0 } _online_enable_zone() { version_id=$1 _online_rest PATCH "domain/$_domain/version/$version_id/enable" if [ "$?" != "0" ]; then return 1 fi return 0 } _online_create_TXT_record() { version=$1 txt_name=$2 txt_value=$3 _online_rest POST "domain/$_domain/version/$version/zone" "type=TXT&name=$txt_name&data=%22$txt_value%22&ttl=60&priority=0" # Note : the normal, expected response SHOULD be "Unknown method". # this happens because the API HTTP response contains a Location: header, that redirect # to an unknown online.net endpoint. if [ "$?" != "0" ] || _contains "$response" "Unknown method" || _contains "$response" "\$ref"; then return 0 else _err "error $response" return 1 fi } _online_rest() { m=$1 ep="$2" data="$3" _debug "$ep" _online_url="$ONLINE_API/$ep" _debug2 _online_url "$_online_url" export _H1="Authorization: Bearer $ONLINE_API_KEY" export _H2="X-Pretty-JSON: 1" if [ "$data" ] || [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$_online_url" "" "$m")" else response="$(_get "$_online_url")" fi if [ "$?" != "0" ] || _contains "$response" "invalid_grant" || _contains "$response" "Method not allowed"; then _err "error $response" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_openprovider.sh000077500000000000000000000222411472032365200202330ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_openprovider_info='OpenProvider.eu Site: OpenProvider.eu Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_openprovider Options: OPENPROVIDER_USER Username OPENPROVIDER_PASSWORDHASH Password hash Issues: github.com/acmesh-official/acme.sh/issues/2104 Author: Sylvia van Os ' OPENPROVIDER_API="https://api.openprovider.eu/" #OPENPROVIDER_API="https://api.cte.openprovider.eu/" # Test API ######## Public functions ##################### #Usage: dns_openprovider_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_openprovider_add() { fulldomain="$1" txtvalue="$2" OPENPROVIDER_USER="${OPENPROVIDER_USER:-$(_readaccountconf_mutable OPENPROVIDER_USER)}" OPENPROVIDER_PASSWORDHASH="${OPENPROVIDER_PASSWORDHASH:-$(_readaccountconf_mutable OPENPROVIDER_PASSWORDHASH)}" if [ -z "$OPENPROVIDER_USER" ] || [ -z "$OPENPROVIDER_PASSWORDHASH" ]; then _err "You didn't specify the openprovider user and/or password hash." return 1 fi # save the username and password to the account conf file. _saveaccountconf_mutable OPENPROVIDER_USER "$OPENPROVIDER_USER" _saveaccountconf_mutable OPENPROVIDER_PASSWORDHASH "$OPENPROVIDER_PASSWORDHASH" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_name "$_domain_name" _debug _domain_extension "$_domain_extension" _debug "Getting current records" existing_items="" results_retrieved=0 while true; do _openprovider_request "$(printf '%s.%s%s' "$_domain_name" "$_domain_extension" "$results_retrieved")" items="$response" while true; do item="$(echo "$items" | _egrep_o '.*<\/openXML>' | sed -n 's/.*\(.*<\/item>\).*/\1/p')" _debug existing_items "$existing_items" _debug results_retrieved "$results_retrieved" _debug item "$item" if [ -z "$item" ]; then break fi tmpitem="$(echo "$item" | sed 's/\*/\\*/g')" items="$(echo "$items" | sed "s|${tmpitem}||")" results_retrieved="$(_math "$results_retrieved" + 1)" new_item="$(echo "$item" | sed -n 's/.*.*\(\(.*\)\.'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/\2<\/name>\3\4\5\6<\/item>/p')" if [ -z "$new_item" ]; then # Domain apex new_item="$(echo "$item" | sed -n 's/.*.*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/\2<\/name>\3\4\5\6<\/item>/p')" fi if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then _debug "not an allowed record type, skipping" "$new_item" continue fi existing_items="$existing_items$new_item" done total="$(echo "$response" | _egrep_o '.*?<\/total>' | sed -n 's/.*\(.*\)<\/total>.*/\1/p')" _debug total "$total" if [ "$results_retrieved" -eq "$total" ]; then break fi done _debug "Creating acme record" acme_record="$(echo "$fulldomain" | sed -e "s/.$_domain_name.$_domain_extension$//")" _openprovider_request "$(printf '%s%smaster%s%sTXT%s600' "$_domain_name" "$_domain_extension" "$existing_items" "$acme_record" "$txtvalue")" return 0 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_openprovider_rm() { fulldomain="$1" txtvalue="$2" OPENPROVIDER_USER="${OPENPROVIDER_USER:-$(_readaccountconf_mutable OPENPROVIDER_USER)}" OPENPROVIDER_PASSWORDHASH="${OPENPROVIDER_PASSWORDHASH:-$(_readaccountconf_mutable OPENPROVIDER_PASSWORDHASH)}" if [ -z "$OPENPROVIDER_USER" ] || [ -z "$OPENPROVIDER_PASSWORDHASH" ]; then _err "You didn't specify the openprovider user and/or password hash." return 1 fi # save the username and password to the account conf file. _saveaccountconf_mutable OPENPROVIDER_USER "$OPENPROVIDER_USER" _saveaccountconf_mutable OPENPROVIDER_PASSWORDHASH "$OPENPROVIDER_PASSWORDHASH" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_name "$_domain_name" _debug _domain_extension "$_domain_extension" _debug "Getting current records" existing_items="" results_retrieved=0 while true; do _openprovider_request "$(printf '%s.%s%s' "$_domain_name" "$_domain_extension" "$results_retrieved")" # Remove acme records from items items="$response" while true; do item="$(echo "$items" | _egrep_o '.*<\/openXML>' | sed -n 's/.*\(.*<\/item>\).*/\1/p')" _debug existing_items "$existing_items" _debug results_retrieved "$results_retrieved" _debug item "$item" if [ -z "$item" ]; then break fi tmpitem="$(echo "$item" | sed 's/\*/\\*/g')" items="$(echo "$items" | sed "s|${tmpitem}||")" results_retrieved="$(_math "$results_retrieved" + 1)" if ! echo "$item" | grep -v "$fulldomain"; then _debug "acme record, skipping" "$item" continue fi new_item="$(echo "$item" | sed -n 's/.*.*\(\(.*\)\.'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/\2<\/name>\3\4\5\6<\/item>/p')" if [ -z "$new_item" ]; then # domain apex new_item="$(echo "$item" | sed -n 's/.*.*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/\2<\/name>\3\4\5\6<\/item>/p')" fi if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then _debug "not an allowed record type, skipping" "$new_item" continue fi existing_items="$existing_items$new_item" done total="$(echo "$response" | _egrep_o '.*?<\/total>' | sed -n 's/.*\(.*\)<\/total>.*/\1/p')" _debug total "$total" if [ "$results_retrieved" -eq "$total" ]; then break fi done _debug "Removing acme record" _openprovider_request "$(printf '%s%smaster%s' "$_domain_name" "$_domain_extension" "$existing_items")" return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _domain_name=domain # _domain_extension=com _get_root() { domain=$1 i=2 results_retrieved=0 while true; do h=$(echo "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi _openprovider_request "$(printf '%s%s' "$(echo "$h" | cut -d . -f 1)" "$results_retrieved")" items="$response" while true; do item="$(echo "$items" | _egrep_o '.*<\/openXML>' | sed -n 's/.*\(.*<\/domain>\).*/\1/p')" _debug existing_items "$existing_items" _debug results_retrieved "$results_retrieved" _debug item "$item" if [ -z "$item" ]; then break fi tmpitem="$(echo "$item" | sed 's/\*/\\*/g')" items="$(echo "$items" | sed "s|${tmpitem}||")" results_retrieved="$(_math "$results_retrieved" + 1)" _domain_name="$(echo "$item" | sed -n 's/.*.*\(.*\)<\/name>.*<\/domain>.*/\1/p')" _domain_extension="$(echo "$item" | sed -n 's/.*.*\(.*\)<\/extension>.*<\/domain>.*/\1/p')" _debug _domain_name "$_domain_name" _debug _domain_extension "$_domain_extension" if [ "$_domain_name.$_domain_extension" = "$h" ]; then return 0 fi done total="$(echo "$response" | _egrep_o '.*?<\/total>' | sed -n 's/.*\(.*\)<\/total>.*/\1/p')" _debug total "$total" if [ "$results_retrieved" -eq "$total" ]; then results_retrieved=0 i="$(_math "$i" + 1)" fi done return 1 } _openprovider_request() { request_xml=$1 xml_prefix='' xml_content=$(printf '%s%s%s' "$OPENPROVIDER_USER" "$OPENPROVIDER_PASSWORDHASH" "$request_xml") response="$(_post "$(echo "$xml_prefix$xml_content" | tr -d '\n')" "$OPENPROVIDER_API" "" "POST" "application/xml")" _debug response "$response" if ! _contains "$response" "0.*"; then _err "API request failed." return 1 fi } acme.sh-3.1.0/dnsapi/dns_openstack.sh000077500000000000000000000274101472032365200175110ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_openstack_info='OpenStack Designate API Depends on OpenStackClient and python-desginateclient. You will require Keystone V3 credentials loaded into your environment, which could be either password or v3 application credential type. Site: docs.openstack.org/api-ref/dns/ Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openstack Options: OS_AUTH_URL Auth URL. E.g. "https://keystone.example.com:5000/" OS_USERNAME Username OS_PASSWORD Password OS_PROJECT_NAME Project name OS_PROJECT_DOMAIN_NAME Project domain name. E.g. "Default" OS_USER_DOMAIN_NAME User domain name. E.g. "Default" Issues: github.com/acmesh-official/acme.sh/issues/3054 Author: Andy Botting ' ######## Public functions ##################### # Usage: dns_openstack_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_openstack_add() { fulldomain=$1 txtvalue=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _dns_openstack_credentials || return $? _dns_openstack_check_setup || return $? _dns_openstack_find_zone || return $? _dns_openstack_get_recordset || return $? _debug _recordset_id "$_recordset_id" if [ -n "$_recordset_id" ]; then _dns_openstack_get_records || return $? _debug _records "$_records" fi _dns_openstack_create_recordset || return $? } # Usage: dns_openstack_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Remove the txt record after validation. dns_openstack_rm() { fulldomain=$1 txtvalue=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" _dns_openstack_credentials || return $? _dns_openstack_check_setup || return $? _dns_openstack_find_zone || return $? _dns_openstack_get_recordset || return $? _debug _recordset_id "$_recordset_id" if [ -n "$_recordset_id" ]; then _dns_openstack_get_records || return $? _debug _records "$_records" fi _dns_openstack_delete_recordset || return $? } #################### Private functions below ################################## _dns_openstack_create_recordset() { if [ -z "$_recordset_id" ]; then _info "Creating a new recordset" if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record="$txtvalue" "$_zone_id" "$fulldomain."); then _err "No recordset ID found after create" return 1 fi else _info "Updating existing recordset" # Build new list of --record= args for update _record_args="--record=$txtvalue" for _rec in $_records; do _record_args="$_record_args --record=$_rec" done # shellcheck disable=SC2086 if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain."); then _err "Recordset update failed" return 1 fi fi _max_retries=60 _sleep_sec=5 _retry_times=0 while [ "$_retry_times" -lt "$_max_retries" ]; do _retry_times=$(_math "$_retry_times" + 1) _debug3 _retry_times "$_retry_times" _record_status=$(openstack recordset show -c status -f value "$_zone_id" "$_recordset_id") _info "Recordset status is $_record_status" if [ "$_record_status" = "ACTIVE" ]; then return 0 elif [ "$_record_status" = "ERROR" ]; then return 1 else _sleep $_sleep_sec fi done _err "Recordset failed to become ACTIVE" return 1 } _dns_openstack_delete_recordset() { if [ "$_records" = "$txtvalue" ]; then _info "Only one record found, deleting recordset" if ! openstack recordset delete "$_zone_id" "$fulldomain." >/dev/null; then _err "Failed to delete recordset" return 1 fi else _info "Found existing records, updating recordset" # Build new list of --record= args for update _record_args="" for _rec in $_records; do if [ "$_rec" = "$txtvalue" ]; then continue fi _record_args="$_record_args --record=$_rec" done # shellcheck disable=SC2086 if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then _err "Recordset update failed" return 1 fi fi } _dns_openstack_get_root() { # Take the full fqdn and strip away pieces until we get an exact zone name # match. For example, _acme-challenge.something.domain.com might need to go # into something.domain.com or domain.com _zone_name=$1 _zone_list=$2 while [ "$_zone_name" != "" ]; do _zone_name="$(echo "$_zone_name" | sed 's/[^.]*\.*//')" echo "$_zone_list" | while read -r id name; do if _startswith "$_zone_name." "$name"; then echo "$id" fi done done | _head_n 1 } _dns_openstack_find_zone() { if ! _zone_list="$(openstack zone list -c id -c name -f value)"; then _err "Can't list zones. Check your OpenStack credentials" return 1 fi _debug _zone_list "$_zone_list" if ! _zone_id="$(_dns_openstack_get_root "$fulldomain" "$_zone_list")"; then _err "Can't find a matching zone. Check your OpenStack credentials" return 1 fi _debug _zone_id "$_zone_id" } _dns_openstack_get_records() { if ! _records=$(openstack recordset show -c records -f value "$_zone_id" "$fulldomain."); then _err "Failed to get records" return 1 fi return 0 } _dns_openstack_get_recordset() { if ! _recordset_id=$(openstack recordset list -c id -f value --name "$fulldomain." "$_zone_id"); then _err "Failed to get recordset" return 1 fi return 0 } _dns_openstack_check_setup() { if ! _exists openstack; then _err "OpenStack client not found" return 1 fi } _dns_openstack_credentials() { _debug "Check OpenStack credentials" # If we have OS_AUTH_URL already set in the environment, then assume we want # to use those, otherwise use stored credentials if [ -n "$OS_AUTH_URL" ]; then _debug "OS_AUTH_URL env var found, using environment" else _debug "OS_AUTH_URL not found, loading stored credentials" OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}" OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}" OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}" OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}" OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}" OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}" OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}" OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}" OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}" OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}" OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}" OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}" OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}" fi # Check each var and either save or clear it depending on whether its set. # The helps us clear out old vars in the case where a user may want # to switch between password and app creds _debug "OS_AUTH_URL" "$OS_AUTH_URL" if [ -n "$OS_AUTH_URL" ]; then export OS_AUTH_URL _saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL" else unset OS_AUTH_URL _clearaccountconf SAVED_OS_AUTH_URL fi _debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION" if [ -n "$OS_IDENTITY_API_VERSION" ]; then export OS_IDENTITY_API_VERSION _saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION" else unset OS_IDENTITY_API_VERSION _clearaccountconf SAVED_OS_IDENTITY_API_VERSION fi _debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE" if [ -n "$OS_AUTH_TYPE" ]; then export OS_AUTH_TYPE _saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE" else unset OS_AUTH_TYPE _clearaccountconf SAVED_OS_AUTH_TYPE fi _debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID" if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then export OS_APPLICATION_CREDENTIAL_ID _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID" else unset OS_APPLICATION_CREDENTIAL_ID _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID fi _secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET" if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then export OS_APPLICATION_CREDENTIAL_SECRET _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET" else unset OS_APPLICATION_CREDENTIAL_SECRET _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET fi _debug "OS_USERNAME" "$OS_USERNAME" if [ -n "$OS_USERNAME" ]; then export OS_USERNAME _saveaccountconf_mutable OS_USERNAME "$OS_USERNAME" else unset OS_USERNAME _clearaccountconf SAVED_OS_USERNAME fi _secure_debug "OS_PASSWORD" "$OS_PASSWORD" if [ -n "$OS_PASSWORD" ]; then export OS_PASSWORD _saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD" else unset OS_PASSWORD _clearaccountconf SAVED_OS_PASSWORD fi _debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME" if [ -n "$OS_PROJECT_NAME" ]; then export OS_PROJECT_NAME _saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME" else unset OS_PROJECT_NAME _clearaccountconf SAVED_OS_PROJECT_NAME fi _debug "OS_PROJECT_ID" "$OS_PROJECT_ID" if [ -n "$OS_PROJECT_ID" ]; then export OS_PROJECT_ID _saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID" else unset OS_PROJECT_ID _clearaccountconf SAVED_OS_PROJECT_ID fi _debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME" if [ -n "$OS_USER_DOMAIN_NAME" ]; then export OS_USER_DOMAIN_NAME _saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME" else unset OS_USER_DOMAIN_NAME _clearaccountconf SAVED_OS_USER_DOMAIN_NAME fi _debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID" if [ -n "$OS_USER_DOMAIN_ID" ]; then export OS_USER_DOMAIN_ID _saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID" else unset OS_USER_DOMAIN_ID _clearaccountconf SAVED_OS_USER_DOMAIN_ID fi _debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME" if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then export OS_PROJECT_DOMAIN_NAME _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME" else unset OS_PROJECT_DOMAIN_NAME _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME fi _debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID" if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then export OS_PROJECT_DOMAIN_ID _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID" else unset OS_PROJECT_DOMAIN_ID _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID fi if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then # Application Credential auth if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then _err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID" _err "and OS_APPLICATION_CREDENTIAL_SECRET must be set." _err "Please check your credentials and try again." return 1 fi else # Password auth if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then _err "OpenStack username or password not found." _err "Please check your credentials and try again." return 1 fi if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then _err "When using password authentication, OS_PROJECT_NAME or" _err "OS_PROJECT_ID must be set." _err "Please check your credentials and try again." return 1 fi fi return 0 } acme.sh-3.1.0/dnsapi/dns_opnsense.sh000077500000000000000000000156021472032365200173540ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_opnsense_info='OPNsense Server Site: docs.opnsense.org/development/api.html Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_opnsense Options: OPNs_Host Server Hostname. E.g. "opnsense.example.com" OPNs_Port Port. Default: "443". OPNs_Key API Key OPNs_Token API Token OPNs_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept Issues: github.com/acmesh-official/acme.sh/issues/2480 ' ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" #fulldomain #txtvalue OPNs_DefaultPort=443 OPNs_DefaultApi_Insecure=0 dns_opnsense_add() { fulldomain=$1 txtvalue=$2 _opns_check_auth || return 1 if ! set_record "$fulldomain" "$txtvalue"; then return 1 fi return 0 } #fulldomain dns_opnsense_rm() { fulldomain=$1 txtvalue=$2 _opns_check_auth || return 1 if ! rm_record "$fulldomain" "$txtvalue"; then return 1 fi return 0 } set_record() { fulldomain=$1 new_challenge=$2 _info "Adding record $fulldomain with challenge: $new_challenge" _debug "Detect root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain "$_domain" _debug _host "$_host" _debug _domainid "$_domainid" _return_str="" _record_string="" _build_record_string "$_domainid" "$_host" "$new_challenge" _uuid="" if _existingchallenge "$_domain" "$_host" "$new_challenge"; then # Update if _opns_rest "POST" "/record/setRecord/${_uuid}" "$_record_string"; then _return_str="$response" else return 1 fi else #create if _opns_rest "POST" "/record/addRecord" "$_record_string"; then _return_str="$response" else return 1 fi fi if echo "$_return_str" | _egrep_o "\"result\":\"saved\"" >/dev/null; then _opns_rest "POST" "/service/reconfigure" "{}" _debug "Record created" else _err "Error creating record $_record_string" return 1 fi return 0 } rm_record() { fulldomain=$1 new_challenge="$2" _info "Remove record $fulldomain with challenge: $new_challenge" _debug "Detect root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain "$_domain" _debug _host "$_host" _debug _domainid "$_domainid" _uuid="" if _existingchallenge "$_domain" "$_host" "$new_challenge"; then # Delete if _opns_rest "POST" "/record/delRecord/${_uuid}" "\{\}"; then if echo "$_return_str" | _egrep_o "\"result\":\"deleted\"" >/dev/null; then _opns_rest "POST" "/service/reconfigure" "{}" _debug "Record deleted" else _err "Error deleting record $_host from domain $fulldomain" return 1 fi else _err "Error deleting record $_host from domain $fulldomain" return 1 fi else _info "Record not found, nothing to remove" fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _domainid=domid #_domain=domain.com _get_root() { domain=$1 i=2 p=1 if _opns_rest "GET" "/domain/searchPrimaryDomain"; then _domain_response="$response" else return 1 fi while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi _debug h "$h" id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"primary\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2) if [ -n "$id" ]; then _debug id "$id" _host=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="${h}" _domainid="${id}" return 0 fi p=$i i=$(_math "$i" + 1) done _debug "$domain not found" return 1 } _opns_rest() { method=$1 ep=$2 data=$3 #Percent encode user and token key=$(echo "$OPNs_Key" | tr -d "\n\r" | _url_encode) token=$(echo "$OPNs_Token" | tr -d "\n\r" | _url_encode) opnsense_url="https://${key}:${token}@${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}" export _H1="Content-Type: application/json" _debug2 "Try to call api: https://${OPNs_Host}:${OPNs_Port:-$OPNs_DefaultPort}/api/bind${ep}" if [ ! "$method" = "GET" ]; then _debug data "$data" export _H1="Content-Type: application/json" response="$(_post "$data" "$opnsense_url" "" "$method")" else export _H1="" response="$(_get "$opnsense_url")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } _build_record_string() { _record_string="{\"record\":{\"enabled\":\"1\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"}}" } _existingchallenge() { if _opns_rest "GET" "/record/searchRecord"; then _record_response="$response" else return 1 fi _uuid="" _uuid=$(echo "$_record_response" | _egrep_o "\"uuid\":\"[^\"]*\",\"enabled\":\"[01]\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"" | cut -d ':' -f 2 | cut -d '"' -f 2) if [ -n "$_uuid" ]; then _debug uuid "$_uuid" return 0 fi _debug "${2}.$1{1} record not found" return 1 } _opns_check_auth() { OPNs_Host="${OPNs_Host:-$(_readaccountconf_mutable OPNs_Host)}" OPNs_Port="${OPNs_Port:-$(_readaccountconf_mutable OPNs_Port)}" OPNs_Key="${OPNs_Key:-$(_readaccountconf_mutable OPNs_Key)}" OPNs_Token="${OPNs_Token:-$(_readaccountconf_mutable OPNs_Token)}" OPNs_Api_Insecure="${OPNs_Api_Insecure:-$(_readaccountconf_mutable OPNs_Api_Insecure)}" if [ -z "$OPNs_Host" ]; then _err "You don't specify OPNsense address." return 1 else _saveaccountconf_mutable OPNs_Host "$OPNs_Host" fi if ! printf '%s' "$OPNs_Port" | grep '^[0-9]*$' >/dev/null; then _err 'OPNs_Port specified but not numeric value' return 1 elif [ -z "$OPNs_Port" ]; then _info "OPNSense port not specified. Defaulting to using port $OPNs_DefaultPort" else _saveaccountconf_mutable OPNs_Port "$OPNs_Port" fi if ! printf '%s' "$OPNs_Api_Insecure" | grep '^[01]$' >/dev/null; then _err 'OPNs_Api_Insecure specified but not 0/1 value' return 1 elif [ -n "$OPNs_Api_Insecure" ]; then _saveaccountconf_mutable OPNs_Api_Insecure "$OPNs_Api_Insecure" fi export HTTPS_INSECURE="${OPNs_Api_Insecure:-$OPNs_DefaultApi_Insecure}" if [ -z "$OPNs_Key" ]; then _err "you have not specified your OPNsense api key id." _err "Please set OPNs_Key and try again." return 1 else _saveaccountconf_mutable OPNs_Key "$OPNs_Key" fi if [ -z "$OPNs_Token" ]; then _err "you have not specified your OPNsense token." _err "Please create OPNs_Token and try again." return 1 else _saveaccountconf_mutable OPNs_Token "$OPNs_Token" fi if ! _opns_rest "GET" "/general/get"; then _err "Call to OPNsense API interface failed. Unable to access OPNsense API." return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_ovh.sh000077500000000000000000000202231472032365200163110ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ovh_info='OVH.com Domains: kimsufi.com soyoustart.com Site: OVH.com Docs: github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api Options: OVH_END_POINT Endpoint. "ovh-eu", "ovh-us", "ovh-ca", "kimsufi-eu", "kimsufi-ca", "soyoustart-eu", "soyoustart-ca" or raw URL. Default: "ovh-eu". OVH_AK Application Key OVH_AS Application Secret OVH_CK Consumer Key ' #OVH_END_POINT=ovh-eu #'ovh-eu' OVH_EU='https://eu.api.ovh.com/1.0' #'ovh-us' OVH_US='https://api.us.ovhcloud.com/1.0' #'ovh-ca': OVH_CA='https://ca.api.ovh.com/1.0' #'kimsufi-eu' KSF_EU='https://eu.api.kimsufi.com/1.0' #'kimsufi-ca' KSF_CA='https://ca.api.kimsufi.com/1.0' #'soyoustart-eu' SYS_EU='https://eu.api.soyoustart.com/1.0' #'soyoustart-ca' SYS_CA='https://ca.api.soyoustart.com/1.0' wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api" ovh_success="https://github.com/acmesh-official/acme.sh/wiki/OVH-Success" _ovh_get_api() { _ogaep="$1" case "${_ogaep}" in ovh-eu | ovheu) printf "%s" $OVH_EU return ;; ovh-us | ovhus) printf "%s" $OVH_US return ;; ovh-ca | ovhca) printf "%s" $OVH_CA return ;; kimsufi-eu | kimsufieu) printf "%s" $KSF_EU return ;; kimsufi-ca | kimsufica) printf "%s" $KSF_CA return ;; soyoustart-eu | soyoustarteu) printf "%s" $SYS_EU return ;; soyoustart-ca | soyoustartca) printf "%s" $SYS_CA return ;; # raw API url starts with https:// https*) printf "%s" "$1" return ;; *) _err "Unknown endpoint : $1" return 1 ;; esac } _initAuth() { OVH_AK="${OVH_AK:-$(_readaccountconf_mutable OVH_AK)}" OVH_AS="${OVH_AS:-$(_readaccountconf_mutable OVH_AS)}" if [ -z "$OVH_AK" ] || [ -z "$OVH_AS" ]; then OVH_AK="" OVH_AS="" _err "You don't specify OVH application key and application secret yet." _err "Please create you key and try again." return 1 fi if [ "$OVH_AK" != "$(_readaccountconf OVH_AK)" ]; then _info "It seems that your ovh key is changed, let's clear consumer key first." _clearaccountconf_mutable OVH_CK fi _saveaccountconf_mutable OVH_AK "$OVH_AK" _saveaccountconf_mutable OVH_AS "$OVH_AS" OVH_END_POINT="${OVH_END_POINT:-$(_readaccountconf_mutable OVH_END_POINT)}" if [ -z "$OVH_END_POINT" ]; then OVH_END_POINT="ovh-eu" fi _info "Using OVH endpoint: $OVH_END_POINT" if [ "$OVH_END_POINT" != "ovh-eu" ]; then _saveaccountconf_mutable OVH_END_POINT "$OVH_END_POINT" fi OVH_API="$(_ovh_get_api "$OVH_END_POINT")" _debug OVH_API "$OVH_API" OVH_CK="${OVH_CK:-$(_readaccountconf_mutable OVH_CK)}" if [ -z "$OVH_CK" ]; then _info "OVH consumer key is empty, Let's get one:" if ! _ovh_authentication; then _err "Can not get consumer key." fi #return and wait for retry. return 1 fi _saveaccountconf_mutable OVH_CK "$OVH_CK" _info "Checking authentication" if ! _ovh_rest GET "domain" || _contains "$response" "INVALID_CREDENTIAL" || _contains "$response" "NOT_CREDENTIAL"; then _err "The consumer key is invalid: $OVH_CK" _err "Please retry to create a new one." _clearaccountconf_mutable OVH_CK return 1 fi _info "Consumer key is ok." return 0 } ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_ovh_add() { fulldomain=$1 txtvalue=$2 if ! _initAuth; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if _ovh_rest POST "domain/zone/$_domain/record" "{\"fieldType\":\"TXT\",\"subDomain\":\"$_sub_domain\",\"target\":\"$txtvalue\",\"ttl\":60}"; then if _contains "$response" "$txtvalue"; then _ovh_rest POST "domain/zone/$_domain/refresh" _debug "Refresh:$response" _info "Added, sleep 10 seconds." _sleep 10 return 0 fi fi _err "Add txt record error." return 1 } #fulldomain dns_ovh_rm() { fulldomain=$1 txtvalue=$2 if ! _initAuth; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" if ! _ovh_rest GET "domain/zone/$_domain/record?fieldType=TXT&subDomain=$_sub_domain"; then return 1 fi for rid in $(echo "$response" | tr '][,' ' '); do _debug rid "$rid" if ! _ovh_rest GET "domain/zone/$_domain/record/$rid"; then return 1 fi if _contains "$response" "\"target\":\"$txtvalue\""; then _debug "Found txt id:$rid" if ! _ovh_rest DELETE "domain/zone/$_domain/record/$rid"; then return 1 fi _ovh_rest POST "domain/zone/$_domain/refresh" _debug "Refresh:$response" return 0 fi done return 1 } #################### Private functions below ################################## _ovh_authentication() { _H1="X-Ovh-Application: $OVH_AK" _H2="Content-type: application/json" _H3="" _H4="" _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}' response="$(_post "$_ovhdata" "$OVH_API/auth/credential")" _debug3 response "$response" validationUrl="$(echo "$response" | _egrep_o "validationUrl\":\"[^\"]*\"" | _egrep_o "http.*\"" | tr -d '"')" if [ -z "$validationUrl" ]; then _err "Unable to get validationUrl" return 1 fi _debug validationUrl "$validationUrl" consumerKey="$(echo "$response" | _egrep_o "consumerKey\":\"[^\"]*\"" | cut -d : -f 2 | tr -d '"')" if [ -z "$consumerKey" ]; then _err "Unable to get consumerKey" return 1 fi _secure_debug consumerKey "$consumerKey" OVH_CK="$consumerKey" _saveaccountconf_mutable OVH_CK "$OVH_CK" _info "Please open this link to do authentication: $(__green "$validationUrl")" _info "Here is a guide for you: $(__green "$wiki")" _info "Please retry after the authentication is done." } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi if ! _ovh_rest GET "domain/zone/$h"; then return 1 fi if ! _contains "$response" "This service does not exist" >/dev/null && ! _contains "$response" "This call has not been granted" >/dev/null && ! _contains "$response" "NOT_GRANTED_CALL" >/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 return 1 } _ovh_timestamp() { _H1="" _H2="" _H3="" _H4="" _H5="" _get "$OVH_API/auth/time" "" 30 } _ovh_rest() { m=$1 ep="$2" data="$3" _debug "$ep" _ovh_url="$OVH_API/$ep" _debug2 _ovh_url "$_ovh_url" _ovh_t="$(_ovh_timestamp)" _debug2 _ovh_t "$_ovh_t" _ovh_p="$OVH_AS+$OVH_CK+$m+$_ovh_url+$data+$_ovh_t" _secure_debug _ovh_p "$_ovh_p" _ovh_hex="$(printf "%s" "$_ovh_p" | _digest sha1 hex)" _debug2 _ovh_hex "$_ovh_hex" export _H1="X-Ovh-Application: $OVH_AK" export _H2="X-Ovh-Signature: \$1\$$_ovh_hex" _debug2 _H2 "$_H2" export _H3="X-Ovh-Timestamp: $_ovh_t" export _H4="X-Ovh-Consumer: $OVH_CK" export _H5="Content-Type: application/json;charset=utf-8" if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then _debug data "$data" response="$(_post "$data" "$_ovh_url" "" "$m")" else response="$(_get "$_ovh_url")" fi if [ "$?" != "0" ] || _contains "$response" "INVALID_CREDENTIAL"; then _err "error $response" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_pdns.sh000077500000000000000000000141731472032365200164700ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_pdns_info='PowerDNS Server API Site: PowerDNS.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_pdns Options: PDNS_Url API URL. E.g. "http://ns.example.com:8081" PDNS_ServerId Server ID. E.g. "localhost" PDNS_Token API Token PDNS_Ttl=60 Domain TTL. Default: "60". ' DEFAULT_PDNS_TTL=60 ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" #fulldomain #txtvalue dns_pdns_add() { fulldomain=$1 txtvalue=$2 PDNS_Url="${PDNS_Url:-$(_readaccountconf_mutable PDNS_Url)}" PDNS_ServerId="${PDNS_ServerId:-$(_readaccountconf_mutable PDNS_ServerId)}" PDNS_Token="${PDNS_Token:-$(_readaccountconf_mutable PDNS_Token)}" PDNS_Ttl="${PDNS_Ttl:-$(_readaccountconf_mutable PDNS_Ttl)}" if [ -z "$PDNS_Url" ]; then PDNS_Url="" _err "You don't specify PowerDNS address." _err "Please set PDNS_Url and try again." return 1 fi if [ -z "$PDNS_ServerId" ]; then PDNS_ServerId="" _err "You don't specify PowerDNS server id." _err "Please set you PDNS_ServerId and try again." return 1 fi if [ -z "$PDNS_Token" ]; then PDNS_Token="" _err "You don't specify PowerDNS token." _err "Please create you PDNS_Token and try again." return 1 fi if [ -z "$PDNS_Ttl" ]; then PDNS_Ttl="$DEFAULT_PDNS_TTL" fi #save the api addr and key to the account conf file. _saveaccountconf_mutable PDNS_Url "$PDNS_Url" _saveaccountconf_mutable PDNS_ServerId "$PDNS_ServerId" _saveaccountconf_mutable PDNS_Token "$PDNS_Token" if [ "$PDNS_Ttl" != "$DEFAULT_PDNS_TTL" ]; then _saveaccountconf_mutable PDNS_Ttl "$PDNS_Ttl" fi _debug "Detect root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain "$_domain" if ! set_record "$_domain" "$fulldomain" "$txtvalue"; then return 1 fi return 0 } #fulldomain dns_pdns_rm() { fulldomain=$1 txtvalue=$2 PDNS_Url="${PDNS_Url:-$(_readaccountconf_mutable PDNS_Url)}" PDNS_ServerId="${PDNS_ServerId:-$(_readaccountconf_mutable PDNS_ServerId)}" PDNS_Token="${PDNS_Token:-$(_readaccountconf_mutable PDNS_Token)}" PDNS_Ttl="${PDNS_Ttl:-$(_readaccountconf_mutable PDNS_Ttl)}" if [ -z "$PDNS_Ttl" ]; then PDNS_Ttl="$DEFAULT_PDNS_TTL" fi _debug "Detect root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain "$_domain" if ! rm_record "$_domain" "$fulldomain" "$txtvalue"; then return 1 fi return 0 } set_record() { _info "Adding record" root=$1 full=$2 new_challenge=$3 _record_string="" _build_record_string "$new_challenge" _list_existingchallenges for oldchallenge in $_existing_challenges; do _build_record_string "$oldchallenge" done if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then _err "Set txt record error." return 1 fi if ! notify_slaves "$root"; then return 1 fi return 0 } rm_record() { _info "Remove record" root=$1 full=$2 txtvalue=$3 #Enumerate existing acme challenges _list_existingchallenges if _contains "$_existing_challenges" "$txtvalue"; then #Delete all challenges (PowerDNS API does not allow to delete content) if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}" "application/json"; then _err "Delete txt record error." return 1 fi _record_string="" #If the only existing challenge was the challenge to delete: nothing to do if ! [ "$_existing_challenges" = "$txtvalue" ]; then for oldchallenge in $_existing_challenges; do #Build up the challenges to re-add, ommitting the one what should be deleted if ! [ "$oldchallenge" = "$txtvalue" ]; then _build_record_string "$oldchallenge" fi done #Recreate the existing challenges if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then _err "Set txt record error." return 1 fi fi if ! notify_slaves "$root"; then return 1 fi else _info "Record not found, nothing to remove" fi return 0 } notify_slaves() { root=$1 if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root/notify"; then _err "Notify slaves error." return 1 fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _domain=domain.com _get_root() { domain=$1 i=1 if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then _zones_response=$(echo "$response" | _normalizeJson) fi while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if _contains "$_zones_response" "\"name\":\"$h.\""; then _domain="$h." if [ -z "$h" ]; then _domain="=2E" fi return 0 fi if [ -z "$h" ]; then return 1 fi i=$(_math "$i" + 1) done _debug "$domain not found" return 1 } _pdns_rest() { method=$1 ep=$2 data=$3 ct=$4 export _H1="X-API-Key: $PDNS_Token" if [ ! "$method" = "GET" ]; then _debug data "$data" response="$(_post "$data" "$PDNS_Url$ep" "" "$method" "$ct")" else response="$(_get "$PDNS_Url$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } _build_record_string() { _record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}" } _list_existingchallenges() { _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones/$root" _existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p') } acme.sh-3.1.0/dnsapi/dns_pleskxml.sh000066400000000000000000000446131472032365200173620ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_pleskxml_info='Plesk Server API Site: Plesk.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_pleskxml Options: pleskxml_uri Plesk server API URL. E.g. "https://your-plesk-server.net:8443/enterprise/control/agent.php" pleskxml_user Username pleskxml_pass Password Issues: github.com/acmesh-official/acme.sh/issues/2577 Author: Stilez, ' ## Plesk XML API described at: ## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709 ## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784 ## Note: a DNS ID with host = empty string is OK for this API, see ## https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 ## For example, to add a TXT record to DNS alias domain "acme-alias.com" would be a valid Plesk action. ## So this API module can handle such a request, if needed. ## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records. ## The user credentials (username+password) and URL/URI for the Plesk XML API must be set by the user #################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ################################## pleskxml_init_checks_done=0 # Variable containing bare newline - not a style issue # shellcheck disable=SC1004 NEWLINE='\ ' pleskxml_tplt_get_domains="" # Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh # Also used to test credentials and URI. # No params. pleskxml_tplt_get_additional_domains="" # Get a list of additional domains that PLESK can manage, so we can check root domain + host for acme.sh # No params. pleskxml_tplt_get_dns_records="%s" # Get all DNS records for a Plesk domain ID. # PARAM = Plesk domain id to query pleskxml_tplt_add_txt_record="%sTXT%s%s" # Add a TXT record to a domain. # PARAMS = (1) Plesk internal domain ID, (2) "hostname" for the new record, eg '_acme_challenge', (3) TXT record value pleskxml_tplt_rmv_dns_record="%s" # Delete a specific TXT record from a domain. # PARAM = the Plesk internal ID for the DNS record to be deleted #################### Public functions ################################## #Usage: dns_pleskxml_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_pleskxml_add() { fulldomain=$1 txtvalue=$2 _info "Entering dns_pleskxml_add() to add TXT record '$txtvalue' to domain '$fulldomain'..." # Get credentials if not already checked, and confirm we can log in to Plesk XML API if ! _credential_check; then return 1 fi # Get root and subdomain details, and Plesk domain ID if ! _pleskxml_get_root_domain "$fulldomain"; then return 1 fi _debug 'Credentials OK, and domain identified. Calling Plesk XML API to add TXT record' # printf using template in a variable - not a style issue # shellcheck disable=SC2059 request="$(printf "$pleskxml_tplt_add_txt_record" "$root_domain_id" "$sub_domain_name" "$txtvalue")" if ! _call_api "$request"; then return 1 fi # OK, we should have added a TXT record. Let's check and return success if so. # All that should be left in the result, is one section, containing okNEW_DNS_RECORD_ID results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' _err 'This is unexpected: something has gone wrong.' _err 'The full response was:' _err "$pleskxml_prettyprint_result" return 1 fi recid="$(_value "$results" | grep '[0-9]\{1,\}' | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" _info "Success. TXT record appears to be correctly added (Plesk record ID=$recid). Exiting dns_pleskxml_add()." return 0 } #Usage: dns_pleskxml_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_pleskxml_rm() { fulldomain=$1 txtvalue=$2 _info "Entering dns_pleskxml_rm() to remove TXT record '$txtvalue' from domain '$fulldomain'..." # Get credentials if not already checked, and confirm we can log in to Plesk XML API if ! _credential_check; then return 1 fi # Get root and subdomain details, and Plesk domain ID if ! _pleskxml_get_root_domain "$fulldomain"; then return 1 fi _debug 'Credentials OK, and domain identified. Calling Plesk XML API to get list of TXT records and their IDs' # printf using template in a variable - not a style issue # shellcheck disable=SC2059 request="$(printf "$pleskxml_tplt_get_dns_records" "$root_domain_id")" if ! _call_api "$request"; then return 1 fi # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) # Also strip out spaces between tags, redundant and group tags and any tags reclist="$( _api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s###g;s#<[a-z][^/<>]*/>##g' | grep "${root_domain_id}" | grep '[0-9]\{1,\}' | grep 'TXT' )" if [ -z "$reclist" ]; then _err "No TXT records found for root domain $fulldomain (Plesk domain ID ${root_domain_id}). Exiting." return 1 fi _debug "Got list of DNS TXT records for root Plesk domain ID ${root_domain_id} of root domain $fulldomain:" _debug "$reclist" # Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value recid="$( _value "$reclist" | grep -i "${fulldomain}." | grep "${txtvalue}" | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/' )" _debug "Got id from line: $recid" if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then _err "DNS records for root domain '${fulldomain}.' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" _err "Cannot delete TXT record. Exiting." return 1 fi _debug "Found Plesk record ID for target text string '${txtvalue}': ID=${recid}" _debug 'Calling Plesk XML API to remove TXT record' # printf using template in a variable - not a style issue # shellcheck disable=SC2059 request="$(printf "$pleskxml_tplt_rmv_dns_record" "$recid")" if ! _call_api "$request"; then return 1 fi # OK, we should have removed a TXT record. Let's check and return success if so. # All that should be left in the result, is one section, containing okPLESK_DELETED_DNS_RECORD_ID results="$(_api_response_split "$pleskxml_prettyprint_result" 'result' '')" if ! _value "$results" | grep 'ok' | grep '[0-9]\{1,\}' >/dev/null; then # Error - doesn't contain expected string. Something's wrong. _err 'Error when calling Plesk XML API.' _err 'The result did not contain the expected XXXXX section, or contained other values as well.' _err 'This is unexpected: something has gone wrong.' _err 'The full response was:' _err "$pleskxml_prettyprint_result" return 1 fi _info "Success. TXT record appears to be correctly removed. Exiting dns_pleskxml_rm()." return 0 } #################### Private functions below (utility functions) ################################## # Outputs value of a variable without additional newlines etc _value() { printf '%s' "$1" } # Outputs value of a variable (FQDN) and cuts it at 2 specified '.' delimiters, returning the text in between # $1, $2 = where to cut # $3 = FQDN _valuecut() { printf '%s' "$3" | cut -d . -f "${1}-${2}" } # Counts '.' present in a domain name or other string # $1 = domain name _countdots() { _value "$1" | tr -dc '.' | wc -c | sed 's/ //g' } # Cleans up an API response, splits it "one line per item in the response" and greps for a string that in the context, identifies "useful" lines # $1 - result string from API # $2 - plain text tag to resplit on (usually "result" or "domain"). NOT REGEX # $3 - basic regex to recognise useful return lines # note: $3 matches via basic NOT extended regex (BRE), as extended regex capabilities not needed at the moment. # Last line could change to instead, with suitable escaping of ['"/$], # if future Plesk XML API changes ever require extended regex _api_response_split() { printf '%s' "$1" | sed 's/^ +//;s/ +$//' | tr -d '\n\r' | sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" | grep "$3" } #################### Private functions below (DNS functions) ################################## # Calls Plesk XML API, and checks results for obvious issues _call_api() { request="$1" errtext='' _debug 'Entered _call_api(). Calling Plesk XML API with request:' _debug "'$request'" export _H1="HTTP_AUTH_LOGIN: $pleskxml_user" export _H2="HTTP_AUTH_PASSWD: $pleskxml_pass" export _H3="content-Type: text/xml" export _H4="HTTP_PRETTY_PRINT: true" pleskxml_prettyprint_result="$(_post "${request}" "$pleskxml_uri" "" "POST")" pleskxml_retcode="$?" _debug 'The responses from the Plesk XML server were:' _debug "retcode=$pleskxml_retcode. Literal response:" _debug "'$pleskxml_prettyprint_result'" # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. # Remove structure from result string, since it might contain values that are related to the status of the domain and not to the API request statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | sed '//,/<\/data>/d' | grep -c '^ *[^<]* *$')" statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | sed '//,/<\/data>/d' | grep -c '^ *ok *$')" _debug "statuslines_count_total=$statuslines_count_total." _debug "statuslines_count_okay=$statuslines_count_okay." if [ -z "$statuslines_count_total" ]; then # We have no status lines at all. Results are empty errtext='The Plesk XML API unexpectedly returned an empty set of results for this call.' elif [ "$statuslines_count_okay" -ne "$statuslines_count_total" ]; then # We have some status lines that aren't "ok". Any available details are in API response fields "status" "errcode" and "errtext" # Workaround for basic regex: # - filter output to keep only lines like this: "SPACEStextSPACES" (shouldn't be necessary with prettyprint but guarantees subsequent code is ok) # - then edit the 3 "useful" error tokens individually and remove closing tags on all lines # - then filter again to remove all lines not edited (which will be the lines not starting A-Z) errtext="$( _value "$pleskxml_prettyprint_result" | grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' | sed 's/^ */Status: /;s/^ */Error code: /;s/^ */Error text: /;s/<\/.*$//' | grep '^[A-Z]' )" fi if [ "$pleskxml_retcode" -ne 0 ] || [ "$errtext" != "" ]; then # Call failed, for reasons either in the retcode or the response text... if [ "$pleskxml_retcode" -eq 0 ]; then _err "The POST request was successfully sent to the Plesk server." else _err "The return code for the POST request was $pleskxml_retcode (non-zero = failure in submitting request to server)." fi if [ "$errtext" != "" ]; then _err 'The error responses received from the Plesk server were:' _err "$errtext" else _err "No additional error messages were received back from the Plesk server" fi _err "The Plesk XML API call failed." return 1 fi _debug "Leaving _call_api(). Successful call." return 0 } # Startup checks (credentials, URI) _credential_check() { _debug "Checking Plesk XML API login credentials and URI..." if [ "$pleskxml_init_checks_done" -eq 1 ]; then _debug "Initial checks already done, no need to repeat. Skipped." return 0 fi pleskxml_user="${pleskxml_user:-$(_readaccountconf_mutable pleskxml_user)}" pleskxml_pass="${pleskxml_pass:-$(_readaccountconf_mutable pleskxml_pass)}" pleskxml_uri="${pleskxml_uri:-$(_readaccountconf_mutable pleskxml_uri)}" if [ -z "$pleskxml_user" ] || [ -z "$pleskxml_pass" ] || [ -z "$pleskxml_uri" ]; then pleskxml_user="" pleskxml_pass="" pleskxml_uri="" _err "You didn't specify one or more of the Plesk XML API username, password, or URI." _err "Please create these and try again." _err "Instructions are in the 'dns_pleskxml' plugin source code or in the acme.sh documentation." return 1 fi # Test the API is usable, by trying to read the list of managed domains... _call_api "$pleskxml_tplt_get_domains" if [ "$pleskxml_retcode" -ne 0 ]; then _err 'Failed to access Plesk XML API.' _err "Please check your login credentials and Plesk URI, and that the URI is reachable, and try again." return 1 fi _saveaccountconf_mutable pleskxml_uri "$pleskxml_uri" _saveaccountconf_mutable pleskxml_user "$pleskxml_user" _saveaccountconf_mutable pleskxml_pass "$pleskxml_pass" _debug "Test login to Plesk XML API successful. Login credentials and URI successfully saved to the acme.sh configuration file for future use." pleskxml_init_checks_done=1 return 0 } # For a FQDN, identify the root domain managed by Plesk, its domain ID in Plesk, and the host if any. # IMPORTANT NOTE: a result with host = empty string is OK for this API, see # https://docs.plesk.com/en-US/obsidian/api-rpc/about-xml-api/reference/managing-dns/managing-dns-records/adding-dns-record.34798 # See notes at top of this file _pleskxml_get_root_domain() { original_full_domain_name="$1" _debug "Identifying DNS root domain for '$original_full_domain_name' that is managed by the Plesk account." # test if the domain as provided is valid for splitting. if [ "$(_countdots "$original_full_domain_name")" -eq 0 ]; then _err "Invalid domain. The ACME domain must contain at least two parts (aa.bb) to identify a domain and tld for the TXT record." return 1 fi _debug "Querying Plesk server for list of managed domains..." _call_api "$pleskxml_tplt_get_domains" if [ "$pleskxml_retcode" -ne 0 ]; then return 1 fi # Generate a crude list of domains known to this Plesk account based on subscriptions. # We convert tags to so it'll flag on a hit with either or fields, # for non-Western character sets. # Output will be one line per known domain, containing 2 tages and a single tag # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. output="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" debug_output="$(printf "%s" "$output" | sed -n 's:.*\(.*\).*:\1:p')" _debug 'Domains managed by Plesk server are:' _debug "$debug_output" _debug "Querying Plesk server for list of additional managed domains..." _call_api "$pleskxml_tplt_get_additional_domains" if [ "$pleskxml_retcode" -ne 0 ]; then return 1 fi # Generate a crude list of additional domains known to this Plesk account based on sites. # We convert tags to so it'll flag on a hit with either or fields, # for non-Western character sets. # Output will be one line per known domain, containing 2 tages and a single tag # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. output_additional="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" debug_additional="$(printf "%s" "$output_additional" | sed -n 's:.*\(.*\).*:\1:p')" _debug 'Additional domains managed by Plesk server are:' _debug "$debug_additional" # Concate the two outputs together. output="$(printf "%s" "$output $NEWLINE $output_additional")" debug_output="$(printf "%s" "$output" | sed -n 's:.*\(.*\).*:\1:p')" _debug 'Domains (including additional) managed by Plesk server are:' _debug "$debug_output" # loop and test if domain, or any parent domain, is managed by Plesk # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain root_domain_name="$original_full_domain_name" while true; do _debug "Checking if '$root_domain_name' is managed by the Plesk server..." root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match # SEE IMPORTANT NOTE ABOVE - THIS FUNCTION CAN RETURN HOST='', AND THAT'S OK FOR PLESK XML API WHICH ALLOWS IT. # SO WE HANDLE IT AND DON'T PREVENT IT sub_domain_name="$(_value "$original_full_domain_name" | sed "s/\.\{0,1\}${root_domain_name}"'$//')" _info "Success. Matched host '$original_full_domain_name' to: DOMAIN '${root_domain_name}' (Plesk ID '${root_domain_id}'), HOST '${sub_domain_name}'. Returning." return 0 fi # No match, try next parent up (if any)... root_domain_name="$(_valuecut 2 1000 "$root_domain_name")" if [ "$(_countdots "$root_domain_name")" -eq 0 ]; then _debug "No match, and next parent would be a TLD..." _err "Cannot find '$original_full_domain_name' or any parent domain of it, in Plesk." _err "Are you sure that this domain is managed by this Plesk server?" return 1 fi _debug "No match, trying next parent up..." done } acme.sh-3.1.0/dnsapi/dns_pointhq.sh000066400000000000000000000104531472032365200172000ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_pointhq_info='pointhq.com PointDNS Site: pointhq.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_pointhq Options: PointHQ_Key API Key PointHQ_Email Email Issues: github.com/acmesh-official/acme.sh/issues/2060 ' PointHQ_Api="https://api.pointhq.com" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_pointhq_add() { fulldomain=$1 txtvalue=$2 PointHQ_Key="${PointHQ_Key:-$(_readaccountconf_mutable PointHQ_Key)}" PointHQ_Email="${PointHQ_Email:-$(_readaccountconf_mutable PointHQ_Email)}" if [ -z "$PointHQ_Key" ] || [ -z "$PointHQ_Email" ]; then PointHQ_Key="" PointHQ_Email="" _err "You didn't specify a PointHQ API key and email yet." _err "Please create the key and try again." return 1 fi if ! _contains "$PointHQ_Email" "@"; then _err "It seems that the PointHQ_Email=$PointHQ_Email is not a valid email address." _err "Please check and retry." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable PointHQ_Key "$PointHQ_Key" _saveaccountconf_mutable PointHQ_Email "$PointHQ_Email" _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 _pointhq_rest POST "zones/$_domain/records" "{\"zone_record\": {\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":3600}}"; then if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_pointhq_rm() { fulldomain=$1 txtvalue=$2 PointHQ_Key="${PointHQ_Key:-$(_readaccountconf_mutable PointHQ_Key)}" PointHQ_Email="${PointHQ_Email:-$(_readaccountconf_mutable PointHQ_Email)}" if [ -z "$PointHQ_Key" ] || [ -z "$PointHQ_Email" ]; then PointHQ_Key="" PointHQ_Email="" _err "You didn't specify a PointHQ API key and email yet." _err "Please create the key and try again." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _pointhq_rest GET "zones/${_domain}/records?record_type=TXT&name=$_sub_domain" if ! printf "%s" "$response" | grep "^\[" >/dev/null; then _err "Error" return 1 fi if [ "$response" = "[]" ]; then _info "No records to remove." else record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | tr -d \" | head -n 1) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _pointhq_rest DELETE "zones/$_domain/records/$record_id"; then _err "Delete record error." return 1 fi _contains "$response" '"status":"OK"' fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _pointhq_rest GET "zones"; then return 1 fi if _contains "$response" "\"name\":\"$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 return 1 } _pointhq_rest() { m=$1 ep="$2" data="$3" _debug "$ep" _pointhq_auth=$(printf "%s:%s" "$PointHQ_Email" "$PointHQ_Key" | _base64) export _H1="Authorization: Basic $_pointhq_auth" export _H2="Content-Type: application/json" export _H3="Accept: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$PointHQ_Api/$ep" "" "$m")" else response="$(_get "$PointHQ_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_porkbun.sh000066400000000000000000000110461472032365200171750ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_porkbun_info='Porkbun.com Site: Porkbun.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_porkbun Options: PORKBUN_API_KEY API Key PORKBUN_SECRET_API_KEY API Secret Issues: github.com/acmesh-official/acme.sh/issues/3450 ' PORKBUN_Api="https://api.porkbun.com/api/json/v3" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_porkbun_add() { fulldomain=$1 txtvalue=$2 PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}" PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}" if [ -z "$PORKBUN_API_KEY" ] || [ -z "$PORKBUN_SECRET_API_KEY" ]; then PORKBUN_API_KEY='' PORKBUN_SECRET_API_KEY='' _err "You didn't specify a Porkbun api key and secret api key yet." _err "You can get yours from here https://porkbun.com/account/api." return 1 fi #save the credentials to the account conf file. _saveaccountconf_mutable PORKBUN_API_KEY "$PORKBUN_API_KEY" _saveaccountconf_mutable PORKBUN_SECRET_API_KEY "$PORKBUN_SECRET_API_KEY" _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so # we can not use updating anymore. # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) # _debug count "$count" # if [ "$count" = "0" ]; then _info "Adding record" if _porkbun_rest POST "dns/create/$_domain" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}"; then if _contains "$response" '\"status\":"SUCCESS"'; then _info "Added, OK" return 0 elif _contains "$response" "The record already exists"; then _info "Already exists, OK" return 0 else _err "Add txt record error. ($response)" return 1 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_porkbun_rm() { fulldomain=$1 txtvalue=$2 PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}" PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}" _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" count=$(echo "$response" | _egrep_o "\"count\": *[^,]*" | cut -d : -f 2 | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else record_id=$(echo "$response" | tr '{' '\n' | grep -- "$txtvalue" | cut -d, -f1 | cut -d: -f2 | tr -d \") _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _porkbun_rest POST "dns/delete/$_domain/$record_id"; then _err "Delete record error." return 1 fi echo "$response" | tr -d " " | grep '"status":"SUCCESS"' >/dev/null fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then return 1 fi if _porkbun_rest POST "dns/retrieve/$h"; then if _contains "$response" "\"status\":\"SUCCESS\""; then _domain=$h _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")" return 0 else _debug "Go to next level of $_domain" fi else _debug "Go to next level of $_domain" fi i=$(_math "$i" + 1) done return 1 } _porkbun_rest() { m=$1 ep="$2" data="$3" _debug "$ep" api_key_trimmed=$(echo "$PORKBUN_API_KEY" | tr -d '"') secret_api_key_trimmed=$(echo "$PORKBUN_SECRET_API_KEY" | tr -d '"') test -z "$data" && data="{" || data="$(echo "$data" | cut -d'}' -f1)," data="$data\"apikey\":\"$api_key_trimmed\",\"secretapikey\":\"$secret_api_key_trimmed\"}" export _H1="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$PORKBUN_Api/$ep" "" "$m")" else response="$(_get "$PORKBUN_Api/$ep")" fi _sleep 3 # prevent rate limit if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_rackcorp.sh000066400000000000000000000075151472032365200173270ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_rackcorp_info='RackCorp.com Site: RackCorp.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rackcorp Options: RACKCORP_APIUUID API UUID. See Portal: ADMINISTRATION -> API RACKCORP_APISECRET API Secret Issues: github.com/acmesh-official/acme.sh/issues/3351 Author: Stephen Dendtler ' RACKCORP_API_ENDPOINT="https://api.rackcorp.net/api/rest/v2.4/json.php" ######## Public functions ##################### dns_rackcorp_add() { fulldomain="$1" txtvalue="$2" _debug fulldomain="$fulldomain" _debug txtvalue="$txtvalue" if ! _rackcorp_validate; then return 1 fi _debug "Searching for root zone" if ! _get_root "$fulldomain"; then return 1 fi _debug _lookup "$_lookup" _debug _domain "$_domain" _info "Creating TXT record." if ! _rackcorp_api dns.record.create "\"name\":\"$_domain\",\"type\":\"TXT\",\"lookup\":\"$_lookup\",\"data\":\"$txtvalue\",\"ttl\":300"; then return 1 fi return 0 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_rackcorp_rm() { fulldomain=$1 txtvalue=$2 _debug fulldomain="$fulldomain" _debug txtvalue="$txtvalue" if ! _rackcorp_validate; then return 1 fi _debug "Searching for root zone" if ! _get_root "$fulldomain"; then return 1 fi _debug _lookup "$_lookup" _debug _domain "$_domain" _info "Creating TXT record." if ! _rackcorp_api dns.record.delete "\"name\":\"$_domain\",\"type\":\"TXT\",\"lookup\":\"$_lookup\",\"data\":\"$txtvalue\""; then return 1 fi return 0 } #################### Private functions below ################################## #_acme-challenge.domain.com #returns # _lookup=_acme-challenge # _domain=domain.com _get_root() { domain=$1 i=1 p=1 if ! _rackcorp_api dns.domain.getall "\"name\":\"$domain\""; then return 1 fi while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug searchhost "$h" if [ -z "$h" ]; then _err "Could not find domain for record $domain in RackCorp using the provided credentials" #not valid return 1 fi _rackcorp_api dns.domain.getall "\"exactName\":\"$h\"" if _contains "$response" "\"matches\":1"; then if _contains "$response" "\"name\":\"$h\""; then _lookup=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi fi p=$i i=$(_math "$i" + 1) done return 1 } _rackcorp_validate() { RACKCORP_APIUUID="${RACKCORP_APIUUID:-$(_readaccountconf_mutable RACKCORP_APIUUID)}" if [ -z "$RACKCORP_APIUUID" ]; then RACKCORP_APIUUID="" _err "You require a RackCorp API UUID (export RACKCORP_APIUUID=\"\")" _err "Please login to the portal and create an API key and try again." return 1 fi _saveaccountconf_mutable RACKCORP_APIUUID "$RACKCORP_APIUUID" RACKCORP_APISECRET="${RACKCORP_APISECRET:-$(_readaccountconf_mutable RACKCORP_APISECRET)}" if [ -z "$RACKCORP_APISECRET" ]; then RACKCORP_APISECRET="" _err "You require a RackCorp API secret (export RACKCORP_APISECRET=\"\")" _err "Please login to the portal and create an API key and try again." return 1 fi _saveaccountconf_mutable RACKCORP_APISECRET "$RACKCORP_APISECRET" return 0 } _rackcorp_api() { _rackcorpcmd=$1 _rackcorpinputdata=$2 _debug cmd "$_rackcorpcmd $_rackcorpinputdata" export _H1="Accept: application/json" response="$(_post "{\"APIUUID\":\"$RACKCORP_APIUUID\",\"APISECRET\":\"$RACKCORP_APISECRET\",\"cmd\":\"$_rackcorpcmd\",$_rackcorpinputdata}" "$RACKCORP_API_ENDPOINT" "" "POST")" if [ "$?" != "0" ]; then _err "error $response" return 1 fi _debug2 response "$response" if _contains "$response" "\"code\":\"OK\""; then _debug code "OK" else _debug code "FAILED" response="" return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_rackspace.sh000066400000000000000000000145101472032365200174500ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_rackspace_info='RackSpace.com Site: RackSpace.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_rackspace Options: RACKSPACE_Apikey API Key RACKSPACE_Username Username Issues: github.com/acmesh-official/acme.sh/issues/2091 ' RACKSPACE_Endpoint="https://dns.api.rackspacecloud.com/v1.0" # 20210923 - RS changed the fields in the API response; fix sed # 20190213 - The name & id fields swapped in the API response; fix sed # 20190101 - Duplicating file for new pull request to dev branch # Original - tcocca:rackspace_dnsapi https://github.com/acmesh-official/acme.sh/pull/1297 ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_rackspace_add() { fulldomain="$1" _debug fulldomain="$fulldomain" txtvalue="$2" _debug txtvalue="$txtvalue" _rackspace_check_auth || return 1 _rackspace_check_rootzone || return 1 _info "Creating TXT record." if ! _rackspace_rest POST "$RACKSPACE_Tenant/domains/$_domain_id/records" "{\"records\":[{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":300}]}"; then return 1 fi _debug2 response "$response" if ! _contains "$response" "$txtvalue" >/dev/null; then _err "Could not add TXT record." return 1 fi return 0 } #fulldomain txtvalue dns_rackspace_rm() { fulldomain=$1 _debug fulldomain="$fulldomain" txtvalue=$2 _debug txtvalue="$txtvalue" _rackspace_check_auth || return 1 _rackspace_check_rootzone || return 1 _info "Checking for TXT record." if ! _get_recordid "$_domain_id" "$fulldomain" "$txtvalue"; then _err "Could not get TXT record id." return 1 fi if [ "$_dns_record_id" = "" ]; then _err "TXT record not found." return 1 fi _info "Removing TXT record." if ! _delete_txt_record "$_domain_id" "$_dns_record_id"; then _err "Could not remove TXT record $_dns_record_id." fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root_zone() { domain="$1" i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _rackspace_rest GET "$RACKSPACE_Tenant/domains/search?name=$h"; then return 1 fi _debug2 response "$response" if _contains "$response" "\"name\":\"$h\"" >/dev/null; then # Response looks like: # {"id":"12345","accountId":"1111111","name": "example.com","ttl":3600,"emailAddress": ... _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\"\([^,]*\)\",\"accountId\":\"[0-9]*\",\"name\":\"$h\",.*/\1/p") _debug2 domain_id "$_domain_id" if [ -n "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _get_recordid() { domainid="$1" fulldomain="$2" txtvalue="$3" if ! _rackspace_rest GET "$RACKSPACE_Tenant/domains/$domainid/records?name=$fulldomain&type=TXT"; then return 1 fi _debug response "$response" if ! _contains "$response" "$txtvalue"; then _dns_record_id=0 return 0 fi _dns_record_id=$(echo "$response" | tr '{' "\n" | grep "\"data\":\"$txtvalue\"" | sed -n 's/^.*"id":"\([^"]*\)".*/\1/p') _debug _dns_record_id "$_dns_record_id" return 0 } _delete_txt_record() { domainid="$1" _dns_record_id="$2" if ! _rackspace_rest DELETE "$RACKSPACE_Tenant/domains/$domainid/records?id=$_dns_record_id"; then return 1 fi _debug response "$response" if ! _contains "$response" "RUNNING"; then return 1 fi return 0 } _rackspace_rest() { m="$1" ep="$2" data="$3" _debug ep "$ep" export _H1="Accept: application/json" export _H2="X-Auth-Token: $RACKSPACE_Token" export _H3="X-Project-Id: $RACKSPACE_Tenant" export _H4="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$RACKSPACE_Endpoint/$ep" "" "$m")" retcode=$? else _info "Getting $RACKSPACE_Endpoint/$ep" response="$(_get "$RACKSPACE_Endpoint/$ep")" retcode=$? fi if [ "$retcode" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } _rackspace_authorization() { export _H1="Content-Type: application/json" data="{\"auth\":{\"RAX-KSKEY:apiKeyCredentials\":{\"username\":\"$RACKSPACE_Username\",\"apiKey\":\"$RACKSPACE_Apikey\"}}}" _debug data "$data" response="$(_post "$data" "https://identity.api.rackspacecloud.com/v2.0/tokens" "" "POST")" retcode=$? _debug2 response "$response" if [ "$retcode" != "0" ]; then _err "Authentication failed." return 1 fi if _contains "$response" "token"; then RACKSPACE_Token="$(echo "$response" | _normalizeJson | sed -n 's/^.*"token":{.*,"id":"\([^"]*\)",".*/\1/p')" RACKSPACE_Tenant="$(echo "$response" | _normalizeJson | sed -n 's/^.*"token":{.*,"id":"\([^"]*\)"}.*/\1/p')" _debug RACKSPACE_Token "$RACKSPACE_Token" _debug RACKSPACE_Tenant "$RACKSPACE_Tenant" fi return 0 } _rackspace_check_auth() { # retrieve the rackspace creds RACKSPACE_Username="${RACKSPACE_Username:-$(_readaccountconf_mutable RACKSPACE_Username)}" RACKSPACE_Apikey="${RACKSPACE_Apikey:-$(_readaccountconf_mutable RACKSPACE_Apikey)}" # check their vals for null if [ -z "$RACKSPACE_Username" ] || [ -z "$RACKSPACE_Apikey" ]; then RACKSPACE_Username="" RACKSPACE_Apikey="" _err "You didn't specify a Rackspace username and api key." _err "Please set those values and try again." return 1 fi # save the username and api key to the account conf file. _saveaccountconf_mutable RACKSPACE_Username "$RACKSPACE_Username" _saveaccountconf_mutable RACKSPACE_Apikey "$RACKSPACE_Apikey" if [ -z "$RACKSPACE_Token" ]; then _info "Getting authorization token." if ! _rackspace_authorization; then _err "Can not get token." fi fi } _rackspace_check_rootzone() { _debug "First detect the root zone" if ! _get_root_zone "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" } acme.sh-3.1.0/dnsapi/dns_rage4.sh000077500000000000000000000057761472032365200165370ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_rage4_info='rage4.com Site: rage4.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_rage4 Options: RAGE4_TOKEN API Key RAGE4_USERNAME Username Issues: github.com/acmesh-official/acme.sh/issues/4306 ' RAGE4_Api="https://rage4.com/rapi/" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_rage4_add() { fulldomain=$1 txtvalue=$2 unquotedtxtvalue=$(echo "$txtvalue" | tr -d \") RAGE4_USERNAME="${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}" RAGE4_TOKEN="${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}" if [ -z "$RAGE4_USERNAME" ] || [ -z "$RAGE4_TOKEN" ]; then RAGE4_USERNAME="" RAGE4_TOKEN="" _err "You didn't specify a Rage4 api token and username yet." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable RAGE4_USERNAME "$RAGE4_USERNAME" _saveaccountconf_mutable RAGE4_TOKEN "$RAGE4_TOKEN" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1" return 0 } #fulldomain txtvalue dns_rage4_rm() { fulldomain=$1 txtvalue=$2 RAGE4_USERNAME="${RAGE4_USERNAME:-$(_readaccountconf_mutable RAGE4_USERNAME)}" RAGE4_TOKEN="${RAGE4_TOKEN:-$(_readaccountconf_mutable RAGE4_TOKEN)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug "Getting txt records" _rage4_rest "getrecords/?id=${_domain_id}" _record_id=$(echo "$response" | sed -rn 's/.*"id":([[:digit:]]+)[^\}]*'"$txtvalue"'.*/\1/p') _rage4_rest "deleterecord/?id=${_record_id}" return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 if ! _rage4_rest "getdomains"; then return 1 fi _debug _get_root_domain "$domain" for line in $(echo "$response" | tr '}' '\n'); do __domain=$(echo "$line" | sed -rn 's/.*"name":"([^"]*)",.*/\1/p') __domain_id=$(echo "$line" | sed -rn 's/.*"id":([^,]*),.*/\1/p') if [ "$domain" != "${domain%"$__domain"*}" ]; then _domain_id="$__domain_id" break fi done if [ -z "$_domain_id" ]; then return 1 fi return 0 } _rage4_rest() { ep="$1" _debug "$ep" username_trimmed=$(echo "$RAGE4_USERNAME" | tr -d '"') token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"') auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64) export _H1="Content-Type: application/json" export _H2="Authorization: Basic $auth" response="$(_get "$RAGE4_Api$ep")" if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_rcode0.sh000077500000000000000000000151111472032365200166710ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_rcode0_info='Rcode0 rcodezero.at Site: rcodezero.at Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rcode0 Options: RCODE0_URL API URL. E.g. "https://my.rcodezero.at" RCODE0_API_TOKEN API Token RCODE0_TTL TTL. Default: "60". Issues: github.com/acmesh-official/acme.sh/issues/2490 ' #Rcode0 API Integration #https://my.rcodezero.at/api-doc # # log into https://my.rcodezero.at/enableapi and get your ACME API Token (the ACME API token has limited # access to the REST calls needed for acme.sh only) DEFAULT_RCODE0_URL="https://my.rcodezero.at" DEFAULT_RCODE0_TTL=60 ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" #fulldomain #txtvalue dns_rcode0_add() { fulldomain=$1 txtvalue=$2 RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}" RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}" RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}" if [ -z "$RCODE0_URL" ]; then RCODE0_URL="$DEFAULT_RCODE0_URL" fi if [ -z "$RCODE0_API_TOKEN" ]; then RCODE0_API_TOKEN="" _err "Missing Rcode0 ACME API Token." _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again." return 1 fi if [ -z "$RCODE0_TTL" ]; then RCODE0_TTL="$DEFAULT_RCODE0_TTL" fi #save the token to the account conf file. _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN" if [ "$RCODE0_URL" != "$DEFAULT_RCODE0_URL" ]; then _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL" fi if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL" fi _debug "Detect root zone" if ! _get_root "$fulldomain"; then _err "No 'MASTER' zone for $fulldomain found at RcodeZero Anycast." return 1 fi _debug _domain "$_domain" _debug "Adding record" _record_string="" _build_record_string "$txtvalue" _list_existingchallenges for oldchallenge in $_existing_challenges; do _build_record_string "$oldchallenge" done _debug "Challenges: $_existing_challenges" if [ -z "$_existing_challenges" ]; then if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"add\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then _err "Add txt record error." return 1 fi else # try update in case a records exists (need for wildcard certs) if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then _err "Set txt record error." return 1 fi fi return 0 } #fulldomain txtvalue dns_rcode0_rm() { fulldomain=$1 txtvalue=$2 RCODE0_API_TOKEN="${RCODE0_API_TOKEN:-$(_readaccountconf_mutable RCODE0_API_TOKEN)}" RCODE0_URL="${RCODE0_URL:-$(_readaccountconf_mutable RCODE0_URL)}" RCODE0_TTL="${RCODE0_TTL:-$(_readaccountconf_mutable RCODE0_TTL)}" if [ -z "$RCODE0_URL" ]; then RCODE0_URL="$DEFAULT_RCODE0_URL" fi if [ -z "$RCODE0_API_TOKEN" ]; then RCODE0_API_TOKEN="" _err "Missing Rcode0 API Token." _err "Please login and create your token at httsp://my.rcodezero.at/enableapi and try again." return 1 fi #save the api addr and key to the account conf file. _saveaccountconf_mutable RCODE0_URL "$RCODE0_URL" _saveaccountconf_mutable RCODE0_API_TOKEN "$RCODE0_API_TOKEN" if [ "$RCODE0_TTL" != "$DEFAULT_RCODE0_TTL" ]; then _saveaccountconf_mutable RCODE0_TTL "$RCODE0_TTL" fi if [ -z "$RCODE0_TTL" ]; then RCODE0_TTL="$DEFAULT_RCODE0_TTL" fi _debug "Detect root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug "Remove record" #Enumerate existing acme challenges _list_existingchallenges if _contains "$_existing_challenges" "$txtvalue"; then #Delete all challenges (PowerDNS API does not allow to delete content) if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"delete\", \"name\": \"$fulldomain.\", \"type\": \"TXT\"}]"; then _err "Delete txt record error." return 1 fi _record_string="" #If the only existing challenge was the challenge to delete: nothing to do if ! [ "$_existing_challenges" = "$txtvalue" ]; then for oldchallenge in $_existing_challenges; do #Build up the challenges to re-add, ommitting the one what should be deleted if ! [ "$oldchallenge" = "$txtvalue" ]; then _build_record_string "$oldchallenge" fi done #Recreate the existing challenges if ! _rcode0_rest "PATCH" "/api/v1/acme/zones/$_domain/rrsets" "[{\"changetype\": \"update\", \"name\": \"$fulldomain.\", \"type\": \"TXT\", \"ttl\": $RCODE0_TTL, \"records\": [$_record_string]}]"; then _err "Set txt record error." return 1 fi fi else _info "Record not found, nothing to remove" fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _domain=domain.com _get_root() { domain=$1 i=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug "try to find: $h" if _rcode0_rest "GET" "/api/v1/acme/zones/$h"; then if [ "$response" = "[\"found\"]" ]; then _domain="$h" if [ -z "$h" ]; then _domain="=2E" fi return 0 elif [ "$response" = "[\"not a master domain\"]" ]; then return 1 fi fi if [ -z "$h" ]; then return 1 fi i=$(_math "$i" + 1) done _debug "no matching domain for $domain found" return 1 } _rcode0_rest() { method=$1 ep=$2 data=$3 export _H1="Authorization: Bearer $RCODE0_API_TOKEN" if [ ! "$method" = "GET" ]; then _debug data "$data" response="$(_post "$data" "$RCODE0_URL$ep" "" "$method")" else response="$(_get "$RCODE0_URL$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } _build_record_string() { _record_string="${_record_string:+${_record_string}, }{\"content\": \"\\\"${1}\\\"\", \"disabled\": false}" } _list_existingchallenges() { _rcode0_rest "GET" "/api/v1/acme/zones/$_domain/rrsets" _existing_challenges=$(echo "$response" | _normalizeJson | _egrep_o "\"name\":\"${fulldomain}[^]]*}" | _egrep_o 'content\":\"\\"[^\\]*' | sed -n 's/^content":"\\"//p') _debug2 "$_existing_challenges" } acme.sh-3.1.0/dnsapi/dns_regru.sh000066400000000000000000000074601472032365200166460ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_regru_info='reg.ru Site: reg.ru Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_regru Options: REGRU_API_Username Username REGRU_API_Password Password Issues: github.com/acmesh-official/acme.sh/issues/2336 ' REGRU_API_URL="https://api.reg.ru/api/regru2" ######## Public functions ##################### dns_regru_add() { fulldomain=$1 txtvalue=$2 REGRU_API_Username="${REGRU_API_Username:-$(_readaccountconf_mutable REGRU_API_Username)}" REGRU_API_Password="${REGRU_API_Password:-$(_readaccountconf_mutable REGRU_API_Password)}" if [ -z "$REGRU_API_Username" ] || [ -z "$REGRU_API_Password" ]; then REGRU_API_Username="" REGRU_API_Password="" _err "You don't specify regru password or username." return 1 fi _saveaccountconf_mutable REGRU_API_Username "$REGRU_API_Username" _saveaccountconf_mutable REGRU_API_Password "$REGRU_API_Password" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain "$_domain" _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//") _debug _subdomain "$_subdomain" _info "Adding TXT record to ${fulldomain}" _regru_rest POST "zone/add_txt" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json" if ! _contains "${response}" 'error'; then return 0 fi _err "Could not create resource record, check logs" _err "${response}" return 1 } dns_regru_rm() { fulldomain=$1 txtvalue=$2 REGRU_API_Username="${REGRU_API_Username:-$(_readaccountconf_mutable REGRU_API_Username)}" REGRU_API_Password="${REGRU_API_Password:-$(_readaccountconf_mutable REGRU_API_Password)}" if [ -z "$REGRU_API_Username" ] || [ -z "$REGRU_API_Password" ]; then REGRU_API_Username="" REGRU_API_Password="" _err "You don't specify regru password or username." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain "$_domain" _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//") _debug _subdomain "$_subdomain" _info "Deleting resource record $fulldomain" _regru_rest POST "zone/remove_record" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json" if ! _contains "${response}" 'error'; then return 0 fi _err "Could not delete resource record, check logs" _err "${response}" return 1 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _domain=domain.com _get_root() { domain=$1 _regru_rest POST "service/get_list" "username=${REGRU_API_Username}&password=${REGRU_API_Password}&output_format=xml&servtype=domain" domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g") for ITEM in ${domains_list}; do IDN_ITEM=${ITEM} case "${domain}" in *${IDN_ITEM}*) _domain="$(_idn "${ITEM}")" _debug _domain "${_domain}" return 0 ;; esac done return 1 } #returns # response _regru_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Content-Type: application/x-www-form-urlencoded" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$REGRU_API_URL/$ep" "" "$m")" else response="$(_get "$REGRU_API_URL/$ep?$data")" fi _debug response "${response}" return 0 } acme.sh-3.1.0/dnsapi/dns_scaleway.sh000077500000000000000000000103161472032365200173270ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_scaleway_info='ScaleWay.com Site: ScaleWay.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_scaleway Options: SCALEWAY_API_TOKEN API Token Issues: github.com/acmesh-official/acme.sh/issues/3295 ' # Scaleway API # https://developers.scaleway.com/en/products/domain/dns/api/ ######## Public functions ##################### SCALEWAY_API="https://api.scaleway.com/domain/v2beta1" #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_scaleway_add() { fulldomain=$1 txtvalue=$2 if ! _scaleway_check_config; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" _scaleway_create_TXT_record "$_domain" "$_sub_domain" "$txtvalue" if _contains "$response" "records"; then return 0 else _err error "$response" return 1 fi } dns_scaleway_rm() { fulldomain=$1 txtvalue=$2 if ! _scaleway_check_config; then return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Deleting record" _scaleway_delete_TXT_record "$_domain" "$_sub_domain" "$txtvalue" if _contains "$response" "records"; then return 0 else _err error "$response" return 1 fi } #################### Private functions below ################################## _scaleway_check_config() { SCALEWAY_API_TOKEN="${SCALEWAY_API_TOKEN:-$(_readaccountconf_mutable SCALEWAY_API_TOKEN)}" if [ -z "$SCALEWAY_API_TOKEN" ]; then _err "No API key specified for Scaleway API." _err "Create your key and export it as SCALEWAY_API_TOKEN" return 1 fi if ! _scaleway_rest GET "dns-zones"; then _err "Invalid API key specified for Scaleway API." return 1 fi _saveaccountconf_mutable SCALEWAY_API_TOKEN "$SCALEWAY_API_TOKEN" return 0 } #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi _scaleway_rest GET "dns-zones/$h/records" if ! _contains "$response" "subdomain not found" >/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 _err "Unable to retrive DNS zone matching this domain" return 1 } # this function add a TXT record _scaleway_create_TXT_record() { txt_zone=$1 txt_name=$2 txt_value=$3 _scaleway_rest PATCH "dns-zones/$txt_zone/records" "{\"return_all_records\":false,\"changes\":[{\"add\":{\"records\":[{\"name\":\"$txt_name\",\"data\":\"$txt_value\",\"type\":\"TXT\",\"ttl\":60}]}}]}" if _contains "$response" "records"; then return 0 else _err "error1 $response" return 1 fi } # this function delete a TXT record based on name and content _scaleway_delete_TXT_record() { txt_zone=$1 txt_name=$2 txt_value=$3 _scaleway_rest PATCH "dns-zones/$txt_zone/records" "{\"return_all_records\":false,\"changes\":[{\"delete\":{\"id_fields\":{\"name\":\"$txt_name\",\"data\":\"$txt_value\",\"type\":\"TXT\"}}}]}" if _contains "$response" "records"; then return 0 else _err "error2 $response" return 1 fi } _scaleway_rest() { m=$1 ep="$2" data="$3" _debug "$ep" _scaleway_url="$SCALEWAY_API/$ep" _debug2 _scaleway_url "$_scaleway_url" export _H1="x-auth-token: $SCALEWAY_API_TOKEN" export _H2="Accept: application/json" export _H3="Content-Type: application/json" if [ "$data" ] || [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$_scaleway_url" "" "$m")" else response="$(_get "$_scaleway_url")" fi if [ "$?" != "0" ] || _contains "$response" "denied_authentication" || _contains "$response" "Method not allowed" || _contains "$response" "json parse error: unexpected EOF"; then _err "error $response" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_schlundtech.sh000066400000000000000000000131501472032365200200170ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_schlundtech_info='SchlundTech.de Site: SchlundTech.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_schlundtech Options: SCHLUNDTECH_USER Username SCHLUNDTECH_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/2246 Author: ' SCHLUNDTECH_API="https://gateway.schlundtech.de" # Arguments: # txtdomain # txt dns_schlundtech_add() { fulldomain="$1" txtvalue="$2" SCHLUNDTECH_USER="${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}" SCHLUNDTECH_PASSWORD="${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}" if [ -z "$SCHLUNDTECH_USER" ] || [ -z "$SCHLUNDTECH_PASSWORD" ]; then _err "You didn't specify schlundtech user and password." return 1 fi _saveaccountconf_mutable SCHLUNDTECH_USER "$SCHLUNDTECH_USER" _saveaccountconf_mutable SCHLUNDTECH_PASSWORD "$SCHLUNDTECH_PASSWORD" _debug "First detect the root zone" if ! _get_autodns_zone "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _zone "$_zone" _debug _system_ns "$_system_ns" _info "Adding TXT record" autodns_response="$(_autodns_zone_update "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")" if [ "$?" -eq "0" ]; then _info "Added, OK" return 0 fi return 1 } # Arguments: # txtdomain # txt dns_schlundtech_rm() { fulldomain="$1" txtvalue="$2" SCHLUNDTECH_USER="${SCHLUNDTECH_USER:-$(_readaccountconf_mutable SCHLUNDTECH_USER)}" SCHLUNDTECH_PASSWORD="${SCHLUNDTECH_PASSWORD:-$(_readaccountconf_mutable SCHLUNDTECH_PASSWORD)}" if [ -z "$SCHLUNDTECH_USER" ] || [ -z "$SCHLUNDTECH_PASSWORD" ]; then _err "You didn't specify schlundtech user and password." return 1 fi _debug "First detect the root zone" if ! _get_autodns_zone "$fulldomain"; then _err "zone not found" return 1 fi _debug _sub_domain "$_sub_domain" _debug _zone "$_zone" _debug _system_ns "$_system_ns" _info "Delete TXT record" autodns_response="$(_autodns_zone_cleanup "$_zone" "$_sub_domain" "$txtvalue" "$_system_ns")" if [ "$?" -eq "0" ]; then _info "Deleted, OK" return 0 fi return 1 } #################### Private functions below ################################## # Arguments: # fulldomain # Returns: # _sub_domain=_acme-challenge.www # _zone=domain.com # _system_ns _get_autodns_zone() { domain="$1" i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then # not valid return 1 fi autodns_response="$(_autodns_zone_inquire "$h")" if [ "$?" -ne "0" ]; then _err "invalid domain" return 1 fi if _contains "$autodns_response" "1" >/dev/null; then _zone="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" _system_ns="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _build_request_auth_xml() { printf " %s %s 10 " "$SCHLUNDTECH_USER" "$SCHLUNDTECH_PASSWORD" } # Arguments: # zone _build_zone_inquire_xml() { printf " %s 0205 1 1 name eq %s " "$(_build_request_auth_xml)" "$1" } # Arguments: # zone # subdomain # txtvalue # system_ns _build_zone_update_xml() { printf " %s 0202001 %s 600 TXT %s %s %s " "$(_build_request_auth_xml)" "$2" "$3" "$1" "$4" } # Arguments: # zone _autodns_zone_inquire() { request_data="$(_build_zone_inquire_xml "$1")" autodns_response="$(_autodns_api_call "$request_data")" ret="$?" printf "%s" "$autodns_response" return "$ret" } # Arguments: # zone # subdomain # txtvalue # system_ns _autodns_zone_update() { request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")" autodns_response="$(_autodns_api_call "$request_data")" ret="$?" printf "%s" "$autodns_response" return "$ret" } # Arguments: # zone # subdomain # txtvalue # system_ns _autodns_zone_cleanup() { request_data="$(_build_zone_update_xml "$1" "$2" "$3" "$4")" # replace 'rr_add>' with 'rr_rem>' in request_data request_data="$(printf -- "%s" "$request_data" | sed 's/rr_add>/rr_rem>/g')" autodns_response="$(_autodns_api_call "$request_data")" ret="$?" printf "%s" "$autodns_response" return "$ret" } # Arguments: # request_data _autodns_api_call() { request_data="$1" _debug request_data "$request_data" autodns_response="$(_post "$request_data" "$SCHLUNDTECH_API")" ret="$?" _debug autodns_response "$autodns_response" if [ "$ret" -ne "0" ]; then _err "error" return 1 fi if _contains "$autodns_response" "success" >/dev/null; then _info "success" printf "%s" "$autodns_response" return 0 fi return 1 } acme.sh-3.1.0/dnsapi/dns_selectel.sh000066400000000000000000000074411472032365200173210ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_selectel_info='Selectel.com Domains: Selectel.ru Site: Selectel.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel Options: SL_Key API Key ' SL_Api="https://api.selectel.ru/domains/v1" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_selectel_add() { fulldomain=$1 txtvalue=$2 SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" if [ -z "$SL_Key" ]; then SL_Key="" _err "You don't specify selectel.ru api key yet." _err "Please create you key and try again." return 1 fi #save the api key to the account conf file. _saveaccountconf_mutable SL_Key "$SL_Key" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if _sl_rest POST "/$_domain_id/records/" "{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"$fulldomain\", \"content\": \"$txtvalue\"}"; then if _contains "$response" "$txtvalue" || _contains "$response" "record_already_exists"; then _info "Added, OK" return 0 fi fi _err "Add txt record error." return 1 } #fulldomain txtvalue dns_selectel_rm() { fulldomain=$1 txtvalue=$2 SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" if [ -z "$SL_Key" ]; then SL_Key="" _err "You don't specify slectel api key yet." _err "Please create you key and try again." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _sl_rest GET "/${_domain_id}/records/" if ! _contains "$response" "$txtvalue"; then _err "Txt record not found" return 1 fi _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")" _debug2 "_record_seg" "$_record_seg" if [ -z "$_record_seg" ]; then _err "can not find _record_seg" return 1 fi _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2)" _debug2 "_record_id" "$_record_id" if [ -z "$_record_id" ]; then _err "can not find _record_id" return 1 fi if ! _sl_rest DELETE "/$_domain_id/records/$_record_id"; then _err "Delete record error." return 1 fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 if ! _sl_rest GET "/"; then return 1 fi i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if _contains "$response" "\"name\" *: *\"$h\","; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h _debug "Getting domain id for $h" if ! _sl_rest GET "/$h"; then return 1 fi _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)" return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _sl_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="X-Token: $SL_Key" export _H2="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$SL_Api/$ep" "" "$m")" else response="$(_get "$SL_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_selfhost.sh000066400000000000000000000101041472032365200173360ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_selfhost_info='SelfHost.de Site: SelfHost.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_selfhost Options: SELFHOSTDNS_USERNAME Username SELFHOSTDNS_PASSWORD Password SELFHOSTDNS_MAP Subdomain name Issues: github.com/acmesh-official/acme.sh/issues/4291 Author: Marvin Edeler ' dns_selfhost_add() { fulldomain=$1 txt=$2 _info "Calling acme-dns on selfhost" _debug fulldomain "$fulldomain" _debug txtvalue "$txt" SELFHOSTDNS_UPDATE_URL="https://selfhost.de/cgi-bin/api.pl" # Get values, but don't save until we successfully validated SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}" SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}" # These values are domain dependent, so read them from there SELFHOSTDNS_MAP="${SELFHOSTDNS_MAP:-$(_readdomainconf SELFHOSTDNS_MAP)}" # Selfhost api can't dynamically add TXT record, # so we have to store the last used RID of the domain to support a second RID for wildcard domains # (format: 'fulldomainA:lastRid fulldomainB:lastRid ...') SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(_readdomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL) if [ -z "${SELFHOSTDNS_USERNAME:-}" ] || [ -z "${SELFHOSTDNS_PASSWORD:-}" ]; then _err "SELFHOSTDNS_USERNAME and SELFHOSTDNS_PASSWORD must be set" return 1 fi # get the domain entry from SELFHOSTDNS_MAP # only match full domains (at the beginning of the string or with a leading whitespace), # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com # if the domain is defined multiple times only the last occurance will be matched mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p") _debug2 mapEntry "$mapEntry" if test -z "$mapEntry"; then _err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID" return 1 fi # get the RIDs from the map entry rid1=$(echo "$mapEntry" | cut -d: -f2) rid2=$(echo "$mapEntry" | cut -d: -f3) # read last used rid domain lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p") _debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry" lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2) rid="$rid1" if [ "$lastUsedRidForDomain" = "$rid" ] && ! test -z "$rid2"; then rid="$rid2" fi _info "Trying to add $txt on selfhost for rid: $rid" data="?username=$SELFHOSTDNS_USERNAME&password=$SELFHOSTDNS_PASSWORD&rid=$rid&content=$txt" response="$(_get "$SELFHOSTDNS_UPDATE_URL$data")" if ! echo "$response" | grep "200 OK" >/dev/null; then _err "Invalid response of acme-dns for selfhost" return 1 fi # write last used rid domain newLastUsedRidForDomainEntry="$fulldomain:$rid" if ! test -z "$lastUsedRidForDomainEntry"; then # replace last used rid entry for domain SELFHOSTDNS_MAP_LAST_USED_INTERNAL=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/$lastUsedRidForDomainEntry/$newLastUsedRidForDomainEntry/p") else # add last used rid entry for domain if test -z "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL"; then SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$newLastUsedRidForDomainEntry" else SELFHOSTDNS_MAP_LAST_USED_INTERNAL="$SELFHOSTDNS_MAP_LAST_USED_INTERNAL $newLastUsedRidForDomainEntry" fi fi # Now that we know the values are good, save them _saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME" _saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD" # These values are domain dependent, so store them there _savedomainconf SELFHOSTDNS_MAP "$SELFHOSTDNS_MAP" _savedomainconf SELFHOSTDNS_MAP_LAST_USED_INTERNAL "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" } dns_selfhost_rm() { fulldomain=$1 txt=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txt" _info "Creating and removing of records is not supported by selfhost API, will not delete anything." } acme.sh-3.1.0/dnsapi/dns_servercow.sh000077500000000000000000000124341472032365200175410ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_servercow_info='ServerCow.de Site: ServerCow.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_servercow Options: SERVERCOW_API_Username Username SERVERCOW_API_Password Password Issues: github.com/jhartlep/servercow-dns-api/issues Author: Jens Hartlep ' SERVERCOW_API="https://api.servercow.de/dns/v1/domains" # Usage dns_servercow_add _acme-challenge.www.domain.com "abcdefghijklmnopqrstuvwxyz" dns_servercow_add() { fulldomain=$1 txtvalue=$2 _info "Using servercow" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" SERVERCOW_API_Username="${SERVERCOW_API_Username:-$(_readaccountconf_mutable SERVERCOW_API_Username)}" SERVERCOW_API_Password="${SERVERCOW_API_Password:-$(_readaccountconf_mutable SERVERCOW_API_Password)}" if [ -z "$SERVERCOW_API_Username" ] || [ -z "$SERVERCOW_API_Password" ]; then SERVERCOW_API_Username="" SERVERCOW_API_Password="" _err "You don't specify servercow api username and password yet." _err "Please create your username and password and try again." return 1 fi # save the credentials to the account conf file _saveaccountconf_mutable SERVERCOW_API_Username "$SERVERCOW_API_Username" _saveaccountconf_mutable SERVERCOW_API_Password "$SERVERCOW_API_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" # check whether a txt record already exists for the subdomain if printf -- "%s" "$response" | grep "{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\"" >/dev/null; then _info "A txt record with the same name already exists." # trim the string on the left txtvalue_old=${response#*{\"name\":\""$_sub_domain"\",\"ttl\":20,\"type\":\"TXT\",\"content\":\"} # trim the string on the right txtvalue_old=${txtvalue_old%%\"*} _debug txtvalue_old "$txtvalue_old" _info "Add the new txtvalue to the existing txt record." if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":[\"$txtvalue\",\"$txtvalue_old\"],\"ttl\":20}"; then if printf -- "%s" "$response" | grep "ok" >/dev/null; then _info "Added additional txtvalue, OK" return 0 else _err "add txt record error." return 1 fi fi _err "add txt record error." return 1 else _info "There is no txt record with the name yet." if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then if printf -- "%s" "$response" | grep "ok" >/dev/null; then _info "Added, OK" return 0 else _err "add txt record error." return 1 fi fi _err "add txt record error." return 1 fi } # Usage fulldomain txtvalue # Remove the txt record after validation dns_servercow_rm() { fulldomain=$1 txtvalue=$2 _info "Using servercow" _debug fulldomain "$fulldomain" _debug txtvalue "$fulldomain" SERVERCOW_API_Username="${SERVERCOW_API_Username:-$(_readaccountconf_mutable SERVERCOW_API_Username)}" SERVERCOW_API_Password="${SERVERCOW_API_Password:-$(_readaccountconf_mutable SERVERCOW_API_Password)}" if [ -z "$SERVERCOW_API_Username" ] || [ -z "$SERVERCOW_API_Password" ]; then SERVERCOW_API_Username="" SERVERCOW_API_Password="" _err "You don't specify servercow api username and password yet." _err "Please create your username and password and try again." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" if _servercow_api DELETE "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\"}"; then if printf -- "%s" "$response" | grep "ok" >/dev/null; then _info "Deleted, OK" _contains "$response" '"message":"ok"' else _err "delete txt record error." return 1 fi fi } #################### Private functions below ################################## # _acme-challenge.www.domain.com # returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { fulldomain=$1 i=2 p=1 while true; do _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) _debug _domain "$_domain" if [ -z "$_domain" ]; then # not valid return 1 fi if ! _servercow_api GET "$_domain"; then return 1 fi if ! _contains "$response" '"error":"no such domain in user context"' >/dev/null; then _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$p") if [ -z "$_sub_domain" ]; then # not valid return 1 fi return 0 fi p=$i i=$(_math "$i" + 1) done return 1 } _servercow_api() { method=$1 domain=$2 data="$3" export _H1="Content-Type: application/json" export _H2="X-Auth-Username: $SERVERCOW_API_Username" export _H3="X-Auth-Password: $SERVERCOW_API_Password" if [ "$method" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$SERVERCOW_API/$domain" "" "$method")" else response="$(_get "$SERVERCOW_API/$domain")" fi if [ "$?" != "0" ]; then _err "error $domain" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_simply.sh000066400000000000000000000140571472032365200170370ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_simply_info='Simply.com Site: Simply.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_simply Options: SIMPLY_AccountName Account name SIMPLY_ApiKey API Key ' #SIMPLY_Api="https://api.simply.com/2/" SIMPLY_Api_Default="https://api.simply.com/2" #This is used for determining success of REST call SIMPLY_SUCCESS_CODE='"status":200' ######## 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 "Find the DNS 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" | sed -n "s/.*\"data\":\"\([^\"]*\)\".*/\1/p") record_type=$(echo "$record" | sed -n "s/.*\"type\":\"\([^\"]*\)\".*/\1/p") _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" "$SIMPLY_SUCCESS_CODE"; 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 if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then _err "Call to API not sucessfull, see below message for more details" _err "$response" 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 if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then _err "Call to API not sucessfull, see below message for more details" _err "$response" return 1 fi return 0 } _simply_rest() { m=$1 ep="$2" data="$3" _debug2 data "$data" _debug2 ep "$ep" _debug2 m "$m" basicauth=$(printf "%s:%s" "$SIMPLY_AccountName" "$SIMPLY_ApiKey" | _base64) if [ "$basicauth" ]; then export _H1="Authorization: Basic $basicauth" fi export _H2="Content-Type: application/json" if [ "$m" != "GET" ]; then response="$(_post "$data" "$SIMPLY_Api/$ep" "" "$m")" else response="$(_get "$SIMPLY_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi response="$(echo "$response" | _normalizeJson)" _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 } acme.sh-3.1.0/dnsapi/dns_tele3.sh000066400000000000000000000031641472032365200165330ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_tele3_info='tele3.cz Site: tele3.cz Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#tele3 Options: TELE3_Key API Key TELE3_Secret API Secret Author: Roman Blizik ' TELE3_API="https://www.tele3.cz/acme/" ######## Public functions ##################### dns_tele3_add() { _info "Using TELE3 DNS" data="\"ope\":\"add\", \"domain\":\"$1\", \"value\":\"$2\"" if ! _tele3_call; then _err "Publish zone failed" return 1 fi _info "Zone published" } dns_tele3_rm() { _info "Using TELE3 DNS" data="\"ope\":\"rm\", \"domain\":\"$1\", \"value\":\"$2\"" if ! _tele3_call; then _err "delete TXT record failed" return 1 fi _info "TXT record successfully deleted" } #################### Private functions below ################################## _tele3_init() { TELE3_Key="${TELE3_Key:-$(_readaccountconf_mutable TELE3_Key)}" TELE3_Secret="${TELE3_Secret:-$(_readaccountconf_mutable TELE3_Secret)}" if [ -z "$TELE3_Key" ] || [ -z "$TELE3_Secret" ]; then TELE3_Key="" TELE3_Secret="" _err "You must export variables: TELE3_Key and TELE3_Secret" return 1 fi #save the config variables to the account conf file. _saveaccountconf_mutable TELE3_Key "$TELE3_Key" _saveaccountconf_mutable TELE3_Secret "$TELE3_Secret" } _tele3_call() { _tele3_init data="{\"key\":\"$TELE3_Key\", \"secret\":\"$TELE3_Secret\", $data}" _debug data "$data" response="$(_post "$data" "$TELE3_API" "" "POST")" _debug response "$response" if [ "$response" != "success" ]; then _err "$response" return 1 fi } acme.sh-3.1.0/dnsapi/dns_tencent.sh000066400000000000000000000142071472032365200171570ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_tencent_info='Tencent.com Site: cloud.Tencent.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_tencent Options: Tencent_SecretId Secret ID Tencent_SecretKey Secret Key Issues: github.com/acmesh-official/acme.sh/issues/4781 ' Tencent_API="https://dnspod.tencentcloudapi.com" #Usage: dns_tencent_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_tencent_add() { fulldomain=$1 txtvalue=$2 Tencent_SecretId="${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}" Tencent_SecretKey="${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}" if [ -z "$Tencent_SecretId" ] || [ -z "$Tencent_SecretKey" ]; then Tencent_SecretId="" Tencent_SecretKey="" _err "You don't specify tencent api SecretId and SecretKey yet." return 1 fi #save the api SecretId and SecretKey to the account conf file. _saveaccountconf_mutable Tencent_SecretId "$Tencent_SecretId" _saveaccountconf_mutable Tencent_SecretKey "$Tencent_SecretKey" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then return 1 fi _debug "Add record" _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _tencent_rest "CreateRecord" } dns_tencent_rm() { fulldomain=$1 txtvalue=$2 Tencent_SecretId="${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}" Tencent_SecretKey="${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then return 1 fi _debug "Get record list" attempt=1 max_attempts=5 while [ -z "$record_id" ] && [ "$attempt" -le $max_attempts ]; do _check_exist_query "$_domain" "$_sub_domain" "$txtvalue" && _tencent_rest "DescribeRecordFilterList" record_id="$(echo "$response" | _egrep_o "\"RecordId\":\s*[0-9]+" | _egrep_o "[0-9]+")" _debug2 record_id "$record_id" if [ -z "$record_id" ]; then _debug "Due to TencentCloud API synchronization delay, record not found, waiting 10 seconds and retrying" _sleep 10 attempt=$(_math "$attempt + 1") fi done record_id="$(echo "$response" | _egrep_o "\"RecordId\":\s*[0-9]+" | _egrep_o "[0-9]+")" _debug2 record_id "$record_id" if [ -z "$record_id" ]; then _debug "record not found after $max_attempts attempts, skip" else _debug "Delete record" _delete_record_query "$record_id" && _tencent_rest "DeleteRecord" fi } #################### Private functions below ################################## _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi _describe_records_query "$h" "@" if ! _tencent_rest "DescribeRecordList" "ignore"; then return 1 fi if _contains "$response" "\"TotalCount\":"; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _debug _sub_domain "$_sub_domain" _domain="$h" _debug _domain "$_domain" return 0 fi p="$i" i=$(_math "$i" + 1) done return 1 } _tencent_rest() { action=$1 service="dnspod" payload="${query}" timestamp=$(date -u +%s) token=$(tencent_signature_v3 $service "$action" "$payload" "$timestamp") version="2021-03-23" if ! response="$(tencent_api_request $service $version "$action" "$payload" "$timestamp")"; then _err "Error <$1>" return 1 fi _debug2 response "$response" if [ -z "$2" ]; then message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" if [ "$message" ]; then _err "$message" return 1 fi fi } _add_record_query() { query="{\"Domain\":\"$1\",\"SubDomain\":\"$2\",\"RecordType\":\"TXT\",\"RecordLineId\":\"0\",\"RecordLine\":\"0\",\"Value\":\"$3\",\"TTL\":600}" } _describe_records_query() { query="{\"Domain\":\"$1\",\"Limit\":3000}" } _delete_record_query() { query="{\"Domain\":\"$_domain\",\"RecordId\":$1}" } _check_exist_query() { _domain="$1" _subdomain="$2" _value="$3" query="{\"Domain\":\"$_domain\",\"SubDomain\":\"$_subdomain\",\"RecordValue\":\"$_value\"}" } # shell client for tencent cloud api v3 | @author: rehiy tencent_sha256() { printf %b "$@" | _digest sha256 hex } tencent_hmac_sha256() { k=$1 shift hex_key=$(printf %b "$k" | _hex_dump | tr -d ' ') printf %b "$@" | _hmac sha256 "$hex_key" hex } tencent_hmac_sha256_hexkey() { k=$1 shift printf %b "$@" | _hmac sha256 "$k" hex } tencent_signature_v3() { service=$1 action=$(echo "$2" | _lower_case) payload=${3:-'{}'} timestamp=${4:-$(date +%s)} domain="$service.tencentcloudapi.com" secretId=${Tencent_SecretId:-'tencent-cloud-secret-id'} secretKey=${Tencent_SecretKey:-'tencent-cloud-secret-key'} algorithm='TC3-HMAC-SHA256' date=$(date -u -d "@$timestamp" +%Y-%m-%d 2>/dev/null) [ -z "$date" ] && date=$(date -u -r "$timestamp" +%Y-%m-%d) canonicalUri='/' canonicalQuery='' canonicalHeaders="content-type:application/json\nhost:$domain\nx-tc-action:$action\n" signedHeaders='content-type;host;x-tc-action' canonicalRequest="POST\n$canonicalUri\n$canonicalQuery\n$canonicalHeaders\n$signedHeaders\n$(tencent_sha256 "$payload")" credentialScope="$date/$service/tc3_request" stringToSign="$algorithm\n$timestamp\n$credentialScope\n$(tencent_sha256 "$canonicalRequest")" secretDate=$(tencent_hmac_sha256 "TC3$secretKey" "$date") secretService=$(tencent_hmac_sha256_hexkey "$secretDate" "$service") secretSigning=$(tencent_hmac_sha256_hexkey "$secretService" 'tc3_request') signature=$(tencent_hmac_sha256_hexkey "$secretSigning" "$stringToSign") echo "$algorithm Credential=$secretId/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature" } tencent_api_request() { service=$1 version=$2 action=$3 payload=${4:-'{}'} timestamp=${5:-$(date +%s)} token=$(tencent_signature_v3 "$service" "$action" "$payload" "$timestamp") _H1="Content-Type: application/json" _H2="Authorization: $token" _H3="X-TC-Version: $version" _H4="X-TC-Timestamp: $timestamp" _H5="X-TC-Action: $action" _post "$payload" "$Tencent_API" "" "POST" "application/json" } acme.sh-3.1.0/dnsapi/dns_timeweb.sh000066400000000000000000000256531472032365200171620ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_timeweb_info='Timeweb.Cloud Site: Timeweb.Cloud Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_timeweb Options: TW_Token API JWT token. Get it from the control panel at https://timeweb.cloud/my/api-keys Issues: github.com/acmesh-official/acme.sh/issues/5140 Author: Nikolay Pronchev ' TW_Api="https://api.timeweb.cloud/api/v1" ################ Public functions ################ # Adds an ACME DNS-01 challenge DNS TXT record via the Timeweb Cloud API. # # Param1: The ACME DNS-01 challenge FQDN. # Param2: The value of the ACME DNS-01 challenge TXT record. # # Example: dns_timeweb_add "_acme-challenge.sub.domain.com" "D-52Wm...4uYM" dns_timeweb_add() { _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_add\" started." _timeweb_set_acme_fqdn "$1" || return 1 _timeweb_set_acme_txt "$2" || return 1 _timeweb_check_token || return 1 _timeweb_split_acme_fqdn || return 1 _timeweb_dns_txt_add || return 1 _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_add\" finished." } # Removes a DNS TXT record via the Timeweb Cloud API. # # Param1: The ACME DNS-01 challenge FQDN. # Param2: The value of the ACME DNS-01 challenge TXT record. # # Example: dns_timeweb_rm "_acme-challenge.sub.domain.com" "D-52Wm...4uYM" dns_timeweb_rm() { _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_rm\" started." _timeweb_set_acme_fqdn "$1" || return 1 _timeweb_set_acme_txt "$2" || return 1 _timeweb_check_token || return 1 _timeweb_split_acme_fqdn || return 1 _timeweb_get_dns_txt || return 1 _timeweb_dns_txt_remove || return 1 _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_rm\" finished." } ################ Private functions ################ # Checks and sets the ACME DNS-01 challenge FQDN. # # Param1: The ACME DNS-01 challenge FQDN. # # Example: _timeweb_set_acme_fqdn "_acme-challenge.sub.domain.com" # # Sets the "Acme_Fqdn" variable (_acme-challenge.sub.domain.com) _timeweb_set_acme_fqdn() { Acme_Fqdn=$1 _debug "Setting ACME DNS-01 challenge FQDN \"$Acme_Fqdn\"." [ -z "$Acme_Fqdn" ] && { _err "ACME DNS-01 challenge FQDN is empty." return 1 } return 0 } # Checks and sets the value of the ACME DNS-01 challenge TXT record. # # Param1: Value of the ACME DNS-01 challenge TXT record. # # Example: _timeweb_set_acme_txt "D-52Wm...4uYM" # # Sets the "Acme_Txt" variable to the provided value (D-52Wm...4uYM) _timeweb_set_acme_txt() { Acme_Txt=$1 _debug "Setting the value of the ACME DNS-01 challenge TXT record to \"$Acme_Txt\"." [ -z "$Acme_Txt" ] && { _err "ACME DNS-01 challenge TXT record value is empty." return 1 } return 0 } # Checks if the Timeweb Cloud API JWT token is present (refer to the script description). # Adds or updates the token in the acme.sh account configuration. _timeweb_check_token() { _debug "Checking for the presence of the Timeweb Cloud API JWT token." TW_Token="${TW_Token:-$(_readaccountconf_mutable TW_Token)}" [ -z "$TW_Token" ] && { _err "Timeweb Cloud API JWT token was not found." return 1 } _saveaccountconf_mutable TW_Token "$TW_Token" } # Divides the ACME DNS-01 challenge FQDN into its main domain and subdomain components. _timeweb_split_acme_fqdn() { _debug "Trying to divide \"$Acme_Fqdn\" into its main domain and subdomain components." TW_Page_Limit=100 TW_Page_Offset=0 TW_Domains_Returned="" while [ -z "$TW_Domains_Returned" ] || [ "$TW_Domains_Returned" -ge "$TW_Page_Limit" ]; do _timeweb_list_domains "$TW_Page_Limit" "$TW_Page_Offset" || return 1 # Remove the 'subdomains' subarray to prevent confusion with FQDNs. TW_Domains=$( echo "$TW_Domains" | sed 's/"subdomains":\[[^]]*]//g' ) [ -z "$TW_Domains" ] && { _err "Failed to parse the list of domains." return 1 } while TW_Domain=$( echo "$TW_Domains" | sed -n 's/.*{[^{]*"fqdn":"\([^"]*\)"[^}]*}.*/\1/p' ) [ -n "$TW_Domain" ] && { _timeweb_is_main_domain "$TW_Domain" && return 0 TW_Domains=$( echo "$TW_Domains" | sed 's/{\([^{]*"fqdn":"'"$TW_Domain"'"[^}]*\)}//' ) continue } do :; done TW_Page_Offset=$(_math "$TW_Page_Offset" + "$TW_Page_Limit") done _err "Failed to divide \"$Acme_Fqdn\" into its main domain and subdomain components." return 1 } # Searches for a previously added DNS TXT record. # # Sets the "TW_Dns_Txt_Id" variable. _timeweb_get_dns_txt() { _debug "Trying to locate a DNS TXT record with the value \"$Acme_Txt\"." TW_Page_Limit=100 TW_Page_Offset=0 TW_Dns_Records_Returned="" while [ -z "$TW_Dns_Records_Returned" ] || [ "$TW_Dns_Records_Returned" -ge "$TW_Page_Limit" ]; do _timeweb_list_dns_records "$TW_Page_Limit" "$TW_Page_Offset" || return 1 while Dns_Record=$( echo "$TW_Dns_Records" | sed -n 's/.*{\([^{]*{[^{]*'"$Acme_Txt"'[^}]*}[^}]*\)}.*/\1/p' ) [ -n "$Dns_Record" ] && { _timeweb_is_added_txt "$Dns_Record" && return 0 TW_Dns_Records=$( echo "$TW_Dns_Records" | sed 's/{\([^{]*{[^{]*'"$Acme_Txt"'[^}]*}[^}]*\)}//' ) continue } do :; done TW_Page_Offset=$(_math "$TW_Page_Offset" + "$TW_Page_Limit") done _err "DNS TXT record was not found." return 1 } # Lists domains via the Timeweb Cloud API. # # Param 1: Limit for listed domains. # Param 2: Offset for domains list. # # Sets the "TW_Domains" variable. # Sets the "TW_Domains_Returned" variable. _timeweb_list_domains() { _debug "Listing domains via Timeweb Cloud API. Limit: $1, offset: $2." export _H1="Authorization: Bearer $TW_Token" if ! TW_Domains=$(_get "$TW_Api/domains?limit=$1&offset=$2"); then _err "The request to the Timeweb Cloud API failed." return 1 fi [ -z "$TW_Domains" ] && { _err "Empty response from the Timeweb Cloud API." return 1 } TW_Domains_Returned=$( echo "$TW_Domains" | sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/' ) [ -z "$TW_Domains_Returned" ] && { _err "Failed to extract the total count of domains." return 1 } [ "$TW_Domains_Returned" -eq "0" ] && { _err "Domains are missing." return 1 } _debug "Domains returned by Timeweb Cloud API: $TW_Domains_Returned." } # Lists domain DNS records via the Timeweb Cloud API. # # Param 1: Limit for listed DNS records. # Param 2: Offset for DNS records list. # # Sets the "TW_Dns_Records" variable. # Sets the "TW_Dns_Records_Returned" variable. _timeweb_list_dns_records() { _debug "Listing domain DNS records via the Timeweb Cloud API. Limit: $1, offset: $2." export _H1="Authorization: Bearer $TW_Token" if ! TW_Dns_Records=$(_get "$TW_Api/domains/$TW_Main_Domain/dns-records?limit=$1&offset=$2"); then _err "The request to the Timeweb Cloud API failed." return 1 fi [ -z "$TW_Dns_Records" ] && { _err "Empty response from the Timeweb Cloud API." return 1 } TW_Dns_Records_Returned=$( echo "$TW_Dns_Records" | sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/' ) [ -z "$TW_Dns_Records_Returned" ] && { _err "Failed to extract the total count of DNS records." return 1 } [ "$TW_Dns_Records_Returned" -eq "0" ] && { _err "DNS records are missing." return 1 } _debug "DNS records returned by Timeweb Cloud API: $TW_Dns_Records_Returned." } # Verifies whether the domain is the primary domain for the ACME DNS-01 challenge FQDN. # The requirement is that the provided domain is the top-level domain # for the ACME DNS-01 challenge FQDN. # # Param 1: Domain object returned by Timeweb Cloud API. # # Sets the "TW_Main_Domain" variable (e.g. "_acme-challenge.s1.domain.co.uk" → "domain.co.uk"). # Sets the "TW_Subdomains" variable (e.g. "_acme-challenge.s1.domain.co.uk" → "_acme-challenge.s1"). _timeweb_is_main_domain() { _debug "Checking if \"$1\" is the main domain of the ACME DNS-01 challenge FQDN." [ -z "$1" ] && { _debug "Failed to extract FQDN. Skipping domain." return 1 } ! echo ".$Acme_Fqdn" | grep -qi "\.$1$" && { _debug "Domain does not match the ACME DNS-01 challenge FQDN. Skipping domain." return 1 } TW_Main_Domain=$1 TW_Subdomains=$( echo "$Acme_Fqdn" | sed "s/\.*.\{${#1}\}$//" ) _debug "Matched domain. ACME DNS-01 challenge FQDN split as [$TW_Subdomains].[$TW_Main_Domain]." return 0 } # Verifies whether a DNS record was previously added based on the following criteria: # - The value matches the ACME DNS-01 challenge TXT record value; # - The record type is TXT; # - The subdomain matches the ACME DNS-01 challenge FQDN. # # Param 1: DNS record object returned by Timeweb Cloud API. # # Sets the "TW_Dns_Txt_Id" variable. _timeweb_is_added_txt() { _debug "Checking if \"$1\" is a previously added DNS TXT record." echo "$1" | grep -qv '"type":"TXT"' && { _debug "Not a TXT record. Skipping the record." return 1 } if [ -n "$TW_Subdomains" ]; then echo "$1" | grep -qvi "\"subdomain\":\"$TW_Subdomains\"" && { _debug "Subdomains do not match. Skipping the record." return 1 } else echo "$1" | grep -q '"subdomain\":"..*"' && { _debug "Subdomains do not match. Skipping the record." return 1 } fi TW_Dns_Txt_Id=$( echo "$1" | sed 's/.*"id":\([0-9]*\)[^0-9].*/\1/' ) [ -z "$TW_Dns_Txt_Id" ] && { _debug "Failed to extract the DNS record ID. Skipping the record." return 1 } _debug "Matching DNS TXT record ID is \"$TW_Dns_Txt_Id\"." return 0 } # Adds a DNS TXT record via the Timeweb Cloud API. _timeweb_dns_txt_add() { _debug "Adding a new DNS TXT record via the Timeweb Cloud API." export _H1="Authorization: Bearer $TW_Token" export _H2="Content-Type: application/json" if ! TW_Response=$( _post "{ \"subdomain\":\"$TW_Subdomains\", \"type\":\"TXT\", \"value\":\"$Acme_Txt\" }" \ "$TW_Api/domains/$TW_Main_Domain/dns-records" ); then _err "The request to the Timeweb Cloud API failed." return 1 fi [ -z "$TW_Response" ] && { _err "An unexpected empty response was received from the Timeweb Cloud API." return 1 } TW_Dns_Txt_Id=$( echo "$TW_Response" | sed 's/.*"id":\([0-9]*\)[^0-9].*/\1/' ) [ -z "$TW_Dns_Txt_Id" ] && { _err "Failed to extract the DNS TXT Record ID." return 1 } _debug "DNS TXT record has been added. ID: \"$TW_Dns_Txt_Id\"." } # Removes a DNS record via the Timeweb Cloud API. _timeweb_dns_txt_remove() { _debug "Removing DNS record via the Timeweb Cloud API." export _H1="Authorization: Bearer $TW_Token" if ! TW_Response=$( _post \ "" \ "$TW_Api/domains/$TW_Main_Domain/dns-records/$TW_Dns_Txt_Id" \ "" \ "DELETE" ); then _err "The request to the Timeweb Cloud API failed." return 1 fi [ -n "$TW_Response" ] && { _err "Received an unexpected response body from the Timeweb Cloud API." return 1 } _debug "DNS TXT record with ID \"$TW_Dns_Txt_Id\" has been removed." } acme.sh-3.1.0/dnsapi/dns_transip.sh000066400000000000000000000130421472032365200171730ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_transip_info='TransIP.nl Site: TransIP.nl Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_transip Options: TRANSIP_Username Username TRANSIP_Key_File Private key file path Issues: github.com/acmesh-official/acme.sh/issues/2949 ' TRANSIP_Api_Url="https://api.transip.nl/v6" TRANSIP_Token_Read_Only="false" TRANSIP_Token_Expiration="30 minutes" # You can't reuse a label token, so we leave this empty normally TRANSIP_Token_Label="" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_transip_add() { fulldomain="$1" _debug fulldomain="$fulldomain" txtvalue="$2" _debug txtvalue="$txtvalue" _transip_setup "$fulldomain" || return 1 _info "Creating TXT record." if ! _transip_rest POST "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then _err "Could not add TXT record." return 1 fi return 0 } dns_transip_rm() { fulldomain=$1 _debug fulldomain="$fulldomain" txtvalue=$2 _debug txtvalue="$txtvalue" _transip_setup "$fulldomain" || return 1 _info "Removing TXT record." if ! _transip_rest DELETE "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then _err "Could not remove TXT record $_sub_domain for $domain" return 1 fi return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain="$1" i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" if _transip_rest GET "domains/$h/dns" && _contains "$response" "dnsEntries"; then return 0 fi p=$i i=$(_math "$i" + 1) done _err "Unable to parse this domain" return 1 } _transip_rest() { m="$1" ep="$2" data="$3" _debug ep "$ep" export _H1="Accept: application/json" export _H2="Authorization: Bearer $_token" export _H4="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$TRANSIP_Api_Url/$ep" "" "$m")" retcode=$? else response="$(_get "$TRANSIP_Api_Url/$ep")" retcode=$? fi if [ "$retcode" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } _transip_get_token() { nonce=$(echo "TRANSIP$(_time)" | _digest sha1 hex | cut -c 1-32) _debug nonce "$nonce" # make IP whitelisting configurable TRANSIP_Token_Global_Key="${TRANSIP_Token_Global_Key:-$(_readaccountconf_mutable TRANSIP_Token_Global_Key)}" _saveaccountconf_mutable TRANSIP_Token_Global_Key "$TRANSIP_Token_Global_Key" data="{\"login\":\"${TRANSIP_Username}\",\"nonce\":\"${nonce}\",\"read_only\":\"${TRANSIP_Token_Read_Only}\",\"expiration_time\":\"${TRANSIP_Token_Expiration}\",\"label\":\"${TRANSIP_Token_Label}\",\"global_key\":\"${TRANSIP_Token_Global_Key:-false}\"}" _debug data "$data" #_signature=$(printf "%s" "$data" | openssl dgst -sha512 -sign "$TRANSIP_Key_File" | _base64) _signature=$(printf "%s" "$data" | _sign "$TRANSIP_Key_File" "sha512") _debug2 _signature "$_signature" export _H1="Signature: $_signature" export _H2="Content-Type: application/json" response="$(_post "$data" "$TRANSIP_Api_Url/auth" "" "POST")" retcode=$? _debug2 response "$response" if [ "$retcode" != "0" ]; then _err "Authentication failed." return 1 fi if _contains "$response" "token"; then _token="$(echo "$response" | _normalizeJson | sed -n 's/^{"token":"\(.*\)"}/\1/p')" _debug _token "$_token" return 0 fi return 1 } _transip_setup() { fulldomain=$1 # retrieve the transip creds TRANSIP_Username="${TRANSIP_Username:-$(_readaccountconf_mutable TRANSIP_Username)}" TRANSIP_Key_File="${TRANSIP_Key_File:-$(_readaccountconf_mutable TRANSIP_Key_File)}" # check their vals for null if [ -z "$TRANSIP_Username" ] || [ -z "$TRANSIP_Key_File" ]; then TRANSIP_Username="" TRANSIP_Key_File="" _err "You didn't specify a TransIP username and api key file location" _err "Please set those values and try again." return 1 fi # save the username and api key to the account conf file. _saveaccountconf_mutable TRANSIP_Username "$TRANSIP_Username" _saveaccountconf_mutable TRANSIP_Key_File "$TRANSIP_Key_File" # download key file if it's an URL if _startswith "$TRANSIP_Key_File" "http"; then _debug "download transip key file" TRANSIP_Key_URL=$TRANSIP_Key_File TRANSIP_Key_File="$(_mktemp)" chmod 600 "$TRANSIP_Key_File" if ! _get "$TRANSIP_Key_URL" >"$TRANSIP_Key_File"; then _err "Error getting key file from : $TRANSIP_Key_URL" return 1 fi fi if [ -f "$TRANSIP_Key_File" ]; then if ! grep "BEGIN PRIVATE KEY" "$TRANSIP_Key_File" >/dev/null 2>&1; then _err "Key file doesn't seem to be a valid key: ${TRANSIP_Key_File}" return 1 fi else _err "Can't read private key file: ${TRANSIP_Key_File}" return 1 fi if [ -z "$_token" ]; then if ! _transip_get_token; then _err "Can not get token." return 1 fi fi if [ -n "${TRANSIP_Key_URL}" ]; then _debug "delete transip key file" rm "${TRANSIP_Key_File}" TRANSIP_Key_File=$TRANSIP_Key_URL fi _get_root "$fulldomain" || return 1 return 0 } acme.sh-3.1.0/dnsapi/dns_udr.sh000066400000000000000000000101611472032365200163040ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_udr_info='united-domains Reselling Site: ud-reselling.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_udr Options: UDR_USER Username UDR_PASS Password Issues: github.com/acmesh-official/acme.sh/issues/3923 Author: Andreas Scherer ' UDR_API="https://api.domainreselling.de/api/call.cgi" UDR_TTL="30" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt" dns_udr_add() { fulldomain=$1 txtvalue=$2 UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}" UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}" if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then UDR_USER="" UDR_PASS="" _err "You didn't specify an UD-Reselling username and password yet" return 1 fi # save the username and password to the account conf file. _saveaccountconf_mutable UDR_USER "$UDR_USER" _saveaccountconf_mutable UDR_PASS "$UDR_PASS" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _dnszone "${_dnszone}" _debug "Getting txt records" if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then return 1 fi rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}" _debug resource_record "${rr}" if _contains "$response" "$rr" >/dev/null; then _err "Error, it would appear that this record already exists. Please review existing TXT records for this domain." return 1 fi _info "Adding record" if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&addrr0=${rr}"; then _err "Adding the record did not succeed, please verify/check." return 1 fi _info "Added, OK" return 0 } dns_udr_rm() { fulldomain=$1 txtvalue=$2 UDR_USER="${UDR_USER:-$(_readaccountconf_mutable UDR_USER)}" UDR_PASS="${UDR_PASS:-$(_readaccountconf_mutable UDR_PASS)}" if [ -z "$UDR_USER" ] || [ -z "$UDR_PASS" ]; then UDR_USER="" UDR_PASS="" _err "You didn't specify an UD-Reselling username and password yet" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _dnszone "${_dnszone}" _debug "Getting txt records" if ! _udr_rest "QueryDNSZoneRRList" "dnszone=${_dnszone}"; then return 1 fi rr="${fulldomain}. ${UDR_TTL} IN TXT ${txtvalue}" _debug resource_record "${rr}" if _contains "$response" "$rr" >/dev/null; then if ! _udr_rest "UpdateDNSZone" "dnszone=${_dnszone}&delrr0=${rr}"; then _err "Deleting the record did not succeed, please verify/check." return 1 fi _info "Removed, OK" return 0 else _info "Text record is not present, will not delete anything." return 0 fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 if ! _udr_rest "QueryDNSZoneList" ""; then return 1 fi while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if _contains "${response}" "${h}." >/dev/null; then _dnszone=$(echo "$response" | _egrep_o "${h}") if [ "$_dnszone" ]; then return 0 fi return 1 fi i=$(_math "$i" + 1) done return 1 } _udr_rest() { if [ -n "$2" ]; then data="command=$1&$2" else data="command=$1" fi _debug data "${data}" response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")" _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') _debug response_code "$_code" _debug response_description "$_description" if [ ! "$_code" = "200" ]; then _err "DNS-API-Error: $_description" return 1 fi return 0 } acme.sh-3.1.0/dnsapi/dns_ultra.sh000066400000000000000000000114521472032365200166450ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_ultra_info='UltraDNS.com Site: UltraDNS.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ultra Options: ULTRA_USR Username ULTRA_PWD Password Issues: github.com/acmesh-official/acme.sh/issues/2118 ' ULTRA_API="https://api.ultradns.com/v3/" ULTRA_AUTH_API="https://api.ultradns.com/v2/" #Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt" dns_ultra_add() { fulldomain=$1 txtvalue=$2 export txtvalue ULTRA_USR="${ULTRA_USR:-$(_readaccountconf_mutable ULTRA_USR)}" ULTRA_PWD="${ULTRA_PWD:-$(_readaccountconf_mutable ULTRA_PWD)}" if [ -z "$ULTRA_USR" ] || [ -z "$ULTRA_PWD" ]; then ULTRA_USR="" ULTRA_PWD="" _err "You didn't specify an UltraDNS username and password yet" return 1 fi # save the username and password to the account conf file. _saveaccountconf_mutable ULTRA_USR "$ULTRA_USR" _saveaccountconf_mutable ULTRA_PWD "$ULTRA_PWD" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "${_domain_id}" _debug _sub_domain "${_sub_domain}" _debug _domain "${_domain}" _debug "Getting txt records" _ultra_rest GET "zones/${_domain_id}/rrsets/TXT?q=value:${fulldomain}" if printf "%s" "$response" | grep \"totalCount\" >/dev/null; then _err "Error, it would appear that this record already exists. Please review existing TXT records for this domain." return 1 fi _info "Adding record" if _ultra_rest POST "zones/$_domain_id/rrsets/TXT/${_sub_domain}" '{"ttl":300,"rdata":["'"${txtvalue}"'"]}'; then if _contains "$response" "Successful"; then _info "Added, OK" return 0 elif _contains "$response" "Resource Record of type 16 with these attributes already exists"; then _info "Already exists, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." } dns_ultra_rm() { fulldomain=$1 txtvalue=$2 export txtvalue ULTRA_USR="${ULTRA_USR:-$(_readaccountconf_mutable ULTRA_USR)}" ULTRA_PWD="${ULTRA_PWD:-$(_readaccountconf_mutable ULTRA_PWD)}" if [ -z "$ULTRA_USR" ] || [ -z "$ULTRA_PWD" ]; then ULTRA_USR="" ULTRA_PWD="" _err "You didn't specify an UltraDNS username and password yet" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "${_domain_id}" _debug _sub_domain "${_sub_domain}" _debug _domain "${domain}" _debug "Getting TXT records" _ultra_rest GET "zones/${_domain_id}/rrsets?q=kind:RECORDS+owner:${_sub_domain}" if ! printf "%s" "$response" | grep \"resultInfo\" >/dev/null; then _err "There was an error in obtaining the resource records for ${_domain_id}" return 1 fi count=$(echo "$response" | _egrep_o "\"returnedCount\":[^,]*" | cut -d: -f2 | cut -d'}' -f1) _debug count "${count}" if [ "${count}" = "" ]; then _info "Text record is not present, will not delete anything." else if ! _ultra_rest DELETE "zones/$_domain_id/rrsets/TXT/${_sub_domain}" '{"ttl":300,"rdata":["'"${txtvalue}"'"]}'; then _err "Deleting the record did not succeed, please verify/check." return 1 fi _contains "$response" "" fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" _debug response "$response" if [ -z "$h" ]; then #not valid return 1 fi if ! _ultra_rest GET "zones"; then return 1 fi if _contains "${response}" "${h}." >/dev/null; then _domain_id=$(echo "$response" | _egrep_o "${h}" | head -1) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="${h}" _debug sub_domain "${_sub_domain}" _debug domain "${_domain}" return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _ultra_rest() { m=$1 ep="$2" data="$3" _debug "$ep" if [ -z "$AUTH_TOKEN" ]; then _ultra_login fi _debug TOKEN "$AUTH_TOKEN" export _H1="Content-Type: application/json" export _H2="Authorization: Bearer $AUTH_TOKEN" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$ULTRA_API$ep" "" "$m")" else response="$(_get "$ULTRA_API$ep")" fi } _ultra_login() { export _H1="" export _H2="" AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_AUTH_API}authorization/token" | cut -d, -f3 | cut -d\" -f4) export AUTH_TOKEN } acme.sh-3.1.0/dnsapi/dns_unoeuro.sh000066400000000000000000000110211472032365200172020ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_unoeuro_info='unoeuro.com Deprecated. The unoeuro.com is now simply.com Site: unoeuro.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_unoeuro Options: UNO_Key API Key UNO_User Username ' Uno_Api="https://api.simply.com/1" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_unoeuro_add() { fulldomain=$1 txtvalue=$2 UNO_Key="${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}" UNO_User="${UNO_User:-$(_readaccountconf_mutable UNO_User)}" if [ -z "$UNO_Key" ] || [ -z "$UNO_User" ]; then UNO_Key="" UNO_User="" _err "You haven't specified a UnoEuro api key and account yet." _err "Please create your key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable UNO_Key "$UNO_Key" _saveaccountconf_mutable UNO_User "$UNO_User" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _uno_rest GET "my/products/$h/dns/records" if ! _contains "$response" "\"status\": 200" >/dev/null; then _err "Error" return 1 fi _info "Adding record" if _uno_rest POST "my/products/$h/dns/records" "{\"name\":\"$fulldomain\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":120,\"priority\":0}"; then if _contains "$response" "\"status\": 200" >/dev/null; then _info "Added, OK" return 0 else _err "Add txt record error." return 1 fi fi } #fulldomain txtvalue dns_unoeuro_rm() { fulldomain=$1 txtvalue=$2 UNO_Key="${UNO_Key:-$(_readaccountconf_mutable UNO_Key)}" UNO_User="${UNO_User:-$(_readaccountconf_mutable UNO_User)}" if [ -z "$UNO_Key" ] || [ -z "$UNO_User" ]; then UNO_Key="" UNO_User="" _err "You haven't specified a UnoEuro api key and account yet." _err "Please create your key and try again." return 1 fi if ! _contains "$UNO_User" "UE"; then _err "It seems that the UNO_User=$UNO_User is not a valid username." _err "Please check and retry." return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _uno_rest GET "my/products/$h/dns/records" if ! _contains "$response" "\"status\": 200"; then _err "Error" return 1 fi if ! _contains "$response" "$_sub_domain"; then _info "Don't need to remove." else for record_line_number in $(echo "$response" | grep -n "$_sub_domain" | cut -d : -f 1); do record_line_number=$(_math "$record_line_number" - 1) _debug "record_line_number" "$record_line_number" record_id=$(echo "$response" | _head_n "$record_line_number" | _tail_n 1 1 | _egrep_o "[0-9]{1,}") _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _uno_rest DELETE "my/products/$h/dns/records/$record_id"; then _err "Delete record error." return 1 fi _contains "$response" "\"status\": 200" done fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _uno_rest GET "my/products/$h/dns/records"; then return 1 fi if _contains "$response" "\"status\": 200"; then _domain_id=$h if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _uno_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Content-Type: application/json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$Uno_Api/$UNO_User/$UNO_Key/$ep" "" "$m")" else response="$(_get "$Uno_Api/$UNO_User/$UNO_Key/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_variomedia.sh000066400000000000000000000074421472032365200176420ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_variomedia_info='variomedia.de Site: variomedia.de Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_variomedia Options: VARIOMEDIA_API_TOKEN API Token Issues: github.com/acmesh-official/acme.sh/issues/2564 ' VARIOMEDIA_API="https://api.variomedia.de" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_variomedia_add() { fulldomain=$1 txtvalue=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}" if test -z "$VARIOMEDIA_API_TOKEN"; then VARIOMEDIA_API_TOKEN="" _err 'VARIOMEDIA_API_TOKEN was not exported' return 1 fi _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN" _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" if ! _variomedia_rest POST "dns-records" "{\"data\": {\"type\": \"dns-record\", \"attributes\": {\"record_type\": \"TXT\", \"name\": \"$_sub_domain\", \"domain\": \"$_domain\", \"data\": \"$txtvalue\", \"ttl\":300}}}"; then _err "$response" return 1 fi _debug2 _response "$response" return 0 } #fulldomain txtvalue dns_variomedia_rm() { fulldomain=$1 txtvalue=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" VARIOMEDIA_API_TOKEN="${VARIOMEDIA_API_TOKEN:-$(_readaccountconf_mutable VARIOMEDIA_API_TOKEN)}" if test -z "$VARIOMEDIA_API_TOKEN"; then VARIOMEDIA_API_TOKEN="" _err 'VARIOMEDIA_API_TOKEN was not exported' return 1 fi _saveaccountconf_mutable VARIOMEDIA_API_TOKEN "$VARIOMEDIA_API_TOKEN" _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug 'Getting txt records' if ! _variomedia_rest GET "dns-records?filter[domain]=$_domain"; then _err 'Error' return 1 fi _record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')" _debug _record_id "$_record_id" if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." else _info "Empty record id, it seems no such record." return 0 fi if ! _variomedia_rest DELETE "/dns-records/$_record_id"; then _err "$response" return 1 fi _debug2 _response "$response" return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then return 1 fi if ! _variomedia_rest GET "domains/$h"; then return 1 fi if _contains "$response" "\"id\":\"$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 } _variomedia_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Authorization: token $VARIOMEDIA_API_TOKEN" export _H2="Content-Type: application/vnd.api+json" export _H3="Accept: application/vnd.variomedia.v1+json" if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$VARIOMEDIA_API/$ep" "" "$m")" else response="$(_get "$VARIOMEDIA_API/$ep")" fi if [ "$?" != "0" ]; then _err "Error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_veesp.sh000066400000000000000000000110431472032365200166340ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_veesp_info='veesp.com Site: veesp.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_veesp Options: VEESP_User Username VEESP_Password Password Issues: github.com/acmesh-official/acme.sh/issues/3712 Author: ' VEESP_Api="https://secure.veesp.com/api" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_veesp_add() { fulldomain=$1 txtvalue=$2 VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}" VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}" VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64) if [ -z "$VEESP_Password" ] || [ -z "$VEESP_User" ]; then VEESP_Password="" VEESP_User="" _err "You don't specify veesp api key and email 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 VEESP_Password "$VEESP_Password" _saveaccountconf_mutable VEESP_User "$VEESP_User" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if VEESP_rest POST "service/$_service_id/dns/$_domain_id/records" "{\"name\":\"$fulldomain\",\"ttl\":1,\"priority\":0,\"type\":\"TXT\",\"content\":\"$txtvalue\"}"; then if _contains "$response" "\"success\":true"; then _info "Added" #todo: check if the record takes effect return 0 else _err "Add txt record error." return 1 fi fi } # Usage: fulldomain txtvalue # Used to remove the txt record after validation dns_veesp_rm() { fulldomain=$1 txtvalue=$2 VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}" VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}" VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64) _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" VEESP_rest GET "service/$_service_id/dns/$_domain_id" count=$(printf "%s\n" "$response" | _egrep_o "\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | wc -l | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Don't need to remove." else record_id=$(printf "%s\n" "$response" | _egrep_o "{\"id\":[^}]*\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | cut -d\" -f4) _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! VEESP_rest DELETE "service/$_service_id/dns/$_domain_id/records/$record_id"; then _err "Delete record error." return 1 fi _contains "$response" "\"success\":true" fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=2 p=1 if ! VEESP_rest GET "dns"; then return 1 fi 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 _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"domain_id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1 | cut -d '"' -f 2) _debug _domain_id "$_domain_id" _service_id=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$h\",\"service_id\":[^}]*" | cut -d : -f 3 | cut -d '"' -f 2) _debug _service_id "$_service_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } VEESP_rest() { m=$1 ep="$2" data="$3" _debug "$ep" export _H1="Accept: application/json" export _H2="Authorization: Basic $VEESP_auth" if [ "$m" != "GET" ]; then _debug data "$data" export _H3="Content-Type: application/json" response="$(_post "$data" "$VEESP_Api/$ep" "" "$m")" else response="$(_get "$VEESP_Api/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_vercel.sh000066400000000000000000000070251472032365200167770ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_vercel_info='Vercel.com Site: Vercel.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vercel Options: VERCEL_TOKEN API Token ' # This is your API token which can be acquired on the account page. # https://vercel.com/account/tokens VERCEL_API="https://api.vercel.com" #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_vercel_add() { fulldomain=$1 txtvalue=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" VERCEL_TOKEN="${VERCEL_TOKEN:-$(_readaccountconf_mutable VERCEL_TOKEN)}" if [ -z "$VERCEL_TOKEN" ]; then VERCEL_TOKEN="" _err "You have not set the Vercel API token yet." _err "Please visit https://vercel.com/account/tokens to generate it." return 1 fi _saveaccountconf_mutable VERCEL_TOKEN "$VERCEL_TOKEN" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _info "Adding record" if _vercel_rest POST "v2/domains/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\"}"; then if printf -- "%s" "$response" | grep "\"uid\":\"" >/dev/null; then _info "Added" return 0 else _err "Unexpected response while adding text record." return 1 fi fi _err "Add txt record error." } dns_vercel_rm() { fulldomain=$1 txtvalue=$2 if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _vercel_rest GET "v2/domains/$_domain/records" count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ") if [ "$count" = "0" ]; then _info "Don't need to remove." else _record_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"slug\":\"[^,]*\",\"name\":\"$_sub_domain\",[^{]*\"type\":\"TXT\",\"value\":\"$txtvalue\"" | cut -d: -f2 | cut -d, -f1 | tr -d '"') if [ "$_record_id" ]; then echo "$_record_id" | while read -r item; do if _vercel_rest DELETE "v2/domains/$_domain/records/$item"; then _info "removed record" "$item" return 0 else _err "failed to remove record" "$item" return 1 fi done fi fi } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { domain="$1" ep="$2" i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) if [ -z "$h" ]; then #not valid return 1 fi if ! _vercel_rest GET "v4/domains/$h"; then return 1 fi if _contains "$response" "\"name\":\"$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 return 1 } _vercel_rest() { m="$1" ep="$2" data="$3" path="$VERCEL_API/$ep" export _H1="Content-Type: application/json" export _H2="Authorization: Bearer $VERCEL_TOKEN" if [ "$m" != "GET" ]; then _secure_debug2 data "$data" response="$(_post "$data" "$path" "" "$m")" else response="$(_get "$path")" fi _ret="$?" _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" _debug "http response code $_code" _secure_debug2 response "$response" if [ "$_ret" != "0" ]; then _err "error $ep" return 1 fi response="$(printf "%s" "$response" | _normalizeJson)" return 0 } acme.sh-3.1.0/dnsapi/dns_vscale.sh000077500000000000000000000073101472032365200167740ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_vscale_info='vscale.io Site: vscale.io Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_vscale Options: VSCALE_API_KEY API Key Author: Alex Loban ' VSCALE_API_URL="https://api.vscale.io/v1" ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_vscale_add() { fulldomain=$1 txtvalue=$2 if [ -z "$VSCALE_API_KEY" ]; then VSCALE_API_KEY="" _err "You didn't specify the VSCALE api key yet." _err "Please create you key and try again." return 1 fi _saveaccountconf VSCALE_API_KEY "$VSCALE_API_KEY" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _vscale_tmpl_json="{\"type\":\"TXT\",\"name\":\"$_sub_domain.$_domain\",\"content\":\"$txtvalue\"}" if _vscale_rest POST "domains/$_domain_id/records/" "$_vscale_tmpl_json"; then response=$(printf "%s\n" "$response" | _egrep_o "{\"error\": \".+\"" | cut -d : -f 2) if [ -z "$response" ]; then _info "txt record updated success." return 0 fi fi return 1 } #fulldomain txtvalue dns_vscale_rm() { fulldomain=$1 txtvalue=$2 _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _vscale_rest GET "domains/$_domain_id/records/" if [ -n "$response" ]; then record_id=$(printf "%s\n" "$response" | _egrep_o "\"TXT\", \"id\": [0-9]+, \"name\": \"$_sub_domain.$_domain\"" | cut -d : -f 2 | tr -d ", \"name\"") _debug record_id "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if _vscale_rest DELETE "domains/$_domain_id/records/$record_id" && [ -z "$response" ]; then _info "txt record deleted success." return 0 fi _debug response "$response" return 1 fi return 1 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=12345 _get_root() { domain=$1 i=2 p=1 if _vscale_rest GET "domains/"; then response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi hostedzone="$(echo "$response" | tr "{" "\n" | _egrep_o "\"name\":\s*\"$h\".*}")" if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done fi return 1 } #method uri qstr data _vscale_rest() { mtd="$1" ep="$2" data="$3" _debug mtd "$mtd" _debug ep "$ep" export _H1="Accept: application/json" export _H2="Content-Type: application/json" export _H3="X-Token: ${VSCALE_API_KEY}" if [ "$mtd" != "GET" ]; then # both POST and DELETE. _debug data "$data" response="$(_post "$data" "$VSCALE_API_URL/$ep" "" "$mtd")" else response="$(_get "$VSCALE_API_URL/$ep")" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_vultr.sh000066400000000000000000000076321472032365200166770ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_vultr_info='vultr.com Site: vultr.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vultr Options: VULTR_API_KEY API Key Issues: github.com/acmesh-official/acme.sh/issues/2374 Author: ' VULTR_Api="https://api.vultr.com/v2" ######## Public functions ##################### # #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_vultr_add() { fulldomain=$1 txtvalue=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" VULTR_API_KEY="${VULTR_API_KEY:-$(_readaccountconf_mutable VULTR_API_KEY)}" if test -z "$VULTR_API_KEY"; then VULTR_API_KEY='' _err 'VULTR_API_KEY was not exported' return 1 fi _saveaccountconf_mutable VULTR_API_KEY "$VULTR_API_KEY" _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug 'Getting txt records' _vultr_rest GET "domains/$_domain/records" if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then _err 'Error' return 1 fi if ! _vultr_rest POST "domains/$_domain/records" "{\"name\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"type\":\"TXT\"}"; then _err "$response" return 1 fi _debug2 _response "$response" return 0 } #fulldomain txtvalue dns_vultr_rm() { fulldomain=$1 txtvalue=$2 _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" VULTR_API_KEY="${VULTR_API_KEY:-$(_readaccountconf_mutable VULTR_API_KEY)}" if test -z "$VULTR_API_KEY"; then VULTR_API_KEY="" _err 'VULTR_API_KEY was not exported' return 1 fi _saveaccountconf_mutable VULTR_API_KEY "$VULTR_API_KEY" _debug 'First detect the root zone' if ! _get_root "$fulldomain"; then return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug 'Getting txt records' _vultr_rest GET "domains/$_domain/records" if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then _err 'Error' return 1 fi _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'id' | cut -d : -f 2 | tr -d '"')" _debug _record_id "$_record_id" if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." else _info "Empty record id, it seems no such record." return 0 fi if ! _vultr_rest DELETE "domains/$_domain/records/$_record_id"; then _err "$response" return 1 fi _debug2 _response "$response" return 0 } #################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www # _domain=domain.com # _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 i=1 while true; do _domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$_domain" if [ -z "$_domain" ]; then return 1 fi if ! _vultr_rest GET "domains"; then return 1 fi if printf "%s\n" "$response" | grep -E '^\{.*\}' >/dev/null; then if _contains "$response" "\"domain\":\"$_domain\""; then _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")" return 0 else _debug "Go to next level of $_domain" fi else _err "$response" return 1 fi i=$(_math "$i" + 1) done return 1 } _vultr_rest() { m=$1 ep="$2" data="$3" _debug "$ep" api_key_trimmed=$(echo "$VULTR_API_KEY" | tr -d '"') export _H1="Authorization: Bearer $api_key_trimmed" export _H2='Content-Type: application/json' if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$VULTR_Api/$ep" "" "$m")" else response="$(_get "$VULTR_Api/$ep")" fi if [ "$?" != "0" ]; then _err "Error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_websupport.sh000066400000000000000000000125341472032365200177320ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_websupport_info='Websupport.sk Site: Websupport.sk Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_websupport Options: WS_ApiKey API Key. Called "Identifier" in the WS Admin WS_ApiSecret API Secret. Called "Secret key" in the WS Admin Issues: github.com/acmesh-official/acme.sh/issues/3486 Author: trgo.sk , akulumbeg ' # Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey WS_Api="https://rest.websupport.sk" ######## Public functions ##################### dns_websupport_add() { fulldomain=$1 txtvalue=$2 WS_ApiKey="${WS_ApiKey:-$(_readaccountconf_mutable WS_ApiKey)}" WS_ApiSecret="${WS_ApiSecret:-$(_readaccountconf_mutable WS_ApiSecret)}" if [ "$WS_ApiKey" ] && [ "$WS_ApiSecret" ]; then _saveaccountconf_mutable WS_ApiKey "$WS_ApiKey" _saveaccountconf_mutable WS_ApiSecret "$WS_ApiSecret" else WS_ApiKey="" WS_ApiSecret="" _err "You did not specify the API Key and/or API Secret" _err "You can get the API login credentials from https://admin.websupport.sk/en/auth/apiKey" return 1 fi _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so # we can not use updating anymore. # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) # _debug count "$count" # if [ "$count" = "0" ]; then _info "Adding record" if _ws_rest POST "/v1/user/self/zone/$_domain/record" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 elif _contains "$response" "The record already exists"; then _info "Already exists, OK" return 0 else _err "Add txt record error." return 1 fi fi _err "Add txt record error." return 1 } dns_websupport_rm() { fulldomain=$1 txtvalue=$2 _debug2 fulldomain "$fulldomain" _debug2 txtvalue "$txtvalue" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" _debug "Getting txt records" _ws_rest GET "/v1/user/self/zone/$_domain/record" if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"items\")" -lt "1" ]; then _err "Error: $response" return 1 fi record_line="$(_get_from_array "$response" "$txtvalue")" _debug record_line "$record_line" if [ -z "$record_line" ]; then _info "Don't need to remove." else record_id=$(echo "$record_line" | _egrep_o "\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ") _debug "record_id" "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 fi if ! _ws_rest DELETE "/v1/user/self/zone/$_domain/record/$record_id"; then _err "Delete record error." return 1 fi if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"success\")" -lt "1" ]; then return 1 else return 0 fi fi } #################### Private Functions ################################## _get_root() { domain=$1 i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi if ! _ws_rest GET "/v1/user/self/zone"; then return 1 fi if _contains "$response" "\"name\":\"$h\""; then _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain=$h return 0 fi return 1 fi p=$i i=$(_math "$i" + 1) done return 1 } _ws_rest() { me=$1 pa="$2" da="$3" _debug2 api_key "$WS_ApiKey" _debug2 api_secret "$WS_ApiSecret" timestamp=$(_time) datez="$(_utc_date | sed "s/ /T/" | sed "s/$/+0000/")" canonical_request="${me} ${pa} ${timestamp}" signature_hash=$(printf "%s" "$canonical_request" | _hmac sha1 "$(printf "%s" "$WS_ApiSecret" | _hex_dump | tr -d " ")" hex) basicauth="$(printf "%s:%s" "$WS_ApiKey" "$signature_hash" | _base64)" _debug2 method "$me" _debug2 path "$pa" _debug2 data "$da" _debug2 timestamp "$timestamp" _debug2 datez "$datez" _debug2 canonical_request "$canonical_request" _debug2 signature_hash "$signature_hash" _debug2 basicauth "$basicauth" export _H1="Accept: application/json" export _H2="Content-Type: application/json" export _H3="Authorization: Basic ${basicauth}" export _H4="Date: ${datez}" _debug2 H1 "$_H1" _debug2 H2 "$_H2" _debug2 H3 "$_H3" _debug2 H4 "$_H4" if [ "$me" != "GET" ]; then _debug2 "${me} $WS_Api${pa}" _debug data "$da" response="$(_post "$da" "${WS_Api}${pa}" "" "$me")" else _debug2 "GET $WS_Api${pa}" response="$(_get "$WS_Api${pa}")" fi _debug2 response "$response" return "$?" } _get_from_array() { va="$1" fi="$2" for i in $(echo "$va" | sed "s/{/ /g"); do if _contains "$i" "$fi"; then echo "$i" break fi done } acme.sh-3.1.0/dnsapi/dns_west_cn.sh000066400000000000000000000052421472032365200171600ustar00rootroot00000000000000#!/usr/bin/env sh # West.cn Domain api #WEST_Username="username" #WEST_Key="sADDsdasdgdsf" #Set key at https://www.west.cn/manager/API/APIconfig.asp REST_API="https://api.west.cn/API/v2" ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_west_cn_add() { fulldomain=$1 txtvalue=$2 WEST_Username="${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}" WEST_Key="${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}" if [ -z "$WEST_Username" ] || [ -z "$WEST_Key" ]; then WEST_Username="" WEST_Key="" _err "You don't specify west api key and username yet." _err "Please set you key and try again." return 1 fi #save the api key and email to the account conf file. _saveaccountconf_mutable WEST_Username "$WEST_Username" _saveaccountconf_mutable WEST_Key "$WEST_Key" add_record "$fulldomain" "$txtvalue" } #Usage: rm _acme-challenge.www.domain.com dns_west_cn_rm() { fulldomain=$1 txtvalue=$2 WEST_Username="${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}" WEST_Key="${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}" if ! _rest POST "domain/dns/" "act=dnsrec.list&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT"; then _err "dnsrec.list error." return 1 fi if _contains "$response" 'no records'; then _info "Don't need to remove." return 0 fi record_id=$(echo "$response" | tr "{" "\n" | grep -- "$txtvalue" | grep '^"record_id"' | cut -d : -f 2 | cut -d ',' -f 1) _debug record_id "$record_id" if [ -z "$record_id" ]; then _err "Can not get record id." return 1 fi if ! _rest POST "domain/dns/" "act=dnsrec.remove&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_id=$record_id"; then _err "dnsrec.remove error." return 1 fi _contains "$response" "success" } #add the txt record. #usage: add fulldomain txtvalue add_record() { fulldomain=$1 txtvalue=$2 _info "Adding record" if ! _rest POST "domain/dns/" "act=dnsrec.add&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT&record_value=$txtvalue"; then return 1 fi _contains "$response" "success" } #Usage: method URI data _rest() { m="$1" ep="$2" data="$3" _debug "$ep" url="$REST_API/$ep" _debug url "$url" if [ "$m" = "GET" ]; then response="$(_get "$url" | tr -d '\r')" else _debug2 data "$data" response="$(_post "$data" "$url" | tr -d '\r')" fi if [ "$?" != "0" ]; then _err "error $ep" return 1 fi _debug2 response "$response" return 0 } acme.sh-3.1.0/dnsapi/dns_world4you.sh000066400000000000000000000160771472032365200174760ustar00rootroot00000000000000#!/usr/bin/env sh # shellcheck disable=SC2034 dns_world4you_info='World4You.com Site: World4You.com Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_world4you Options: WORLD4YOU_USERNAME Username WORLD4YOU_PASSWORD Password Issues: github.com/acmesh-official/acme.sh/issues/3269 Author: Lorenz Stechauner ' WORLD4YOU_API="https://my.world4you.com/en" PAKETNR='' TLD='' RECORD='' ################ Public functions ################ # Usage: dns_world4you_add dns_world4you_add() { fqdn=$(echo "$1" | _lower_case) 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/") _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/') 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[uniqueFormIdDP]=$formiddp&AddDnsRecordForm[_token]=$form_token" _info "Adding record..." ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded') _resethttp if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then res=$(_get "$WORLD4YOU_API/$paketnr/dns") if _contains "$res" "successfully"; then return 0 else msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to add record: Unknown error" echo "$ret" >'error-01.html' echo "$res" >'error-02.html' _err "View error-01.html and error-02.html for debugging" else _err "Unable to add record: my.world4you.com: $msg" fi return 1 fi else msg=$(echo "$ret" | grep '"form-error-message"' | sed 's/^.*
\([^<]*\)<\/div>.*$/\1/') _err "Unable to add record: my.world4you.com: $msg" return 1 fi } # Usage: dns_world4you_rm dns_world4you_rm() { fqdn=$(echo "$1" | _lower_case) 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/") _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/') 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[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[_token]=$form_token" _info "Removing record..." ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns/record/delete" '' POST 'application/x-www-form-urlencoded') _resethttp if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then res=$(_get "$WORLD4YOU_API/$paketnr/dns") if _contains "$res" "successfully"; then return 0 else msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to remove record: Unknown error" echo "$ret" >'error-01.html' echo "$res" >'error-02.html' _err "View error-01.html and error-02.html for debugging" else _err "Unable to remove record: my.world4you.com: $msg" fi return 1 fi else msg=$(echo "$ret" | grep "form-error-message" | sed 's/^.*
\([^<]*\)<\/div>.*$/\1/') _err "Unable to remove record: my.world4you.com: $msg" 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" _resethttp export ACME_HTTP_NO_REDIRECTS=1 page=$(_get "$WORLD4YOU_API/login") _resethttp if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then _info "Already logged in" _parse_sessid return 0 fi _info "Logging in..." username="$WORLD4YOU_USERNAME" password="$WORLD4YOU_PASSWORD" csrf_token=$(echo "$page" | grep '_csrf_token' | sed 's/^.*]*value=\"\([^"]*\)\".*$/\1/') _parse_sessid 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" _parse_sessid else msg=$(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/') _err "Unable to log in: my.world4you.com: $msg" return 1 fi } # Usage: _get_paketnr
_get_paketnr() { fqdn="$1" form="$2" domains=$(echo "$form" | grep '