]> git.proxmox.com Git - mirror_acme.sh.git/commitdiff
Merge branch 'acmesh-official:master' into master
authorF-Plass <60349140+F-Plass@users.noreply.github.com>
Fri, 3 Dec 2021 16:18:44 +0000 (17:18 +0100)
committerGitHub <noreply@github.com>
Fri, 3 Dec 2021 16:18:44 +0000 (17:18 +0100)
62 files changed:
.github/auto-comment.yml [deleted file]
.github/workflows/DNS.yml
.github/workflows/FreeBSD.yml [new file with mode: 0644]
.github/workflows/LetsEncrypt.yml [deleted file]
.github/workflows/Linux.yml [new file with mode: 0644]
.github/workflows/MacOS.yml [new file with mode: 0644]
.github/workflows/PebbleStrict.yml
.github/workflows/Solaris.yml [new file with mode: 0644]
.github/workflows/Ubuntu.yml [new file with mode: 0644]
.github/workflows/Windows.yml [new file with mode: 0644]
.github/workflows/dockerhub.yml
.github/workflows/shellcheck.yml
Dockerfile
README.md
acme.sh
deploy/cleverreach.sh
deploy/consul.sh [new file with mode: 0644]
deploy/gcore_cdn.sh
deploy/haproxy.sh
deploy/kong.sh
deploy/lighttpd.sh [new file with mode: 0644]
deploy/ssh.sh
deploy/synology_dsm.sh
deploy/unifi.sh
deploy/vault_cli.sh
dnsapi/dns_1984hosting.sh
dnsapi/dns_arvan.sh
dnsapi/dns_aurora.sh [new file with mode: 0644]
dnsapi/dns_aws.sh
dnsapi/dns_azion.sh [new file with mode: 0644]
dnsapi/dns_constellix.sh
dnsapi/dns_cpanel.sh [new file with mode: 0755]
dnsapi/dns_desec.sh
dnsapi/dns_dp.sh
dnsapi/dns_duckdns.sh
dnsapi/dns_gcloud.sh
dnsapi/dns_he.sh
dnsapi/dns_huaweicloud.sh
dnsapi/dns_infoblox.sh
dnsapi/dns_ionos.sh
dnsapi/dns_namecheap.sh
dnsapi/dns_netcup.sh
dnsapi/dns_nsd.sh
dnsapi/dns_oci.sh [new file with mode: 0644]
dnsapi/dns_one.sh
dnsapi/dns_ovh.sh
dnsapi/dns_pdns.sh
dnsapi/dns_porkbun.sh [new file with mode: 0644]
dnsapi/dns_rackspace.sh
dnsapi/dns_servercow.sh
dnsapi/dns_simply.sh
dnsapi/dns_veesp.sh [new file with mode: 0644]
dnsapi/dns_vultr.sh
dnsapi/dns_websupport.sh [new file with mode: 0644]
dnsapi/dns_world4you.sh
notify/bark.sh [new file with mode: 0644]
notify/feishu.sh [new file with mode: 0644]
notify/mail.sh
notify/pushbullet.sh [new file with mode: 0644]
notify/sendgrid.sh
notify/smtp.sh
notify/telegram.sh

diff --git a/.github/auto-comment.yml b/.github/auto-comment.yml
deleted file mode 100644 (file)
index 520b3ce..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-# Comment to a new issue.\r
-issuesOpened: >\r
-  If this is a bug report, please upgrade to the latest code and try again:\r
-  \r
-  如果有 bug, 请先更新到最新版试试:\r
-  \r
-  ```\r
-  acme.sh --upgrade\r
-  ```\r
-  \r
-  please also provide the log with `--debug 2`.\r
-  \r
-  同时请提供调试输出 `--debug 2`\r
-  \r
-  see: https://github.com/acmesh-official/acme.sh/wiki/How-to-debug-acme.sh\r
-  \r
-  Without `--debug 2` log, your issue will NEVER get replied.\r
-  \r
-  没有调试输出, 你的 issue 不会得到任何解答.\r
-\r
-\r
-pullRequestOpened: >\r
-  First, NEVER send a PR to `master` branch, it will NEVER be accepted. Please send to the `dev` branch instead.\r
-  \r
-  If this is a PR to support new DNS API or new notification API, please read this guide first:\r
-  https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide\r
-  \r
-  Please check the guide items one by one.\r
-  \r
-  Then add your usage here:\r
-  https://github.com/acmesh-official/acme.sh/wiki/dnsapi\r
-   \r
-  Or some other wiki pages:\r
-  \r
-  https://github.com/acmesh-official/acme.sh/wiki/deployhooks\r
-  \r
-  https://github.com/acmesh-official/acme.sh/wiki/notify\r
-  \r
-\r
-\r
index 5dc2d453469f62eb5e86616174c515168129b7f3..56781fffec09b03682383b48ef3266f9fe7140f9 100644 (file)
@@ -59,24 +59,24 @@ jobs:
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
     - name: Set env file\r
       run: |\r
-        cd ../acmetest \r
+        cd ../acmetest\r
         if [ "${{ secrets.TokenName1}}" ] ; then\r
-          echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> env.list\r
+          echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> docker.env\r
         fi\r
         if [ "${{ secrets.TokenName2}}" ] ; then\r
-          echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> env.list\r
+          echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> docker.env\r
         fi\r
         if [ "${{ secrets.TokenName3}}" ] ; then\r
-          echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> env.list\r
+          echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> docker.env\r
         fi\r
         if [ "${{ secrets.TokenName4}}" ] ; then\r
-          echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> env.list\r
+          echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> docker.env\r
         fi\r
         if [ "${{ secrets.TokenName5}}" ] ; then\r
-          echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> env.list\r
+          echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env\r
         fi\r
-        echo "TEST_DNS_NO_WILDCARD" >> env.list \r
-        echo "TEST_DNS_SLEEP" >> env.list\r
+        echo "TEST_DNS_NO_WILDCARD" >> docker.env\r
+        echo "TEST_DNS_SLEEP" >> docker.env\r
     - name: Run acmetest\r
       run: cd ../acmetest && ./rundocker.sh  testall\r
 \r
@@ -170,7 +170,7 @@ jobs:
         ./letest.sh\r
 \r
   FreeBSD:\r
-    runs-on: macos-latest\r
+    runs-on: macos-10.15\r
     needs: Windows\r
     env:\r
       TEST_DNS : ${{ secrets.TEST_DNS }}\r
@@ -184,7 +184,7 @@ jobs:
     - uses: actions/checkout@v2\r
     - name: Clone acmetest\r
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
-    - uses: vmactions/freebsd-vm@v0.0.7\r
+    - uses: vmactions/freebsd-vm@v0.1.4\r
       with:\r
         envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\r
         prepare: pkg install -y socat curl\r
@@ -209,7 +209,7 @@ jobs:
           ./letest.sh\r
 \r
   Solaris:\r
-    runs-on: macos-latest\r
+    runs-on: macos-10.15\r
     needs: FreeBSD\r
     env:\r
       TEST_DNS : ${{ secrets.TEST_DNS }}\r
@@ -223,11 +223,13 @@ jobs:
     - uses: actions/checkout@v2\r
     - name: Clone acmetest\r
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
-    - uses: vmactions/solaris-vm@v0.0.1\r
+    - uses: vmactions/solaris-vm@v0.0.3\r
       with:\r
         envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'\r
-        prepare: pkgutil -y -i socat curl\r
+        prepare: pkgutil -y -i socat\r
         run: |\r
+          pkg set-mediator -v -I default@1.1 openssl\r
+          export PATH=/usr/gnu/bin:$PATH\r
           if [ "${{ secrets.TokenName1}}" ] ; then\r
             export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}\r
           fi\r
@@ -245,5 +247,3 @@ jobs:
           fi\r
           cd ../acmetest\r
           ./letest.sh\r
-\r
-        \r
diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml
new file mode 100644 (file)
index 0000000..5d03276
--- /dev/null
@@ -0,0 +1,63 @@
+name: FreeBSD\r
+on:\r
+  push:\r
+    branches:\r
+      - '*'\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/FreeBSD.yml'\r
+\r
+  pull_request:\r
+    branches:\r
+      - dev\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/FreeBSD.yml'\r
+\r
+\r
+jobs:\r
+  FreeBSD:\r
+    strategy:\r
+      matrix:\r
+        include:\r
+         - TEST_ACME_Server: "LetsEncrypt.org_test"\r
+           CA_ECDSA: ""\r
+           CA: ""\r
+           CA_EMAIL: ""\r
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1\r
+         - TEST_ACME_Server: "ZeroSSL.com"\r
+           CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"\r
+           CA: "ZeroSSL RSA Domain Secure Site CA"\r
+           CA_EMAIL: "githubtest@acme.sh"\r
+           TEST_PREFERRED_CHAIN: ""\r
+    runs-on: macos-10.15\r
+    env:\r
+      TEST_LOCAL: 1\r
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r
+      CA: ${{ matrix.CA }}\r
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r
+    steps:\r
+    - uses: actions/checkout@v2\r
+    - uses: vmactions/cf-tunnel@v0.0.3\r
+      id: tunnel\r
+      with:\r
+        protocol: http\r
+        port: 8080\r
+    - name: Set envs\r
+      run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV\r
+    - name: Clone acmetest\r
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
+    - uses: vmactions/freebsd-vm@v0.1.5\r
+      with:\r
+        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'\r
+        nat: |\r
+          "8080": "80"\r
+        prepare: pkg install -y socat curl\r
+        usesh: true\r
+        run: |\r
+          cd ../acmetest \\r
+          && ./letest.sh\r
+\r
+\r
diff --git a/.github/workflows/LetsEncrypt.yml b/.github/workflows/LetsEncrypt.yml
deleted file mode 100644 (file)
index 8d0c4eb..0000000
+++ /dev/null
@@ -1,147 +0,0 @@
-name: LetsEncrypt\r
-on:\r
-  push:\r
-    branches:\r
-      - '*'\r
-    paths:\r
-      - '**.sh'\r
-      - '**.yml'\r
-  pull_request:\r
-    branches:\r
-      - dev\r
-    paths:\r
-      - '**.sh'\r
-      - '**.yml'\r
-\r
-\r
-jobs:\r
-  CheckToken:\r
-    runs-on: ubuntu-latest\r
-    outputs:\r
-      hasToken: ${{ steps.step_one.outputs.hasToken }}\r
-    env: \r
-      NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }}\r
-    steps:\r
-      - name: Set the value\r
-        id: step_one\r
-        run: |\r
-          if [ "$NGROK_TOKEN" ] ; then\r
-            echo "::set-output name=hasToken::true"\r
-          else\r
-            echo "::set-output name=hasToken::false"\r
-          fi\r
-      - name: Check the value\r
-        run: echo ${{ steps.step_one.outputs.hasToken }}\r
-\r
-  Ubuntu:\r
-    runs-on: ubuntu-latest\r
-    needs: CheckToken\r
-    if: "contains(needs.CheckToken.outputs.hasToken, 'true')"\r
-    env:\r
-      NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }}\r
-      TEST_LOCAL: 1\r
-    steps:\r
-    - uses: actions/checkout@v2\r
-    - name: Install tools\r
-      run: sudo apt-get install -y socat\r
-    - name: Clone acmetest\r
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
-    - name: Run acmetest\r
-      run: cd ../acmetest && sudo --preserve-env ./letest.sh\r
-\r
-  MacOS:\r
-    runs-on: macos-latest\r
-    needs: Ubuntu\r
-    env:\r
-      NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }}\r
-      TEST_LOCAL: 1\r
-    steps:\r
-    - uses: actions/checkout@v2\r
-    - name: Install tools\r
-      run:  brew install socat\r
-    - name: Clone acmetest\r
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
-    - name: Run acmetest\r
-      run: cd ../acmetest && sudo --preserve-env ./letest.sh\r
-\r
-  Windows:\r
-    runs-on: windows-latest\r
-    needs: MacOS\r
-    env:\r
-      NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }}\r
-      TEST_LOCAL: 1\r
-      #The 80 port is used by Windows server, we have to use a custom port, ngrok will also use this port.\r
-      Le_HTTPPort: 8888\r
-    steps:\r
-    - name: Set git to use LF\r
-      run: |\r
-          git config --global core.autocrlf false\r
-    - uses: actions/checkout@v2\r
-    - name: Install cygwin base packages with chocolatey\r
-      run: |\r
-          choco config get cacheLocation\r
-          choco install --no-progress cygwin\r
-      shell: cmd\r
-    - name: Install cygwin additional packages\r
-      run: |\r
-          C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git\r
-      shell: cmd\r
-    - name: Set ENV\r
-      shell: cmd\r
-      run: |\r
-          echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%\r
-    - name: Check ENV\r
-      shell: cmd\r
-      run: |\r
-          echo "PATH=%PATH%"\r
-    - name: Clone acmetest\r
-      shell: cmd\r
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
-    - name: Run acmetest\r
-      shell: cmd\r
-      run: cd ../acmetest && bash.exe -c ./letest.sh\r
-\r
-  FreeBSD:\r
-    runs-on: macos-latest\r
-    needs: Windows\r
-    env:\r
-      NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }}\r
-      TEST_LOCAL: 1\r
-    steps:\r
-    - uses: actions/checkout@v2\r
-    - name: Clone acmetest\r
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
-    - uses: vmactions/freebsd-vm@v0.0.7\r
-      with:\r
-        envs: 'NGROK_TOKEN TEST_LOCAL'\r
-        prepare: pkg install -y socat curl\r
-        usesh: true\r
-        run: |\r
-          cd ../acmetest && ./letest.sh\r
-\r
-  Solaris:\r
-    runs-on: macos-latest\r
-    needs: FreeBSD\r
-    env:\r
-      NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }}\r
-      TEST_LOCAL: 1\r
-    steps:\r
-    - uses: actions/checkout@v2\r
-    - uses: vmactions/ngrok-tunnel@v0.0.1\r
-      id: ngrok\r
-      with:\r
-        protocol: http\r
-        port: 8080\r
-    - name: Set envs\r
-      run: echo "TestingDomain=${{steps.ngrok.outputs.server}}" >> $GITHUB_ENV\r
-    - name: Clone acmetest\r
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
-    - uses: vmactions/solaris-vm@v0.0.1\r
-      with:\r
-        envs: 'TEST_LOCAL TestingDomain'\r
-        nat: |\r
-          "8080": "80"\r
-        prepare: pkgutil -y -i socat curl\r
-        run: |\r
-          cd ../acmetest && ./letest.sh\r
-\r
diff --git a/.github/workflows/Linux.yml b/.github/workflows/Linux.yml
new file mode 100644 (file)
index 0000000..cba708b
--- /dev/null
@@ -0,0 +1,41 @@
+name: Linux\r
+on:\r
+  push:\r
+    branches:\r
+      - '*'\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Linux.yml'\r
+\r
+  pull_request:\r
+    branches:\r
+      - dev\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Linux.yml'\r
+\r
+\r
+\r
+jobs:\r
+  Linux:\r
+    strategy:\r
+      matrix:\r
+        os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "centos:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3-amd64"]\r
+    runs-on: ubuntu-latest\r
+    env:\r
+      TEST_LOCAL: 1\r
+      TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1\r
+    steps:\r
+    - uses: actions/checkout@v2\r
+    - name: Clone acmetest\r
+      run: |\r
+          cd .. \\r
+          && git clone https://github.com/acmesh-official/acmetest.git \\r
+          && cp -r acme.sh acmetest/\r
+    - name: Run acmetest\r
+      run: |\r
+          cd ../acmetest \\r
+          && ./rundocker.sh  testplat ${{ matrix.os }}\r
+\r
+\r
+\r
diff --git a/.github/workflows/MacOS.yml b/.github/workflows/MacOS.yml
new file mode 100644 (file)
index 0000000..4b529f6
--- /dev/null
@@ -0,0 +1,55 @@
+name: MacOS\r
+on:\r
+  push:\r
+    branches:\r
+      - '*'\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/MacOS.yml'\r
+\r
+  pull_request:\r
+    branches:\r
+      - dev\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/MacOS.yml'\r
+\r
+\r
+jobs:\r
+  MacOS:\r
+    strategy:\r
+      matrix:\r
+        include:\r
+         - TEST_ACME_Server: "LetsEncrypt.org_test"\r
+           CA_ECDSA: ""\r
+           CA: ""\r
+           CA_EMAIL: ""\r
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1\r
+         - TEST_ACME_Server: "ZeroSSL.com"\r
+           CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"\r
+           CA: "ZeroSSL RSA Domain Secure Site CA"\r
+           CA_EMAIL: "githubtest@acme.sh"\r
+           TEST_PREFERRED_CHAIN: ""\r
+    runs-on: macos-latest\r
+    env:\r
+      TEST_LOCAL: 1\r
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r
+      CA: ${{ matrix.CA }}\r
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r
+    steps:\r
+    - uses: actions/checkout@v2\r
+    - name: Install tools\r
+      run:  brew install socat\r
+    - name: Clone acmetest\r
+      run: |\r
+          cd .. \\r
+          && git clone https://github.com/acmesh-official/acmetest.git \\r
+          && cp -r acme.sh acmetest/\r
+    - name: Run acmetest\r
+      run: |\r
+          cd ../acmetest \\r
+          && sudo --preserve-env ./letest.sh\r
+\r
+\r
index 976e5373d80189b941e9825d63a4de76c303d10f..c1ea1cd2258e8abc7fba0f0dcd105eb34eb2a4ea 100644 (file)
@@ -4,14 +4,14 @@ on:
     branches:\r
       - '*'\r
     paths:\r
-      - '**.sh'\r
-      - '**.yml'\r
+      - '*.sh'\r
+      - '.github/workflows/PebbleStrict.yml'\r
   pull_request:\r
     branches:\r
       - dev\r
     paths:\r
-      - '**.sh'\r
-      - '**.yml'\r
+      - '*.sh'\r
+      - '.github/workflows/PebbleStrict.yml'\r
 \r
 jobs:\r
   PebbleStrict:\r
@@ -19,7 +19,7 @@ jobs:
     env:\r
       TestingDomain: example.com\r
       TestingAltDomains: www.example.com\r
-      ACME_DIRECTORY: https://localhost:14000/dir\r
+      TEST_ACME_Server: https://localhost:14000/dir\r
       HTTPS_INSECURE: 1\r
       Le_HTTPPort: 5002\r
       TEST_LOCAL: 1\r
@@ -35,5 +35,28 @@ jobs:
       run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4\r
     - name: Clone acmetest\r
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
+    - name: Run acmetest\r
+      run: cd ../acmetest && ./letest.sh\r
+\r
+  PebbleStrict_IPCert:\r
+    runs-on: ubuntu-latest\r
+    env:\r
+      TestingDomain: 10.30.50.1\r
+      ACME_DIRECTORY: https://localhost:14000/dir\r
+      HTTPS_INSECURE: 1\r
+      Le_HTTPPort: 5002\r
+      Le_TLSPort: 5001\r
+      TEST_LOCAL: 1\r
+      TEST_CA: "Pebble Intermediate CA"\r
+      TEST_IPCERT: 1\r
+\r
+    steps:\r
+    - uses: actions/checkout@v2\r
+    - name: Install tools\r
+      run: sudo apt-get install -y socat\r
+    - name: Run Pebble\r
+      run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker-compose up -d\r
+    - name: Clone acmetest\r
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
     - name: Run acmetest\r
       run: cd ../acmetest && ./letest.sh
\ No newline at end of file
diff --git a/.github/workflows/Solaris.yml b/.github/workflows/Solaris.yml
new file mode 100644 (file)
index 0000000..4df1009
--- /dev/null
@@ -0,0 +1,61 @@
+name: Solaris\r
+on:\r
+  push:\r
+    branches:\r
+      - '*'\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Solaris.yml'\r
+\r
+  pull_request:\r
+    branches:\r
+      - dev\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Solaris.yml'\r
+\r
+\r
+jobs:\r
+  Solaris:\r
+    strategy:\r
+      matrix:\r
+        include:\r
+         - TEST_ACME_Server: "LetsEncrypt.org_test"\r
+           CA_ECDSA: ""\r
+           CA: ""\r
+           CA_EMAIL: ""\r
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1\r
+         - TEST_ACME_Server: "ZeroSSL.com"\r
+           CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"\r
+           CA: "ZeroSSL RSA Domain Secure Site CA"\r
+           CA_EMAIL: "githubtest@acme.sh"\r
+           TEST_PREFERRED_CHAIN: ""\r
+    runs-on: macos-10.15\r
+    env:\r
+      TEST_LOCAL: 1\r
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r
+      CA: ${{ matrix.CA }}\r
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r
+    steps:\r
+    - uses: actions/checkout@v2\r
+    - uses: vmactions/cf-tunnel@v0.0.3\r
+      id: tunnel\r
+      with:\r
+        protocol: http\r
+        port: 8080\r
+    - name: Set envs\r
+      run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV\r
+    - name: Clone acmetest\r
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
+    - uses: vmactions/solaris-vm@v0.0.3\r
+      with:\r
+        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'\r
+        nat: |\r
+          "8080": "80"\r
+        prepare: pkgutil -y -i socat curl\r
+        run: |\r
+          cd ../acmetest \\r
+          && ./letest.sh\r
+\r
diff --git a/.github/workflows/Ubuntu.yml b/.github/workflows/Ubuntu.yml
new file mode 100644 (file)
index 0000000..28b0654
--- /dev/null
@@ -0,0 +1,57 @@
+name: Ubuntu\r
+on:\r
+  push:\r
+    branches:\r
+      - '*'\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Ubuntu.yml'\r
+\r
+  pull_request:\r
+    branches:\r
+      - dev\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Ubuntu.yml'\r
+\r
+\r
+jobs:\r
+  Ubuntu:\r
+    strategy:\r
+      matrix:\r
+        include:\r
+         - TEST_ACME_Server: "LetsEncrypt.org_test"\r
+           CA_ECDSA: ""\r
+           CA: ""\r
+           CA_EMAIL: ""\r
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1\r
+         - TEST_ACME_Server: "ZeroSSL.com"\r
+           CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"\r
+           CA: "ZeroSSL RSA Domain Secure Site CA"\r
+           CA_EMAIL: "githubtest@acme.sh"\r
+           TEST_PREFERRED_CHAIN: ""\r
+\r
+    runs-on: ubuntu-latest\r
+    env:\r
+      TEST_LOCAL: 1\r
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r
+      CA: ${{ matrix.CA }}\r
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r
+      NO_ECC_384: ${{ matrix.NO_ECC_384 }}\r
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r
+    steps:\r
+    - uses: actions/checkout@v2\r
+    - name: Install tools\r
+      run: sudo apt-get install -y socat\r
+    - name: Clone acmetest\r
+      run: |\r
+          cd .. \\r
+          && git clone https://github.com/acmesh-official/acmetest.git \\r
+          && cp -r acme.sh acmetest/\r
+    - name: Run acmetest\r
+      run: |\r
+          cd ../acmetest \\r
+          && sudo --preserve-env ./letest.sh\r
+\r
+\r
diff --git a/.github/workflows/Windows.yml b/.github/workflows/Windows.yml
new file mode 100644 (file)
index 0000000..2d7eeea
--- /dev/null
@@ -0,0 +1,73 @@
+name: Windows\r
+on:\r
+  push:\r
+    branches:\r
+      - '*'\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Windows.yml'\r
+\r
+  pull_request:\r
+    branches:\r
+      - dev\r
+    paths:\r
+      - '*.sh'\r
+      - '.github/workflows/Windows.yml'\r
+\r
+\r
+jobs:\r
+  Windows:\r
+    strategy:\r
+      matrix:\r
+        include:\r
+         - TEST_ACME_Server: "LetsEncrypt.org_test"\r
+           CA_ECDSA: ""\r
+           CA: ""\r
+           CA_EMAIL: ""\r
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1\r
+         - TEST_ACME_Server: "ZeroSSL.com"\r
+           CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"\r
+           CA: "ZeroSSL RSA Domain Secure Site CA"\r
+           CA_EMAIL: "githubtest@acme.sh"\r
+           TEST_PREFERRED_CHAIN: ""\r
+    runs-on: windows-latest\r
+    env:\r
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}\r
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}\r
+      CA: ${{ matrix.CA }}\r
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}\r
+      TEST_LOCAL: 1\r
+      #The 80 port is used by Windows server, we have to use a custom port, tunnel will also use this port.\r
+      Le_HTTPPort: 8888\r
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}\r
+    steps:\r
+    - name: Set git to use LF\r
+      run: |\r
+          git config --global core.autocrlf false\r
+    - uses: actions/checkout@v2\r
+    - name: Install cygwin base packages with chocolatey\r
+      run: |\r
+          choco config get cacheLocation\r
+          choco install --no-progress cygwin\r
+      shell: cmd\r
+    - name: Install cygwin additional packages\r
+      run: |\r
+          C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git,xxd\r
+      shell: cmd\r
+    - name: Set ENV\r
+      shell: cmd\r
+      run: |\r
+          echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin;%PATH% >> %GITHUB_ENV%\r
+    - name: Check ENV\r
+      shell: cmd\r
+      run: |\r
+          echo "PATH=%PATH%"\r
+    - name: Clone acmetest\r
+      shell: cmd\r
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/\r
+    - name: Run acmetest\r
+      shell: cmd\r
+      run: cd ../acmetest && bash.exe -c ./letest.sh\r
+\r
+\r
+\r
index 238fde3a175df23319dda3918e321ffe820705df..0c3aec0ad43da469564845397298fc82b5662c72 100644 (file)
@@ -6,6 +6,11 @@ on:
       - '*'\r
     tags:\r
       - '*'\r
+    paths:\r
+      - '**.sh'\r
+      - "Dockerfile"\r
+      - '.github/workflows/dockerhub.yml'\r
+\r
     \r
 jobs:\r
   CheckToken:\r
index b22a2fd8ae36ee903b8f2ae31e35eeee52835301..940a187dc3c4173c919d88fb97f70c703ae38d14 100644 (file)
@@ -5,13 +5,13 @@ on:
       - '*'\r
     paths:\r
       - '**.sh'\r
-      - '**.yml'\r
+      - '.github/workflows/shellcheck.yml'\r
   pull_request:\r
     branches:\r
       - dev\r
     paths:\r
       - '**.sh'\r
-      - '**.yml'\r
+      - '.github/workflows/shellcheck.yml'\r
 \r
 jobs:\r
   ShellCheck:\r
index 4618efaf7090a315a0f618b375f1226e5389dd3d..fb842c83dc7eed8f0e1aa97ba362ef1bb9091166 100644 (file)
@@ -1,7 +1,6 @@
 FROM alpine:3.12
 
-RUN apk update -f \
-  && apk --no-cache add -f \
+RUN apk --no-cache add -f \
   openssl \
   openssh-client \
   coreutils \
@@ -12,8 +11,7 @@ RUN apk update -f \
   tzdata \
   oath-toolkit-oathtool \
   tar \
-  libidn \
-  && rm -rf /var/cache/apk/*
+  libidn
 
 ENV LE_CONFIG_HOME /acme.sh
 
@@ -22,7 +20,7 @@ ARG AUTO_UPGRADE=1
 ENV AUTO_UPGRADE $AUTO_UPGRADE
 
 #Install
-ADD ./ /install_acme.sh/
+COPY ./ /install_acme.sh/
 RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/
 
 
@@ -57,6 +55,7 @@ RUN for verb in help \
   deactivate-account \
   set-notify \
   set-default-ca \
+  set-default-chain \
   ; do \
     printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \
   ; done
index edd6442f5e39491c095604ef94e1445bc9e14c23..91a1898531b10f9ef2955e5c9b82adbc0ee3652d 100644 (file)
--- a/README.md
+++ b/README.md
@@ -1,6 +1,11 @@
 # An ACME Shell script: acme.sh 
 
-![LetsEncrypt](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)
+[![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)
+[![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)
+[![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)
+[![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)
+[![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)
+
 ![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg)
 ![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg)
 ![DockerHub](https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg)
 
 - An ACME protocol client written purely in Shell (Unix shell) language.
 - Full ACME protocol implementation.
-- Support ACME v1 and ACME v2
-- Support ACME v2 wildcard certs
+- Support ECDSA certs
+- Support SAN and wildcard certs
 - Simple, powerful and very easy to use. You only need 3 minutes to learn it.
 - Bash, dash and sh compatible.
-- Purely written in Shell with no dependencies on python or the official Let's Encrypt client.
+- Purely written in Shell with no dependencies on python.
 - Just one script to issue, renew and install your certificates automatically.
 - DOES NOT require `root/sudoer` access.
-- Docker friendly
-- IPv6 support
+- Docker ready
+- IPv6 ready
 - Cron job notifications for renewal or error etc.
 
-It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt.
+It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates.
 
 Wiki: https://github.com/acmesh-official/acme.sh/wiki
 
@@ -57,37 +62,39 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 
 | NO | Status| Platform|
 |----|-------|---------|
-|1|[![MacOS](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Mac OSX
-|2|[![Windows](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Windows (cygwin with curl, openssl and crontab included)
-|3|[![FreeBSD](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|FreeBSD
-|4|[![Solaris](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Solaris
-|5|[![Ubuntu](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)| Ubuntu
-|6|[![](https://acmesh-official.github.io/acmetest/status/pfsense.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|pfsense
-|7|[![](https://acmesh-official.github.io/acmetest/status/openbsd.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|OpenBSD
-|8|[![](https://acmesh-official.github.io/acmetest/status/debian-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Debian
-|9|[![](https://acmesh-official.github.io/acmetest/status/centos-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|CentOS
-|10|[![](https://acmesh-official.github.io/acmetest/status/opensuse-leap-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|openSUSE
-|11|[![](https://acmesh-official.github.io/acmetest/status/alpine-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Alpine Linux (with curl)
-|12|[![](https://acmesh-official.github.io/acmetest/status/archlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Archlinux
-|13|[![](https://acmesh-official.github.io/acmetest/status/fedora-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|fedora
-|14|[![](https://acmesh-official.github.io/acmetest/status/kalilinux-kali.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Kali Linux
-|15|[![](https://acmesh-official.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Oracle Linux
-|16|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
-|17|-----| Cloud Linux  https://github.com/acmesh-official/acme.sh/issues/111
-|18|[![](https://acmesh-official.github.io/acmetest/status/mageia.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Mageia
-|19|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
-|20|[![](https://acmesh-official.github.io/acmetest/status/gentoo-stage3-amd64.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Gentoo Linux
-|21|[![](https://acmesh-official.github.io/acmetest/status/clearlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|ClearLinux
-
-For all build statuses, check our [weekly build project](https://github.com/acmesh-official/acmetest):
+|1|[![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)|Mac OSX
+|2|[![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)|Windows (cygwin with curl, openssl and crontab included)
+|3|[![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)|FreeBSD
+|4|[![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)|Solaris
+|5|[![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)| Ubuntu
+|6|NA|pfsense
+|7|NA|OpenBSD
+|8|[![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
+|9|[![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
+|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)|openSUSE
+|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)|Alpine Linux (with curl)
+|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)|Archlinux
+|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)|fedora
+|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)|Kali Linux
+|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)|Oracle Linux
+|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)|Mageia
+|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)|Gentoo 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)|ClearLinux
+|19|-----| Cloud Linux  https://github.com/acmesh-official/acme.sh/issues/111
+|20|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
+|21|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
+
+
+Check our [testing project](https://github.com/acmesh-official/acmetest):
 
 https://github.com/acmesh-official/acmetest
 
 # Supported CA
 
-- Letsencrypt.org CA(default)
-- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)
+- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)(default)
+- Letsencrypt.org CA
 - [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA)
+- [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA)
 - [Pebble strict Mode](https://github.com/letsencrypt/pebble)
 - Any other [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA
 
@@ -469,7 +476,7 @@ TODO:
 
 ### Code Contributors
 
-This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
+This project exists thanks to all the people who contribute.
 <a href="https://github.com/acmesh-official/acme.sh/graphs/contributors"><img src="https://opencollective.com/acmesh/contributors.svg?width=890&button=false" /></a>
 
 ### Financial Contributors
diff --git a/acme.sh b/acme.sh
index a1ad41951e8249d33b479db503ca5f7ce1e6bf59..d2e8b04d81f142be831eff8df0fb16a23f169ca7 100755 (executable)
--- a/acme.sh
+++ b/acme.sh
@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-VER=2.8.9
+VER=3.0.2
 
 PROJECT_NAME="acme.sh"
 
@@ -20,8 +20,7 @@ _SUB_FOLDER_DEPLOY="deploy"
 
 _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY"
 
-LETSENCRYPT_CA_V1="https://acme-v01.api.letsencrypt.org/directory"
-LETSENCRYPT_STAGING_CA_V1="https://acme-staging.api.letsencrypt.org/directory"
+CA_LETSENCRYPT_V1="https://acme-v01.api.letsencrypt.org/directory"
 
 CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory"
 CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory"
@@ -32,18 +31,22 @@ CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory"
 CA_ZEROSSL="https://acme.zerossl.com/v2/DV90"
 _ZERO_EAB_ENDPOINT="http://api.zerossl.com/acme/eab-credentials-email"
 
-DEFAULT_CA=$CA_LETSENCRYPT_V2
+CA_SSLCOM_RSA="https://acme.ssl.com/sslcom-dv-rsa"
+CA_SSLCOM_ECC="https://acme.ssl.com/sslcom-dv-ecc"
+
+DEFAULT_CA=$CA_ZEROSSL
 DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST
 
 CA_NAMES="
+ZeroSSL.com,zerossl
 LetsEncrypt.org,letsencrypt
 LetsEncrypt.org_test,letsencrypt_test,letsencrypttest
 BuyPass.com,buypass
 BuyPass.com_test,buypass_test,buypasstest
-ZeroSSL.com,zerossl
+SSL.com,sslcom
 "
 
-CA_SERVERS="$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_ZEROSSL"
+CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_SSLCOM_RSA"
 
 DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)"
 
@@ -56,6 +59,9 @@ VTYPE_HTTP="http-01"
 VTYPE_DNS="dns-01"
 VTYPE_ALPN="tls-alpn-01"
 
+ID_TYPE_DNS="dns"
+ID_TYPE_IP="ip"
+
 LOCAL_ANY_ADDRESS="0.0.0.0"
 
 DEFAULT_RENEW=60
@@ -102,6 +108,8 @@ DEBUG_LEVEL_NONE=0
 
 DOH_CLOUDFLARE=1
 DOH_GOOGLE=2
+DOH_ALI=3
+DOH_DP=4
 
 HIDDEN_VALUE="[hidden](please add '--output-insecure' to see this value)"
 
@@ -156,6 +164,8 @@ _REVOKE_WIKI="https://github.com/acmesh-official/acme.sh/wiki/revokecert"
 
 _ZEROSSL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA"
 
+_SSLCOM_WIKI="https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA"
+
 _SERVER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Server"
 
 _PREFERRED_CHAIN_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Preferred-Chain"
@@ -419,19 +429,27 @@ _secure_debug3() {
 }
 
 _upper_case() {
-  # shellcheck disable=SC2018,SC2019
-  tr 'a-z' 'A-Z'
+  if _is_solaris; then
+    tr '[:lower:]' '[:upper:]'
+  else
+    # shellcheck disable=SC2018,SC2019
+    tr 'a-z' 'A-Z'
+  fi
 }
 
 _lower_case() {
-  # shellcheck disable=SC2018,SC2019
-  tr 'A-Z' 'a-z'
+  if _is_solaris; then
+    tr '[:upper:]' '[:lower:]'
+  else
+    # shellcheck disable=SC2018,SC2019
+    tr 'A-Z' 'a-z'
+  fi
 }
 
 _startswith() {
   _str="$1"
   _sub="$2"
-  echo "$_str" | grep "^$_sub" >/dev/null 2>&1
+  echo "$_str" | grep -- "^$_sub" >/dev/null 2>&1
 }
 
 _endswith() {
@@ -562,8 +580,16 @@ if _exists xargs && [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then
 fi
 
 _h2b() {
-  if _exists xxd && xxd -r -p 2>/dev/null; then
-    return
+  if _exists xxd; then
+    if _contains "$(xxd --help 2>&1)" "assumes -c30"; then
+      if xxd -r -p -c 9999 2>/dev/null; then
+        return
+      fi
+    else
+      if xxd -r -p 2>/dev/null; then
+        return
+      fi
+    fi
   fi
 
   hex=$(cat)
@@ -1124,7 +1150,7 @@ _createkey() {
 
   if _isEccKey "$length"; then
     _debug "Using ec name: $eccname"
-    if _opkey="$(${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -genkey 2>/dev/null)"; then
+    if _opkey="$(${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -noout -genkey 2>/dev/null)"; then
       echo "$_opkey" >"$f"
     else
       _err "error ecc key name: $eccname"
@@ -1132,7 +1158,11 @@ _createkey() {
     fi
   else
     _debug "Using RSA: $length"
-    if _opkey="$(${ACME_OPENSSL_BIN:-openssl} genrsa "$length" 2>/dev/null)"; then
+    __traditional=""
+    if _contains "$(${ACME_OPENSSL_BIN:-openssl} help genrsa 2>&1)" "-traditional"; then
+      __traditional="-traditional"
+    fi
+    if _opkey="$(${ACME_OPENSSL_BIN:-openssl} genrsa $__traditional "$length" 2>/dev/null)"; then
       echo "$_opkey" >"$f"
     else
       _err "error rsa key: $length"
@@ -1199,23 +1229,31 @@ _createcsr() {
   _debug2 csr "$csr"
   _debug2 csrconf "$csrconf"
 
-  printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment" >"$csrconf"
+  printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\n\n" >"$csrconf"
 
   if [ "$acmeValidationv1" ]; then
     domainlist="$(_idn "$domainlist")"
-    printf -- "\nsubjectAltName=DNS:$domainlist" >>"$csrconf"
+    _debug2 domainlist "$domainlist"
+    alt=""
+    for dl in $(echo "$domainlist" | tr "," ' '); do
+      if [ "$alt" ]; then
+        alt="$alt,$(_getIdType "$dl" | _upper_case):$dl"
+      else
+        alt="$(_getIdType "$dl" | _upper_case):$dl"
+      fi
+    done
+    printf -- "\nsubjectAltName=$alt" >>"$csrconf"
   elif [ -z "$domainlist" ] || [ "$domainlist" = "$NO_VALUE" ]; then
     #single domain
     _info "Single domain" "$domain"
-    printf -- "\nsubjectAltName=DNS:$(_idn "$domain")" >>"$csrconf"
+    printf -- "\nsubjectAltName=$(_getIdType "$domain" | _upper_case):$(_idn "$domain")" >>"$csrconf"
   else
     domainlist="$(_idn "$domainlist")"
     _debug2 domainlist "$domainlist"
-    if _contains "$domainlist" ","; then
-      alt="DNS:$(_idn "$domain"),DNS:$(echo "$domainlist" | sed "s/,,/,/g" | sed "s/,/,DNS:/g")"
-    else
-      alt="DNS:$(_idn "$domain"),DNS:$domainlist"
-    fi
+    alt="$(_getIdType "$domain" | _upper_case):$domain"
+    for dl in $(echo "$domainlist" | tr "," ' '); do
+      alt="$alt,$(_getIdType "$dl" | _upper_case):$dl"
+    done
     #multi
     _info "Multi domain" "$alt"
     printf -- "\nsubjectAltName=$alt" >>"$csrconf"
@@ -1751,7 +1789,7 @@ _inithttp() {
     if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then
       _ACME_CURL="$_ACME_CURL -L "
     fi
-    if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
+    if [ "$DEBUG" ] && [ "$DEBUG" -ge 2 ]; then
       _CURL_DUMP="$(_mktemp)"
       _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
     fi
@@ -1791,6 +1829,8 @@ _inithttp() {
 
 }
 
+_HTTP_MAX_RETRY=8
+
 # body  url [needbase64] [POST|PUT|DELETE] [ContentType]
 _post() {
   body="$1"
@@ -1798,6 +1838,33 @@ _post() {
   needbase64="$3"
   httpmethod="$4"
   _postContentType="$5"
+  _sleep_retry_sec=1
+  _http_retry_times=0
+  _hcode=0
+  while [ "${_http_retry_times}" -le "$_HTTP_MAX_RETRY" ]; do
+    [ "$_http_retry_times" = "$_HTTP_MAX_RETRY" ]
+    _lastHCode="$?"
+    _debug "Retrying post"
+    _post_impl "$body" "$_post_url" "$needbase64" "$httpmethod" "$_postContentType" "$_lastHCode"
+    _hcode="$?"
+    _debug _hcode "$_hcode"
+    if [ "$_hcode" = "0" ]; then
+      break
+    fi
+    _http_retry_times=$(_math $_http_retry_times + 1)
+    _sleep $_sleep_retry_sec
+  done
+  return $_hcode
+}
+
+# body  url [needbase64] [POST|PUT|DELETE] [ContentType] [displayError]
+_post_impl() {
+  body="$1"
+  _post_url="$2"
+  needbase64="$3"
+  httpmethod="$4"
+  _postContentType="$5"
+  displayError="$6"
 
   if [ -z "$httpmethod" ]; then
     httpmethod="POST"
@@ -1849,7 +1916,9 @@ _post() {
     fi
     _ret="$?"
     if [ "$_ret" != "0" ]; then
-      _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
+      if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
+        _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
+      fi
       if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
         _err "Here is the curl dump log:"
         _err "$(cat "$_CURL_DUMP")"
@@ -1905,7 +1974,9 @@ _post() {
       _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later."
     fi
     if [ "$_ret" != "0" ]; then
-      _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
+      if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
+        _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
+      fi
     fi
     _sed_i "s/^ *//g" "$HTTP_HEADER"
   else
@@ -1919,13 +1990,38 @@ _post() {
 
 # url getheader timeout
 _get() {
+  url="$1"
+  onlyheader="$2"
+  t="$3"
+  _sleep_retry_sec=1
+  _http_retry_times=0
+  _hcode=0
+  while [ "${_http_retry_times}" -le "$_HTTP_MAX_RETRY" ]; do
+    [ "$_http_retry_times" = "$_HTTP_MAX_RETRY" ]
+    _lastHCode="$?"
+    _debug "Retrying GET"
+    _get_impl "$url" "$onlyheader" "$t" "$_lastHCode"
+    _hcode="$?"
+    _debug _hcode "$_hcode"
+    if [ "$_hcode" = "0" ]; then
+      break
+    fi
+    _http_retry_times=$(_math $_http_retry_times + 1)
+    _sleep $_sleep_retry_sec
+  done
+  return $_hcode
+}
+
+# url getheader timeout displayError
+_get_impl() {
   _debug GET
   url="$1"
   onlyheader="$2"
   t="$3"
+  displayError="$4"
   _debug url "$url"
   _debug "timeout=$t"
-
+  _debug "displayError" "$displayError"
   _inithttp
 
   if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
@@ -1944,7 +2040,9 @@ _get() {
     fi
     ret=$?
     if [ "$ret" != "0" ]; then
-      _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
+      if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
+        _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
+      fi
       if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
         _err "Here is the curl dump log:"
         _err "$(cat "$_CURL_DUMP")"
@@ -1970,7 +2068,9 @@ _get() {
       _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later."
     fi
     if [ "$ret" != "0" ]; then
-      _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret"
+      if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
+        _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret"
+      fi
     fi
   else
     ret=$?
@@ -2026,7 +2126,7 @@ _send_signed_request() {
         if _post "" "$nonceurl" "" "HEAD" "$__request_conent_type" >/dev/null; then
           _headers="$(cat "$HTTP_HEADER")"
           _debug2 _headers "$_headers"
-          _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
+          _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2 | cut -d , -f 1)"
         fi
       fi
       if [ -z "$_CACHED_NONCE" ]; then
@@ -2058,17 +2158,15 @@ _send_signed_request() {
       _sleep 2
       continue
     fi
-    if [ "$ACME_VERSION" = "2" ]; then
-      if [ "$url" = "$ACME_NEW_ACCOUNT" ]; then
-        protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
-      elif [ "$url" = "$ACME_REVOKE_CERT" ] && [ "$keyfile" != "$ACCOUNT_KEY_PATH" ]; then
-        protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
-      else
-        protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}'
-      fi
-    else
+
+    if [ "$url" = "$ACME_NEW_ACCOUNT" ]; then
       protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
+    elif [ "$url" = "$ACME_REVOKE_CERT" ] && [ "$keyfile" != "$ACCOUNT_KEY_PATH" ]; then
+      protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}'
+    else
+      protected="$JWK_HEADERPLACE_PART1$nonce\", \"url\": \"${url}$JWK_HEADERPLACE_PART2, \"kid\": \"${ACCOUNT_URL}\""'}'
     fi
+
     _debug3 protected "$protected"
 
     protected64="$(printf "%s" "$protected" | _base64 | _url_replace)"
@@ -2106,7 +2204,7 @@ _send_signed_request() {
     fi
     _debug2 response "$response"
 
-    _CACHED_NONCE="$(echo "$responseHeaders" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)"
+    _CACHED_NONCE="$(echo "$responseHeaders" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2 | cut -d , -f 1)"
 
     if ! _startswith "$code" "2"; then
       _body="$response"
@@ -2121,6 +2219,12 @@ _send_signed_request() {
         _sleep $_sleep_retry_sec
         continue
       fi
+      if _contains "$_body" "The Replay Nonce is not recognized"; then
+        _info "The replay Nonce is not valid, let's get a new one, Sleeping $_sleep_retry_sec seconds."
+        _CACHED_NONCE=""
+        _sleep $_sleep_retry_sec
+        continue
+      fi
     fi
     return 0
   done
@@ -2248,7 +2352,7 @@ _getdeployconf() {
     return 0 # do nothing
   fi
   _saved=$(_readdomainconf "SAVED_$_rac_key")
-  eval "export $_rac_key=\"$_saved\""
+  eval "export $_rac_key=\"\$_saved\""
 }
 
 #_saveaccountconf  key  value  base64encode
@@ -2279,6 +2383,13 @@ _clearaccountconf() {
   _clear_conf "$ACCOUNT_CONF_PATH" "$1"
 }
 
+#key
+_clearaccountconf_mutable() {
+  _clearaccountconf "SAVED_$1"
+  #remove later
+  _clearaccountconf "$1"
+}
+
 #_savecaconf  key  value
 _savecaconf() {
   _save_conf "$CA_CONF" "$1" "$2"
@@ -2332,7 +2443,7 @@ _startserver() {
 echo 'HTTP/1.0 200 OK'; \
 echo 'Content-Length\: $_content_len'; \
 echo ''; \
-printf -- '$content';" &
+printf '%s' '$content';" &
   serverproc="$!"
 }
 
@@ -2507,76 +2618,56 @@ __initHome() {
   fi
 }
 
+_clearAPI() {
+  ACME_NEW_ACCOUNT=""
+  ACME_KEY_CHANGE=""
+  ACME_NEW_AUTHZ=""
+  ACME_NEW_ORDER=""
+  ACME_REVOKE_CERT=""
+  ACME_NEW_NONCE=""
+  ACME_AGREEMENT=""
+}
+
 #server
 _initAPI() {
   _api_server="${1:-$ACME_DIRECTORY}"
   _debug "_init api for server: $_api_server"
 
-  if [ -z "$ACME_NEW_ACCOUNT" ]; then
+  MAX_API_RETRY_TIMES=10
+  _sleep_retry_sec=10
+  _request_retry_times=0
+  while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do
+    _request_retry_times=$(_math "$_request_retry_times" + 1)
     response=$(_get "$_api_server")
     if [ "$?" != "0" ]; then
       _debug2 "response" "$response"
-      _err "Can not init api."
-      return 1
+      _info "Can not init api for: $_api_server."
+      _info "Sleep $_sleep_retry_sec and retry."
+      _sleep "$_sleep_retry_sec"
+      continue
     fi
     response=$(echo "$response" | _json_decode)
     _debug2 "response" "$response"
 
-    ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'key-change" *: *"[^"]*"' | cut -d '"' -f 3)
-    if [ -z "$ACME_KEY_CHANGE" ]; then
-      ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3)
-    fi
+    ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'keyChange" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_KEY_CHANGE
 
-    ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'new-authz" *: *"[^"]*"' | cut -d '"' -f 3)
-    if [ -z "$ACME_NEW_AUTHZ" ]; then
-      ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3)
-    fi
+    ACME_NEW_AUTHZ=$(echo "$response" | _egrep_o 'newAuthz" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_NEW_AUTHZ
 
-    ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-cert" *: *"[^"]*"' | cut -d '"' -f 3)
-    ACME_NEW_ORDER_RES="new-cert"
-    if [ -z "$ACME_NEW_ORDER" ]; then
-      ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'new-order" *: *"[^"]*"' | cut -d '"' -f 3)
-      ACME_NEW_ORDER_RES="new-order"
-      if [ -z "$ACME_NEW_ORDER" ]; then
-        ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3)
-      fi
-    fi
+    ACME_NEW_ORDER=$(echo "$response" | _egrep_o 'newOrder" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_NEW_ORDER
-    export ACME_NEW_ORDER_RES
-
-    ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-reg" *: *"[^"]*"' | cut -d '"' -f 3)
-    ACME_NEW_ACCOUNT_RES="new-reg"
-    if [ -z "$ACME_NEW_ACCOUNT" ]; then
-      ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'new-account" *: *"[^"]*"' | cut -d '"' -f 3)
-      ACME_NEW_ACCOUNT_RES="new-account"
-      if [ -z "$ACME_NEW_ACCOUNT" ]; then
-        ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3)
-        if [ "$ACME_NEW_ACCOUNT" ]; then
-          export ACME_VERSION=2
-        fi
-      fi
-    fi
+
+    ACME_NEW_ACCOUNT=$(echo "$response" | _egrep_o 'newAccount" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_NEW_ACCOUNT
-    export ACME_NEW_ACCOUNT_RES
 
-    ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revoke-cert" *: *"[^"]*"' | cut -d '"' -f 3)
-    if [ -z "$ACME_REVOKE_CERT" ]; then
-      ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3)
-    fi
+    ACME_REVOKE_CERT=$(echo "$response" | _egrep_o 'revokeCert" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_REVOKE_CERT
 
-    ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'new-nonce" *: *"[^"]*"' | cut -d '"' -f 3)
-    if [ -z "$ACME_NEW_NONCE" ]; then
-      ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3)
-    fi
+    ACME_NEW_NONCE=$(echo "$response" | _egrep_o 'newNonce" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_NEW_NONCE
 
-    ACME_AGREEMENT=$(echo "$response" | _egrep_o 'terms-of-service" *: *"[^"]*"' | cut -d '"' -f 3)
-    if [ -z "$ACME_AGREEMENT" ]; then
-      ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3)
-    fi
+    ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3)
     export ACME_AGREEMENT
 
     _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE"
@@ -2586,9 +2677,17 @@ _initAPI() {
     _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT"
     _debug "ACME_AGREEMENT" "$ACME_AGREEMENT"
     _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE"
-    _debug "ACME_VERSION" "$ACME_VERSION"
-
+    if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then
+      return 0
+    fi
+    _info "Sleep $_sleep_retry_sec and retry."
+    _sleep "$_sleep_retry_sec"
+  done
+  if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then
+    return 0
   fi
+  _err "Can not init api, for $_api_server"
+  return 1
 }
 
 #[domain]  [keylength or isEcc flag]
@@ -2632,15 +2731,44 @@ _initpath() {
   _ACME_SERVER_HOST="$(echo "$ACME_DIRECTORY" | cut -d : -f 2 | tr -s / | cut -d / -f 2)"
   _debug2 "_ACME_SERVER_HOST" "$_ACME_SERVER_HOST"
 
-  CA_DIR="$CA_HOME/$_ACME_SERVER_HOST"
+  _ACME_SERVER_PATH="$(echo "$ACME_DIRECTORY" | cut -d : -f 2- | tr -s / | cut -d / -f 3-)"
+  _debug2 "_ACME_SERVER_PATH" "$_ACME_SERVER_PATH"
 
+  CA_DIR="$CA_HOME/$_ACME_SERVER_HOST/$_ACME_SERVER_PATH"
   _DEFAULT_CA_CONF="$CA_DIR/ca.conf"
-
   if [ -z "$CA_CONF" ]; then
     CA_CONF="$_DEFAULT_CA_CONF"
   fi
   _debug3 CA_CONF "$CA_CONF"
 
+  _OLD_CADIR="$CA_HOME/$_ACME_SERVER_HOST"
+  _OLD_ACCOUNT_KEY="$_OLD_CADIR/account.key"
+  _OLD_ACCOUNT_JSON="$_OLD_CADIR/account.json"
+  _OLD_CA_CONF="$_OLD_CADIR/ca.conf"
+
+  _DEFAULT_ACCOUNT_KEY_PATH="$CA_DIR/account.key"
+  _DEFAULT_ACCOUNT_JSON_PATH="$CA_DIR/account.json"
+  if [ -z "$ACCOUNT_KEY_PATH" ]; then
+    ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH"
+    if [ -f "$_OLD_ACCOUNT_KEY" ] && ! [ -f "$ACCOUNT_KEY_PATH" ]; then
+      mkdir -p "$CA_DIR"
+      mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
+    fi
+  fi
+
+  if [ -z "$ACCOUNT_JSON_PATH" ]; then
+    ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH"
+    if [ -f "$_OLD_ACCOUNT_JSON" ] && ! [ -f "$ACCOUNT_JSON_PATH" ]; then
+      mkdir -p "$CA_DIR"
+      mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
+    fi
+  fi
+
+  if [ -f "$_OLD_CA_CONF" ] && ! [ -f "$CA_CONF" ]; then
+    mkdir -p "$CA_DIR"
+    mv "$_OLD_CA_CONF" "$CA_CONF"
+  fi
+
   if [ -f "$CA_CONF" ]; then
     . "$CA_CONF"
   fi
@@ -2661,19 +2789,6 @@ _initpath() {
     HTTP_HEADER="$LE_CONFIG_HOME/http.header"
   fi
 
-  _OLD_ACCOUNT_KEY="$LE_WORKING_DIR/account.key"
-  _OLD_ACCOUNT_JSON="$LE_WORKING_DIR/account.json"
-
-  _DEFAULT_ACCOUNT_KEY_PATH="$CA_DIR/account.key"
-  _DEFAULT_ACCOUNT_JSON_PATH="$CA_DIR/account.json"
-  if [ -z "$ACCOUNT_KEY_PATH" ]; then
-    ACCOUNT_KEY_PATH="$_DEFAULT_ACCOUNT_KEY_PATH"
-  fi
-
-  if [ -z "$ACCOUNT_JSON_PATH" ]; then
-    ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH"
-  fi
-
   _DEFAULT_CERT_HOME="$LE_CONFIG_HOME"
   if [ -z "$CERT_HOME" ]; then
     CERT_HOME="$_DEFAULT_CERT_HOME"
@@ -3071,6 +3186,11 @@ _checkConf() {
       _debug "Try include files"
       for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do
         _debug "check included $included"
+        if ! _startswith "$included" "/" && _exists dirname; then
+          _relpath="$(dirname "$2")"
+          _debug "_relpath" "$_relpath"
+          included="$_relpath/$included"
+        fi
         if _checkConf "$1" "$included"; then
           return 0
         fi
@@ -3281,6 +3401,8 @@ _on_before_issue() {
   if [ "$_chk_pre_hook" ]; then
     _info "Run pre hook:'$_chk_pre_hook'"
     if ! (
+      export Le_Domain="$_chk_main_domain"
+      export Le_Alt="$_chk_alt_domains"
       cd "$DOMAIN_PATH" && eval "$_chk_pre_hook"
     ); then
       _err "Error when run pre hook."
@@ -3342,7 +3464,7 @@ _on_before_issue() {
       _netprc="$(_ss "$_checkport" | grep "$_checkport")"
       netprc="$(echo "$_netprc" | grep "$_checkaddr")"
       if [ -z "$netprc" ]; then
-        netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS")"
+        netprc="$(echo "$_netprc" | grep "$LOCAL_ANY_ADDRESS:$_checkport")"
       fi
       if [ "$netprc" ]; then
         _err "$netprc"
@@ -3499,15 +3621,6 @@ _regAccount() {
   _initAPI
 
   mkdir -p "$CA_DIR"
-  if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
-    _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
-    mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
-  fi
-
-  if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
-    _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
-    mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
-  fi
 
   if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
     if ! _create_account_key "$_reg_length"; then
@@ -3531,68 +3644,66 @@ _regAccount() {
   if [ "$_email" ]; then
     _savecaconf "CA_EMAIL" "$_email"
   fi
-  if [ "$ACME_VERSION" = "2" ]; then
-    if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then
-      if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then
-        _info "No EAB credentials found for ZeroSSL, let's get one"
-        if [ -z "$_email" ]; then
-          _err "Please provide a email address for ZeroSSL account."
-          _err "See ZeroSSL usage: $_ZEROSSL_WIKI"
-          return 1
-        fi
-        _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT)
-        if [ "$?" != "0" ]; then
-          _debug2 "$_eabresp"
-          _err "Can not get EAB credentials from ZeroSSL."
-          return 1
-        fi
-        _eab_id="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
-        if [ -z "$_eab_id" ]; then
-          _err "Can not resolve _eab_id"
-          return 1
-        fi
-        _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')"
-        if [ -z "$_eab_hmac_key" ]; then
-          _err "Can not resolve _eab_hmac_key"
-          return 1
-        fi
-        _savecaconf CA_EAB_KEY_ID "$_eab_id"
-        _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
+
+  if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then
+    if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then
+      _info "No EAB credentials found for ZeroSSL, let's get one"
+      if [ -z "$_email" ]; then
+        _info "$(__green "$PROJECT_NAME is using ZeroSSL as default CA now.")"
+        _info "$(__green "Please update your account with an email address first.")"
+        _info "$(__green "$PROJECT_ENTRY --register-account -m my@example.com")"
+        _info "See: $(__green "$_ZEROSSL_WIKI")"
+        return 1
+      fi
+      _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT)
+      if [ "$?" != "0" ]; then
+        _debug2 "$_eabresp"
+        _err "Can not get EAB credentials from ZeroSSL."
+        return 1
+      fi
+      _secure_debug2 _eabresp "$_eabresp"
+      _eab_id="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')"
+      _secure_debug2 _eab_id "$_eab_id"
+      if [ -z "$_eab_id" ]; then
+        _err "Can not resolve _eab_id"
+        return 1
+      fi
+      _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')"
+      _secure_debug2 _eab_hmac_key "$_eab_hmac_key"
+      if [ -z "$_eab_hmac_key" ]; then
+        _err "Can not resolve _eab_hmac_key"
+        return 1
       fi
+      _savecaconf CA_EAB_KEY_ID "$_eab_id"
+      _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key"
     fi
-    if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
-      eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
-      _debug3 eab_protected "$eab_protected"
+  fi
+  if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then
+    eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}"
+    _debug3 eab_protected "$eab_protected"
 
-      eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace)
-      _debug3 eab_protected64 "$eab_protected64"
+    eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace)
+    _debug3 eab_protected64 "$eab_protected64"
 
-      eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace)
-      _debug3 eab_payload64 "$eab_payload64"
+    eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace)
+    _debug3 eab_payload64 "$eab_payload64"
 
-      eab_sign_t="$eab_protected64.$eab_payload64"
-      _debug3 eab_sign_t "$eab_sign_t"
+    eab_sign_t="$eab_protected64.$eab_payload64"
+    _debug3 eab_sign_t "$eab_sign_t"
 
-      key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')"
-      _debug3 key_hex "$key_hex"
+    key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 multi | _hex_dump | tr -d ' ')"
+    _debug3 key_hex "$key_hex"
 
-      eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)
-      _debug3 eab_signature "$eab_signature"
+    eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace)
+    _debug3 eab_signature "$eab_signature"
 
-      externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}"
-      _debug3 externalBinding "$externalBinding"
-    fi
-    if [ "$_email" ]; then
-      email_sg="\"contact\": [\"mailto:$_email\"], "
-    fi
-    regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}"
-  else
-    _reg_res="$ACME_NEW_ACCOUNT_RES"
-    regjson='{"resource": "'$_reg_res'", "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
-    if [ "$_email" ]; then
-      regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$_email'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}'
-    fi
+    externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}"
+    _debug3 externalBinding "$externalBinding"
+  fi
+  if [ "$_email" ]; then
+    email_sg="\"contact\": [\"mailto:$_email\"], "
   fi
+  regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}"
 
   _info "Registering account: $ACME_DIRECTORY"
 
@@ -3647,16 +3758,6 @@ _regAccount() {
 updateaccount() {
   _initpath
 
-  if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
-    _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
-    mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
-  fi
-
-  if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
-    _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
-    mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
-  fi
-
   if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
     _err "Account key is not found at: $ACCOUNT_KEY_PATH"
     return 1
@@ -3677,20 +3778,13 @@ updateaccount() {
   _initAPI
 
   _email="$(_getAccountEmail)"
-  if [ "$ACME_VERSION" = "2" ]; then
-    if [ "$ACCOUNT_EMAIL" ]; then
-      updjson='{"contact": ["mailto:'$_email'"]}'
-    else
-      updjson='{"contact": []}'
-    fi
+
+  if [ "$ACCOUNT_EMAIL" ]; then
+    updjson='{"contact": ["mailto:'$_email'"]}'
   else
-    # ACMEv1: Updates happen the same way a registration is done.
-    # https://tools.ietf.org/html/draft-ietf-acme-acme-01#section-6.3
-    _regAccount
-    return
+    updjson='{"contact": []}'
   fi
 
-  # this part handles ACMEv2 account updates.
   _send_signed_request "$_accUri" "$updjson"
 
   if [ "$code" = '200' ]; then
@@ -3706,16 +3800,6 @@ updateaccount() {
 deactivateaccount() {
   _initpath
 
-  if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then
-    _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH"
-    mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH"
-  fi
-
-  if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then
-    _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH"
-    mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH"
-  fi
-
   if [ ! -f "$ACCOUNT_KEY_PATH" ]; then
     _err "Account key is not found at: $ACCOUNT_KEY_PATH"
     return 1
@@ -3735,11 +3819,8 @@ deactivateaccount() {
   fi
   _initAPI
 
-  if [ "$ACME_VERSION" = "2" ]; then
-    _djson="{\"status\":\"deactivated\"}"
-  else
-    _djson="{\"resource\": \"reg\", \"status\":\"deactivated\"}"
-  fi
+  _djson="{\"status\":\"deactivated\"}"
+
   if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then
     _info "Deactivate account success for $_accUri."
     _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,')
@@ -3844,11 +3925,9 @@ __trigger_validation() {
   _debug2 _t_key_authz "$_t_key_authz"
   _t_vtype="$3"
   _debug2 _t_vtype "$_t_vtype"
-  if [ "$ACME_VERSION" = "2" ]; then
-    _send_signed_request "$_t_url" "{}"
-  else
-    _send_signed_request "$_t_url" "{\"resource\": \"challenge\", \"type\": \"$_t_vtype\", \"keyAuthorization\": \"$_t_key_authz\"}"
-  fi
+
+  _send_signed_request "$_t_url" "{}"
+
 }
 
 #endpoint  domain type
@@ -3891,7 +3970,15 @@ _ns_purge_cf() {
 
 #checks if cf server is available
 _ns_is_available_cf() {
-  if _get "https://cloudflare-dns.com" >/dev/null 2>&1; then
+  if _get "https://cloudflare-dns.com" "" 1 >/dev/null 2>&1; then
+    return 0
+  else
+    return 1
+  fi
+}
+
+_ns_is_available_google() {
+  if _get "https://dns.google" "" 1 >/dev/null 2>&1; then
     return 0
   else
     return 1
@@ -3906,6 +3993,38 @@ _ns_lookup_google() {
   _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
 }
 
+_ns_is_available_ali() {
+  if _get "https://dns.alidns.com" "" 1 >/dev/null 2>&1; then
+    return 0
+  else
+    return 1
+  fi
+}
+
+#domain, type
+_ns_lookup_ali() {
+  _cf_ld="$1"
+  _cf_ld_type="$2"
+  _cf_ep="https://dns.alidns.com/resolve"
+  _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
+}
+
+_ns_is_available_dp() {
+  if _get "https://doh.pub" "" 1 >/dev/null 2>&1; then
+    return 0
+  else
+    return 1
+  fi
+}
+
+#dnspod
+_ns_lookup_dp() {
+  _cf_ld="$1"
+  _cf_ld_type="$2"
+  _cf_ep="https://doh.pub/dns-query"
+  _ns_lookup_impl "$_cf_ep" "$_cf_ld" "$_cf_ld_type"
+}
+
 #domain, type
 _ns_lookup() {
   if [ -z "$DOH_USE" ]; then
@@ -3913,16 +4032,30 @@ _ns_lookup() {
     if _ns_is_available_cf; then
       _debug "Use cloudflare doh server"
       export DOH_USE=$DOH_CLOUDFLARE
-    else
+    elif _ns_is_available_google; then
       _debug "Use google doh server"
       export DOH_USE=$DOH_GOOGLE
+    elif _ns_is_available_ali; then
+      _debug "Use aliyun doh server"
+      export DOH_USE=$DOH_ALI
+    elif _ns_is_available_dp; then
+      _debug "Use dns pod doh server"
+      export DOH_USE=$DOH_DP
+    else
+      _err "No doh"
     fi
   fi
 
   if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
     _ns_lookup_cf "$@"
-  else
+  elif [ "$DOH_USE" = "$DOH_GOOGLE" ]; then
     _ns_lookup_google "$@"
+  elif [ "$DOH_USE" = "$DOH_ALI" ]; then
+    _ns_lookup_ali "$@"
+  elif [ "$DOH_USE" = "$DOH_DP" ]; then
+    _ns_lookup_dp "$@"
+  else
+    _err "Unknown doh provider: DOH_USE=$DOH_USE"
   fi
 
 }
@@ -3947,7 +4080,7 @@ __purge_txt() {
   if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then
     _ns_purge_cf "$_p_txtdomain" "TXT"
   else
-    _debug "no purge api for google dns api, just sleep 5 secs"
+    _debug "no purge api for this doh api, just sleep 5 secs"
     _sleep 5
   fi
 
@@ -4009,12 +4142,42 @@ _check_dns_entries() {
 }
 
 #file
-_get_cert_issuers() {
+_get_chain_issuers() {
+  _cfile="$1"
+  if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)" "unknown option help"; then
+    ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep -i 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
+  else
+    _cindex=1
+    for _startn in $(grep -n -- "$BEGIN_CERT" "$_cfile" | cut -d : -f 1); do
+      _endn="$(grep -n -- "$END_CERT" "$_cfile" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)"
+      _debug2 "_startn" "$_startn"
+      _debug2 "_endn" "$_endn"
+      if [ "$DEBUG" ]; then
+        _debug2 "cert$_cindex" "$(sed -n "$_startn,${_endn}p" "$_cfile")"
+      fi
+      sed -n "$_startn,${_endn}p" "$_cfile" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 | sed "s/ *\(.*\)/\1/"
+      _cindex=$(_math $_cindex + 1)
+    done
+  fi
+}
+
+#
+_get_chain_subjects() {
   _cfile="$1"
-  if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)" "unknown option help"; then
-    ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
+  if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -help 2>&1)" "Usage: crl2pkcs7" || _contains "$(${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 help 2>&1)" "unknown option help"; then
+    ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep -i 'Subject:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
   else
-    ${ACME_OPENSSL_BIN:-openssl} x509 -in $_cfile -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2
+    _cindex=1
+    for _startn in $(grep -n -- "$BEGIN_CERT" "$_cfile" | cut -d : -f 1); do
+      _endn="$(grep -n -- "$END_CERT" "$_cfile" | cut -d : -f 1 | _head_n $_cindex | _tail_n 1)"
+      _debug2 "_startn" "$_startn"
+      _debug2 "_endn" "$_endn"
+      if [ "$DEBUG" ]; then
+        _debug2 "cert$_cindex" "$(sed -n "$_startn,${_endn}p" "$_cfile")"
+      fi
+      sed -n "$_startn,${_endn}p" "$_cfile" | ${ACME_OPENSSL_BIN:-openssl} x509 -text -noout | grep -i 'Subject:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 | sed "s/ *\(.*\)/\1/"
+      _cindex=$(_math $_cindex + 1)
+    done
   fi
 }
 
@@ -4022,14 +4185,42 @@ _get_cert_issuers() {
 _match_issuer() {
   _cfile="$1"
   _missuer="$2"
-  _fissuers="$(_get_cert_issuers $_cfile)"
+  _fissuers="$(_get_chain_issuers $_cfile)"
   _debug2 _fissuers "$_fissuers"
-  if _contains "$_fissuers" "$_missuer"; then
-    return 0
-  fi
-  _fissuers="$(echo "$_fissuers" | _lower_case)"
+  _rootissuer="$(echo "$_fissuers" | _lower_case | _tail_n 1)"
+  _debug2 _rootissuer "$_rootissuer"
   _missuer="$(echo "$_missuer" | _lower_case)"
-  _contains "$_fissuers" "$_missuer"
+  _contains "$_rootissuer" "$_missuer"
+}
+
+#ip
+_isIPv4() {
+  for seg in $(echo "$1" | tr '.' ' '); do
+    if [ $seg -ge 0 ] 2>/dev/null && [ $seg -le 255 ] 2>/dev/null; then
+      continue
+    fi
+    return 1
+  done
+  return 0
+}
+
+#ip6
+_isIPv6() {
+  _contains "$1" ":"
+}
+
+#ip
+_isIP() {
+  _isIPv4 "$1" || _isIPv6 "$1"
+}
+
+#identifier
+_getIdType() {
+  if _isIP "$1"; then
+    echo "$ID_TYPE_IP"
+  else
+    echo "$ID_TYPE_DNS"
+  fi
 }
 
 #webroot, domain domainlist  keylength
@@ -4069,6 +4260,10 @@ issue() {
   if [ -z "$_ACME_IS_RENEW" ]; then
     _initpath "$_main_domain" "$_key_length"
     mkdir -p "$DOMAIN_PATH"
+  elif ! _hasfield "$_web_roots" "$W_DNS"; then
+    Le_OrderFinalize=""
+    Le_LinkOrder=""
+    Le_LinkCert=""
   fi
 
   if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
@@ -4076,10 +4271,6 @@ issue() {
     return 1
   fi
 
-  _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY"
-
-  _initAPI
-
   if [ -f "$DOMAIN_CONF" ]; then
     Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime)
     _debug Le_NextRenewTime "$Le_NextRenewTime"
@@ -4099,6 +4290,11 @@ issue() {
     fi
   fi
 
+  _debug "Using ACME_DIRECTORY: $ACME_DIRECTORY"
+  if ! _initAPI; then
+    return 1
+  fi
+
   _savedomainconf "Le_Domain" "$_main_domain"
   _savedomainconf "Le_Alt" "$_alt_domains"
   _savedomainconf "Le_Webroot" "$_web_roots"
@@ -4182,74 +4378,72 @@ issue() {
   sep='#'
   dvsep=','
   if [ -z "$vlist" ]; then
-    if [ "$ACME_VERSION" = "2" ]; then
-      #make new order request
-      _identifiers="{\"type\":\"dns\",\"value\":\"$(_idn "$_main_domain")\"}"
-      _w_index=1
-      while true; do
-        d="$(echo "$_alt_domains," | cut -d , -f "$_w_index")"
-        _w_index="$(_math "$_w_index" + 1)"
-        _debug d "$d"
-        if [ -z "$d" ]; then
-          break
-        fi
-        _identifiers="$_identifiers,{\"type\":\"dns\",\"value\":\"$(_idn "$d")\"}"
-      done
-      _debug2 _identifiers "$_identifiers"
-      if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
-        _err "Create new order error."
-        _clearup
-        _on_issue_err "$_post_hook"
-        return 1
-      fi
-      Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)"
-      _debug Le_LinkOrder "$Le_LinkOrder"
-      Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
-      _debug Le_OrderFinalize "$Le_OrderFinalize"
-      if [ -z "$Le_OrderFinalize" ]; then
-        _err "Create new order error. Le_OrderFinalize not found. $response"
-        _clearup
-        _on_issue_err "$_post_hook"
-        return 1
+    #make new order request
+    _identifiers="{\"type\":\"$(_getIdType "$_main_domain")\",\"value\":\"$(_idn "$_main_domain")\"}"
+    _w_index=1
+    while true; do
+      d="$(echo "$_alt_domains," | cut -d , -f "$_w_index")"
+      _w_index="$(_math "$_w_index" + 1)"
+      _debug d "$d"
+      if [ -z "$d" ]; then
+        break
       fi
+      _identifiers="$_identifiers,{\"type\":\"$(_getIdType "$d")\",\"value\":\"$(_idn "$d")\"}"
+    done
+    _debug2 _identifiers "$_identifiers"
+    if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
+      _err "Create new order error."
+      _clearup
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
+    Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)"
+    _debug Le_LinkOrder "$Le_LinkOrder"
+    Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)"
+    _debug Le_OrderFinalize "$Le_OrderFinalize"
+    if [ -z "$Le_OrderFinalize" ]; then
+      _err "Create new order error. Le_OrderFinalize not found. $response"
+      _clearup
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
+
+    #for dns manual mode
+    _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
 
-      #for dns manual mode
-      _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize"
+    _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
+    _debug2 _authorizations_seg "$_authorizations_seg"
+    if [ -z "$_authorizations_seg" ]; then
+      _err "_authorizations_seg not found."
+      _clearup
+      _on_issue_err "$_post_hook"
+      return 1
+    fi
 
-      _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
-      _debug2 _authorizations_seg "$_authorizations_seg"
-      if [ -z "$_authorizations_seg" ]; then
-        _err "_authorizations_seg not found."
+    #domain and authz map
+    _authorizations_map=""
+    for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
+      _debug2 "_authz_url" "$_authz_url"
+      if ! _send_signed_request "$_authz_url"; then
+        _err "get to authz error."
+        _err "_authorizations_seg" "$_authorizations_seg"
+        _err "_authz_url" "$_authz_url"
         _clearup
         _on_issue_err "$_post_hook"
         return 1
       fi
 
-      #domain and authz map
-      _authorizations_map=""
-      for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do
-        _debug2 "_authz_url" "$_authz_url"
-        if ! _send_signed_request "$_authz_url"; then
-          _err "get to authz error."
-          _err "_authorizations_seg" "$_authorizations_seg"
-          _err "_authz_url" "$_authz_url"
-          _clearup
-          _on_issue_err "$_post_hook"
-          return 1
-        fi
-
-        response="$(echo "$response" | _normalizeJson)"
-        _debug2 response "$response"
-        _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
+      response="$(echo "$response" | _normalizeJson)"
+      _debug2 response "$response"
+      _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"
-      done
-      _debug2 _authorizations_map "$_authorizations_map"
-    fi
+    done
+    _debug2 _authorizations_map "$_authorizations_map"
 
     _index=0
     _currentRoot=""
@@ -4280,33 +4474,25 @@ $_authorizations_map"
         vtype="$VTYPE_ALPN"
       fi
 
-      if [ "$ACME_VERSION" = "2" ]; then
-        _idn_d="$(_idn "$d")"
-        _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")"
-        _debug2 _candidates "$_candidates"
-        if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then
-          for _can in $_candidates; do
-            if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then
-              _candidates="$_can"
-              break
-            fi
-          done
-        fi
-        response="$(echo "$_candidates" | sed "s/$_idn_d,//")"
-        _debug2 "response" "$response"
-        if [ -z "$response" ]; then
-          _err "get to authz error."
-          _err "_authorizations_map" "$_authorizations_map"
-          _clearup
-          _on_issue_err "$_post_hook"
-          return 1
-        fi
-      else
-        if ! __get_domain_new_authz "$d"; then
-          _clearup
-          _on_issue_err "$_post_hook"
-          return 1
-        fi
+      _idn_d="$(_idn "$d")"
+      _candidates="$(echo "$_authorizations_map" | grep -i "^$_idn_d,")"
+      _debug2 _candidates "$_candidates"
+      if [ "$(echo "$_candidates" | wc -l)" -gt 1 ]; then
+        for _can in $_candidates; do
+          if _startswith "$(echo "$_can" | tr '.' '|')" "$(echo "$_idn_d" | tr '.' '|'),"; then
+            _candidates="$_can"
+            break
+          fi
+        done
+      fi
+      response="$(echo "$_candidates" | sed "s/$_idn_d,//")"
+      _debug2 "response" "$response"
+      if [ -z "$response" ]; then
+        _err "get to authz error."
+        _err "_authorizations_map" "$_authorizations_map"
+        _clearup
+        _on_issue_err "$_post_hook"
+        return 1
       fi
 
       if [ -z "$thumbprint" ]; then
@@ -4347,11 +4533,9 @@ $_authorizations_map"
           _on_issue_err "$_post_hook"
           return 1
         fi
-        if [ "$ACME_VERSION" = "2" ]; then
-          uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
-        else
-          uri="$(echo "$entry" | _egrep_o '"uri":"[^"]*' | cut -d '"' -f 4)"
-        fi
+
+        uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)"
+
         _debug uri "$uri"
 
         if [ -z "$uri" ]; then
@@ -4646,36 +4830,14 @@ $_authorizations_map"
         return 1
       fi
 
-      _debug "sleep 2 secs to verify"
-      sleep 2
-      _debug "checking"
-      if [ "$ACME_VERSION" = "2" ]; then
-        _send_signed_request "$uri"
-      else
-        response="$(_get "$uri")"
-      fi
-      if [ "$?" != "0" ]; then
-        _err "$d:Verify error:$response"
-        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
-        _clearup
-        _on_issue_err "$_post_hook" "$vlist"
-        return 1
-      fi
       _debug2 original "$response"
 
       response="$(echo "$response" | _normalizeJson)"
       _debug2 response "$response"
 
       status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
-      if [ "$status" = "valid" ]; then
-        _info "$(__green Success)"
-        _stopserver "$serverproc"
-        serverproc=""
-        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
-        break
-      fi
-
-      if [ "$status" = "invalid" ]; then
+      _debug2 status "$status"
+      if _contains "$status" "invalid"; then
         error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')"
         _debug2 error "$error"
         errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)"
@@ -4697,10 +4859,18 @@ $_authorizations_map"
         return 1
       fi
 
+      if _contains "$status" "valid"; then
+        _info "$(__green Success)"
+        _stopserver "$serverproc"
+        serverproc=""
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
+        break
+      fi
+
       if [ "$status" = "pending" ]; then
-        _info "Pending"
+        _info "Pending, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)"
       elif [ "$status" = "processing" ]; then
-        _info "Processing"
+        _info "Processing, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)"
       else
         _err "$d:Verify error:$response"
         _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
@@ -4708,7 +4878,19 @@ $_authorizations_map"
         _on_issue_err "$_post_hook" "$vlist"
         return 1
       fi
+      _debug "sleep 2 secs to verify again"
+      sleep 2
+      _debug "checking"
 
+      _send_signed_request "$uri"
+
+      if [ "$?" != "0" ]; then
+        _err "$d:Verify error:$response"
+        _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
+        _clearup
+        _on_issue_err "$_post_hook" "$vlist"
+        return 1
+      fi
     done
 
   done
@@ -4717,138 +4899,129 @@ $_authorizations_map"
   _info "Verify finished, start to sign."
   der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)"
 
-  if [ "$ACME_VERSION" = "2" ]; then
-    _info "Lets finalize the order."
-    _info "Le_OrderFinalize" "$Le_OrderFinalize"
-    if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
-      _err "Sign failed."
-      _on_issue_err "$_post_hook"
-      return 1
-    fi
-    if [ "$code" != "200" ]; then
-      _err "Sign failed, finalize code is not 200."
-      _err "$response"
-      _on_issue_err "$_post_hook"
-      return 1
-    fi
-    if [ -z "$Le_LinkOrder" ]; then
-      Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n \t" | cut -d ":" -f 2-)"
-    fi
+  _info "Lets finalize the order."
+  _info "Le_OrderFinalize" "$Le_OrderFinalize"
+  if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then
+    _err "Sign failed."
+    _on_issue_err "$_post_hook"
+    return 1
+  fi
+  if [ "$code" != "200" ]; then
+    _err "Sign failed, finalize code is not 200."
+    _err "$response"
+    _on_issue_err "$_post_hook"
+    return 1
+  fi
+  if [ -z "$Le_LinkOrder" ]; then
+    Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n \t" | cut -d ":" -f 2-)"
+  fi
 
-    _savedomainconf "Le_LinkOrder" "$Le_LinkOrder"
+  _savedomainconf "Le_LinkOrder" "$Le_LinkOrder"
 
-    _link_cert_retry=0
-    _MAX_CERT_RETRY=30
-    while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
-      if _contains "$response" "\"status\":\"valid\""; then
-        _debug "Order status is valid."
-        Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
-        _debug Le_LinkCert "$Le_LinkCert"
-        if [ -z "$Le_LinkCert" ]; then
-          _err "Sign error, can not find Le_LinkCert"
-          _err "$response"
-          _on_issue_err "$_post_hook"
-          return 1
-        fi
-        break
-      elif _contains "$response" "\"processing\""; then
-        _info "Order status is processing, lets sleep and retry."
-        _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
-        _debug "_retryafter" "$_retryafter"
-        if [ "$_retryafter" ]; then
-          _info "Retry after: $_retryafter"
-          _sleep $_retryafter
-        else
-          _sleep 2
-        fi
-      else
-        _err "Sign error, wrong status"
+  _link_cert_retry=0
+  _MAX_CERT_RETRY=30
+  while [ "$_link_cert_retry" -lt "$_MAX_CERT_RETRY" ]; do
+    if _contains "$response" "\"status\":\"valid\""; then
+      _debug "Order status is valid."
+      Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)"
+      _debug Le_LinkCert "$Le_LinkCert"
+      if [ -z "$Le_LinkCert" ]; then
+        _err "Sign error, can not find Le_LinkCert"
         _err "$response"
         _on_issue_err "$_post_hook"
         return 1
       fi
-      #the order is processing, so we are going to poll order status
-      if [ -z "$Le_LinkOrder" ]; then
-        _err "Sign error, can not get order link location header"
-        _err "responseHeaders" "$responseHeaders"
-        _on_issue_err "$_post_hook"
-        return 1
-      fi
-      _info "Polling order status: $Le_LinkOrder"
-      if ! _send_signed_request "$Le_LinkOrder"; then
-        _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder."
-        _err "$response"
-        _on_issue_err "$_post_hook"
-        return 1
+      break
+    elif _contains "$response" "\"processing\""; then
+      _info "Order status is processing, lets sleep and retry."
+      _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r')
+      _debug "_retryafter" "$_retryafter"
+      if [ "$_retryafter" ]; then
+        _info "Retry after: $_retryafter"
+        _sleep $_retryafter
+      else
+        _sleep 2
       fi
-      _link_cert_retry="$(_math $_link_cert_retry + 1)"
-    done
-
-    if [ -z "$Le_LinkCert" ]; then
-      _err "Sign failed, can not get Le_LinkCert, retry time limit."
+    else
+      _err "Sign error, wrong status"
       _err "$response"
       _on_issue_err "$_post_hook"
       return 1
     fi
-    _info "Downloading cert."
-    _info "Le_LinkCert" "$Le_LinkCert"
-    if ! _send_signed_request "$Le_LinkCert"; then
-      _err "Sign failed, can not download cert:$Le_LinkCert."
-      _err "$response"
+    #the order is processing, so we are going to poll order status
+    if [ -z "$Le_LinkOrder" ]; then
+      _err "Sign error, can not get order link location header"
+      _err "responseHeaders" "$responseHeaders"
       _on_issue_err "$_post_hook"
       return 1
     fi
-
-    echo "$response" >"$CERT_PATH"
-    _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH"
-
-    if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then
-      if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then
-        rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)"
-        _debug2 "rels" "$rels"
-        for rel in $rels; do
-          _info "Try rel: $rel"
-          if ! _send_signed_request "$rel"; then
-            _err "Sign failed, can not download cert:$rel"
-            _err "$response"
-            continue
-          fi
-          _relcert="$CERT_PATH.alt"
-          _relfullchain="$CERT_FULLCHAIN_PATH.alt"
-          _relca="$CA_CERT_PATH.alt"
-          echo "$response" >"$_relcert"
-          _split_cert_chain "$_relcert" "$_relfullchain" "$_relca"
-          if _match_issuer "$_relfullchain" "$_preferred_chain"; then
-            _info "Matched issuer in: $rel"
-            cat $_relcert >"$CERT_PATH"
-            cat $_relfullchain >"$CERT_FULLCHAIN_PATH"
-            cat $_relca >"$CA_CERT_PATH"
-            break
-          fi
-        done
-      fi
-    fi
-  else
-    if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then
-      _err "Sign failed. $response"
+    _info "Polling order status: $Le_LinkOrder"
+    if ! _send_signed_request "$Le_LinkOrder"; then
+      _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder."
+      _err "$response"
       _on_issue_err "$_post_hook"
       return 1
     fi
-    _rcert="$response"
-    Le_LinkCert="$(grep -i '^Location.*$' "$HTTP_HEADER" | _tail_n 1 | tr -d "\r\n" | cut -d " " -f 2)"
-    echo "$BEGIN_CERT" >"$CERT_PATH"
+    _link_cert_retry="$(_math $_link_cert_retry + 1)"
+  done
 
-    #if ! _get "$Le_LinkCert" | _base64 "multiline"  >> "$CERT_PATH" ; then
-    #  _debug "Get cert failed. Let's try last response."
-    #  printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >> "$CERT_PATH"
-    #fi
+  if [ -z "$Le_LinkCert" ]; then
+    _err "Sign failed, can not get Le_LinkCert, retry time limit."
+    _err "$response"
+    _on_issue_err "$_post_hook"
+    return 1
+  fi
+  _info "Downloading cert."
+  _info "Le_LinkCert" "$Le_LinkCert"
+  if ! _send_signed_request "$Le_LinkCert"; then
+    _err "Sign failed, can not download cert:$Le_LinkCert."
+    _err "$response"
+    _on_issue_err "$_post_hook"
+    return 1
+  fi
 
-    if ! printf -- "%s" "$_rcert" | _dbase64 "multiline" | _base64 "multiline" >>"$CERT_PATH"; then
-      _debug "Try cert link."
-      _get "$Le_LinkCert" | _base64 "multiline" >>"$CERT_PATH"
+  echo "$response" >"$CERT_PATH"
+  _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH"
+  if [ -z "$_preferred_chain" ]; then
+    _preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN)
+  fi
+  if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then
+    if [ "$DEBUG" ]; then
+      _debug "default chain issuers: " "$(_get_chain_issuers "$CERT_FULLCHAIN_PATH")"
+    fi
+    if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then
+      rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)"
+      _debug2 "rels" "$rels"
+      for rel in $rels; do
+        _info "Try rel: $rel"
+        if ! _send_signed_request "$rel"; then
+          _err "Sign failed, can not download cert:$rel"
+          _err "$response"
+          continue
+        fi
+        _relcert="$CERT_PATH.alt"
+        _relfullchain="$CERT_FULLCHAIN_PATH.alt"
+        _relca="$CA_CERT_PATH.alt"
+        echo "$response" >"$_relcert"
+        _split_cert_chain "$_relcert" "$_relfullchain" "$_relca"
+        if [ "$DEBUG" ]; then
+          _debug "rel chain issuers: " "$(_get_chain_issuers "$_relfullchain")"
+        fi
+        if _match_issuer "$_relfullchain" "$_preferred_chain"; then
+          _info "Matched issuer in: $rel"
+          cat $_relcert >"$CERT_PATH"
+          cat $_relfullchain >"$CERT_FULLCHAIN_PATH"
+          cat $_relca >"$CA_CERT_PATH"
+          rm -f "$_relcert"
+          rm -f "$_relfullchain"
+          rm -f "$_relca"
+          break
+        fi
+        rm -f "$_relcert"
+        rm -f "$_relfullchain"
+        rm -f "$_relca"
+      done
     fi
-
-    echo "$END_CERT" >>"$CERT_PATH"
   fi
 
   _debug "Le_LinkCert" "$Le_LinkCert"
@@ -4865,10 +5038,10 @@ $_authorizations_map"
     _info "$(__green "Cert success.")"
     cat "$CERT_PATH"
 
-    _info "Your cert is in $(__green " $CERT_PATH ")"
+    _info "Your cert is in: $(__green "$CERT_PATH")"
 
     if [ -f "$CERT_KEY_PATH" ]; then
-      _info "Your cert key is in $(__green " $CERT_KEY_PATH ")"
+      _info "Your cert key is in: $(__green "$CERT_KEY_PATH")"
     fi
 
     if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then
@@ -4877,55 +5050,8 @@ $_authorizations_map"
     fi
   fi
 
-  if [ "$ACME_VERSION" = "2" ]; then
-    _debug "v2 chain."
-  else
-    cp "$CERT_PATH" "$CERT_FULLCHAIN_PATH"
-    Le_LinkIssuer=$(grep -i '^Link' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | cut -d ';' -f 1 | tr -d '<>')
-
-    if [ "$Le_LinkIssuer" ]; then
-      if ! _contains "$Le_LinkIssuer" ":"; then
-        _info "$(__red "Relative issuer link found.")"
-        Le_LinkIssuer="$_ACME_SERVER_HOST$Le_LinkIssuer"
-      fi
-      _debug Le_LinkIssuer "$Le_LinkIssuer"
-      _savedomainconf "Le_LinkIssuer" "$Le_LinkIssuer"
-
-      _link_issuer_retry=0
-      _MAX_ISSUER_RETRY=5
-      while [ "$_link_issuer_retry" -lt "$_MAX_ISSUER_RETRY" ]; do
-        _debug _link_issuer_retry "$_link_issuer_retry"
-        if [ "$ACME_VERSION" = "2" ]; then
-          if _send_signed_request "$Le_LinkIssuer"; then
-            echo "$response" >"$CA_CERT_PATH"
-            break
-          fi
-        else
-          if _get "$Le_LinkIssuer" >"$CA_CERT_PATH.der"; then
-            echo "$BEGIN_CERT" >"$CA_CERT_PATH"
-            _base64 "multiline" <"$CA_CERT_PATH.der" >>"$CA_CERT_PATH"
-            echo "$END_CERT" >>"$CA_CERT_PATH"
-            if ! _checkcert "$CA_CERT_PATH"; then
-              _err "Can not get the ca cert."
-              break
-            fi
-            cat "$CA_CERT_PATH" >>"$CERT_FULLCHAIN_PATH"
-            rm -f "$CA_CERT_PATH.der"
-            break
-          fi
-        fi
-        _link_issuer_retry=$(_math $_link_issuer_retry + 1)
-        _sleep "$_link_issuer_retry"
-      done
-      if [ "$_link_issuer_retry" = "$_MAX_ISSUER_RETRY" ]; then
-        _err "Max retry for issuer ca cert is reached."
-      fi
-    else
-      _debug "No Le_LinkIssuer header found."
-    fi
-  fi
-  [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in $(__green " $CA_CERT_PATH ")"
-  [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green " $CERT_FULLCHAIN_PATH ")"
+  [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in: $(__green "$CA_CERT_PATH")"
+  [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green "$CERT_FULLCHAIN_PATH")"
 
   Le_CertCreateTime=$(_time)
   _savedomainconf "Le_CertCreateTime" "$Le_CertCreateTime"
@@ -5036,17 +5162,16 @@ renew() {
 
   . "$DOMAIN_CONF"
   _debug Le_API "$Le_API"
-
-  if [ "$Le_API" = "$LETSENCRYPT_CA_V1" ]; then
-    _cleardomainconf Le_API
-    Le_API="$DEFAULT_CA"
-  fi
-  if [ "$Le_API" = "$LETSENCRYPT_STAGING_CA_V1" ]; then
-    _cleardomainconf Le_API
-    Le_API="$DEFAULT_STAGING_CA"
+  if [ -z "$Le_API" ] || [ "$CA_LETSENCRYPT_V1" = "$Le_API" ]; then
+    #if this is from an old version, Le_API is empty,
+    #so, we force to use letsencrypt server
+    Le_API="$CA_LETSENCRYPT_V2"
   fi
 
   if [ "$Le_API" ]; then
+    if [ "$Le_API" != "$ACME_DIRECTORY" ]; then
+      _clearAPI
+    fi
     export ACME_DIRECTORY="$Le_API"
     #reload ca configs
     ACCOUNT_KEY_PATH=""
@@ -5222,6 +5347,7 @@ signcsr() {
   _renew_hook="${10}"
   _local_addr="${11}"
   _challenge_alias="${12}"
+  _preferred_chain="${13}"
 
   _csrsubj=$(_readSubjectFromCSR "$_csrfile")
   if [ "$?" != "0" ]; then
@@ -5259,16 +5385,13 @@ signcsr() {
     return 1
   fi
 
-  if [ -z "$ACME_VERSION" ] && _contains "$_csrsubj,$_csrdomainlist" "*."; then
-    export ACME_VERSION=2
-  fi
   _initpath "$_csrsubj" "$_csrkeylength"
   mkdir -p "$DOMAIN_PATH"
 
   _info "Copy csr to: $CSR_PATH"
   cp "$_csrfile" "$CSR_PATH"
 
-  issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias"
+  issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" "$_preferred_chain"
 
 }
 
@@ -5471,7 +5594,7 @@ _installcert() {
   mkdir -p "$_backup_path"
 
   if [ "$_real_cert" ]; then
-    _info "Installing cert to:$_real_cert"
+    _info "Installing cert to: $_real_cert"
     if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then
       cp "$_real_cert" "$_backup_path/cert.bak"
     fi
@@ -5479,7 +5602,7 @@ _installcert() {
   fi
 
   if [ "$_real_ca" ]; then
-    _info "Installing CA to:$_real_ca"
+    _info "Installing CA to: $_real_ca"
     if [ "$_real_ca" = "$_real_cert" ]; then
       echo "" >>"$_real_ca"
       cat "$CA_CERT_PATH" >>"$_real_ca" || return 1
@@ -5492,7 +5615,7 @@ _installcert() {
   fi
 
   if [ "$_real_key" ]; then
-    _info "Installing key to:$_real_key"
+    _info "Installing key to: $_real_key"
     if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then
       cp "$_real_key" "$_backup_path/key.bak"
     fi
@@ -5505,7 +5628,7 @@ _installcert() {
   fi
 
   if [ "$_real_fullchain" ]; then
-    _info "Installing full chain to:$_real_fullchain"
+    _info "Installing full chain to: $_real_fullchain"
     if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then
       cp "$_real_fullchain" "$_backup_path/fullchain.bak"
     fi
@@ -5600,8 +5723,16 @@ installcronjob() {
   if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY" ]; then
     lesh="\"$LE_WORKING_DIR\"/$PROJECT_ENTRY"
   else
-    _err "Can not install cronjob, $PROJECT_ENTRY not found."
-    return 1
+    _debug "_SCRIPT_" "$_SCRIPT_"
+    _script="$(_readlink "$_SCRIPT_")"
+    _debug _script "$_script"
+    if [ -f "$_script" ]; then
+      _info "Using the current script from: $_script"
+      lesh="$_script"
+    else
+      _err "Can not install cronjob, $PROJECT_ENTRY not found."
+      return 1
+    fi
   fi
   if [ "$_c_home" ]; then
     _c_entry="--config-home \"$_c_home\" "
@@ -5673,7 +5804,7 @@ uninstallcronjob() {
   _info "Removing cron job"
   cr="$($_CRONTAB -l | grep "$PROJECT_ENTRY --cron")"
   if [ "$cr" ]; then
-    if _exists uname && uname -a | grep solaris >/dev/null; then
+    if _exists uname && uname -a | grep SunOS >/dev/null; then
       $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB --
     else
       $_CRONTAB -l | sed "/$PROJECT_ENTRY --cron/d" | $_CRONTAB -
@@ -5713,6 +5844,23 @@ revoke() {
     return 1
   fi
 
+  . "$DOMAIN_CONF"
+  _debug Le_API "$Le_API"
+
+  if [ "$Le_API" ]; then
+    if [ "$Le_API" != "$ACME_DIRECTORY" ]; then
+      _clearAPI
+    fi
+    export ACME_DIRECTORY="$Le_API"
+    #reload ca configs
+    ACCOUNT_KEY_PATH=""
+    ACCOUNT_JSON_PATH=""
+    CA_CONF=""
+    _debug3 "initpath again."
+    _initpath "$Le_Domain" "$_isEcc"
+    _initAPI
+  fi
+
   cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _url_replace)"
 
   if [ -z "$cert" ]; then
@@ -5722,11 +5870,8 @@ revoke() {
 
   _initAPI
 
-  if [ "$ACME_VERSION" = "2" ]; then
-    data="{\"certificate\": \"$cert\",\"reason\":$_reason}"
-  else
-    data="{\"resource\": \"revoke-cert\", \"certificate\": \"$cert\"}"
-  fi
+  data="{\"certificate\": \"$cert\",\"reason\":$_reason}"
+
   uri="${ACME_REVOKE_CERT}"
 
   if [ -f "$CERT_KEY_PATH" ]; then
@@ -5795,60 +5940,62 @@ remove() {
 _deactivate() {
   _d_domain="$1"
   _d_type="$2"
-  _initpath
+  _initpath "$_d_domain" "$_d_type"
 
-  if [ "$ACME_VERSION" = "2" ]; then
-    _identifiers="{\"type\":\"dns\",\"value\":\"$_d_domain\"}"
-    if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
-      _err "Can not get domain new order."
-      return 1
-    fi
-    _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
-    _debug2 _authorizations_seg "$_authorizations_seg"
-    if [ -z "$_authorizations_seg" ]; then
-      _err "_authorizations_seg not found."
-      _clearup
-      _on_issue_err "$_post_hook"
-      return 1
-    fi
+  . "$DOMAIN_CONF"
+  _debug Le_API "$Le_API"
 
-    authzUri="$_authorizations_seg"
-    _debug2 "authzUri" "$authzUri"
-    if ! _send_signed_request "$authzUri"; then
-      _err "get to authz error."
-      _err "_authorizations_seg" "$_authorizations_seg"
-      _err "authzUri" "$authzUri"
-      _clearup
-      _on_issue_err "$_post_hook"
-      return 1
+  if [ "$Le_API" ]; then
+    if [ "$Le_API" != "$ACME_DIRECTORY" ]; then
+      _clearAPI
     fi
+    export ACME_DIRECTORY="$Le_API"
+    #reload ca configs
+    ACCOUNT_KEY_PATH=""
+    ACCOUNT_JSON_PATH=""
+    CA_CONF=""
+    _debug3 "initpath again."
+    _initpath "$Le_Domain" "$_d_type"
+    _initAPI
+  fi
 
-    response="$(echo "$response" | _normalizeJson)"
-    _debug2 response "$response"
-    _URL_NAME="url"
-  else
-    if ! __get_domain_new_authz "$_d_domain"; then
-      _err "Can not get domain new authz token."
-      return 1
-    fi
+  _identifiers="{\"type\":\"$(_getIdType "$_d_domain")\",\"value\":\"$_d_domain\"}"
+  if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then
+    _err "Can not get domain new order."
+    return 1
+  fi
+  _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')"
+  _debug2 _authorizations_seg "$_authorizations_seg"
+  if [ -z "$_authorizations_seg" ]; then
+    _err "_authorizations_seg not found."
+    _clearup
+    _on_issue_err "$_post_hook"
+    return 1
+  fi
 
-    authzUri="$(echo "$responseHeaders" | grep "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")"
-    _debug "authzUri" "$authzUri"
-    if [ "$code" ] && [ ! "$code" = '201' ]; then
-      _err "new-authz error: $response"
-      return 1
-    fi
-    _URL_NAME="uri"
+  authzUri="$_authorizations_seg"
+  _debug2 "authzUri" "$authzUri"
+  if ! _send_signed_request "$authzUri"; then
+    _err "get to authz error."
+    _err "_authorizations_seg" "$_authorizations_seg"
+    _err "authzUri" "$authzUri"
+    _clearup
+    _on_issue_err "$_post_hook"
+    return 1
   fi
 
-  entries="$(echo "$response" | tr '][' '==' | _egrep_o "challenges\": *=[^=]*=" | tr '}{' '\n' | grep "\"status\": *\"valid\"")"
+  response="$(echo "$response" | _normalizeJson)"
+  _debug2 response "$response"
+  _URL_NAME="url"
+
+  entries="$(echo "$response" | tr '][' '==' | _egrep_o "challenges\": *=[^=]*=" | tr '}{' '\n\n' | grep "\"status\": *\"valid\"")"
   if [ -z "$entries" ]; then
     _info "No valid entries found."
     if [ -z "$thumbprint" ]; then
       thumbprint="$(__calc_account_thumbprint)"
     fi
     _debug "Trigger validation."
-    vtype="$VTYPE_DNS"
+    vtype="$(_getIdType "$_d_domain")"
     entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')"
     _debug entry "$entry"
     if [ -z "$entry" ]; then
@@ -5894,11 +6041,7 @@ _deactivate() {
 
     _info "Deactivate: $_vtype"
 
-    if [ "$ACME_VERSION" = "2" ]; then
-      _djson="{\"status\":\"deactivated\"}"
-    else
-      _djson="{\"resource\": \"authz\", \"status\":\"deactivated\"}"
-    fi
+    _djson="{\"status\":\"deactivated\"}"
 
     if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then
       _info "Deactivate: $_vtype success."
@@ -6461,6 +6604,8 @@ Commands:
   --deactivate             Deactivate the domain authz, professional use.
   --set-default-ca         Used with '--server', Set the default CA to use.
                            See: $_SERVER_WIKI
+  --set-default-chain      Set the default preferred chain for a CA.
+                           See: $_PREFERRED_CHAIN_WIKI
 
 
 Parameters:
@@ -6609,7 +6754,7 @@ _getRepoHash() {
   _hash_path=$1
   shift
   _hash_url="https://api.github.com/repos/acmesh-official/$PROJECT_NAME/git/refs/$_hash_path"
-  _get $_hash_url | tr -d "\r\n" | tr '{},' '\n' | grep '"sha":' | cut -d '"' -f 4
+  _get $_hash_url | tr -d "\r\n" | tr '{},' '\n\n\n' | grep '"sha":' | cut -d '"' -f 4
 }
 
 _getUpgradeHash() {
@@ -6682,9 +6827,10 @@ _checkSudo() {
   return 0
 }
 
-#server
+#server  #keylength
 _selectServer() {
   _server="$1"
+  _skeylength="$2"
   _server_lower="$(echo "$_server" | _lower_case)"
   _sindex=0
   for snames in $CA_NAMES; do
@@ -6695,6 +6841,9 @@ _selectServer() {
       if [ "$_server_lower" = "$sname" ]; then
         _debug2 "_selectServer match $sname"
         _serverdir="$(_getfield "$CA_SERVERS" $_sindex)"
+        if [ "$_serverdir" = "$CA_SSLCOM_RSA" ] && _isEccKey "$_skeylength"; then
+          _serverdir="$CA_SSLCOM_ECC"
+        fi
         _debug "Selected server: $_serverdir"
         ACME_DIRECTORY="$_serverdir"
         export ACME_DIRECTORY
@@ -6712,6 +6861,9 @@ _getCAShortName() {
   if [ -z "$caurl" ]; then
     caurl="$DEFAULT_CA"
   fi
+  if [ "$CA_SSLCOM_ECC" = "$caurl" ]; then
+    caurl="$CA_SSLCOM_RSA" #just hack to get the short name
+  fi
   caurl_lower="$(echo $caurl | _lower_case)"
   _sindex=0
   for surl in $(echo "$CA_SERVERS" | _lower_case | tr , ' '); do
@@ -6740,6 +6892,18 @@ setdefaultca() {
   _info "Changed default CA to: $(__green "$ACME_DIRECTORY")"
 }
 
+#preferred-chain
+setdefaultchain() {
+  _initpath
+  _preferred_chain="$1"
+  if [ -z "$_preferred_chain" ]; then
+    _err "Please give a '--preferred-chain value' value."
+    return 1
+  fi
+  mkdir -p "$CA_DIR"
+  _savecaconf "DEFAULT_PREFERRED_CHAIN" "$_preferred_chain"
+}
+
 _process() {
   _CMD=""
   _domain=""
@@ -6891,6 +7055,9 @@ _process() {
     --set-default-ca)
       _CMD="setdefaultca"
       ;;
+    --set-default-chain)
+      _CMD="setdefaultchain"
+      ;;
     -d | --domain)
       _dvalue="$2"
 
@@ -6904,10 +7071,6 @@ _process() {
           return 1
         fi
 
-        if _startswith "$_dvalue" "*."; then
-          _debug "Wildcard domain"
-          export ACME_VERSION=2
-        fi
         if [ -z "$_domain" ]; then
           _domain="$_dvalue"
         else
@@ -6930,7 +7093,6 @@ _process() {
       ;;
     --server)
       _server="$2"
-      _selectServer "$_server"
       shift
       ;;
     --debug)
@@ -7029,7 +7191,6 @@ _process() {
       Le_DNSSleep="$_dnssleep"
       shift
       ;;
-
     --keylength | -k)
       _keylength="$2"
       shift
@@ -7038,7 +7199,6 @@ _process() {
       _accountkeylength="$2"
       shift
       ;;
-
     --cert-file | --certpath)
       _cert_file="$2"
       shift
@@ -7302,6 +7462,10 @@ _process() {
     shift 1
   done
 
+  if [ "$_server" ]; then
+    _selectServer "$_server" "${_ecc:-$_keylength}"
+  fi
+
   if [ "${_CMD}" != "install" ]; then
     if [ "$__INTERACTIVE" ] && ! _checkSudo; then
       if [ -z "$FORCE" ]; then
@@ -7365,7 +7529,7 @@ _process() {
     deploy "$_domain" "$_deploy_hook" "$_ecc"
     ;;
   signcsr)
-    signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias"
+    signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain"
     ;;
   showcsr)
     showcsr "$_csr" "$_domain"
@@ -7424,6 +7588,9 @@ _process() {
   setdefaultca)
     setdefaultca
     ;;
+  setdefaultchain)
+    setdefaultchain "$_preferred_chain"
+    ;;
   *)
     if [ "$_CMD" ]; then
       _err "Invalid command: $_CMD"
index 552d81492d8624132b531edcd282859d4ac4f996..a460a1390781510beb91b90da71c0a3d15be5282 100644 (file)
@@ -17,6 +17,8 @@ cleverreach_deploy() {
   _cca="$4"
   _cfullchain="$5"
 
+  _rest_endpoint="https://rest.cleverreach.com"
+
   _debug _cdomain "$_cdomain"
   _debug _ckey "$_ckey"
   _debug _ccert "$_ccert"
@@ -25,6 +27,7 @@ cleverreach_deploy() {
 
   _getdeployconf DEPLOY_CLEVERREACH_CLIENT_ID
   _getdeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET
+  _getdeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID
 
   if [ -z "${DEPLOY_CLEVERREACH_CLIENT_ID}" ]; then
     _err "CleverReach Client ID is not found, please define DEPLOY_CLEVERREACH_CLIENT_ID."
@@ -37,11 +40,12 @@ cleverreach_deploy() {
 
   _savedeployconf DEPLOY_CLEVERREACH_CLIENT_ID "${DEPLOY_CLEVERREACH_CLIENT_ID}"
   _savedeployconf DEPLOY_CLEVERREACH_CLIENT_SECRET "${DEPLOY_CLEVERREACH_CLIENT_SECRET}"
+  _savedeployconf DEPLOY_CLEVERREACH_SUBCLIENT_ID "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}"
 
   _info "Obtaining a CleverReach access token"
 
   _data="{\"grant_type\": \"client_credentials\", \"client_id\": \"${DEPLOY_CLEVERREACH_CLIENT_ID}\", \"client_secret\": \"${DEPLOY_CLEVERREACH_CLIENT_SECRET}\"}"
-  _auth_result="$(_post "$_data" "https://rest.cleverreach.com/oauth/token.php" "" "POST" "application/json")"
+  _auth_result="$(_post "$_data" "$_rest_endpoint/oauth/token.php" "" "POST" "application/json")"
 
   _debug _data "$_data"
   _debug _auth_result "$_auth_result"
@@ -50,14 +54,32 @@ cleverreach_deploy() {
   _debug _regex "$_regex"
   _access_token=$(echo "$_auth_result" | _json_decode | sed -n "s/$_regex/\1/p")
 
+  _debug _subclient "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}"
+
+  if [ -n "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" ]; then
+    _info "Obtaining token for sub-client ${DEPLOY_CLEVERREACH_SUBCLIENT_ID}"
+    export _H1="Authorization: Bearer ${_access_token}"
+    _subclient_token_result="$(_get "$_rest_endpoint/v3/clients/$DEPLOY_CLEVERREACH_SUBCLIENT_ID/token")"
+    _access_token=$(echo "$_subclient_token_result" | sed -n "s/\"//p")
+
+    _debug _subclient_token_result "$_access_token"
+
+    _info "Destroying parent token at CleverReach, as it not needed anymore"
+    _destroy_result="$(_post "" "$_rest_endpoint/v3/oauth/token.json" "" "DELETE" "application/json")"
+    _debug _destroy_result "$_destroy_result"
+  fi
+
   _info "Uploading certificate and key to CleverReach"
 
   _certData="{\"cert\":\"$(_json_encode <"$_cfullchain")\", \"key\":\"$(_json_encode <"$_ckey")\"}"
   export _H1="Authorization: Bearer ${_access_token}"
-  _add_cert_result="$(_post "$_certData" "https://rest.cleverreach.com/v3/ssl" "" "POST" "application/json")"
+  _add_cert_result="$(_post "$_certData" "$_rest_endpoint/v3/ssl" "" "POST" "application/json")"
 
-  _debug "Destroying token at CleverReach"
-  _post "" "https://rest.cleverreach.com/v3/oauth/token.json" "" "DELETE" "application/json"
+  if [ -z "${DEPLOY_CLEVERREACH_SUBCLIENT_ID}" ]; then
+    _info "Destroying token at CleverReach, as it not needed anymore"
+    _destroy_result="$(_post "" "$_rest_endpoint/v3/oauth/token.json" "" "DELETE" "application/json")"
+    _debug _destroy_result "$_destroy_result"
+  fi
 
   if ! echo "$_add_cert_result" | grep '"error":' >/dev/null; then
     _info "Uploaded certificate successfully"
diff --git a/deploy/consul.sh b/deploy/consul.sh
new file mode 100644 (file)
index 0000000..f93fb45
--- /dev/null
@@ -0,0 +1,98 @@
+#!/usr/bin/env sh
+
+# Here is a script to deploy cert to hashicorp consul using curl
+# (https://www.consul.io/)
+#
+# it requires following environment variables:
+#
+# CONSUL_PREFIX - this contains the prefix path in consul
+# CONSUL_HTTP_ADDR - consul requires this to find your consul server
+#
+# additionally, you need to ensure that CONSUL_HTTP_TOKEN is available
+# to access the consul server
+
+#returns 0 means success, otherwise error.
+
+########  Public functions #####################
+
+#domain keyfile certfile cafile fullchain
+consul_deploy() {
+
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  # validate required env vars
+  _getdeployconf CONSUL_PREFIX
+  if [ -z "$CONSUL_PREFIX" ]; then
+    _err "CONSUL_PREFIX needs to be defined (contains prefix path in vault)"
+    return 1
+  fi
+  _savedeployconf CONSUL_PREFIX "$CONSUL_PREFIX"
+
+  _getdeployconf CONSUL_HTTP_ADDR
+  if [ -z "$CONSUL_HTTP_ADDR" ]; then
+    _err "CONSUL_HTTP_ADDR needs to be defined (contains consul connection address)"
+    return 1
+  fi
+  _savedeployconf CONSUL_HTTP_ADDR "$CONSUL_HTTP_ADDR"
+
+  CONSUL_CMD=$(command -v consul)
+
+  # force CLI, but the binary does not exist => error
+  if [ -n "$USE_CLI" ] && [ -z "$CONSUL_CMD" ]; then
+    _err "Cannot find the consul binary!"
+    return 1
+  fi
+
+  # use the CLI first
+  if [ -n "$USE_CLI" ] || [ -n "$CONSUL_CMD" ]; then
+    _info "Found consul binary, deploying with CLI"
+    consul_deploy_cli "$CONSUL_CMD" "$CONSUL_PREFIX"
+  else
+    _info "Did not find consul binary, deploying with API"
+    consul_deploy_api "$CONSUL_HTTP_ADDR" "$CONSUL_PREFIX" "$CONSUL_HTTP_TOKEN"
+  fi
+}
+
+consul_deploy_api() {
+  CONSUL_HTTP_ADDR="$1"
+  CONSUL_PREFIX="$2"
+  CONSUL_HTTP_TOKEN="$3"
+
+  URL="$CONSUL_HTTP_ADDR/v1/kv/$CONSUL_PREFIX"
+  export _H1="X-Consul-Token: $CONSUL_HTTP_TOKEN"
+
+  if [ -n "$FABIO" ]; then
+    _post "$(cat "$_cfullchain")" "$URL/${_cdomain}-cert.pem" '' "PUT" || return 1
+    _post "$(cat "$_ckey")" "$URL/${_cdomain}-key.pem" '' "PUT" || return 1
+  else
+    _post "$(cat "$_ccert")" "$URL/${_cdomain}/cert.pem" '' "PUT" || return 1
+    _post "$(cat "$_ckey")" "$URL/${_cdomain}/cert.key" '' "PUT" || return 1
+    _post "$(cat "$_cca")" "$URL/${_cdomain}/chain.pem" '' "PUT" || return 1
+    _post "$(cat "$_cfullchain")" "$URL/${_cdomain}/fullchain.pem" '' "PUT" || return 1
+  fi
+}
+
+consul_deploy_cli() {
+  CONSUL_CMD="$1"
+  CONSUL_PREFIX="$2"
+
+  if [ -n "$FABIO" ]; then
+    $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-cert.pem" @"$_cfullchain" || return 1
+    $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}-key.pem" @"$_ckey" || return 1
+  else
+    $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
+    $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
+    $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
+    $CONSUL_CMD kv put "${CONSUL_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
+  fi
+}
index a2a35f7b691ef074aad36f3d08f76eb17a79c053..f573a3aad05bdea26b741fe4fccd018f8698f6f2 100644 (file)
@@ -56,9 +56,9 @@ gcore_cdn_deploy() {
   _request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}"
   _debug _request "$_request"
   export _H1="Content-Type:application/json"
-  _response=$(_post "$_request" "https://api.gcdn.co/auth/signin")
+  _response=$(_post "$_request" "https://api.gcdn.co/auth/jwt/login")
   _debug _response "$_response"
-  _regex=".*\"token\":\"\([-._0-9A-Za-z]*\)\".*$"
+  _regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$"
   _debug _regex "$_regex"
   _token=$(echo "$_response" | sed -n "s/$_regex/\1/p")
   _debug _token "$_token"
@@ -72,12 +72,15 @@ gcore_cdn_deploy() {
   export _H2="Authorization:Token $_token"
   _response=$(_get "https://api.gcdn.co/resources")
   _debug _response "$_response"
-  _regex=".*(\"id\".*?\"cname\":\"$_cdomain\".*?})"
+  _regex="\"primary_resource\":null},"
+  _debug _regex "$_regex"
+  _response=$(echo "$_response" | sed "s/$_regex/$_regex\n/g")
+  _debug _response "$_response"
   _regex="^.*\"cname\":\"$_cdomain\".*$"
   _debug _regex "$_regex"
-  _resource=$(echo "$_response" | sed 's/},{/},\n{/g' | _egrep_o "$_regex")
+  _resource=$(echo "$_response" | _egrep_o "$_regex")
   _debug _resource "$_resource"
-  _regex=".*\"id\":\([0-9]*\).*\"rules\".*$"
+  _regex=".*\"id\":\([0-9]*\).*$"
   _debug _regex "$_regex"
   _resourceId=$(echo "$_resource" | sed -n "s/$_regex/\1/p")
   _debug _resourceId "$_resourceId"
index 0a45ee0701fb27c111f736e78c54825a9ff27575..c255059d94b5662306a86ee7e45faa00d86d642e 100644 (file)
@@ -54,11 +54,6 @@ haproxy_deploy() {
   DEPLOY_HAPROXY_ISSUER_DEFAULT="no"
   DEPLOY_HAPROXY_RELOAD_DEFAULT="true"
 
-  if [ -f "${DOMAIN_CONF}" ]; then
-    # shellcheck disable=SC1090
-    . "${DOMAIN_CONF}"
-  fi
-
   _debug _cdomain "${_cdomain}"
   _debug _ckey "${_ckey}"
   _debug _ccert "${_ccert}"
@@ -66,6 +61,8 @@ haproxy_deploy() {
   _debug _cfullchain "${_cfullchain}"
 
   # PEM_PATH is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_PATH_DEFAULT}"
+  _getdeployconf DEPLOY_HAPROXY_PEM_PATH
+  _debug2 DEPLOY_HAPROXY_PEM_PATH "${DEPLOY_HAPROXY_PEM_PATH}"
   if [ -n "${DEPLOY_HAPROXY_PEM_PATH}" ]; then
     Le_Deploy_haproxy_pem_path="${DEPLOY_HAPROXY_PEM_PATH}"
     _savedomainconf Le_Deploy_haproxy_pem_path "${Le_Deploy_haproxy_pem_path}"
@@ -82,6 +79,8 @@ haproxy_deploy() {
   fi
 
   # PEM_NAME is optional. If not provided then assume "${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}"
+  _getdeployconf DEPLOY_HAPROXY_PEM_NAME
+  _debug2 DEPLOY_HAPROXY_PEM_NAME "${DEPLOY_HAPROXY_PEM_NAME}"
   if [ -n "${DEPLOY_HAPROXY_PEM_NAME}" ]; then
     Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME}"
     _savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}"
@@ -90,6 +89,8 @@ haproxy_deploy() {
   fi
 
   # BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}"
+  _getdeployconf DEPLOY_HAPROXY_BUNDLE
+  _debug2 DEPLOY_HAPROXY_BUNDLE "${DEPLOY_HAPROXY_BUNDLE}"
   if [ -n "${DEPLOY_HAPROXY_BUNDLE}" ]; then
     Le_Deploy_haproxy_bundle="${DEPLOY_HAPROXY_BUNDLE}"
     _savedomainconf Le_Deploy_haproxy_bundle "${Le_Deploy_haproxy_bundle}"
@@ -98,6 +99,8 @@ haproxy_deploy() {
   fi
 
   # ISSUER is optional. If not provided then assume "${DEPLOY_HAPROXY_ISSUER_DEFAULT}"
+  _getdeployconf DEPLOY_HAPROXY_ISSUER
+  _debug2 DEPLOY_HAPROXY_ISSUER "${DEPLOY_HAPROXY_ISSUER}"
   if [ -n "${DEPLOY_HAPROXY_ISSUER}" ]; then
     Le_Deploy_haproxy_issuer="${DEPLOY_HAPROXY_ISSUER}"
     _savedomainconf Le_Deploy_haproxy_issuer "${Le_Deploy_haproxy_issuer}"
@@ -106,6 +109,8 @@ haproxy_deploy() {
   fi
 
   # RELOAD is optional. If not provided then assume "${DEPLOY_HAPROXY_RELOAD_DEFAULT}"
+  _getdeployconf DEPLOY_HAPROXY_RELOAD
+  _debug2 DEPLOY_HAPROXY_RELOAD "${DEPLOY_HAPROXY_RELOAD}"
   if [ -n "${DEPLOY_HAPROXY_RELOAD}" ]; then
     Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD}"
     _savedomainconf Le_Deploy_haproxy_reload "${Le_Deploy_haproxy_reload}"
@@ -190,7 +195,7 @@ haproxy_deploy() {
     _info "Updating OCSP stapling info"
     _debug _ocsp "${_ocsp}"
     _info "Extracting OCSP URL"
-    _ocsp_url=$(openssl x509 -noout -ocsp_uri -in "${_pem}")
+    _ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in "${_pem}")
     _debug _ocsp_url "${_ocsp_url}"
 
     # Only process OCSP if URL was present
@@ -203,9 +208,9 @@ haproxy_deploy() {
       # Only process the certificate if we have a .issuer file
       if [ -r "${_issuer}" ]; then
         # Check if issuer cert is also a root CA cert
-        _subjectdn=$(openssl x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
+        _subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
         _debug _subjectdn "${_subjectdn}"
-        _issuerdn=$(openssl x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
+        _issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
         _debug _issuerdn "${_issuerdn}"
         _info "Requesting OCSP response"
         # If the issuer is a CA cert then our command line has "-CAfile" added
@@ -216,7 +221,7 @@ haproxy_deploy() {
         fi
         _debug _cafile_argument "${_cafile_argument}"
         # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
-        _openssl_version=$(openssl version | cut -d' ' -f2)
+        _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
         _debug _openssl_version "${_openssl_version}"
         _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
         _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
@@ -226,7 +231,7 @@ haproxy_deploy() {
           _header_sep=" "
         fi
         # Request the OCSP response from the issuer and store it
-        _openssl_ocsp_cmd="openssl ocsp \
+        _openssl_ocsp_cmd="${ACME_OPENSSL_BIN:-openssl} ocsp \
           -issuer \"${_issuer}\" \
           -cert \"${_pem}\" \
           -url \"${_ocsp_url}\" \
index 1e1e310ca579496fd61123eaecf0f38ac0674524..b8facedff4ce34c6f63c3c6027da7db7963eef4b 100755 (executable)
@@ -45,7 +45,7 @@ kong_deploy() {
   #Generate data for request (Multipart/form-data with mixed content)
   if [ -z "$ssl_uuid" ]; then
     #set sni to domain
-    content="--$delim${nl}Content-Disposition: form-data; name=\"snis\"${nl}${nl}$_cdomain"
+    content="--$delim${nl}Content-Disposition: form-data; name=\"snis[]\"${nl}${nl}$_cdomain"
   fi
   #add key
   content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")"
diff --git a/deploy/lighttpd.sh b/deploy/lighttpd.sh
new file mode 100644 (file)
index 0000000..71f64b9
--- /dev/null
@@ -0,0 +1,280 @@
+#!/usr/bin/env sh
+
+# Script for acme.sh to deploy certificates to lighttpd
+#
+# The following variables can be exported:
+#
+# export DEPLOY_LIGHTTPD_PEM_NAME="${domain}.pem"
+#
+# Defines the name of the PEM file.
+# Defaults to "<domain>.pem"
+#
+# export DEPLOY_LIGHTTPD_PEM_PATH="/etc/lighttpd"
+#
+# Defines location of PEM file for Lighttpd.
+# Defaults to /etc/lighttpd
+#
+# export DEPLOY_LIGHTTPD_RELOAD="systemctl reload lighttpd"
+#
+# OPTIONAL: Reload command used post deploy
+# This defaults to be a no-op (ie "true").
+# It is strongly recommended to set this something that makes sense
+# for your distro.
+#
+# export DEPLOY_LIGHTTPD_ISSUER="yes"
+#
+# OPTIONAL: Places CA file as "${DEPLOY_LIGHTTPD_PEM}.issuer"
+# Note: Required for OCSP stapling to work
+#
+# export DEPLOY_LIGHTTPD_BUNDLE="no"
+#
+# OPTIONAL: Deploy this certificate as part of a multi-cert bundle
+# This adds a suffix to the certificate based on the certificate type
+# eg RSA certificates will have .rsa as a suffix to the file name
+# Lighttpd will load all certificates and provide one or the other
+# depending on client capabilities
+# Note: This functionality requires Lighttpd was compiled against
+# a version of OpenSSL that supports this.
+#
+
+########  Public functions #####################
+
+#domain keyfile certfile cafile fullchain
+lighttpd_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  # Some defaults
+  DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT="/etc/lighttpd"
+  DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT="${_cdomain}.pem"
+  DEPLOY_LIGHTTPD_BUNDLE_DEFAULT="no"
+  DEPLOY_LIGHTTPD_ISSUER_DEFAULT="yes"
+  DEPLOY_LIGHTTPD_RELOAD_DEFAULT="true"
+
+  _debug _cdomain "${_cdomain}"
+  _debug _ckey "${_ckey}"
+  _debug _ccert "${_ccert}"
+  _debug _cca "${_cca}"
+  _debug _cfullchain "${_cfullchain}"
+
+  # PEM_PATH is optional. If not provided then assume "${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}"
+  _getdeployconf DEPLOY_LIGHTTPD_PEM_PATH
+  _debug2 DEPLOY_LIGHTTPD_PEM_PATH "${DEPLOY_LIGHTTPD_PEM_PATH}"
+  if [ -n "${DEPLOY_LIGHTTPD_PEM_PATH}" ]; then
+    Le_Deploy_lighttpd_pem_path="${DEPLOY_LIGHTTPD_PEM_PATH}"
+    _savedomainconf Le_Deploy_lighttpd_pem_path "${Le_Deploy_lighttpd_pem_path}"
+  elif [ -z "${Le_Deploy_lighttpd_pem_path}" ]; then
+    Le_Deploy_lighttpd_pem_path="${DEPLOY_LIGHTTPD_PEM_PATH_DEFAULT}"
+  fi
+
+  # Ensure PEM_PATH exists
+  if [ -d "${Le_Deploy_lighttpd_pem_path}" ]; then
+    _debug "PEM_PATH ${Le_Deploy_lighttpd_pem_path} exists"
+  else
+    _err "PEM_PATH ${Le_Deploy_lighttpd_pem_path} does not exist"
+    return 1
+  fi
+
+  # PEM_NAME is optional. If not provided then assume "${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}"
+  _getdeployconf DEPLOY_LIGHTTPD_PEM_NAME
+  _debug2 DEPLOY_LIGHTTPD_PEM_NAME "${DEPLOY_LIGHTTPD_PEM_NAME}"
+  if [ -n "${DEPLOY_LIGHTTPD_PEM_NAME}" ]; then
+    Le_Deploy_lighttpd_pem_name="${DEPLOY_LIGHTTPD_PEM_NAME}"
+    _savedomainconf Le_Deploy_lighttpd_pem_name "${Le_Deploy_lighttpd_pem_name}"
+  elif [ -z "${Le_Deploy_lighttpd_pem_name}" ]; then
+    Le_Deploy_lighttpd_pem_name="${DEPLOY_LIGHTTPD_PEM_NAME_DEFAULT}"
+  fi
+
+  # BUNDLE is optional. If not provided then assume "${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}"
+  _getdeployconf DEPLOY_LIGHTTPD_BUNDLE
+  _debug2 DEPLOY_LIGHTTPD_BUNDLE "${DEPLOY_LIGHTTPD_BUNDLE}"
+  if [ -n "${DEPLOY_LIGHTTPD_BUNDLE}" ]; then
+    Le_Deploy_lighttpd_bundle="${DEPLOY_LIGHTTPD_BUNDLE}"
+    _savedomainconf Le_Deploy_lighttpd_bundle "${Le_Deploy_lighttpd_bundle}"
+  elif [ -z "${Le_Deploy_lighttpd_bundle}" ]; then
+    Le_Deploy_lighttpd_bundle="${DEPLOY_LIGHTTPD_BUNDLE_DEFAULT}"
+  fi
+
+  # ISSUER is optional. If not provided then assume "${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}"
+  _getdeployconf DEPLOY_LIGHTTPD_ISSUER
+  _debug2 DEPLOY_LIGHTTPD_ISSUER "${DEPLOY_LIGHTTPD_ISSUER}"
+  if [ -n "${DEPLOY_LIGHTTPD_ISSUER}" ]; then
+    Le_Deploy_lighttpd_issuer="${DEPLOY_LIGHTTPD_ISSUER}"
+    _savedomainconf Le_Deploy_lighttpd_issuer "${Le_Deploy_lighttpd_issuer}"
+  elif [ -z "${Le_Deploy_lighttpd_issuer}" ]; then
+    Le_Deploy_lighttpd_issuer="${DEPLOY_LIGHTTPD_ISSUER_DEFAULT}"
+  fi
+
+  # RELOAD is optional. If not provided then assume "${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}"
+  _getdeployconf DEPLOY_LIGHTTPD_RELOAD
+  _debug2 DEPLOY_LIGHTTPD_RELOAD "${DEPLOY_LIGHTTPD_RELOAD}"
+  if [ -n "${DEPLOY_LIGHTTPD_RELOAD}" ]; then
+    Le_Deploy_lighttpd_reload="${DEPLOY_LIGHTTPD_RELOAD}"
+    _savedomainconf Le_Deploy_lighttpd_reload "${Le_Deploy_lighttpd_reload}"
+  elif [ -z "${Le_Deploy_lighttpd_reload}" ]; then
+    Le_Deploy_lighttpd_reload="${DEPLOY_LIGHTTPD_RELOAD_DEFAULT}"
+  fi
+
+  # Set the suffix depending if we are creating a bundle or not
+  if [ "${Le_Deploy_lighttpd_bundle}" = "yes" ]; then
+    _info "Bundle creation requested"
+    # Initialise $Le_Keylength if its not already set
+    if [ -z "${Le_Keylength}" ]; then
+      Le_Keylength=""
+    fi
+    if _isEccKey "${Le_Keylength}"; then
+      _info "ECC key type detected"
+      _suffix=".ecdsa"
+    else
+      _info "RSA key type detected"
+      _suffix=".rsa"
+    fi
+  else
+    _suffix=""
+  fi
+  _debug _suffix "${_suffix}"
+
+  # Set variables for later
+  _pem="${Le_Deploy_lighttpd_pem_path}/${Le_Deploy_lighttpd_pem_name}${_suffix}"
+  _issuer="${_pem}.issuer"
+  _ocsp="${_pem}.ocsp"
+  _reload="${Le_Deploy_lighttpd_reload}"
+
+  _info "Deploying PEM file"
+  # Create a temporary PEM file
+  _temppem="$(_mktemp)"
+  _debug _temppem "${_temppem}"
+  cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}"
+  _ret="$?"
+
+  # Check that we could create the temporary file
+  if [ "${_ret}" != "0" ]; then
+    _err "Error code ${_ret} returned during PEM file creation"
+    [ -f "${_temppem}" ] && rm -f "${_temppem}"
+    return ${_ret}
+  fi
+
+  # Move PEM file into place
+  _info "Moving new certificate into place"
+  _debug _pem "${_pem}"
+  cat "${_temppem}" >"${_pem}"
+  _ret=$?
+
+  # Clean up temp file
+  [ -f "${_temppem}" ] && rm -f "${_temppem}"
+
+  # Deal with any failure of moving PEM file into place
+  if [ "${_ret}" != "0" ]; then
+    _err "Error code ${_ret} returned while moving new certificate into place"
+    return ${_ret}
+  fi
+
+  # Update .issuer file if requested
+  if [ "${Le_Deploy_lighttpd_issuer}" = "yes" ]; then
+    _info "Updating .issuer file"
+    _debug _issuer "${_issuer}"
+    cat "${_cca}" >"${_issuer}"
+    _ret="$?"
+
+    if [ "${_ret}" != "0" ]; then
+      _err "Error code ${_ret} returned while copying issuer/CA certificate into place"
+      return ${_ret}
+    fi
+  else
+    [ -f "${_issuer}" ] && _err "Issuer file update not requested but .issuer file exists"
+  fi
+
+  # Update .ocsp file if certificate was requested with --ocsp/--ocsp-must-staple option
+  if [ -z "${Le_OCSP_Staple}" ]; then
+    Le_OCSP_Staple="0"
+  fi
+  if [ "${Le_OCSP_Staple}" = "1" ]; then
+    _info "Updating OCSP stapling info"
+    _debug _ocsp "${_ocsp}"
+    _info "Extracting OCSP URL"
+    _ocsp_url=$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -ocsp_uri -in "${_pem}")
+    _debug _ocsp_url "${_ocsp_url}"
+
+    # Only process OCSP if URL was present
+    if [ "${_ocsp_url}" != "" ]; then
+      # Extract the hostname from the OCSP URL
+      _info "Extracting OCSP URL"
+      _ocsp_host=$(echo "${_ocsp_url}" | cut -d/ -f3)
+      _debug _ocsp_host "${_ocsp_host}"
+
+      # Only process the certificate if we have a .issuer file
+      if [ -r "${_issuer}" ]; then
+        # Check if issuer cert is also a root CA cert
+        _subjectdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -subject -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
+        _debug _subjectdn "${_subjectdn}"
+        _issuerdn=$(${ACME_OPENSSL_BIN:-openssl} x509 -in "${_issuer}" -issuer -noout | cut -d'/' -f2,3,4,5,6,7,8,9,10)
+        _debug _issuerdn "${_issuerdn}"
+        _info "Requesting OCSP response"
+        # If the issuer is a CA cert then our command line has "-CAfile" added
+        if [ "${_subjectdn}" = "${_issuerdn}" ]; then
+          _cafile_argument="-CAfile \"${_issuer}\""
+        else
+          _cafile_argument=""
+        fi
+        _debug _cafile_argument "${_cafile_argument}"
+        # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed
+        _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2)
+        _debug _openssl_version "${_openssl_version}"
+        _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1)
+        _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2)
+        if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then
+          _header_sep="="
+        else
+          _header_sep=" "
+        fi
+        # Request the OCSP response from the issuer and store it
+        _openssl_ocsp_cmd="${ACME_OPENSSL_BIN:-openssl} ocsp \
+          -issuer \"${_issuer}\" \
+          -cert \"${_pem}\" \
+          -url \"${_ocsp_url}\" \
+          -header Host${_header_sep}\"${_ocsp_host}\" \
+          -respout \"${_ocsp}\" \
+          -verify_other \"${_issuer}\" \
+          ${_cafile_argument} \
+          | grep -q \"${_pem}: good\""
+        _debug _openssl_ocsp_cmd "${_openssl_ocsp_cmd}"
+        eval "${_openssl_ocsp_cmd}"
+        _ret=$?
+      else
+        # Non fatal: No issuer file was present so no OCSP stapling file created
+        _err "OCSP stapling in use but no .issuer file was present"
+      fi
+    else
+      # Non fatal: No OCSP url was found int the certificate
+      _err "OCSP update requested but no OCSP URL was found in certificate"
+    fi
+
+    # Non fatal: Check return code of openssl command
+    if [ "${_ret}" != "0" ]; then
+      _err "Updating OCSP stapling failed with return code ${_ret}"
+    fi
+  else
+    # An OCSP file was already present but certificate did not have OCSP extension
+    if [ -f "${_ocsp}" ]; then
+      _err "OCSP was not requested but .ocsp file exists."
+      # Could remove the file at this step, although Lighttpd just ignores it in this case
+      # rm -f "${_ocsp}" || _err "Problem removing stale .ocsp file"
+    fi
+  fi
+
+  # Reload Lighttpd
+  _debug _reload "${_reload}"
+  eval "${_reload}"
+  _ret=$?
+  if [ "${_ret}" != "0" ]; then
+    _err "Error code ${_ret} during reload"
+    return ${_ret}
+  else
+    _info "Reload successful"
+  fi
+
+  return 0
+}
index 18de4aa650756be2e6b7cceee87b333392959972..89962621071a0e949e18b88ff1079b8ebfe95ebc 100644 (file)
@@ -35,11 +35,6 @@ ssh_deploy() {
   _cfullchain="$5"
   _deploy_ssh_servers=""
 
-  if [ -f "$DOMAIN_CONF" ]; then
-    # shellcheck disable=SC1090
-    . "$DOMAIN_CONF"
-  fi
-
   _debug _cdomain "$_cdomain"
   _debug _ckey "$_ckey"
   _debug _ccert "$_ccert"
@@ -47,6 +42,8 @@ ssh_deploy() {
   _debug _cfullchain "$_cfullchain"
 
   # USER is required to login by SSH to remote host.
+  _getdeployconf DEPLOY_SSH_USER
+  _debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER"
   if [ -z "$DEPLOY_SSH_USER" ]; then
     if [ -z "$Le_Deploy_ssh_user" ]; then
       _err "DEPLOY_SSH_USER not defined."
@@ -58,6 +55,8 @@ ssh_deploy() {
   fi
 
   # SERVER is optional. If not provided then use _cdomain
+  _getdeployconf DEPLOY_SSH_SERVER
+  _debug2 DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER"
   if [ -n "$DEPLOY_SSH_SERVER" ]; then
     Le_Deploy_ssh_server="$DEPLOY_SSH_SERVER"
     _savedomainconf Le_Deploy_ssh_server "$Le_Deploy_ssh_server"
@@ -66,6 +65,8 @@ ssh_deploy() {
   fi
 
   # CMD is optional. If not provided then use ssh
+  _getdeployconf DEPLOY_SSH_CMD
+  _debug2 DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD"
   if [ -n "$DEPLOY_SSH_CMD" ]; then
     Le_Deploy_ssh_cmd="$DEPLOY_SSH_CMD"
     _savedomainconf Le_Deploy_ssh_cmd "$Le_Deploy_ssh_cmd"
@@ -74,6 +75,8 @@ ssh_deploy() {
   fi
 
   # BACKUP is optional. If not provided then default to previously saved value or yes.
+  _getdeployconf DEPLOY_SSH_BACKUP
+  _debug2 DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP"
   if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then
     Le_Deploy_ssh_backup="no"
   elif [ -z "$Le_Deploy_ssh_backup" ] || [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then
@@ -82,6 +85,8 @@ ssh_deploy() {
   _savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup"
 
   # BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy
+  _getdeployconf DEPLOY_SSH_BACKUP_PATH
+  _debug2 DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH"
   if [ -n "$DEPLOY_SSH_BACKUP_PATH" ]; then
     Le_Deploy_ssh_backup_path="$DEPLOY_SSH_BACKUP_PATH"
   elif [ -z "$Le_Deploy_ssh_backup_path" ]; then
@@ -91,6 +96,8 @@ ssh_deploy() {
 
   # MULTI_CALL is optional. If not provided then default to previously saved
   # value (which may be undefined... equivalent to "no").
+  _getdeployconf DEPLOY_SSH_MULTI_CALL
+  _debug2 DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL"
   if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then
     Le_Deploy_ssh_multi_call="yes"
     _savedomainconf Le_Deploy_ssh_multi_call "$Le_Deploy_ssh_multi_call"
@@ -141,6 +148,8 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
 
   # KEYFILE is optional.
   # If provided then private key will be copied to provided filename.
+  _getdeployconf DEPLOY_SSH_KEYFILE
+  _debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE"
   if [ -n "$DEPLOY_SSH_KEYFILE" ]; then
     Le_Deploy_ssh_keyfile="$DEPLOY_SSH_KEYFILE"
     _savedomainconf Le_Deploy_ssh_keyfile "$Le_Deploy_ssh_keyfile"
@@ -163,6 +172,8 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
 
   # CERTFILE is optional.
   # If provided then certificate will be copied or appended to provided filename.
+  _getdeployconf DEPLOY_SSH_CERTFILE
+  _debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE"
   if [ -n "$DEPLOY_SSH_CERTFILE" ]; then
     Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE"
     _savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile"
@@ -189,6 +200,8 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
 
   # CAFILE is optional.
   # If provided then CA intermediate certificate will be copied or appended to provided filename.
+  _getdeployconf DEPLOY_SSH_CAFILE
+  _debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE"
   if [ -n "$DEPLOY_SSH_CAFILE" ]; then
     Le_Deploy_ssh_cafile="$DEPLOY_SSH_CAFILE"
     _savedomainconf Le_Deploy_ssh_cafile "$Le_Deploy_ssh_cafile"
@@ -216,6 +229,8 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
 
   # FULLCHAIN is optional.
   # If provided then fullchain certificate will be copied or appended to provided filename.
+  _getdeployconf DEPLOY_SSH_FULLCHAIN
+  _debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN"
   if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then
     Le_Deploy_ssh_fullchain="$DEPLOY_SSH_FULLCHAIN"
     _savedomainconf Le_Deploy_ssh_fullchain "$Le_Deploy_ssh_fullchain"
@@ -244,6 +259,8 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d
 
   # REMOTE_CMD is optional.
   # If provided then this command will be executed on remote host.
+  _getdeployconf DEPLOY_SSH_REMOTE_CMD
+  _debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD"
   if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then
     Le_Deploy_ssh_remote_cmd="$DEPLOY_SSH_REMOTE_CMD"
     _savedomainconf Le_Deploy_ssh_remote_cmd "$Le_Deploy_ssh_remote_cmd"
index 35d332099a15118a148ed7f28fd222c96a7a0c1e..177b3fbed83dc823ac3bd394cd05fe45104dd2fb 100644 (file)
@@ -66,6 +66,12 @@ synology_dsm_deploy() {
   _getdeployconf SYNO_Certificate
   _debug SYNO_Certificate "${SYNO_Certificate:-}"
 
+  # shellcheck disable=SC1003 # We are not trying to escape a single quote
+  if printf "%s" "$SYNO_Certificate" | grep '\\'; then
+    _err "Do not use a backslash (\) in your certificate description"
+    return 1
+  fi
+
   _base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port"
   _debug _base_url "$_base_url"
 
@@ -94,6 +100,7 @@ synology_dsm_deploy() {
   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, you have to choose another user."
     return 1
   fi
   sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p')
@@ -110,7 +117,9 @@ synology_dsm_deploy() {
   _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"
-  id=$(echo "$response" | sed -n "s/.*\"desc\":\"$SYNO_Certificate\",\"id\":\"\([^\"]*\).*/\1/p")
+  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
@@ -119,13 +128,7 @@ synology_dsm_deploy() {
   fi
 
   # we've verified this certificate description is a thing, so save it
-  _savedeployconf SYNO_Certificate "$SYNO_Certificate"
-
-  default=false
-  if echo "$response" | sed -n "s/.*\"desc\":\"$SYNO_Certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
-    default=true
-  fi
-  _debug2 default "$default"
+  _savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64"
 
   _info "Generate form POST request"
   nl="\0015\0012"
@@ -135,7 +138,12 @@ synology_dsm_deploy() {
   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=\"as_default\"${nl}${nl}${default}"
+  if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then
+    _debug2 default "this is the default certificate"
+    content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true"
+  else
+    _debug2 default "this is NOT the default certificate"
+  fi
   content="$content${nl}--$delim--${nl}"
   content="$(printf "%b_" "$content")"
   content="${content%_}" # protect trailing \n
index 184aa62e8850eb079b3800250eab006979ffc8dd..a864135e82cd0895536d8514f47dcc6ad680425d 100644 (file)
@@ -1,12 +1,43 @@
 #!/usr/bin/env sh
 
-#Here is a script to deploy cert to unifi server.
+# Here is a script to deploy cert on a Unifi Controller or Cloud Key device.
+# It supports:
+#   - self-hosted Unifi Controller
+#   - Unifi Cloud Key (Gen1/2/2+)
+#   - Unifi Cloud Key running UnifiOS (v2.0.0+, Gen2/2+ only)
+# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3359
 
 #returns 0 means success, otherwise error.
 
+# The deploy-hook automatically detects standard Unifi installations
+# for each of the supported environments. Most users should not need
+# to set any of these variables, but if you are running a self-hosted
+# Controller with custom locations, set these as necessary before running
+# the deploy hook. (Defaults shown below.)
+#
+# Settings for Unifi Controller:
+# Location of Java keystore or unifi.keystore.jks file:
 #DEPLOY_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore"
+# Keystore password (built into Unifi Controller, not a user-set password):
 #DEPLOY_UNIFI_KEYPASS="aircontrolenterprise"
+# Command to restart Unifi Controller:
 #DEPLOY_UNIFI_RELOAD="service unifi restart"
+#
+# Settings for Unifi Cloud Key Gen1 (nginx admin pages):
+# Directory where cloudkey.crt and cloudkey.key live:
+#DEPLOY_UNIFI_CLOUDKEY_CERTDIR="/etc/ssl/private"
+# Command to restart maintenance pages and Controller
+# (same setting as above, default is updated when running on Cloud Key Gen1):
+#DEPLOY_UNIFI_RELOAD="service nginx restart && service unifi restart"
+#
+# Settings for UnifiOS (Cloud Key Gen2):
+# Directory where unifi-core.crt and unifi-core.key live:
+#DEPLOY_UNIFI_CORE_CONFIG="/data/unifi-core/config/"
+# Command to restart unifi-core:
+#DEPLOY_UNIFI_RELOAD="systemctl restart unifi-core"
+#
+# At least one of DEPLOY_UNIFI_KEYSTORE, DEPLOY_UNIFI_CLOUDKEY_CERTDIR,
+# or DEPLOY_UNIFI_CORE_CONFIG must exist to receive the deployed certs.
 
 ########  Public functions #####################
 
@@ -24,77 +55,160 @@ unifi_deploy() {
   _debug _cca "$_cca"
   _debug _cfullchain "$_cfullchain"
 
-  if ! _exists keytool; then
-    _err "keytool not found"
-    return 1
-  fi
+  _getdeployconf DEPLOY_UNIFI_KEYSTORE
+  _getdeployconf DEPLOY_UNIFI_KEYPASS
+  _getdeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR
+  _getdeployconf DEPLOY_UNIFI_CORE_CONFIG
+  _getdeployconf DEPLOY_UNIFI_RELOAD
+
+  _debug2 DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
+  _debug2 DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
+  _debug2 DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
+  _debug2 DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
+  _debug2 DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
+
+  # Space-separated list of environments detected and installed:
+  _services_updated=""
+
+  # Default reload commands accumulated as we auto-detect environments:
+  _reload_cmd=""
+
+  # Unifi Controller environment (self hosted or any Cloud Key) --
+  # auto-detect by file /usr/lib/unifi/data/keystore:
+  _unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-/usr/lib/unifi/data/keystore}"
+  if [ -f "$_unifi_keystore" ]; then
+    _info "Installing certificate for Unifi Controller (Java keystore)"
+    _debug _unifi_keystore "$_unifi_keystore"
+    if ! _exists keytool; then
+      _err "keytool not found"
+      return 1
+    fi
+    if [ ! -w "$_unifi_keystore" ]; then
+      _err "The file $_unifi_keystore is not writable, please change the permission."
+      return 1
+    fi
+
+    _unifi_keypass="${DEPLOY_UNIFI_KEYPASS:-aircontrolenterprise}"
 
-  DEFAULT_UNIFI_KEYSTORE="/usr/lib/unifi/data/keystore"
-  _unifi_keystore="${DEPLOY_UNIFI_KEYSTORE:-$DEFAULT_UNIFI_KEYSTORE}"
-  DEFAULT_UNIFI_KEYPASS="aircontrolenterprise"
-  _unifi_keypass="${DEPLOY_UNIFI_KEYPASS:-$DEFAULT_UNIFI_KEYPASS}"
-  DEFAULT_UNIFI_RELOAD="service unifi restart"
-  _reload="${DEPLOY_UNIFI_RELOAD:-$DEFAULT_UNIFI_RELOAD}"
-
-  _debug _unifi_keystore "$_unifi_keystore"
-  if [ ! -f "$_unifi_keystore" ]; then
-    if [ -z "$DEPLOY_UNIFI_KEYSTORE" ]; then
-      _err "unifi keystore is not found, please define DEPLOY_UNIFI_KEYSTORE"
+    _debug "Generate import pkcs12"
+    _import_pkcs12="$(_mktemp)"
+    _toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root
+    # shellcheck disable=SC2181
+    if [ "$?" != "0" ]; then
+      _err "Error generating pkcs12. Please re-run with --debug and report a bug."
       return 1
+    fi
+
+    _debug "Import into keystore: $_unifi_keystore"
+    if keytool -importkeystore \
+      -deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
+      -srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
+      -alias unifi -noprompt; then
+      _debug "Import keystore success!"
+      rm "$_import_pkcs12"
     else
-      _err "It seems that the specified unifi keystore is not valid, please check."
+      _err "Error importing into Unifi Java keystore."
+      _err "Please re-run with --debug and report a bug."
+      rm "$_import_pkcs12"
       return 1
     fi
+
+    if systemctl -q is-active unifi; then
+      _reload_cmd="${_reload_cmd:+$_reload_cmd && }service unifi restart"
+    fi
+    _services_updated="${_services_updated} unifi"
+    _info "Install Unifi Controller certificate success!"
+  elif [ "$DEPLOY_UNIFI_KEYSTORE" ]; then
+    _err "The specified DEPLOY_UNIFI_KEYSTORE='$DEPLOY_UNIFI_KEYSTORE' is not valid, please check."
+    return 1
   fi
-  if [ ! -w "$_unifi_keystore" ]; then
-    _err "The file $_unifi_keystore is not writable, please change the permission."
+
+  # Cloud Key environment (non-UnifiOS -- nginx serves admin pages) --
+  # auto-detect by file /etc/ssl/private/cloudkey.key:
+  _cloudkey_certdir="${DEPLOY_UNIFI_CLOUDKEY_CERTDIR:-/etc/ssl/private}"
+  if [ -f "${_cloudkey_certdir}/cloudkey.key" ]; then
+    _info "Installing certificate for Cloud Key Gen1 (nginx admin pages)"
+    _debug _cloudkey_certdir "$_cloudkey_certdir"
+    if [ ! -w "$_cloudkey_certdir" ]; then
+      _err "The directory $_cloudkey_certdir is not writable; please check permissions."
+      return 1
+    fi
+    # Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks.
+    # Normally /usr/lib/unifi/data/keystore is a symlink there (so the keystore was
+    # updated above), but if not, we don't know how to handle this installation:
+    if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then
+      _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'"
+      return 1
+    fi
+
+    cat "$_cfullchain" >"${_cloudkey_certdir}/cloudkey.crt"
+    cat "$_ckey" >"${_cloudkey_certdir}/cloudkey.key"
+    (cd "$_cloudkey_certdir" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks)
+
+    if systemctl -q is-active nginx; then
+      _reload_cmd="${_reload_cmd:+$_reload_cmd && }service nginx restart"
+    fi
+    _info "Install Cloud Key Gen1 certificate success!"
+    _services_updated="${_services_updated} nginx"
+  elif [ "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR" ]; then
+    _err "The specified DEPLOY_UNIFI_CLOUDKEY_CERTDIR='$DEPLOY_UNIFI_CLOUDKEY_CERTDIR' is not valid, please check."
     return 1
   fi
 
-  _info "Generate import pkcs12"
-  _import_pkcs12="$(_mktemp)"
-  _toPkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca" "$_unifi_keypass" unifi root
-  if [ "$?" != "0" ]; then
-    _err "Oops, error creating import pkcs12, please report bug to us."
+  # UnifiOS environment -- auto-detect by /data/unifi-core/config/unifi-core.key:
+  _unifi_core_config="${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}"
+  if [ -f "${_unifi_core_config}/unifi-core.key" ]; then
+    _info "Installing certificate for UnifiOS"
+    _debug _unifi_core_config "$_unifi_core_config"
+    if [ ! -w "$_unifi_core_config" ]; then
+      _err "The directory $_unifi_core_config is not writable; please check permissions."
+      return 1
+    fi
+
+    cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt"
+    cat "$_ckey" >"${_unifi_core_config}/unifi-core.key"
+
+    if systemctl -q is-active unifi-core; then
+      _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi-core"
+    fi
+    _info "Install UnifiOS certificate success!"
+    _services_updated="${_services_updated} unifi-core"
+  elif [ "$DEPLOY_UNIFI_CORE_CONFIG" ]; then
+    _err "The specified DEPLOY_UNIFI_CORE_CONFIG='$DEPLOY_UNIFI_CORE_CONFIG' is not valid, please check."
     return 1
   fi
 
-  _info "Modify unifi keystore: $_unifi_keystore"
-  if keytool -importkeystore \
-    -deststorepass "$_unifi_keypass" -destkeypass "$_unifi_keypass" -destkeystore "$_unifi_keystore" \
-    -srckeystore "$_import_pkcs12" -srcstoretype PKCS12 -srcstorepass "$_unifi_keypass" \
-    -alias unifi -noprompt; then
-    _info "Import keystore success!"
-    rm "$_import_pkcs12"
-  else
-    _err "Import unifi keystore error, please report bug to us."
-    rm "$_import_pkcs12"
+  if [ -z "$_services_updated" ]; then
+    # None of the Unifi environments were auto-detected, so no deployment has occurred
+    # (and none of DEPLOY_UNIFI_{KEYSTORE,CLOUDKEY_CERTDIR,CORE_CONFIG} were set).
+    _err "Unable to detect Unifi environment in standard location."
+    _err "(This deploy hook must be run on the Unifi device, not a remote machine.)"
+    _err "For non-standard Unifi installations, set DEPLOY_UNIFI_KEYSTORE,"
+    _err "DEPLOY_UNIFI_CLOUDKEY_CERTDIR, and/or DEPLOY_UNIFI_CORE_CONFIG as appropriate."
     return 1
   fi
 
-  _info "Run reload: $_reload"
-  if eval "$_reload"; then
+  _reload_cmd="${DEPLOY_UNIFI_RELOAD:-$_reload_cmd}"
+  if [ -z "$_reload_cmd" ]; then
+    _err "Certificates were installed for services:${_services_updated},"
+    _err "but none appear to be active. Please set DEPLOY_UNIFI_RELOAD"
+    _err "to a command that will restart the necessary services."
+    return 1
+  fi
+  _info "Reload services (this may take some time): $_reload_cmd"
+  if eval "$_reload_cmd"; then
     _info "Reload success!"
-    if [ "$DEPLOY_UNIFI_KEYSTORE" ]; then
-      _savedomainconf DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
-    else
-      _cleardomainconf DEPLOY_UNIFI_KEYSTORE
-    fi
-    if [ "$DEPLOY_UNIFI_KEYPASS" ]; then
-      _savedomainconf DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
-    else
-      _cleardomainconf DEPLOY_UNIFI_KEYPASS
-    fi
-    if [ "$DEPLOY_UNIFI_RELOAD" ]; then
-      _savedomainconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
-    else
-      _cleardomainconf DEPLOY_UNIFI_RELOAD
-    fi
-    return 0
   else
     _err "Reload error"
     return 1
   fi
-  return 0
 
+  # Successful, so save all (non-default) config:
+  _savedeployconf DEPLOY_UNIFI_KEYSTORE "$DEPLOY_UNIFI_KEYSTORE"
+  _savedeployconf DEPLOY_UNIFI_KEYPASS "$DEPLOY_UNIFI_KEYPASS"
+  _savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR"
+  _savedeployconf DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG"
+  _savedeployconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD"
+
+  return 0
 }
index 8b85413771927007d1e25844604b7669381d208e..cbb8cc5925dcab08ea364cab5655b1fdc961b6d5 100644 (file)
@@ -50,12 +50,12 @@ vault_cli_deploy() {
   fi
 
   if [ -n "$FABIO" ]; then
-    $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1
+    $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1
   else
-    $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
-    $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
-    $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
-    $VAULT_CMD write "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
+    $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1
+    $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1
+    $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1
+    $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1
   fi
 
 }
index d720c1c5741de195dfd461281cd0901200566130..db0cbe15aba8b46d04d301f3e5e6cd1c85bca4b4 100755 (executable)
@@ -46,7 +46,7 @@ dns_1984hosting_add() {
 
   postdata="entry=new"
   postdata="$postdata&type=TXT"
-  postdata="$postdata&ttl=3600"
+  postdata="$postdata&ttl=900"
   postdata="$postdata&zone=$_domain"
   postdata="$postdata&host=$_sub_domain"
   postdata="$postdata&rdata=%22$value%22"
@@ -59,7 +59,7 @@ dns_1984hosting_add() {
   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
+  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
@@ -93,20 +93,15 @@ dns_1984hosting_rm() {
   fi
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
-
   _debug "Delete $fulldomain TXT record"
-  url="https://management.1984hosting.com/domains"
 
-  _htmlget "$url" "$_domain"
-  _debug2 _response "$_response"
-  zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+')"
-  _debug2 zone_id "$zone_id"
-  if [ -z "$zone_id" ]; then
-    _err "Error getting zone_id for $1"
+  url="https://management.1984hosting.com/domains"
+  if ! _get_zone_id "$url" "$_domain"; then
+    _err "invalid zone" "$_domain"
     return 1
   fi
 
-  _htmlget "$url/$zone_id" "$_sub_domain"
+  _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"
@@ -135,7 +130,7 @@ dns_1984hosting_rm() {
 _1984hosting_login() {
   if ! _check_credentials; then return 1; fi
 
-  if _check_cookie; then
+  if _check_cookies; then
     _debug "Already logged in"
     return 0
   fi
@@ -145,14 +140,17 @@ _1984hosting_login() {
   password=$(printf '%s' "$One984HOSTING_Password" | _url_encode)
   url="https://management.1984hosting.com/accounts/checkuserauth/"
 
-  response="$(_post "username=$username&password=$password&otpkey=" "$url")"
+  response="$(_post "username=$username&password=$password&otpkey=" $url)"
   response="$(echo "$response" | _normalizeJson)"
   _debug2 response "$response"
 
   if _contains "$response" '"loggedin": true'; then
-    One984HOSTING_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _tail_n 1 | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
-    export One984HOSTING_COOKIE
-    _saveaccountconf_mutable One984HOSTING_COOKIE "$One984HOSTING_COOKIE"
+    One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')"
+    One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')"
+    export One984HOSTING_SESSIONID_COOKIE
+    export One984HOSTING_CSRFTOKEN_COOKIE
+    _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
+    _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
     return 0
   fi
   return 1
@@ -169,22 +167,24 @@ _check_credentials() {
   return 0
 }
 
-_check_cookie() {
-  One984HOSTING_COOKIE="${One984HOSTING_COOKIE:-$(_readaccountconf_mutable One984HOSTING_COOKIE)}"
-  if [ -z "$One984HOSTING_COOKIE" ]; then
-    _debug "No cached cookie found"
+_check_cookies() {
+  One984HOSTING_SESSIONID_COOKIE="${One984HOSTING_SESSIONID_COOKIE:-$(_readaccountconf_mutable One984HOSTING_SESSIONID_COOKIE)}"
+  One984HOSTING_CSRFTOKEN_COOKIE="${One984HOSTING_CSRFTOKEN_COOKIE:-$(_readaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE)}"
+  if [ -z "$One984HOSTING_SESSIONID_COOKIE" ] || [ -z "$One984HOSTING_CSRFTOKEN_COOKIE" ]; then
+    _debug "No cached cookie(s) found"
     return 1
   fi
 
   _authget "https://management.1984hosting.com/accounts/loginstatus/"
-  response="$(echo "$_response" | _normalizeJson)"
   if _contains "$response" '"ok": true'; then
-    _debug "Cached cookie still valid"
+    _debug "Cached cookies still valid"
     return 0
   fi
-  _debug "Cached cookie no longer valid"
-  One984HOSTING_COOKIE=""
-  _saveaccountconf_mutable One984HOSTING_COOKIE "$One984HOSTING_COOKIE"
+  _debug "Cached cookies no longer valid"
+  One984HOSTING_SESSIONID_COOKIE=""
+  One984HOSTING_CSRFTOKEN_COOKIE=""
+  _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE"
+  _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE"
   return 1
 }
 
@@ -194,7 +194,7 @@ _check_cookie() {
 # _domain=domain.com
 _get_root() {
   domain="$1"
-  i=2
+  i=1
   p=1
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@@ -205,7 +205,7 @@ _get_root() {
     fi
 
     _authget "https://management.1984hosting.com/domains/soacheck/?zone=$h&nameserver=ns0.1984.is."
-    if _contains "$_response" "serial"; then
+    if _contains "$_response" "serial" && ! _contains "$_response" "null"; then
       _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
       _domain="$h"
       return 0
@@ -216,21 +216,46 @@ _get_root() {
   return 1
 }
 
+#usage: _get_zone_id url domain.com
+#returns zone id for domain.com
+_get_zone_id() {
+  url=$1
+  domain=$2
+  _htmlget "$url" "$domain"
+  _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"
+    return 1
+  fi
+  return 0
+}
+
 # add extra headers to request
 _authget() {
-  export _H1="Cookie: $One984HOSTING_COOKIE"
-  _response=$(_get "$1")
+  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
 _htmlget() {
-  export _H1="Cookie: $One984HOSTING_COOKIE"
-  _response=$(_get "$1" | grep "$2" | _head_n 1)
+  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
 }
 
 # add extra headers to request
 _authpost() {
-  export _H1="Cookie: $One984HOSTING_COOKIE"
+  url="https://management.1984hosting.com/domains"
+  _get_zone_id "$url" "$_domain"
+  csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")"
+  export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE"
+  export _H2="Referer: https://management.1984hosting.com/domains/$_zone_id"
+  export _H3="X-CSRFToken: $csrf_header"
   _response=$(_post "$1" "$2")
 }
index ca1f56c7e3a33555e0ebb186a78df16a45e7266d..4c9217e5828dd93804328b4d45d455b933102251 100644 (file)
@@ -1,10 +1,9 @@
 #!/usr/bin/env sh
 
-#Arvan_Token="xxxx"
+#Arvan_Token="Apikey xxxx"
 
 ARVAN_API_URL="https://napi.arvancloud.com/cdn/4.0/domains"
-
-#Author: Ehsan Aliakbar
+#Author: Vahid Fardi
 #Report Bugs here: https://github.com/Neilpang/acme.sh
 #
 ########  Public functions #####################
@@ -38,6 +37,7 @@ dns_arvan_add() {
   _info "Adding record"
   if _arvan_rest POST "$_domain/dns-records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":{\"text\":\"$txtvalue\"},\"ttl\":120}"; then
     if _contains "$response" "$txtvalue"; then
+      _info "response id is $response"
       _info "Added, OK"
       return 0
     elif _contains "$response" "Record Data is Duplicated"; then
@@ -49,7 +49,7 @@ dns_arvan_add() {
     fi
   fi
   _err "Add txt record error."
-  return 1
+  return 0
 }
 
 #Usage: fulldomain txtvalue
@@ -73,33 +73,21 @@ dns_arvan_rm() {
   _debug _domain "$_domain"
 
   _debug "Getting txt records"
-  shorted_txtvalue=$(printf "%s" "$txtvalue" | cut -d "-" -d "_" -f1)
-  _arvan_rest GET "${_domain}/dns-records?search=$shorted_txtvalue"
-
+  _arvan_rest GET "${_domain}/dns-records"
   if ! printf "%s" "$response" | grep \"current_page\":1 >/dev/null; then
     _err "Error on Arvan Api"
     _err "Please create a github issue with debbug log"
     return 1
   fi
 
-  count=$(printf "%s\n" "$response" | _egrep_o "\"total\":[^,]*" | cut -d : -f 2)
-  _debug count "$count"
-  if [ "$count" = "0" ]; then
-    _info "Don't need to remove."
-  else
-    record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1)
-    _debug "record_id" "$record_id"
-    if [ -z "$record_id" ]; then
-      _err "Can not get record id to remove."
-      return 1
-    fi
-    if ! _arvan_rest "DELETE" "${_domain}/dns-records/$record_id"; then
-      _err "Delete record error."
-      return 1
-    fi
-    _debug "$response"
-    _contains "$response" 'dns record deleted'
+  _record_id=$(echo "$response" | _egrep_o ".\"id\":\"[^\"]*\",\"type\":\"txt\",\"name\":\"_acme-challenge\",\"value\":{\"text\":\"$txtvalue\"}" | cut -d : -f 2 | cut -d , -f 1 | tr -d \")
+  if ! _arvan_rest "DELETE" "${_domain}/dns-records/${_record_id}"; then
+    _err "Error on Arvan Api"
+    return 1
   fi
+  _debug "$response"
+  _contains "$response" 'dns record deleted'
+  return 0
 }
 
 ####################  Private functions below ##################################
@@ -111,7 +99,7 @@ dns_arvan_rm() {
 # _domain_id=sdjkglgdfewsdfg
 _get_root() {
   domain=$1
-  i=1
+  i=2
   p=1
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@@ -121,12 +109,11 @@ _get_root() {
       return 1
     fi
 
-    if ! _arvan_rest GET "?search=$h"; then
+    if ! _arvan_rest GET "$h"; then
       return 1
     fi
-
-    if _contains "$response" "\"domain\":\"$h\"" || _contains "$response" '"total":1'; then
-      _domain_id=$(echo "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \")
+    if _contains "$response" "\"domain\":\"$h\""; then
+      _domain_id=$(echo "$response" | cut -d : -f 3 | cut -d , -f 1 | tr -d \")
       if [ "$_domain_id" ]; then
         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
         _domain=$h
@@ -146,7 +133,6 @@ _arvan_rest() {
   data="$3"
 
   token_trimmed=$(echo "$Arvan_Token" | tr -d '"')
-
   export _H1="Authorization: $token_trimmed"
 
   if [ "$mtd" = "DELETE" ]; then
@@ -160,4 +146,5 @@ _arvan_rest() {
   else
     response="$(_get "$ARVAN_API_URL/$ep$data")"
   fi
+  return 0
 }
diff --git a/dnsapi/dns_aurora.sh b/dnsapi/dns_aurora.sh
new file mode 100644 (file)
index 0000000..00f4473
--- /dev/null
@@ -0,0 +1,171 @@
+#!/usr/bin/env sh
+
+#
+#AURORA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+#
+#AURORA_Secret="sdfsdfsdfljlbjkljlkjsdfoiwje"
+
+AURORA_Api="https://api.auroradns.eu"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_aurora_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  AURORA_Key="${AURORA_Key:-$(_readaccountconf_mutable AURORA_Key)}"
+  AURORA_Secret="${AURORA_Secret:-$(_readaccountconf_mutable AURORA_Secret)}"
+
+  if [ -z "$AURORA_Key" ] || [ -z "$AURORA_Secret" ]; then
+    AURORA_Key=""
+    AURORA_Secret=""
+    _err "You didn't specify an Aurora api key and secret yet."
+    _err "You can get yours from here https://cp.pcextreme.nl/auroradns/users."
+    return 1
+  fi
+
+  #save the api key and secret to the account conf file.
+  _saveaccountconf_mutable AURORA_Key "$AURORA_Key"
+  _saveaccountconf_mutable AURORA_Secret "$AURORA_Secret"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _info "Adding record"
+  if _aurora_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":300}"; then
+    if _contains "$response" "$txtvalue"; then
+      _info "Added, OK"
+      return 0
+    elif _contains "$response" "RecordExistsError"; then
+      _info "Already exists, OK"
+      return 0
+    else
+      _err "Add txt record error."
+      return 1
+    fi
+  fi
+  _err "Add txt record error."
+  return 1
+
+}
+
+#fulldomain txtvalue
+dns_aurora_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  AURORA_Key="${AURORA_Key:-$(_readaccountconf_mutable AURORA_Key)}"
+  AURORA_Secret="${AURORA_Secret:-$(_readaccountconf_mutable AURORA_Secret)}"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting records"
+  _aurora_rest GET "zones/${_domain_id}/records"
+
+  if ! _contains "$response" "$txtvalue"; then
+    _info "Don't need to remove."
+  else
+    records=$(echo "$response" | _normalizeJson | tr -d "[]" | sed "s/},{/}|{/g" | tr "|" "\n")
+    if [ "$(echo "$records" | wc -l)" -le 2 ]; then
+      _err "Can not parse records."
+      return 1
+    fi
+    record_id=$(echo "$records" | grep "\"type\": *\"TXT\"" | grep "\"name\": *\"$_sub_domain\"" | grep "\"content\": *\"$txtvalue\"" | _egrep_o "\"id\": *\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
+    _debug "record_id" "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if ! _aurora_rest DELETE "zones/$_domain_id/records/$record_id"; then
+      _err "Delete record error."
+      return 1
+    fi
+  fi
+  return 0
+
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain=$1
+  i=1
+  p=1
+
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if ! _aurora_rest GET "zones/$h"; then
+      return 1
+    fi
+
+    if _contains "$response" "\"name\": \"$h\""; then
+      _domain_id=$(echo "$response" | _normalizeJson | tr -d "{}" | tr "," "\n" | grep "\"id\": *\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ")
+      _debug _domain_id "$_domain_id"
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _domain=$h
+        return 0
+      fi
+      return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_aurora_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  key_trimmed=$(echo "$AURORA_Key" | tr -d '"')
+  secret_trimmed=$(echo "$AURORA_Secret" | tr -d '"')
+
+  timestamp=$(date -u +"%Y%m%dT%H%M%SZ")
+  signature=$(printf "%s/%s%s" "$m" "$ep" "$timestamp" | _hmac sha256 "$(printf "%s" "$secret_trimmed" | _hex_dump | tr -d " ")" | _base64)
+  authorization=$(printf "AuroraDNSv1 %s" "$(printf "%s:%s" "$key_trimmed" "$signature" | _base64)")
+
+  export _H1="Content-Type: application/json; charset=UTF-8"
+  export _H2="X-AuroraDNS-Date: $timestamp"
+  export _H3="Authorization: $authorization"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$AURORA_Api/$ep" "" "$m")"
+  else
+    response="$(_get "$AURORA_Api/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}
index 068c337c448b2862f915e6acc5da4364a8d0e1c4..14a4594d12fff23d127c6811d49ce9a0fc71ba3f 100755 (executable)
@@ -32,7 +32,7 @@ dns_aws_add() {
   if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then
     AWS_ACCESS_KEY_ID=""
     AWS_SECRET_ACCESS_KEY=""
-    _err "You haven't specifed the aws route53 api key id and and api key secret yet."
+    _err "You haven't specified the aws route53 api key id and and api key secret yet."
     _err "Please create your key and try again. see $(__green $AWS_WIKI)"
     return 1
   fi
diff --git a/dnsapi/dns_azion.sh b/dnsapi/dns_azion.sh
new file mode 100644 (file)
index 0000000..f215686
--- /dev/null
@@ -0,0 +1,204 @@
+#!/usr/bin/env sh
+
+#
+#AZION_Email=""
+#AZION_Password=""
+#
+
+AZION_Api="https://api.azionapi.net"
+
+########  Public functions ########
+
+# Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+dns_azion_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _debug "Detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Domain not found"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+  _debug _domain_id "$_domain_id"
+
+  _info "Add or update record"
+  _get_record "$_domain_id" "$_sub_domain"
+  if [ "$record_id" ]; then
+    _payload="{\"record_type\": \"TXT\", \"entry\": \"$_sub_domain\", \"answers_list\": [$answers_list, \"$txtvalue\"], \"ttl\": 20}"
+    if _azion_rest PUT "intelligent_dns/$_domain_id/records/$record_id" "$_payload"; then
+      if _contains "$response" "$txtvalue"; then
+        _info "Record updated."
+        return 0
+      fi
+    fi
+  else
+    _payload="{\"record_type\": \"TXT\", \"entry\": \"$_sub_domain\", \"answers_list\": [\"$txtvalue\"], \"ttl\": 20}"
+    if _azion_rest POST "intelligent_dns/$_domain_id/records" "$_payload"; then
+      if _contains "$response" "$txtvalue"; then
+        _info "Record added."
+        return 0
+      fi
+    fi
+  fi
+  _err "Failed to add or update record."
+  return 1
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_azion_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _debug "Detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "Domain not found"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+  _debug _domain_id "$_domain_id"
+
+  _info "Removing record"
+  _get_record "$_domain_id" "$_sub_domain"
+  if [ "$record_id" ]; then
+    if _azion_rest DELETE "intelligent_dns/$_domain_id/records/$record_id"; then
+      _info "Record removed."
+      return 0
+    else
+      _err "Failed to remove record."
+      return 1
+    fi
+  else
+    _info "Record not found or already removed."
+    return 0
+  fi
+}
+
+####################  Private functions below ##################################
+# Usage: _acme-challenge.www.domain.com
+# returns
+#  _sub_domain=_acme-challenge.www
+#  _domain=domain.com
+#  _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain=$1
+  i=1
+  p=1
+
+  if ! _azion_rest GET "intelligent_dns"; then
+    return 1
+  fi
+
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      # not valid
+      return 1
+    fi
+
+    if _contains "$response" "\"domain\":\"$h\""; then
+      _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"domain\":\"$h\"" | _egrep_o "\"id\":[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d \")
+      _debug _domain_id "$_domain_id"
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _domain=$h
+        return 0
+      fi
+      return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_get_record() {
+  _domain_id=$1
+  _record=$2
+
+  if ! _azion_rest GET "intelligent_dns/$_domain_id/records"; then
+    return 1
+  fi
+
+  if _contains "$response" "\"entry\":\"$_record\""; then
+    _json_record=$(echo "$response" | tr '{' "\n" | grep "\"entry\":\"$_record\"")
+    if [ "$_json_record" ]; then
+      record_id=$(echo "$_json_record" | _egrep_o "\"record_id\":[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d \")
+      answers_list=$(echo "$_json_record" | _egrep_o "\"answers_list\":\[.*\]" | _head_n 1 | cut -d : -f 2 | tr -d \[\])
+      return 0
+    fi
+    return 1
+  fi
+  return 1
+}
+
+_get_token() {
+  AZION_Email="${AZION_Email:-$(_readaccountconf_mutable AZION_Email)}"
+  AZION_Password="${AZION_Password:-$(_readaccountconf_mutable AZION_Password)}"
+
+  if ! _contains "$AZION_Email" "@"; then
+    _err "It seems that the AZION_Email is not a valid email address. Revalidate your environments."
+    return 1
+  fi
+
+  if [ -z "$AZION_Email" ] || [ -z "$AZION_Password" ]; then
+    _err "You didn't specified a AZION_Email/AZION_Password to generate Azion token."
+    return 1
+  fi
+
+  _saveaccountconf_mutable AZION_Email "$AZION_Email"
+  _saveaccountconf_mutable AZION_Password "$AZION_Password"
+
+  _basic_auth=$(printf "%s:%s" "$AZION_Email" "$AZION_Password" | _base64)
+  _debug _basic_auth "$_basic_auth"
+
+  export _H1="Accept: application/json; version=3"
+  export _H2="Content-Type: application/json"
+  export _H3="Authorization: Basic $_basic_auth"
+
+  response="$(_post "" "$AZION_Api/tokens" "" "POST")"
+  if _contains "$response" "\"token\":\"" >/dev/null; then
+    _azion_token=$(echo "$response" | _egrep_o "\"token\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")
+    export AZION_Token="$_azion_token"
+  else
+    _err "Failed to generate Azion token"
+    return 1
+  fi
+}
+
+_azion_rest() {
+  _method=$1
+  _uri="$2"
+  _data="$3"
+
+  if [ -z "$AZION_Token" ]; then
+    _get_token
+  fi
+  _debug2 token "$AZION_Token"
+
+  export _H1="Accept: application/json; version=3"
+  export _H2="Content-Type: application/json"
+  export _H3="Authorization: token $AZION_Token"
+
+  if [ "$_method" != "GET" ]; then
+    _debug _data "$_data"
+    response="$(_post "$_data" "$AZION_Api/$_uri" "" "$_method")"
+  else
+    response="$(_get "$AZION_Api/$_uri")"
+  fi
+
+  _debug2 response "$response"
+
+  if [ "$?" != "0" ]; then
+    _err "error $_method $_uri $_data"
+    return 1
+  fi
+  return 0
+}
index 42df710d7c526a68c4d963fb8c11c6d28fc283c4..69d216f090ec4e3de206520bb76ccb34e461e1ac 100644 (file)
@@ -30,16 +30,41 @@ dns_constellix_add() {
     return 1
   fi
 
-  _info "Adding TXT record"
-  if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"add\":true,\"set\":{\"name\":\"${_sub_domain}\",\"ttl\":120,\"roundRobin\":[{\"value\":\"${txtvalue}\"}]}}]"; then
-    if printf -- "%s" "$response" | grep "{\"success\":\"1 record(s) added, 0 record(s) updated, 0 record(s) deleted\"}" >/dev/null; then
-      _info "Added"
-      return 0
+  # The TXT record might already exist when working with wildcard certificates. In that case, update the record by adding the new value.
+  _debug "Search TXT record"
+  if _constellix_rest GET "domains/${_domain_id}/records/TXT/search?exact=${_sub_domain}"; then
+    if printf -- "%s" "$response" | grep "{\"errors\":\[\"Requested record was not found\"\]}" >/dev/null; then
+      _info "Adding TXT record"
+      if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"add\":true,\"set\":{\"name\":\"${_sub_domain}\",\"ttl\":60,\"roundRobin\":[{\"value\":\"${txtvalue}\"}]}}]"; then
+        if printf -- "%s" "$response" | grep "{\"success\":\"1 record(s) added, 0 record(s) updated, 0 record(s) deleted\"}" >/dev/null; then
+          _info "Added"
+          return 0
+        else
+          _err "Error adding TXT record"
+        fi
+      fi
     else
-      _err "Error adding TXT record"
-      return 1
+      _record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]*" | cut -d ':' -f 2)
+      if _constellix_rest GET "domains/${_domain_id}/records/TXT/${_record_id}"; then
+        _new_rr_values=$(printf "%s\n" "$response" | _egrep_o '"roundRobin":\[[^]]*\]' | sed "s/\]$/,{\"value\":\"${txtvalue}\"}]/")
+        _debug _new_rr_values "$_new_rr_values"
+        _info "Updating TXT record"
+        if _constellix_rest PUT "domains/${_domain_id}/records/TXT/${_record_id}" "{\"name\":\"${_sub_domain}\",\"ttl\":60,${_new_rr_values}}"; then
+          if printf -- "%s" "$response" | grep "{\"success\":\"Record.*updated successfully\"}" >/dev/null; then
+            _info "Updated"
+            return 0
+          elif printf -- "%s" "$response" | grep "{\"errors\":\[\"Contents are identical\"\]}" >/dev/null; then
+            _info "Already exists, no need to update"
+            return 0
+          else
+            _err "Error updating TXT record"
+          fi
+        fi
+      fi
     fi
   fi
+
+  return 1
 }
 
 # Usage: fulldomain txtvalue
@@ -61,16 +86,26 @@ dns_constellix_rm() {
     return 1
   fi
 
-  _info "Removing TXT record"
-  if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"delete\":true,\"filter\":{\"field\":\"name\",\"op\":\"eq\",\"value\":\"${_sub_domain}\"}}]"; then
-    if printf -- "%s" "$response" | grep "{\"success\":\"0 record(s) added, 0 record(s) updated, 1 record(s) deleted\"}" >/dev/null; then
+  # The TXT record might have been removed already when working with some wildcard certificates.
+  _debug "Search TXT record"
+  if _constellix_rest GET "domains/${_domain_id}/records/TXT/search?exact=${_sub_domain}"; then
+    if printf -- "%s" "$response" | grep "{\"errors\":\[\"Requested record was not found\"\]}" >/dev/null; then
       _info "Removed"
       return 0
     else
-      _err "Error removing TXT record"
-      return 1
+      _info "Removing TXT record"
+      if _constellix_rest POST "domains/${_domain_id}/records" "[{\"type\":\"txt\",\"delete\":true,\"filter\":{\"field\":\"name\",\"op\":\"eq\",\"value\":\"${_sub_domain}\"}}]"; then
+        if printf -- "%s" "$response" | grep "{\"success\":\"0 record(s) added, 0 record(s) updated, 1 record(s) deleted\"}" >/dev/null; then
+          _info "Removed"
+          return 0
+        else
+          _err "Error removing TXT record"
+        fi
+      fi
     fi
   fi
+
+  return 1
 }
 
 ####################  Private functions below ##################################
@@ -91,7 +126,7 @@ _get_root() {
     fi
 
     if _contains "$response" "\"name\":\"$h\""; then
-      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]+" | cut -d ':' -f 2)
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]*" | cut -d ':' -f 2)
       if [ "$_domain_id" ]; then
         _sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-$p)
         _domain="$h"
diff --git a/dnsapi/dns_cpanel.sh b/dnsapi/dns_cpanel.sh
new file mode 100755 (executable)
index 0000000..f91725a
--- /dev/null
@@ -0,0 +1,159 @@
+#!/usr/bin/env sh
+#
+#Author: Bjarne Saltbaek
+#Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/3732
+#
+#
+########  Public functions #####################
+#
+# Export CPANEL username,api token and hostname in the following variables
+#
+# cPanel_Username=username
+# cPanel_Apitoken=apitoken
+# cPanel_Hostname=hostname
+#
+# Usage: add   _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+# Used to add txt record
+dns_cpanel_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Adding TXT record to cPanel based system"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+  _debug cPanel_Username "$cPanel_Username"
+  _debug cPanel_Apitoken "$cPanel_Apitoken"
+  _debug cPanel_Hostname "$cPanel_Hostname"
+
+  if ! _cpanel_login; then
+    _err "cPanel Login failed for user $cPanel_Username. Check $HTTP_HEADER file"
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "No matching root domain for $fulldomain found"
+    return 1
+  fi
+  # adding entry
+  _info "Adding the entry"
+  stripped_fulldomain=$(echo "$fulldomain" | sed "s/.$_domain//")
+  _debug "Adding $stripped_fulldomain to $_domain zone"
+  _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1"
+  if _successful_update; then return 0; fi
+  _err "Couldn't create entry!"
+  return 1
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_cpanel_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Using cPanel based system"
+  _debug fulldomain "$fulldomain"
+  _debug txtvalue "$txtvalue"
+
+  if ! _cpanel_login; then
+    _err "cPanel Login failed for user $cPanel_Username. Check $HTTP_HEADER file"
+    return 1
+  fi
+
+  if ! _get_root; then
+    _err "No matching root domain for $fulldomain found"
+    return 1
+  fi
+
+  _findentry "$fulldomain" "$txtvalue"
+  if [ -z "$_id" ]; then
+    _info "Entry doesn't exist, nothing to delete"
+    return 0
+  fi
+  _debug "Deleting record..."
+  _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=remove_zone_record&domain=$_domain&line=$_id"
+  # removing entry
+  _debug "_result is: $_result"
+
+  if _successful_update; then return 0; fi
+  _err "Couldn't delete entry!"
+  return 1
+}
+
+####################  Private functions below ##################################
+
+_checkcredentials() {
+  cPanel_Username="${cPanel_Username:-$(_readaccountconf_mutable cPanel_Username)}"
+  cPanel_Apitoken="${cPanel_Apitoken:-$(_readaccountconf_mutable cPanel_Apitoken)}"
+  cPanel_Hostname="${cPanel_Hostname:-$(_readaccountconf_mutable cPanel_Hostname)}"
+
+  if [ -z "$cPanel_Username" ] || [ -z "$cPanel_Apitoken" ] || [ -z "$cPanel_Hostname" ]; then
+    cPanel_Username=""
+    cPanel_Apitoken=""
+    cPanel_Hostname=""
+    _err "You haven't specified cPanel username, apitoken and hostname yet."
+    _err "Please add credentials and try again."
+    return 1
+  fi
+  #save the credentials to the account conf file.
+  _saveaccountconf_mutable cPanel_Username "$cPanel_Username"
+  _saveaccountconf_mutable cPanel_Apitoken "$cPanel_Apitoken"
+  _saveaccountconf_mutable cPanel_Hostname "$cPanel_Hostname"
+  return 0
+}
+
+_cpanel_login() {
+  if ! _checkcredentials; then return 1; fi
+
+  if ! _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=CustInfo&cpanel_jsonapi_func=displaycontactinfo"; then
+    _err "cPanel login failed for user $cPanel_Username."
+    return 1
+  fi
+  return 0
+}
+
+_myget() {
+  #Adds auth header to request
+  export _H1="Authorization: cpanel $cPanel_Username:$cPanel_Apitoken"
+  _result=$(_get "$cPanel_Hostname/$1")
+}
+
+_get_root() {
+  _myget 'json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzones'
+  _domains=$(echo "$_result" | sed 's/.*\(zones.*\[\).*/\1/' | cut -d':' -f2 | sed 's/"//g' | sed 's/{//g')
+  _debug "_result is: $_result"
+  _debug "_domains is: $_domains"
+  if [ -z "$_domains" ]; then
+    _err "Primary domain list not found!"
+    return 1
+  fi
+  for _domain in $_domains; do
+    _debug "Checking if $fulldomain ends with $_domain"
+    if (_endswith "$fulldomain" "$_domain"); then
+      _debug "Root domain: $_domain"
+      return 0
+    fi
+  done
+  return 1
+}
+
+_successful_update() {
+  if (echo "$_result" | grep -q 'newserial'); then return 0; fi
+  return 1
+}
+
+_findentry() {
+  _debug "In _findentry"
+  #returns id of dns entry, if it exists
+  _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=fetchzone_records&domain=$_domain"
+  _id=$(echo "$_result" | sed "s/.*\(line.*$fulldomain.*$txtvalue\).*/\1/" | cut -d ':' -f 2 | cut -d ',' -f 1)
+  _debug "_result is: $_result"
+  _debug "fulldomain. is $fulldomain."
+  _debug "txtvalue is $txtvalue"
+  _debug "_id is: $_id"
+  if [ -n "$_id" ]; then
+    _debug "Entry found with _id=$_id"
+    return 0
+  fi
+  return 1
+}
index f64660a82318aa13112042953c648bf29a4d315b..495a67802bef96ab0e46dbe7e7e1f30ddd8ad68e 100644 (file)
@@ -20,21 +20,17 @@ dns_desec_add() {
   _debug txtvalue "$txtvalue"
 
   DEDYN_TOKEN="${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}"
-  DEDYN_NAME="${DEDYN_NAME:-$(_readaccountconf_mutable DEDYN_NAME)}"
 
-  if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then
+  if [ -z "$DEDYN_TOKEN" ]; then
     DEDYN_TOKEN=""
-    DEDYN_NAME=""
-    _err "You did not specify DEDYN_TOKEN and DEDYN_NAME yet."
+    _err "You did not specify DEDYN_TOKEN yet."
     _err "Please create your key and try again."
     _err "e.g."
     _err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e"
-    _err "export DEDYN_NAME=foobar.dedyn.io"
     return 1
   fi
-  #save the api token and name to the account conf file.
+  #save the api token to the account conf file.
   _saveaccountconf_mutable DEDYN_TOKEN "$DEDYN_TOKEN"
-  _saveaccountconf_mutable DEDYN_NAME "$DEDYN_NAME"
 
   _debug "First detect the root zone"
   if ! _get_root "$fulldomain" "$REST_API/"; then
@@ -47,7 +43,7 @@ dns_desec_add() {
   # Get existing TXT record
   _debug "Getting txt records"
   txtvalues="\"\\\"$txtvalue\\\"\""
-  _desec_rest GET "$REST_API/$DEDYN_NAME/rrsets/$_sub_domain/TXT/"
+  _desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/"
 
   if [ "$_code" = "200" ]; then
     oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
@@ -63,7 +59,7 @@ dns_desec_add() {
   _info "Adding record"
   body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":3600}]"
 
-  if _desec_rest PUT "$REST_API/$DEDYN_NAME/rrsets/" "$body"; then
+  if _desec_rest PUT "$REST_API/$_domain/rrsets/" "$body"; then
     if _contains "$response" "$txtvalue"; then
       _info "Added, OK"
       return 0
@@ -87,16 +83,13 @@ dns_desec_rm() {
   _debug txtvalue "$txtvalue"
 
   DEDYN_TOKEN="${DEDYN_TOKEN:-$(_readaccountconf_mutable DEDYN_TOKEN)}"
-  DEDYN_NAME="${DEDYN_NAME:-$(_readaccountconf_mutable DEDYN_NAME)}"
 
-  if [ -z "$DEDYN_TOKEN" ] || [ -z "$DEDYN_NAME" ]; then
+  if [ -z "$DEDYN_TOKEN" ]; then
     DEDYN_TOKEN=""
-    DEDYN_NAME=""
-    _err "You did not specify DEDYN_TOKEN and DEDYN_NAME yet."
+    _err "You did not specify DEDYN_TOKEN yet."
     _err "Please create your key and try again."
     _err "e.g."
     _err "export DEDYN_TOKEN=d41d8cd98f00b204e9800998ecf8427e"
-    _err "export DEDYN_NAME=foobar.dedyn.io"
     return 1
   fi
 
@@ -112,7 +105,7 @@ dns_desec_rm() {
   # Get existing TXT record
   _debug "Getting txt records"
   txtvalues=""
-  _desec_rest GET "$REST_API/$DEDYN_NAME/rrsets/$_sub_domain/TXT/"
+  _desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/"
 
   if [ "$_code" = "200" ]; then
     oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")"
@@ -131,7 +124,7 @@ dns_desec_rm() {
 
   _info "Deleting record"
   body="[{\"subname\":\"$_sub_domain\", \"type\":\"TXT\", \"records\":[$txtvalues], \"ttl\":3600}]"
-  _desec_rest PUT "$REST_API/$DEDYN_NAME/rrsets/" "$body"
+  _desec_rest PUT "$REST_API/$_domain/rrsets/" "$body"
   if [ "$_code" = "200" ]; then
     _info "Deleted, OK"
     return 0
index 033fa5aae066f3a8e6e6fd013f4896f5776f63af..9b8b7a8b83e033b76652f09bc5777b2411a680c4 100755 (executable)
@@ -89,7 +89,7 @@ add_record() {
 
   _info "Adding record"
 
-  if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then
+  if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&lang=en&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=%E9%BB%98%E8%AE%A4"; then
     return 1
   fi
 
index 618e12c66154fa2b7bf061a62371348d22c39a5d..d6e1dbdceabb5bbe32d65f15e60c0897135a12a9 100755 (executable)
@@ -12,7 +12,7 @@
 
 DuckDNS_API="https://www.duckdns.org/update"
 
-########  Public functions #####################
+########  Public functions ######################
 
 #Usage: dns_duckdns_add _acme-challenge.domain.duckdns.org "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_duckdns_add() {
@@ -112,7 +112,7 @@ _duckdns_rest() {
   param="$2"
   _debug param "$param"
   url="$DuckDNS_API?$param"
-  if [ "$DEBUG" -gt 0 ]; then
+  if [ -n "$DEBUG" ] && [ "$DEBUG" -gt 0 ]; then
     url="$url&verbose=true"
   fi
   _debug url "$url"
@@ -121,7 +121,7 @@ _duckdns_rest() {
   if [ "$method" = "GET" ]; then
     response="$(_get "$url")"
     _debug2 response "$response"
-    if [ "$DEBUG" -gt 0 ] && _contains "$response" "UPDATED" && _contains "$response" "OK"; then
+    if [ -n "$DEBUG" ] && [ "$DEBUG" -gt 0 ] && _contains "$response" "UPDATED" && _contains "$response" "OK"; then
       response="OK"
     fi
   else
index 03060a8cb626b6f40796eb3d5f254f21f4f904ef..d560996c73b2b0360cc08b9f5858e4cb24fd810c 100755 (executable)
@@ -163,5 +163,8 @@ _dns_gcloud_get_rrdatas() {
     return 1
   fi
   ttl=$(echo "$rrdatas" | cut -f1)
-  rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/","/"\n"/g')
+  # starting with version 353.0.0 gcloud seems to
+  # separate records with a semicolon instead of commas
+  # see also https://cloud.google.com/sdk/docs/release-notes#35300_2021-08-17
+  rrdatas=$(echo "$rrdatas" | cut -f2 | sed 's/"[,;]"/"\n"/g')
 }
index ef09fa0a0ebec73d7c4066205fbdbe3ad46ac096..bf4a5030a22c0686753ff14c634c38e70b05beb4 100755 (executable)
@@ -85,7 +85,7 @@ dns_he_rm() {
     _debug "The txt record is not found, just skip"
     return 0
   fi
-  _record_id="$(echo "$response" | tr -d "#" | sed "s/<tr/#<tr/g" | tr -d "\n" | tr "#" "\n" | grep "$_full_domain" | grep '"dns_tr"' | grep "$_txt_value" | cut -d '"' -f 4)"
+  _record_id="$(echo "$response" | tr -d "#" | sed "s/<tr/#<tr/g" | tr -d "\n" | tr "#" "\n" | grep "$_full_domain" | grep '"dns_tr"' | grep -- "$_txt_value" | cut -d '"' -f 4)"
   _debug2 _record_id "$_record_id"
   if [ -z "$_record_id" ]; then
     _err "Can not find record id"
index 74fec2a96c81439f8e6214837bc409d54aadc982..f7192725e2cb1f263c2a9a38aa9a9a344e7bd68f 100644 (file)
@@ -5,7 +5,7 @@
 # HUAWEICLOUD_ProjectID
 
 iam_api="https://iam.myhuaweicloud.com"
-dns_api="https://dns.ap-southeast-1.myhuaweicloud.com"
+dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
 
 ########  Public functions #####################
 
@@ -29,16 +29,27 @@ dns_huaweicloud_add() {
     return 1
   fi
 
+  unset token # Clear token
   token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
-  _debug2 "${token}"
+  if [ -z "${token}" ]; then # Check token
+    _err "dns_api(dns_huaweicloud): Error getting token."
+    return 1
+  fi
+  _debug "Access token is: ${token}"
+
+  unset zoneid
   zoneid="$(_get_zoneid "${token}" "${fulldomain}")"
-  _debug "${zoneid}"
+  if [ -z "${zoneid}" ]; then
+    _err "dns_api(dns_huaweicloud): Error getting zone id."
+    return 1
+  fi
+  _debug "Zone ID is: ${zoneid}"
 
   _debug "Adding Record"
   _add_record "${token}" "${fulldomain}" "${txtvalue}"
   ret="$?"
   if [ "${ret}" != "0" ]; then
-    _err "dns_huaweicloud: Error adding record."
+    _err "dns_api(dns_huaweicloud): Error adding record."
     return 1
   fi
 
@@ -69,12 +80,21 @@ dns_huaweicloud_rm() {
     return 1
   fi
 
+  unset token # Clear token
   token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
-  _debug2 "${token}"
+  if [ -z "${token}" ]; then # Check token
+    _err "dns_api(dns_huaweicloud): Error getting token."
+    return 1
+  fi
+  _debug "Access token is: ${token}"
+
+  unset zoneid
   zoneid="$(_get_zoneid "${token}" "${fulldomain}")"
-  _debug "${zoneid}"
-  record_id="$(_get_recordset_id "${token}" "${fulldomain}" "${zoneid}")"
-  _debug "Record Set ID is: ${record_id}"
+  if [ -z "${zoneid}" ]; then
+    _err "dns_api(dns_huaweicloud): Error getting zone id."
+    return 1
+  fi
+  _debug "Zone ID is: ${zoneid}"
 
   # Remove all records
   # Therotically HuaweiCloud does not allow more than one record set
index 4cbb2146089d5db186b11ce0f8a4609b1dd0946c..6bfd36eefbde7db9542350806b1364d0cf9d4dfc 100644 (file)
@@ -9,7 +9,6 @@ dns_infoblox_add() {
   ## Nothing to see here, just some housekeeping
   fulldomain=$1
   txtvalue=$2
-  baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View"
 
   _info "Using Infoblox API"
   _debug fulldomain "$fulldomain"
@@ -19,12 +18,13 @@ dns_infoblox_add() {
   if [ -z "$Infoblox_Creds" ] || [ -z "$Infoblox_Server" ]; then
     Infoblox_Creds=""
     Infoblox_Server=""
-    _err "You didn't specify the credentials, server or infoblox view yet (Infoblox_Creds, Infoblox_Server and Infoblox_View)."
-    _err "Please set them via EXPORT ([username:password], [ip or hostname]) and try again."
+    _err "You didn't specify the Infoblox credentials or server (Infoblox_Creds; Infoblox_Server)."
+    _err "Please set them via EXPORT Infoblox_Creds=username:password or EXPORT Infoblox_server=ip/hostname and try again."
     return 1
   fi
 
   if [ -z "$Infoblox_View" ]; then
+    _info "No Infoblox_View set, using fallback value 'default'"
     Infoblox_View="default"
   fi
 
@@ -33,6 +33,9 @@ dns_infoblox_add() {
   _saveaccountconf Infoblox_Server "$Infoblox_Server"
   _saveaccountconf Infoblox_View "$Infoblox_View"
 
+  ## URLencode Infoblox View to deal with e.g. spaces
+  Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode)
+
   ## Base64 encode the credentials
   Infoblox_CredsEncoded=$(printf "%b" "$Infoblox_Creds" | _base64)
 
@@ -40,11 +43,14 @@ dns_infoblox_add() {
   export _H1="Accept-Language:en-US"
   export _H2="Authorization: Basic $Infoblox_CredsEncoded"
 
+  ## Construct the request URL
+  baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}"
+
   ## Add the challenge record to the Infoblox grid member
   result="$(_post "" "$baseurlnObject" "" "POST")"
 
   ## Let's see if we get something intelligible back from the unit
-  if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+  if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
     _info "Successfully created the txt record"
     return 0
   else
@@ -65,6 +71,9 @@ dns_infoblox_rm() {
   _debug fulldomain "$fulldomain"
   _debug txtvalue "$txtvalue"
 
+  ## URLencode Infoblox View to deal with e.g. spaces
+  Infoblox_ViewEncoded=$(printf "%b" "$Infoblox_View" | _url_encode)
+
   ## Base64 encode the credentials
   Infoblox_CredsEncoded="$(printf "%b" "$Infoblox_Creds" | _base64)"
 
@@ -73,18 +82,18 @@ dns_infoblox_rm() {
   export _H2="Authorization: Basic $Infoblox_CredsEncoded"
 
   ## Does the record exist?  Let's check.
-  baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=$Infoblox_View&_return_type=xml-pretty"
+  baseurlnObject="https://$Infoblox_Server/wapi/v2.2.2/record:txt?name=$fulldomain&text=$txtvalue&view=${Infoblox_ViewEncoded}&_return_type=xml-pretty"
   result="$(_get "$baseurlnObject")"
 
   ## Let's see if we get something intelligible back from the grid
-  if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+  if [ "$(echo "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
     ## Extract the object reference
-    objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/$Infoblox_View")"
+    objRef="$(printf "%b" "$result" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")"
     objRmUrl="https://$Infoblox_Server/wapi/v2.2.2/$objRef"
     ## Delete them! All the stale records!
     rmResult="$(_post "" "$objRmUrl" "" "DELETE")"
     ## Let's see if that worked
-    if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/$Infoblox_View")" ]; then
+    if [ "$(echo "$rmResult" | _egrep_o "record:txt/.*:.*/${Infoblox_ViewEncoded}")" ]; then
       _info "Successfully deleted $objRef"
       return 0
     else
index e6bd5000b2564dba519d9467a9f4c659388300a6..c2c431bbdd52050e78efb87416a11597649d7ede 100755 (executable)
@@ -24,20 +24,9 @@ dns_ionos_add() {
     return 1
   fi
 
-  _new_record="{\"name\":\"$_sub_domain.$_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":$IONOS_TXT_TTL,\"prio\":$IONOS_TXT_PRIO,\"disabled\":false}"
+  _body="[{\"name\":\"$_sub_domain.$_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":$IONOS_TXT_TTL,\"prio\":$IONOS_TXT_PRIO,\"disabled\":false}]"
 
-  # As no POST route is supported by the API, check for existing records and include them in the PATCH request in order not delete them.
-  # This is required to support ACME v2 wildcard certificate creation, where two TXT records for the same domain name are created.
-
-  _ionos_get_existing_records "$fulldomain" "$_zone_id"
-
-  if [ "$_existing_records" ]; then
-    _body="[$_new_record,$_existing_records]"
-  else
-    _body="[$_new_record]"
-  fi
-
-  if _ionos_rest PATCH "$IONOS_ROUTE_ZONES/$_zone_id" "$_body" && [ -z "$response" ]; then
+  if _ionos_rest POST "$IONOS_ROUTE_ZONES/$_zone_id/records" "$_body" && [ -z "$response" ]; then
     _info "TXT record has been created successfully."
     return 0
   fi
@@ -125,17 +114,6 @@ _get_root() {
   return 1
 }
 
-_ionos_get_existing_records() {
-  fulldomain=$1
-  zone_id=$2
-
-  if _ionos_rest GET "$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT"; then
-    response="$(echo "$response" | tr -d "\n")"
-
-    _existing_records="$(printf "%s\n" "$response" | _egrep_o "\"records\":\[.*\]" | _head_n 1 | cut -d '[' -f 2 | sed 's/]//')"
-  fi
-}
-
 _ionos_get_record() {
   fulldomain=$1
   zone_id=$2
@@ -168,17 +146,18 @@ _ionos_rest() {
     export _H2="Accept: application/json"
     export _H3="Content-Type: application/json"
 
-    response="$(_post "$data" "$IONOS_API$route" "" "$method")"
+    response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")"
   else
     export _H2="Accept: */*"
-
+    export _H3=
     response="$(_get "$IONOS_API$route")"
   fi
 
   if [ "$?" != "0" ]; then
-    _err "Error $route"
+    _err "Error $route: $response"
     return 1
   fi
+  _debug2 "response" "$response"
 
   return 0
 }
index 7ce39fa951d291ccc5bea4226f755718a970c330..d15d6b0e74e5ef952e2f91039d53092105751635 100755 (executable)
@@ -208,7 +208,7 @@ _namecheap_parse_host() {
   _hostid=$(echo "$_host" | _egrep_o ' HostId="[^"]*' | cut -d '"' -f 2)
   _hostname=$(echo "$_host" | _egrep_o ' Name="[^"]*' | cut -d '"' -f 2)
   _hosttype=$(echo "$_host" | _egrep_o ' Type="[^"]*' | cut -d '"' -f 2)
-  _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2)
+  _hostaddress=$(echo "$_host" | _egrep_o ' Address="[^"]*' | cut -d '"' -f 2 | _xml_decode)
   _hostmxpref=$(echo "$_host" | _egrep_o ' MXPref="[^"]*' | cut -d '"' -f 2)
   _hostttl=$(echo "$_host" | _egrep_o ' TTL="[^"]*' | cut -d '"' -f 2)
 
@@ -405,3 +405,7 @@ _namecheap_set_tld_sld() {
   done
 
 }
+
+_xml_decode() {
+  sed 's/&quot;/"/g'
+}
index d519e4f7123eba26bea7ae4eb97e6645ea1f0b51..776fa02d93c2d4fd196e243d3c259948dd11e02a 100644 (file)
@@ -119,16 +119,16 @@ login() {
   tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
   sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4)
   _debug "$tmp"
-  if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
-    _err "$msg"
+  if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+    _err "$tmp"
     return 1
   fi
 }
 logout() {
   tmp=$(_post "{\"action\": \"logout\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apisessionid\": \"$sid\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST")
   _debug "$tmp"
-  if [ "$(_getfield "$msg" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
-    _err "$msg"
+  if [ "$(_getfield "$tmp" "4" | sed s/\"status\":\"//g | sed s/\"//g)" != "success" ]; then
+    _err "$tmp"
     return 1
   fi
 }
index 83cc4cacd4d241151863ce2f645691f21ab3656b..0d29a485b84f62bf0a07726cf02010048a7e4df5 100644 (file)
@@ -51,7 +51,7 @@ dns_nsd_rm() {
   Nsd_ZoneFile="${Nsd_ZoneFile:-$(_readdomainconf Nsd_ZoneFile)}"
   Nsd_Command="${Nsd_Command:-$(_readdomainconf Nsd_Command)}"
 
-  sed -i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
+  _sed_i "/$fulldomain. $ttlvalue IN TXT \"$txtvalue\"/d" "$Nsd_ZoneFile"
   _info "Removed TXT record for $fulldomain"
   _debug "Running $Nsd_Command"
   if eval "$Nsd_Command"; then
diff --git a/dnsapi/dns_oci.sh b/dnsapi/dns_oci.sh
new file mode 100644 (file)
index 0000000..eb00612
--- /dev/null
@@ -0,0 +1,324 @@
+#!/usr/bin/env sh
+#
+# Acme.sh DNS API plugin for Oracle Cloud Infrastructure
+# Copyright (c) 2021, Oracle and/or its affiliates
+#
+# The plugin will automatically use the default profile from an OCI SDK and CLI
+# configuration file, if it exists.
+#
+# Alternatively, set the following environment variables:
+# - OCI_CLI_TENANCY : OCID of tenancy that contains the target DNS zone
+# - OCI_CLI_USER    : OCID of user with permission to add/remove records from zones
+# - OCI_CLI_REGION  : Should point to the tenancy home region
+#
+# One of the following two variables is required:
+# - OCI_CLI_KEY_FILE: Path to private API signing key file in PEM format; or
+# - OCI_CLI_KEY     : The private API signing key in PEM format
+#
+# NOTE: using an encrypted private key that needs a passphrase is not supported.
+#
+
+dns_oci_add() {
+  _fqdn="$1"
+  _rdata="$2"
+
+  if _get_oci_zone; then
+
+    _add_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"ttl\": 30,\"operation\":\"ADD\"}]}"
+    response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_add_record_body")
+    if [ "$response" ]; then
+      _info "Success: added TXT record for ${_sub_domain}.${_domain}."
+    else
+      _err "Error: failed to add TXT record for ${_sub_domain}.${_domain}."
+      _err "Check that the user has permission to add records to this zone."
+      return 1
+    fi
+
+  else
+    return 1
+  fi
+
+}
+
+dns_oci_rm() {
+  _fqdn="$1"
+  _rdata="$2"
+
+  if _get_oci_zone; then
+
+    _remove_record_body="{\"items\":[{\"domain\":\"${_sub_domain}.${_domain}\",\"rdata\":\"$_rdata\",\"rtype\":\"TXT\",\"operation\":\"REMOVE\"}]}"
+    response=$(_signed_request "PATCH" "/20180115/zones/${_domain}/records" "$_remove_record_body")
+    if [ "$response" ]; then
+      _info "Success: removed TXT record for ${_sub_domain}.${_domain}."
+    else
+      _err "Error: failed to remove TXT record for ${_sub_domain}.${_domain}."
+      _err "Check that the user has permission to remove records from this zone."
+      return 1
+    fi
+
+  else
+    return 1
+  fi
+
+}
+
+####################  Private functions below ##################################
+_get_oci_zone() {
+
+  if ! _oci_config; then
+    return 1
+  fi
+
+  if ! _get_zone "$_fqdn"; then
+    _err "Error: DNS Zone not found for $_fqdn in $OCI_CLI_TENANCY"
+    return 1
+  fi
+
+  return 0
+
+}
+
+_oci_config() {
+
+  _DEFAULT_OCI_CLI_CONFIG_FILE="$HOME/.oci/config"
+  OCI_CLI_CONFIG_FILE="${OCI_CLI_CONFIG_FILE:-$(_readaccountconf_mutable OCI_CLI_CONFIG_FILE)}"
+
+  if [ -z "$OCI_CLI_CONFIG_FILE" ]; then
+    OCI_CLI_CONFIG_FILE="$_DEFAULT_OCI_CLI_CONFIG_FILE"
+  fi
+
+  if [ "$_DEFAULT_OCI_CLI_CONFIG_FILE" != "$OCI_CLI_CONFIG_FILE" ]; then
+    _saveaccountconf_mutable OCI_CLI_CONFIG_FILE "$OCI_CLI_CONFIG_FILE"
+  else
+    _clearaccountconf_mutable OCI_CLI_CONFIG_FILE
+  fi
+
+  _DEFAULT_OCI_CLI_PROFILE="DEFAULT"
+  OCI_CLI_PROFILE="${OCI_CLI_PROFILE:-$(_readaccountconf_mutable OCI_CLI_PROFILE)}"
+  if [ "$_DEFAULT_OCI_CLI_PROFILE" != "$OCI_CLI_PROFILE" ]; then
+    _saveaccountconf_mutable OCI_CLI_PROFILE "$OCI_CLI_PROFILE"
+  else
+    OCI_CLI_PROFILE="$_DEFAULT_OCI_CLI_PROFILE"
+    _clearaccountconf_mutable OCI_CLI_PROFILE
+  fi
+
+  OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}"
+  if [ "$OCI_CLI_TENANCY" ]; then
+    _saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY"
+  elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+    _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE"
+    OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")}"
+  fi
+
+  if [ -z "$OCI_CLI_TENANCY" ]; then
+    _err "Error: unable to read OCI_CLI_TENANCY from config file or environment variable."
+    return 1
+  fi
+
+  OCI_CLI_USER="${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}"
+  if [ "$OCI_CLI_USER" ]; then
+    _saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER"
+  elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+    _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE"
+    OCI_CLI_USER="${OCI_CLI_USER:-$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")}"
+  fi
+  if [ -z "$OCI_CLI_USER" ]; then
+    _err "Error: unable to read OCI_CLI_USER from config file or environment variable."
+    return 1
+  fi
+
+  OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}"
+  if [ "$OCI_CLI_REGION" ]; then
+    _saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION"
+  elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then
+    _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE"
+    OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")}"
+  fi
+  if [ -z "$OCI_CLI_REGION" ]; then
+    _err "Error: unable to read OCI_CLI_REGION from config file or environment variable."
+    return 1
+  fi
+
+  OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}"
+  if [ -z "$OCI_CLI_KEY" ]; then
+    _clearaccountconf_mutable OCI_CLI_KEY
+    OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}"
+    if [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then
+      _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE"
+      OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE")
+      _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+    fi
+  else
+    _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY"
+  fi
+
+  if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then
+    _err "Error: unable to find key file path in OCI config file or OCI_CLI_KEY_FILE."
+    _err "Error: unable to load private API signing key from OCI_CLI_KEY."
+    return 1
+  fi
+
+  if [ "$(printf "%s\n" "$OCI_CLI_KEY" | wc -l)" -eq 1 ]; then
+    OCI_CLI_KEY=$(printf "%s" "$OCI_CLI_KEY" | _dbase64 multiline)
+  fi
+
+  return 0
+
+}
+
+# _get_zone(): retrieves the Zone name and OCID
+#
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_ociid=ocid1.dns-zone.oc1..
+_get_zone() {
+  domain=$1
+  i=1
+  p=1
+
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      # not valid
+      return 1
+    fi
+
+    _domain_id=$(_signed_request "GET" "/20180115/zones/$h" "" "id")
+    if [ "$_domain_id" ]; then
+      _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+      _domain=$h
+
+      _debug _domain_id "$_domain_id"
+      _debug _sub_domain "$_sub_domain"
+      _debug _domain "$_domain"
+      return 0
+    fi
+
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+
+}
+
+#Usage: privatekey
+#Output MD5 fingerprint
+_fingerprint() {
+
+  pkey="$1"
+  if [ -z "$pkey" ]; then
+    _usage "Usage: _fingerprint privkey"
+    return 1
+  fi
+
+  printf "%s" "$pkey" | ${ACME_OPENSSL_BIN:-openssl} rsa -pubout -outform DER 2>/dev/null | ${ACME_OPENSSL_BIN:-openssl} md5 -c | cut -d = -f 2 | tr -d ' '
+
+}
+
+_signed_request() {
+
+  _sig_method="$1"
+  _sig_target="$2"
+  _sig_body="$3"
+  _return_field="$4"
+
+  _key_fingerprint=$(_fingerprint "$OCI_CLI_KEY")
+  _sig_host="dns.$OCI_CLI_REGION.oraclecloud.com"
+  _sig_keyId="$OCI_CLI_TENANCY/$OCI_CLI_USER/$_key_fingerprint"
+  _sig_alg="rsa-sha256"
+  _sig_version="1"
+  _sig_now="$(LC_ALL=C \date -u "+%a, %d %h %Y %H:%M:%S GMT")"
+
+  _request_method=$(printf %s "$_sig_method" | _lower_case)
+  _curl_method=$(printf %s "$_sig_method" | _upper_case)
+
+  _request_target="(request-target): $_request_method $_sig_target"
+  _date_header="date: $_sig_now"
+  _host_header="host: $_sig_host"
+
+  _string_to_sign="$_request_target\n$_date_header\n$_host_header"
+  _sig_headers="(request-target) date host"
+
+  if [ "$_sig_body" ]; then
+    _secure_debug3 _sig_body "$_sig_body"
+    _sig_body_sha256="x-content-sha256: $(printf %s "$_sig_body" | _digest sha256)"
+    _sig_body_type="content-type: application/json"
+    _sig_body_length="content-length: ${#_sig_body}"
+    _string_to_sign="$_string_to_sign\n$_sig_body_sha256\n$_sig_body_type\n$_sig_body_length"
+    _sig_headers="$_sig_headers x-content-sha256 content-type content-length"
+  fi
+
+  _tmp_file=$(_mktemp)
+  if [ -f "$_tmp_file" ]; then
+    printf '%s' "$OCI_CLI_KEY" >"$_tmp_file"
+    _signature=$(printf '%b' "$_string_to_sign" | _sign "$_tmp_file" sha256 | tr -d '\r\n')
+    rm -f "$_tmp_file"
+  fi
+
+  _signed_header="Authorization: Signature version=\"$_sig_version\",keyId=\"$_sig_keyId\",algorithm=\"$_sig_alg\",headers=\"$_sig_headers\",signature=\"$_signature\""
+  _secure_debug3 _signed_header "$_signed_header"
+
+  if [ "$_curl_method" = "GET" ]; then
+    export _H1="$_date_header"
+    export _H2="$_signed_header"
+    _response="$(_get "https://${_sig_host}${_sig_target}")"
+  elif [ "$_curl_method" = "PATCH" ]; then
+    export _H1="$_date_header"
+    export _H2="$_sig_body_sha256"
+    export _H3="$_sig_body_type"
+    export _H4="$_sig_body_length"
+    export _H5="$_signed_header"
+    _response="$(_post "$_sig_body" "https://${_sig_host}${_sig_target}" "" "PATCH")"
+  else
+    _err "Unable to process method: $_curl_method."
+  fi
+
+  _ret="$?"
+  if [ "$_return_field" ]; then
+    _response="$(echo "$_response" | sed 's/\\\"//g'))"
+    _return=$(echo "${_response}" | _egrep_o "\"$_return_field\"\\s*:\\s*\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d "\"")
+  else
+    _return="$_response"
+  fi
+
+  printf "%s" "$_return"
+  return $_ret
+
+}
+
+# file  key  [section]
+_readini() {
+  _file="$1"
+  _key="$2"
+  _section="${3:-DEFAULT}"
+
+  _start_n=$(grep -n '\['"$_section"']' "$_file" | cut -d : -f 1)
+  _debug3 _start_n "$_start_n"
+  if [ -z "$_start_n" ]; then
+    _err "Can not find section: $_section"
+    return 1
+  fi
+
+  _start_nn=$(_math "$_start_n" + 1)
+  _debug3 "_start_nn" "$_start_nn"
+
+  _left="$(sed -n "${_start_nn},99999p" "$_file")"
+  _debug3 _left "$_left"
+  _end="$(echo "$_left" | grep -n "^\[" | _head_n 1)"
+  _debug3 "_end" "$_end"
+  if [ "$_end" ]; then
+    _end_n=$(echo "$_end" | cut -d : -f 1)
+    _debug3 "_end_n" "$_end_n"
+    _seg_n=$(echo "$_left" | sed -n "1,${_end_n}p")
+  else
+    _seg_n="$_left"
+  fi
+
+  _debug3 "_seg_n" "$_seg_n"
+  _lineini="$(echo "$_seg_n" | grep "^ *$_key *= *")"
+  _inivalue="$(printf "%b" "$(eval "echo $_lineini | sed \"s/^ *${_key} *= *//g\"")")"
+  _debug2 _inivalue "$_inivalue"
+  echo "$_inivalue"
+
+}
index 890cc804c7b43b0f6031265156d1a9d6fc7011d4..1565b767c2090c79404979fccc6957bcfcb72bae 100644 (file)
@@ -1,22 +1,9 @@
 #!/usr/bin/env sh
-# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*-
-
 # one.com ui wrapper for acme.sh
-# Author: github: @diseq
-# Created: 2019-02-17
-# Fixed by: @der-berni
-# Modified: 2020-04-07
-#
-#     Use ONECOM_KeepCnameProxy to keep the CNAME DNS record
-#     export ONECOM_KeepCnameProxy="1"
+
 #
 #     export ONECOM_User="username"
 #     export ONECOM_Password="password"
-#
-# Usage:
-#     acme.sh --issue --dns dns_one -d example.com
-#
-#     only single domain supported atm
 
 dns_one_add() {
   fulldomain=$1
@@ -36,27 +23,9 @@ dns_one_add() {
   subdomain="${_sub_domain}"
   maindomain=${_domain}
 
-  useProxy=0
-  if [ "${_sub_domain}" = "_acme-challenge" ]; then
-    subdomain="proxy${_sub_domain}"
-    useProxy=1
-  fi
-
   _debug subdomain "$subdomain"
   _debug maindomain "$maindomain"
 
-  if [ $useProxy -eq 1 ]; then
-    #Check if the CNAME exists
-    _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
-    if [ -z "$id" ]; then
-      _info "$(__red "Add CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
-      _dns_one_addrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
-
-      _info "Not valid yet, let's wait 1 hour to take effect."
-      _sleep 3600
-    fi
-  fi
-
   #Check if the TXT exists
   _dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
   if [ -n "$id" ]; then
@@ -92,26 +61,8 @@ dns_one_rm() {
   subdomain="${_sub_domain}"
   maindomain=${_domain}
 
-  useProxy=0
-  if [ "${_sub_domain}" = "_acme-challenge" ]; then
-    subdomain="proxy${_sub_domain}"
-    useProxy=1
-  fi
-
   _debug subdomain "$subdomain"
   _debug maindomain "$maindomain"
-  if [ $useProxy -eq 1 ]; then
-    if [ "$ONECOM_KeepCnameProxy" = "1" ]; then
-      _info "$(__red "Keeping CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
-    else
-      #Check if the CNAME exists
-      _dns_one_getrecord "CNAME" "$_sub_domain" "$subdomain.$maindomain"
-      if [ -n "$id" ]; then
-        _info "$(__red "Removing CNAME Proxy record: '$(__green "\"$_sub_domain\" => \"$subdomain.$maindomain\"")'")"
-        _dns_one_delrecord "$id"
-      fi
-    fi
-  fi
 
   #Check if the TXT exists
   _dns_one_getrecord "TXT" "$subdomain" "$txtvalue"
@@ -136,7 +87,7 @@ dns_one_rm() {
 # _domain=domain.com
 _get_root() {
   domain="$1"
-  i=2
+  i=1
   p=1
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@@ -163,8 +114,6 @@ _get_root() {
 _dns_one_login() {
 
   # get credentials
-  ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-$(_readaccountconf_mutable ONECOM_KeepCnameProxy)}"
-  ONECOM_KeepCnameProxy="${ONECOM_KeepCnameProxy:-0}"
   ONECOM_User="${ONECOM_User:-$(_readaccountconf_mutable ONECOM_User)}"
   ONECOM_Password="${ONECOM_Password:-$(_readaccountconf_mutable ONECOM_Password)}"
   if [ -z "$ONECOM_User" ] || [ -z "$ONECOM_Password" ]; then
@@ -176,7 +125,6 @@ _dns_one_login() {
   fi
 
   #save the api key and email to the account conf file.
-  _saveaccountconf_mutable ONECOM_KeepCnameProxy "$ONECOM_KeepCnameProxy"
   _saveaccountconf_mutable ONECOM_User "$ONECOM_User"
   _saveaccountconf_mutable ONECOM_Password "$ONECOM_Password"
 
index f6f9689a607cdb36f31f67ebac0776eb6605477b..e65babbdc0ae25763eed3f2c46c9806d82a4259b 100755 (executable)
@@ -261,7 +261,9 @@ _get_root() {
       return 1
     fi
 
-    if ! _contains "$response" "This service does not exist" >/dev/null && ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then
+    if ! _contains "$response" "This service does not exist" >/dev/null &&
+      ! _contains "$response" "This call has not been granted" >/dev/null &&
+      ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then
       _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
       _domain="$h"
       return 0
index 8f07e8c4a6a44a3a0afc7d2637db2c30de7e1cdb..6aa2e9538f156dcf047b337c27c82fe8ceaa4476 100755 (executable)
@@ -103,7 +103,7 @@ set_record() {
     _build_record_string "$oldchallenge"
   done
 
-  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+  if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then
     _err "Set txt record error."
     return 1
   fi
@@ -126,7 +126,7 @@ rm_record() {
 
   if _contains "$_existing_challenges" "$txtvalue"; then
     #Delete all challenges (PowerDNS API does not allow to delete content)
-    if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then
+    if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}" "application/json"; then
       _err "Delete txt record error."
       return 1
     fi
@@ -140,7 +140,7 @@ rm_record() {
         fi
       done
       #Recreate the existing challenges
-      if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}"; then
+      if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root" "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [$_record_string]}]}" "application/json"; then
         _err "Set txt record error."
         return 1
       fi
@@ -175,13 +175,13 @@ _get_root() {
   i=1
 
   if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then
-    _zones_response="$response"
+    _zones_response=$(echo "$response" | _normalizeJson)
   fi
 
   while true; do
     h=$(printf "%s" "$domain" | cut -d . -f $i-100)
 
-    if _contains "$_zones_response" "\"name\": \"$h.\""; then
+    if _contains "$_zones_response" "\"name\":\"$h.\""; then
       _domain="$h."
       if [ -z "$h" ]; then
         _domain="=2E"
@@ -203,12 +203,13 @@ _pdns_rest() {
   method=$1
   ep=$2
   data=$3
+  ct=$4
 
   export _H1="X-API-Key: $PDNS_Token"
 
   if [ ! "$method" = "GET" ]; then
     _debug data "$data"
-    response="$(_post "$data" "$PDNS_Url$ep" "" "$method")"
+    response="$(_post "$data" "$PDNS_Url$ep" "" "$method" "$ct")"
   else
     response="$(_get "$PDNS_Url$ep")"
   fi
diff --git a/dnsapi/dns_porkbun.sh b/dnsapi/dns_porkbun.sh
new file mode 100644 (file)
index 0000000..ad4455b
--- /dev/null
@@ -0,0 +1,157 @@
+#!/usr/bin/env sh
+
+#
+#PORKBUN_API_KEY="pk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+#PORKBUN_SECRET_API_KEY="sk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
+
+PORKBUN_Api="https://porkbun.com/api/json/v3"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_porkbun_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}"
+  PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}"
+
+  if [ -z "$PORKBUN_API_KEY" ] || [ -z "$PORKBUN_SECRET_API_KEY" ]; then
+    PORKBUN_API_KEY=''
+    PORKBUN_SECRET_API_KEY=''
+    _err "You didn't specify a Porkbun api key and secret api key yet."
+    _err "You can get yours from here https://porkbun.com/account/api."
+    return 1
+  fi
+
+  #save the credentials to the account conf file.
+  _saveaccountconf_mutable PORKBUN_API_KEY "$PORKBUN_API_KEY"
+  _saveaccountconf_mutable PORKBUN_SECRET_API_KEY "$PORKBUN_SECRET_API_KEY"
+
+  _debug 'First detect the root zone'
+  if ! _get_root "$fulldomain"; then
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
+  # we can not use updating anymore.
+  #  count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+  #  _debug count "$count"
+  #  if [ "$count" = "0" ]; then
+  _info "Adding record"
+  if _porkbun_rest POST "dns/create/$_domain" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+    if _contains "$response" '\"status\":"SUCCESS"'; then
+      _info "Added, OK"
+      return 0
+    elif _contains "$response" "The record already exists"; then
+      _info "Already exists, OK"
+      return 0
+    else
+      _err "Add txt record error. ($response)"
+      return 1
+    fi
+  fi
+  _err "Add txt record error."
+  return 1
+
+}
+
+#fulldomain txtvalue
+dns_porkbun_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  PORKBUN_API_KEY="${PORKBUN_API_KEY:-$(_readaccountconf_mutable PORKBUN_API_KEY)}"
+  PORKBUN_SECRET_API_KEY="${PORKBUN_SECRET_API_KEY:-$(_readaccountconf_mutable PORKBUN_SECRET_API_KEY)}"
+
+  _debug 'First detect the root zone'
+  if ! _get_root "$fulldomain"; then
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  count=$(echo "$response" | _egrep_o "\"count\": *[^,]*" | cut -d : -f 2 | tr -d " ")
+  _debug count "$count"
+  if [ "$count" = "0" ]; then
+    _info "Don't need to remove."
+  else
+    record_id=$(echo "$response" | tr '{' '\n' | grep -- "$txtvalue" | cut -d, -f1 | cut -d: -f2 | tr -d \")
+    _debug "record_id" "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if ! _porkbun_rest POST "dns/delete/$_domain/$record_id"; then
+      _err "Delete record error."
+      return 1
+    fi
+    echo "$response" | tr -d " " | grep '\"status\":"SUCCESS"' >/dev/null
+  fi
+
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+_get_root() {
+  domain=$1
+  i=1
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      return 1
+    fi
+
+    if _porkbun_rest POST "dns/retrieve/$h"; then
+      if _contains "$response" "\"status\":\"SUCCESS\""; then
+        _domain=$h
+        _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
+        return 0
+      else
+        _debug "Go to next level of $_domain"
+      fi
+    else
+      _debug "Go to next level of $_domain"
+    fi
+    i=$(_math "$i" + 1)
+  done
+
+  return 1
+}
+
+_porkbun_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  api_key_trimmed=$(echo "$PORKBUN_API_KEY" | tr -d '"')
+  secret_api_key_trimmed=$(echo "$PORKBUN_SECRET_API_KEY" | tr -d '"')
+
+  test -z "$data" && data="{" || data="$(echo $data | cut -d'}' -f1),"
+  data="$data\"apikey\":\"$api_key_trimmed\",\"secretapikey\":\"$secret_api_key_trimmed\"}"
+
+  export _H1="Content-Type: application/json"
+
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    response="$(_post "$data" "$PORKBUN_Api/$ep" "" "$m")"
+  else
+    response="$(_get "$PORKBUN_Api/$ep")"
+  fi
+
+  _sleep 3 # prevent rate limit
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}
index 03e1fa686ef2ed56177ad5c2df4f754d82b4959b..b50d9168da6b0d36ef0a09522c174953439f81b0 100644 (file)
@@ -7,6 +7,7 @@
 
 RACKSPACE_Endpoint="https://dns.api.rackspacecloud.com/v1.0"
 
+# 20210923 - RS changed the fields in the API response; fix sed
 # 20190213 - The name & id fields swapped in the API response; fix sed
 # 20190101 - Duplicating file for new pull request to dev branch
 # Original - tcocca:rackspace_dnsapi https://github.com/acmesh-official/acme.sh/pull/1297
@@ -79,8 +80,8 @@ _get_root_zone() {
     _debug2 response "$response"
     if _contains "$response" "\"name\":\"$h\"" >/dev/null; then
       # Response looks like:
-      #   {"ttl":300,"accountId":12345,"id":1111111,"name":"example.com","emailAddress": ...<and so on>
-      _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\([^,]*\),\"name\":\"$h\",.*/\1/p")
+      #   {"id":"12345","accountId":"1111111","name": "example.com","ttl":3600,"emailAddress": ... <and so on>
+      _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\"\([^,]*\)\",\"accountId\":\"[0-9]*\",\"name\":\"$h\",.*/\1/p")
       _debug2 domain_id "$_domain_id"
       if [ -n "$_domain_id" ]; then
         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
index e73d85b0977744a1f848826b2bbef1449b9d4a59..f70a2294434fcfd9c5a59010cf4150b2a41049b0 100755 (executable)
@@ -49,16 +49,42 @@ dns_servercow_add() {
   _debug _sub_domain "$_sub_domain"
   _debug _domain "$_domain"
 
-  if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then
-    if printf -- "%s" "$response" | grep "ok" >/dev/null; then
-      _info "Added, OK"
-      return 0
-    else
-      _err "add txt record error."
-      return 1
+  # check whether a txt record already exists for the subdomain
+  if printf -- "%s" "$response" | grep "{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\"" >/dev/null; then
+    _info "A txt record with the same name already exists."
+    # trim the string on the left
+    txtvalue_old=${response#*{\"name\":\"$_sub_domain\",\"ttl\":20,\"type\":\"TXT\",\"content\":\"}
+    # trim the string on the right
+    txtvalue_old=${txtvalue_old%%\"*}
+
+    _debug txtvalue_old "$txtvalue_old"
+
+    _info "Add the new txtvalue to the existing txt record."
+    if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":[\"$txtvalue\",\"$txtvalue_old\"],\"ttl\":20}"; then
+      if printf -- "%s" "$response" | grep "ok" >/dev/null; then
+        _info "Added additional txtvalue, OK"
+        return 0
+      else
+        _err "add txt record error."
+        return 1
+      fi
     fi
+    _err "add txt record error."
+    return 1
+  else
+    _info "There is no txt record with the name yet."
+    if _servercow_api POST "$_domain" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":20}"; then
+      if printf -- "%s" "$response" | grep "ok" >/dev/null; then
+        _info "Added, OK"
+        return 0
+      else
+        _err "add txt record error."
+        return 1
+      fi
+    fi
+    _err "add txt record error."
+    return 1
   fi
-  _err "add txt record error."
 
   return 1
 }
index d053dcf6cea518403f84b6b3f8be5754cc9950b4..e0e0501761ae389c2ad67aa20c31bc75a91137da 100644 (file)
@@ -6,9 +6,11 @@
 #SIMPLY_ApiKey="apikey"
 #
 #SIMPLY_Api="https://api.simply.com/1/[ACCOUNTNAME]/[APIKEY]"
-
 SIMPLY_Api_Default="https://api.simply.com/1"
 
+#This is used for determining success of REST call
+SIMPLY_SUCCESS_CODE='"status": 200'
+
 ########  Public functions #####################
 #Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_simply_add() {
@@ -171,7 +173,7 @@ _get_root() {
       return 1
     fi
 
-    if _contains "$response" '"code":"NOT_FOUND"'; then
+    if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
       _debug "$h not found"
     else
       _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
@@ -196,6 +198,12 @@ _simply_add_record() {
     return 1
   fi
 
+  if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+    _err "Call to API not sucessfull, see below message for more details"
+    _err "$response"
+    return 1
+  fi
+
   return 0
 }
 
@@ -211,6 +219,12 @@ _simply_delete_record() {
     return 1
   fi
 
+  if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then
+    _err "Call to API not sucessfull, see below message for more details"
+    _err "$response"
+    return 1
+  fi
+
   return 0
 }
 
diff --git a/dnsapi/dns_veesp.sh b/dnsapi/dns_veesp.sh
new file mode 100644 (file)
index 0000000..b8a41d0
--- /dev/null
@@ -0,0 +1,158 @@
+#!/usr/bin/env sh
+
+# bug reports to stepan@plyask.in
+
+#
+#     export VEESP_User="username"
+#     export VEESP_Password="password"
+
+VEESP_Api="https://secure.veesp.com/api"
+
+########  Public functions #####################
+
+#Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_veesp_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}"
+  VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}"
+  VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64)
+
+  if [ -z "$VEESP_Password" ] || [ -z "$VEESP_User" ]; then
+    VEESP_Password=""
+    VEESP_User=""
+    _err "You don't specify veesp api key and email yet."
+    _err "Please create you key and try again."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable VEESP_Password "$VEESP_Password"
+  _saveaccountconf_mutable VEESP_User "$VEESP_User"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _info "Adding record"
+  if VEESP_rest POST "service/$_service_id/dns/$_domain_id/records" "{\"name\":\"$fulldomain\",\"ttl\":1,\"priority\":0,\"type\":\"TXT\",\"content\":\"$txtvalue\"}"; then
+    if _contains "$response" "\"success\":true"; then
+      _info "Added"
+      #todo: check if the record takes effect
+      return 0
+    else
+      _err "Add txt record error."
+      return 1
+    fi
+  fi
+}
+
+# Usage: fulldomain txtvalue
+# Used to remove the txt record after validation
+dns_veesp_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  VEESP_Password="${VEESP_Password:-$(_readaccountconf_mutable VEESP_Password)}"
+  VEESP_User="${VEESP_User:-$(_readaccountconf_mutable VEESP_User)}"
+  VEESP_auth=$(printf "%s" "$VEESP_User:$VEESP_Password" | _base64)
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  VEESP_rest GET "service/$_service_id/dns/$_domain_id"
+
+  count=$(printf "%s\n" "$response" | _egrep_o "\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | wc -l | tr -d " ")
+  _debug count "$count"
+  if [ "$count" = "0" ]; then
+    _info "Don't need to remove."
+  else
+    record_id=$(printf "%s\n" "$response" | _egrep_o "{\"id\":[^}]*\"type\":\"TXT\",\"content\":\".\"$txtvalue.\"\"" | cut -d\" -f4)
+    _debug "record_id" "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if ! VEESP_rest DELETE "service/$_service_id/dns/$_domain_id/records/$record_id"; then
+      _err "Delete record error."
+      return 1
+    fi
+    _contains "$response" "\"success\":true"
+  fi
+}
+
+####################  Private functions below ##################################
+#_acme-challenge.www.domain.com
+#returns
+# _sub_domain=_acme-challenge.www
+# _domain=domain.com
+# _domain_id=sdjkglgdfewsdfg
+_get_root() {
+  domain=$1
+  i=2
+  p=1
+  if ! VEESP_rest GET "dns"; then
+    return 1
+  fi
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if _contains "$response" "\"name\":\"$h\""; then
+      _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"domain_id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1 | cut -d '"' -f 2)
+      _debug _domain_id "$_domain_id"
+      _service_id=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$h\",\"service_id\":[^}]*" | cut -d : -f 3 | cut -d '"' -f 2)
+      _debug _service_id "$_service_id"
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _domain="$h"
+        return 0
+      fi
+      return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+VEESP_rest() {
+  m=$1
+  ep="$2"
+  data="$3"
+  _debug "$ep"
+
+  export _H1="Accept: application/json"
+  export _H2="Authorization: Basic $VEESP_auth"
+  if [ "$m" != "GET" ]; then
+    _debug data "$data"
+    export _H3="Content-Type: application/json"
+    response="$(_post "$data" "$VEESP_Api/$ep" "" "$m")"
+  else
+    response="$(_get "$VEESP_Api/$ep")"
+  fi
+
+  if [ "$?" != "0" ]; then
+    _err "error $ep"
+    return 1
+  fi
+  _debug2 response "$response"
+  return 0
+}
index c7b52e845414b9c7a47045cc36d237fefa45c67b..848579661bf6cd268cca1d0abdde5264eae9af55 100644 (file)
@@ -33,7 +33,7 @@ dns_vultr_add() {
   _debug 'Getting txt records'
   _vultr_rest GET "dns/records?domain=$_domain"
 
-  if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
+  if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
     _err 'Error'
     return 1
   fi
@@ -73,12 +73,12 @@ dns_vultr_rm() {
   _debug 'Getting txt records'
   _vultr_rest GET "dns/records?domain=$_domain"
 
-  if printf "%s\n" "$response" | grep "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
+  if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
     _err 'Error'
     return 1
   fi
 
-  _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
+  _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
   _debug _record_id "$_record_id"
   if [ "$_record_id" ]; then
     _info "Successfully retrieved the record id for ACME challenge."
diff --git a/dnsapi/dns_websupport.sh b/dnsapi/dns_websupport.sh
new file mode 100644 (file)
index 0000000..e824c9c
--- /dev/null
@@ -0,0 +1,207 @@
+#!/usr/bin/env sh
+
+# Acme.sh DNS API wrapper for websupport.sk
+#
+# Original author: trgo.sk (https://github.com/trgosk)
+# Tweaks by: akulumbeg (https://github.com/akulumbeg)
+# Report Bugs here: https://github.com/akulumbeg/acme.sh
+
+# Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey
+#
+# WS_ApiKey="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+# (called "Identifier" in the WS Admin)
+#
+# WS_ApiSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+# (called "Secret key" in the WS Admin)
+
+WS_Api="https://rest.websupport.sk"
+
+########  Public functions #####################
+
+dns_websupport_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  WS_ApiKey="${WS_ApiKey:-$(_readaccountconf_mutable WS_ApiKey)}"
+  WS_ApiSecret="${WS_ApiSecret:-$(_readaccountconf_mutable WS_ApiSecret)}"
+
+  if [ "$WS_ApiKey" ] && [ "$WS_ApiSecret" ]; then
+    _saveaccountconf_mutable WS_ApiKey "$WS_ApiKey"
+    _saveaccountconf_mutable WS_ApiSecret "$WS_ApiSecret"
+  else
+    WS_ApiKey=""
+    WS_ApiSecret=""
+    _err "You did not specify the API Key and/or API Secret"
+    _err "You can get the API login credentials from https://admin.websupport.sk/en/auth/apiKey"
+    return 1
+  fi
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so
+  # we can not use updating anymore.
+  #  count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2)
+  #  _debug count "$count"
+  #  if [ "$count" = "0" ]; then
+  _info "Adding record"
+  if _ws_rest POST "/v1/user/self/zone/$_domain/record" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then
+    if _contains "$response" "$txtvalue"; then
+      _info "Added, OK"
+      return 0
+    elif _contains "$response" "The record already exists"; then
+      _info "Already exists, OK"
+      return 0
+    else
+      _err "Add txt record error."
+      return 1
+    fi
+  fi
+  _err "Add txt record error."
+  return 1
+
+}
+
+dns_websupport_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _debug2 fulldomain "$fulldomain"
+  _debug2 txtvalue "$txtvalue"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  _ws_rest GET "/v1/user/self/zone/$_domain/record"
+
+  if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"items\")" -lt "1" ]; then
+    _err "Error: $response"
+    return 1
+  fi
+
+  record_line="$(_get_from_array "$response" "$txtvalue")"
+  _debug record_line "$record_line"
+  if [ -z "$record_line" ]; then
+    _info "Don't need to remove."
+  else
+    record_id=$(echo "$record_line" | _egrep_o "\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+    _debug "record_id" "$record_id"
+    if [ -z "$record_id" ]; then
+      _err "Can not get record id to remove."
+      return 1
+    fi
+    if ! _ws_rest DELETE "/v1/user/self/zone/$_domain/record/$record_id"; then
+      _err "Delete record error."
+      return 1
+    fi
+    if [ "$(printf "%s" "$response" | tr -d " " | grep -c \"success\")" -lt "1" ]; then
+      return 1
+    else
+      return 0
+    fi
+  fi
+
+}
+
+####################  Private Functions ##################################
+
+_get_root() {
+  domain=$1
+  i=1
+  p=1
+
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug h "$h"
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if ! _ws_rest GET "/v1/user/self/zone"; then
+      return 1
+    fi
+
+    if _contains "$response" "\"name\":\"$h\""; then
+      _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ")
+      if [ "$_domain_id" ]; then
+        _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
+        _domain=$h
+        return 0
+      fi
+      return 1
+    fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
+  return 1
+}
+
+_ws_rest() {
+  me=$1
+  pa="$2"
+  da="$3"
+
+  _debug2 api_key "$WS_ApiKey"
+  _debug2 api_secret "$WS_ApiSecret"
+
+  timestamp=$(_time)
+  datez="$(_utc_date | sed "s/ /T/" | sed "s/$/+0000/")"
+  canonical_request="${me} ${pa} ${timestamp}"
+  signature_hash=$(printf "%s" "$canonical_request" | _hmac sha1 "$(printf "%s" "$WS_ApiSecret" | _hex_dump | tr -d " ")" hex)
+  basicauth="$(printf "%s:%s" "$WS_ApiKey" "$signature_hash" | _base64)"
+
+  _debug2 method "$me"
+  _debug2 path "$pa"
+  _debug2 data "$da"
+  _debug2 timestamp "$timestamp"
+  _debug2 datez "$datez"
+  _debug2 canonical_request "$canonical_request"
+  _debug2 signature_hash "$signature_hash"
+  _debug2 basicauth "$basicauth"
+
+  export _H1="Accept: application/json"
+  export _H2="Content-Type: application/json"
+  export _H3="Authorization: Basic ${basicauth}"
+  export _H4="Date: ${datez}"
+
+  _debug2 H1 "$_H1"
+  _debug2 H2 "$_H2"
+  _debug2 H3 "$_H3"
+  _debug2 H4 "$_H4"
+
+  if [ "$me" != "GET" ]; then
+    _debug2 "${me} $WS_Api${pa}"
+    _debug data "$da"
+    response="$(_post "$da" "${WS_Api}${pa}" "" "$me")"
+  else
+    _debug2 "GET $WS_Api${pa}"
+    response="$(_get "$WS_Api${pa}")"
+  fi
+
+  _debug2 response "$response"
+  return "$?"
+}
+
+_get_from_array() {
+  va="$1"
+  fi="$2"
+  for i in $(echo "$va" | sed "s/{/ /g"); do
+    if _contains "$i" "$fi"; then
+      echo "$i"
+      break
+    fi
+  done
+}
index 9ab406f6e101d65c7d6454a6fbb63d69ec7a63db..231c34b3990b1fc83f7628e7180b943c8a7034fe 100644 (file)
@@ -36,7 +36,6 @@ dns_world4you_add() {
   export _H1="Cookie: W4YSESSID=$sessid"
   form=$(_get "$WORLD4YOU_API/$paketnr/dns")
   formiddp=$(echo "$form" | grep 'AddDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="AddDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/')
-  formidttl=$(echo "$form" | grep 'AddDnsRecordForm\[uniqueFormIdTTL\]' | sed 's/^.*name="AddDnsRecordForm\[uniqueFormIdTTL\]" value="\([^"]*\)".*$/\1/')
   form_token=$(echo "$form" | grep 'AddDnsRecordForm\[_token\]' | sed 's/^.*name="AddDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/')
   if [ -z "$formiddp" ]; then
     _err "Unable to parse form"
@@ -45,9 +44,7 @@ dns_world4you_add() {
 
   _resethttp
   export ACME_HTTP_NO_REDIRECTS=1
-  body="AddDnsRecordForm[name]=$RECORD&AddDnsRecordForm[dnsType][type]=TXT&\
-AddDnsRecordForm[value]=$value&AddDnsRecordForm[aktivPaket]=$paketnr&AddDnsRecordForm[uniqueFormIdDP]=$formiddp&\
-AddDnsRecordForm[uniqueFormIdTTL]=$formidttl&AddDnsRecordForm[_token]=$form_token"
+  body="AddDnsRecordForm[name]=$RECORD&AddDnsRecordForm[dnsType][type]=TXT&AddDnsRecordForm[value]=$value&AddDnsRecordForm[uniqueFormIdDP]=$formiddp&AddDnsRecordForm[_token]=$form_token"
   _info "Adding record..."
   ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded')
   _resethttp
@@ -101,7 +98,6 @@ dns_world4you_rm() {
 
   form=$(_get "$WORLD4YOU_API/$paketnr/dns")
   formiddp=$(echo "$form" | grep 'DeleteDnsRecordForm\[uniqueFormIdDP\]' | sed 's/^.*name="DeleteDnsRecordForm\[uniqueFormIdDP\]" value="\([^"]*\)".*$/\1/')
-  formidttl=$(echo "$form" | grep 'DeleteDnsRecordForm\[uniqueFormIdTTL\]' | sed 's/^.*name="DeleteDnsRecordForm\[uniqueFormIdTTL\]" value="\([^"]*\)".*$/\1/')
   form_token=$(echo "$form" | grep 'DeleteDnsRecordForm\[_token\]' | sed 's/^.*name="DeleteDnsRecordForm\[_token\]" value="\([^"]*\)".*$/\1/')
   if [ -z "$formiddp" ]; then
     _err "Unable to parse form"
@@ -113,11 +109,9 @@ dns_world4you_rm() {
 
   _resethttp
   export ACME_HTTP_NO_REDIRECTS=1
-  body="DeleteDnsRecordForm[recordId]=$recordid&DeleteDnsRecordForm[aktivPaket]=$paketnr&\
-DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[uniqueFormIdTTL]=$formidttl&\
-DeleteDnsRecordForm[_token]=$form_token"
+  body="DeleteDnsRecordForm[recordId]=$recordid&DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[_token]=$form_token"
   _info "Removing record..."
-  ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/deleteRecord" '' POST 'application/x-www-form-urlencoded')
+  ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns/record/delete" '' POST 'application/x-www-form-urlencoded')
   _resethttp
 
   if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then
@@ -190,7 +184,7 @@ _get_paketnr() {
   fqdn="$1"
   form="$2"
 
-  domains=$(echo "$form" | grep '^ *[A-Za-z0-9_\.-]*\.[A-Za-z0-9_-]*$' | sed 's/^\s*\(\S*\)$/\1/')
+  domains=$(echo "$form" | grep '^ *[A-Za-z0-9_\.-]*\.[A-Za-z0-9_-]*$' | sed 's/^ *\(.*\)$/\1/')
   domain=''
   for domain in $domains; do
     if _contains "$fqdn" "$domain\$"; then
diff --git a/notify/bark.sh b/notify/bark.sh
new file mode 100644 (file)
index 0000000..bbd5bf3
--- /dev/null
@@ -0,0 +1,51 @@
+#!/usr/bin/env sh
+
+#Support iOS Bark Notification
+
+#BARK_API_URL="https://api.day.app/xxxx"
+#BARK_SOUND="yyyy"
+#BARK_GROUP="zzzz"
+
+# subject  content statusCode
+bark_send() {
+  _subject="$1"
+  _content="$2"
+  _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+  _debug "_subject" "$_subject"
+  _debug "_content" "$_content"
+  _debug "_statusCode" "$_statusCode"
+
+  BARK_API_URL="${BARK_API_URL:-$(_readaccountconf_mutable BARK_API_URL)}"
+  if [ -z "$BARK_API_URL" ]; then
+    BARK_API_URL=""
+    _err "You didn't specify a Bark API URL BARK_API_URL yet."
+    _err "You can download Bark from App Store and get yours."
+    return 1
+  fi
+  _saveaccountconf_mutable BARK_API_URL "$BARK_API_URL"
+
+  BARK_SOUND="${BARK_SOUND:-$(_readaccountconf_mutable BARK_SOUND)}"
+  _saveaccountconf_mutable BARK_SOUND "$BARK_SOUND"
+
+  BARK_GROUP="${BARK_GROUP:-$(_readaccountconf_mutable BARK_GROUP)}"
+  if [ -z "$BARK_GROUP" ]; then
+    BARK_GROUP="ACME"
+    _info "The BARK_GROUP is not set, so use the default ACME as group name."
+  else
+    _saveaccountconf_mutable BARK_GROUP "$BARK_GROUP"
+  fi
+
+  _content=$(echo "$_content" | _url_encode)
+  _subject=$(echo "$_subject" | _url_encode)
+
+  response="$(_get "$BARK_API_URL/$_subject/$_content?sound=$BARK_SOUND&group=$BARK_GROUP")"
+
+  if [ "$?" = "0" ] && _contains "$response" "success"; then
+    _info "Bark API fired success."
+    return 0
+  fi
+
+  _err "Bark API fired error."
+  _err "$response"
+  return 1
+}
diff --git a/notify/feishu.sh b/notify/feishu.sh
new file mode 100644 (file)
index 0000000..18693c2
--- /dev/null
@@ -0,0 +1,48 @@
+#!/usr/bin/env sh
+
+#Support feishu webhooks api
+
+#required
+#FEISHU_WEBHOOK="xxxx"
+
+#optional
+#FEISHU_KEYWORD="yyyy"
+
+# subject content statusCode
+feishu_send() {
+  _subject="$1"
+  _content="$2"
+  _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+  _debug "_subject" "$_subject"
+  _debug "_content" "$_content"
+  _debug "_statusCode" "$_statusCode"
+
+  FEISHU_WEBHOOK="${FEISHU_WEBHOOK:-$(_readaccountconf_mutable FEISHU_WEBHOOK)}"
+  if [ -z "$FEISHU_WEBHOOK" ]; then
+    FEISHU_WEBHOOK=""
+    _err "You didn't specify a feishu webhooks FEISHU_WEBHOOK yet."
+    _err "You can get yours from https://www.feishu.cn"
+    return 1
+  fi
+  _saveaccountconf_mutable FEISHU_WEBHOOK "$FEISHU_WEBHOOK"
+
+  FEISHU_KEYWORD="${FEISHU_KEYWORD:-$(_readaccountconf_mutable FEISHU_KEYWORD)}"
+  if [ "$FEISHU_KEYWORD" ]; then
+    _saveaccountconf_mutable FEISHU_KEYWORD "$FEISHU_KEYWORD"
+  fi
+
+  _content=$(echo "$_content" | _json_encode)
+  _subject=$(echo "$_subject" | _json_encode)
+  _data="{\"msg_type\": \"text\", \"content\": {\"text\": \"[$FEISHU_KEYWORD]\n$_subject\n$_content\"}}"
+
+  response="$(_post "$_data" "$FEISHU_WEBHOOK" "" "POST" "application/json")"
+
+  if [ "$?" = "0" ] && _contains "$response" "StatusCode\":0"; then
+    _info "feishu webhooks event fired success."
+    return 0
+  fi
+
+  _err "feishu webhooks event fired error."
+  _err "$response"
+  return 1
+}
index d33fd0d2881db89f373a41197238b7e78afe39f5..656dd371512929608b605a3b6e7135c8e12c999e 100644 (file)
@@ -62,7 +62,7 @@ mail_send() {
   fi
 
   contenttype="text/plain; charset=utf-8"
-  subject="=?UTF-8?B?$(echo "$_subject" | _base64)?="
+  subject="=?UTF-8?B?$(printf -- "%b" "$_subject" | _base64)?="
   result=$({ _mail_body | eval "$(_mail_cmnd)"; } 2>&1)
 
   # shellcheck disable=SC2181
@@ -79,7 +79,7 @@ mail_send() {
 _mail_bin() {
   _MAIL_BIN=""
 
-  for b in "$MAIL_BIN" sendmail ssmtp mutt mail msmtp; do
+  for b in $MAIL_BIN sendmail ssmtp mutt mail msmtp; do
     if _exists "$b"; then
       _MAIL_BIN="$b"
       break
@@ -131,6 +131,7 @@ _mail_body() {
     echo "To: $MAIL_TO"
     echo "Subject: $subject"
     echo "Content-Type: $contenttype"
+    echo "MIME-Version: 1.0"
     echo
     ;;
   esac
diff --git a/notify/pushbullet.sh b/notify/pushbullet.sh
new file mode 100644 (file)
index 0000000..ca997c8
--- /dev/null
@@ -0,0 +1,44 @@
+#!/usr/bin/env sh
+
+#Support for pushbullet.com's api. Push notification, notification sync and message platform for multiple platforms
+#PUSHBULLET_TOKEN="" Required, pushbullet application token
+#PUSHBULLET_DEVICE="" Optional, Specific device, ignore to send to all devices
+
+PUSHBULLET_URI="https://api.pushbullet.com/v2/pushes"
+pushbullet_send() {
+  _subject="$1"
+  _content="$2"
+  _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+  _debug "_statusCode" "$_statusCode"
+
+  PUSHBULLET_TOKEN="${PUSHBULLET_TOKEN:-$(_readaccountconf_mutable PUSHBULLET_TOKEN)}"
+  if [ -z "$PUSHBULLET_TOKEN" ]; then
+    PUSHBULLET_TOKEN=""
+    _err "You didn't specify a Pushbullet application token yet."
+    return 1
+  fi
+  _saveaccountconf_mutable PUSHBULLET_TOKEN "$PUSHBULLET_TOKEN"
+
+  PUSHBULLET_DEVICE="${PUSHBULLET_DEVICE:-$(_readaccountconf_mutable PUSHBULLET_DEVICE)}"
+  if [ -z "$PUSHBULLET_DEVICE" ]; then
+    _clearaccountconf_mutable PUSHBULLET_DEVICE
+  else
+    _saveaccountconf_mutable PUSHBULLET_DEVICE "$PUSHBULLET_DEVICE"
+  fi
+
+  export _H1="Content-Type: application/json"
+  export _H2="Access-Token: ${PUSHBULLET_TOKEN}"
+  _content="$(printf "*%s*\n" "$_content" | _json_encode)"
+  _subject="$(printf "*%s*\n" "$_subject" | _json_encode)"
+  _data="{\"type\": \"note\",\"title\": \"${_subject}\",\"body\": \"${_content}\",\"device_iden\": \"${PUSHBULLET_DEVICE}\"}"
+  response="$(_post "$_data" "$PUSHBULLET_URI")"
+
+  if [ "$?" != "0" ] || _contains "$response" "\"error_code\""; then
+    _err "PUSHBULLET send error."
+    _err "$response"
+    return 1
+  fi
+
+  _info "PUSHBULLET send success."
+  return 0
+}
index 0d5ea3b3683a752a40edc5c33073b259b293fb42..82d3f6c679ff3e06e3d147a212114484ddc614fb 100644 (file)
@@ -37,11 +37,19 @@ sendgrid_send() {
   fi
   _saveaccountconf_mutable SENDGRID_FROM "$SENDGRID_FROM"
 
+  SENDGRID_FROM_NAME="${SENDGRID_FROM_NAME:-$(_readaccountconf_mutable SENDGRID_FROM_NAME)}"
+  _saveaccountconf_mutable SENDGRID_FROM_NAME "$SENDGRID_FROM_NAME"
+
   export _H1="Authorization: Bearer $SENDGRID_API_KEY"
   export _H2="Content-Type: application/json"
 
   _content="$(echo "$_content" | _json_encode)"
-  _data="{\"personalizations\": [{\"to\": [{\"email\": \"$SENDGRID_TO\"}]}],\"from\": {\"email\": \"$SENDGRID_FROM\"},\"subject\": \"$_subject\",\"content\": [{\"type\": \"text/plain\", \"value\": \"$_content\"}]}"
+
+  if [ -z "$SENDGRID_FROM_NAME" ]; then
+    _data="{\"personalizations\": [{\"to\": [{\"email\": \"$SENDGRID_TO\"}]}],\"from\": {\"email\": \"$SENDGRID_FROM\"},\"subject\": \"$_subject\",\"content\": [{\"type\": \"text/plain\", \"value\": \"$_content\"}]}"
+  else
+    _data="{\"personalizations\": [{\"to\": [{\"email\": \"$SENDGRID_TO\"}]}],\"from\": {\"email\": \"$SENDGRID_FROM\", \"name\": \"$SENDGRID_FROM_NAME\"},\"subject\": \"$_subject\",\"content\": [{\"type\": \"text/plain\", \"value\": \"$_content\"}]}"
+  fi
   response="$(_post "$_data" "https://api.sendgrid.com/v3/mail/send")"
 
   if [ "$?" = "0" ] && [ -z "$response" ]; then
index 6aa37ca33b14a57aabf44ced3f5acb27c9917bd8..293c665ea7bc5442651a030034557147c68a14cd 100644 (file)
 
 # support smtp
 
+# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/3358
+
+# This implementation uses either curl or Python (3 or 2.7).
+# (See also the "mail" notify hook, which supports other ways to send mail.)
+
+# SMTP_FROM="from@example.com"  # required
+# SMTP_TO="to@example.com"  # required
+# SMTP_HOST="smtp.example.com"  # required
+# SMTP_PORT="25"  # defaults to 25, 465 or 587 depending on SMTP_SECURE
+# SMTP_SECURE="tls"  # one of "none", "ssl" (implicit TLS, TLS Wrapper), "tls" (explicit TLS, STARTTLS)
+# SMTP_USERNAME=""  # set if SMTP server requires login
+# SMTP_PASSWORD=""  # set if SMTP server requires login
+# SMTP_TIMEOUT="30"  # seconds for SMTP operations to timeout
+# SMTP_BIN="/path/to/python_or_curl"  # default finds first of python3, python2.7, python, pypy3, pypy, curl on PATH
+
+SMTP_SECURE_DEFAULT="tls"
+SMTP_TIMEOUT_DEFAULT="30"
+
+# subject content statuscode
 smtp_send() {
-  _subject="$1"
-  _content="$2"
-  _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
-  _debug "_subject" "$_subject"
-  _debug "_content" "$_content"
-  _debug "_statusCode" "$_statusCode"
-
-  _err "Not implemented yet."
-  return 1
+  SMTP_SUBJECT="$1"
+  SMTP_CONTENT="$2"
+  # UNUSED: _statusCode="$3" # 0: success, 1: error 2($RENEW_SKIP): skipped
+
+  # Load and validate config:
+  SMTP_BIN="$(_readaccountconf_mutable_default SMTP_BIN)"
+  if [ -n "$SMTP_BIN" ] && ! _exists "$SMTP_BIN"; then
+    _err "SMTP_BIN '$SMTP_BIN' does not exist."
+    return 1
+  fi
+  if [ -z "$SMTP_BIN" ]; then
+    # Look for a command that can communicate with an SMTP server.
+    # (Please don't add sendmail, ssmtp, mutt, mail, or msmtp here.
+    # Those are already handled by the "mail" notify hook.)
+    for cmd in python3 python2.7 python pypy3 pypy curl; do
+      if _exists "$cmd"; then
+        SMTP_BIN="$cmd"
+        break
+      fi
+    done
+    if [ -z "$SMTP_BIN" ]; then
+      _err "The smtp notify-hook requires curl or Python, but can't find any."
+      _err 'If you have one of them, define SMTP_BIN="/path/to/curl_or_python".'
+      _err 'Otherwise, see if you can use the "mail" notify-hook instead.'
+      return 1
+    fi
+  fi
+  _debug SMTP_BIN "$SMTP_BIN"
+  _saveaccountconf_mutable_default SMTP_BIN "$SMTP_BIN"
+
+  SMTP_FROM="$(_readaccountconf_mutable_default SMTP_FROM)"
+  SMTP_FROM="$(_clean_email_header "$SMTP_FROM")"
+  if [ -z "$SMTP_FROM" ]; then
+    _err "You must define SMTP_FROM as the sender email address."
+    return 1
+  fi
+  if _email_has_display_name "$SMTP_FROM"; then
+    _err "SMTP_FROM must be only a simple email address (sender@example.com)."
+    _err "Change your SMTP_FROM='$SMTP_FROM' to remove the display name."
+    return 1
+  fi
+  _debug SMTP_FROM "$SMTP_FROM"
+  _saveaccountconf_mutable_default SMTP_FROM "$SMTP_FROM"
+
+  SMTP_TO="$(_readaccountconf_mutable_default SMTP_TO)"
+  SMTP_TO="$(_clean_email_header "$SMTP_TO")"
+  if [ -z "$SMTP_TO" ]; then
+    _err "You must define SMTP_TO as the recipient email address(es)."
+    return 1
+  fi
+  if _email_has_display_name "$SMTP_TO"; then
+    _err "SMTP_TO must be only simple email addresses (to@example.com,to2@example.com)."
+    _err "Change your SMTP_TO='$SMTP_TO' to remove the display name(s)."
+    return 1
+  fi
+  _debug SMTP_TO "$SMTP_TO"
+  _saveaccountconf_mutable_default SMTP_TO "$SMTP_TO"
+
+  SMTP_HOST="$(_readaccountconf_mutable_default SMTP_HOST)"
+  if [ -z "$SMTP_HOST" ]; then
+    _err "You must define SMTP_HOST as the SMTP server hostname."
+    return 1
+  fi
+  _debug SMTP_HOST "$SMTP_HOST"
+  _saveaccountconf_mutable_default SMTP_HOST "$SMTP_HOST"
+
+  SMTP_SECURE="$(_readaccountconf_mutable_default SMTP_SECURE "$SMTP_SECURE_DEFAULT")"
+  case "$SMTP_SECURE" in
+  "none") smtp_port_default="25" ;;
+  "ssl") smtp_port_default="465" ;;
+  "tls") smtp_port_default="587" ;;
+  *)
+    _err "Invalid SMTP_SECURE='$SMTP_SECURE'. It must be 'ssl', 'tls' or 'none'."
+    return 1
+    ;;
+  esac
+  _debug SMTP_SECURE "$SMTP_SECURE"
+  _saveaccountconf_mutable_default SMTP_SECURE "$SMTP_SECURE" "$SMTP_SECURE_DEFAULT"
+
+  SMTP_PORT="$(_readaccountconf_mutable_default SMTP_PORT "$smtp_port_default")"
+  case "$SMTP_PORT" in
+  *[!0-9]*)
+    _err "Invalid SMTP_PORT='$SMTP_PORT'. It must be a port number."
+    return 1
+    ;;
+  esac
+  _debug SMTP_PORT "$SMTP_PORT"
+  _saveaccountconf_mutable_default SMTP_PORT "$SMTP_PORT" "$smtp_port_default"
+
+  SMTP_USERNAME="$(_readaccountconf_mutable_default SMTP_USERNAME)"
+  _debug SMTP_USERNAME "$SMTP_USERNAME"
+  _saveaccountconf_mutable_default SMTP_USERNAME "$SMTP_USERNAME"
+
+  SMTP_PASSWORD="$(_readaccountconf_mutable_default SMTP_PASSWORD)"
+  _secure_debug SMTP_PASSWORD "$SMTP_PASSWORD"
+  _saveaccountconf_mutable_default SMTP_PASSWORD "$SMTP_PASSWORD"
+
+  SMTP_TIMEOUT="$(_readaccountconf_mutable_default SMTP_TIMEOUT "$SMTP_TIMEOUT_DEFAULT")"
+  _debug SMTP_TIMEOUT "$SMTP_TIMEOUT"
+  _saveaccountconf_mutable_default SMTP_TIMEOUT "$SMTP_TIMEOUT" "$SMTP_TIMEOUT_DEFAULT"
+
+  SMTP_X_MAILER="$(_clean_email_header "$PROJECT_NAME $VER --notify-hook smtp")"
+
+  # Run with --debug 2 (or above) to echo the transcript of the SMTP session.
+  # Careful: this may include SMTP_PASSWORD in plaintext!
+  if [ "${DEBUG:-$DEBUG_LEVEL_NONE}" -ge "$DEBUG_LEVEL_2" ]; then
+    SMTP_SHOW_TRANSCRIPT="True"
+  else
+    SMTP_SHOW_TRANSCRIPT=""
+  fi
+
+  SMTP_SUBJECT=$(_clean_email_header "$SMTP_SUBJECT")
+  _debug SMTP_SUBJECT "$SMTP_SUBJECT"
+  _debug SMTP_CONTENT "$SMTP_CONTENT"
+
+  # Send the message:
+  case "$(basename "$SMTP_BIN")" in
+  curl) _smtp_send=_smtp_send_curl ;;
+  py*) _smtp_send=_smtp_send_python ;;
+  *)
+    _err "Can't figure out how to invoke '$SMTP_BIN'."
+    _err "Check your SMTP_BIN setting."
+    return 1
+    ;;
+  esac
+
+  if ! smtp_output="$($_smtp_send)"; then
+    _err "Error sending message with $SMTP_BIN."
+    if [ -n "$smtp_output" ]; then
+      _err "$smtp_output"
+    fi
+    return 1
+  fi
+
+  return 0
+}
+
+# Strip CR and NL from text to prevent MIME header injection
+# text
+_clean_email_header() {
+  printf "%s" "$(echo "$1" | tr -d "\r\n")"
+}
+
+# Simple check for display name in an email address (< > or ")
+# email
+_email_has_display_name() {
+  _email="$1"
+  expr "$_email" : '^.*[<>"]' >/dev/null
+}
+
+##
+## curl smtp sending
+##
+
+# Send the message via curl using SMTP_* variables
+_smtp_send_curl() {
+  # Build curl args in $@
+  case "$SMTP_SECURE" in
+  none)
+    set -- --url "smtp://${SMTP_HOST}:${SMTP_PORT}"
+    ;;
+  ssl)
+    set -- --url "smtps://${SMTP_HOST}:${SMTP_PORT}"
+    ;;
+  tls)
+    set -- --url "smtp://${SMTP_HOST}:${SMTP_PORT}" --ssl-reqd
+    ;;
+  *)
+    # This will only occur if someone adds a new SMTP_SECURE option above
+    # without updating this code for it.
+    _err "Unhandled SMTP_SECURE='$SMTP_SECURE' in _smtp_send_curl"
+    _err "Please re-run with --debug and report a bug."
+    return 1
+    ;;
+  esac
+
+  set -- "$@" \
+    --upload-file - \
+    --mail-from "$SMTP_FROM" \
+    --max-time "$SMTP_TIMEOUT"
+
+  # Burst comma-separated $SMTP_TO into individual --mail-rcpt args.
+  _to="${SMTP_TO},"
+  while [ -n "$_to" ]; do
+    _rcpt="${_to%%,*}"
+    _to="${_to#*,}"
+    set -- "$@" --mail-rcpt "$_rcpt"
+  done
+
+  _smtp_login="${SMTP_USERNAME}:${SMTP_PASSWORD}"
+  if [ "$_smtp_login" != ":" ]; then
+    set -- "$@" --user "$_smtp_login"
+  fi
+
+  if [ "$SMTP_SHOW_TRANSCRIPT" = "True" ]; then
+    set -- "$@" --verbose
+  else
+    set -- "$@" --silent --show-error
+  fi
+
+  raw_message="$(_smtp_raw_message)"
+
+  _debug2 "curl command:" "$SMTP_BIN" "$*"
+  _debug2 "raw_message:\n$raw_message"
+
+  echo "$raw_message" | "$SMTP_BIN" "$@"
+}
+
+# Output an RFC-822 / RFC-5322 email message using SMTP_* variables.
+# (This assumes variables have already been cleaned for use in email headers.)
+_smtp_raw_message() {
+  echo "From: $SMTP_FROM"
+  echo "To: $SMTP_TO"
+  echo "Subject: $(_mime_encoded_word "$SMTP_SUBJECT")"
+  echo "Date: $(_rfc2822_date)"
+  echo "Content-Type: text/plain; charset=utf-8"
+  echo "X-Mailer: $SMTP_X_MAILER"
+  echo
+  echo "$SMTP_CONTENT"
+}
+
+# Convert text to RFC-2047 MIME "encoded word" format if it contains non-ASCII chars
+# text
+_mime_encoded_word() {
+  _text="$1"
+  # (regex character ranges like [a-z] can be locale-dependent; enumerate ASCII chars to avoid that)
+  _ascii='] $`"'"[!#%&'()*+,./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ~^_abcdefghijklmnopqrstuvwxyz{|}~-"
+  if expr "$_text" : "^.*[^$_ascii]" >/dev/null; then
+    # At least one non-ASCII char; convert entire thing to encoded word
+    printf "%s" "=?UTF-8?B?$(printf "%s" "$_text" | _base64)?="
+  else
+    # Just printable ASCII, no conversion needed
+    printf "%s" "$_text"
+  fi
+}
+
+# Output current date in RFC-2822 Section 3.3 format as required in email headers
+# (e.g., "Mon, 15 Feb 2021 14:22:01 -0800")
+_rfc2822_date() {
+  # Notes:
+  #   - this is deliberately not UTC, because it "SHOULD express local time" per spec
+  #   - the spec requires weekday and month in the C locale (English), not localized
+  #   - this date format specifier has been tested on Linux, Mac, Solaris and FreeBSD
+  _old_lc_time="$LC_TIME"
+  LC_TIME=C
+  date +'%a, %-d %b %Y %H:%M:%S %z'
+  LC_TIME="$_old_lc_time"
+}
+
+##
+## Python smtp sending
+##
+
+# Send the message via Python using SMTP_* variables
+_smtp_send_python() {
+  _debug "Python version" "$("$SMTP_BIN" --version 2>&1)"
+
+  # language=Python
+  "$SMTP_BIN" <<PYTHON
+# This code is meant to work with either Python 2.7.x or Python 3.4+.
+try:
+    try:
+        from email.message import EmailMessage
+        from email.policy import default as email_policy_default
+    except ImportError:
+        # Python 2 (or < 3.3)
+        from email.mime.text import MIMEText as EmailMessage
+        email_policy_default = None
+    from email.utils import formatdate as rfc2822_date
+    from smtplib import SMTP, SMTP_SSL, SMTPException
+    from socket import error as SocketError
+except ImportError as err:
+    print("A required Python standard package is missing. This system may have"
+          " a reduced version of Python unsuitable for sending mail: %s" % err)
+    exit(1)
+
+show_transcript = """$SMTP_SHOW_TRANSCRIPT""" == "True"
+
+smtp_host = """$SMTP_HOST"""
+smtp_port = int("""$SMTP_PORT""")
+smtp_secure = """$SMTP_SECURE"""
+username = """$SMTP_USERNAME"""
+password = """$SMTP_PASSWORD"""
+timeout=int("""$SMTP_TIMEOUT""")  # seconds
+x_mailer="""$SMTP_X_MAILER"""
+
+from_email="""$SMTP_FROM"""
+to_emails="""$SMTP_TO"""  # can be comma-separated
+subject="""$SMTP_SUBJECT"""
+content="""$SMTP_CONTENT"""
+
+try:
+    msg = EmailMessage(policy=email_policy_default)
+    msg.set_content(content)
+except (AttributeError, TypeError):
+    # Python 2 MIMEText
+    msg = EmailMessage(content)
+msg["Subject"] = subject
+msg["From"] = from_email
+msg["To"] = to_emails
+msg["Date"] = rfc2822_date(localtime=True)
+msg["X-Mailer"] = x_mailer
+
+smtp = None
+try:
+    if smtp_secure == "ssl":
+        smtp = SMTP_SSL(smtp_host, smtp_port, timeout=timeout)
+    else:
+        smtp = SMTP(smtp_host, smtp_port, timeout=timeout)
+    smtp.set_debuglevel(show_transcript)
+    if smtp_secure == "tls":
+        smtp.starttls()
+    if username or password:
+        smtp.login(username, password)
+    smtp.sendmail(msg["From"], msg["To"].split(","), msg.as_string())
+
+except SMTPException as err:
+    # Output just the error (skip the Python stack trace) for SMTP errors
+    print("Error sending: %r" % err)
+    exit(1)
+
+except SocketError as err:
+    print("Error connecting to %s:%d: %r" % (smtp_host, smtp_port, err))
+    exit(1)
+
+finally:
+    if smtp is not None:
+        smtp.quit()
+PYTHON
+}
+
+##
+## Conf helpers
+##
+
+#_readaccountconf_mutable_default name default_value
+# Given a name like MY_CONF:
+#   - if MY_CONF is set and non-empty, output $MY_CONF
+#   - if MY_CONF is set _empty_, output $default_value
+#     (lets user `export MY_CONF=` to clear previous saved value
+#     and return to default, without user having to know default)
+#   - otherwise if _readaccountconf_mutable MY_CONF is non-empty, return that
+#     (value of SAVED_MY_CONF from account.conf)
+#   - otherwise output $default_value
+_readaccountconf_mutable_default() {
+  _name="$1"
+  _default_value="$2"
+
+  eval "_value=\"\$$_name\""
+  eval "_name_is_set=\"\${${_name}+true}\""
+  # ($_name_is_set is "true" if $$_name is set to anything, including empty)
+  if [ -z "${_value}" ] && [ "${_name_is_set:-}" != "true" ]; then
+    _value="$(_readaccountconf_mutable "$_name")"
+  fi
+  if [ -z "${_value}" ]; then
+    _value="$_default_value"
+  fi
+  printf "%s" "$_value"
+}
+
+#_saveaccountconf_mutable_default name value default_value base64encode
+# Like _saveaccountconf_mutable, but if value is default_value
+# then _clearaccountconf_mutable instead
+_saveaccountconf_mutable_default() {
+  _name="$1"
+  _value="$2"
+  _default_value="$3"
+  _base64encode="$4"
+
+  if [ "$_value" != "$_default_value" ]; then
+    _saveaccountconf_mutable "$_name" "$_value" "$_base64encode"
+  else
+    _clearaccountconf_mutable "$_name"
+  fi
 }
index b1306ee1257fcc97bc7e6db51009b3f98851163b..454b4146fab6a1bd40895b77f14b34dab83cd61a 100644 (file)
@@ -27,15 +27,18 @@ telegram_send() {
   fi
   _saveaccountconf_mutable TELEGRAM_BOT_CHATID "$TELEGRAM_BOT_CHATID"
 
+  _content="$(printf "%s" "$_content" | sed -e 's/\([_*`\[]\)/\\\\\1/g')"
   _content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)"
   _data="{\"text\": \"$_content\", "
   _data="$_data\"chat_id\": \"$TELEGRAM_BOT_CHATID\", "
   _data="$_data\"parse_mode\": \"markdown\", "
   _data="$_data\"disable_web_page_preview\": \"1\"}"
 
+  _debug "$_data"
+
   export _H1="Content-Type: application/json"
   _telegram_bot_url="https://api.telegram.org/bot${TELEGRAM_BOT_APITOKEN}/sendMessage"
-  if _post "$_data" "$_telegram_bot_url"; then
+  if _post "$_data" "$_telegram_bot_url" >/dev/null; then
     # shellcheck disable=SC2154
     _message=$(printf "%s\n" "$response" | sed -n 's/.*"ok":\([^,]*\).*/\1/p')
     if [ "$_message" = "true" ]; then