소스 검색

Merge branch 'acmesh-official:dev' into dev

Marvo2011 3 년 전
부모
커밋
f3e77a989c
46개의 변경된 파일1849개의 추가작업 그리고 559개의 파일을 삭제
  1. 426 254
      .github/workflows/DNS.yml
  2. 71 0
      .github/workflows/DragonFlyBSD.yml
  3. 17 4
      .github/workflows/FreeBSD.yml
  4. 6 0
      .github/workflows/Linux.yml
  5. 5 0
      .github/workflows/MacOS.yml
  6. 72 0
      .github/workflows/NetBSD.yml
  7. 76 0
      .github/workflows/OpenBSD.yml
  8. 13 3
      .github/workflows/PebbleStrict.yml
  9. 17 4
      .github/workflows/Solaris.yml
  10. 13 1
      .github/workflows/Ubuntu.yml
  11. 5 0
      .github/workflows/Windows.yml
  12. 5 1
      .github/workflows/dockerhub.yml
  13. 19 0
      .github/workflows/issue.yml
  14. 30 0
      .github/workflows/pr_dns.yml
  15. 30 0
      .github/workflows/pr_notify.yml
  16. 5 0
      .github/workflows/shellcheck.yml
  17. 27 15
      README.md
  18. 46 25
      acme.sh
  19. 3 13
      deploy/mailcow.sh
  20. 132 0
      deploy/proxmoxve.sh
  21. 2 2
      deploy/qiniu.sh
  22. 24 26
      dnsapi/dns_aws.sh
  23. 10 1
      dnsapi/dns_cf.sh
  24. 1 1
      dnsapi/dns_cyon.sh
  25. 234 0
      dnsapi/dns_dnsservices.sh
  26. 4 0
      dnsapi/dns_edgedns.sh
  27. 2 2
      dnsapi/dns_gcloud.sh
  28. 40 16
      dnsapi/dns_gd.sh
  29. 13 11
      dnsapi/dns_huaweicloud.sh
  30. 19 11
      dnsapi/dns_ionos.sh
  31. 8 4
      dnsapi/dns_ispconfig.sh
  32. 208 95
      dnsapi/dns_kas.sh
  33. 147 0
      dnsapi/dns_la.sh
  34. 1 0
      dnsapi/dns_miab.sh
  35. 1 15
      dnsapi/dns_mydnsjp.sh
  36. 2 2
      dnsapi/dns_namecheap.sh
  37. 3 7
      dnsapi/dns_nederhost.sh
  38. 1 1
      dnsapi/dns_oci.sh
  39. 2 2
      dnsapi/dns_opnsense.sh
  40. 4 4
      dnsapi/dns_ovh.sh
  41. 2 2
      dnsapi/dns_regru.sh
  42. 2 2
      dnsapi/dns_selectel.sh
  43. 12 9
      dnsapi/dns_ultra.sh
  44. 11 11
      dnsapi/dns_vultr.sh
  45. 33 15
      dnsapi/dns_world4you.sh
  46. 45 0
      notify/slack_app.sh

+ 426 - 254
.github/workflows/DNS.yml

@@ -1,254 +1,426 @@
-name: DNS
-on:
-  push:
-    paths:
-      - 'dnsapi/*.sh'
-      - '.github/workflows/DNS.yml'
-  pull_request:
-    branches:
-      - 'dev'
-    paths:
-      - 'dnsapi/*.sh'
-      - '.github/workflows/DNS.yml'
-
-
-jobs:
-  CheckToken:
-    runs-on: ubuntu-latest
-    outputs:
-      hasToken: ${{ steps.step_one.outputs.hasToken }}
-    steps:
-      - name: Set the value
-        id: step_one
-        run: |
-          if [ "${{secrets.TokenName1}}" ] ; then
-            echo "::set-output name=hasToken::true"
-          else
-            echo "::set-output name=hasToken::false"
-          fi
-      - name: Check the value
-        run: echo ${{ steps.step_one.outputs.hasToken }}
-
-  Fail:
-    runs-on: ubuntu-latest
-    needs: CheckToken
-    if: "contains(needs.CheckToken.outputs.hasToken, 'false')"
-    steps:
-    - name: "Read this:   https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
-      run: |
-        echo "Read this:   https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
-        if [ "${{github.repository_owner}}" != "acmesh-official" ]; then
-          false
-        fi
-
-  Docker:
-    runs-on: ubuntu-latest
-    needs: CheckToken
-    if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
-    env:
-      TEST_DNS : ${{ secrets.TEST_DNS }}
-      TestingDomain: ${{ secrets.TestingDomain }}
-      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
-      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
-      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
-      CASE: le_test_dnsapi
-      TEST_LOCAL: 1
-      DEBUG: 1
-    steps:
-    - uses: actions/checkout@v2
-    - name: Clone acmetest
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - name: Set env file
-      run: |
-        cd ../acmetest
-        if [ "${{ secrets.TokenName1}}" ] ; then
-          echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> docker.env
-        fi
-        if [ "${{ secrets.TokenName2}}" ] ; then
-          echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> docker.env
-        fi
-        if [ "${{ secrets.TokenName3}}" ] ; then
-          echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> docker.env
-        fi
-        if [ "${{ secrets.TokenName4}}" ] ; then
-          echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> docker.env
-        fi
-        if [ "${{ secrets.TokenName5}}" ] ; then
-          echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env
-        fi
-        echo "TEST_DNS_NO_WILDCARD" >> docker.env
-        echo "TEST_DNS_SLEEP" >> docker.env
-    - name: Run acmetest
-      run: cd ../acmetest && ./rundocker.sh  testall
-
-  MacOS:
-    runs-on: macos-latest
-    needs: Docker
-    env:
-      TEST_DNS : ${{ secrets.TEST_DNS }}
-      TestingDomain: ${{ secrets.TestingDomain }}
-      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
-      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
-      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
-      CASE: le_test_dnsapi
-      TEST_LOCAL: 1
-      DEBUG: 1
-    steps:
-    - uses: actions/checkout@v2
-    - name: Install tools
-      run:  brew install socat
-    - name: Clone acmetest
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - name: Run acmetest
-      run: |
-        if [ "${{ secrets.TokenName1}}" ] ; then
-          export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
-        fi
-        if [ "${{ secrets.TokenName2}}" ] ; then
-          export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
-        fi
-        if [ "${{ secrets.TokenName3}}" ] ; then
-          export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
-        fi
-        if [ "${{ secrets.TokenName4}}" ] ; then
-          export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
-        fi
-        if [ "${{ secrets.TokenName5}}" ] ; then
-          export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
-        fi
-        cd ../acmetest
-        ./letest.sh
-
-  Windows:
-    runs-on: windows-latest
-    needs: MacOS
-    env:
-      TEST_DNS : ${{ secrets.TEST_DNS }}
-      TestingDomain: ${{ secrets.TestingDomain }}
-      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
-      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
-      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
-      CASE: le_test_dnsapi
-      TEST_LOCAL: 1
-      DEBUG: 1
-    steps:
-    - name: Set git to use LF
-      run: |
-          git config --global core.autocrlf false
-    - uses: actions/checkout@v2
-    - name: Install cygwin base packages with chocolatey
-      run: |
-          choco config get cacheLocation
-          choco install --no-progress cygwin
-      shell: cmd
-    - name: Install cygwin additional packages
-      run: |
-          C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
-      shell: cmd
-    - name: Set ENV
-      shell: cmd
-      run: |
-          echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
-    - name: Clone acmetest
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - name: Run acmetest
-      shell: bash
-      run: |
-        if [ "${{ secrets.TokenName1}}" ] ; then
-          export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
-        fi
-        if [ "${{ secrets.TokenName2}}" ] ; then
-          export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
-        fi
-        if [ "${{ secrets.TokenName3}}" ] ; then
-          export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
-        fi
-        if [ "${{ secrets.TokenName4}}" ] ; then
-          export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
-        fi
-        if [ "${{ secrets.TokenName5}}" ] ; then
-          export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
-        fi
-        cd ../acmetest
-        ./letest.sh
-
-  FreeBSD:
-    runs-on: macos-10.15
-    needs: Windows
-    env:
-      TEST_DNS : ${{ secrets.TEST_DNS }}
-      TestingDomain: ${{ secrets.TestingDomain }}
-      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
-      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
-      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
-      CASE: le_test_dnsapi
-      TEST_LOCAL: 1
-      DEBUG: 1
-    steps:
-    - uses: actions/checkout@v2
-    - name: Clone acmetest
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/[email protected]
-      with:
-        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
-        prepare: pkg install -y socat curl
-        usesh: true
-        run: |
-          if [ "${{ secrets.TokenName1}}" ] ; then
-            export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
-          fi
-          if [ "${{ secrets.TokenName2}}" ] ; then
-            export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
-          fi
-          if [ "${{ secrets.TokenName3}}" ] ; then
-            export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
-          fi
-          if [ "${{ secrets.TokenName4}}" ] ; then
-            export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
-          fi
-          if [ "${{ secrets.TokenName5}}" ] ; then
-            export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
-          fi
-          cd ../acmetest
-          ./letest.sh
-
-  Solaris:
-    runs-on: macos-10.15
-    needs: FreeBSD
-    env:
-      TEST_DNS : ${{ secrets.TEST_DNS }}
-      TestingDomain: ${{ secrets.TestingDomain }}
-      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
-      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
-      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
-      CASE: le_test_dnsapi
-      TEST_LOCAL: 1
-      DEBUG: 1
-    steps:
-    - uses: actions/checkout@v2
-    - name: Clone acmetest
-      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/[email protected]
-      with:
-        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
-        prepare: pkgutil -y -i socat
-        run: |
-          pkg set-mediator -v -I [email protected] openssl
-          export PATH=/usr/gnu/bin:$PATH
-          if [ "${{ secrets.TokenName1}}" ] ; then
-            export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
-          fi
-          if [ "${{ secrets.TokenName2}}" ] ; then
-            export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
-          fi
-          if [ "${{ secrets.TokenName3}}" ] ; then
-            export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
-          fi
-          if [ "${{ secrets.TokenName4}}" ] ; then
-            export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
-          fi
-          if [ "${{ secrets.TokenName5}}" ] ; then
-            export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
-          fi
-          cd ../acmetest
-          ./letest.sh
+name: DNS
+on:
+  push:
+    paths:
+      - 'dnsapi/*.sh'
+      - '.github/workflows/DNS.yml'
+  pull_request:
+    branches:
+      - 'dev'
+    paths:
+      - 'dnsapi/*.sh'
+      - '.github/workflows/DNS.yml'
+
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+jobs:
+  CheckToken:
+    runs-on: ubuntu-latest
+    outputs:
+      hasToken: ${{ steps.step_one.outputs.hasToken }}
+    steps:
+      - name: Set the value
+        id: step_one
+        run: |
+          if [ "${{secrets.TokenName1}}" ] ; then
+            echo "::set-output name=hasToken::true"
+          else
+            echo "::set-output name=hasToken::false"
+          fi
+      - name: Check the value
+        run: echo ${{ steps.step_one.outputs.hasToken }}
+
+  Fail:
+    runs-on: ubuntu-latest
+    needs: CheckToken
+    if: "contains(needs.CheckToken.outputs.hasToken, 'false')"
+    steps:
+    - name: "Read this:   https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
+      run: |
+        echo "Read this:   https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test"
+        if [ "${{github.repository_owner}}" != "acmesh-official" ]; then
+          false
+        fi
+
+  Docker:
+    runs-on: ubuntu-latest
+    needs: CheckToken
+    if: "contains(needs.CheckToken.outputs.hasToken, 'true')"
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - name: Set env file
+      run: |
+        cd ../acmetest
+        if [ "${{ secrets.TokenName1}}" ] ; then
+          echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> docker.env
+        fi
+        if [ "${{ secrets.TokenName2}}" ] ; then
+          echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> docker.env
+        fi
+        if [ "${{ secrets.TokenName3}}" ] ; then
+          echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> docker.env
+        fi
+        if [ "${{ secrets.TokenName4}}" ] ; then
+          echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> docker.env
+        fi
+        if [ "${{ secrets.TokenName5}}" ] ; then
+          echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> docker.env
+        fi
+        echo "TEST_DNS_NO_WILDCARD" >> docker.env
+        echo "http_proxy" >> docker.env
+        echo "https_proxy" >> docker.env
+    - name: Run acmetest
+      run: cd ../acmetest && ./rundocker.sh  testall
+
+
+
+
+  MacOS:
+    runs-on: macos-latest
+    needs: Docker
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Install tools
+      run:  brew install socat
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - name: Run acmetest
+      run: |
+        if [ "${{ secrets.TokenName1}}" ] ; then
+          export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
+        fi
+        if [ "${{ secrets.TokenName2}}" ] ; then
+          export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
+        fi
+        if [ "${{ secrets.TokenName3}}" ] ; then
+          export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
+        fi
+        if [ "${{ secrets.TokenName4}}" ] ; then
+          export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
+        fi
+        if [ "${{ secrets.TokenName5}}" ] ; then
+          export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
+        fi
+        cd ../acmetest
+        ./letest.sh
+
+
+
+
+  Windows:
+    runs-on: windows-latest
+    needs: MacOS
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - name: Set git to use LF
+      run: |
+          git config --global core.autocrlf false
+    - uses: actions/checkout@v2
+    - name: Install cygwin base packages with chocolatey
+      run: |
+          choco config get cacheLocation
+          choco install --no-progress cygwin
+      shell: cmd
+    - name: Install cygwin additional packages
+      run: |
+          C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git
+      shell: cmd
+    - name: Set ENV
+      shell: cmd
+      run: |
+          echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV%
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - name: Run acmetest
+      shell: bash
+      run: |
+        if [ "${{ secrets.TokenName1}}" ] ; then
+          export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
+        fi
+        if [ "${{ secrets.TokenName2}}" ] ; then
+          export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
+        fi
+        if [ "${{ secrets.TokenName3}}" ] ; then
+          export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
+        fi
+        if [ "${{ secrets.TokenName4}}" ] ; then
+          export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
+        fi
+        if [ "${{ secrets.TokenName5}}" ] ; then
+          export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
+        fi
+        cd ../acmetest
+        ./letest.sh
+
+
+
+  FreeBSD:
+    runs-on: macos-12
+    needs: Windows
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/freebsd-vm@v0
+      with:
+        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
+        prepare: pkg install -y socat curl
+        usesh: true
+        copyback: false
+        run: |
+          if [ "${{ secrets.TokenName1}}" ] ; then
+            export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
+          fi
+          if [ "${{ secrets.TokenName2}}" ] ; then
+            export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
+          fi
+          if [ "${{ secrets.TokenName3}}" ] ; then
+            export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
+          fi
+          if [ "${{ secrets.TokenName4}}" ] ; then
+            export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
+          fi
+          if [ "${{ secrets.TokenName5}}" ] ; then
+            export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
+          fi
+          cd ../acmetest
+          ./letest.sh
+
+
+
+
+  Solaris:
+    runs-on: macos-12
+    needs: FreeBSD
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/solaris-vm@v0
+      with:
+        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
+        copyback: false
+        prepare: pkgutil -y -i socat
+        run: |
+          pkg set-mediator -v -I [email protected] openssl
+          export PATH=/usr/gnu/bin:$PATH
+          if [ "${{ secrets.TokenName1}}" ] ; then
+            export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
+          fi
+          if [ "${{ secrets.TokenName2}}" ] ; then
+            export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
+          fi
+          if [ "${{ secrets.TokenName3}}" ] ; then
+            export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
+          fi
+          if [ "${{ secrets.TokenName4}}" ] ; then
+            export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
+          fi
+          if [ "${{ secrets.TokenName5}}" ] ; then
+            export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
+          fi
+          cd ../acmetest
+          ./letest.sh
+
+
+
+
+  OpenBSD:
+    runs-on: macos-12
+    needs: Solaris
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/openbsd-vm@v0
+      with:
+        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
+        prepare: pkg_add socat curl
+        usesh: true
+        copyback: false
+        run: |
+          if [ "${{ secrets.TokenName1}}" ] ; then
+            export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
+          fi
+          if [ "${{ secrets.TokenName2}}" ] ; then
+            export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
+          fi
+          if [ "${{ secrets.TokenName3}}" ] ; then
+            export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
+          fi
+          if [ "${{ secrets.TokenName4}}" ] ; then
+            export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
+          fi
+          if [ "${{ secrets.TokenName5}}" ] ; then
+            export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
+          fi
+          cd ../acmetest
+          ./letest.sh
+
+
+
+
+  NetBSD:
+    runs-on: macos-12
+    needs: OpenBSD
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/netbsd-vm@v0
+      with:
+        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
+        prepare: |
+          pkg_add curl socat
+        usesh: true
+        copyback: false
+        run: |
+          if [ "${{ secrets.TokenName1}}" ] ; then
+            export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
+          fi
+          if [ "${{ secrets.TokenName2}}" ] ; then
+            export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
+          fi
+          if [ "${{ secrets.TokenName3}}" ] ; then
+            export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
+          fi
+          if [ "${{ secrets.TokenName4}}" ] ; then
+            export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
+          fi
+          if [ "${{ secrets.TokenName5}}" ] ; then
+            export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
+          fi
+          cd ../acmetest
+          ./letest.sh
+
+
+
+
+  DragonFlyBSD:
+    runs-on: macos-12
+    needs: NetBSD
+    env:
+      TEST_DNS : ${{ secrets.TEST_DNS }}
+      TestingDomain: ${{ secrets.TestingDomain }}
+      TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }}
+      TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }}
+      TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }}
+      CASE: le_test_dnsapi
+      TEST_LOCAL: 1
+      DEBUG: ${{ secrets.DEBUG }}
+      http_proxy: ${{ secrets.http_proxy }}
+      https_proxy: ${{ secrets.https_proxy }}
+    steps:
+    - uses: actions/checkout@v2
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/dragonflybsd-vm@v0
+      with:
+        envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}'
+        prepare: |
+          pkg install -y curl socat
+        usesh: true
+        copyback: false
+        run: |
+          if [ "${{ secrets.TokenName1}}" ] ; then
+            export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}
+          fi
+          if [ "${{ secrets.TokenName2}}" ] ; then
+            export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}
+          fi
+          if [ "${{ secrets.TokenName3}}" ] ; then
+            export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}
+          fi
+          if [ "${{ secrets.TokenName4}}" ] ; then
+            export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}
+          fi
+          if [ "${{ secrets.TokenName5}}" ] ; then
+            export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}
+          fi
+          cd ../acmetest
+          ./letest.sh
+
+
+
+
+

+ 71 - 0
.github/workflows/DragonFlyBSD.yml

@@ -0,0 +1,71 @@
+name: DragonFlyBSD
+on:
+  push:
+    branches:
+      - '*'
+    paths:
+      - '*.sh'
+      - '.github/workflows/DragonFlyBSD.yml'
+
+  pull_request:
+    branches:
+      - dev
+    paths:
+      - '*.sh'
+      - '.github/workflows/DragonFlyBSD.yml'
+
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+
+
+jobs:
+  DragonFlyBSD:
+    strategy:
+      matrix:
+        include:
+         - TEST_ACME_Server: "LetsEncrypt.org_test"
+           CA_ECDSA: ""
+           CA: ""
+           CA_EMAIL: ""
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+         #- TEST_ACME_Server: "ZeroSSL.com"
+         #  CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
+         #  CA: "ZeroSSL RSA Domain Secure Site CA"
+         #  CA_EMAIL: "[email protected]"
+         #  TEST_PREFERRED_CHAIN: ""
+    runs-on: macos-12
+    env:
+      TEST_LOCAL: 1
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}
+      CA: ${{ matrix.CA }}
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
+    steps:
+    - uses: actions/checkout@v2
+    - uses: vmactions/[email protected]
+      id: tunnel
+      with:
+        protocol: http
+        port: 8080
+    - name: Set envs
+      run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/dragonflybsd-vm@v0
+      with:
+        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
+        copyback: "false"
+        nat: |
+          "8080": "80"
+        prepare: |
+          pkg install -y curl socat
+        usesh: true
+        run: |
+          cd ../acmetest \
+          && ./letest.sh
+
+

+ 17 - 4
.github/workflows/FreeBSD.yml

@@ -14,6 +14,11 @@ on:
       - '*.sh'
       - '.github/workflows/FreeBSD.yml'
 
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 
 jobs:
   FreeBSD:
@@ -25,12 +30,18 @@ jobs:
            CA: ""
            CA_EMAIL: ""
            TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+         - TEST_ACME_Server: "LetsEncrypt.org_test"
+           CA_ECDSA: ""
+           CA: ""
+           CA_EMAIL: ""
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+           ACME_USE_WGET: 1
          #- TEST_ACME_Server: "ZeroSSL.com"
          #  CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
          #  CA: "ZeroSSL RSA Domain Secure Site CA"
          #  CA_EMAIL: "[email protected]"
          #  TEST_PREFERRED_CHAIN: ""
-    runs-on: macos-10.15
+    runs-on: macos-12
     env:
       TEST_LOCAL: 1
       TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@@ -38,6 +49,7 @@ jobs:
       CA: ${{ matrix.CA }}
       CA_EMAIL: ${{ matrix.CA_EMAIL }}
       TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
+      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
     steps:
     - uses: actions/checkout@v2
     - uses: vmactions/[email protected]
@@ -49,13 +61,14 @@ jobs:
       run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
     - name: Clone acmetest
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/freebsd-vm@v0.1.5
+    - uses: vmactions/freebsd-vm@v0
       with:
-        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
+        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
         nat: |
           "8080": "80"
-        prepare: pkg install -y socat curl
+        prepare: pkg install -y socat curl wget
         usesh: true
+        copyback: false
         run: |
           cd ../acmetest \
           && ./letest.sh

+ 6 - 0
.github/workflows/Linux.yml

@@ -15,6 +15,12 @@ on:
       - '.github/workflows/Linux.yml'
 
 
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+
 
 jobs:
   Linux:

+ 5 - 0
.github/workflows/MacOS.yml

@@ -14,6 +14,11 @@ on:
       - '*.sh'
       - '.github/workflows/MacOS.yml'
 
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 
 jobs:
   MacOS:

+ 72 - 0
.github/workflows/NetBSD.yml

@@ -0,0 +1,72 @@
+name: NetBSD
+on:
+  push:
+    branches:
+      - '*'
+    paths:
+      - '*.sh'
+      - '.github/workflows/NetBSD.yml'
+
+  pull_request:
+    branches:
+      - dev
+    paths:
+      - '*.sh'
+      - '.github/workflows/NetBSD.yml'
+
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+
+
+jobs:
+  NetBSD:
+    strategy:
+      matrix:
+        include:
+         - TEST_ACME_Server: "LetsEncrypt.org_test"
+           CA_ECDSA: ""
+           CA: ""
+           CA_EMAIL: ""
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+         #- TEST_ACME_Server: "ZeroSSL.com"
+         #  CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
+         #  CA: "ZeroSSL RSA Domain Secure Site CA"
+         #  CA_EMAIL: "[email protected]"
+         #  TEST_PREFERRED_CHAIN: ""
+    runs-on: macos-12
+    env:
+      TEST_LOCAL: 1
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}
+      CA: ${{ matrix.CA }}
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
+    steps:
+    - uses: actions/checkout@v2
+    - uses: vmactions/[email protected]
+      id: tunnel
+      with:
+        protocol: http
+        port: 8080
+    - name: Set envs
+      run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/netbsd-vm@v0
+      with:
+        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
+        nat: |
+          "8080": "80"
+        prepare: |
+          export PKG_PATH="http://cdn.NetBSD.org/pub/pkgsrc/packages/NetBSD/$(uname -p)/$(uname -r|cut -f '1 2' -d.)/All/"
+          pkg_add curl socat
+        usesh: true
+        copyback: false
+        run: |
+          cd ../acmetest \
+          && ./letest.sh
+
+

+ 76 - 0
.github/workflows/OpenBSD.yml

@@ -0,0 +1,76 @@
+name: OpenBSD
+on:
+  push:
+    branches:
+      - '*'
+    paths:
+      - '*.sh'
+      - '.github/workflows/OpenBSD.yml'
+
+  pull_request:
+    branches:
+      - dev
+    paths:
+      - '*.sh'
+      - '.github/workflows/OpenBSD.yml'
+
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+
+jobs:
+  OpenBSD:
+    strategy:
+      matrix:
+        include:
+         - TEST_ACME_Server: "LetsEncrypt.org_test"
+           CA_ECDSA: ""
+           CA: ""
+           CA_EMAIL: ""
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+         - TEST_ACME_Server: "LetsEncrypt.org_test"
+           CA_ECDSA: ""
+           CA: ""
+           CA_EMAIL: ""
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+           ACME_USE_WGET: 1
+         #- TEST_ACME_Server: "ZeroSSL.com"
+         #  CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
+         #  CA: "ZeroSSL RSA Domain Secure Site CA"
+         #  CA_EMAIL: "[email protected]"
+         #  TEST_PREFERRED_CHAIN: ""
+    runs-on: macos-12
+    env:
+      TEST_LOCAL: 1
+      TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
+      CA_ECDSA: ${{ matrix.CA_ECDSA }}
+      CA: ${{ matrix.CA }}
+      CA_EMAIL: ${{ matrix.CA_EMAIL }}
+      TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
+      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
+    steps:
+    - uses: actions/checkout@v2
+    - uses: vmactions/[email protected]
+      id: tunnel
+      with:
+        protocol: http
+        port: 8080
+    - name: Set envs
+      run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
+    - name: Clone acmetest
+      run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
+    - uses: vmactions/openbsd-vm@v0
+      with:
+        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
+        nat: |
+          "8080": "80"
+        prepare: pkg_add socat curl wget
+        usesh: true
+        copyback: false
+        run: |
+          cd ../acmetest \
+          && ./letest.sh
+
+

+ 13 - 3
.github/workflows/PebbleStrict.yml

@@ -13,6 +13,13 @@ on:
       - '*.sh'
       - '.github/workflows/PebbleStrict.yml'
 
+
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
+
 jobs:
   PebbleStrict:
     runs-on: ubuntu-latest
@@ -41,8 +48,8 @@ jobs:
   PebbleStrict_IPCert:
     runs-on: ubuntu-latest
     env:
-      TestingDomain: 10.30.50.1
-      ACME_DIRECTORY: https://localhost:14000/dir
+      TestingDomain: 1.23.45.67
+      TEST_ACME_Server: https://localhost:14000/dir
       HTTPS_INSECURE: 1
       Le_HTTPPort: 5002
       Le_TLSPort: 5001
@@ -55,7 +62,10 @@ jobs:
     - name: Install tools
       run: sudo apt-get install -y socat
     - name: Run Pebble
-      run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker-compose up -d
+      run: |
+        docker run --rm -itd --name=pebble \
+        -e PEBBLE_VA_ALWAYS_VALID=1 \
+        -p 14000:14000 -p 15000:15000   letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict
     - name: Clone acmetest
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
     - name: Run acmetest

+ 17 - 4
.github/workflows/Solaris.yml

@@ -15,6 +15,11 @@ on:
       - '.github/workflows/Solaris.yml'
 
 
+
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
 jobs:
   Solaris:
     strategy:
@@ -25,12 +30,18 @@ jobs:
            CA: ""
            CA_EMAIL: ""
            TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+         - TEST_ACME_Server: "LetsEncrypt.org_test"
+           CA_ECDSA: ""
+           CA: ""
+           CA_EMAIL: ""
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+           ACME_USE_WGET: 1
          #- TEST_ACME_Server: "ZeroSSL.com"
          #  CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
          #  CA: "ZeroSSL RSA Domain Secure Site CA"
          #  CA_EMAIL: "[email protected]"
          #  TEST_PREFERRED_CHAIN: ""
-    runs-on: macos-10.15
+    runs-on: macos-12
     env:
       TEST_LOCAL: 1
       TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }}
@@ -38,6 +49,7 @@ jobs:
       CA: ${{ matrix.CA }}
       CA_EMAIL: ${{ matrix.CA_EMAIL }}
       TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }}
+      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
     steps:
     - uses: actions/checkout@v2
     - uses: vmactions/[email protected]
@@ -49,12 +61,13 @@ jobs:
       run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV
     - name: Clone acmetest
       run: cd .. && git clone https://github.com/acmesh-official/acmetest.git  && cp -r acme.sh acmetest/
-    - uses: vmactions/solaris-vm@v0.0.5
+    - uses: vmactions/solaris-vm@v0
       with:
-        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN'
+        envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET'
+        copyback: "false"
         nat: |
           "8080": "80"
-        prepare: pkgutil -y -i socat curl
+        prepare: pkgutil -y -i socat curl wget
         run: |
           cd ../acmetest \
           && ./letest.sh

+ 13 - 1
.github/workflows/Ubuntu.yml

@@ -14,6 +14,11 @@ on:
       - '*.sh'
       - '.github/workflows/Ubuntu.yml'
 
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 
 jobs:
   Ubuntu:
@@ -25,6 +30,12 @@ jobs:
            CA: ""
            CA_EMAIL: ""
            TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+         - TEST_ACME_Server: "LetsEncrypt.org_test"
+           CA_ECDSA: ""
+           CA: ""
+           CA_EMAIL: ""
+           TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1
+           ACME_USE_WGET: 1
          - TEST_ACME_Server: "ZeroSSL.com"
            CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA"
            CA: "ZeroSSL RSA Domain Secure Site CA"
@@ -57,10 +68,11 @@ jobs:
       NO_REVOKE: ${{ matrix.NO_REVOKE }}
       TEST_IPCERT: ${{ matrix.TEST_IPCERT }}
       TestingDomain: ${{ matrix.TestingDomain }}
+      ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }}
     steps:
     - uses: actions/checkout@v2
     - name: Install tools
-      run: sudo apt-get install -y socat
+      run: sudo apt-get install -y socat wget
     - name: Start StepCA
       if: ${{ matrix.TEST_ACME_Server=='https://localhost:9000/acme/acme/directory' }}
       run: |

+ 5 - 0
.github/workflows/Windows.yml

@@ -15,6 +15,11 @@ on:
       - '.github/workflows/Windows.yml'
 
 
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
   Windows:
     strategy:

+ 5 - 1
.github/workflows/dockerhub.yml

@@ -11,7 +11,11 @@ on:
       - "Dockerfile"
       - '.github/workflows/dockerhub.yml'
 
-    
+concurrency:
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
   CheckToken:
     runs-on: ubuntu-latest

+ 19 - 0
.github/workflows/issue.yml

@@ -0,0 +1,19 @@
+name: "Update issues"
+on:
+  issues:
+    types: [opened]
+
+jobs:
+  comment:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/github-script@v6
+        with:
+          script: |
+            github.rest.issues.createComment({
+              issue_number: context.issue.number,
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              body: "Please upgrade to the latest code and try again first. Maybe it's already fixed.              ```acme.sh --upgrade```              If it's still not working, please provide the log with `--debug 2`, otherwise, nobody can help you."
+              
+            })

+ 30 - 0
.github/workflows/pr_dns.yml

@@ -0,0 +1,30 @@
+name: Check dns api
+
+on:
+  pull_request_target:
+    types:
+      - opened
+    branches:
+      - 'dev'
+    paths:
+      - 'dnsapi/*.sh'
+
+
+jobs:
+  welcome:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/github-script@v6
+        with:
+          script: |
+            await github.rest.issues.createComment({
+              issue_number: context.issue.number,
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              body: `**Welcome**
+                Please make sure you're read our [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide) and [DNS-API-Test](../wiki/DNS-API-Test).
+                Then reply on this message, otherwise, your code will not be reviewed or merged.
+                We look forward to reviewing your Pull request shortly ✨
+                `
+            })
+

+ 30 - 0
.github/workflows/pr_notify.yml

@@ -0,0 +1,30 @@
+name: Check dns api
+
+on:
+  pull_request_target:
+    types:
+      - opened
+    branches:
+      - 'dev'
+    paths:
+      - 'notify/*.sh'
+
+
+jobs:
+  welcome:
+    runs-on: ubuntu-latest
+    steps:
+      - uses: actions/github-script@v6
+        with:
+          script: |
+            await github.rest.issues.createComment({
+              issue_number: context.issue.number,
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              body: `**Welcome**
+                Please make sure you're read our [Code-of-conduct](../wiki/Code-of-conduct) and  add the usage here: [notify](../wiki/notify).
+                Then reply on this message, otherwise, your code will not be reviewed or merged.
+                We look forward to reviewing your Pull request shortly ✨
+                `
+            })
+

+ 5 - 0
.github/workflows/shellcheck.yml

@@ -13,6 +13,11 @@ on:
       - '**.sh'
       - '.github/workflows/shellcheck.yml'
 
+concurrency: 
+  group: ${{ github.workflow }}-${{ github.ref }}
+  cancel-in-progress: true
+
+
 jobs:
   ShellCheck:
     runs-on: ubuntu-latest

+ 27 - 15
README.md

@@ -1,10 +1,14 @@
 # An ACME Shell script: acme.sh 
 
 [![FreeBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/FreeBSD.yml)
+[![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)
+[![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)
 [![MacOS](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MacOS.yml)
 [![Ubuntu](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Ubuntu.yml)
 [![Windows](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Windows.yml)
 [![Solaris](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Solaris.yml)
+[![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)
+
 
 ![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg)
 ![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg)
@@ -68,21 +72,23 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa)
 |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)
+|7|[![OpenBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenBSD.yml)|OpenBSD
+|8|[![NetBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/NetBSD.yml)|NetBSD
+|9|[![DragonFlyBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/DragonFlyBSD.yml)|DragonFlyBSD
+|10|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)| Debian
+|11|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|CentOS
+|12|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|openSUSE
+|13|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Alpine Linux (with curl)
+|14|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Archlinux
+|15|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|fedora
+|16|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Kali Linux
+|17|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux
+|18|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Mageia
+|19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Gentoo Linux
+|10|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|ClearLinux
+|11|-----| Cloud Linux  https://github.com/acmesh-official/acme.sh/issues/111
+|22|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT)
+|23|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management)
 
 
 Check our [testing project](https://github.com/acmesh-official/acmetest):
@@ -503,6 +509,12 @@ Support this project with your organization. Your logo will show up here with a
 <a href="https://opencollective.com/acmesh/organization/8/website"><img src="https://opencollective.com/acmesh/organization/8/avatar.svg"></a>
 <a href="https://opencollective.com/acmesh/organization/9/website"><img src="https://opencollective.com/acmesh/organization/9/avatar.svg"></a>
 
+
+#### Sponsors
+
+[![quantumca-acmesh-logo](https://user-images.githubusercontent.com/8305679/183255712-634ee1db-bb61-4c03-bca0-bacce99e078c.svg)](https://www.quantumca.com.cn/?__utm_source=acmesh-donation)
+
+
 # 19. License & Others
 
 License is GPLv3

+ 46 - 25
acme.sh

@@ -91,6 +91,7 @@ END_CERT="-----END CERTIFICATE-----"
 
 CONTENT_TYPE_JSON="application/jose+json"
 RENEW_SKIP=2
+CODE_DNS_MANUAL=3
 
 B64CONF_START="__ACME_BASE64__START_"
 B64CONF_END="__ACME_BASE64__END_"
@@ -436,21 +437,13 @@ _secure_debug3() {
 }
 
 _upper_case() {
-  if _is_solaris; then
-    tr '[:lower:]' '[:upper:]'
-  else
-    # shellcheck disable=SC2018,SC2019
-    tr 'a-z' 'A-Z'
-  fi
+  # shellcheck disable=SC2018,SC2019
+  tr '[a-z]' '[A-Z]'
 }
 
 _lower_case() {
-  if _is_solaris; then
-    tr '[:upper:]' '[:lower:]'
-  else
-    # shellcheck disable=SC2018,SC2019
-    tr 'A-Z' 'a-z'
-  fi
+  # shellcheck disable=SC2018,SC2019
+  tr '[A-Z]' '[a-z]'
 }
 
 _startswith() {
@@ -1193,7 +1186,7 @@ _createkey() {
 _is_idn() {
   _is_idn_d="$1"
   _debug2 _is_idn_d "$_is_idn_d"
-  _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '*.,-_')
+  _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '*.,-_')
   _debug2 _idn_temp "$_idn_temp"
   [ "$_idn_temp" ]
 }
@@ -4202,7 +4195,7 @@ _match_issuer() {
 _isIPv4() {
   for seg in $(echo "$1" | tr '.' ' '); do
     _debug2 seg "$seg"
-    if [ "$(echo "$seg" | tr -d [0-9])" ]; then
+    if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then
       #not all number
       return 1
     fi
@@ -4762,7 +4755,9 @@ $_authorizations_map"
       _err "Please add the TXT records to the domains, and re-run with --renew."
       _on_issue_err "$_post_hook"
       _clearup
-      return 1
+      # If asked to be in manual DNS mode, flag this exit with a separate
+      # error so it can be distinguished from other failures.
+      return $CODE_DNS_MANUAL
     fi
 
   fi
@@ -5205,11 +5200,25 @@ $_authorizations_map"
       _info "The domain is set to be valid to: $_valid_to"
       _info "It can not be renewed automatically"
       _info "See: $_VALIDITY_WIKI"
+    else
+      _now=$(_time)
+      _debug2 "_now" "$_now"
+      _lifetime=$(_math $Le_NextRenewTime - $_now)
+      _debug2 "_lifetime" "$_lifetime"
+      if [ $_lifetime -gt 86400 ]; then
+        #if lifetime is logner than one day, it will renew one day before
+        Le_NextRenewTime=$(_math $Le_NextRenewTime - 86400)
+        Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
+      else
+        #if lifetime is less than 24 hours, it will renew one hour before
+        Le_NextRenewTime=$(_math $Le_NextRenewTime - 3600)
+        Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
+      fi
     fi
   else
     Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60)
-    Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
     Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400)
+    Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime")
   fi
   _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr"
   _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime"
@@ -5752,7 +5761,9 @@ _installcert() {
     if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then
       cp "$_real_cert" "$_backup_path/cert.bak"
     fi
-    cat "$CERT_PATH" >"$_real_cert" || return 1
+    if [ "$CERT_PATH" != "$_real_cert" ]; then
+      cat "$CERT_PATH" >"$_real_cert" || return 1
+    fi
   fi
 
   if [ "$_real_ca" ]; then
@@ -5764,7 +5775,9 @@ _installcert() {
       if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then
         cp "$_real_ca" "$_backup_path/ca.bak"
       fi
-      cat "$CA_CERT_PATH" >"$_real_ca" || return 1
+      if [ "$CA_CERT_PATH" != "$_real_ca" ]; then
+        cat "$CA_CERT_PATH" >"$_real_ca" || return 1
+      fi
     fi
   fi
 
@@ -5773,12 +5786,14 @@ _installcert() {
     if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then
       cp "$_real_key" "$_backup_path/key.bak"
     fi
-    if [ -f "$_real_key" ]; then
-      cat "$CERT_KEY_PATH" >"$_real_key" || return 1
-    else
-      touch "$_real_key" || return 1
-      chmod 600 "$_real_key"
-      cat "$CERT_KEY_PATH" >"$_real_key" || return 1
+    if [ "$CERT_KEY_PATH" != "$_real_key" ]; then
+      if [ -f "$_real_key" ]; then
+        cat "$CERT_KEY_PATH" >"$_real_key" || return 1
+      else
+        touch "$_real_key" || return 1
+        chmod 600 "$_real_key"
+        cat "$CERT_KEY_PATH" >"$_real_key" || return 1
+      fi
     fi
   fi
 
@@ -5787,7 +5802,9 @@ _installcert() {
     if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then
       cp "$_real_fullchain" "$_backup_path/fullchain.bak"
     fi
-    cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
+    if [ "$_real_fullchain" != "$CERT_FULLCHAIN_PATH" ]; then
+      cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1
+    fi
   fi
 
   if [ "$_reload_cmd" ]; then
@@ -6035,6 +6052,8 @@ revoke() {
       if [ -z "$response" ]; then
         _info "Revoke success."
         rm -f "$CERT_PATH"
+        cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked"
+        cat "$CSR_PATH" >"$CSR_PATH.revoked"
         return 0
       else
         _err "Revoke error by domain key."
@@ -6051,6 +6070,8 @@ revoke() {
     if [ -z "$response" ]; then
       _info "Revoke success."
       rm -f "$CERT_PATH"
+      cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked"
+      cat "$CSR_PATH" >"$CSR_PATH.revoked"
       return 0
     else
       _err "Revoke error."

+ 3 - 13
deploy/mailcow.sh

@@ -44,30 +44,20 @@ mailcow_deploy() {
     return 1
   fi
 
-  # ECC or RSA
-  length=$(_readdomainconf Le_Keylength)
-  if _isEccKey "$length"; then
-    _info "ECC key type detected"
-    _cert_name_prefix="ecdsa-"
-  else
-    _info "RSA key type detected"
-    _cert_name_prefix=""
-  fi
-
   _info "Copying key and cert"
-  _real_key="$_ssl_path/${_cert_name_prefix}key.pem"
+  _real_key="$_ssl_path/key.pem"
   if ! cat "$_ckey" >"$_real_key"; then
     _err "Error: write key file to: $_real_key"
     return 1
   fi
 
-  _real_fullchain="$_ssl_path/${_cert_name_prefix}cert.pem"
+  _real_fullchain="$_ssl_path/cert.pem"
   if ! cat "$_cfullchain" >"$_real_fullchain"; then
     _err "Error: write cert file to: $_real_fullchain"
     return 1
   fi
 
-  DEFAULT_MAILCOW_RELOAD="docker restart \$(docker ps --quiet --filter name=nginx-mailcow --filter name=dovecot-mailcow)"
+  DEFAULT_MAILCOW_RELOAD="docker restart \$(docker ps --quiet --filter name=nginx-mailcow --filter name=dovecot-mailcow --filter name=postfix-mailcow)"
   _reload="${DEPLOY_MAILCOW_RELOAD:-$DEFAULT_MAILCOW_RELOAD}"
 
   _info "Run reload: $_reload"

+ 132 - 0
deploy/proxmoxve.sh

@@ -0,0 +1,132 @@
+#!/usr/bin/env sh
+
+# Deploy certificates to a proxmox virtual environment node using the API.
+#
+# Environment variables that can be set are:
+# `DEPLOY_PROXMOXVE_SERVER`: The hostname of the proxmox ve node. Defaults to
+#                            _cdomain.
+# `DEPLOY_PROXMOXVE_SERVER_PORT`: The port number the management interface is on.
+#                                 Defaults to 8006.
+# `DEPLOY_PROXMOXVE_NODE_NAME`: The name of the node we'll be connecting to.
+#                               Defaults to the host portion of the server
+#                               domain name.
+# `DEPLOY_PROXMOXVE_USER`: The user we'll connect as. Defaults to root.
+# `DEPLOY_PROXMOXVE_USER_REALM`: The authentication realm the user authenticates
+#                                with. Defaults to pam.
+# `DEPLOY_PROXMOXVE_API_TOKEN_NAME`: The name of the API token created for the
+#                                    user account. Defaults to acme.
+# `DEPLOY_PROXMOXVE_API_TOKEN_KEY`: The API token. Required.
+
+proxmoxve_deploy() {
+  _cdomain="$1"
+  _ckey="$2"
+  _ccert="$3"
+  _cca="$4"
+  _cfullchain="$5"
+
+  _debug _cdomain "$_cdomain"
+  _debug2 _ckey "$_ckey"
+  _debug _ccert "$_ccert"
+  _debug _cca "$_cca"
+  _debug _cfullchain "$_cfullchain"
+
+  # "Sane" defaults.
+  _getdeployconf DEPLOY_PROXMOXVE_SERVER
+  if [ -z "$DEPLOY_PROXMOXVE_SERVER" ]; then
+    _target_hostname="$_cdomain"
+  else
+    _target_hostname="$DEPLOY_PROXMOXVE_SERVER"
+    _savedeployconf DEPLOY_PROXMOXVE_SERVER "$DEPLOY_PROXMOXVE_SERVER"
+  fi
+  _debug2 DEPLOY_PROXMOXVE_SERVER "$_target_hostname"
+
+  _getdeployconf DEPLOY_PROXMOXVE_SERVER_PORT
+  if [ -z "$DEPLOY_PROXMOXVE_SERVER_PORT" ]; then
+    _target_port="8006"
+  else
+    _target_port="$DEPLOY_PROXMOXVE_SERVER_PORT"
+    _savedeployconf DEPLOY_PROXMOXVE_SERVER_PORT "$DEPLOY_PROXMOXVE_SERVER_PORT"
+  fi
+  _debug2 DEPLOY_PROXMOXVE_SERVER_PORT "$_target_port"
+
+  _getdeployconf DEPLOY_PROXMOXVE_NODE_NAME
+  if [ -z "$DEPLOY_PROXMOXVE_NODE_NAME" ]; then
+    _node_name=$(echo "$_target_hostname" | cut -d. -f1)
+  else
+    _node_name="$DEPLOY_PROXMOXVE_NODE_NAME"
+    _savedeployconf DEPLOY_PROXMOXVE_NODE_NAME "$DEPLOY_PROXMOXVE_NODE_NAME"
+  fi
+  _debug2 DEPLOY_PROXMOXVE_NODE_NAME "$_node_name"
+
+  # Complete URL.
+  _target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/${_node_name}/certificates/custom"
+  _debug TARGET_URL "$_target_url"
+
+  # More "sane" defaults.
+  _getdeployconf DEPLOY_PROXMOXVE_USER
+  if [ -z "$DEPLOY_PROXMOXVE_USER" ]; then
+    _proxmoxve_user="root"
+  else
+    _proxmoxve_user="$DEPLOY_PROXMOXVE_USER"
+    _savedeployconf DEPLOY_PROXMOXVE_USER "$DEPLOY_PROXMOXVE_USER"
+  fi
+  _debug2 DEPLOY_PROXMOXVE_USER "$_proxmoxve_user"
+
+  _getdeployconf DEPLOY_PROXMOXVE_USER_REALM
+  if [ -z "$DEPLOY_PROXMOXVE_USER_REALM" ]; then
+    _proxmoxve_user_realm="pam"
+  else
+    _proxmoxve_user_realm="$DEPLOY_PROXMOXVE_USER_REALM"
+    _savedeployconf DEPLOY_PROXMOXVE_USER_REALM "$DEPLOY_PROXMOXVE_USER_REALM"
+  fi
+  _debug2 DEPLOY_PROXMOXVE_USER_REALM "$_proxmoxve_user_realm"
+
+  _getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME
+  if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_NAME" ]; then
+    _proxmoxve_api_token_name="acme"
+  else
+    _proxmoxve_api_token_name="$DEPLOY_PROXMOXVE_API_TOKEN_NAME"
+    _savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_NAME "$DEPLOY_PROXMOXVE_API_TOKEN_NAME"
+  fi
+  _debug2 DEPLOY_PROXMOXVE_API_TOKEN_NAME "$_proxmoxve_api_token_name"
+
+  # This is required.
+  _getdeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY
+  if [ -z "$DEPLOY_PROXMOXVE_API_TOKEN_KEY" ]; then
+    _err "API key not provided."
+    return 1
+  else
+    _proxmoxve_api_token_key="$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
+    _savedeployconf DEPLOY_PROXMOXVE_API_TOKEN_KEY "$DEPLOY_PROXMOXVE_API_TOKEN_KEY"
+  fi
+  _debug2 DEPLOY_PROXMOXVE_API_TOKEN_KEY _proxmoxve_api_token_key
+
+  # PVE API Token header value. Used in "Authorization: PVEAPIToken".
+  _proxmoxve_header_api_token="${_proxmoxve_user}@${_proxmoxve_user_realm}!${_proxmoxve_api_token_name}=${_proxmoxve_api_token_key}"
+  _debug2 "Auth Header" _proxmoxve_header_api_token
+
+  # Ugly. I hate putting heredocs inside functions because heredocs don't
+  # account for whitespace correctly but it _does_ work and is several times
+  # cleaner than anything else I had here.
+  #
+  # This dumps the json payload to a variable that should be passable to the
+  # _psot function.
+  _json_payload=$(
+    cat <<HEREDOC
+{
+  "certificates": "$(tr '\n' ':' <"$_cfullchain" | sed 's/:/\\n/g')",
+  "key": "$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')",
+  "node":"$_node_name",
+  "restart":"1",
+  "force":"1"
+}
+HEREDOC
+  )
+  _debug2 Payload "$_json_payload"
+
+  # Push certificates to server.
+  export _HTTPS_INSECURE=1
+  export _H1="Authorization: PVEAPIToken=${_proxmoxve_header_api_token}"
+  _post "$_json_payload" "$_target_url" "" POST "application/json"
+
+}

+ 2 - 2
deploy/qiniu.sh

@@ -53,7 +53,7 @@ qiniu_deploy() {
   sslcert_access_token="$(_make_access_token "$sslcert_path")"
   _debug sslcert_access_token "$sslcert_access_token"
   export _H1="Authorization: QBox $sslcert_access_token"
-  sslcert_response=$(_post "$sslcerl_body" "$QINIU_API_BASE$sslcert_path" 0 "POST" "application/json" | _dbase64 "multiline")
+  sslcert_response=$(_post "$sslcerl_body" "$QINIU_API_BASE$sslcert_path" 0 "POST" "application/json" | _dbase64)
 
   if ! _contains "$sslcert_response" "certID"; then
     _err "Error in creating certificate:"
@@ -75,7 +75,7 @@ qiniu_deploy() {
     update_access_token="$(_make_access_token "$update_path")"
     _debug update_access_token "$update_access_token"
     export _H1="Authorization: QBox $update_access_token"
-    update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64 "multiline")
+    update_response=$(_post "$update_body" "$QINIU_API_BASE$update_path" 0 "PUT" "application/json" | _dbase64)
 
     if _contains "$update_response" "error"; then
       _err "Error in updating domain $domain httpsconf:"

+ 24 - 26
dnsapi/dns_aws.sh

@@ -155,31 +155,20 @@ _get_root() {
   i=1
   p=1
 
-  if aws_rest GET "2013-04-01/hostedzone"; then
-    while true; do
-      h=$(printf "%s" "$domain" | cut -d . -f $i-100)
-      _debug2 "Checking domain: $h"
-      if [ -z "$h" ]; then
-        if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then
-          _debug "IsTruncated"
-          _nextMarker="$(echo "$response" | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1)"
-          _debug "NextMarker" "$_nextMarker"
-          if aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"; then
-            _debug "Truncated request OK"
-            i=2
-            p=1
-            continue
-          else
-            _err "Truncated request error."
-          fi
-        fi
-        #not valid
-        _err "Invalid domain"
-        return 1
-      fi
+  # iterate over names (a.b.c.d -> b.c.d -> c.d -> d)
+  while true; do
+    h=$(printf "%s" "$domain" | cut -d . -f $i-100)
+    _debug "Checking domain: $h"
+    if [ -z "$h" ]; then
+      _error "invalid domain"
+      return 1
+    fi
 
+    # iterate over paginated result for list_hosted_zones
+    aws_rest GET "2013-04-01/hostedzone"
+    while true; do
       if _contains "$response" "<Name>$h.</Name>"; then
-        hostedzone="$(echo "$response" | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>")"
+        hostedzone="$(echo "$response" | tr -d '\n' | sed 's/<HostedZone>/#&/g' | tr '#' '\n' | _egrep_o "<HostedZone><Id>[^<]*<.Id><Name>$h.<.Name>.*<PrivateZone>false<.PrivateZone>.*<.HostedZone>")"
         _debug hostedzone "$hostedzone"
         if [ "$hostedzone" ]; then
           _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "<Id>.*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>")
@@ -192,10 +181,19 @@ _get_root() {
           return 1
         fi
       fi
-      p=$i
-      i=$(_math "$i" + 1)
+      if _contains "$response" "<IsTruncated>true</IsTruncated>" && _contains "$response" "<NextMarker>"; then
+        _debug "IsTruncated"
+        _nextMarker="$(echo "$response" | _egrep_o "<NextMarker>.*</NextMarker>" | cut -d '>' -f 2 | cut -d '<' -f 1)"
+        _debug "NextMarker" "$_nextMarker"
+      else
+        break
+      fi
+      _debug "Checking domain: $h - Next Page "
+      aws_rest GET "2013-04-01/hostedzone" "marker=$_nextMarker"
     done
-  fi
+    p=$i
+    i=$(_math "$i" + 1)
+  done
   return 1
 }
 

+ 10 - 1
dnsapi/dns_cf.sh

@@ -32,7 +32,8 @@ dns_cf_add() {
     else
       _saveaccountconf_mutable CF_Token "$CF_Token"
       _saveaccountconf_mutable CF_Account_ID "$CF_Account_ID"
-      _saveaccountconf_mutable CF_Zone_ID "$CF_Zone_ID"
+      _clearaccountconf_mutable CF_Zone_ID
+      _clearaccountconf CF_Zone_ID
     fi
   else
     if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then
@@ -51,6 +52,14 @@ dns_cf_add() {
     #save the api key and email to the account conf file.
     _saveaccountconf_mutable CF_Key "$CF_Key"
     _saveaccountconf_mutable CF_Email "$CF_Email"
+
+    _clearaccountconf_mutable CF_Token
+    _clearaccountconf_mutable CF_Account_ID
+    _clearaccountconf_mutable CF_Zone_ID
+    _clearaccountconf CF_Token
+    _clearaccountconf CF_Account_ID
+    _clearaccountconf CF_Zone_ID
+
   fi
 
   _debug "First detect the root zone"

+ 1 - 1
dnsapi/dns_cyon.sh

@@ -44,7 +44,7 @@ dns_cyon_rm() {
 _cyon_load_credentials() {
   # Convert loaded password to/from base64 as needed.
   if [ "${CY_Password_B64}" ]; then
-    CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64 "multiline")"
+    CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64)"
   elif [ "${CY_Password}" ]; then
     CY_Password_B64="$(printf "%s" "${CY_Password}" | _base64)"
   fi

+ 234 - 0
dnsapi/dns_dnsservices.sh

@@ -0,0 +1,234 @@
+#!/usr/bin/env sh
+
+#This file name is "dns_dnsservices.sh"
+#Script for Danish DNS registra and DNS hosting provider https://dns.services
+
+#Author: Bjarke Bruun <[email protected]>
+#Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/4152
+
+# Global variable to connect to the DNS.Services API
+DNSServices_API=https://dns.services/api
+
+########  Public functions #####################
+
+#Usage: dns_dnsservices_add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_dnsservices_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Using dns.services to create ACME DNS challenge"
+  _debug2 add_fulldomain "$fulldomain"
+  _debug2 add_txtvalue "$txtvalue"
+
+  # Read username/password from environment or .acme.sh/accounts.conf
+  DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}"
+  DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}"
+  if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
+    DnsServices_Username=""
+    DnsServices_Password=""
+    _err "You didn't specify dns.services api username and password yet."
+    _err "Set environment variables DnsServices_Username and DnsServices_Password"
+    return 1
+  fi
+
+  # Setup GET/POST/DELETE headers
+  _setup_headers
+
+  #save the credentials to the account conf file.
+  _saveaccountconf_mutable DnsServices_Username "$DnsServices_Username"
+  _saveaccountconf_mutable DnsServices_Password "$DnsServices_Password"
+
+  if ! _contains "$DnsServices_Username" "@"; then
+    _err "It seems that the username variable DnsServices_Username has not been set/left blank"
+    _err "or is not a valid email. Please correct and try again."
+    return 1
+  fi
+
+  if ! _get_root "${fulldomain}"; then
+    _err "Invalid domain ${fulldomain}"
+    return 1
+  fi
+
+  if ! createRecord "$fulldomain" "${txtvalue}"; then
+    _err "Error creating TXT record in domain $fulldomain in $rootZoneName"
+    return 1
+  fi
+
+  _debug2 challenge-created "Created $fulldomain"
+  return 0
+}
+
+#Usage: fulldomain txtvalue
+#Description: Remove the txt record after validation.
+dns_dnsservices_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _info "Using dns.services to remove DNS record $fulldomain TXT $txtvalue"
+  _debug rm_fulldomain "$fulldomain"
+  _debug rm_txtvalue "$txtvalue"
+
+  # Read username/password from environment or .acme.sh/accounts.conf
+  DnsServices_Username="${DnsServices_Username:-$(_readaccountconf_mutable DnsServices_Username)}"
+  DnsServices_Password="${DnsServices_Password:-$(_readaccountconf_mutable DnsServices_Password)}"
+  if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
+    DnsServices_Username=""
+    DnsServices_Password=""
+    _err "You didn't specify dns.services api username and password yet."
+    _err "Set environment variables DnsServices_Username and DnsServices_Password"
+    return 1
+  fi
+
+  # Setup GET/POST/DELETE headers
+  _setup_headers
+
+  if ! _get_root "${fulldomain}"; then
+    _err "Invalid domain ${fulldomain}"
+    return 1
+  fi
+
+  _debug2 rm_rootDomainInfo "found root domain $rootZoneName for $fulldomain"
+
+  if ! deleteRecord "${fulldomain}" "${txtvalue}"; then
+    _err "Error removing record: $fulldomain TXT ${txtvalue}"
+    return 1
+  fi
+
+  return 0
+}
+
+####################  Private functions below ##################################
+
+_setup_headers() {
+  # Set up API Headers for _get() and _post()
+  # The <function>_add or <function>_rm must have been called before to work
+
+  if [ -z "$DnsServices_Username" ] || [ -z "$DnsServices_Password" ]; then
+    _err "Could not setup BASIC authentication headers, they are missing"
+    return 1
+  fi
+
+  DnsServiceCredentials="$(printf "%s" "$DnsServices_Username:$DnsServices_Password" | _base64)"
+  export _H1="Authorization: Basic $DnsServiceCredentials"
+  export _H2="Content-Type: application/json"
+
+  # Just return if headers are set
+  return 0
+}
+
+_get_root() {
+  domain=$1
+  _debug2 _get_root "Get the root domain of ${domain} for DNS API"
+
+  # Setup _get() and _post() headers
+  #_setup_headers
+
+  result=$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/dns")
+  _debug2 _get_root "Got the following root domain(s) $result"
+  _debug2 _get_root "- JSON: $result"
+
+  if [ "$(echo "$result" | grep -c '"name"')" -gt "1" ]; then
+    checkMultiZones="true"
+    _debug2 _get_root "- multiple zones found"
+  else
+    checkMultiZones="false"
+
+  fi
+
+  # Find/isolate the root zone to work with in createRecord() and deleteRecord()
+  rootZone=""
+  if [ "$checkMultiZones" = "true" ]; then
+    rootZone=$(for zone in $(echo "$result" | tr -d '\n' ' '); do
+      if [ "$(echo "$domain" | grep "$zone")" != "" ]; then
+        _debug2 _get_root "- trying to figure out if $zone is in $domain"
+        echo "$zone"
+        break
+      fi
+    done)
+  else
+    rootZone=$(echo "$result" | _egrep_o '"name":"[^"]*' | cut -d'"' -f4)
+    _debug2 _get_root "- only found 1 domain in API: $rootZone"
+  fi
+
+  if [ -z "$rootZone" ]; then
+    _err "Could not find root domain for $domain - is it correctly typed?"
+    return 1
+  fi
+
+  # Setup variables used by other functions to communicate with DNS.Services API
+  #zoneInfo=$(echo "$result" | sed "s,\"zones,\n&,g" | grep zones | cut -d'[' -f2 | cut -d']' -f1 | tr '}' '\n' | grep "\"$rootZone\"")
+  zoneInfo=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"name":")([^"]*)"(.*)$,\2,g' | grep "\"$rootZone\"")
+  rootZoneName="$rootZone"
+  subDomainName="$(echo "$domain" | sed "s,\.$rootZone,,g")"
+  subDomainNameClean="$(echo "$domain" | sed "s,_acme-challenge.,,g")"
+  rootZoneDomainID=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"domain_id":")([^"]*)"(.*)$,\2,g')
+  rootZoneServiceID=$(echo "$result" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"service_id":")([^"]*)"(.*)$,\2,g')
+
+  _debug2 _zoneInfo "Zone info from API  : $zoneInfo"
+  _debug2 _get_root "Root zone name      : $rootZoneName"
+  _debug2 _get_root "Root zone domain ID : $rootZoneDomainID"
+  _debug2 _get_root "Root zone service ID: $rootZoneServiceID"
+  _debug2 _get_root "Sub domain          : $subDomainName"
+
+  _debug _get_root "Found valid root domain $rootZone for $subDomainNameClean"
+  return 0
+}
+
+createRecord() {
+  fulldomain=$1
+  txtvalue="$2"
+
+  # Get root domain information - needed for DNS.Services API communication
+  if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
+    _get_root "$fulldomain"
+  fi
+
+  _debug2 createRecord "CNAME TXT value is: $txtvalue"
+
+  # Prepare data to send to API
+  data="{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\", \"ttl\":\"10\"}"
+
+  _debug2 createRecord "data to API: $data"
+  result=$(_post "$data" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records" "" "POST")
+  _debug2 createRecord "result from API: $result"
+
+  if [ "$(echo "$result" | _egrep_o "\"success\":true")" = "" ]; then
+    _err "Failed to create TXT record $fulldomain with content $txtvalue in zone $rootZoneName"
+    _err "$result"
+    return 1
+  fi
+
+  _info "Record \"$fulldomain TXT $txtvalue\" has been created"
+  return 0
+}
+
+deleteRecord() {
+  fulldomain=$1
+  txtvalue=$2
+
+  _log deleteRecord "Deleting $fulldomain TXT $txtvalue record"
+
+  if [ -z "$rootZoneName" ] || [ -z "$rootZoneDomainID" ] || [ -z "$rootZoneServiceID" ]; then
+    _get_root "$fulldomain"
+  fi
+
+  result="$(_H1="$_H1" _H2="$_H2" _get "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID")"
+  recordInfo="$(echo "$result" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}")"
+  recordID="$(echo "$recordInfo" | sed -e 's/:{/:{\n/g' -e 's/},/\n},\n/g' | grep "${txtvalue}" | sed -E 's,.*(zones)(.*),\1\2,g' | sed -E 's,^(.*"id":")([^"]*)"(.*)$,\2,g')"
+
+  if [ -z "$recordID" ]; then
+    _info "Record $fulldomain TXT $txtvalue not found or already deleted"
+    return 0
+  else
+    _debug2 deleteRecord "Found recordID=$recordID"
+  fi
+
+  _debug2 deleteRecord "DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID"
+  _log "curl DELETE request $DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID"
+  result="$(_H1="$_H1" _H2="$_H2" _post "" "$DNSServices_API/service/$rootZoneServiceID/dns/$rootZoneDomainID/records/$recordID" "" "DELETE")"
+  _debug2 deleteRecord "API Delete result \"$result\""
+  _log "curl API Delete result \"$result\""
+
+  # Return OK regardless
+  return 0
+}

+ 4 - 0
dnsapi/dns_edgedns.sh

@@ -176,6 +176,7 @@ _EDGEDNS_credentials() {
   _debug "GettingEdge DNS credentials"
   _log "$(printf "ACME DNSAPI Edge DNS version %s" ${ACME_EDGEDNS_VERSION})"
   args_missing=0
+  AKAMAI_ACCESS_TOKEN="${AKAMAI_ACCESS_TOKEN:-$(_readaccountconf_mutable AKAMAI_ACCESS_TOKEN)}"
   if [ -z "$AKAMAI_ACCESS_TOKEN" ]; then
     AKAMAI_ACCESS_TOKEN=""
     AKAMAI_CLIENT_TOKEN=""
@@ -184,6 +185,7 @@ _EDGEDNS_credentials() {
     _err "AKAMAI_ACCESS_TOKEN is missing"
     args_missing=1
   fi
+  AKAMAI_CLIENT_TOKEN="${AKAMAI_CLIENT_TOKEN:-$(_readaccountconf_mutable AKAMAI_CLIENT_TOKEN)}"
   if [ -z "$AKAMAI_CLIENT_TOKEN" ]; then
     AKAMAI_ACCESS_TOKEN=""
     AKAMAI_CLIENT_TOKEN=""
@@ -192,6 +194,7 @@ _EDGEDNS_credentials() {
     _err "AKAMAI_CLIENT_TOKEN is missing"
     args_missing=1
   fi
+  AKAMAI_HOST="${AKAMAI_HOST:-$(_readaccountconf_mutable AKAMAI_HOST)}"
   if [ -z "$AKAMAI_HOST" ]; then
     AKAMAI_ACCESS_TOKEN=""
     AKAMAI_CLIENT_TOKEN=""
@@ -200,6 +203,7 @@ _EDGEDNS_credentials() {
     _err "AKAMAI_HOST is missing"
     args_missing=1
   fi
+  AKAMAI_CLIENT_SECRET="${AKAMAI_CLIENT_SECRET:-$(_readaccountconf_mutable AKAMAI_CLIENT_SECRET)}"
   if [ -z "$AKAMAI_CLIENT_SECRET" ]; then
     AKAMAI_ACCESS_TOKEN=""
     AKAMAI_CLIENT_TOKEN=""

+ 2 - 2
dnsapi/dns_gcloud.sh

@@ -98,7 +98,7 @@ _dns_gcloud_remove_rrs() {
     --ttl="$ttl" \
     --type=TXT \
     --zone="$managedZone" \
-    --transaction-file="$tr"; then
+    --transaction-file="$tr" --; then
     _debug tr "$(cat "$tr")"
     rm -r "$trd"
     _err "_dns_gcloud_remove_rrs: failed to remove RRs"
@@ -113,7 +113,7 @@ _dns_gcloud_add_rrs() {
     --ttl="$ttl" \
     --type=TXT \
     --zone="$managedZone" \
-    --transaction-file="$tr"; then
+    --transaction-file="$tr" --; then
     _debug tr "$(cat "$tr")"
     rm -r "$trd"
     _err "_dns_gcloud_add_rrs: failed to add RRs"

+ 40 - 16
dnsapi/dns_gd.sh

@@ -1,10 +1,12 @@
 #!/usr/bin/env sh
 
 #Godaddy domain api
+# Get API key and secret from https://developer.godaddy.com/
 #
-#GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+# GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje"
+# GD_Secret="asdfsdfsfsdfsdfdfsdf"
 #
-#GD_Secret="asdfsdfsfsdfsdfdfsdf"
+# Ex.: acme.sh --issue --staging --dns dns_gd -d "*.s.example.com" -d "s.example.com"
 
 GD_Api="https://api.godaddy.com/v1"
 
@@ -51,7 +53,8 @@ dns_gd_add() {
   _add_data="{\"data\":\"$txtvalue\"}"
   for t in $(echo "$response" | tr '{' "\n" | grep "\"name\":\"$_sub_domain\"" | tr ',' "\n" | grep '"data"' | cut -d : -f 2); do
     _debug2 t "$t"
-    if [ "$t" ]; then
+    # ignore empty (previously removed) records, to prevent useless _acme-challenge TXT entries
+    if [ "$t" ] && [ "$t" != '""' ]; then
       _add_data="$_add_data,{\"data\":$t}"
     fi
   done
@@ -59,13 +62,25 @@ dns_gd_add() {
 
   _info "Adding record"
   if _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
-    _info "Added, sleeping 10 seconds"
-    _sleep 10
-    #todo: check if the record takes effect
-    return 0
+    _debug "Checking updated records of '${fulldomain}'"
+
+    if ! _gd_rest GET "domains/$_domain/records/TXT/$_sub_domain"; then
+      _err "Validating TXT record for '${fulldomain}' with rest error [$?]." "$response"
+      return 1
+    fi
+
+    if ! _contains "$response" "$txtvalue"; then
+      _err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!"
+      return 1
+    fi
+  else
+    _err "Add txt record error, value '${txtvalue}' for '${fulldomain}' was not set."
+    return 1
   fi
-  _err "Add txt record error."
-  return 1
+
+  _sleep 10
+  _info "Added TXT record '${txtvalue}' for '${fulldomain}'."
+  return 0
 }
 
 #fulldomain
@@ -107,11 +122,20 @@ dns_gd_rm() {
     fi
   done
   if [ -z "$_add_data" ]; then
-    _add_data="{\"data\":\"\"}"
+    # delete empty record
+    _debug "Delete last record for '${fulldomain}'"
+    if ! _gd_rest DELETE "domains/$_domain/records/TXT/$_sub_domain"; then
+      _err "Cannot delete empty TXT record for '$fulldomain'"
+      return 1
+    fi
+  else
+    # remove specific TXT value, keeping other entries
+    _debug2 _add_data "$_add_data"
+    if ! _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"; then
+      _err "Cannot update TXT record for '$fulldomain'"
+      return 1
+    fi
   fi
-  _debug2 _add_data "$_add_data"
-
-  _gd_rest PUT "domains/$_domain/records/TXT/$_sub_domain" "[$_add_data]"
 }
 
 ####################  Private functions below ##################################
@@ -156,15 +180,15 @@ _gd_rest() {
   export _H1="Authorization: sso-key $GD_Key:$GD_Secret"
   export _H2="Content-Type: application/json"
 
-  if [ "$data" ]; then
-    _debug data "$data"
+  if [ "$data" ] || [ "$m" = "DELETE" ]; then
+    _debug "data ($m): " "$data"
     response="$(_post "$data" "$GD_Api/$ep" "" "$m")"
   else
     response="$(_get "$GD_Api/$ep")"
   fi
 
   if [ "$?" != "0" ]; then
-    _err "error $ep"
+    _err "error on rest call ($m): $ep"
     return 1
   fi
   _debug2 response "$response"

+ 13 - 11
dnsapi/dns_huaweicloud.sh

@@ -2,7 +2,7 @@
 
 # HUAWEICLOUD_Username
 # HUAWEICLOUD_Password
-# HUAWEICLOUD_ProjectID
+# HUAWEICLOUD_DomainName
 
 iam_api="https://iam.myhuaweicloud.com"
 dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
@@ -14,6 +14,8 @@ dns_api="https://dns.ap-southeast-1.myhuaweicloud.com" # Should work
 #
 # Ref: https://support.huaweicloud.com/intl/zh-cn/api-dns/zh-cn_topic_0132421999.html
 #
+# About "DomainName" parameters see: https://support.huaweicloud.com/api-iam/iam_01_0006.html
+#
 
 dns_huaweicloud_add() {
   fulldomain=$1
@@ -21,16 +23,16 @@ dns_huaweicloud_add() {
 
   HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
   HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
-  HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}"
+  HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
 
   # Check information
-  if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then
+  if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
     _err "Not enough information provided to dns_huaweicloud!"
     return 1
   fi
 
   unset token # Clear token
-  token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
+  token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
   if [ -z "${token}" ]; then # Check token
     _err "dns_api(dns_huaweicloud): Error getting token."
     return 1
@@ -56,7 +58,7 @@ dns_huaweicloud_add() {
   # Do saving work if all succeeded
   _saveaccountconf_mutable HUAWEICLOUD_Username "${HUAWEICLOUD_Username}"
   _saveaccountconf_mutable HUAWEICLOUD_Password "${HUAWEICLOUD_Password}"
-  _saveaccountconf_mutable HUAWEICLOUD_ProjectID "${HUAWEICLOUD_ProjectID}"
+  _saveaccountconf_mutable HUAWEICLOUD_DomainName "${HUAWEICLOUD_DomainName}"
   return 0
 }
 
@@ -72,16 +74,16 @@ dns_huaweicloud_rm() {
 
   HUAWEICLOUD_Username="${HUAWEICLOUD_Username:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
   HUAWEICLOUD_Password="${HUAWEICLOUD_Password:-$(_readaccountconf_mutable HUAWEICLOUD_Password)}"
-  HUAWEICLOUD_ProjectID="${HUAWEICLOUD_ProjectID:-$(_readaccountconf_mutable HUAWEICLOUD_ProjectID)}"
+  HUAWEICLOUD_DomainName="${HUAWEICLOUD_DomainName:-$(_readaccountconf_mutable HUAWEICLOUD_Username)}"
 
   # Check information
-  if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_ProjectID}" ]; then
+  if [ -z "${HUAWEICLOUD_Username}" ] || [ -z "${HUAWEICLOUD_Password}" ] || [ -z "${HUAWEICLOUD_DomainName}" ]; then
     _err "Not enough information provided to dns_huaweicloud!"
     return 1
   fi
 
   unset token # Clear token
-  token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_ProjectID}")"
+  token="$(_get_token "${HUAWEICLOUD_Username}" "${HUAWEICLOUD_Password}" "${HUAWEICLOUD_DomainName}")"
   if [ -z "${token}" ]; then # Check token
     _err "dns_api(dns_huaweicloud): Error getting token."
     return 1
@@ -253,7 +255,7 @@ _rm_record() {
 _get_token() {
   _username=$1
   _password=$2
-  _project=$3
+  _domain_name=$3
 
   _debug "Getting Token"
   body="{
@@ -267,14 +269,14 @@ _get_token() {
             \"name\": \"${_username}\",
             \"password\": \"${_password}\",
             \"domain\": {
-              \"name\": \"${_username}\"
+              \"name\": \"${_domain_name}\"
             }
           }
         }
       },
       \"scope\": {
         \"project\": {
-          \"id\": \"${_project}\"
+          \"name\": \"ap-southeast-1\"
         }
       }
     }

+ 19 - 11
dnsapi/dns_ionos.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-# Supports IONOS DNS API Beta v1.0.0
+# Supports IONOS DNS API v1.0.1
 #
 # Usage:
 #   Export IONOS_PREFIX and IONOS_SECRET before calling acme.sh:
@@ -26,7 +26,7 @@ dns_ionos_add() {
 
   _body="[{\"name\":\"$_sub_domain.$_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":$IONOS_TXT_TTL,\"prio\":$IONOS_TXT_PRIO,\"disabled\":false}]"
 
-  if _ionos_rest POST "$IONOS_ROUTE_ZONES/$_zone_id/records" "$_body" && [ -z "$response" ]; then
+  if _ionos_rest POST "$IONOS_ROUTE_ZONES/$_zone_id/records" "$_body" && [ "$_code" = "201" ]; then
     _info "TXT record has been created successfully."
     return 0
   fi
@@ -47,7 +47,7 @@ dns_ionos_rm() {
     return 1
   fi
 
-  if _ionos_rest DELETE "$IONOS_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ -z "$response" ]; then
+  if _ionos_rest DELETE "$IONOS_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ "$_code" = "200" ]; then
     _info "TXT record has been deleted successfully."
     return 0
   fi
@@ -85,7 +85,7 @@ _get_root() {
   p=1
 
   if _ionos_rest GET "$IONOS_ROUTE_ZONES"; then
-    response="$(echo "$response" | tr -d "\n")"
+    _response="$(echo "$_response" | tr -d "\n")"
 
     while true; do
       h=$(printf "%s" "$domain" | cut -d . -f $i-100)
@@ -93,7 +93,7 @@ _get_root() {
         return 1
       fi
 
-      _zone="$(echo "$response" | _egrep_o "\"name\":\"$h\".*\}")"
+      _zone="$(echo "$_response" | _egrep_o "\"name\":\"$h\".*\}")"
       if [ "$_zone" ]; then
         _zone_id=$(printf "%s\n" "$_zone" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
         if [ "$_zone_id" ]; then
@@ -120,9 +120,9 @@ _ionos_get_record() {
   txtrecord=$3
 
   if _ionos_rest GET "$IONOS_ROUTE_ZONES/$zone_id?recordName=$fulldomain&recordType=TXT"; then
-    response="$(echo "$response" | tr -d "\n")"
+    _response="$(echo "$_response" | tr -d "\n")"
 
-    _record="$(echo "$response" | _egrep_o "\"name\":\"$fulldomain\"[^\}]*\"type\":\"TXT\"[^\}]*\"content\":\"\\\\\"$txtrecord\\\\\"\".*\}")"
+    _record="$(echo "$_response" | _egrep_o "\"name\":\"$fulldomain\"[^\}]*\"type\":\"TXT\"[^\}]*\"content\":\"\\\\\"$txtrecord\\\\\"\".*\}")"
     if [ "$_record" ]; then
       _record_id=$(printf "%s\n" "$_record" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"')
 
@@ -142,22 +142,30 @@ _ionos_rest() {
 
   export _H1="X-API-Key: $IONOS_API_KEY"
 
+  # clear headers
+  : >"$HTTP_HEADER"
+
   if [ "$method" != "GET" ]; then
     export _H2="Accept: application/json"
     export _H3="Content-Type: application/json"
 
-    response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")"
+    _response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")"
   else
     export _H2="Accept: */*"
     export _H3=
-    response="$(_get "$IONOS_API$route")"
+
+    _response="$(_get "$IONOS_API$route")"
   fi
 
+  _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
+
   if [ "$?" != "0" ]; then
-    _err "Error $route: $response"
+    _err "Error $route: $_response"
     return 1
   fi
-  _debug2 "response" "$response"
+
+  _debug2 "_response" "$_response"
+  _debug2 "_code" "$_code"
 
   return 0
 }

+ 8 - 4
dnsapi/dns_ispconfig.sh

@@ -32,6 +32,10 @@ dns_ispconfig_rm() {
 ####################  Private functions below ##################################
 
 _ISPC_credentials() {
+  ISPC_User="${ISPC_User:-$(_readaccountconf_mutable ISPC_User)}"
+  ISPC_Password="${ISPC_Password:-$(_readaccountconf_mutable ISPC_Password)}"
+  ISPC_Api="${ISPC_Api:-$(_readaccountconf_mutable ISPC_Api)}"
+  ISPC_Api_Insecure="${ISPC_Api_Insecure:-$(_readaccountconf_mutable ISPC_Api_Insecure)}"
   if [ -z "${ISPC_User}" ] || [ -z "${ISPC_Password}" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then
     ISPC_User=""
     ISPC_Password=""
@@ -40,10 +44,10 @@ _ISPC_credentials() {
     _err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again."
     return 1
   else
-    _saveaccountconf ISPC_User "${ISPC_User}"
-    _saveaccountconf ISPC_Password "${ISPC_Password}"
-    _saveaccountconf ISPC_Api "${ISPC_Api}"
-    _saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}"
+    _saveaccountconf_mutable ISPC_User "${ISPC_User}"
+    _saveaccountconf_mutable ISPC_Password "${ISPC_Password}"
+    _saveaccountconf_mutable ISPC_Api "${ISPC_Api}"
+    _saveaccountconf_mutable ISPC_Api_Insecure "${ISPC_Api_Insecure}"
     # Set whether curl should use secure or insecure mode
     export HTTPS_INSECURE="${ISPC_Api_Insecure}"
   fi

+ 208 - 95
dnsapi/dns_kas.sh

@@ -5,51 +5,81 @@
 # Environment variables:
 #
 #  - $KAS_Login (Kasserver API login name)
-#  - $KAS_Authtype (Kasserver API auth type. Default: sha1)
+#  - $KAS_Authtype (Kasserver API auth type. Default: plain)
 #  - $KAS_Authdata (Kasserver API auth data.)
 #
-# Author: Martin Kammerlander, Phlegx Systems OG <[email protected]>
-# Updated by: Marc-Oliver Lange <[email protected]>
-# Credits: Inspired by dns_he.sh. Thanks a lot man!
-# Git repo: https://github.com/phlegx/acme.sh
-# TODO: Better Error handling
+# Last update: squared GmbH <[email protected]>
+# Credits:
+# - dns_he.sh. Thanks a lot man!
+# - Martin Kammerlander, Phlegx Systems OG <[email protected]>
+# - Marc-Oliver Lange <[email protected]>
+# - https://github.com/o1oo11oo/kasapi.sh
 ########################################################################
-KAS_Api="https://kasapi.kasserver.com/dokumentation/formular.php"
+KAS_Api_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasApi.wsdl")"
+KAS_Api="$(echo "$KAS_Api_GET" | tr -d ' ' | grep -i "<soap:addresslocation=" | sed "s/='/\n/g" | grep -i "http" | sed "s/'\/>//g")"
+_info "[KAS] -> API URL $KAS_Api"
+
+KAS_Auth_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasAuth.wsdl")"
+KAS_Auth="$(echo "$KAS_Auth_GET" | tr -d ' ' | grep -i "<soap:addresslocation=" | sed "s/='/\n/g" | grep -i "http" | sed "s/'\/>//g")"
+_info "[KAS] -> AUTH URL $KAS_Auth"
+
+KAS_default_ratelimit=5 # TODO - Every response delivers a ratelimit (seconds) where KASAPI is blocking a request.
+
 ########  Public functions  #####################
 dns_kas_add() {
   _fulldomain=$1
   _txtvalue=$2
-  _info "Using DNS-01 All-inkl/Kasserver hook"
-  _info "Adding $_fulldomain DNS TXT entry on All-inkl/Kasserver"
-  _info "Check and Save Props"
+
+  _info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook"
+  _info "[KAS] -> Check and Save Props"
   _check_and_save
-  _info "Checking Zone and Record_Name"
+
+  _info "[KAS] -> Adding $_fulldomain DNS TXT entry on all-inkl.com/Kasserver"
+  _info "[KAS] -> Retriving Credential Token"
+  _get_credential_token
+
+  _info "[KAS] -> Checking Zone and Record_Name"
   _get_zone_and_record_name "$_fulldomain"
-  _info "Getting Record ID"
+
+  _info "[KAS] -> Checking for existing Record entries"
   _get_record_id
 
-  _info "Creating TXT DNS record"
-  params="?kas_login=$KAS_Login"
-  params="$params&kas_auth_type=$KAS_Authtype"
-  params="$params&kas_auth_data=$KAS_Authdata"
-  params="$params&var1=record_name"
-  params="$params&wert1=$_record_name"
-  params="$params&var2=record_type"
-  params="$params&wert2=TXT"
-  params="$params&var3=record_data"
-  params="$params&wert3=$_txtvalue"
-  params="$params&var4=record_aux"
-  params="$params&wert4=0"
-  params="$params&kas_action=add_dns_settings"
-  params="$params&var5=zone_host"
-  params="$params&wert5=$_zone"
-  _debug2 "Wait for 10 seconds by default before calling KAS API."
-  _sleep 10
-  response="$(_get "$KAS_Api$params")"
-  _debug2 "response" "$response"
-
-  if ! _contains "$response" "TRUE"; then
-    _err "An unkown error occurred, please check manually."
+  # If there is a record_id, delete the entry
+  if [ -n "$_record_id" ]; then
+    _info "[KAS] -> Existing records found. Now deleting old entries"
+    for i in $_record_id; do
+      _delete_RecordByID "$i"
+    done
+  else
+    _info "[KAS] -> No record found."
+  fi
+
+  _info "[KAS] -> Creating TXT DNS record"
+  action="add_dns_settings"
+  kasReqParam="\"record_name\":\"$_record_name\""
+  kasReqParam="$kasReqParam,\"record_type\":\"TXT\""
+  kasReqParam="$kasReqParam,\"record_data\":\"$_txtvalue\""
+  kasReqParam="$kasReqParam,\"record_aux\":\"0\""
+  kasReqParam="$kasReqParam,\"zone_host\":\"$_zone\""
+  response="$(_callAPI "$action" "$kasReqParam")"
+  _debug2 "[KAS] -> Response" "$response"
+
+  if [ -z "$response" ]; then
+    _info "[KAS] -> Response was empty, please check manually."
+    return 1
+  elif _contains "$response" "<SOAP-ENV:Fault>"; then
+    faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+    case "${faultstring}" in
+    "record_already_exists")
+      _info "[KAS] -> The record already exists, which must not be a problem. Please check manually."
+      ;;
+    *)
+      _err "[KAS] -> An error =>$faultstring<= occurred, please check manually."
+      return 1
+      ;;
+    esac
+  elif ! _contains "$response" "<item><key xsi:type=\"xsd:string\">ReturnString</key><value xsi:type=\"xsd:string\">TRUE</value></item>"; then
+    _err "[KAS] -> An unknown error occurred, please check manually."
     return 1
   fi
   return 0
@@ -58,45 +88,62 @@ dns_kas_add() {
 dns_kas_rm() {
   _fulldomain=$1
   _txtvalue=$2
-  _info "Using DNS-01 All-inkl/Kasserver hook"
-  _info "Cleaning up after All-inkl/Kasserver hook"
-  _info "Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
 
-  _info "Check and Save Props"
+  _info "[KAS] -> Using DNS-01 All-inkl/Kasserver hook"
+  _info "[KAS] -> Check and Save Props"
   _check_and_save
-  _info "Checking Zone and Record_Name"
+
+  _info "[KAS] -> Cleaning up after All-inkl/Kasserver hook"
+  _info "[KAS] -> Removing $_fulldomain DNS TXT entry on All-inkl/Kasserver"
+  _info "[KAS] -> Retriving Credential Token"
+  _get_credential_token
+
+  _info "[KAS] -> Checking Zone and Record_Name"
   _get_zone_and_record_name "$_fulldomain"
-  _info "Getting Record ID"
+
+  _info "[KAS] -> Getting Record ID"
   _get_record_id
 
+  _info "[KAS] -> Removing entries with ID: $_record_id"
   # If there is a record_id, delete the entry
   if [ -n "$_record_id" ]; then
-    params="?kas_login=$KAS_Login"
-    params="$params&kas_auth_type=$KAS_Authtype"
-    params="$params&kas_auth_data=$KAS_Authdata"
-    params="$params&kas_action=delete_dns_settings"
-
     for i in $_record_id; do
-      params2="$params&var1=record_id"
-      params2="$params2&wert1=$i"
-      _debug2 "Wait for 10 seconds by default before calling KAS API."
-      _sleep 10
-      response="$(_get "$KAS_Api$params2")"
-      _debug2 "response" "$response"
-      if ! _contains "$response" "TRUE"; then
-        _err "Either the txt record is not found or another error occurred, please check manually."
-        return 1
-      fi
+      _delete_RecordByID "$i"
     done
   else # Cannot delete or unkown error
-    _err "No record_id found that can be deleted. Please check manually."
-    return 1
+    _info "[KAS] -> No record_id found that can be deleted. Please check manually."
   fi
   return 0
 }
 
 ########################## PRIVATE FUNCTIONS ###########################
+# Delete Record ID
+_delete_RecordByID() {
+  recId=$1
+  action="delete_dns_settings"
+  kasReqParam="\"record_id\":\"$recId\""
+  response="$(_callAPI "$action" "$kasReqParam")"
+  _debug2 "[KAS] -> Response" "$response"
 
+  if [ -z "$response" ]; then
+    _info "[KAS] -> Response was empty, please check manually."
+    return 1
+  elif _contains "$response" "<SOAP-ENV:Fault>"; then
+    faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+    case "${faultstring}" in
+    "record_id_not_found")
+      _info "[KAS] -> The record was not found, which perhaps is not a problem. Please check manually."
+      ;;
+    *)
+      _err "[KAS] -> An error =>$faultstring<= occurred, please check manually."
+      return 1
+      ;;
+    esac
+  elif ! _contains "$response" "<item><key xsi:type=\"xsd:string\">ReturnString</key><value xsi:type=\"xsd:string\">TRUE</value></item>"; then
+    _err "[KAS] -> An unknown error occurred, please check manually."
+    return 1
+  fi
+}
 # Checks for the ENV variables and saves them
 _check_and_save() {
   KAS_Login="${KAS_Login:-$(_readaccountconf_mutable KAS_Login)}"
@@ -107,7 +154,7 @@ _check_and_save() {
     KAS_Login=
     KAS_Authtype=
     KAS_Authdata=
-    _err "No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
+    _err "[KAS] -> No auth details provided. Please set user credentials using the \$KAS_Login, \$KAS_Authtype, and \$KAS_Authdata environment variables."
     return 1
   fi
   _saveaccountconf_mutable KAS_Login "$KAS_Login"
@@ -119,50 +166,116 @@ _check_and_save() {
 # Gets back the base domain/zone and record name.
 # See: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide
 _get_zone_and_record_name() {
-  params="?kas_login=$KAS_Login"
-  params="?kas_login=$KAS_Login"
-  params="$params&kas_auth_type=$KAS_Authtype"
-  params="$params&kas_auth_data=$KAS_Authdata"
-  params="$params&kas_action=get_domains"
-
-  _debug2 "Wait for 10 seconds by default before calling KAS API."
-  _sleep 10
-  response="$(_get "$KAS_Api$params")"
-  _debug2 "response" "$response"
-  _zonen="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "domain_name" | tr '<' '\n' | grep "domain_name" | sed "s/domain_name>=>//g")"
-  _domain="$1"
-  _temp_domain="$(echo "$1" | sed 's/\.$//')"
-  _rootzone="$_domain"
-  for i in $_zonen; do
-    l1=${#_rootzone}
+  action="get_domains"
+  response="$(_callAPI "$action")"
+  _debug2 "[KAS] -> Response" "$response"
+
+  if [ -z "$response" ]; then
+    _info "[KAS] -> Response was empty, please check manually."
+    return 1
+  elif _contains "$response" "<SOAP-ENV:Fault>"; then
+    faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+    _err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually."
+    return 1
+  fi
+
+  zonen="$(echo "$response" | sed 's/<item>/\n/g' | sed -r 's/(.*<key xsi:type="xsd:string">domain_name<\/key><value xsi:type="xsd:string">)(.*)(<\/value.*)/\2/' | sed '/^</d')"
+  domain="$1"
+  temp_domain="$(echo "$1" | sed 's/\.$//')"
+  rootzone="$domain"
+  for i in $zonen; do
+    l1=${#rootzone}
     l2=${#i}
-    if _endswith "$_domain" "$i" && [ "$l1" -ge "$l2" ]; then
-      _rootzone="$i"
+    if _endswith "$domain" "$i" && [ "$l1" -ge "$l2" ]; then
+      rootzone="$i"
     fi
   done
-  _zone="${_rootzone}."
-  _temp_record_name="$(echo "$_temp_domain" | sed "s/$_rootzone//g")"
-  _record_name="$(echo "$_temp_record_name" | sed 's/\.$//')"
-  _debug2 "Zone:" "$_zone"
-  _debug2 "Domain:" "$_domain"
-  _debug2 "Record_Name:" "$_record_name"
+  _zone="${rootzone}."
+  temp_record_name="$(echo "$temp_domain" | sed "s/$rootzone//g")"
+  _record_name="$(echo "$temp_record_name" | sed 's/\.$//')"
+  _debug "[KAS] -> Zone:" "$_zone"
+  _debug "[KAS] -> Domain:" "$domain"
+  _debug "[KAS] -> Record_Name:" "$_record_name"
   return 0
 }
 
 # Retrieve the DNS record ID
 _get_record_id() {
-  params="?kas_login=$KAS_Login"
-  params="$params&kas_auth_type=$KAS_Authtype"
-  params="$params&kas_auth_data=$KAS_Authdata"
-  params="$params&kas_action=get_dns_settings"
-  params="$params&var1=zone_host"
-  params="$params&wert1=$_zone"
-
-  _debug2 "Wait for 10 seconds by default before calling KAS API."
-  _sleep 10
-  response="$(_get "$KAS_Api$params")"
-  _debug2 "response" "$response"
-  _record_id="$(echo "$response" | tr -d "\n\r" | tr -d " " | tr '[]' '<>' | sed "s/=>Array/\n=> Array/g" | tr ' ' '\n' | grep "=>$_record_name<" | grep '>TXT<' | tr '<' '\n' | grep record_id | sed "s/record_id>=>//g")"
-  _debug2 _record_id "$_record_id"
+  action="get_dns_settings"
+  kasReqParam="\"zone_host\":\"$_zone\""
+  response="$(_callAPI "$action" "$kasReqParam")"
+  _debug2 "[KAS] -> Response" "$response"
+
+  if [ -z "$response" ]; then
+    _info "[KAS] -> Response was empty, please check manually."
+    return 1
+  elif _contains "$response" "<SOAP-ENV:Fault>"; then
+    faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+    _err "[KAS] -> Either no domains were found or another error =>$faultstring<= occurred, please check manually."
+    return 1
+  fi
+
+  _record_id="$(echo "$response" | tr -d '\n\r' | sed "s/<item xsi:type=\"ns2:Map\">/\n/g" | grep -i "$_record_name" | grep -i ">TXT<" | sed "s/<item><key xsi:type=\"xsd:string\">record_id<\/key><value xsi:type=\"xsd:string\">/=>/g" | sed "s/<\/value><\/item>/\n/g" | grep "=>" | sed "s/=>//g")"
+  _debug "[KAS] -> Record Id: " "$_record_id"
+  return 0
+}
+
+# Retrieve credential token
+_get_credential_token() {
+  baseParamAuth="\"kas_login\":\"$KAS_Login\""
+  baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"$KAS_Authtype\""
+  baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$KAS_Authdata\""
+  baseParamAuth="$baseParamAuth,\"session_lifetime\":600"
+  baseParamAuth="$baseParamAuth,\"session_update_lifetime\":\"Y\""
+
+  data='<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:xmethodsKasApiAuthentication" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:KasAuth><Params xsi:type="xsd:string">{'
+  data="$data$baseParamAuth}</Params></ns1:KasAuth></SOAP-ENV:Body></SOAP-ENV:Envelope>"
+
+  _debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API."
+  _sleep $KAS_default_ratelimit
+
+  contentType="text/xml"
+  export _H1="SOAPAction: urn:xmethodsKasApiAuthentication#KasAuth"
+  response="$(_post "$data" "$KAS_Auth" "" "POST" "$contentType")"
+  _debug2 "[KAS] -> Response" "$response"
+
+  if [ -z "$response" ]; then
+    _info "[KAS] -> Response was empty, please check manually."
+    return 1
+  elif _contains "$response" "<SOAP-ENV:Fault>"; then
+    faultstring="$(echo "$response" | tr -d '\n\r' | sed "s/<faultstring>/\n=> /g" | sed "s/<\/faultstring>/\n/g" | grep "=>" | sed "s/=> //g")"
+    _err "[KAS] -> Could not retrieve login token or antoher error =>$faultstring<= occurred, please check manually."
+    return 1
+  fi
+
+  _credential_token="$(echo "$response" | tr '\n' ' ' | sed 's/.*return xsi:type="xsd:string">\(.*\)<\/return>/\1/' | sed 's/<\/ns1:KasAuthResponse\(.*\)Envelope>.*//')"
+  _debug "[KAS] -> Credential Token: " "$_credential_token"
   return 0
 }
+
+_callAPI() {
+  kasaction=$1
+  kasReqParams=$2
+
+  baseParamAuth="\"kas_login\":\"$KAS_Login\""
+  baseParamAuth="$baseParamAuth,\"kas_auth_type\":\"session\""
+  baseParamAuth="$baseParamAuth,\"kas_auth_data\":\"$_credential_token\""
+
+  data='<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="urn:xmethodsKasApi" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:KasApi><Params xsi:type="xsd:string">{'
+  data="$data$baseParamAuth,\"kas_action\":\"$kasaction\""
+  if [ -n "$kasReqParams" ]; then
+    data="$data,\"KasRequestParams\":{$kasReqParams}"
+  fi
+  data="$data}</Params></ns1:KasApi></SOAP-ENV:Body></SOAP-ENV:Envelope>"
+
+  _debug2 "[KAS] -> Request" "$data"
+
+  _debug "[KAS] -> Be friendly and wait $KAS_default_ratelimit seconds by default before calling KAS API."
+  _sleep $KAS_default_ratelimit
+
+  contentType="text/xml"
+  export _H1="SOAPAction: urn:xmethodsKasApi#KasApi"
+  response="$(_post "$data" "$KAS_Api" "" "POST" "$contentType")"
+  _debug2 "[KAS] -> Response" "$response"
+  echo "$response"
+}

+ 147 - 0
dnsapi/dns_la.sh

@@ -0,0 +1,147 @@
+#!/usr/bin/env sh
+
+#LA_Id="test123"
+#LA_Key="d1j2fdo4dee3948"
+
+LA_Api="https://api.dns.la/api"
+
+########  Public functions #####################
+
+#Usage: dns_la_add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
+dns_la_add() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}"
+  LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}"
+
+  if [ -z "$LA_Id" ] || [ -z "$LA_Key" ]; then
+    LA_Id=""
+    LA_Key=""
+    _err "You didn't specify a dnsla api id and key yet."
+    return 1
+  fi
+
+  #save the api key and email to the account conf file.
+  _saveaccountconf_mutable LA_Id "$LA_Id"
+  _saveaccountconf_mutable LA_Key "$LA_Key"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _info "Adding record"
+  if _la_rest "record.ashx?cmd=create&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue&recordline="; then
+    if _contains "$response" '"resultid":'; then
+      _info "Added, OK"
+      return 0
+    elif _contains "$response" '"code":532'; 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_la_rm() {
+  fulldomain=$1
+  txtvalue=$2
+
+  LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}"
+  LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}"
+
+  _debug "First detect the root zone"
+  if ! _get_root "$fulldomain"; then
+    _err "invalid domain"
+    return 1
+  fi
+  _debug _domain_id "$_domain_id"
+  _debug _sub_domain "$_sub_domain"
+  _debug _domain "$_domain"
+
+  _debug "Getting txt records"
+  if ! _la_rest "record.ashx?cmd=listn&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&host=$_sub_domain&recordtype=TXT&recorddata=$txtvalue"; then
+    _err "Error"
+    return 1
+  fi
+
+  if ! _contains "$response" '"recordid":'; then
+    _info "Don't need to remove."
+    return 0
+  fi
+
+  record_id=$(printf "%s" "$response" | grep '"recordid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n')
+  _debug "record_id" "$record_id"
+  if [ -z "$record_id" ]; then
+    _err "Can not get record id to remove."
+    return 1
+  fi
+  if ! _la_rest "record.ashx?cmd=remove&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domainid=$_domain_id&domain=$_domain&recordid=$record_id"; then
+    _err "Delete record error."
+    return 1
+  fi
+  _contains "$response" '"code":300'
+
+}
+
+####################  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)
+    if [ -z "$h" ]; then
+      #not valid
+      return 1
+    fi
+
+    if ! _la_rest "domain.ashx?cmd=get&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domain=$h"; then
+      return 1
+    fi
+
+    if _contains "$response" '"domainid":'; then
+      _domain_id=$(printf "%s" "$response" | grep '"domainid":' | cut -d : -f 2 | cut -d , -f 1 | tr -d '\r' | tr -d '\n')
+      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
+}
+
+#Usage:  URI
+_la_rest() {
+  url="$LA_Api/$1"
+  _debug "$url"
+
+  if ! response="$(_get "$url" | tr -d ' ' | tr "}" ",")"; then
+    _err "Error: $url"
+    return 1
+  fi
+
+  _debug2 response "$response"
+  return 0
+}

+ 1 - 0
dnsapi/dns_miab.sh

@@ -163,6 +163,7 @@ _retrieve_miab_env() {
   _saveaccountconf_mutable MIAB_Username "$MIAB_Username"
   _saveaccountconf_mutable MIAB_Password "$MIAB_Password"
   _saveaccountconf_mutable MIAB_Server "$MIAB_Server"
+  return 0
 }
 
 #Useage: _miab_rest  "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"  "custom/_acme-challenge.www.domain.com/txt  "POST"

+ 1 - 15
dnsapi/dns_mydnsjp.sh

@@ -150,7 +150,7 @@ _get_root() {
 _mydnsjp_retrieve_domain() {
   _debug "Login to MyDNS.JP"
 
-  response="$(_post "masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/?MENU=100")"
+  response="$(_post "MENU=100&masterid=$MYDNSJP_MasterID&masterpwd=$MYDNSJP_Password" "$MYDNSJP_API/members/")"
   cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)"
 
   # If cookies is not empty then logon successful
@@ -159,22 +159,8 @@ _mydnsjp_retrieve_domain() {
     return 1
   fi
 
-  _debug "Retrieve DOMAIN INFO page"
-
-  export _H1="Cookie:${cookie}"
-
-  response="$(_get "$MYDNSJP_API/?MENU=300")"
-
-  if [ "$?" != "0" ]; then
-    _err "Fail to retrieve DOMAIN INFO."
-    return 1
-  fi
-
   _root_domain=$(echo "$response" | grep "DNSINFO\[domainname\]" | sed 's/^.*value="\([^"]*\)".*/\1/')
 
-  # Logout
-  response="$(_get "$MYDNSJP_API/?MENU=090")"
-
   _debug _root_domain "$_root_domain"
 
   if [ -z "$_root_domain" ]; then

+ 2 - 2
dnsapi/dns_namecheap.sh

@@ -259,7 +259,7 @@ _set_namecheap_TXT() {
   _debug hosts "$hosts"
 
   if [ -z "$hosts" ]; then
-    _error "Hosts not found"
+    _err "Hosts not found"
     return 1
   fi
 
@@ -313,7 +313,7 @@ _del_namecheap_TXT() {
   _debug hosts "$hosts"
 
   if [ -z "$hosts" ]; then
-    _error "Hosts not found"
+    _err "Hosts not found"
     return 1
   fi
 

+ 3 - 7
dnsapi/dns_nederhost.sh

@@ -1,6 +1,6 @@
 #!/usr/bin/env sh
 
-#NederHost_Key="sdfgikogfdfghjklkjhgfcdcfghjk"
+#NederHost_Key="sdfgikogfdfghjklkjhgfcdcfghj"
 
 NederHost_Api="https://api.nederhost.nl/dns/v1"
 
@@ -112,12 +112,8 @@ _nederhost_rest() {
   export _H1="Authorization: Bearer $NederHost_Key"
   export _H2="Content-Type: application/json"
 
-  if [ "$m" != "GET" ]; then
-    _debug data "$data"
-    response="$(_post "$data" "$NederHost_Api/$ep" "" "$m")"
-  else
-    response="$(_get "$NederHost_Api/$ep")"
-  fi
+  _debug data "$data"
+  response="$(_post "$data" "$NederHost_Api/$ep" "" "$m")"
 
   _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")"
   _debug "http response code $_code"

+ 1 - 1
dnsapi/dns_oci.sh

@@ -159,7 +159,7 @@ _oci_config() {
   fi
 
   if [ "$(printf "%s\n" "$OCI_CLI_KEY" | wc -l)" -eq 1 ]; then
-    OCI_CLI_KEY=$(printf "%s" "$OCI_CLI_KEY" | _dbase64 multiline)
+    OCI_CLI_KEY=$(printf "%s" "$OCI_CLI_KEY" | _dbase64)
   fi
 
   return 0

+ 2 - 2
dnsapi/dns_opnsense.sh

@@ -137,7 +137,7 @@ _get_root() {
   domain=$1
   i=2
   p=1
-  if _opns_rest "GET" "/domain/get"; then
+  if _opns_rest "GET" "/domain/searchDomain"; then
     _domain_response="$response"
   else
     return 1
@@ -150,7 +150,7 @@ _get_root() {
       return 1
     fi
     _debug h "$h"
-    id=$(echo "$_domain_response" | _egrep_o "\"[^\"]*\":{\"enabled\":\"1\",\"type\":{\"master\":{\"value\":\"master\",\"selected\":1},\"slave\":{\"value\":\"slave\",\"selected\":0}},\"masterip\":{\"[^\"]*\":{[^}]*}},\"transferkeyalgo\":{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^{]*{[^}]*}},\"transferkey\":\"[^\"]*\"(,\"allownotifyslave\":{\"\":{[^}]*}},|,)\"domainname\":\"${h}\"" | cut -d ':' -f 1 | cut -d '"' -f 2)
+    id=$(echo "$_domain_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"master\",[^.]*,\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2)
     if [ -n "$id" ]; then
       _debug id "$id"
       _host=$(printf "%s" "$domain" | cut -d . -f 1-$p)

+ 4 - 4
dnsapi/dns_ovh.sh

@@ -92,7 +92,7 @@ _initAuth() {
 
   if [ "$OVH_AK" != "$(_readaccountconf OVH_AK)" ]; then
     _info "It seems that your ovh key is changed, let's clear consumer key first."
-    _clearaccountconf OVH_CK
+    _clearaccountconf_mutable OVH_CK
   fi
   _saveaccountconf_mutable OVH_AK "$OVH_AK"
   _saveaccountconf_mutable OVH_AS "$OVH_AS"
@@ -118,13 +118,14 @@ _initAuth() {
     #return and wait for retry.
     return 1
   fi
+  _saveaccountconf_mutable OVH_CK "$OVH_CK"
 
   _info "Checking authentication"
 
   if ! _ovh_rest GET "domain" || _contains "$response" "INVALID_CREDENTIAL" || _contains "$response" "NOT_CREDENTIAL"; then
     _err "The consumer key is invalid: $OVH_CK"
     _err "Please retry to create a new one."
-    _clearaccountconf OVH_CK
+    _clearaccountconf_mutable OVH_CK
     return 1
   fi
   _info "Consumer key is ok."
@@ -235,8 +236,7 @@ _ovh_authentication() {
   _secure_debug consumerKey "$consumerKey"
 
   OVH_CK="$consumerKey"
-  _saveaccountconf OVH_CK "$OVH_CK"
-
+  _saveaccountconf_mutable OVH_CK "$OVH_CK"
   _info "Please open this link to do authentication: $(__green "$validationUrl")"
 
   _info "Here is a guide for you: $(__green "$wiki")"

+ 2 - 2
dnsapi/dns_regru.sh

@@ -92,10 +92,10 @@ _get_root() {
   domains_list=$(echo "${response}" | grep dname | sed -r "s/.*dname=\"([^\"]+)\".*/\\1/g")
 
   for ITEM in ${domains_list}; do
-    IDN_ITEM="$(_idn "${ITEM}")"
+    IDN_ITEM=${ITEM}
     case "${domain}" in
     *${IDN_ITEM}*)
-      _domain=${IDN_ITEM}
+      _domain="$(_idn "${ITEM}")"
       _debug _domain "${_domain}"
       return 0
       ;;

+ 2 - 2
dnsapi/dns_selectel.sh

@@ -76,7 +76,7 @@ dns_selectel_rm() {
     return 1
   fi
 
-  _record_seg="$(echo "$response" | _egrep_o "\"content\" *: *\"$txtvalue\"[^}]*}")"
+  _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")"
   _debug2 "_record_seg" "$_record_seg"
   if [ -z "$_record_seg" ]; then
     _err "can not find _record_seg"
@@ -120,7 +120,7 @@ _get_root() {
       return 1
     fi
 
-    if _contains "$response" "\"name\": \"$h\","; then
+    if _contains "$response" "\"name\" *: *\"$h\","; then
       _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
       _domain=$h
       _debug "Getting domain id for $h"

+ 12 - 9
dnsapi/dns_ultra.sh

@@ -5,7 +5,8 @@
 #
 # ULTRA_PWD="some_password_goes_here"
 
-ULTRA_API="https://restapi.ultradns.com/v2/"
+ULTRA_API="https://api.ultradns.com/v3/"
+ULTRA_AUTH_API="https://api.ultradns.com/v2/"
 
 #Usage: add _acme-challenge.www.domain.com "some_long_string_of_characters_go_here_from_lets_encrypt"
 dns_ultra_add() {
@@ -121,7 +122,7 @@ _get_root() {
       return 1
     fi
     if _contains "${response}" "${h}." >/dev/null; then
-      _domain_id=$(echo "$response" | _egrep_o "${h}")
+      _domain_id=$(echo "$response" | _egrep_o "${h}" | head -1)
       if [ "$_domain_id" ]; then
         _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p)
         _domain="${h}"
@@ -142,23 +143,25 @@ _ultra_rest() {
   ep="$2"
   data="$3"
   _debug "$ep"
-  _debug TOKEN "${AUTH_TOKEN}"
+  if [ -z "$AUTH_TOKEN" ]; then
+    _ultra_login
+  fi
+  _debug TOKEN "$AUTH_TOKEN"
 
-  _ultra_login
   export _H1="Content-Type: application/json"
-  export _H2="Authorization: Bearer ${AUTH_TOKEN}"
+  export _H2="Authorization: Bearer $AUTH_TOKEN"
 
   if [ "$m" != "GET" ]; then
-    _debug data "${data}"
-    response="$(_post "${data}" "${ULTRA_API}"/"${ep}" "" "${m}")"
+    _debug data "$data"
+    response="$(_post "$data" "$ULTRA_API$ep" "" "$m")"
   else
-    response="$(_get "$ULTRA_API/$ep")"
+    response="$(_get "$ULTRA_API$ep")"
   fi
 }
 
 _ultra_login() {
   export _H1=""
   export _H2=""
-  AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_API}authorization/token" | cut -d, -f3 | cut -d\" -f4)
+  AUTH_TOKEN=$(_post "grant_type=password&username=${ULTRA_USR}&password=${ULTRA_PWD}" "${ULTRA_AUTH_API}authorization/token" | cut -d, -f3 | cut -d\" -f4)
   export AUTH_TOKEN
 }

+ 11 - 11
dnsapi/dns_vultr.sh

@@ -3,10 +3,10 @@
 #
 #VULTR_API_KEY=000011112222333344445555666677778888
 
-VULTR_Api="https://api.vultr.com/v1"
+VULTR_Api="https://api.vultr.com/v2"
 
 ########  Public functions #####################
-
+#
 #Usage: add  _acme-challenge.www.domain.com   "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs"
 dns_vultr_add() {
   fulldomain=$1
@@ -31,14 +31,14 @@ dns_vultr_add() {
   _debug _domain "$_domain"
 
   _debug 'Getting txt records'
-  _vultr_rest GET "dns/records?domain=$_domain"
+  _vultr_rest GET "domains/$_domain/records"
 
   if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
     _err 'Error'
     return 1
   fi
 
-  if ! _vultr_rest POST 'dns/create_record' "domain=$_domain&name=$_sub_domain&data=\"$txtvalue\"&type=TXT"; then
+  if ! _vultr_rest POST "domains/$_domain/records" "{\"name\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"type\":\"TXT\"}"; then
     _err "$response"
     return 1
   fi
@@ -71,14 +71,14 @@ dns_vultr_rm() {
   _debug _domain "$_domain"
 
   _debug 'Getting txt records'
-  _vultr_rest GET "dns/records?domain=$_domain"
+  _vultr_rest GET "domains/$_domain/records"
 
   if printf "%s\n" "$response" | grep -- "\"type\":\"TXT\",\"name\":\"$fulldomain\"" >/dev/null; then
     _err 'Error'
     return 1
   fi
 
-  _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'RECORDID' | cut -d : -f 2)"
+  _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'id' | cut -d : -f 2)"
   _debug _record_id "$_record_id"
   if [ "$_record_id" ]; then
     _info "Successfully retrieved the record id for ACME challenge."
@@ -87,7 +87,7 @@ dns_vultr_rm() {
     return 0
   fi
 
-  if ! _vultr_rest POST 'dns/delete_record' "domain=$_domain&RECORDID=$_record_id"; then
+  if ! _vultr_rest DELETE "domains/$_domain/records/$_record_id"; then
     _err "$response"
     return 1
   fi
@@ -112,11 +112,11 @@ _get_root() {
       return 1
     fi
 
-    if ! _vultr_rest GET "dns/list"; then
+    if ! _vultr_rest GET "domains"; then
       return 1
     fi
 
-    if printf "%s\n" "$response" | grep '^\[.*\]' >/dev/null; then
+    if printf "%s\n" "$response" | grep '^\{.*\}' >/dev/null; then
       if _contains "$response" "\"domain\":\"$_domain\""; then
         _sub_domain="$(echo "$fulldomain" | sed "s/\\.$_domain\$//")"
         return 0
@@ -141,8 +141,8 @@ _vultr_rest() {
 
   api_key_trimmed=$(echo $VULTR_API_KEY | tr -d '"')
 
-  export _H1="Api-Key: $api_key_trimmed"
-  export _H2='Content-Type: application/x-www-form-urlencoded'
+  export _H1="Authorization: Bearer $api_key_trimmed"
+  export _H2='Content-Type: application/json'
 
   if [ "$m" != "GET" ]; then
     _debug data "$data"

+ 33 - 15
dnsapi/dns_world4you.sh

@@ -12,7 +12,7 @@ RECORD=''
 
 # Usage: dns_world4you_add <fqdn> <value>
 dns_world4you_add() {
-  fqdn="$1"
+  fqdn=$(echo "$1" | _lower_case)
   value="$2"
   _info "Using world4you to add record"
   _debug fulldomain "$fqdn"
@@ -49,12 +49,12 @@ dns_world4you_add() {
   ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns" '' POST 'application/x-www-form-urlencoded')
   _resethttp
 
-  if _contains "$(_head_n 3 <"$HTTP_HEADER")" '302'; then
+  if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then
     res=$(_get "$WORLD4YOU_API/$paketnr/dns")
     if _contains "$res" "successfully"; then
       return 0
     else
-      msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>\|^\s*//g')
+      msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g')
       if [ "$msg" = '' ]; then
         _err "Unable to add record: Unknown error"
         echo "$ret" >'error-01.html'
@@ -66,15 +66,15 @@ dns_world4you_add() {
       return 1
     fi
   else
-    _err "$(_head_n 3 <"$HTTP_HEADER")"
-    _err "View $HTTP_HEADER for debugging"
+    msg=$(echo "$ret" | grep '"form-error-message"' | sed 's/^.*<div class="form-error-message">\([^<]*\)<\/div>.*$/\1/')
+    _err "Unable to add record: my.world4you.com: $msg"
     return 1
   fi
 }
 
 # Usage: dns_world4you_rm <fqdn> <value>
 dns_world4you_rm() {
-  fqdn="$1"
+  fqdn=$(echo "$1" | _lower_case)
   value="$2"
   _info "Using world4you to remove record"
   _debug fulldomain "$fqdn"
@@ -113,12 +113,12 @@ dns_world4you_rm() {
   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
+  if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then
     res=$(_get "$WORLD4YOU_API/$paketnr/dns")
     if _contains "$res" "successfully"; then
       return 0
     else
-      msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>\|^\s*//g')
+      msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "<h3[^>]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g')
       if [ "$msg" = '' ]; then
         _err "Unable to remove record: Unknown error"
         echo "$ret" >'error-01.html'
@@ -130,8 +130,8 @@ dns_world4you_rm() {
       return 1
     fi
   else
-    _err "$(_head_n 3 <"$HTTP_HEADER")"
-    _err "View $HTTP_HEADER for debugging"
+    msg=$(echo "$ret" | grep "form-error-message" | sed 's/^.*<div class="form-error-message">\([^<]*\)<\/div>.*$/\1/')
+    _err "Unable to remove record: my.world4you.com: $msg"
     return 1
   fi
 }
@@ -155,29 +155,42 @@ _login() {
   _saveaccountconf_mutable WORLD4YOU_USERNAME "$WORLD4YOU_USERNAME"
   _saveaccountconf_mutable WORLD4YOU_PASSWORD "$WORLD4YOU_PASSWORD"
 
+  _resethttp
+  export ACME_HTTP_NO_REDIRECTS=1
+  page=$(_get "$WORLD4YOU_API/login")
+  _resethttp
+
+  if _contains "$(_head_n 1 <"$HTTP_HEADER")" '302'; then
+    _info "Already logged in"
+    _parse_sessid
+    return 0
+  fi
+
   _info "Logging in..."
 
   username="$WORLD4YOU_USERNAME"
   password="$WORLD4YOU_PASSWORD"
-  csrf_token=$(_get "$WORLD4YOU_API/login" | grep '_csrf_token' | sed 's/^.*<input[^>]*value=\"\([^"]*\)\".*$/\1/')
-  sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
+  csrf_token=$(echo "$page" | grep '_csrf_token' | sed 's/^.*<input[^>]*value=\"\([^"]*\)\".*$/\1/')
+  _parse_sessid
 
   export _H1="Cookie: W4YSESSID=$sessid"
   export _H2="X-Requested-With: XMLHttpRequest"
   body="_username=$username&_password=$password&_csrf_token=$csrf_token"
   ret=$(_post "$body" "$WORLD4YOU_API/login" '' POST 'application/x-www-form-urlencoded')
   unset _H2
+
   _debug ret "$ret"
   if _contains "$ret" "\"success\":true"; then
     _info "Successfully logged in"
-    sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
+    _parse_sessid
   else
-    _err "Unable to log in: $(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/')"
+    msg=$(echo "$ret" | sed 's/^.*"message":"\([^\"]*\)".*$/\1/')
+    _err "Unable to log in: my.world4you.com: $msg"
     return 1
   fi
 }
 
-# Usage _get_paketnr <fqdn> <form>
+# Usage: _get_paketnr <fqdn> <form>
 _get_paketnr() {
   fqdn="$1"
   form="$2"
@@ -200,3 +213,8 @@ _get_paketnr() {
   PAKETNR=$(echo "$form" | grep "data-textfilter=\".* $domain " | _tail_n 1 | sed "s|.*$WORLD4YOU_API/\\([0-9]*\\)/.*|\\1|")
   return 0
 }
+
+# Usage: _parse_sessid
+_parse_sessid() {
+  sessid=$(grep 'W4YSESSID' <"$HTTP_HEADER" | _tail_n 1 | sed 's/^.*W4YSESSID=\([^;]*\);.*$/\1/')
+}

+ 45 - 0
notify/slack_app.sh

@@ -0,0 +1,45 @@
+#!/usr/bin/env sh
+
+#Support Slack APP notifications
+
+#SLACK_APP_CHANNEL=""
+#SLACK_APP_TOKEN=""
+
+slack_app_send() {
+  _subject="$1"
+  _content="$2"
+  _statusCode="$3" #0: success, 1: error 2($RENEW_SKIP): skipped
+  _debug "_statusCode" "$_statusCode"
+
+  SLACK_APP_CHANNEL="${SLACK_APP_CHANNEL:-$(_readaccountconf_mutable SLACK_APP_CHANNEL)}"
+  if [ -n "$SLACK_APP_CHANNEL" ]; then
+    _saveaccountconf_mutable SLACK_APP_CHANNEL "$SLACK_APP_CHANNEL"
+  fi
+
+  SLACK_APP_TOKEN="${SLACK_APP_TOKEN:-$(_readaccountconf_mutable SLACK_APP_TOKEN)}"
+  if [ -n "$SLACK_APP_TOKEN" ]; then
+    _saveaccountconf_mutable SLACK_APP_TOKEN "$SLACK_APP_TOKEN"
+  fi
+
+  _content="$(printf "*%s*\n%s" "$_subject" "$_content" | _json_encode)"
+  _data="{\"text\": \"$_content\", "
+  if [ -n "$SLACK_APP_CHANNEL" ]; then
+    _data="$_data\"channel\": \"$SLACK_APP_CHANNEL\", "
+  fi
+  _data="$_data\"mrkdwn\": \"true\"}"
+
+  export _H1="Authorization: Bearer $SLACK_APP_TOKEN"
+
+  SLACK_APP_API_URL="https://slack.com/api/chat.postMessage"
+  if _post "$_data" "$SLACK_APP_API_URL" "" "POST" "application/json; charset=utf-8"; then
+    # shellcheck disable=SC2154
+    SLACK_APP_RESULT_OK=$(echo "$response" | _egrep_o 'ok" *: *true')
+    if [ "$?" = "0" ] && [ "$SLACK_APP_RESULT_OK" ]; then
+      _info "slack send success."
+      return 0
+    fi
+  fi
+  _err "slack send error."
+  _err "$response"
+  return 1
+}