diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 615e5d8b..727ba315 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -65,7 +65,7 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v3 + - 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 @@ -113,7 +113,7 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install tools run: brew install socat - name: Clone acmetest @@ -164,7 +164,7 @@ jobs: - name: Set git to use LF run: | git config --global core.autocrlf false - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install cygwin base packages with chocolatey run: | choco config get cacheLocation @@ -204,7 +204,7 @@ jobs: FreeBSD: - runs-on: macos-12 + runs-on: ubuntu-latest needs: Windows env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -223,10 +223,10 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v3 + - 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@v0 + - 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 @@ -255,7 +255,7 @@ jobs: OpenBSD: - runs-on: macos-12 + runs-on: ubuntu-latest needs: FreeBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -274,10 +274,10 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v3 + - 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@v0 + - 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 @@ -306,7 +306,7 @@ jobs: NetBSD: - runs-on: macos-12 + runs-on: ubuntu-latest needs: OpenBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -325,14 +325,14 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v3 + - 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@v0 + - 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: | - pkg_add curl socat + /usr/sbin/pkg_add curl socat usesh: true copyback: false run: | @@ -358,7 +358,7 @@ jobs: DragonFlyBSD: - runs-on: macos-12 + runs-on: ubuntu-latest needs: NetBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -377,14 +377,14 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v3 + - 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@v0 + - 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 + pkg install -y curl socat libnghttp2 usesh: true copyback: false run: | @@ -413,7 +413,7 @@ jobs: Solaris: - runs-on: macos-12 + runs-on: ubuntu-latest needs: DragonFlyBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -433,10 +433,10 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v3 + - 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@v0 + - 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 @@ -463,3 +463,52 @@ jobs: ./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 + + diff --git a/.github/workflows/DragonFlyBSD.yml b/.github/workflows/DragonFlyBSD.yml index 6daa9be4..f360f85c 100644 --- a/.github/workflows/DragonFlyBSD.yml +++ b/.github/workflows/DragonFlyBSD.yml @@ -1,71 +1,71 @@ -name: 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) Pretend Pear X1 - #- 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-12 - 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@v3 - - 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@v0 - with: - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN' - copyback: "false" - nat: | - "8080": "80" - prepare: | - pkg install -y curl socat - usesh: true - run: | - cd ../acmetest \ - && ./letest.sh - - +name: 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) Pretend Pear X1 + #- 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 + + diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml index 0fa55fd4..b90c9ccd 100644 --- a/.github/workflows/FreeBSD.yml +++ b/.github/workflows/FreeBSD.yml @@ -41,7 +41,7 @@ jobs: # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" - runs-on: macos-12 + runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} @@ -51,7 +51,7 @@ jobs: TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: @@ -61,7 +61,7 @@ jobs: 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@v0 + - 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: | diff --git a/.github/workflows/Linux.yml b/.github/workflows/Linux.yml index 156fa5df..33e43483 100644 --- a/.github/workflows/Linux.yml +++ b/.github/workflows/Linux.yml @@ -33,7 +33,7 @@ jobs: TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 TEST_ACME_Server: "LetsEncrypt.org_test" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Clone acmetest run: | cd .. \ diff --git a/.github/workflows/MacOS.yml b/.github/workflows/MacOS.yml index c1f29769..c3f046ab 100644 --- a/.github/workflows/MacOS.yml +++ b/.github/workflows/MacOS.yml @@ -44,7 +44,7 @@ jobs: CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install tools run: brew install socat - name: Clone acmetest diff --git a/.github/workflows/NetBSD.yml b/.github/workflows/NetBSD.yml index 25872c42..4574bef5 100644 --- a/.github/workflows/NetBSD.yml +++ b/.github/workflows/NetBSD.yml @@ -1,71 +1,71 @@ -name: 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) Pretend Pear X1 - #- 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-12 - 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@v3 - - 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@v0 - with: - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN' - nat: | - "8080": "80" - prepare: | - pkg_add curl socat - usesh: true - copyback: false - run: | - cd ../acmetest \ - && ./letest.sh - - +name: 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) Pretend Pear X1 + #- 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 + + diff --git a/.github/workflows/Omnios.yml b/.github/workflows/Omnios.yml new file mode 100644 index 00000000..e3da0be8 --- /dev/null +++ b/.github/workflows/Omnios.yml @@ -0,0 +1,75 @@ +name: 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) Pretend Pear X1 + - TEST_ACME_Server: "LetsEncrypt.org_test" + CA_ECDSA: "" + CA: "" + CA_EMAIL: "" + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 + 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 + + diff --git a/.github/workflows/OpenBSD.yml b/.github/workflows/OpenBSD.yml index 7746645a..e141c47b 100644 --- a/.github/workflows/OpenBSD.yml +++ b/.github/workflows/OpenBSD.yml @@ -41,7 +41,7 @@ jobs: # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" - runs-on: macos-12 + runs-on: ubuntu-latest env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} @@ -51,7 +51,7 @@ jobs: TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: vmactions/cf-tunnel@v0 id: tunnel with: @@ -61,12 +61,12 @@ jobs: 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@v0 + - 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 + prepare: pkg_add socat curl wget libnghttp2 usesh: true copyback: false run: | diff --git a/.github/workflows/PebbleStrict.yml b/.github/workflows/PebbleStrict.yml index 9f3a98ce..3f8fdb62 100644 --- a/.github/workflows/PebbleStrict.yml +++ b/.github/workflows/PebbleStrict.yml @@ -33,7 +33,7 @@ jobs: TEST_CA: "Pebble Intermediate CA" steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install tools run: sudo apt-get install -y socat - name: Run Pebble @@ -58,7 +58,7 @@ jobs: TEST_IPCERT: 1 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install tools run: sudo apt-get install -y socat - name: Run Pebble diff --git a/.github/workflows/Solaris.yml b/.github/workflows/Solaris.yml index 34d31a59..bdd3f040 100644 --- a/.github/workflows/Solaris.yml +++ b/.github/workflows/Solaris.yml @@ -1,74 +1,75 @@ -name: 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) Pretend Pear X1 - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 - 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: macos-12 - 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@v3 - - 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@v0 - with: - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - copyback: "false" - nat: | - "8080": "80" - prepare: pkgutil -y -i socat curl wget - run: | - cd ../acmetest \ - && ./letest.sh - +name: 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) Pretend Pear X1 + - TEST_ACME_Server: "LetsEncrypt.org_test" + CA_ECDSA: "" + CA: "" + CA_EMAIL: "" + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 + 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 + + diff --git a/.github/workflows/Ubuntu.yml b/.github/workflows/Ubuntu.yml index 4bf2ba29..53cc1060 100644 --- a/.github/workflows/Ubuntu.yml +++ b/.github/workflows/Ubuntu.yml @@ -70,7 +70,7 @@ jobs: TestingDomain: ${{ matrix.TestingDomain }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install tools run: sudo apt-get install -y socat wget - name: Start StepCA diff --git a/.github/workflows/Windows.yml b/.github/workflows/Windows.yml index c02e2f77..61ef5ad8 100644 --- a/.github/workflows/Windows.yml +++ b/.github/workflows/Windows.yml @@ -49,7 +49,7 @@ jobs: - name: Set git to use LF run: | git config --global core.autocrlf false - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install cygwin base packages with chocolatey run: | choco config get cacheLocation diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 48c44429..ea446d84 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -41,7 +41,7 @@ jobs: if: "contains(needs.CheckToken.outputs.hasToken, 'true')" steps: - name: checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx diff --git a/.github/workflows/pr_dns.yml b/.github/workflows/pr_dns.yml index 5faa9105..4d7a325d 100644 --- a/.github/workflows/pr_dns.yml +++ b/.github/workflows/pr_dns.yml @@ -4,8 +4,6 @@ on: pull_request_target: types: - opened - branches: - - 'dev' paths: - 'dnsapi/*.sh' @@ -22,9 +20,11 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: `**Welcome** - Please make sure you're read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test). + 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, 都必须确保通过这个测试. ` }) diff --git a/.github/workflows/pr_notify.yml b/.github/workflows/pr_notify.yml index 4844e297..3b0e3e4b 100644 --- a/.github/workflows/pr_notify.yml +++ b/.github/workflows/pr_notify.yml @@ -22,7 +22,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: `**Welcome** - Please make sure you're read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify). + 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 ✨ ` diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index a5a08bbf..746727d4 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -22,7 +22,7 @@ jobs: ShellCheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install Shellcheck run: sudo apt-get install -y shellcheck - name: DoShellcheck @@ -31,7 +31,7 @@ jobs: shfmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - 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 diff --git a/README.md b/README.md index 15bc4089..9a5c106b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ [![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) @@ -51,14 +51,12 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) - [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) -- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) - [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html) - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) -- [archlinux](https://www.archlinux.org/packages/community/any/acme.sh) - [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) -- [CentOS Web Panel](http://centos-webpanel.com/) +- [CentOS Web Panel](https://control-webpanel.com) - [lnmp.org](https://lnmp.org/) - [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials) @@ -75,20 +73,21 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) |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|[![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 -|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)|CentOS -|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)|openSUSE -|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)|Alpine Linux (with curl) -|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)|Archlinux -|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)|fedora -|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)|Kali Linux -|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)|Oracle 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)|Mageia -|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)|Gentoo Linux -|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)|ClearLinux -|11|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 -|22|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) -|23|[![](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) +|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): @@ -508,10 +507,6 @@ Support this project with your organization. Your logo will show up here with a -#### Sponsors - -[![quantumca-acmesh-logo](https://user-images.githubusercontent.com/8305679/183255712-634ee1db-bb61-4c03-bca0-bacce99e078c.svg)](https://www.quantumca.com.cn/?__utm_source=acmesh-donation) - # 19. License & Others diff --git a/acme.sh b/acme.sh index 8fbc16d0..a51b0249 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=3.0.6 +VER=3.0.8 PROJECT_NAME="acme.sh" @@ -102,12 +102,12 @@ ECC_SUFFIX="${ECC_SEP}ecc" LOG_LEVEL_1=1 LOG_LEVEL_2=2 LOG_LEVEL_3=3 -DEFAULT_LOG_LEVEL="$LOG_LEVEL_1" +DEFAULT_LOG_LEVEL="$LOG_LEVEL_2" DEBUG_LEVEL_1=1 DEBUG_LEVEL_2=2 DEBUG_LEVEL_3=3 -DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_1 +DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_2 DEBUG_LEVEL_NONE=0 DOH_CLOUDFLARE=1 @@ -923,8 +923,16 @@ _sed_i() { fi } +if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then + __USE_EGREP=1 +else + __USE_EGREP="" +fi + _egrep_o() { - if ! egrep -o "$1" 2>/dev/null; then + if [ "$__USE_EGREP" ]; then + egrep -o -- "$1" 2>/dev/null + else sed -n 's/.*\('"$1"'\).*/\1/p' fi } @@ -1422,6 +1430,9 @@ _toPkcs() { else ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" fi + if [ "$?" == "0" ]; then + _savedomainconf "Le_PFXPassword" "$pfxPassword" + fi } @@ -1553,7 +1564,7 @@ createDomainKey() { createCSR() { _info "Creating csr" if [ -z "$1" ]; then - _usage "Usage: $PROJECT_ENTRY --create-csr --domain [--domain ...]" + _usage "Usage: $PROJECT_ENTRY --create-csr --domain [--domain ...] [--ecc]" return fi @@ -1787,6 +1798,10 @@ _date2time() { 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 da="$(echo "$1" | tr -d "Z" | tr "T" ' ')" perl -MTime::Piece -e 'print Time::Piece->strptime($ENV{da}, "%Y-%m-%d %H:%M:%S")->epoch, "\n";' 2>/dev/null; then + return + fi _err "Can not parse _date2time $1" return 1 } @@ -2101,12 +2116,18 @@ _head_n() { } _tail_n() { - if ! tail -n "$1" 2>/dev/null; then + 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 @@ -2116,6 +2137,7 @@ _send_signed_request() { if [ -z "$keyfile" ]; then keyfile="$ACCOUNT_KEY_PATH" fi + _debug "=======Begin Send Signed Request=======" _debug url "$url" _debug payload "$payload" @@ -2277,7 +2299,7 @@ _setopt() { if [ ! -f "$__conf" ]; then touch "$__conf" fi - if [ -n "$(tail -c 1 <"$__conf")" ]; then + if [ -n "$(_tail_c 1 <"$__conf")" ]; then echo >>"$__conf" fi @@ -2377,13 +2399,18 @@ _migratedomainconf() { _old_key="$1" _new_key="$2" _b64encode="$3" - _value=$(_readdomainconf "$_old_key") - if [ -z "$_value" ]; then - return 1 # oldkey is not found - fi - _savedomainconf "$_new_key" "$_value" "$_b64encode" + _old_value=$(_readdomainconf "$_old_key") _cleardomainconf "$_old_key" - _debug "Domain config $_old_key has been migrated to $_new_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 @@ -2480,10 +2507,10 @@ _startserver() { _debug Le_Listen_V6 "$Le_Listen_V6" _NC="socat" - if [ "$Le_Listen_V4" ]; then - _NC="$_NC -4" - elif [ "$Le_Listen_V6" ]; then + if [ "$Le_Listen_V6" ]; then _NC="$_NC -6" + else + _NC="$_NC -4" fi if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then @@ -2500,22 +2527,34 @@ _startserver() { _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';" & +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" } @@ -2884,6 +2923,7 @@ _initpath() { fi fi _debug DOMAIN_PATH "$DOMAIN_PATH" + export DOMAIN_PATH fi if [ -z "$DOMAIN_BACKUP_PATH" ]; then @@ -2935,22 +2975,6 @@ _initpath() { } -_exec() { - if [ -z "$_EXEC_TEMP_ERR" ]; then - _EXEC_TEMP_ERR="$(_mktemp)" - fi - - if [ "$_EXEC_TEMP_ERR" ]; then - eval "$@ 2>>$_EXEC_TEMP_ERR" - else - eval "$@" - fi -} - -_exec_err() { - [ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" && echo "" >"$_EXEC_TEMP_ERR" -} - _apachePath() { _APACHECTL="apachectl" if ! _exists apachectl; then @@ -2963,8 +2987,7 @@ _apachePath() { fi fi - if ! _exec $_APACHECTL -V >/dev/null; then - _exec_err + if ! $_APACHECTL -V >/dev/null; then return 1 fi @@ -3016,8 +3039,7 @@ _restoreApache() { cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf" _debug "Restored: $httpdconf." - if ! _exec $_APACHECTL -t; then - _exec_err + if ! $_APACHECTL -t; then _err "Sorry, restore apache config error, please contact me." return 1 fi @@ -3035,8 +3057,7 @@ _setApache() { #test the conf first _info "Checking if there is an error in the apache config file before starting." - if ! _exec "$_APACHECTL" -t >/dev/null; then - _exec_err + if ! $_APACHECTL -t >/dev/null; then _err "The apache config file has error, please fix it first, then try again." _err "Don't worry, there is nothing changed to your system." return 1 @@ -3097,8 +3118,7 @@ Allow from all chmod 755 "$ACME_DIR" fi - if ! _exec "$_APACHECTL" graceful; then - _exec_err + if ! $_APACHECTL graceful; then _err "$_APACHECTL graceful error, please contact me." _restoreApache return 1 @@ -3129,7 +3149,7 @@ _setNginx() { _err "nginx command is not found." return 1 fi - NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "--conf-path=[^ ]* " | tr -d " ")" + 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" @@ -3183,8 +3203,8 @@ _setNginx() { return 1 fi _info "Check the nginx conf before setting up." - if ! _exec "nginx -t" >/dev/null; then - _exec_err + if ! nginx -t >/dev/null 2>&1; then + _err "It seems that nginx conf is not correct, cannot continue." return 1 fi @@ -3211,16 +3231,14 @@ location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" { fi _debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)" _info "nginx conf is done, let's check it again." - if ! _exec "nginx -t" >/dev/null; then - _exec_err + if ! nginx -t >/dev/null 2>&1; then _err "It seems that nginx conf was broken, let's restore." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 fi _info "Reload nginx" - if ! _exec "nginx -s reload" >/dev/null; then - _exec_err + if ! nginx -s reload >/dev/null 2>&1; then _err "It seems that nginx reload error, let's restore." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 @@ -3345,8 +3363,7 @@ _restoreNginx() { done _info "Reload nginx" - if ! _exec "nginx -s reload" >/dev/null; then - _exec_err + if ! nginx -s reload >/dev/null; then _err "It seems that nginx reload error, please report bug." return 1 fi @@ -3759,7 +3776,7 @@ _regAccount() { eab_sign_t="$eab_protected64.$eab_payload64" _debug3 eab_sign_t "$eab_sign_t" - key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 multi | _hex_dump | tr -d ' ')" + 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) @@ -4519,7 +4536,7 @@ issue() { vlist="$Le_Vlist" _cleardomainconf "Le_Vlist" - _info "Getting domain auth token for each domain" + _debug "Getting domain auth token for each domain" sep='#' dvsep=',' if [ -z "$vlist" ]; then @@ -4575,12 +4592,22 @@ issue() { if [ "$_notAfter" ]; then _newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\"" fi + _debug "STEP 1, Ordering a Certificate" if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then _err "Create new order error." _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" @@ -4605,6 +4632,7 @@ issue() { 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 @@ -4613,6 +4641,7 @@ issue() { _err "get to authz error." _err "_authorizations_seg" "$_authorizations_seg" _err "_authz_url" "$_authz_url" + _err "$response" _clearup _on_issue_err "$_post_hook" return 1 @@ -4620,14 +4649,23 @@ issue() { 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 + _authorizations_map="$_d,$response#$_authz_url $_authorizations_map" done + _debug2 _authorizations_map "$_authorizations_map" _index=0 @@ -4679,33 +4717,32 @@ $_authorizations_map" _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" - keyauthorization="" - if [ -z "$entry" ]; then - if ! _startswith "$d" '*.'; then - _debug "Not a wildcard domain, lets check whether the validation is already valid." - if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then - _debug "$d is already valid." - keyauthorization="$STATE_VERIFIED" - _debug keyauthorization "$keyauthorization" - fi - fi - if [ -z "$keyauthorization" ]; then - _err "Error, can not 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 "The supported validation types are: $_supported_vtypes, but you specified: $vtype" - fi - _clearup - _on_issue_err "$_post_hook" - return 1 + + if [ -z "$keyauthorization" -a -z "$entry" ]; then + _err "Error, can not 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 "The supported validation types are: $_supported_vtypes, but you specified: $vtype" fi + _clearup + _on_issue_err "$_post_hook" + return 1 fi if [ -z "$keyauthorization" ]; then @@ -4731,15 +4768,9 @@ $_authorizations_map" fi keyauthorization="$token.$thumbprint" _debug keyauthorization "$keyauthorization" - - if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then - _debug "$d is already verified." - keyauthorization="$STATE_VERIFIED" - _debug keyauthorization "$keyauthorization" - fi fi - dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot" + dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url" _debug dvlist "$dvlist" vlist="$vlist$dvlist$dvsep" @@ -4756,6 +4787,7 @@ $_authorizations_map" 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 is already verified, skip $vtype." @@ -4881,7 +4913,7 @@ $_authorizations_map" 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, skip $vtype." continue @@ -4891,6 +4923,7 @@ $_authorizations_map" _debug "d" "$d" _debug "keyauthorization" "$keyauthorization" _debug "uri" "$uri" + _debug "_authz_url" "$_authz_url" removelevel="" token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)" @@ -4960,18 +4993,6 @@ $_authorizations_map" if ! chmod a+r "$wellknown_path/$token"; then _debug "chmod failed, but we just continue." fi - if [ ! "$usingApache" ]; then - if webroot_owner=$(_stat "$_currentRoot"); then - _debug "Changing owner/group of .well-known to $webroot_owner" - if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then - _debug "$(cat "$_EXEC_TEMP_ERR")" - _exec_err >/dev/null 2>&1 - fi - else - _debug "not changing owner/group of webroot" - fi - fi - fi elif [ "$vtype" = "$VTYPE_ALPN" ]; then acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")" @@ -5010,6 +5031,7 @@ $_authorizations_map" MAX_RETRY_TIMES=30 fi + _debug "Lets check the status of the authz" while true; do waittimes=$(_math "$waittimes" + 1) if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then @@ -5033,9 +5055,9 @@ $_authorizations_map" errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)" _debug2 errordetail "$errordetail" if [ "$errordetail" ]; then - _err "$d:Verify error:$errordetail" + _err "Invalid status, $d:Verify error detail:$errordetail" else - _err "$d:Verify error:$error" + _err "Invalid status, $d:Verify error:$error" fi if [ "$DEBUG" ]; then if [ "$vtype" = "$VTYPE_HTTP" ]; then @@ -5057,12 +5079,12 @@ $_authorizations_map" break fi - if [ "$status" = "pending" ]; then + if _contains "$status" "pending"; then _info "Pending, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" - elif [ "$status" = "processing" ]; then + elif _contains "$status" "processing"; then _info "Processing, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" else - _err "$d:Verify error:$response" + _err "Unknown status: $status, $d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -5072,10 +5094,10 @@ $_authorizations_map" _sleep 2 _debug "checking" - _send_signed_request "$uri" + _send_signed_request "$_authz_url" if [ "$?" != "0" ]; then - _err "$d:Verify error:$response" + _err "Invalid code, $d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -5319,6 +5341,12 @@ $_authorizations_map" _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" @@ -6011,6 +6039,7 @@ installcronjob() { fi _t=$(_time) random_minute=$(_math $_t % 60) + random_hour=$(_math $_t / 60 % 24) if ! _exists "$_CRONTAB" && _exists "fcrontab"; then _CRONTAB="fcrontab" @@ -6035,16 +6064,14 @@ installcronjob() { _info "Installing cron job" if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then if _exists uname && uname -a | grep SunOS >/dev/null; then - $_CRONTAB -l | { - cat - echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" - } | $_CRONTAB -- + _CRONTAB_STDIN="$_CRONTAB --" else - $_CRONTAB -l | { - cat - echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" - } | $_CRONTAB - + _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 "Install cron job failed. You need to manually renew your certs." @@ -6917,7 +6944,7 @@ Parameters: -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 1 if argument is omitted. + --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. @@ -6935,7 +6962,7 @@ Parameters: -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 1. + --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. @@ -6945,7 +6972,7 @@ Parameters: 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.. + --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. @@ -6975,7 +7002,8 @@ Parameters: --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 to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--to-pkcs12' and '--create-csr' + --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. diff --git a/deploy/docker.sh b/deploy/docker.sh index 3aa1b2cd..c9815d5b 100755 --- a/deploy/docker.sh +++ b/deploy/docker.sh @@ -273,16 +273,27 @@ _check_curl_version() { _minor="$(_getfield "$_cversion" 2 '.')" _debug2 "_minor" "$_minor" - if [ "$_major$_minor" -lt "740" ]; then + 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 - if [ "$_major$_minor" -lt "750" ]; then - _debug "Use short host name" - export _CURL_NO_HOST=1 - else - export _CURL_NO_HOST= - fi - return 0 + } diff --git a/deploy/haproxy.sh b/deploy/haproxy.sh index c255059d..c8491d92 100644 --- a/deploy/haproxy.sh +++ b/deploy/haproxy.sh @@ -36,6 +36,19 @@ # 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 ##################### @@ -46,6 +59,7 @@ haproxy_deploy() { _ccert="$3" _cca="$4" _cfullchain="$5" + _cmdpfx="" # Some defaults DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy" @@ -53,6 +67,8 @@ haproxy_deploy() { 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}" @@ -86,6 +102,11 @@ haproxy_deploy() { _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}" @@ -118,6 +139,36 @@ haproxy_deploy() { 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" @@ -142,12 +193,13 @@ haproxy_deploy() { _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 "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}" + cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}" _ret="$?" # Check that we could create the temporary file @@ -265,15 +317,86 @@ haproxy_deploy() { fi fi - # Reload HAProxy - _debug _reload "${_reload}" - eval "${_reload}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Error code ${_ret} during reload" - return ${_ret} + 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 - _info "Reload successful" + # 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 diff --git a/deploy/panos.sh b/deploy/panos.sh index ef622ded..0dc1b2f0 100644 --- a/deploy/panos.sh +++ b/deploy/panos.sh @@ -7,11 +7,18 @@ # # Firewall admin with superuser and IP address is required. # -# export PANOS_USER="" # required -# export PANOS_PASS="" # required -# export PANOS_HOST="" # 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 +# This function is to parse the XML response from the firewall parse_response() { type=$2 if [ "$type" = 'keygen' ]; then @@ -23,25 +30,46 @@ parse_response() { message="PAN-OS Key could not be set." fi else - status=$(echo "$1" | sed 's/^.*"\([a-z]*\)".*/\1/g') - message=$(echo "$1" | sed 's/^.*\(.*\)<\/result.*/\1/g') + 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 keygen, cert, key, commit - _debug "**** Deploying $type *****" + 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 - #Generate DEIM + _debug "**** Deploying $type ****" + #Generate DELIM delim="-----MultipartDelimiter$(date "+%s%N")" nl="\015\012" #Set Header @@ -53,6 +81,9 @@ deployer() { 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" @@ -61,7 +92,10 @@ deployer() { 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 "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" + 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}" @@ -69,16 +103,25 @@ deployer() { content=$(printf %b "$content") fi + # Commit changes if [ "$type" = 'commit' ]; then + _debug "**** Committing changes ****" export _H1="Content-Type: application/x-www-form-urlencoded" - cmd=$(printf "%s" "<$_panos_user>" | _url_encode) - content="type=commit&key=$_panos_key&cmd=$cmd" + #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 _debug response_status "$response_status" if [ "$response_status" = "success" ]; then _debug "Successfully deployed $type" @@ -92,43 +135,95 @@ deployer() { # This is the main function that will call the other functions to deploy everything. panos_deploy() { - _cdomain="$1" + _cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename _ckey="$2" _cfullchain="$5" - # PANOS ENV VAR check - if [ -z "$PANOS_USER" ] || [ -z "$PANOS_PASS" ] || [ -z "$PANOS_HOST" ]; then - _debug "No ENV variables found lets check for saved variables" - _getdeployconf PANOS_USER - _getdeployconf PANOS_PASS - _getdeployconf PANOS_HOST - _panos_user=$PANOS_USER - _panos_pass=$PANOS_PASS - _panos_host=$PANOS_HOST - if [ -z "$_panos_user" ] && [ -z "$_panos_pass" ] && [ -z "$_panos_host" ]; then - _err "No host, user and pass found.. If this is the first time deploying please set PANOS_HOST, PANOS_USER and PANOS_PASS in environment variables. Delete them after you have succesfully deployed certs." - return 1 - else - _debug "Using saved env variables." - fi + + # 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 "Detected ENV variables to be saved to the deploy conf." - # Encrypt and save user + _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 - _savedeployconf PANOS_HOST "$PANOS_HOST" 1 - _panos_user="$PANOS_USER" - _panos_pass="$PANOS_PASS" - _panos_host="$PANOS_HOST" + 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 - _debug "Let's use username and pass to generate token." - if [ -z "$_panos_user" ] || [ -z "$_panos_pass" ] || [ -z "$_panos_host" ]; then - _err "Please pass username and password and host as env variables PANOS_USER, PANOS_PASS and PANOS_HOST" + + # 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 - _debug "Getting PANOS KEY" - deployer keygen + # 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 "Missing apikey." + _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 diff --git a/deploy/proxmoxve.sh b/deploy/proxmoxve.sh index 216a8fc7..f9de590c 100644 --- a/deploy/proxmoxve.sh +++ b/deploy/proxmoxve.sh @@ -99,11 +99,11 @@ proxmoxve_deploy() { _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 + _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 + _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 @@ -124,8 +124,8 @@ HEREDOC ) _debug2 Payload "$_json_payload" - # Push certificates to server. - export _HTTPS_INSECURE=1 + _info "Push certificates to server" + export HTTPS_INSECURE=1 export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}" _post "$_json_payload" "$_target_url" "" POST "application/json" diff --git a/deploy/routeros.sh b/deploy/routeros.sh index c4c9470d..d1779b8d 100644 --- a/deploy/routeros.sh +++ b/deploy/routeros.sh @@ -137,7 +137,7 @@ routeros_deploy() { return $_err_code fi - DEPLOY_SCRIPT_CMD="/system script add name=\"LE Cert Deploy - $_cdomain\" owner=$ROUTER_OS_USERNAME \ + DEPLOY_SCRIPT_CMD="/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 ];\ @@ -158,11 +158,11 @@ source=\"/certificate remove [ find name=$_cdomain.cer_0 ];\ return $_err_code fi - if ! _ssh_remote_cmd "/system script run \"LE Cert Deploy - $_cdomain\""; then + if ! _ssh_remote_cmd "/system script run \"LECertDeploy-$_cdomain\""; then return $_err_code fi - if ! _ssh_remote_cmd "/system script remove \"LE Cert Deploy - $_cdomain\""; then + if ! _ssh_remote_cmd "/system script remove \"LECertDeploy-$_cdomain\""; then return $_err_code fi diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index c31a5df0..342fb32e 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -1,34 +1,53 @@ -#!/usr/bin/env sh - -# Here is a script to deploy cert to Synology DSM -# -# It requires following environment variables: -# -# SYNO_Username - Synology Username to login (must be an administrator) -# SYNO_Password - Synology Password to login -# SYNO_Certificate - Certificate description to target for replacement -# -# The following environmental variables may be set if you don't like their -# default values: -# -# SYNO_Scheme - defaults to http -# SYNO_Hostname - defaults to localhost -# SYNO_Port - defaults to 5000 -# SYNO_DID - device ID to skip OTP - defaults to empty -# SYNO_TOTP_SECRET - TOTP secret to generate OTP - defaults to empty -# -# Dependencies: -# ------------- -# - jq and curl -# - oathtool (When using 2 Factor Authentication and SYNO_TOTP_SECRET is set) -# -#returns 0 means success, otherwise error. +#!/bin/bash -######## Public functions ##################### +################################################################################ +# 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 (When available and SYNO_USE_TEMP_ADMIN is set) +################################################################################ +# Return value: +# 0 means success, otherwise error. +################################################################################ +########## Public functions #################################################### #domain keyfile certfile cafile fullchain synology_dsm_deploy() { - _cdomain="$1" _ckey="$2" _ccert="$3" @@ -36,148 +55,380 @@ synology_dsm_deploy() { _debug _cdomain "$_cdomain" - # Get Username and Password, but don't save until we successfully authenticate - _getdeployconf SYNO_Username - _getdeployconf SYNO_Password - _getdeployconf SYNO_Create - _getdeployconf SYNO_DID - _getdeployconf SYNO_TOTP_SECRET - if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then - _err "SYNO_Username & SYNO_Password must be set" - return 1 - fi - _debug2 SYNO_Username "$SYNO_Username" - _secure_debug2 SYNO_Password "$SYNO_Password" + # 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 + _debug2 SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN" + _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; then + _err "Tools are missing for creating temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead." + return 1 + fi - # Optional scheme, hostname, and port for Synology DSM - _getdeployconf SYNO_Scheme - _getdeployconf SYNO_Hostname - _getdeployconf SYNO_Port + [ -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 - # default vaules 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" + 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 - _savedeployconf SYNO_Scheme "$SYNO_Scheme" - _savedeployconf SYNO_Hostname "$SYNO_Hostname" - _savedeployconf SYNO_Port "$SYNO_Port" + # 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 - _debug2 SYNO_Scheme "$SYNO_Scheme" - _debug2 SYNO_Hostname "$SYNO_Hostname" - _debug2 SYNO_Port "$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 verfiy it's real - _getdeployconf SYNO_Certificate - _debug SYNO_Certificate "${SYNO_Certificate:-}" + # 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 + if printf "%s" "$SYNO_CERTIFICATE" | grep '\\'; then _err "Do not use a backslash (\) in your certificate description" return 1 fi - _base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port" + _debug "Getting API version..." + _base_url="$SYNO_SCHEME://$SYNO_HOSTNAME:$SYNO_PORT" _debug _base_url "$_base_url" - - _debug "Getting API version" 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 token from JSON and session id from cookie - _info "Logging into $SYNO_Hostname:$SYNO_Port" - encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)" - encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)" + # 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 - otp_code="" if [ -n "$SYNO_TOTP_SECRET" ]; then - if _exists oathtool; then - otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)" - else + _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 - fi + DEPRECATED_otp_code="$(oathtool --base32 --totp "$SYNO_TOTP_SECRET" 2>/dev/null)" - if [ -n "$SYNO_DID" ]; then - _H1="Cookie: did=$SYNO_DID" - export _H1 - _debug3 H1 "${_H1}" + 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/auth.cgi?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 locally deployment, if you are sure that 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 "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 + 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 "Temporary disabled enforce 2FA-OTP to complete authentication." + _info "previous_otp_enforce_option" "$otp_enforce_option" + else + otp_enforce_option="" + fi + fi + response=$(_get "$_base_url/webapi/entry.cgi?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 - response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$otp_code&device_name=certrenewal&device_id=$SYNO_DID" "$_base_url/webapi/auth.cgi?enable_syno_token=yes") - token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p') - _debug3 response "$response" - _debug token "$token" + 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/Firmware/DSM/All/enu/Synology_DiskStation_Administration_CLI_Guide.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" - if [ -z "$token" ]; then - _err "Unable to authenticate to $SYNO_Hostname:$SYNO_Port using $SYNO_Scheme." - _err "Check your username and password." - _err "If two-factor authentication is enabled for the user, set SYNO_TOTP_SECRET." + 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 "SYNO_USE_TEMP_ADMIN=1 is not supported if enforce auth with 2FA-OTP is enabled." + else + _err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue." + fi + elif [ "$error_code" == "400" ] || [ "$error_code" == "401" ] || [ "$error_code" == "408" ] || [ "$error_code" == "409" ] || [ "$error_code" == "410" ]; then + _err "Failed to authenticate with a non-existent or disabled account, or the account password is incorrect or has expired." + 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 the full log to the community." + _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 - _savedeployconf SYNO_Username "$SYNO_Username" - _savedeployconf SYNO_Password "$SYNO_Password" - _savedeployconf SYNO_DID "$SYNO_DID" - _savedeployconf SYNO_TOTP_SECRET "$SYNO_TOTP_SECRET" + # 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" + _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')" + 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" - if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then - _err "Unable to find certificate: $SYNO_Certificate and \$SYNO_Create is not set" + 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 with error: $error_code, please try again or contact Synology to learn more." + fi + _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" + _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 - _info "Generate form POST request" + # 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}" + 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" + _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" + _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" + _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 "http services were restarted" + _info "Restart HTTP services succeeded." else - _info "http services were NOT restarted" + _info "Restart HTTP services failed." fi + _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" + _logout return 0 else - _err "Unable to update certificate, error code $response" + _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 +} diff --git a/dnsapi/dns_1984hosting.sh b/dnsapi/dns_1984hosting.sh index 6accc597..e4ef2e4b 100755 --- a/dnsapi/dns_1984hosting.sh +++ b/dnsapi/dns_1984hosting.sh @@ -1,46 +1,46 @@ #!/usr/bin/env sh -#This file name is "dns_1984hosting.sh" -#So, here must be a method dns_1984hosting_add() -#Which will be called by acme.sh to add the txt record to your api system. -#returns 0 means success, otherwise error. +# This file name is "dns_1984hosting.sh" +# So, here must be a method dns_1984hosting_add() +# Which will be called by acme.sh to add the txt record to your api system. +# returns 0 means success, otherwise error. -#Author: Adrian Fedoreanu -#Report Bugs here: https://github.com/acmesh-official/acme.sh +# Author: Adrian Fedoreanu +# Report Bugs here: https://github.com/acmesh-official/acme.sh # or here... https://github.com/acmesh-official/acme.sh/issues/2851 -# -######## Public functions ##################### + +######## Public functions ##################### # Export 1984HOSTING username and password in following variables # # One984HOSTING_Username=username # One984HOSTING_Password=password # -# sessionid cookie is saved in ~/.acme.sh/account.conf -# username/password need to be set only when changed. +# username/password and csrftoken/sessionid cookies are saved in ~/.acme.sh/account.conf -#Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# 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" + _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" + _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file." return 1 fi - _debug "First detect the root zone" + _debug "First detect the root zone." if ! _get_root "$fulldomain"; then - _err "invalid domain" "$fulldomain" + _err "Invalid domain '$fulldomain'." return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _debug "Add TXT record $fulldomain with value '$txtvalue'" + _debug "Add TXT record $fulldomain with value '$txtvalue'." value="$(printf '%s' "$txtvalue" | _url_encode)" url="https://1984.hosting/domains/entry/" @@ -53,92 +53,96 @@ dns_1984hosting_add() { _debug2 postdata "$postdata" _authpost "$postdata" "$url" - response="$(echo "$_response" | _normalizeJson)" - _debug2 response "$response" - - if _contains "$response" '"haserrors": true'; then - _err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post" + 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" + 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" + 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" + _info "Added acme challenge TXT record for $fulldomain at 1984Hosting." return 0 } -#Usage: fulldomain txtvalue -#Remove the txt record after validation. +# Usage: fulldomain txtvalue +# Remove the txt record after validation. dns_1984hosting_rm() { fulldomain=$1 txtvalue=$2 - _info "Delete TXT record using 1984Hosting" + _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" + _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file." return 1 fi - _debug "First detect the root zone" + _debug "First detect the root zone." if ! _get_root "$fulldomain"; then - _err "invalid domain" "$fulldomain" + _err "Invalid domain '$fulldomain'." return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _debug "Delete $fulldomain TXT record" + _debug "Delete $fulldomain TXT record." url="https://1984.hosting/domains" if ! _get_zone_id "$url" "$_domain"; then - _err "invalid zone" "$_domain" + _err "Invalid zone '$_domain'." return 1 fi _htmlget "$url/$_zone_id" "$txtvalue" - _debug2 _response "$_response" 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" + _err "Error getting TXT entry_id for $1." return 1 fi _authpost "entry=$entry_id" "$url/delentry/" - response="$(echo "$_response" | _normalizeJson)" - _debug2 response "$response" - - if ! _contains "$response" '"ok": true'; then - _err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post" + 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" + _info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting." return 0 } #################### Private functions below ################################## - -# usage: _1984hosting_login username password -# returns 0 success _1984hosting_login() { if ! _check_credentials; then return 1; fi if _check_cookies; then - _debug "Already logged in" + _debug "Already logged in." return 0 fi - _debug "Login to 1984Hosting as user $One984HOSTING_Username" + _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/accounts/checkuserauth/" + 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)" @@ -149,6 +153,8 @@ _1984hosting_login() { 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 @@ -157,9 +163,13 @@ _1984hosting_login() { } _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 @@ -171,42 +181,43 @@ _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" + _debug "No cached cookie(s) found." return 1 fi - _authget "https://1984.hosting/accounts/loginstatus/" - if _contains "$response" '"ok": true'; then - _debug "Cached cookies still valid" + _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" + + _debug "Cached cookies no longer valid. Clearing cookies." One984HOSTING_SESSIONID_COOKIE="" One984HOSTING_CSRFTOKEN_COOKIE="" - _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE" - _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_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 +# _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) + h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + # not valid if [ -z "$h" ]; then - #not valid return 1 fi _authget "https://1984.hosting/domains/soacheck/?zone=$h&nameserver=ns0.1984.is." if _contains "$_response" "serial" && ! _contains "$_response" "null"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") _domain="$h" return 0 fi @@ -216,46 +227,47 @@ _get_root() { return 1 } -#usage: _get_zone_id url domain.com -#returns zone id for domain.com +# Usage: _get_zone_id url domain.com +# Returns zone id for domain.com _get_zone_id() { url=$1 domain=$2 _htmlget "$url" "$domain" - _debug2 _response "$_response" _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" + _err "Error getting _zone_id for $2." return 1 fi return 0 } -# add extra headers to request +# Add extra headers to request _authget() { - export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE" + export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE" _response=$(_get "$1" | _normalizeJson) _debug2 _response "$_response" } -# truncate huge HTML response -# echo: Argument list too long +# Truncate huge HTML response +# Echo: Argument list too long _htmlget() { - export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE" + 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 +# 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 _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") + _response="$(_post "$1" "$2" | _normalizeJson)" + _debug2 _response "$_response" } diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh index c2105672..c69839dc 100755 --- a/dnsapi/dns_ali.sh +++ b/dnsapi/dns_ali.sh @@ -117,7 +117,7 @@ _ali_urlencode() { _ali_nonce() { #_head_n 1 b.c.d -> c.d -> d) while true; do - h=$(printf "%s" "$domain" | cut -d . -f $i-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100 | sed 's/\./\\./g') _debug "Checking domain: $h" if [ -z "$h" ]; then _error "invalid domain" @@ -207,24 +206,40 @@ _use_container_role() { } _use_instance_role() { - _url="http://169.254.169.254/latest/meta-data/iam/security-credentials/" - _debug "_url" "$_url" - if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then + _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 - _aws_role=$(_get "$_url" "" 1) - _debug "_aws_role" "$_aws_role" - _use_metadata "$_url$_aws_role" + + _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 '"')" + _key="$(echo "${_line%%:*}" | tr -d '\"')" _value="${_line#*:}" _debug3 "_key" "$_key" _secure_debug3 "_value" "$_value" diff --git a/dnsapi/dns_bookmyname.sh b/dnsapi/dns_bookmyname.sh new file mode 100644 index 00000000..62548fd0 --- /dev/null +++ b/dnsapi/dns_bookmyname.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env sh + +#Here is a sample custom api script. +#This file name is "dns_bookmyname.sh" +#So, here must be a method dns_bookmyname_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: Neilpang +#Report Bugs here: https://github.com/acmesh-official/acme.sh +# +######## Public functions ##################### + +# Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide + +# 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 ################################## diff --git a/dnsapi/dns_dnsexit.sh b/dnsapi/dns_dnsexit.sh new file mode 100644 index 00000000..62d7d757 --- /dev/null +++ b/dnsapi/dns_dnsexit.sh @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +#use dns-01 at DNSExit.com + +#Author: Samuel Jimenez +#Report Bugs here: https://github.com/acmesh-official/acme.sh + +#DNSEXIT_API_KEY=ABCDEFGHIJ0123456789abcdefghij +#DNSEXIT_AUTH_USER=login@email.address +#DNSEXIT_AUTH_PASS=aStrongPassword +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 +} diff --git a/dnsapi/dns_do.sh b/dnsapi/dns_do.sh deleted file mode 100755 index 3850890c..00000000 --- a/dnsapi/dns_do.sh +++ /dev/null @@ -1,148 +0,0 @@ -#!/usr/bin/env sh - -# DNS API for Domain-Offensive / Resellerinterface / Domainrobot - -# Report bugs at https://github.com/seidler2547/acme.sh/issues - -# set these environment variables to match your customer ID and password: -# DO_PID="KD-1234567" -# DO_PW="cdfkjl3n2" - -DO_URL="https://soap.resellerinterface.de/" - -######## Public functions ##################### - -#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_do_add() { - fulldomain=$1 - txtvalue=$2 - if _dns_do_authenticate; then - _info "Adding TXT record to ${_domain} as ${fulldomain}" - _dns_do_soap createRR origin "${_domain}" name "${fulldomain}" type TXT data "${txtvalue}" ttl 300 - if _contains "${response}" '>success<'; then - return 0 - fi - _err "Could not create resource record, check logs" - fi - return 1 -} - -#fulldomain -dns_do_rm() { - fulldomain=$1 - if _dns_do_authenticate; then - if _dns_do_list_rrs; then - _dns_do_had_error=0 - for _rrid in ${_rr_list}; do - _info "Deleting resource record $_rrid for $_domain" - _dns_do_soap deleteRR origin "${_domain}" rrid "${_rrid}" - if ! _contains "${response}" '>success<'; then - _dns_do_had_error=1 - _err "Could not delete resource record for ${_domain}, id ${_rrid}" - fi - done - return $_dns_do_had_error - fi - fi - return 1 -} - -#################### Private functions below ################################## -_dns_do_authenticate() { - _info "Authenticating as ${DO_PID}" - _dns_do_soap authPartner partner "${DO_PID}" password "${DO_PW}" - if _contains "${response}" '>success<'; then - _get_root "$fulldomain" - _debug "_domain $_domain" - return 0 - else - _err "Authentication failed, are DO_PID and DO_PW set correctly?" - fi - return 1 -} - -_dns_do_list_rrs() { - _dns_do_soap getRRList origin "${_domain}" - if ! _contains "${response}" 'SOAP-ENC:Array'; then - _err "getRRList origin ${_domain} failed" - return 1 - fi - _rr_list="$(echo "${response}" | - tr -d "\n\r\t" | - sed -e 's//\n/g' | - grep ">$(_regexcape "$fulldomain")" | - sed -e 's/<\/item>/\n/g' | - grep '>id[0-9]{1,16}<' | - tr -d '><')" - [ "${_rr_list}" ] -} - -_dns_do_soap() { - func="$1" - shift - # put the parameters to xml - body="" - while [ "$1" ]; do - _k="$1" - shift - _v="$1" - shift - body="$body<$_k>$_v" - done - body="$body" - _debug2 "SOAP request ${body}" - - # build SOAP XML - _xml=' - - '"$body"' -' - - # set SOAP headers - export _H1="SOAPAction: ${DO_URL}#${func}" - - if ! response="$(_post "${_xml}" "${DO_URL}")"; then - _err "Error <$1>" - return 1 - fi - _debug2 "SOAP response $response" - - # retrieve cookie header - _H2="$(_egrep_o 'Cookie: [^;]+' <"$HTTP_HEADER" | _head_n 1)" - export _H2 - - return 0 -} - -_get_root() { - domain=$1 - i=1 - - _dns_do_soap getDomainList - _all_domains="$(echo "${response}" | - tr -d "\n\r\t " | - _egrep_o 'domain]+>[^<]+' | - sed -e 's/^domain<\/key>]*>//g')" - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f $i-100) - if [ -z "$h" ]; then - return 1 - fi - - if _contains "${_all_domains}" "^$(_regexcape "$h")\$"; then - _domain="$h" - return 0 - fi - - i=$(_math $i + 1) - done - _debug "$domain not found" - - return 1 -} - -_regexcape() { - echo "$1" | sed -e 's/\([]\.$*^[]\)/\\\1/g' -} diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh index 931da883..6092f45c 100644 --- a/dnsapi/dns_gandi_livedns.sh +++ b/dnsapi/dns_gandi_livedns.sh @@ -1,7 +1,8 @@ #!/usr/bin/env sh # Gandi LiveDNS v5 API -# https://doc.livedns.gandi.net/ +# https://api.gandi.net/docs/livedns/ +# https://api.gandi.net/docs/authentication/ for token + apikey (deprecated) authentication # currently under beta # # Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable @@ -12,20 +13,27 @@ # ######## Public functions ##################### -GANDI_LIVEDNS_API="https://dns.api.gandi.net/api/v5" +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" ]; then - _err "No API key specified for Gandi LiveDNS." - _err "Create your key and export it as GANDI_LIVEDNS_KEY" + 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 - _saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" + # 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 @@ -70,7 +78,7 @@ dns_gandi_livedns_rm() { _gandi_livedns_rest PUT \ "domains/$_domain/records/$_sub_domain/TXT" \ "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" && - _contains "$response" '{"message": "DNS Record Created"}' && + _contains "$response" '{"message":"DNS Record Created"}' && _info "Removing record $(__green "success")" } @@ -126,7 +134,7 @@ _dns_gandi_append_record() { _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"}' && + _contains "$response" '{"message":"DNS Record Created"}' && _info "Adding record $(__green "success")" } @@ -136,11 +144,11 @@ _dns_gandi_existing_rrset_values() { if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then return 1 fi - if ! _contains "$response" '"rrset_type": "TXT"'; then + 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 + if _contains "$response" '"rrset_values":\[\]'; then _debug "Empty rrset_values for TXT record, no previous TXT record." return 1 fi @@ -157,7 +165,12 @@ _gandi_livedns_rest() { _debug "$ep" export _H1="Content-Type: application/json" - export _H2="X-Api-Key: $GANDI_LIVEDNS_KEY" + + 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")" diff --git a/dnsapi/dns_gcloud.sh b/dnsapi/dns_gcloud.sh index 2788ad59..dc82c09d 100755 --- a/dnsapi/dns_gcloud.sh +++ b/dnsapi/dns_gcloud.sh @@ -42,7 +42,7 @@ dns_gcloud_rm() { echo "$rrdatas" | grep -F -v -- "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $? _dns_gcloud_execute_tr || return $? - _info "$fulldomain record added" + _info "$fulldomain record removed" } #################### Private functions below ################################## diff --git a/dnsapi/dns_gcore.sh b/dnsapi/dns_gcore.sh index d549a650..5f7f037e 100755 --- a/dnsapi/dns_gcore.sh +++ b/dnsapi/dns_gcore.sh @@ -4,8 +4,8 @@ #GCORE_Key='773$7b7adaf2a2b32bfb1b83787b4ff32a67eb178e3ada1af733e47b1411f2461f7f4fa7ed7138e2772a46124377bad7384b3bb8d87748f87b3f23db4b8bbe41b2bb' # -GCORE_Api="https://api.gcorelabs.com/dns/v2" -GCORE_Doc="https://apidocs.gcore.com/dns" +GCORE_Api="https://api.gcore.com/dns/v2" +GCORE_Doc="https://api.gcore.com/docs/dns" ######## Public functions ##################### diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index ba789da9..e483c0e8 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -194,7 +194,7 @@ _inwx_login() { response="$(_post "$xml_content" "$INWX_Api" "" "POST")" - INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')") + 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 diff --git a/dnsapi/dns_kappernet.sh b/dnsapi/dns_kappernet.sh index 83a7e5f8..914671d6 100644 --- a/dnsapi/dns_kappernet.sh +++ b/dnsapi/dns_kappernet.sh @@ -6,8 +6,7 @@ #KAPPERNETDNS_Key="yourKAPPERNETapikey" #KAPPERNETDNS_Secret="yourKAPPERNETapisecret" - -KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret" +#KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret" ############################################################################### # called with @@ -19,10 +18,9 @@ dns_kappernet_add() { 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 - KAPPERNETDNS_Key="" - KAPPERNETDNS_Secret="" _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." @@ -41,12 +39,12 @@ dns_kappernet_add() { _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%223600%22%2C%22prio%22%3A%22%22%7D" + 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 120 seconds for DNS to spread the new record" - _sleep 120 + _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" @@ -66,10 +64,9 @@ dns_kappernet_rm() { 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 - KAPPERNETDNS_Key="" - KAPPERNETDNS_Secret="" _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." @@ -81,7 +78,7 @@ dns_kappernet_rm() { _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%223600%22%2C%22prio%22%3A%22%22%7D" + 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 @@ -141,7 +138,7 @@ _kappernet_api() { if [ "$method" = "GET" ]; then response="$(_get "$url")" else - _err "Unsupported method" + _err "Unsupported method or missing Secret/Key" return 1 fi diff --git a/dnsapi/dns_limacity.sh b/dnsapi/dns_limacity.sh new file mode 100644 index 00000000..649550ae --- /dev/null +++ b/dnsapi/dns_limacity.sh @@ -0,0 +1,94 @@ +#!/usr/bin/env sh + +# Created by Laraveluser +# +# Pass credentials before "acme.sh --issue --dns dns_limacity ..." +# -- +# export LIMACITY_APIKEY="" +# -- +# +# Pleas note: APIKEY must have following roles: dns.admin, domains.reader + +######## Public functions ##################### + +LIMACITY_APIKEY="${LIMACITY_APIKEY:-$(_readaccountconf_mutable LIMACITY_APIKEY)}" +AUTH=$(printf "%s" "api:$LIMACITY_APIKEY" | _base64 -w 0) +export _H1="Authorization: Basic $AUTH" +export _H2="Content-Type: application/json" +APIBASE=https://www.lima-city.de/usercp + +#Usage: dns_limacity_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_limacity_add() { + _debug LIMACITY_APIKEY "$LIMACITY_APIKEY" + if [ "$LIMACITY_APIKEY" = "" ]; then + _err "No Credentials given" + return 1 + fi + + # save the dns server and key to the account conf file. + _saveaccountconf_mutable LIMACITY_APIKEY "${LIMACITY_APIKEY}" + + fulldomain=$1 + txtvalue=$2 + if ! _lima_get_domain_id "$fulldomain"; then return 1; fi + + msg=$(_post "{\"nameserver_record\":{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\",\"ttl\":60}}" "${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json" "" "POST") + _debug "$msg" + + if [ "$(echo "$msg" | _egrep_o "\"status\":\"ok\"")" = "" ]; then + _err "$msg" + return 1 + fi + + return 0 +} + +#Usage: dns_limacity_rm _acme-challenge.www.domain.com +dns_limacity_rm() { + + fulldomain=$1 + txtvalue=$2 + if ! _lima_get_domain_id "$fulldomain"; then return 1; fi + + for recordId in $(_get "${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json" | _egrep_o "{\"id\":[0-9]*[^}]*,\"name\":\"${fulldomain}\"" | _egrep_o "[0-9]*"); do + _post "" "${APIBASE}/domains/${LIMACITY_DOMAINID}/records/${recordId}" "" "DELETE" + done + + return 0 +} + +#################### Private functions below ################################## + +_lima_get_domain_id() { + domain="$1" + _debug "$domain" + i=2 + p=1 + + domains=$(_get "${APIBASE}/domains.json") + if [ "$(echo "$domains" | _egrep_o "\{.*""domains""")" ]; then + response="$(echo "$domains" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" + 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 "\{.*""unicode_fqdn""[^,]+""$h"".*\}")" + if [ "$hostedzone" ]; then + LIMACITY_DOMAINID=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$LIMACITY_DOMAINID" ]; 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 +} diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh index 399c7867..60d072e0 100644 --- a/dnsapi/dns_loopia.sh +++ b/dnsapi/dns_loopia.sh @@ -107,7 +107,7 @@ _loopia_load_config() { fi if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then - _err "Password contains quoute or double quoute and this is not supported by dns_loopia.sh" + _err "Password contains a quotation mark or double quotation marks and this is not supported by dns_loopia.sh" return 1 fi diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh index c2806a1b..d40cbe28 100755 --- a/dnsapi/dns_opnsense.sh +++ b/dnsapi/dns_opnsense.sh @@ -137,7 +137,7 @@ _get_root() { domain=$1 i=2 p=1 - if _opns_rest "GET" "/domain/searchMasterDomain"; then + if _opns_rest "GET" "/domain/searchPrimaryDomain"; then _domain_response="$response" else return 1 @@ -150,7 +150,7 @@ _get_root() { return 1 fi _debug h "$h" - id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"master\",\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2) + 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) diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh index 5e35011b..e1a958f6 100755 --- a/dnsapi/dns_ovh.sh +++ b/dnsapi/dns_ovh.sh @@ -14,6 +14,9 @@ #'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' @@ -29,9 +32,6 @@ SYS_EU='https://eu.api.soyoustart.com/1.0' #'soyoustart-ca' SYS_CA='https://ca.api.soyoustart.com/1.0' -#'runabove-ca' -RAV_CA='https://api.runabove.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" @@ -45,6 +45,10 @@ _ovh_get_api() { printf "%s" $OVH_EU return ;; + ovh-us | ovhus) + printf "%s" $OVH_US + return + ;; ovh-ca | ovhca) printf "%s" $OVH_CA return @@ -65,14 +69,15 @@ _ovh_get_api() { printf "%s" $SYS_CA return ;; - runabove-ca | runaboveca) - printf "%s" $RAV_CA + # raw API url starts with https:// + https*) + printf "%s" "$1" return ;; *) - _err "Unknown parameter : $1" + _err "Unknown endpoint : $1" return 1 ;; esac diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index f5986827..81973e07 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -41,11 +41,15 @@ pleskxml_init_checks_done=0 NEWLINE='\ ' -pleskxml_tplt_get_domains="" +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 @@ -145,22 +149,25 @@ dns_pleskxml_rm() { )" if [ -z "$reclist" ]; then - _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting." + _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 domain '$root_domain_name':" + _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 "${fulldomain}." | + 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 '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" + _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 @@ -251,9 +258,12 @@ _call_api() { # 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" | grep -c '^ *[^<]* *$')" - statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *ok *$')" + 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 @@ -369,16 +379,44 @@ _pleskxml_get_root_domain() { return 1 fi - # Generate a crude list of domains known to this Plesk account. + # 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="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" + 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 managed by Plesk server are (ignore the hacked output):' - _debug "$output" + _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 diff --git a/dnsapi/dns_tencent.sh b/dnsapi/dns_tencent.sh new file mode 100644 index 00000000..2f8d3b67 --- /dev/null +++ b/dnsapi/dns_tencent.sh @@ -0,0 +1,211 @@ +#!/usr/bin/env sh +Tencent_API="https://dnspod.tencentcloudapi.com" + +#Tencent_SecretId="AKIDz81d2cd22cdcdc2dcd1cc1d1A" +#Tencent_SecretKey="Gu5t9abcabcaabcbabcbbbcbcbbccbbcb" + +#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" +} diff --git a/dnsapi/dns_variomedia.sh b/dnsapi/dns_variomedia.sh index a35b8f0f..aa743807 100644 --- a/dnsapi/dns_variomedia.sh +++ b/dnsapi/dns_variomedia.sh @@ -69,7 +69,7 @@ dns_variomedia_rm() { return 1 fi - _record_id="$(echo "$response" | 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 ' ')" + _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." @@ -93,11 +93,11 @@ dns_variomedia_rm() { # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { - fulldomain=$1 + domain=$1 i=1 + p=1 while true; do - h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) - _debug h "$h" + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then return 1 fi @@ -106,17 +106,14 @@ _get_root() { return 1 fi - if _startswith "$response" "\{\"data\":"; then - if _contains "$response" "\"id\":\"$h\""; then - _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")" - _domain=$h - return 0 - 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 - - _debug "root domain not found" return 1 } diff --git a/dnsapi/dns_west_cn.sh b/dnsapi/dns_west_cn.sh new file mode 100644 index 00000000..d0bb7d49 --- /dev/null +++ b/dnsapi/dns_west_cn.sh @@ -0,0 +1,105 @@ +#!/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 +} diff --git a/notify/aws_ses.sh b/notify/aws_ses.sh new file mode 100644 index 00000000..30db45ad --- /dev/null +++ b/notify/aws_ses.sh @@ -0,0 +1,226 @@ +#!/usr/bin/env sh + +# +#AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#AWS_SECRET_ACCESS_KEY="xxxxxxx" +# +#AWS_SES_REGION="us-east-1" +# +#AWS_SES_TO="xxxx@xxx.com" +# +#AWS_SES_FROM="xxxx@cccc.com" +# +#AWS_SES_FROM_NAME="Something something" +#This is the Amazon SES api wrapper for acme.sh +AWS_WIKI="https://docs.aws.amazon.com/ses/latest/dg/send-email-api.html" + +aws_ses_send() { + _subject="$1" + _content="$2" + _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped + _debug "_statusCode" "$_statusCode" + + 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_SES_REGION="${AWS_SES_REGION:-$(_readaccountconf_mutable AWS_SES_REGION)}" + + 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 SES api key id and and api key secret yet." + _err "Please create your key and try again. see $(__green $AWS_WIKI)" + return 1 + fi + + if [ -z "$AWS_SES_REGION" ]; then + AWS_SES_REGION="" + _err "You haven't specified the aws SES api region yet." + _err "Please specify your region and try again. see https://docs.aws.amazon.com/general/latest/gr/ses.html" + return 1 + fi + _saveaccountconf_mutable AWS_SES_REGION "$AWS_SES_REGION" + + #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" + fi + + AWS_SES_TO="${AWS_SES_TO:-$(_readaccountconf_mutable AWS_SES_TO)}" + if [ -z "$AWS_SES_TO" ]; then + AWS_SES_TO="" + _err "You didn't specify an email to AWS_SES_TO receive messages." + return 1 + fi + _saveaccountconf_mutable AWS_SES_TO "$AWS_SES_TO" + + AWS_SES_FROM="${AWS_SES_FROM:-$(_readaccountconf_mutable AWS_SES_FROM)}" + if [ -z "$AWS_SES_FROM" ]; then + AWS_SES_FROM="" + _err "You didn't specify an email to AWS_SES_FROM receive messages." + return 1 + fi + _saveaccountconf_mutable AWS_SES_FROM "$AWS_SES_FROM" + + AWS_SES_FROM_NAME="${AWS_SES_FROM_NAME:-$(_readaccountconf_mutable AWS_SES_FROM_NAME)}" + _saveaccountconf_mutable AWS_SES_FROM_NAME "$AWS_SES_FROM_NAME" + + AWS_SES_SENDFROM="$AWS_SES_FROM_NAME <$AWS_SES_FROM>" + + AWS_SES_ACTION="Action=SendEmail" + AWS_SES_SOURCE="Source=$AWS_SES_SENDFROM" + AWS_SES_TO="Destination.ToAddresses.member.1=$AWS_SES_TO" + AWS_SES_SUBJECT="Message.Subject.Data=$_subject" + AWS_SES_MESSAGE="Message.Body.Text.Data=$_content" + + _data="${AWS_SES_ACTION}&${AWS_SES_SOURCE}&${AWS_SES_TO}&${AWS_SES_SUBJECT}&${AWS_SES_MESSAGE}" + + response="$(aws_rest POST "" "" "$_data")" +} + +_use_metadata() { + _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="email.$AWS_SES_REGION.amazonaws.com" + 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="$AWS_SES_REGION" + Service="ses" + + 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="https://$aws_host/$ep" + if [ "$qsr" ]; then + url="https://$aws_host/$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" "