diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 100644 index 0a121fd7..00000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1,190 +0,0 @@ -# GitHub Copilot Shell Scripting (sh) Review Instructions for acme.sh - -## Overall Goal - -Your role is to act as a rigorous yet helpful senior engineer, reviewing Shell script code (`.sh` files) for the [acme.sh](https://github.com/acmesh-official/acme.sh) project. Ensure the code exhibits the highest levels of robustness, security, and portability. -The review must focus on risks unique to Shell scripting, such as proper quoting, robust error handling, and the secure execution of external commands. - -## Required Output Format - -Organize the feedback into a single, structured report, using the three-level marking system: - -1. **Critical Issues (Must Fix Before Merge)** -2. **Suggestions (Improvements to Consider)** -3. **Good Practices (Points to Commend)** - ---- - -## Shell Compatibility - -- **POSIX sh only** -- all scripts must target `sh`, not `bash`. No bash-isms allowed. -- **Shebang**: always use `#!/usr/bin/env sh` (not `#!/bin/sh`, not `#!/usr/bin/env bash`). -- **Use `return`, never `exit`** -- scripts are sourced, not executed as subprocesses. `exit` would kill the parent shell. -- **Cross-platform**: code must work on Linux, macOS, FreeBSD, Solaris, and BusyBox environments. - ---- - -## Robustness and Error Handling - -- **(Critical)** Enforce the use of the following combination at the start of the script for safety and robustness: - - `set -e`: Exit immediately if a command exits with a non-zero status. - - `set -u`: Treat unset variables as an error and exit. - - `set -o pipefail`: Ensure the whole pipeline fails if any command in the pipe fails. -- **Always check return values** of function calls. If an error occurs, there must be a way to stop execution. -- **Return 1** after `_err` messages: - ```sh - if [ -z "$VARIABLE" ]; then - _err "VARIABLE is required" - return 1 - fi - ``` -- Check for the use of `mktemp` when creating temporary files to prevent race conditions and security risks. - ---- - -## Security and Quoting - -- **(Critical)** Check that all variable expansions (like `$VAR` and `$(COMMAND)`) are properly enclosed in **double quotes** (i.e., `"$VAR"` and `"$(COMMAND)"`) to prevent **Word Splitting** and **Globbing**. -- **(Critical)** Find and flag any hardcoded passwords, keys, tokens, or authentication details. -- Verify that all user input, command-line arguments (`$1`, `$2`, etc.), or environment variables are rigorously validated and sanitized before use. -- Avoid `eval` -- warn against and suggest alternatives, as it can lead to arbitrary code execution. - ---- - -## Use Built-in Helper Functions - -Never use raw shell commands when acme.sh provides a wrapper function. This is the most critical rule for portability. - -| Instead of | Use | -|---|---| -| `tr '[:upper:]' '[:lower:]'` | `_lower_case()` | -| `tr '[:lower:]' '[:upper:]'` | `_upper_case()` | -| `head -n 1` | `_head_n 1` | -| `openssl dgst` / `openssl` | `_digest()` / `_hmac()` | -| `date` | `_utc_date()` with `sed`/`tr` | -| `curl` / `wget` | `_get()` or `_post()` | -| `sleep` | `_sleep` | -| `base64` / `openssl base64` | `_base64()` | -| `$(( ))` arithmetic | `_math()` | -| `grep -E` / `grep -Po` | `_egrep_o()` | -| `printf` | `echo` | -| `idn` command | `_idn()` / `_is_idn()` | -| `mktemp` | `_mktemp()` | -| `[:space:]` | ` ` | -| `[:alnum:]` | `A-Za-z0-9` | -| `[:alpha:]` | `A-Za-z` | -| `[:digit:]` | `0-9` | -| `awk` | `cut` / `sed` / `while read` loops | - - - -When fixing a pattern issue, fix **all instances** in the file, not just the one highlighted. - ---- - -## Forbidden External Tools - -Do not use these commands -- they are not portable across all target platforms: - -- `jq` (parse JSON with built-in string manipulation) -- `grep -A` (removed throughout the project) -- `grep -Po` (Perl regex not available everywhere) -- `rev`, `xargs`, `iconv` -- If you must depend on an external tool, check with `_exists` first: - ```sh - if ! _exists jq; then - _err "jq is required" - return 1 - fi - ``` -- Warn against patterns like `for i in $(cat file)` or `for i in $(ls)` and recommend the more robust `while IFS= read -r line` pattern for safely processing file contents or filenames that might contain spaces. - ---- - -## Configuration Management - -Use the correct save/read functions depending on hook type: - -- **DNS hooks**: `_readaccountconf_mutable` to read API keys, `_saveaccountconf_mutable` to save them. Do not use `_saveaccountconf` or `_readaccountconf`. -- **Deploy hooks**: `_savedeployconf` / `_getdeployconf` -- **Notification hooks**: use account conf functions. -- Save operations should only happen in the correct lifecycle function (e.g., `_issue()`). -- Use environment variables for all configurable values -- do not introduce hardcoded config files. -- Do not clear account conf without a clear reason. - ---- - -## DNS API Conventions - -- Read the [DNS API Dev Guide](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide) before writing a DNS plugin. -- Each file under `dnsapi/` must contain a `{filename}_add` function for adding DNS TXT records. -- The `_get_root()` loop counter `i` must start from `1` (not `2`) to support DNS alias mode. -- The `dns_*_rm()` function must remove records **by TXT value**, not by replacing/updating. See [#1261](https://github.com/acmesh-official/acme.sh/issues/1261). -- Preserve the `dns_*_info` metadata variable block in each DNS script header. - ---- - -## Variable Naming - -- Use CamelCase with provider prefix: `KINGHOST_Username` (not `KINGHOST_username`). -- Variable names should use uppercase letters and underscores (e.g., `MY_VARIABLE`), or follow established project conventions. -- Avoid confusingly similar names. Prefer one variable with comma-separated values over multiple variables (e.g., `CZ_Zones` with comma support instead of separate `CZ_Zone` and `CZ_Zones`). -- Do not define variables with the same name in different scopes. -- Variables inside functions should be declared using the `local` keyword to avoid unintentionally modifying global state. - ---- - -## Code Style - -- Use `shfmt` for formatting -- CI enforces it. -- Reduce indentation where possible. -- Single space, not double spaces. -- No trailing semicolons after `return` statements. -- Add a newline at the end of every file. -- Use `$(command)` over backticks `` `command` `` for command substitution. - ---- - -## Simplicity - -- Prefer hardcoded sensible defaults over unnecessary configuration variables (e.g., use `3600` for TTL instead of a `DESEC_TTL` variable). -- Reject over-engineered solutions. If it can be done in one line, do it in one line. -- Follow existing patterns in the codebase -- new hooks should look like existing hooks. -- Respect user choices: do not `chmod` files that already exist; the user's permissions take priority. - ---- - -## Documentation Requirements - -Before a PR can be merged, the following documentation must be provided: - -- **Wiki page**: add or update the relevant page: - - DNS APIs: [dnsapi](https://github.com/acmesh-official/acme.sh/wiki/dnsapi) or [dnsapi2](https://github.com/acmesh-official/acme.sh/wiki/dnsapi2) - - Deploy hooks: [deployhooks](https://github.com/acmesh-official/acme.sh/wiki/deployhooks) - - Notification hooks: [notify](https://github.com/acmesh-official/acme.sh/wiki/notify) - - Options: [Options-and-Params](https://github.com/acmesh-official/acme.sh/wiki/Options-and-Params) -- **In-code usage**: add usage examples in the help text of `acme.sh` itself. -- **README**: add website URLs for new DNS providers. - ---- - -## CI and Merge Hygiene - -- All CI checks must pass before merge. -- Rebase to the latest `dev` branch frequently -- do not use merge commits. -- Enable GitHub Actions on your fork to catch errors early. -- Run the [DNS API Test](https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test) workflow for DNS plugins. -- For Docker changes, ensure the Dockerfile includes any required dependencies. - ---- - -## Debug Logging - -- Use `_debug2` (not `_debug3` or other levels) unless there is a specific reason for a different level. - ---- - -## Things to Avoid in Reviews - -- Do not comment on purely stylistic issues like spacing or indentation, which should be handled by tools like ShellCheck or `shfmt`. -- Do not be overly verbose unless a significant issue is found. Keep feedback concise and actionable. diff --git a/.github/workflows/Apache.yml b/.github/workflows/Apache.yml deleted file mode 100644 index b17abbd1..00000000 --- a/.github/workflows/Apache.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Apache -on: - push: - paths: - - '*.sh' - - '.github/workflows/Apache.yml' - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/Apache.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - Apache: - runs-on: ubuntu-latest - env: - TestingDomain: example.com - TEST_ACME_Server: https://localhost:14000/dir - HTTPS_INSECURE: 1 - TEST_LOCAL: 1 - TEST_CA: "Pebble Intermediate CA" - TEST_APACHE: 1 - CASE: le_test_apache - steps: - - uses: actions/checkout@v6 - - name: Install tools - run: sudo apt-get install -y socat apache2 - - name: Run Pebble - run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d - - name: Set up Pebble - run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 - - name: Set up Apache - # Apache serves on 5002, which is the HTTP-01 validation port in - # Pebble's default config; acme.sh appends the challenge Alias to - # the main config itself - run: | - echo "Listen 5002" | sudo tee /etc/apache2/ports.conf - sudo sed -i "s/\*:80/*:5002/" /etc/apache2/sites-available/000-default.conf - sudo apache2ctl configtest - sudo systemctl restart apache2 - curl -s -o /dev/null -w "%{http_code}" -H "Host: example.com" http://127.0.0.1:5002/ | grep -E "200|403|404" - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Run acmetest - run: cd ../acmetest && sudo --preserve-env ./letest.sh diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml index 84a17470..17e98ae3 100644 --- a/.github/workflows/DNS.yml +++ b/.github/workflows/DNS.yml @@ -1,6 +1,5 @@ name: DNS on: - workflow_dispatch: push: paths: - 'dnsapi/*.sh' @@ -26,9 +25,9 @@ jobs: id: step_one run: | if [ "${{secrets.TokenName1}}" ] ; then - echo "hasToken=true" >> "$GITHUB_OUTPUT" + echo "::set-output name=hasToken::true" else - echo "hasToken=false" >> "$GITHUB_OUTPUT" + echo "::set-output name=hasToken::false" fi - name: Check the value run: echo ${{ steps.step_one.outputs.hasToken }} @@ -66,7 +65,7 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Set env file @@ -114,29 +113,27 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Install tools - run: | - brew untap aws/tap || true - brew install socat + run: brew install socat - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest run: | if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} fi if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} fi if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} fi if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} fi if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} fi cd ../acmetest ./letest.sh @@ -167,7 +164,7 @@ jobs: - name: Set git to use LF run: | git config --global core.autocrlf false - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Install cygwin base packages with chocolatey run: | choco config get cacheLocation @@ -178,33 +175,28 @@ jobs: C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s https://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git shell: cmd - name: Set ENV - shell: bash + shell: cmd run: | - echo 'PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' >> "$GITHUB_ENV" - # cygwin git sees the runner workspace as owned by another user and - # fails with "dubious ownership" (exit 128) in the checkout post step - echo 'GIT_CONFIG_COUNT=1' >> "$GITHUB_ENV" - echo 'GIT_CONFIG_KEY_0=safe.directory' >> "$GITHUB_ENV" - echo 'GIT_CONFIG_VALUE_0=*' >> "$GITHUB_ENV" + echo PATH=C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin >> %GITHUB_ENV% - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest shell: bash run: | if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} fi if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} fi if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} fi if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} fi if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} fi cd ../acmetest ./letest.sh @@ -212,7 +204,7 @@ jobs: FreeBSD: - runs-on: ubuntu-latest + runs-on: macos-12 needs: Windows env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -231,105 +223,40 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/freebsd-vm@v1 + - uses: vmactions/freebsd-vm@v0 with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: pkg install -y socat curl usesh: true - sync: nfs + copyback: false run: | if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} fi if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} fi if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} fi if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} fi if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} fi cd ../acmetest ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - GhostBSD: - runs-on: ubuntu-latest - needs: FreeBSD - # GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run - continue-on-error: true - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/ghostbsd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - prepare: pkg install -y socat curl - usesh: true - sync: nfs - 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 - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - OpenBSD: - runs-on: ubuntu-latest - needs: GhostBSD + runs-on: macos-12 + needs: FreeBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} @@ -347,45 +274,39 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/openbsd-vm@v1 + - uses: vmactions/openbsd-vm@v0 with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - prepare: pkg_add socat curl libiconv + prepare: pkg_add socat curl usesh: true - sync: nfs + copyback: false run: | if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} fi if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} fi if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} fi if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} fi if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} fi cd ../acmetest ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" + NetBSD: - runs-on: ubuntu-latest + runs-on: macos-12 needs: OpenBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -404,46 +325,40 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/netbsd-vm@v1 + - uses: vmactions/netbsd-vm@v0 with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | - /usr/sbin/pkg_add curl socat + pkg_add curl socat usesh: true - sync: nfs + copyback: false run: | if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} fi if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} fi if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} fi if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} fi if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} fi cd ../acmetest ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" + DragonFlyBSD: - runs-on: ubuntu-latest + runs-on: macos-12 needs: NetBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} @@ -462,108 +377,44 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/dragonflybsd-vm@v1 + - uses: vmactions/dragonflybsd-vm@v0 with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' prepare: | - pkg install -y libnghttp2 pkg install -y curl socat usesh: true - sync: nfs + copyback: false run: | if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} fi if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} fi if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} fi if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} fi if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} fi cd ../acmetest ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - MidnightBSD: - runs-on: ubuntu-latest - needs: DragonFlyBSD - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/midnightbsd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - prepare: mport install socat curl || true - usesh: true - sync: nfs - 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 - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - Solaris: - runs-on: ubuntu-latest - needs: MidnightBSD + runs-on: macos-12 + needs: DragonFlyBSD env: TEST_DNS : ${{ secrets.TEST_DNS }} TestingDomain: ${{ secrets.TestingDomain }} @@ -582,394 +433,33 @@ jobs: TokenName4: ${{ secrets.TokenName4}} TokenName5: ${{ secrets.TokenName5}} steps: - - uses: actions/checkout@v7 + - uses: actions/checkout@v3 - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/solaris-vm@v1 + - uses: vmactions/solaris-vm@v0 with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - sync: nfs - prepare: | - pkgutil -U - pkgutil -y -i socat + copyback: false + prepare: pkgutil -y -i socat run: | pkg set-mediator -v -I default@1.1 openssl export PATH=/usr/gnu/bin:$PATH if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} fi if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} fi if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} fi if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} fi if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} fi cd ../acmetest ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - - Omnios: - runs-on: ubuntu-latest - needs: Solaris - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Omnios doesn't accept the expired ISRG X1 root - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/omnios-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - sync: nfs - prepare: pkg install socat - run: | - if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" - fi - if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" - fi - if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" - fi - if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" - fi - if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" - fi - cd ../acmetest - ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - - - OpenIndiana: - runs-on: ubuntu-latest - needs: Omnios - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/openindiana-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - sync: nfs - prepare: pkg install socat - run: | - if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" - fi - if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" - fi - if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" - fi - if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" - fi - if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" - fi - cd ../acmetest - ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - - - Tribblix: - runs-on: ubuntu-latest - needs: OpenIndiana - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since Tribblix doesn't accept the expired ISRG X1 root - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/tribblix-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - sync: nfs - prepare: zap install socat - run: | - if [ "${{ secrets.TokenName1}}" ] ; then - export ${{ secrets.TokenName1}}="${{ secrets.TokenValue1}}" - fi - if [ "${{ secrets.TokenName2}}" ] ; then - export ${{ secrets.TokenName2}}="${{ secrets.TokenValue2}}" - fi - if [ "${{ secrets.TokenName3}}" ] ; then - export ${{ secrets.TokenName3}}="${{ secrets.TokenValue3}}" - fi - if [ "${{ secrets.TokenName4}}" ] ; then - export ${{ secrets.TokenName4}}="${{ secrets.TokenValue4}}" - fi - if [ "${{ secrets.TokenName5}}" ] ; then - export ${{ secrets.TokenName5}}="${{ secrets.TokenValue5}}" - fi - cd ../acmetest - ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - - - Haiku: - runs-on: ubuntu-latest - needs: Tribblix - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - HTTPS_INSECURE: 1 # always set to 1 to ignore https error, since OpenIndiana doesn't accept the expired ISRG X1 root - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/haiku-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - sync: rsync - copyback: false - prepare: | - mkdir -p /boot/home/.cache - pkgman install -y cronie - - 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 - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - - - Hurd: - runs-on: ubuntu-latest - needs: Haiku - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - HTTPS_INSECURE: 1 # always set to 1 to ignore https error - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/hurd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - sync: rsync - copyback: false - usesh: true - prepare: | - apt-get update -y - apt-get install -y curl cron - 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 - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - - - - OpenEuler: - runs-on: ubuntu-latest - needs: Hurd - env: - TEST_DNS : ${{ secrets.TEST_DNS }} - TestingDomain: ${{ secrets.TestingDomain }} - TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} - TEST_DNS_NO_SUBDOMAIN: ${{ secrets.TEST_DNS_NO_SUBDOMAIN }} - TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} - CASE: le_test_dnsapi - TEST_LOCAL: 1 - DEBUG: ${{ secrets.DEBUG }} - http_proxy: ${{ secrets.http_proxy }} - https_proxy: ${{ secrets.https_proxy }} - HTTPS_INSECURE: 1 # always set to 1 to ignore https error - TokenName1: ${{ secrets.TokenName1}} - TokenName2: ${{ secrets.TokenName2}} - TokenName3: ${{ secrets.TokenName3}} - TokenName4: ${{ secrets.TokenName4}} - TokenName5: ${{ secrets.TokenName5}} - steps: - - uses: actions/checkout@v7 - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/openeuler-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_NO_SUBDOMAIN TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG http_proxy https_proxy HTTPS_INSECURE TokenName1 TokenName2 TokenName3 TokenName4 TokenName5 ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' - sync: rsync - copyback: false - usesh: true - prepare: dnf install -y curl socat cronie tar gzip - 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 - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - diff --git a/.github/workflows/DragonFlyBSD.yml b/.github/workflows/DragonFlyBSD.yml index 20c61dcc..6daa9be4 100644 --- a/.github/workflows/DragonFlyBSD.yml +++ b/.github/workflows/DragonFlyBSD.yml @@ -1,78 +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) - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/dragonflybsd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - 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 libnghttp2 - pkg install -y curl socat - usesh: true - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - +name: DragonFlyBSD +on: + push: + branches: + - '*' + paths: + - '*.sh' + - '.github/workflows/DragonFlyBSD.yml' + + pull_request: + branches: + - dev + paths: + - '*.sh' + - '.github/workflows/DragonFlyBSD.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + + + +jobs: + DragonFlyBSD: + strategy: + matrix: + include: + - TEST_ACME_Server: "LetsEncrypt.org_test" + CA_ECDSA: "" + CA: "" + CA_EMAIL: "" + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 + #- TEST_ACME_Server: "ZeroSSL.com" + # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + # CA: "ZeroSSL RSA Domain Secure Site CA" + # CA_EMAIL: "githubtest@acme.sh" + # TEST_PREFERRED_CHAIN: "" + runs-on: macos-12 + env: + TEST_LOCAL: 1 + TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} + CA_ECDSA: ${{ matrix.CA_ECDSA }} + CA: ${{ matrix.CA }} + CA_EMAIL: ${{ matrix.CA_EMAIL }} + TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} + steps: + - uses: actions/checkout@v3 + - uses: vmactions/cf-tunnel@v0 + id: tunnel + with: + protocol: http + port: 8080 + - name: Set envs + run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV + - name: Clone acmetest + run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/dragonflybsd-vm@v0 + with: + envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN' + copyback: "false" + nat: | + "8080": "80" + prepare: | + pkg install -y curl socat + usesh: true + run: | + cd ../acmetest \ + && ./letest.sh + + diff --git a/.github/workflows/FreeBSD.yml b/.github/workflows/FreeBSD.yml index 88ef0a6e..0fa55fd4 100644 --- a/.github/workflows/FreeBSD.yml +++ b/.github/workflows/FreeBSD.yml @@ -29,19 +29,19 @@ jobs: CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 ACME_USE_WGET: 1 #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" + # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest + runs-on: macos-12 env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} @@ -51,8 +51,8 @@ jobs: TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 + - uses: actions/checkout@v3 + - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http @@ -61,22 +61,16 @@ jobs: run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/freebsd-vm@v1 + - uses: vmactions/freebsd-vm@v0 with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" prepare: pkg install -y socat curl wget usesh: true - sync: nfs + copyback: false run: | cd ../acmetest \ && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" + diff --git a/.github/workflows/GhostBSD.yml b/.github/workflows/GhostBSD.yml deleted file mode 100644 index 04510c15..00000000 --- a/.github/workflows/GhostBSD.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: GhostBSD -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/GhostBSD.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/GhostBSD.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - GhostBSD: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - ACME_USE_WGET: 1 - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - # GhostBSD VM frequently flakes on boot/ssh; don't let it fail the whole run - continue-on-error: true - 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@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/ghostbsd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: pkg install -y socat curl wget - usesh: true - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" diff --git a/.github/workflows/Haiku.yml b/.github/workflows/Haiku.yml deleted file mode 100644 index b133dd18..00000000 --- a/.github/workflows/Haiku.yml +++ /dev/null @@ -1,85 +0,0 @@ -name: Haiku -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/Haiku.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/Haiku.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - Haiku: - strategy: - fail-fast: false - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - ACME_USE_WGET: 1 - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/haiku-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: | - mkdir -p /boot/home/.cache - pkgman install -y cronie - sync: rsync - copyback: false - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - diff --git a/.github/workflows/Hurd.yml b/.github/workflows/Hurd.yml deleted file mode 100644 index fee80d29..00000000 --- a/.github/workflows/Hurd.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Hurd -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/Hurd.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/Hurd.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - Hurd: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - steps: - - uses: actions/checkout@v7 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/hurd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN' - nat: | - "8080": "80" - # Do NOT install socat: socat's SYSTEM: address is broken on GNU Hurd - # (the child shell output goes to socat's stdout instead of the socket, - # so clients get an empty reply). Without socat, acme.sh standalone - # mode falls back to its python3 server, which works on Hurd. - prepare: | - apt-get update -y - apt-get install -y curl cron - usesh: true - sync: rsync - copyback: false - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" diff --git a/.github/workflows/Linux.yml b/.github/workflows/Linux.yml index 17462033..156fa5df 100644 --- a/.github/workflows/Linux.yml +++ b/.github/workflows/Linux.yml @@ -26,21 +26,14 @@ jobs: Linux: strategy: matrix: - os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "gentoo/stage3"] + os: ["ubuntu:latest", "debian:latest", "almalinux:latest", "fedora:latest", "centos:7", "opensuse/leap:latest", "alpine:latest", "oraclelinux:8", "kalilinux/kali", "archlinux:latest", "mageia", "gentoo/stage3"] runs-on: ubuntu-latest env: TEST_LOCAL: 1 - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 TEST_ACME_Server: "LetsEncrypt.org_test" steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 80 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV + - uses: actions/checkout@v3 - name: Clone acmetest run: | cd .. \ diff --git a/.github/workflows/MacOS.yml b/.github/workflows/MacOS.yml index ef9580a6..c1f29769 100644 --- a/.github/workflows/MacOS.yml +++ b/.github/workflows/MacOS.yml @@ -29,10 +29,10 @@ jobs: CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" + # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: macos-latest @@ -44,16 +44,9 @@ jobs: CA_EMAIL: ${{ matrix.CA_EMAIL }} TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - name: Install tools run: brew install socat - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 80 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: | cd .. \ diff --git a/.github/workflows/MidnightBSD.yml b/.github/workflows/MidnightBSD.yml deleted file mode 100644 index ce499e4e..00000000 --- a/.github/workflows/MidnightBSD.yml +++ /dev/null @@ -1,75 +0,0 @@ -name: MidnightBSD -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/MidnightBSD.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/MidnightBSD.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - MidnightBSD: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/midnightbsd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: mport install socat curl wget || true - usesh: true - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" diff --git a/.github/workflows/NetBSD.yml b/.github/workflows/NetBSD.yml index 6695f71e..33bcf23c 100644 --- a/.github/workflows/NetBSD.yml +++ b/.github/workflows/NetBSD.yml @@ -1,77 +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) - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/netbsd-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: | - /usr/sbin/pkg_add curl socat - usesh: true - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - +name: NetBSD +on: + push: + branches: + - '*' + paths: + - '*.sh' + - '.github/workflows/NetBSD.yml' + + pull_request: + branches: + - dev + paths: + - '*.sh' + - '.github/workflows/NetBSD.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + + + + +jobs: + NetBSD: + strategy: + matrix: + include: + - TEST_ACME_Server: "LetsEncrypt.org_test" + CA_ECDSA: "" + CA: "" + CA_EMAIL: "" + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 + #- TEST_ACME_Server: "ZeroSSL.com" + # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + # CA: "ZeroSSL RSA Domain Secure Site CA" + # CA_EMAIL: "githubtest@acme.sh" + # TEST_PREFERRED_CHAIN: "" + runs-on: macos-12 + env: + TEST_LOCAL: 1 + TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} + CA_ECDSA: ${{ matrix.CA_ECDSA }} + CA: ${{ matrix.CA }} + CA_EMAIL: ${{ matrix.CA_EMAIL }} + TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} + steps: + - uses: actions/checkout@v3 + - uses: vmactions/cf-tunnel@v0 + id: tunnel + with: + protocol: http + port: 8080 + - name: Set envs + run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV + - name: Clone acmetest + run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/netbsd-vm@v0 + with: + envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN' + nat: | + "8080": "80" + prepare: | + export PKG_PATH="https://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 + + diff --git a/.github/workflows/Nginx.yml b/.github/workflows/Nginx.yml deleted file mode 100644 index 2ca9d64a..00000000 --- a/.github/workflows/Nginx.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Nginx -on: - push: - paths: - - '*.sh' - - '.github/workflows/Nginx.yml' - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/Nginx.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - Nginx: - runs-on: ubuntu-latest - env: - TestingDomain: example.com - TEST_ACME_Server: https://localhost:14000/dir - HTTPS_INSECURE: 1 - TEST_LOCAL: 1 - TEST_CA: "Pebble Intermediate CA" - TEST_NGINX: 1 - CASE: le_test_nginx - steps: - - uses: actions/checkout@v6 - - name: Install tools - run: sudo apt-get install -y socat nginx - - name: Run Pebble - run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker compose up -d - - name: Set up Pebble - run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 - - name: Set up nginx - # a backend on 8081 plus a site with an aaPanel/BT style - # "location ^~ /" proxy block that shadows plain regex locations - # (regression for #6125); the site listens on 5002, which is the - # HTTP-01 validation port in Pebble's default config - run: | - sudo tee /etc/nginx/sites-available/default >/dev/null <<'EOF' - server { - listen 127.0.0.1:8081; - location / { - default_type text/plain; - return 200 "backend"; - } - } - server { - listen 5002 default_server; - server_name example.com; - location ^~ / { - proxy_pass http://127.0.0.1:8081; - proxy_set_header Host $http_host; - } - } - EOF - sudo nginx -t - sudo systemctl restart nginx - curl -s -H "Host: example.com" http://127.0.0.1:5002/ | grep backend - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - name: Run acmetest - run: cd ../acmetest && sudo --preserve-env ./letest.sh diff --git a/.github/workflows/Omnios.yml b/.github/workflows/Omnios.yml deleted file mode 100644 index aabb168b..00000000 --- a/.github/workflows/Omnios.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: Omnios -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/Omnios.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/Omnios.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - Omnios: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - ACME_USE_WGET: 1 - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/omnios-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: pkg install socat wget - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - diff --git a/.github/workflows/OpenBSD.yml b/.github/workflows/OpenBSD.yml index 46318163..7746645a 100644 --- a/.github/workflows/OpenBSD.yml +++ b/.github/workflows/OpenBSD.yml @@ -29,19 +29,19 @@ jobs: CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 ACME_USE_WGET: 1 #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" + # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest + runs-on: macos-12 env: TEST_LOCAL: 1 TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} @@ -51,8 +51,8 @@ jobs: TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 + - uses: actions/checkout@v3 + - uses: vmactions/cf-tunnel@v0 id: tunnel with: protocol: http @@ -61,22 +61,16 @@ jobs: run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/openbsd-vm@v1 + - uses: vmactions/openbsd-vm@v0 with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' nat: | "8080": "80" - prepare: pkg_add socat curl wget libnghttp2 + prepare: pkg_add socat curl wget usesh: true - sync: nfs + copyback: false run: | cd ../acmetest \ && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" + diff --git a/.github/workflows/OpenEuler.yml b/.github/workflows/OpenEuler.yml deleted file mode 100644 index 2b4bd0ab..00000000 --- a/.github/workflows/OpenEuler.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: OpenEuler -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/OpenEuler.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/OpenEuler.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - OpenEuler: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - steps: - - uses: actions/checkout@v7 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/openeuler-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN' - nat: | - "8080": "80" - prepare: dnf install -y curl socat cronie tar gzip - usesh: true - sync: rsync - copyback: false - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" diff --git a/.github/workflows/OpenIndiana.yml b/.github/workflows/OpenIndiana.yml deleted file mode 100644 index e3119f8e..00000000 --- a/.github/workflows/OpenIndiana.yml +++ /dev/null @@ -1,81 +0,0 @@ -name: OpenIndiana -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/OpenIndiana.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/OpenIndiana.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - OpenIndiana: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - ACME_USE_WGET: 1 - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/openindiana-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: pkg install socat curl - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - diff --git a/.github/workflows/PebbleStrict.yml b/.github/workflows/PebbleStrict.yml index 946d993a..9f3a98ce 100644 --- a/.github/workflows/PebbleStrict.yml +++ b/.github/workflows/PebbleStrict.yml @@ -33,11 +33,11 @@ jobs: TEST_CA: "Pebble Intermediate CA" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - 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: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker-compose up -d - name: Set up Pebble run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 - name: Clone acmetest @@ -58,14 +58,14 @@ jobs: TEST_IPCERT: 1 steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - name: Install tools run: sudo apt-get install -y socat - name: Run Pebble run: | docker run --rm -itd --name=pebble \ -e PEBBLE_VA_ALWAYS_VALID=1 \ - -p 14000:14000 -p 15000:15000 ghcr.io/letsencrypt/pebble:latest -config /test/config/pebble-config.json -strict + -p 14000:14000 -p 15000:15000 letsencrypt/pebble:latest pebble -config /test/config/pebble-config.json -strict - name: Clone acmetest run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - name: Run acmetest diff --git a/.github/workflows/Solaris.yml b/.github/workflows/Solaris.yml index 30e4e291..34d31a59 100644 --- a/.github/workflows/Solaris.yml +++ b/.github/workflows/Solaris.yml @@ -1,83 +1,74 @@ -name: Solaris -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/Solaris.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/Solaris.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - Solaris: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - ACME_USE_WGET: 1 - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/solaris-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: | - pkgutil -U - pkgutil -y -i socat curl wget - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" - +name: Solaris +on: + push: + branches: + - '*' + paths: + - '*.sh' + - '.github/workflows/Solaris.yml' + + pull_request: + branches: + - dev + paths: + - '*.sh' + - '.github/workflows/Solaris.yml' + + + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + Solaris: + strategy: + matrix: + include: + - TEST_ACME_Server: "LetsEncrypt.org_test" + CA_ECDSA: "" + CA: "" + CA_EMAIL: "" + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 + - TEST_ACME_Server: "LetsEncrypt.org_test" + CA_ECDSA: "" + CA: "" + CA_EMAIL: "" + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 + ACME_USE_WGET: 1 + #- TEST_ACME_Server: "ZeroSSL.com" + # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + # CA: "ZeroSSL RSA Domain Secure Site CA" + # CA_EMAIL: "githubtest@acme.sh" + # TEST_PREFERRED_CHAIN: "" + runs-on: macos-12 + env: + TEST_LOCAL: 1 + TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} + CA_ECDSA: ${{ matrix.CA_ECDSA }} + CA: ${{ matrix.CA }} + CA_EMAIL: ${{ matrix.CA_EMAIL }} + TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} + ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} + steps: + - uses: actions/checkout@v3 + - uses: vmactions/cf-tunnel@v0 + id: tunnel + with: + protocol: http + port: 8080 + - name: Set envs + run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV + - name: Clone acmetest + run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/solaris-vm@v0 + with: + envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' + copyback: "false" + nat: | + "8080": "80" + prepare: pkgutil -y -i socat curl wget + run: | + cd ../acmetest \ + && ./letest.sh + diff --git a/.github/workflows/Tribblix.yml b/.github/workflows/Tribblix.yml deleted file mode 100644 index 68e61dc8..00000000 --- a/.github/workflows/Tribblix.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Tribblix -on: - push: - branches: - - '*' - paths: - - '*.sh' - - '.github/workflows/Tribblix.yml' - - pull_request: - branches: - - dev - paths: - - '*.sh' - - '.github/workflows/Tribblix.yml' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - - - -jobs: - Tribblix: - strategy: - matrix: - include: - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - - TEST_ACME_Server: "LetsEncrypt.org_test" - CA_ECDSA: "" - CA: "" - CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) - ACME_USE_WGET: 1 - #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" - # CA_EMAIL: "githubtest@acme.sh" - # TEST_PREFERRED_CHAIN: "" - runs-on: ubuntu-latest - env: - TEST_LOCAL: 1 - TEST_ACME_Server: ${{ matrix.TEST_ACME_Server }} - CA_ECDSA: ${{ matrix.CA_ECDSA }} - CA: ${{ matrix.CA }} - CA_EMAIL: ${{ matrix.CA_EMAIL }} - TEST_PREFERRED_CHAIN: ${{ matrix.TEST_PREFERRED_CHAIN }} - ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} - steps: - - uses: actions/checkout@v6 - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 8080 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - - name: Clone acmetest - run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ - - uses: vmactions/tribblix-vm@v1 - with: - debug-on-error: ${{ vars.DEBUG_ON_ERROR }} - cache-after-prepare: true - envs: 'TEST_LOCAL TestingDomain TEST_ACME_Server CA_ECDSA CA CA_EMAIL TEST_PREFERRED_CHAIN ACME_USE_WGET' - nat: | - "8080": "80" - prepare: zap install socat curl wget - sync: nfs - run: | - cd ../acmetest \ - && ./letest.sh - - name: DebugOnError - if: ${{ failure() }} - run: | - echo "See how to debug in VM:" - echo "https://github.com/acmesh-official/acme.sh/wiki/debug-in-VM" diff --git a/.github/workflows/Ubuntu.yml b/.github/workflows/Ubuntu.yml index 36dfdbe6..a6ec714c 100644 --- a/.github/workflows/Ubuntu.yml +++ b/.github/workflows/Ubuntu.yml @@ -29,16 +29,16 @@ jobs: CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 - TEST_ACME_Server: "LetsEncrypt.org_test" CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 ACME_USE_WGET: 1 - TEST_ACME_Server: "ZeroSSL.com" - CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - CA: "ZeroSSL RSA DV SSL CA 2" + CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + CA: "ZeroSSL RSA Domain Secure Site CA" CA_EMAIL: "githubtest@acme.sh" TEST_PREFERRED_CHAIN: "" - TEST_ACME_Server: "https://localhost:9000/acme/acme/directory" @@ -70,7 +70,7 @@ jobs: TestingDomain: ${{ matrix.TestingDomain }} ACME_USE_WGET: ${{ matrix.ACME_USE_WGET }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - name: Install tools run: sudo apt-get install -y socat wget - name: Start StepCA @@ -80,14 +80,9 @@ jobs: -p 9000:9000 \ -e "DOCKER_STEPCA_INIT_NAME=Smallstep" \ -e "DOCKER_STEPCA_INIT_DNS_NAMES=localhost,$(hostname -f)" \ - -e "DOCKER_STEPCA_INIT_REMOTE_MANAGEMENT=true" \ - -e "DOCKER_STEPCA_INIT_PASSWORD=test" \ --name stepca \ - smallstep/step-ca:0.23.1 - - sleep 5 - docker exec stepca bash -c "echo test >test" \ - && docker exec stepca step ca provisioner add acme --type ACME --admin-subject step --admin-password-file=/home/step/test \ + smallstep/step-ca \ + && sleep 5 && docker exec stepca step ca provisioner add acme --type ACME \ && docker exec stepca kill -1 1 \ && docker exec stepca cat /home/step/certs/root_ca.crt | sudo bash -c "cat - >>/etc/ssl/certs/ca-certificates.crt" - name: Clone acmetest diff --git a/.github/workflows/Windows.yml b/.github/workflows/Windows.yml index 1de120f9..c02e2f77 100644 --- a/.github/workflows/Windows.yml +++ b/.github/workflows/Windows.yml @@ -29,10 +29,10 @@ jobs: CA_ECDSA: "" CA: "" CA_EMAIL: "" - TEST_PREFERRED_CHAIN: (STAGING) + TEST_PREFERRED_CHAIN: (STAGING) Pretend Pear X1 #- TEST_ACME_Server: "ZeroSSL.com" - # CA_ECDSA: "ZeroSSL ECC DV SSL CA 2" - # CA: "ZeroSSL RSA DV SSL CA 2" + # CA_ECDSA: "ZeroSSL ECC Domain Secure Site CA" + # CA: "ZeroSSL RSA Domain Secure Site CA" # CA_EMAIL: "githubtest@acme.sh" # TEST_PREFERRED_CHAIN: "" runs-on: windows-latest @@ -49,7 +49,7 @@ jobs: - name: Set git to use LF run: | git config --global core.autocrlf false - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - name: Install cygwin base packages with chocolatey run: | choco config get cacheLocation @@ -67,13 +67,6 @@ jobs: shell: cmd run: | echo "PATH=%PATH%" - - uses: anyvm-org/cf-tunnel@v0 - id: tunnel - with: - protocol: http - port: 80 - - name: Set envs - run: echo "TestingDomain=${{steps.tunnel.outputs.server}}" >> $GITHUB_ENV - name: Clone acmetest shell: cmd run: cd .. && git clone --depth=1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ diff --git a/.github/workflows/blacklist-command.yml b/.github/workflows/blacklist-command.yml deleted file mode 100644 index 35e53677..00000000 --- a/.github/workflows/blacklist-command.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: Blacklist Command - -# An issue titled "blacklist: " opened by the maintainer -# or a write-access member adds that identity to the Blacklist wiki page -# (see wiki-guard.yml) and closes the issue. The wiki-monitor notification -# embeds a prefilled link that opens such an issue in one click. - -on: - issues: - types: [opened] - -permissions: - contents: write - issues: write - -# Share the wiki-guard concurrency group so we never push to the wiki -# at the same time as the guard. -concurrency: - group: wiki-guard - cancel-in-progress: false - -jobs: - blacklist: - # Upstream only: forks have no .wiki repository to push to. - if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'blacklist:') - runs-on: ubuntu-latest - steps: - - name: Check authorization - id: auth - run: | - assoc="${{ github.event.issue.author_association }}" - case "$assoc" in - OWNER|MEMBER|COLLABORATOR) - echo "ok=true" >> "$GITHUB_OUTPUT" - ;; - *) - echo "issue author is not authorized ($assoc); ignoring" - echo "ok=false" >> "$GITHUB_OUTPUT" - ;; - esac - - - name: Checkout wiki repository - if: steps.auth.outputs.ok == 'true' - uses: actions/checkout@v7 - with: - repository: ${{ github.repository }}.wiki - path: wiki - - - name: Add the identity to the blacklist page - if: steps.auth.outputs.ok == 'true' - id: add - env: - TITLE: ${{ github.event.issue.title }} - run: | - target="$(printf '%s' "$TITLE" \ - | sed 's/^blacklist:[[:space:]]*//; s/^@//; s/[[:space:]].*$//' \ - | tr 'A-Z' 'a-z')" - case "$target" in - ''|*[!a-z0-9._+@-]*) - echo "invalid target: '$target'" - echo "result=invalid" >> "$GITHUB_OUTPUT" - exit 0 - ;; - esac - echo "target=$target" >> "$GITHUB_OUTPUT" - cd wiki - if [ ! -e Blacklist.md ]; then - echo "result=nopage" >> "$GITHUB_OUTPUT" - exit 0 - fi - if grep -Fxiq -- "- $target" Blacklist.md; then - echo "result=already" >> "$GITHUB_OUTPUT" - exit 0 - fi - if [ -n "$(tail -c1 Blacklist.md)" ]; then - echo >> Blacklist.md - fi - printf -- '- %s\n' "$target" >> Blacklist.md - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add Blacklist.md - git commit -m "blacklist $target (requested in #${{ github.event.issue.number }})" - git push origin HEAD || { git pull --rebase && git push origin HEAD; } - echo "result=added" >> "$GITHUB_OUTPUT" - - - name: Reply and close - if: steps.auth.outputs.ok == 'true' - uses: actions/github-script@v9 - env: - RESULT: ${{ steps.add.outputs.result }} - TARGET: ${{ steps.add.outputs.target }} - with: - script: | - const result = process.env.RESULT; - const target = process.env.TARGET; - const messages = { - added: `\`${target}\` has been added to the [Blacklist](https://github.com/${context.repo.owner}/${context.repo.repo}/wiki/Blacklist). The wiki guard will revert their recent wiki changes on its next run.`, - already: `\`${target}\` is already on the blacklist.`, - invalid: "Could not parse a valid login or email from the issue title.", - nopage: "The Blacklist wiki page does not exist." - }; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: messages[result] || "No action taken." - }); - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - state: "closed", - state_reason: result === "added" ? "completed" : "not_planned" - }); diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index 7dc42290..bd2c01aa 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -15,8 +15,6 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -env: - DOCKER_IMAGE: neilpang/acme.sh jobs: CheckToken: @@ -30,9 +28,9 @@ jobs: id: step_one run: | if [ "$DOCKER_PASSWORD" ] ; then - echo "hasToken=true" >>$GITHUB_OUTPUT + echo "::set-output name=hasToken::true" else - echo "hasToken=false" >>$GITHUB_OUTPUT + echo "::set-output name=hasToken::false" fi - name: Check the value run: echo ${{ steps.step_one.outputs.hasToken }} @@ -41,31 +39,20 @@ jobs: runs-on: ubuntu-latest needs: CheckToken if: "contains(needs.CheckToken.outputs.hasToken, 'true')" - permissions: - contents: read - packages: write steps: - name: checkout code - uses: actions/checkout@v6 - with: - persist-credentials: false + uses: actions/checkout@v3 - name: Set up QEMU - uses: docker/setup-qemu-action@v4 - - name: Extract Docker metadata - id: meta - uses: docker/metadata-action@v6 - with: - images: ${DOCKER_IMAGE} + uses: docker/setup-qemu-action@v1 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v4 + uses: docker/setup-buildx-action@v1 - name: login to docker hub run: | echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - - name: login to ghcr - run: | - echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin - name: build and push the image run: | + DOCKER_IMAGE=neilpang/acme.sh + if [[ $GITHUB_REF == refs/tags/* ]]; then DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/} fi @@ -79,22 +66,8 @@ jobs: fi fi - echo "DOCKER_IMAGE_TAG=${DOCKER_IMAGE_TAG}" >>"$GITHUB_ENV" - - DOCKER_LABELS=() - while read -r label; do - DOCKER_LABELS+=(--label "${label}") - done <<<"${DOCKER_METADATA_OUTPUT_LABELS}" - docker buildx build \ --tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \ - "${DOCKER_LABELS[@]}" \ --output "type=image,push=true" \ --build-arg AUTO_UPGRADE=${AUTO_UPGRADE} \ --platform linux/arm64/v8,linux/amd64,linux/arm/v6,linux/arm/v7,linux/386,linux/ppc64le,linux/s390x . - - name: mirror the image to ghcr (best-effort) - run: | - docker buildx imagetools create \ - --tag ghcr.io/${{ github.repository }}:${DOCKER_IMAGE_TAG} \ - ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \ - || echo "::warning::GHCR mirror failed; Docker Hub publish unaffected" diff --git a/.github/workflows/issue.yml b/.github/workflows/issue.yml index a25cd4ef..e92b0411 100644 --- a/.github/workflows/issue.yml +++ b/.github/workflows/issue.yml @@ -2,128 +2,18 @@ name: "Update issues" on: issues: types: [opened] - issue_comment: - types: [created] - pull_request_target: - types: [opened] - -permissions: - issues: write - pull-requests: write jobs: comment: runs-on: ubuntu-latest steps: - - uses: actions/github-script@v9 + - uses: actions/github-script@v6 with: script: | - const item = context.payload.issue || context.payload.pull_request; - - // Close on sight anything opened by a user on the wiki Blacklist - // page (maintained by the Wiki Guard workflow). - let blacklist = []; - try { - const res = await fetch(`https://raw.githubusercontent.com/wiki/${context.repo.owner}/${context.repo.repo}/Blacklist.md`); - if (res.ok) { - blacklist = (await res.text()).split("\n") - .filter(l => l.startsWith("- ")) - .map(l => l.slice(2).trim().toLowerCase()) - .filter(Boolean); - } - } catch (e) { - core.warning(`Failed to fetch the blacklist: ${e}`); - } - // A comment on a closed tracking issue reopens it (the standard - // closing note promises this). Bots, blacklisted users and the - // maintainer's own comments don't reopen. - if (context.eventName === "issue_comment") { - const issue = context.payload.issue; - const commenter = context.payload.comment.user; - if (issue.pull_request || issue.state !== "closed") { - return; - } - if (!/^report\s+(bugs?|issues?)\b/i.test(issue.title)) { - return; - } - if (commenter.type === "Bot" || - commenter.login.toLowerCase() === "neilpang" || - blacklist.includes(commenter.login.toLowerCase())) { - return; - } - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - state: "open" - }); - return; - } - - if (blacklist.includes(item.user.login.toLowerCase())) { - if (context.payload.pull_request) { - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: item.number, - state: "closed" - }); - } else { - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: item.number, - state: "closed", - state_reason: "not_planned" - }); - } - return; - } - - if (context.payload.pull_request) { - return; - } - - const issue = context.payload.issue; - if (issue.title.startsWith("blacklist:") || issue.title.startsWith("revert:")) { - // Handled by the Blacklist / Revert Command workflows. - return; - } - if (/^report\s+(bugs?|issues?)\b/i.test(issue.title)) { - // Tracking issue for a third-party dns/deploy/notify api: - // no upgrade boilerplate; assign it to the opener, label it, - // then close it right away to keep the issue list clean. Any - // later comment reopens it (see the issue_comment handler). - await github.rest.issues.addAssignees({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - assignees: [issue.user.login] - }); - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - labels: ["3rd party api"] - }); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - body: "Closing this tracking issue for now to keep the issue list clean. It remains the place to report problems with this provider -- if you hit a bug, comment here and the issue will be reopened." - }); - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: issue.number, - state: "closed", - state_reason: "completed" - }); - return; - } - await github.rest.issues.createComment({ - issue_number: issue.number, + 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. Before posting the log, review it and REDACT any secrets: private keys (`-----BEGIN ... PRIVATE KEY-----` blocks), API tokens and passwords." + 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." + }) \ No newline at end of file diff --git a/.github/workflows/pr_dns.yml b/.github/workflows/pr_dns.yml index 19763a15..5faa9105 100644 --- a/.github/workflows/pr_dns.yml +++ b/.github/workflows/pr_dns.yml @@ -4,6 +4,8 @@ on: pull_request_target: types: - opened + branches: + - 'dev' paths: - 'dnsapi/*.sh' @@ -11,9 +13,8 @@ on: jobs: welcome: runs-on: ubuntu-latest - if: github.actor != 'neilpang' steps: - - uses: actions/github-script@v9 + - uses: actions/github-script@v6 with: script: | await github.rest.issues.createComment({ @@ -21,14 +22,9 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: `**Welcome** - READ ME !!!!! - Read me !!!!!! - First thing: don't send PR to the master branch, please send to the dev branch instead. - Please read the [DNS API Dev Guide](../wiki/DNS-API-Dev-Guide). - You MUST pass the [DNS-API-Test](../wiki/DNS-API-Test). - Then reply on this message, otherwise, your code will not be reviewed or merged. - Please also make sure to add/update the usage here: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2 - 注意: 必须通过了 [DNS-API-Test](../wiki/DNS-API-Test) 才会被 review. 无论是修改, 还是新加的 dns api, 都必须确保通过这个测试. + 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 ✨ ` }) diff --git a/.github/workflows/pr_notify.yml b/.github/workflows/pr_notify.yml index 76ae76f6..4844e297 100644 --- a/.github/workflows/pr_notify.yml +++ b/.github/workflows/pr_notify.yml @@ -1,4 +1,4 @@ -name: Check notify api +name: Check dns api on: pull_request_target: @@ -13,9 +13,8 @@ on: jobs: welcome: runs-on: ubuntu-latest - if: github.actor != 'neilpang' steps: - - uses: actions/github-script@v9 + - uses: actions/github-script@v6 with: script: | await github.rest.issues.createComment({ @@ -23,7 +22,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, body: `**Welcome** - Please make sure you've read our [Code-of-conduct](../wiki/Code-of-conduct) and add the usage here: [notify](../wiki/notify). + 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 ✨ ` diff --git a/.github/workflows/revert-command.yml b/.github/workflows/revert-command.yml deleted file mode 100644 index 03161bbb..00000000 --- a/.github/workflows/revert-command.yml +++ /dev/null @@ -1,110 +0,0 @@ -name: Revert Command - -# An issue titled "revert: " opened by the maintainer or -# a write-access member reverts that commit in the wiki repository and -# closes the issue. The wiki-monitor notification embeds a prefilled link -# that opens such an issue in one click. - -on: - issues: - types: [opened] - -permissions: - contents: write - issues: write - -# Share the wiki-guard concurrency group so we never push to the wiki -# at the same time as the guard. -concurrency: - group: wiki-guard - cancel-in-progress: false - -jobs: - revert: - # Upstream only: forks have no .wiki repository to push to. - if: github.repository == 'acmesh-official/acme.sh' && startsWith(github.event.issue.title, 'revert:') - runs-on: ubuntu-latest - steps: - - name: Check authorization - id: auth - run: | - assoc="${{ github.event.issue.author_association }}" - case "$assoc" in - OWNER|MEMBER|COLLABORATOR) - echo "ok=true" >> "$GITHUB_OUTPUT" - ;; - *) - echo "issue author is not authorized ($assoc); ignoring" - echo "ok=false" >> "$GITHUB_OUTPUT" - ;; - esac - - - name: Checkout wiki repository - if: steps.auth.outputs.ok == 'true' - uses: actions/checkout@v7 - with: - repository: ${{ github.repository }}.wiki - path: wiki - fetch-depth: 0 - - - name: Revert the wiki commit - if: steps.auth.outputs.ok == 'true' - id: revert - env: - TITLE: ${{ github.event.issue.title }} - run: | - target="$(printf '%s' "$TITLE" \ - | sed 's/^revert:[[:space:]]*//; s/[[:space:]].*$//' \ - | tr 'A-Z' 'a-z')" - case "$target" in - *[!0-9a-f]*|"") - echo "invalid commit sha: '$target'" - echo "result=invalid" >> "$GITHUB_OUTPUT" - exit 0 - ;; - esac - echo "target=$target" >> "$GITHUB_OUTPUT" - cd wiki - if ! git cat-file -e "$target^{commit}" 2>/dev/null; then - echo "result=notfound" >> "$GITHUB_OUTPUT" - exit 0 - fi - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - if git revert --no-edit "$target"; then - git push origin HEAD || { git pull --rebase && git push origin HEAD; } - echo "result=reverted" >> "$GITHUB_OUTPUT" - else - git revert --abort || true - echo "result=conflict" >> "$GITHUB_OUTPUT" - fi - - - name: Reply and close - if: steps.auth.outputs.ok == 'true' - uses: actions/github-script@v9 - env: - RESULT: ${{ steps.revert.outputs.result }} - TARGET: ${{ steps.revert.outputs.target }} - with: - script: | - const result = process.env.RESULT; - const target = process.env.TARGET; - const messages = { - reverted: `Wiki commit \`${target}\` has been reverted.`, - conflict: `Reverting \`${target}\` conflicts with later edits; please revert manually from the page history.`, - notfound: `Commit \`${target}\` was not found in the wiki repository.`, - invalid: "Could not parse a commit sha from the issue title." - }; - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: messages[result] || "No action taken." - }); - await github.rest.issues.update({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - state: "closed", - state_reason: result === "reverted" ? "completed" : "not_planned" - }); diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index eb10b2b0..a5a08bbf 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -22,7 +22,7 @@ jobs: ShellCheck: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - name: Install Shellcheck run: sudo apt-get install -y shellcheck - name: DoShellcheck @@ -31,7 +31,7 @@ jobs: shfmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v3 - name: Install shfmt run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt - name: shfmt diff --git a/.github/workflows/vtag.yml b/.github/workflows/vtag.yml deleted file mode 100644 index e9f7e8df..00000000 --- a/.github/workflows/vtag.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Mirror version tag - -# Historical release tags are plain version numbers ("3.1.3") and cannot be -# renamed. When a plain version tag is pushed (including the tag created by -# publishing a GitHub release), mirror it as a "v"-prefixed tag ("v3.1.3") -# pointing to the same object, so both forms exist. -# No retrigger loop: the tag filter never matches a "v"-prefixed tag, and -# refs created with GITHUB_TOKEN do not fire workflows anyway. - -on: - push: - tags: - - '[0-9]*' - -permissions: - contents: write - -jobs: - vtag: - if: github.repository == 'acmesh-official/acme.sh' - runs-on: ubuntu-latest - steps: - - name: Create the v-prefixed tag - env: - GH_TOKEN: ${{ github.token }} - run: | - if gh api "repos/${{ github.repository }}/git/ref/tags/v${{ github.ref_name }}" >/dev/null 2>&1; then - echo "Tag v${{ github.ref_name }} already exists, nothing to do." - exit 0 - fi - gh api "repos/${{ github.repository }}/git/refs" -f ref="refs/tags/v${{ github.ref_name }}" -f sha="${{ github.sha }}" - echo "Created tag v${{ github.ref_name }} -> ${{ github.sha }}" diff --git a/.github/workflows/wiki-guard.yml b/.github/workflows/wiki-guard.yml deleted file mode 100644 index dbe3dd8d..00000000 --- a/.github/workflows/wiki-guard.yml +++ /dev/null @@ -1,325 +0,0 @@ -name: Wiki Guard - -# Rules enforced here: -# - Only the maintainer and write-access members may delete or rename wiki -# pages. Anyone else doing so gets blacklisted and the page restored to -# its last good revision. -# - Only the maintainer and write-access members may edit the Blacklist -# wiki page. Anyone else touching it gets blacklisted and the page -# reverted. -# - Any wiki change made by a blacklisted identity is reverted. -# A "good" revision is one authored by the maintainer, by this bot, or by -# a non-blacklisted user -- restoring from the deleted commit's parent is -# NOT safe, because vandals replace a page before destroying it and the -# parent would launder their version into a bot commit. -# The gollum event only fires on page create/update, never on deletion, -# so violations are caught by polling the wiki git history. - -on: - schedule: - - cron: "*/10 * * * *" - gollum: - # Piggyback on frequent repo activity, because the cron schedule is - # best-effort and often delayed well beyond its interval. - issues: - types: [opened] - issue_comment: - types: [created] - workflow_dispatch: - -permissions: - contents: write - issues: write - -concurrency: - group: wiki-guard - cancel-in-progress: false - -jobs: - guard: - # Forks have no .wiki repository, so the checkout below would - # fail there -- run only in the upstream repository. - if: github.repository == 'acmesh-official/acme.sh' - runs-on: ubuntu-latest - steps: - - name: Checkout wiki repository - uses: actions/checkout@v7 - with: - repository: ${{ github.repository }}.wiki - path: wiki - fetch-depth: 0 - - - name: Enforce wiki rules - id: guard - env: - # WIKI_GUARD_TOKEN: a PAT with read:org, needed to enumerate - # members whose write access comes via the organization -- the - # repo-scoped GITHUB_TOKEN only sees direct collaborators. - GH_TOKEN: ${{ secrets.WIKI_GUARD_TOKEN || secrets.GITHUB_TOKEN }} - run: | - # Logins with write (push) access to the repository, including - # organization members -- they may delete/rename pages and edit - # the blacklist just like the maintainer. If the API call fails, - # the list stays empty and enforcement falls back to - # maintainer-only, which is the safe direction. - gh api "repos/${GITHUB_REPOSITORY}/collaborators?per_page=100" --paginate \ - -q '.[] | select(.permissions.push) | .login' 2>/dev/null \ - | tr 'A-Z' 'a-z' | sort -u > writers.txt || true - echo "write-access members loaded: $(wc -l < writers.txt)" - cd wiki - git config core.quotePath false - - # Any author email under this domain is the maintainer. - OWNER_DOMAIN="neilpang.com" - # Our own enforcement commits. - BOT_EMAIL="41898282+github-actions[bot]@users.noreply.github.com" - BL_PAGE="Blacklist.md" - # Rolling window; the cron runs every 10 minutes, so 7 days gives - # ample overlap without re-judging old changes the maintainer - # already accepted. - WINDOW="7 days ago" - - : > ../actions.txt - : > ../bl_new.txt - - is_owner() { - case "$1" in - *@"$OWNER_DOMAIN") return 0 ;; - esac - return 1 - } - - is_bot() { - [ "$1" = "$BOT_EMAIL" ] - } - - author_email() { - git show -s --format=%ae "$1" | tr 'A-Z' 'a-z' - } - - # Identity of a commit author: the GitHub login when the email is a - # users.noreply.github.com address, otherwise the email itself. - identity_of() { - case "$1" in - *+*@users.noreply.github.com) - printf '%s\n' "$1" | sed 's/^[^+]*+//; s/@users\.noreply\.github\.com$//' - ;; - *@users.noreply.github.com) - printf '%s\n' "$1" | sed 's/@users\.noreply\.github\.com$//' - ;; - *) - printf '%s\n' "$1" - ;; - esac - } - - is_blacklisted() { - grep -Fxq "$1" ../bl_all.txt - } - - # Trusted committers: the maintainer (by email domain), this bot, - # and anyone whose GitHub login has write access to the repo. - is_trusted() { - if is_owner "$1" || is_bot "$1"; then - return 0 - fi - grep -Fxq "$(identity_of "$1")" ../writers.txt - } - - # Newest commit on file $1 authored by a non-blacklisted user. - last_good_for() { - for g in $(git log --format=%H --no-renames -- "$1"); do - gae="$(author_email "$g")" - if is_trusted "$gae"; then - printf '%s\n' "$g" - return 0 - fi - gid="$(identity_of "$gae")" - if ! is_blacklisted "$gid" && ! is_blacklisted "$gae"; then - printf '%s\n' "$g" - return 0 - fi - done - return 0 - } - - if [ -e "$BL_PAGE" ]; then - page_existed=1 - else - page_existed="" - fi - - # ---- 1. Last good version of the blacklist page: the newest - # revision authored by the maintainer or by this bot. Everything - # else on that page is tampering and is discarded. - bl_good_commit="" - for c in $(git log --format=%H --no-renames -- "$BL_PAGE"); do - ae="$(author_email "$c")" - if is_trusted "$ae"; then - bl_good_commit="$c" - break - fi - done - if [ -n "$bl_good_commit" ] && git cat-file -e "$bl_good_commit:$BL_PAGE" 2>/dev/null; then - git show "$bl_good_commit:$BL_PAGE" > ../bl_page.txt - else - { - echo "# Blacklist" - echo "" - echo "Users listed below violated the wiki rules (deleted or renamed" - echo "pages, or tampered with this page). Their new issues and pull" - echo "requests are closed on sight and their wiki edits are reverted" - echo "automatically. Only the maintainer and write-access members" - echo "may edit this page." - echo "" - echo "To pardon a user while their violation is still inside the" - echo "scan window, replace their entry with: pardon: username" - echo "" - } > ../bl_page.txt - fi - sed -n 's/^- *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_good.txt - sed -n 's/^[Pp]ardon: *//p' ../bl_page.txt | tr -d '\r' | tr 'A-Z' 'a-z' | sort -u > ../bl_pardon.txt - - bl_add() { - if grep -Fxq "$1" ../bl_pardon.txt; then - return 0 - fi - if ! grep -Fxq "$1" ../bl_good.txt && ! grep -Fxq "$1" ../bl_new.txt; then - printf '%s\n' "$1" >> ../bl_new.txt - printf '%s\n' "- blacklisted \`$1\`: $2" >> ../actions.txt - fi - } - - # ---- 2. Blacklist everyone who deleted or renamed a page. - # --no-renames makes a rename count as a deletion of the old path. - for c in $(git log --since="$WINDOW" --diff-filter=D --no-renames --format=%H); do - ae="$(author_email "$c")" - if is_trusted "$ae"; then - continue - fi - an="$(git show -s --format=%an "$c")" - bl_add "$(identity_of "$ae")" "deleted or renamed pages in $c ($an <$ae>)" - done - - # ---- 3. Blacklist everyone else who touched the blacklist page. - # The revert of their tampering falls out of steps 5 and 6. - for c in $(git log --since="$WINDOW" --format=%H --no-renames -- "$BL_PAGE"); do - ae="$(author_email "$c")" - if is_trusted "$ae"; then - continue - fi - an="$(git show -s --format=%an "$c")" - bl_add "$(identity_of "$ae")" "tampered with \`$BL_PAGE\` in $c ($an <$ae>)" - done - - sort -u ../bl_new.txt > ../bl_new_u.txt - cat ../bl_good.txt ../bl_new_u.txt | sort -u > ../bl_all.txt - - # ---- 4. Restore pages that are currently missing because a - # non-maintainer deleted them, using the last good revision. - git log --since="$WINDOW" --diff-filter=D --no-renames --name-only --format= \ - | sort -u \ - | while IFS= read -r f; do - if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ] || [ -e "$f" ]; then - continue - fi - del="$(git log -1 --diff-filter=D --no-renames --format=%H -- "$f")" - if [ -z "$del" ]; then - continue - fi - ae="$(author_email "$del")" - if is_trusted "$ae"; then - continue - fi - good="$(last_good_for "$f")" - if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then - git checkout "$good" -- "$f" - printf '%s\n' "- restored \`$f\` (deleted in $del) from its last good revision $good" >> ../actions.txt - fi - done - - # ---- 5. Revert every recent change made by a blacklisted - # identity: each touched file goes back to its newest revision - # authored by a non-blacklisted user; a file that has no such - # revision (they created it) is removed. - if [ -s ../bl_all.txt ]; then - for c in $(git log --since="$WINDOW" --format=%H --no-renames); do - ae="$(author_email "$c")" - if is_trusted "$ae"; then - continue - fi - id="$(identity_of "$ae")" - if ! is_blacklisted "$id" && ! is_blacklisted "$ae"; then - continue - fi - git show --name-only --no-renames --format= "$c" \ - | while IFS= read -r f; do - if [ -z "$f" ] || [ "$f" = "$BL_PAGE" ]; then - continue - fi - good="$(last_good_for "$f")" - if [ -n "$good" ] && git cat-file -e "$good:$f" 2>/dev/null; then - want="$(git rev-parse "$good:$f")" - have="$(git hash-object -- "$f" 2>/dev/null || echo missing)" - if [ "$want" != "$have" ]; then - git checkout "$good" -- "$f" - printf '%s\n' "- reverted \`$f\` to its last good revision $good (undoing change by \`$id\` in $c)" >> ../actions.txt - fi - elif [ -e "$f" ]; then - git rm -q -- "$f" - printf '%s\n' "- removed \`$f\` created by blacklisted \`$id\` in $c" >> ../actions.txt - fi - done - done - fi - - # ---- 6. Regenerate the blacklist page: the last good text plus - # any newly blacklisted identities. This both reverts tampering - # and records new violators; manual edits by the maintainer are - # preserved as the new good text. - cp ../bl_page.txt ../bl_page_new.txt - if [ -s ../bl_page_new.txt ] && [ -n "$(tail -c1 ../bl_page_new.txt)" ]; then - echo >> ../bl_page_new.txt - fi - while IFS= read -r id; do - if [ -n "$id" ] && ! grep -Fxiq -- "- $id" ../bl_page_new.txt; then - printf -- '- %s\n' "$id" >> ../bl_page_new.txt - fi - done < ../bl_new_u.txt - if ! cmp -s ../bl_page_new.txt "$BL_PAGE" 2>/dev/null; then - cp ../bl_page_new.txt "$BL_PAGE" - git add -- "$BL_PAGE" - if [ -n "$page_existed" ] || [ -s ../bl_new_u.txt ]; then - printf '%s\n' "- updated \`$BL_PAGE\`" >> ../actions.txt - fi - fi - - # ---- 7. Commit, push, notify. - if [ -n "$(git status --porcelain)" ]; then - git config user.name "github-actions[bot]" - git config user.email "$BOT_EMAIL" - git commit -m "wiki-guard: restore pages and enforce blacklist" - git push origin HEAD || { git pull --rebase && git push origin HEAD; } - fi - if [ -s ../actions.txt ]; then - { - echo "The wiki guard handled the following rule violations:" - echo "" - cat ../actions.txt - echo "" - echo "Blacklist: https://github.com/${GITHUB_REPOSITORY}/wiki/Blacklist" - echo "Wiki: https://github.com/${GITHUB_REPOSITORY}/wiki" - } > ../guard-msg.txt - echo "acted=true" >> "$GITHUB_OUTPUT" - else - echo "No rule violations found." - echo "acted=false" >> "$GITHUB_OUTPUT" - fi - - - name: Create issue to notify Neilpang - if: steps.guard.outputs.acted == 'true' - uses: peter-evans/create-issue-from-file@v6 - with: - title: "Wiki guard: rule violations handled" - content-filepath: ./guard-msg.txt - assignees: Neilpang diff --git a/.github/workflows/wiki-monitor.yml b/.github/workflows/wiki-monitor.yml deleted file mode 100644 index 89bb1f3a..00000000 --- a/.github/workflows/wiki-monitor.yml +++ /dev/null @@ -1,82 +0,0 @@ -name: Notify via Issue on Wiki Edit - -on: - gollum: - -jobs: - notify: - runs-on: ubuntu-latest - if: github.actor != 'neilpang' - steps: - - name: Checkout wiki repository - uses: actions/checkout@v7 - with: - repository: ${{ github.repository }}.wiki - path: wiki - fetch-depth: 0 - - - name: Generate wiki change message - id: msg - run: | - actor="${{ github.actor }}" - sender_url=$(jq -r '.sender.html_url' "$GITHUB_EVENT_PATH") - page_name=$(jq -r '.pages[0].page_name' "$GITHUB_EVENT_PATH") - page_sha=$(jq -r '.pages[0].sha' "$GITHUB_EVENT_PATH") - page_url=$(jq -r '.pages[0].html_url' "$GITHUB_EVENT_PATH") - page_action=$(jq -r '.pages[0].action' "$GITHUB_EVENT_PATH") - page_summary=$(jq -r '.pages[0].summary' "$GITHUB_EVENT_PATH") - now="$(date '+%Y-%m-%d %H:%M:%S')" - - cd wiki - # Skip notification when the change was authored by the - # maintainer himself (any author email under neilpang.com), - # e.g. a direct git push to the wiki repository. - author_email=$(git show -s --format=%ae "$page_sha" 2>/dev/null | tr 'A-Z' 'a-z') - case "$author_email" in - *@neilpang.com) - echo "Change authored by maintainer ($author_email); skipping notification." - echo "notify=false" >> "$GITHUB_OUTPUT" - exit 0 - ;; - esac - prev_sha=$(git rev-list $page_sha^ -- "$page_name.md" | head -n 1) - if [ -n "$prev_sha" ]; then - git diff $prev_sha $page_sha -- "$page_name.md" > ../wiki.diff || echo "(No diff found)" > ../wiki.diff - else - echo "(no diff)" > ../wiki.diff - fi - cd .. - { - echo "Wiki edited" - echo -n "User: " - echo "@$actor [$actor]($sender_url)" - echo "Time: $now" - echo "Page: [$page_name]($page_url) (Action: $page_action)" - echo "Comment: $page_summary" - echo "[Click here to Revert](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=revert%3A+${page_sha}&body=Revert+wiki+commit+${page_sha}+by+@${actor}.)" - echo "" - echo "[Click here to Blacklist @$actor](https://github.com/${GITHUB_REPOSITORY}/issues/new?title=blacklist%3A+${actor}&body=Blacklist+@${actor},+requested+from+the+wiki+monitor.)" - echo "" - echo "----" - echo "### diff:" - echo '```diff' - cat wiki.diff - echo '```' - } > wiki-change-msg.txt - echo "notify=true" >> "$GITHUB_OUTPUT" - - - name: Create issue to notify Neilpang - if: steps.msg.outputs.notify == 'true' - uses: peter-evans/create-issue-from-file@v6 - with: - title: "Wiki edited" - content-filepath: ./wiki-change-msg.txt - assignees: Neilpang - env: - TZ: Asia/Shanghai - - - - - - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 33294ce7..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,8 +0,0 @@ -# Contributing - -1. Do NOT send pull request to `master` branch. -Please send to `dev` branch instead. -Any PR to `master` branch will NOT be merged. - -2. For dns api support, read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide -You will NOT get any review without passing this guide. You also need to fix the CI errors. diff --git a/Dockerfile b/Dockerfile index 229e4830..79fd1d89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.23 +FROM alpine:3.16.3 RUN apk --no-cache add -f \ openssl \ @@ -12,33 +12,20 @@ RUN apk --no-cache add -f \ oath-toolkit-oathtool \ tar \ libidn \ - jq \ - yq-go \ - supercronic + jq -ENV LE_WORKING_DIR=/acmebin - -ENV LE_CONFIG_HOME=/acme.sh - -ENV HOME=/acme.sh +ENV LE_CONFIG_HOME /acme.sh ARG AUTO_UPGRADE=1 -ENV AUTO_UPGRADE=$AUTO_UPGRADE +ENV AUTO_UPGRADE $AUTO_UPGRADE #Install -COPY ./acme.sh /install_acme.sh/acme.sh -COPY ./deploy /install_acme.sh/deploy -COPY ./dnsapi /install_acme.sh/dnsapi -COPY ./notify /install_acme.sh/notify - -RUN addgroup -g 1000 acme && adduser -h $LE_CONFIG_HOME -s /bin/sh -G acme -D -H -u 1000 acme - +COPY ./ /install_acme.sh/ RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/ -RUN ln -s $LE_WORKING_DIR/acme.sh /usr/local/bin/acme.sh -RUN chown -R acme:acme $LE_CONFIG_HOME +RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh && crontab -l | grep acme.sh | sed 's#> /dev/null##' | crontab - RUN for verb in help \ version \ @@ -72,23 +59,17 @@ RUN for verb in help \ set-default-ca \ set-default-chain \ ; do \ - printf -- "%b" "#!/usr/bin/env sh\n$LE_WORKING_DIR/acme.sh --${verb} --config-home $LE_CONFIG_HOME \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \ + printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \ ; done RUN printf "%b" '#!'"/usr/bin/env sh\n \ if [ \"\$1\" = \"daemon\" ]; then \n \ - if [ ! -f \"\$LE_CONFIG_HOME/crontab\" ]; then \n \ - echo \"\$LE_CONFIG_HOME/crontab not found, generating one\" \n \ - time=\$(date -u \"+%s\") \n \ - random_minute=\$((\$time % 60)) \n \ - random_hour=\$((\$time / 60 % 6)) \n \ - echo \"\$random_minute \$random_hour,\$((\$random_hour + 6)),\$((\$random_hour + 12)),\$((\$random_hour + 18)) * * * \\\"\$LE_WORKING_DIR\\\"/acme.sh --cron --home \\\"\$LE_WORKING_DIR\\\" --config-home \\\"\$LE_CONFIG_HOME\\\"\" > \"\$LE_CONFIG_HOME\"/crontab \n \ - fi \n \ - echo \"Running Supercronic using crontab at \$LE_CONFIG_HOME/crontab\" \n \ - exec -- /usr/bin/supercronic \"\$LE_CONFIG_HOME/crontab\" \n \ + trap \"echo stop && killall crond && exit 0\" SIGTERM SIGINT \n \ + crond && sleep infinity &\n \ + wait \n \ else \n \ exec -- \"\$@\"\n \ -fi\n" >/entry.sh && chmod +x /entry.sh && chmod -R o+rwx $LE_WORKING_DIR && chmod -R o+rwx $LE_CONFIG_HOME +fi" >/entry.sh && chmod +x /entry.sh VOLUME /acme.sh diff --git a/README.md b/README.md index 90280e94..30e6e554 100644 --- a/README.md +++ b/README.md @@ -1,107 +1,68 @@ -

- - - - - - - - ZeroSSL - - -

+# An ACME Shell script: acme.sh -

🔐 acme.sh

-

An ACME Protocol Client Written Purely in Shell

- -

- FreeBSD - OpenBSD - NetBSD - MacOS - Ubuntu - Windows - Solaris - DragonFlyBSD - MidnightBSD - GhostBSD - Omnios - OpenIndiana - Tribblix - Haiku - Hurd - OpenEuler -

- -

- Shellcheck - PebbleStrict - DockerHub -

- -

- Financial Contributors on Open Collective - Join the chat at Gitter - Docker stars - Docker pulls -

+[![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) +![DockerHub](https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg) -## ✨ Features -- 🐚 An ACME protocol client written **purely in Shell** (Unix shell) language -- 📜 Full ACME protocol implementation -- 🔑 Support **ECDSA** certificates -- 🌐 Support **SAN** and **wildcard** certificates -- ⚡ Simple, powerful and very easy to use — only **3 minutes** to learn! -- 🔧 Compatible with **Bash**, **dash** and **sh** -- 🚫 No dependencies on Python -- 🔄 One script to issue, renew and install your certificates automatically -- 👤 **DOES NOT** require `root/sudoer` access -- 🐳 Docker ready -- 🌍 IPv6 ready -- 📧 Cron job notifications for renewal or error + +[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Docker stars](https://img.shields.io/docker/stars/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") +[![Docker pulls](https://img.shields.io/docker/pulls/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") -> 💡 It's probably the **easiest & smartest** shell script to automatically issue & renew free certificates. -

- 📚 Wiki • - 🐳 Docker Guide • - 🐦 Twitter -

---- +- An ACME protocol client written purely in Shell (Unix shell) language. +- Full ACME protocol implementation. +- Support ECDSA certs +- Support SAN and wildcard certs +- Simple, powerful and very easy to use. You only need 3 minutes to learn it. +- Bash, dash and sh compatible. +- Purely written in Shell with no dependencies on python. +- Just one script to issue, renew and install your certificates automatically. +- DOES NOT require `root/sudoer` access. +- Docker ready +- IPv6 ready +- Cron job notifications for renewal or error etc. -## 🌏 [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E) +It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates. ---- +Wiki: https://github.com/acmesh-official/acme.sh/wiki -## 🏆 Who Uses acme.sh? +For Docker Fans: [acme.sh :two_hearts: Docker ](https://github.com/acmesh-official/acme.sh/wiki/Run-acme.sh-in-docker) + +Twitter: [@neilpangxa](https://twitter.com/neilpangxa) + + +# [中文说明](https://github.com/acmesh-official/acme.sh/wiki/%E8%AF%B4%E6%98%8E) + +# Who: - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) - [ruby-china.org](https://ruby-china.org/topics/31983) - [Proxmox](https://pve.proxmox.com/wiki/Certificate_Management) - [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) +- [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) - [Centminmod](https://centminmod.com/letsencrypt-acmetool-https.html) - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) +- [archlinux](https://www.archlinux.org/packages/community/any/acme.sh) - [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) -- [CentOS Web Panel](https://control-webpanel.com) +- [CentOS Web Panel](http://centos-webpanel.com/) - [lnmp.org](https://lnmp.org/) - [more...](https://github.com/acmesh-official/acme.sh/wiki/Blogs-and-tutorials) ---- - -## 🖥️ Tested OS +# Tested OS | NO | Status| Platform| |----|-------|---------| @@ -114,83 +75,66 @@ |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|[![MidnightBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/MidnightBSD.yml)|MidnightBSD -|11|[![Omnios](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Omnios.yml)|Omnios -|12|[![OpenIndiana](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenIndiana.yml)|OpenIndiana -|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)| Debian -|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)|openSUSE -|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)|Alpine Linux (with curl) -|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)|Archlinux -|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)|fedora -|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)|Kali Linux -|19|[![Linux](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Linux.yml)|Oracle Linux -|20|[![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 -|21|[![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 -|22|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 -|23|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) -|24|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management) -|25|[![Haiku](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Haiku.yml)|Haiku OS -|26|[![Tribblix](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Tribblix.yml)|Tribblix -|27|[![GhostBSD](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/GhostBSD.yml)|GhostBSD -|28|[![Hurd](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/Hurd.yml)|GNU Hurd -|29|[![OpenEuler](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml/badge.svg)](https://github.com/acmesh-official/acme.sh/actions/workflows/OpenEuler.yml)|openEuler +|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) -> -> 🖥️ The testing VMs are supported by [vmactions.org](https://vmactions.org) +Check our [testing project](https://github.com/acmesh-official/acmetest): ---- +https://github.com/acmesh-official/acmetest -## 🏛️ Supported CA +# Supported CA -| CA | Status | -|---|---| -| [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA) | ⭐ **Default** | -| Letsencrypt.org CA | ✅ Supported | -| [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) | ✅ Supported | -| [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) | ✅ Supported | -| [Actalis.com CA](https://github.com/acmesh-official/acme.sh/wiki/Actalis.com-CA) | ✅ Supported | -| [Pebble strict Mode](https://github.com/letsencrypt/pebble) | ✅ Supported | -| Any [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA | ✅ Supported | +- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA)(default) +- Letsencrypt.org CA +- [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA) +- [SSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/SSL.com-CA) +- [Google.com Public CA](https://github.com/acmesh-official/acme.sh/wiki/Google-Public-CA) +- [Pebble strict Mode](https://github.com/letsencrypt/pebble) +- Any other [RFC8555](https://tools.ietf.org/html/rfc8555)-compliant CA ---- +# Supported modes -## ⚙️ Supported Modes +- Webroot mode +- Standalone mode +- Standalone tls-alpn mode +- Apache mode +- Nginx mode +- DNS mode +- [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) +- [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) -| Mode | Description | -|------|-------------| -| 📁 Webroot mode | Use existing webroot directory | -| 🖥️ Standalone mode | Built-in webserver on port 80 | -| 🔐 Standalone tls-alpn mode | Built-in webserver on port 443 | -| 🪶 Apache mode | Use Apache for verification | -| ⚡ Nginx mode | Use Nginx for verification | -| 🌐 DNS mode | Use DNS TXT records | -| 🔗 [DNS alias mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode) | Use DNS alias for verification | -| 📡 [Stateless mode](https://github.com/acmesh-official/acme.sh/wiki/Stateless-Mode) | Stateless verification | -| 📌 [DNS persist mode](https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode) | Persistent DNS TXT record ([draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/)) | ---- +# 1. How to install -## 📖 Usage Guide +### 1. Install online -### 1️⃣ How to Install - -#### 📥 Install Online - -> Check this project: https://github.com/acmesh-official/get.acme.sh +Check this project: https://github.com/acmesh-official/get.acme.sh ```bash curl https://get.acme.sh | sh -s email=my@example.com ``` -**Or:** +Or: ```bash wget -O - https://get.acme.sh | sh -s email=my@example.com ``` -#### 📦 Install from Git + +### 2. Or, Install from git Clone this project and launch installation: @@ -200,11 +144,11 @@ cd ./acme.sh ./acme.sh --install -m my@example.com ``` -> 💡 You `don't have to be root` then, although `it is recommended`. +You `don't have to be root` then, although `it is recommended`. -📚 **Advanced Installation:** https://github.com/acmesh-official/acme.sh/wiki/How-to-install +Advanced Installation: https://github.com/acmesh-official/acme.sh/wiki/How-to-install -**The installer will perform 3 actions:** +The installer will perform 3 actions: 1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. All certs will be placed in this folder too. @@ -217,19 +161,17 @@ Cron entry example: 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null ``` -> ⚠️ After the installation, you must close the current terminal and reopen it to make the alias take effect. +After the installation, you must close the current terminal and reopen it to make the alias take effect. -✅ **You are ready to issue certs now!** +Ok, you are ready to issue certs now. -**Show help message:** +Show help message: ```sh -acme.sh -h +root@v1:~# acme.sh -h ``` ---- - -### 2️⃣ Issue a Certificate +# 2. Just issue a cert **Example 1:** Single domain. @@ -264,21 +206,17 @@ You must point and bind all the domains to the same webroot dir: `/home/wwwroot/ The certs will be placed in `~/.acme.sh/example.com/` -> 🔄 The certs will be renewed automatically every **30** days. +The certs will be renewed automatically every **60** days. -> 🔐 The certs will default to **ECC** certificates. +More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert -📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert ---- - -### 3️⃣ Install the Certificate to Apache/Nginx +# 3. Install the cert to Apache/Nginx etc. After the cert is generated, you probably want to install/copy the cert to your Apache/Nginx or other servers. +You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future. -> ⚠️ **IMPORTANT:** You **MUST** use this command to copy the certs to the target files. **DO NOT** use the certs files in `~/.acme.sh/` folder — they are for internal use only, the folder structure may change in the future. - -#### 🪶 Apache Example: +**Apache** example: ```bash acme.sh --install-cert -d example.com \ --cert-file /path/to/certfile/in/apache/cert.pem \ @@ -287,7 +225,7 @@ acme.sh --install-cert -d example.com \ --reloadcmd "service apache2 force-reload" ``` -#### ⚡ Nginx Example: +**Nginx** example: ```bash acme.sh --install-cert -d example.com \ --key-file /path/to/keyfile/in/nginx/key.pem \ @@ -301,89 +239,91 @@ The ownership and permission info of existing files are preserved. You can pre-c Install/copy the cert/key to the production Apache or Nginx path. -> 🔄 The cert will be renewed every **30** days by default (configurable). Once renewed, the Apache/Nginx service will be reloaded automatically. +The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. -> ⚠️ **IMPORTANT:** The `reloadcmd` is very important. The cert can be automatically renewed, but without a correct `reloadcmd`, the cert may not be flushed to your server (like nginx or apache), then your website will not be able to show the renewed cert. ---- +**Please take care: The reloadcmd is very important. The cert can be automatically renewed, but, without a correct 'reloadcmd' the cert may not be flushed to your server(like nginx or apache), then your website will not be able to show renewed cert in 60 days.** -### 4️⃣ Use Standalone Server to Issue Certificate +# 4. Use Standalone server to issue cert -> 🔐 Requires root/sudoer or permission to listen on port **80** (TCP) +**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** -> ⚠️ Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. +Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com ``` -📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert ---- +# 5. Use Standalone ssl server to issue cert -### 5️⃣ Use Standalone TLS Server to Issue Certificate +**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** -> 🔐 Requires root/sudoer or permission to listen on port **443** (TCP) - -> ⚠️ Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. +Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --alpn -d example.com -d www.example.com -d cp.example.com ``` -📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert ---- -### 6️⃣ Use Apache Mode +# 6. Use Apache mode -> 🔐 Requires root/sudoer to interact with Apache server +**(requires you to be root/sudoer, since it is required to interact with Apache server)** If you are running a web server, it is recommended to use the `Webroot mode`. Particularly, if you are running an Apache server, you can use Apache mode instead. This mode doesn't write any files to your web root folder. +Just set string "apache" as the second argument and it will force use of apache plugin automatically. + ```sh acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com ``` -> 💡 **Note:** This Apache mode is only to issue the cert, it will **not** change your Apache config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Apache server, don't worry! +**This apache mode is only to issue the cert, it will not change your apache config files. +You will need to configure your website config files to use the cert by yourself. +We don't want to mess with your apache server, don't worry.** -📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert ---- +# 7. Use Nginx mode -### 7️⃣ Use Nginx Mode - -> 🔐 Requires root/sudoer to interact with Nginx server +**(requires you to be root/sudoer, since it is required to interact with Nginx server)** If you are running a web server, it is recommended to use the `Webroot mode`. -Particularly, if you are running an Nginx server, you can use Nginx mode instead. This mode doesn't write any files to your web root folder. +Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder. -It will configure Nginx server automatically to verify the domain and then restore the Nginx config to the original version. So, the config is not changed. +Just set string "nginx" as the second argument. + +It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version. + +So, the config is not changed. ```sh acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com ``` -> 💡 **Note:** This Nginx mode is only to issue the cert, it will **not** change your Nginx config files. You will need to configure your website config files to use the cert by yourself. We don't want to mess with your Nginx server, don't worry! +**This nginx mode is only to issue the cert, it will not change your nginx config files. +You will need to configure your website config files to use the cert by yourself. +We don't want to mess with your nginx server, don't worry.** -📚 **More examples:** https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert +More examples: https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert ---- - -### 8️⃣ Automatic DNS API Integration +# 8. Automatic DNS API integration If your DNS provider supports API access, we can use that API to automatically issue the certs. -> ✨ **You don't have to do anything manually!** +You don't have to do anything manually! -📚 **Currently acme.sh supports most DNS providers:** https://github.com/acmesh-official/acme.sh/wiki/dnsapi +### Currently acme.sh supports most of the dns providers: ---- +https://github.com/acmesh-official/acme.sh/wiki/dnsapi -### 9️⃣ Use DNS Manual Mode +# 9. Use DNS manual mode: See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first. @@ -413,154 +353,72 @@ Then just rerun with `renew` argument: acme.sh --renew -d example.com ``` -✅ **Done!** +Ok, it's done. -> ⚠️ **WARNING:** This is DNS manual mode — it **cannot** be renewed automatically. You will have to add a new TXT record to your domain manually when you renew your cert. **Please use DNS API mode instead.** +**Take care, this is dns manual mode, it can not be renewed automatically. you will have to add a new txt record to your domain by your hand when you renew your cert.** ---- +**Please use dns api mode instead.** -### 🔟 Use DNS Persist Mode +# 10. Issue ECC certificates -📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/DNS-persist-mode +`Let's Encrypt` can now issue **ECDSA** certificates. -📚 Spec: [draft-ietf-acme-dns-persist-01](https://datatracker.ietf.org/doc/draft-ietf-acme-dns-persist/) +And we support them too! -DNS persist mode lets you place a **single, long‑lived `_validation-persist` TXT record** in your zone and reuse it for every subsequent issuance and renewal. There is no per-issuance challenge token, so renewals require **no DNS edits** — useful when DNS API access is not available but you still want unattended renewals. +Just set the `keylength` parameter with a prefix `ec-`. -#### 🪄 Step 1: Print the TXT record value +For example: + +### Single domain ECC certificate ```bash -acme.sh --make-dns-persist-value -d example.com [--server letsencrypt] [--dns-persist-wildcard] [--dns-persist-ca-name "sectigo.com"] [--dns-persist-days 365] +acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 ``` -Options: +### SAN multi domain ECC certificate -| Flag | Description | -|------|-------------| -| `--server ` | Pick the CA (default is your configured default). The account is registered automatically if you have not used this CA before. | -| `--dns-persist-wildcard` | Adds `policy=wildcard` to the record so it also authorizes wildcard / subdomain certs. | -| `--dns-persist-ca-name ` | Use a specific CA identity domain (e.g. `sectigo.com`). If omitted, identities are read from the ACME directory's `caaIdentities` field and one record per identity is printed — you only need to add **any one** of them. | -| `--dns-persist-days ` | Adds `persistUntil=` to the record, set to N days from now. The CA will refuse new validations against the record after that time. Omit for a record with no expiry. | +```bash +acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 +``` -You should get an output like: +Please look at the `keylength` parameter above. + +Valid values are: + +1. **ec-256 (prime256v1, "ECDSA P-256")** +2. **ec-384 (secp384r1, "ECDSA P-384")** +3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** + + + +# 11. Issue Wildcard certificates + +It's simple, just give a wildcard domain as the `-d` parameter. ```sh -TXT persist domain:_validation-persist.example.com -TXT persist value :"letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/123456789" -``` - -#### ✍️ Step 2: Add the TXT record to your DNS - -Add the printed `TXT persist domain` / `TXT persist value` pair as a TXT record at your DNS provider, then wait for it to propagate. - -#### 📜 Step 3: Issue the certificate - -```bash -acme.sh --issue -d example.com --dns-persist -``` - -✅ **Done!** No challenge token is provisioned during issuance — the CA reads the persistent TXT record directly. - -> 🔄 Renewals just work: `acme.sh --renew -d example.com` (or the cron job) reuses the same TXT record automatically — no further DNS edits needed. - ---- - -### 1️⃣1️⃣ Issue Certificates of Different Key Types (ECC or RSA) - -Just set the `keylength` to a valid, supported value. - -**Valid values for the `keylength` parameter:** - -| Key Length | Description | -|------------|-------------| -| `ec-256` | prime256v1, "ECDSA P-256" ⭐ **Default** | -| `ec-384` | secp384r1, "ECDSA P-384" | -| `ec-521` | secp521r1, "ECDSA P-521" ⚠️ Not supported by Let's Encrypt yet | -| `2048` | RSA 2048-bit | -| `3072` | RSA 3072-bit | -| `4096` | RSA 4096-bit | - -**Examples:** - -#### Single domain with ECDSA P-384 certificate - -```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-384 -``` - -#### SAN multi domain with RSA4096 certificate - -```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength 4096 -``` - ---- - -### 1️⃣2️⃣ Issue Wildcard Certificates - -It's simple! Just give a wildcard domain as the `-d` parameter: - -```sh -acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf +acme.sh --issue -d example.com -d '*.example.com' --dns dns_cf ``` ---- +# 12. How to renew the certs -### 1️⃣3️⃣ How to Renew Certificates +No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. -> 🔄 No need to renew manually! All certs will be renewed automatically every **30** days, **or earlier when the CA's ARI says so** (see below). - -However, you can force a renewal: +However, you can also force to renew a cert: ```sh acme.sh --renew -d example.com --force ``` -**For ECC cert:** +or, for ECC cert: ```sh acme.sh --renew -d example.com --force --ecc ``` -#### 📡 ACME Renewal Information (ARI) — RFC 9773 -📖 Wiki: https://github.com/acmesh-official/acme.sh/wiki/ARI - -If the CA exposes a `renewalInfo` endpoint in its ACME directory (Let's Encrypt, ZeroSSL, etc.), `acme.sh` follows [RFC 9773](https://www.rfc-editor.org/rfc/rfc9773.html) automatically — **no flag needed, no opt-in**: - -| What | When | Why | -|------|------|-----| -| 🔍 **Polls `suggestedWindow`** | Every cron run, before deciding to skip | Lets the CA shift the renewal time forward in case of an incident (key compromise, mass revocation, etc.) | -| 🎯 **Picks a random renewal time** inside the window | Right after a successful issuance/renewal | Disperses renewals across the network so all clients don't hit the CA at the same instant | -| 🔗 **Sends `replaces=`** in `newOrder` | On renewal | Lets the CA correlate the new order with the certificate it supersedes (RFC 9773 §5) | -| ↩️ **Retries without `replaces`** | If the CA rejects with `alreadyReplaced` or an ARI validation error | Robust against edge cases (e.g. switching CAs, retired issuers) | - -**Renewal trigger logic:** the cert is renewed if **any one** of the following becomes true: - -1. `--force` is given -2. The CA's **ARI `suggestedWindow` has started** -3. The cached `Le_NextRenewTime` has passed (default fallback for CAs without ARI) - -You can see the resulting next renewal time (already ARI-picked when applicable) in: - -```sh -acme.sh --info -d example.com -# Look for: Le_NextRenewTimeStr=... -``` - -For the live ARI window the CA is currently advertising, run with `--debug 2`: - -```sh -acme.sh --renew -d example.com --debug 2 2>&1 | grep -i 'ARI suggestedWindow' -``` - -> 💡 If your CA does not advertise `renewalInfo`, `acme.sh` falls back to the classic 30-day rule — no behavior change. - ---- - -### 1️⃣4️⃣ How to Stop Certificate Renewal +# 13. How to stop cert renewal To stop renewal of a cert, you can execute the following to remove the cert from the renewal list: @@ -570,80 +428,73 @@ acme.sh --remove -d example.com [--ecc] The cert/key file is not removed from the disk. -> 💡 You can remove the respective directory (e.g. `~/.acme.sh/example.com`) manually. +You can remove the respective directory (e.g. `~/.acme.sh/example.com`) by yourself. ---- -### 1️⃣5️⃣ How to Upgrade acme.sh +# 14. How to upgrade `acme.sh` -> 🚀 acme.sh is in constant development — it's strongly recommended to use the latest code. +acme.sh is in constant development, so it's strongly recommended to use the latest code. -**Update to latest:** +You can update acme.sh to the latest code: ```sh acme.sh --upgrade ``` -**Enable auto upgrade:** +You can also enable auto upgrade: ```sh acme.sh --upgrade --auto-upgrade ``` -**Disable auto upgrade:** +Then **acme.sh** will be kept up to date automatically. + +Disable auto upgrade: ```sh acme.sh --upgrade --auto-upgrade 0 ``` ---- -### 1️⃣6️⃣ Issue a Certificate from an Existing CSR +# 15. Issue a cert from an existing CSR -📚 https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR +https://github.com/acmesh-official/acme.sh/wiki/Issue-a-cert-from-existing-CSR ---- -### 1️⃣7️⃣ Send Notifications in Cronjob +# 16. Send notifications in cronjob -📚 https://github.com/acmesh-official/acme.sh/wiki/notify +https://github.com/acmesh-official/acme.sh/wiki/notify ---- -### 1️⃣8️⃣ Under the Hood +# 17. Under the Hood -> 🔧 Speak ACME language using shell, directly to "Let's Encrypt". +Speak ACME language using shell, directly to "Let's Encrypt". ---- +TODO: -### 1️⃣9️⃣ Acknowledgments -| Project | Link | -|---------|------| -| 🙏 Acme-tiny | https://github.com/diafygi/acme-tiny | -| 📜 ACME protocol | https://github.com/ietf-wg-acme/acme | +# 18. Acknowledgments ---- +1. Acme-tiny: https://github.com/diafygi/acme-tiny +2. ACME protocol: https://github.com/ietf-wg-acme/acme -## 👥 Contributors -### 💻 Code Contributors +## Contributors + +### Code Contributors This project exists thanks to all the people who contribute. - -If you want to become a contributor make sure to read [CONTRIBUTING.md](./CONTRIBUTING.md). - -### 💰 Financial Contributors +### Financial Contributors Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/acmesh/contribute)] -#### 👤 Individuals +#### Individuals -#### 🏢 Organizations +#### Organizations Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/acmesh/contribute)] @@ -658,43 +509,24 @@ Support this project with your organization. Your logo will show up here with a ---- -### 2️⃣0️⃣ License & Others +#### Sponsors -📄 **License:** GPLv3 +[![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) -⭐ Please **Star** and **Fork** this project! -🐛 [Issues](https://github.com/acmesh-official/acme.sh/issues) and 🔀 [Pull Requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome. +# 19. License & Others ---- +License is GPLv3 -### 2️⃣1️⃣ Donate +Please Star and Fork me. -> 💝 Your donation makes **acme.sh** better! +[Issues](https://github.com/acmesh-official/acme.sh/issues) and [pull requests](https://github.com/acmesh-official/acme.sh/pulls) are welcome. -| Method | Link | -|--------|------| -| PayPal / Alipay(支付宝) / Wechat(微信) | [https://donate.acme.sh/](https://donate.acme.sh/) | -📜 [Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list) +# 20. Donate +Your donation makes **acme.sh** better: ---- +1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) -### 2️⃣2️⃣ About This Repository - -> [!NOTE] -> This repository is officially maintained by ZeroSSL as part of our commitment to providing secure and reliable SSL/TLS solutions. We welcome contributions and feedback from the community! -> For more information about our services, including free and paid SSL/TLS certificates, visit https://zerossl.com. -> -> All donations made through this repository go directly to the original independent maintainer (Neil Pang), not to ZeroSSL. -

- - - - - ZeroSSL - - -

+[Donate List](https://github.com/acmesh-official/acme.sh/wiki/Donate-list) diff --git a/acme.sh b/acme.sh index 4d4f6cbe..d6d8e48c 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=3.1.5 +VER=3.0.5 PROJECT_NAME="acme.sh" @@ -23,6 +23,9 @@ _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY" CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory" CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory" +CA_BUYPASS="https://api.buypass.com/acme/directory" +CA_BUYPASS_TEST="https://api.test4.buypass.no/acme/directory" + CA_ZEROSSL="https://acme.zerossl.com/v2/DV90" _ZERO_EAB_ENDPOINT="https://api.zerossl.com/acme/eab-credentials-email" @@ -32,8 +35,6 @@ CA_SSLCOM_ECC="https://acme.ssl.com/sslcom-dv-ecc" CA_GOOGLE="https://dv.acme-v02.api.pki.goog/directory" CA_GOOGLE_TEST="https://dv.acme-v02.test-api.pki.goog/directory" -CA_ACTALIS="https://acme-api.actalis.com/acme/directory" - DEFAULT_CA=$CA_ZEROSSL DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST @@ -41,38 +42,37 @@ CA_NAMES=" ZeroSSL.com,zerossl LetsEncrypt.org,letsencrypt LetsEncrypt.org_test,letsencrypt_test,letsencrypttest +BuyPass.com,buypass +BuyPass.com_test,buypass_test,buypasstest SSL.com,sslcom Google.com,google Google.com_test,googletest,google_test -Actalis.com,actalis.com,actalis " -CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST,$CA_ACTALIS" +CA_SERVERS="$CA_ZEROSSL,$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_SSLCOM_RSA,$CA_GOOGLE,$CA_GOOGLE_TEST" DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)" -DEFAULT_ACCOUNT_KEY_LENGTH=ec-256 -DEFAULT_DOMAIN_KEY_LENGTH=ec-256 +DEFAULT_ACCOUNT_KEY_LENGTH=2048 +DEFAULT_DOMAIN_KEY_LENGTH=2048 DEFAULT_OPENSSL_BIN="openssl" VTYPE_HTTP="http-01" VTYPE_DNS="dns-01" VTYPE_ALPN="tls-alpn-01" -VTYPE_DNS_PERSIST="dns-persist-01" ID_TYPE_DNS="dns" ID_TYPE_IP="ip" LOCAL_ANY_ADDRESS="0.0.0.0" -DEFAULT_RENEW="${DEFAULT_RENEW:-30}" +DEFAULT_RENEW=60 NO_VALUE="no" W_DNS="dns" W_ALPN="alpn" -W_DNS_PERSIST="dns_persist" DNS_ALIAS_PREFIX="=" MODE_STATELESS="stateless" @@ -102,12 +102,12 @@ ECC_SUFFIX="${ECC_SEP}ecc" LOG_LEVEL_1=1 LOG_LEVEL_2=2 LOG_LEVEL_3=3 -DEFAULT_LOG_LEVEL="$LOG_LEVEL_2" +DEFAULT_LOG_LEVEL="$LOG_LEVEL_1" DEBUG_LEVEL_1=1 DEBUG_LEVEL_2=2 DEBUG_LEVEL_3=3 -DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_2 +DEBUG_LEVEL_DEFAULT=$DEBUG_LEVEL_1 DEBUG_LEVEL_NONE=0 DOH_CLOUDFLARE=1 @@ -180,8 +180,6 @@ _VALIDITY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Validity" _DNSCHECK_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnscheck" -_PROFILESELECTION_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Profile-selection" - _DNS_MANUAL_ERR="The dns manual mode can not renew automatically, you must issue it again manually. You'd better use the other modes instead." _DNS_MANUAL_WARN="It seems that you are using dns manual mode. please take care: $_DNS_MANUAL_ERR" @@ -233,11 +231,11 @@ _dlg_versions() { echo "$ACME_OPENSSL_BIN doesn't exist." fi - echo "Apache:" + echo "apache:" if [ "$_APACHECTL" ] && _exists "$_APACHECTL"; then $_APACHECTL -V 2>&1 else - echo "Apache doesn't exist." + echo "apache doesn't exist." fi echo "nginx:" @@ -252,13 +250,6 @@ _dlg_versions() { socat -V 2>&1 else _debug "socat doesn't exist." - if _exists "python3"; then - python3 -V 2>&1 - elif _exists "python2"; then - python2 -V 2>&1 - elif _exists "python"; then - python -V 2>&1 - fi fi } @@ -445,28 +436,14 @@ _secure_debug3() { fi } -__USE_TR_TAG="" -if [ "$(echo "abc" | LANG=C tr a-z A-Z 2>/dev/null)" != "ABC" ]; then - __USE_TR_TAG="1" -fi -export __USE_TR_TAG - _upper_case() { - if [ "$__USE_TR_TAG" ]; then - LANG=C tr '[:lower:]' '[:upper:]' - else - # shellcheck disable=SC2018,SC2019 - LANG=C tr '[a-z]' '[A-Z]' - fi + # shellcheck disable=SC2018,SC2019 + tr '[a-z]' '[A-Z]' } _lower_case() { - if [ "$__USE_TR_TAG" ]; then - LANG=C tr '[:upper:]' '[:lower:]' - else - # shellcheck disable=SC2018,SC2019 - LANG=C tr '[A-Z]' '[a-z]' - fi + # shellcheck disable=SC2018,SC2019 + tr '[A-Z]' '[a-z]' } _startswith() { @@ -597,6 +574,11 @@ if [ "$(printf '\x41')" != 'A' ]; then _URGLY_PRINTF=1 fi +_ESCAPE_XARGS="" +if _exists xargs && [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then + _ESCAPE_XARGS=1 +fi + _h2b() { if _exists xxd; then if _contains "$(xxd --help 2>&1)" "assumes -c30"; then @@ -615,8 +597,17 @@ _h2b() { jc="" _debug2 _URGLY_PRINTF "$_URGLY_PRINTF" if [ -z "$_URGLY_PRINTF" ]; then - # shellcheck disable=SC2059 - printf "$(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\x\1/g')" + if [ "$_ESCAPE_XARGS" ] && _exists xargs; then + _debug2 "xargs" + echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/g' | xargs printf + else + for h in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/ \1/g'); do + if [ -z "$h" ]; then + break + fi + printf "\x$h%s" + done + fi else for c in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\)/ \1/g'); do if [ -z "$ic" ]; then @@ -681,10 +672,8 @@ _hex_dump() { #0 1 2 3 4 5 6 7 8 9 - _ . ~ #30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e -#_url_encode [upper-hex] the encoded hex will be upper-case if the argument upper-hex is followed #stdin stdout _url_encode() { - _upper_hex=$1 _hex_str=$(_hex_dump) _debug3 "_url_encode" _debug3 "_hex_str" "$_hex_str" @@ -894,9 +883,6 @@ _url_encode() { ;; #other hex *) - if [ "$_upper_hex" = "upper-hex" ]; then - _hex_code=$(printf "%s" "$_hex_code" | _upper_case) - fi printf '%%%s' "$_hex_code" ;; esac @@ -918,15 +904,6 @@ _json_decode() { echo "$_j_str" } -#extract the authorization URLs from an order response on stdin, as a -#comma-separated list. The entries are quoted URL strings and a quote cannot -#occur inside a URL, so the first '"]' is always the end of the array. A -#char-class scan would stop early on the brackets of an IPv6 host -#(https://[2001:db8::1]/...). Outputs nothing if the field is missing. -_authorizations_from_order() { - sed -n 's/.*"authorizations" *: *\[//p' | sed 's/" *\].*//' | tr -d '" ' -} - #options file _sed_i() { options="$1" @@ -939,9 +916,6 @@ _sed_i() { if sed -h 2>&1 | grep "\-i\[SUFFIX]" >/dev/null 2>&1; then _debug "Using sed -i" sed -i "$options" "$filename" - elif sed -h 2>&1 | grep "\-i extension" >/dev/null 2>&1; then - _debug "Using FreeBSD sed -i" - sed -i "" "$options" "$filename" else _debug "No -i support in sed" text="$(cat "$filename")" @@ -949,16 +923,8 @@ _sed_i() { fi } -if [ "$(echo abc | egrep -o b 2>/dev/null)" = "b" ]; then - __USE_EGREP=1 -else - __USE_EGREP="" -fi - _egrep_o() { - if [ "$__USE_EGREP" ]; then - egrep -o -- "$1" 2>/dev/null - else + if ! egrep -o "$1" 2>/dev/null; then sed -n 's/.*\('"$1"'\).*/\1/p' fi } @@ -975,7 +941,7 @@ _getfile() { i="$(grep -n -- "$startline" "$filename" | cut -d : -f 1)" if [ -z "$i" ]; then - _err "Cannot find start line: $startline" + _err "Can not find start line: $startline" return 1 fi i="$(_math "$i" + 1)" @@ -983,7 +949,7 @@ _getfile() { j="$(grep -n -- "$endline" "$filename" | cut -d : -f 1)" if [ -z "$j" ]; then - _err "Cannot find end line: $endline" + _err "Can not find end line: $endline" return 1 fi j="$(_math "$j" - 1)" @@ -1024,24 +990,6 @@ _checkcert() { fi } -#file -_enddate() { - _cf="$1" - _res="$(${ACME_OPENSSL_BIN:-openssl} x509 -noout -enddate -in "$_cf")" - if [ "$?" != "0" ] || [ -z "$_res" ]; then - return 1 - fi - - case "$_res" in - notAfter=*) - echo "${_res#notAfter=}" - ;; - *) - return 1 - ;; - esac -} - #Usage: hashalg [outputhex] #Output Base64-encoded digest _digest() { @@ -1053,7 +1001,7 @@ _digest() { outputhex="$2" - if [ "$alg" = "sha3-256" ] || [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then + if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then if [ "$outputhex" ]; then ${ACME_OPENSSL_BIN:-openssl} dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' ' else @@ -1066,25 +1014,6 @@ _digest() { } -#Usage: certpath hashalg -#Output certificate fingerprint without colons -_fingerprint() { - cert="$1" - alg="$2" - if [ -z "$alg" ]; then - _usage "Usage: _fingerprint certpath hashalg" - return 1 - fi - - if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then - # openssl prints "SHA1 Fingerprint=AA:BB:CC:..."; strip prefix and colons. - ${ACME_OPENSSL_BIN:-openssl} x509 -in "$cert" -noout -fingerprint -"$alg" | sed 's/.*=//; s/://g' - else - _err "$alg is not supported yet" - return 1 - fi -} - #Usage: hashalg secret_hex [outputhex] #Output binary hmac _hmac() { @@ -1128,7 +1057,7 @@ _sign() { if ! _signedECText="$($_sign_openssl -sha$__ECC_KEY_LEN | ${ACME_OPENSSL_BIN:-openssl} asn1parse -inform DER)"; then _err "Sign failed: $_sign_openssl" _err "Key file: $keyfile" - _err "Key content: $(wc -l <"$keyfile") lines" + _err "Key content:$(wc -l <"$keyfile") lines" return 1 fi _debug3 "_signedECText" "$_signedECText" @@ -1188,11 +1117,6 @@ _createkey() { length="$1" f="$2" _debug2 "_createkey for file:$f" - if ! _exists "${ACME_OPENSSL_BIN:-openssl}"; then - _err "Please install openssl first. ACME_OPENSSL_BIN=$ACME_OPENSSL_BIN" - _err "We need openssl to generate keys." - return 1 - fi eccname="$length" if _startswith "$length" "ec-"; then length=$(printf "%s" "$length" | cut -d '-' -f 2-100) @@ -1213,15 +1137,14 @@ _createkey() { length=2048 fi - _debug "Using length $length" + _debug "Use length $length" - _new_key_file="" if ! [ -e "$f" ]; then if ! touch "$f" >/dev/null 2>&1; then _f_path="$(dirname "$f")" _debug _f_path "$_f_path" if ! mkdir -p "$_f_path"; then - _err "Cannot create path: $_f_path" + _err "Can not create path: $_f_path" return 1 fi fi @@ -1229,19 +1152,14 @@ _createkey() { return 1 fi chmod 600 "$f" - _new_key_file="1" fi if _isEccKey "$length"; then - _debug "Using EC name: $eccname" + _debug "Using ec name: $eccname" if _opkey="$(${ACME_OPENSSL_BIN:-openssl} ecparam -name "$eccname" -noout -genkey 2>/dev/null)"; then echo "$_opkey" >"$f" else - _err "Error encountered for ECC key named $eccname" - #do not leave an empty file behind, or the next run would treat the key as existing - if [ "$_new_key_file" ]; then - rm -f "$f" - fi + _err "error ecc key name: $eccname" return 1 fi else @@ -1253,17 +1171,13 @@ _createkey() { if _opkey="$(${ACME_OPENSSL_BIN:-openssl} genrsa $__traditional "$length" 2>/dev/null)"; then echo "$_opkey" >"$f" else - _err "Error encountered for RSA key of length $length" - #do not leave an empty file behind, or the next run would treat the key as existing - if [ "$_new_key_file" ]; then - rm -f "$f" - fi + _err "error rsa key: $length" return 1 fi fi if [ "$?" != "0" ]; then - _err "Key creation error." + _err "Create key error." return 1 fi } @@ -1306,24 +1220,7 @@ _idn() { fi } -#_createcsr cn san_list keyfile csrfile conf acmeValidationv1 extendedUsage -#cn -#The x509 Common Name is limited to 64 characters (RFC 5280 ub-common-name, -#enforced by openssl in ASN1_mbstring_ncopy), and an IP address or an empty -#name is not usable as CN either. When this rejects the name, _createcsr -#omits CN from the CSR subject and the CA takes the identifiers from the -#subjectAltName extension (issue 4867). -_is_valid_cn() { - _cn_v="$1" - if [ -z "$_cn_v" ] || [ "${#_cn_v}" -gt 64 ]; then - return 1 - fi - if _isIP "$_cn_v"; then - return 1 - fi - return 0 -} - +#_createcsr cn san_list keyfile csrfile conf acmeValidationv1 _createcsr() { _debug _createcsr domain="$1" @@ -1332,20 +1229,13 @@ _createcsr() { csr="$4" csrconf="$5" acmeValidationv1="$6" - extusage="$7" _debug2 domain "$domain" _debug2 domainlist "$domainlist" _debug2 csrkey "$csrkey" _debug2 csr "$csr" _debug2 csrconf "$csrconf" - printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]" >"$csrconf" - - if [ "$extusage" ]; then - printf "\nextendedKeyUsage=$extusage\n" >>"$csrconf" - else - printf "\nextendedKeyUsage=serverAuth,clientAuth\n" >>"$csrconf" - fi + printf "[ req_distinguished_name ]\n[ req ]\ndistinguished_name = req_distinguished_name\nreq_extensions = v3_req\n[ v3_req ]\nextendedKeyUsage=serverAuth,clientAuth\n" >"$csrconf" if [ "$acmeValidationv1" ]; then domainlist="$(_idn "$domainlist")" @@ -1387,16 +1277,16 @@ _createcsr() { _csr_cn="$(_idn "$domain")" _debug2 _csr_cn "$_csr_cn" if _contains "$(uname -a)" "MINGW"; then - if _is_valid_cn "$_csr_cn"; then - ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr" - else + if _isIP "$_csr_cn"; then ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//O=$PROJECT_NAME" -config "$csrconf" -out "$csr" + else + ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "//CN=$_csr_cn" -config "$csrconf" -out "$csr" fi else - if _is_valid_cn "$_csr_cn"; then - ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" - else + if _isIP "$_csr_cn"; then ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/O=$PROJECT_NAME" -config "$csrconf" -out "$csr" + else + ${ACME_OPENSSL_BIN:-openssl} req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" fi fi } @@ -1422,9 +1312,7 @@ _readSubjectFromCSR() { _usage "_readSubjectFromCSR mycsr.csr" return 1 fi - # -config /dev/null: reading a CSR needs no config, but a missing default - # openssl.cnf is fatal on some systems (e.g. NetBSD does not install one) - ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject -config /dev/null | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n' + ${ACME_OPENSSL_BIN:-openssl} req -noout -in "$_csrfile" -subject | tr ',' "\n" | _egrep_o "CN *=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d ' \n' } #_csrfile @@ -1439,17 +1327,16 @@ _readSubjectAltNamesFromCSR() { _csrsubj="$(_readSubjectFromCSR "$_csrfile")" _debug _csrsubj "$_csrsubj" - _dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" -config /dev/null | grep "^ *DNS:.*" | tr -d ' \n')" + _dnsAltnames="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')" _debug _dnsAltnames "$_dnsAltnames" - # escape the wildcard '*' so it is not taken as a regex operator by grep/sed below - _escapedAltnames="$(echo "$_dnsAltnames" | tr '*' '#')" - _debug _escapedAltnames "$_escapedAltnames" - _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')" - _debug _escapedSubject "$_escapedSubject" - if _contains "$_escapedAltnames," "DNS:$_escapedSubject,"; then + if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then _debug "AltNames contains subject" - _dnsAltnames="$(echo "$_escapedAltnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" + _excapedAlgnames="$(echo "$_dnsAltnames" | tr '*' '#')" + _debug _excapedAlgnames "$_excapedAlgnames" + _escapedSubject="$(echo "$_csrsubj" | tr '*' '#')" + _debug _escapedSubject "$_escapedSubject" + _dnsAltnames="$(echo "$_excapedAlgnames," | sed "s/DNS:$_escapedSubject,//g" | tr '#' '*' | sed "s/,\$//g")" _debug _dnsAltnames "$_dnsAltnames" else _debug "AltNames doesn't contain subject" @@ -1466,7 +1353,7 @@ _readKeyLengthFromCSR() { return 1 fi - _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile" -config /dev/null)" + _outcsr="$(${ACME_OPENSSL_BIN:-openssl} req -noout -text -in "$_csrfile")" _debug2 _outcsr "$_outcsr" if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then _debug "ECC CSR" @@ -1491,12 +1378,6 @@ _ss() { return 0 fi - if [ "$(uname)" = "AIX" ]; then - _debug "Using: AIX netstat" - netstat -an | grep "^tcp" | grep "LISTEN" | grep "\.$_port " - return 0 - fi - if _exists "netstat"; then _debug "Using: netstat" if netstat -help 2>&1 | grep "\-p proto" >/dev/null; then @@ -1541,25 +1422,6 @@ _toPkcs() { else ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" fi - if [ "$?" = "0" ]; then - _savedomainconf "Le_PFXPassword" "$pfxPassword" "base64" - fi - -} - -_toPkcs8() { - _cpkcs8="$1" - _ckey="$2" - pkcs8Password="$3" - - if [ "$pkcs8Password" ]; then - ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -v2 aes256 -passout "pass:$pkcs8Password" -in "$_ckey" -out "$_cpkcs8" - else - ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$_ckey" -out "$_cpkcs8" - fi - if [ "$?" = "0" ]; then - _savedomainconf "Le_PKCS8Password" "$pkcs8Password" "base64" - fi } @@ -1579,26 +1441,25 @@ toPkcs() { _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$pfxPassword" if [ "$?" = "0" ]; then - _info "Success, PFX has been exported to: $CERT_PFX_PATH" + _info "Success, Pfx is exported to: $CERT_PFX_PATH" fi } -#domain [password] [isEcc] +#domain [isEcc] toPkcs8() { domain="$1" - pkcs8Password="$2" if [ -z "$domain" ]; then - _usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain [--password ] [--ecc]" + _usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain [--ecc]" return 1 fi - _isEcc="$3" + _isEcc="$2" _initpath "$domain" "$_isEcc" - _toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$pkcs8Password" + ${ACME_OPENSSL_BIN:-openssl} pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in "$CERT_KEY_PATH" -out "$CERT_PKCS8_PATH" if [ "$?" = "0" ]; then _info "Success, $CERT_PKCS8_PATH" @@ -1624,7 +1485,7 @@ _create_account_key() { length=$1 if [ -z "$length" ] || [ "$length" = "$NO_VALUE" ]; then - _debug "Using default length $DEFAULT_ACCOUNT_KEY_LENGTH" + _debug "Use default length $DEFAULT_ACCOUNT_KEY_LENGTH" length="$DEFAULT_ACCOUNT_KEY_LENGTH" fi @@ -1633,15 +1494,15 @@ _create_account_key() { mkdir -p "$CA_DIR" if [ -s "$ACCOUNT_KEY_PATH" ]; then - _info "Account key exists, skipping" + _info "Account key exists, skip" return 0 else #generate account key if _createkey "$length" "$ACCOUNT_KEY_PATH"; then - _info "Account key creation OK." + _info "Create account key ok." return 0 else - _err "Account key creation error." + _err "Create account key error." return 1 fi fi @@ -1660,7 +1521,7 @@ createDomainKey() { _cdl=$2 if [ -z "$_cdl" ]; then - _debug "Using DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH" + _debug "Use DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH" _cdl="$DEFAULT_DOMAIN_KEY_LENGTH" fi @@ -1672,16 +1533,16 @@ createDomainKey() { _info "The domain key is here: $(__green $CERT_KEY_PATH)" return 0 else - _err "Cannot create domain key" + _err "Can not create domain key" return 1 fi else if [ "$_ACME_IS_RENEW" ]; then - _info "Domain key exists, skipping" + _info "Domain key exists, skip" return 0 else - _err "Domain key exists, do you want to overwrite it?" - _err "If so, add '--force' and try again." + _err "Domain key exists, do you want to overwrite the key?" + _err "Add '--force', and try again." return 1 fi fi @@ -1690,30 +1551,29 @@ createDomainKey() { # domain domainlist isEcc createCSR() { - _info "Creating CSR" + _info "Creating csr" if [ -z "$1" ]; then - _usage "Usage: $PROJECT_ENTRY --create-csr --domain [--domain ...] [--ecc]" + _usage "Usage: $PROJECT_ENTRY --create-csr --domain [--domain ...]" return fi domain="$1" domainlist="$2" _isEcc="$3" - _csreku="$4" _initpath "$domain" "$_isEcc" if [ -f "$CSR_PATH" ] && [ "$_ACME_IS_RENEW" ] && [ -z "$FORCE" ]; then - _info "CSR exists, skipping" + _info "CSR exists, skip" return fi if [ ! -f "$CERT_KEY_PATH" ]; then - _err "This key file was not found: $CERT_KEY_PATH" - _err "Please create it first." + _err "The key file is not found: $CERT_KEY_PATH" + _err "Please create the key file first." return 1 fi - _createcsr "$domain" "$domainlist" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF" "" "$_csreku" + _createcsr "$domain" "$domainlist" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF" } @@ -1745,11 +1605,6 @@ _time2str() { return fi - #Omnios - if date -u -r "$1" +"%Y-%m-%dT%H:%M:%SZ" 2>/dev/null; then - return - fi - #Solaris if printf "%(%Y-%m-%dT%H:%M:%SZ)T\n" $1 2>/dev/null; then return @@ -1782,7 +1637,7 @@ _stat() { #keyfile _isRSA() { keyfile=$1 - if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text 2>&1 | grep "^publicExponent:" 2>&1 >/dev/null; then + if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} rsa -in "$keyfile" -noout -text | grep "^publicExponent:" >/dev/null 2>&1; then return 0 fi return 1 @@ -1791,7 +1646,7 @@ _isRSA() { #keyfile _isEcc() { keyfile=$1 - if grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" 2>&1 >/dev/null; then + if grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1 || ${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" >/dev/null 2>&1; then return 0 fi return 1 @@ -1859,7 +1714,7 @@ _calcjwk() { __ECC_KEY_LEN=512 ;; *) - _err "ECC oid: $crv_oid" + _err "ECC oid : $crv_oid" return 1 ;; esac @@ -1889,7 +1744,7 @@ _calcjwk() { _debug3 x64 "$x64" xend=$(_math "$xend" + 1) - y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-2048)" + y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-10000)" _debug3 y "$y" y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)" @@ -1902,7 +1757,7 @@ _calcjwk() { JWK_HEADERPLACE_PART1='{"nonce": "' JWK_HEADERPLACE_PART2='", "alg": "ES'$__ECC_KEY_LEN'"' else - _err "Only RSA or EC keys are supported. keyfile=$keyfile" + _err "Only RSA or EC key is supported. keyfile=$keyfile" _debug2 "$(cat "$keyfile")" return 1 fi @@ -1919,10 +1774,6 @@ _time() { # 2022-04-01 08:10:33 to 1648800633 #or 2022-04-01T08:10:33Z to 1648800633 _date2time() { - #Mac/BSD - if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then - return - fi #Linux if date -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return @@ -1932,35 +1783,11 @@ _date2time() { if gdate -u -d "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return fi - #Omnios. Pass the date as argv (sys.argv[1]) instead of interpolating it into - #the -c program text, so a quote in the input cannot inject Python code. - if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%d %H:%M:%S\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then - return - fi - #Omnios - if python3 -c "import datetime,sys; print(int(datetime.datetime.strptime(sys.argv[1], \"%Y-%m-%dT%H:%M:%SZ\").replace(tzinfo=datetime.timezone.utc).timestamp()))" "$1" 2>/dev/null; then - return - fi - _err "Cannot parse _date2time $1" - return 1 -} - -#support the output format of openssl -enddate: -# Apr 01 08:10:33 2022 GMT to 1641283833 -_ssldate2time() { - #Linux - if date -u -d "$1" +"%s" 2>/dev/null; then - return - fi - #Solaris - if gdate -u -d "$1" +"%s" 2>/dev/null; then - return - fi #Mac/BSD - if date -j -f "%b %d %T %Y %Z" "$1" +"%s" 2>/dev/null; then + if date -u -j -f "%Y-%m-%d %H:%M:%S" "$(echo "$1" | tr -d "Z" | tr "T" ' ')" +"%s" 2>/dev/null; then return fi - _err "Cannot parse _ssldate2time $1" + _err "Can not parse _date2time $1" return 1 } @@ -1968,53 +1795,6 @@ _utc_date() { date -u "+%Y-%m-%d %H:%M:%S" } -#Usage: _calc_next_renew_time createtime renewaldays [endtime] -#Prints createtime + renewaldays*86400 - 86400, capped so it never passes -#the certificate expiry: with short-lived certs (internal CAs, upcoming -#CA/B SC-081 47-day maximum) a fixed RenewalDays would otherwise schedule -#the renewal after notAfter and leave an expired cert in place. -#The cap is one day before endtime, or one hour before for certs whose -#lifetime is 24 hours or less, mirroring the --valid-to scheduling. -_calc_next_renew_time() { - _cnrt_create="$1" - _cnrt_days="$2" - _cnrt_end="$3" - _cnrt_next=$(_math "$_cnrt_create" + "$_cnrt_days" \* 24 \* 60 \* 60 - 86400) - if [ -z "$_cnrt_end" ]; then - printf "%s" "$_cnrt_next" - return 0 - fi - if [ "$(_math "$_cnrt_end" - "$_cnrt_create")" -gt 86400 ]; then - _cnrt_cap=$(_math "$_cnrt_end" - 86400) - else - _cnrt_cap=$(_math "$_cnrt_end" - 3600) - fi - if [ "$_cnrt_next" -gt "$_cnrt_cap" ]; then - _cnrt_next="$_cnrt_cap" - fi - printf "%s" "$_cnrt_next" -} - -#Usage: _calc_validto_renew_time notaftertime renewaldays now -#Prints the next renew time for a cert issued with a relative --valid-to. -#A negative renewaldays is anchored to the expiry: notaftertime + -#renewaldays*86400. Otherwise the cert renews one day before the expiry, -#or one hour before for certs whose lifetime is 24 hours or less. -_calc_validto_renew_time() { - _cvrt_end="$1" - _cvrt_days="$2" - _cvrt_now="$3" - if [ "$_cvrt_days" ] && [ "$_cvrt_days" -lt 0 ]; then - _math "$_cvrt_end" + "$_cvrt_days" \* 24 \* 60 \* 60 - return 0 - fi - if [ "$(_math "$_cvrt_end" - "$_cvrt_now")" -gt 86400 ]; then - _math "$_cvrt_end" - 86400 - else - _math "$_cvrt_end" - 3600 - fi -} - _mktemp() { if _exists mktemp; then if mktemp 2>/dev/null; then @@ -2031,7 +1811,7 @@ _mktemp() { echo "/$LE_TEMP_DIR/wefADf24sf.$(_time).tmp" return 0 fi - _err "Cannot create temp file." + _err "Can not create temp file." } #clear all the https envs to cause _inithttp() to run next time. @@ -2058,11 +1838,6 @@ _inithttp() { if [ -z "$_ACME_CURL" ] && _exists "curl"; then _ACME_CURL="curl --silent --dump-header $HTTP_HEADER " - if [ "$ACME_USE_IPV6_REQUESTS" ]; then - _ACME_CURL="$_ACME_CURL --ipv6 " - elif [ "$ACME_USE_IPV4_REQUESTS" ]; then - _ACME_CURL="$_ACME_CURL --ipv4 " - fi if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then _ACME_CURL="$_ACME_CURL -L " fi @@ -2077,24 +1852,13 @@ _inithttp() { _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE " fi - if _contains "$(curl --help 2>&1)" "--globoff" || _contains "$(curl --help curl 2>&1)" "--globoff"; then + if _contains "$(curl --help 2>&1)" "--globoff"; then _ACME_CURL="$_ACME_CURL -g " fi - - #don't use --fail-with-body - ##from curl 7.76: return fail on HTTP errors but keep the body - #if _contains "$(curl --help http 2>&1)" "--fail-with-body"; then - # _ACME_CURL="$_ACME_CURL --fail-with-body " - #fi fi if [ -z "$_ACME_WGET" ] && _exists "wget"; then _ACME_WGET="wget -q" - if [ "$ACME_USE_IPV6_REQUESTS" ]; then - _ACME_WGET="$_ACME_WGET --inet6-only " - elif [ "$ACME_USE_IPV4_REQUESTS" ]; then - _ACME_WGET="$_ACME_WGET --inet4-only " - fi if [ "$ACME_HTTP_NO_REDIRECTS" ]; then _ACME_WGET="$_ACME_WGET --max-redirect 0 " fi @@ -2108,11 +1872,11 @@ _inithttp() { elif [ "$CA_BUNDLE" ]; then _ACME_WGET="$_ACME_WGET --ca-certificate=$CA_BUNDLE " fi + fi - #from wget 1.14: do not skip body on 404 error - if _contains "$(wget --help 2>&1)" "--content-on-error"; then - _ACME_WGET="$_ACME_WGET --content-on-error " - fi + #from wget 1.14: do not skip body on 404 error + if [ "$_ACME_WGET" ] && _contains "$($_ACME_WGET --help 2>&1)" "--content-on-error"; then + _ACME_WGET="$_ACME_WGET --content-on-error " fi __HTTP_INITIALIZED=1 @@ -2230,7 +1994,7 @@ _post() { _ret="$?" if [ "$_ret" = "8" ]; then _ret=0 - _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later." + _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." fi if [ "$_ret" != "0" ]; then _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret" @@ -2244,7 +2008,7 @@ _post() { _sed_i 's/^ //g' "$HTTP_HEADER" else _ret="$?" - _err "Neither curl nor wget have been found, cannot make $httpmethod request." + _err "Neither curl nor wget is found, can not do $httpmethod." fi _debug "_ret" "$_ret" printf "%s" "$response" @@ -2294,7 +2058,7 @@ _get() { fi _debug "_WGET" "$_WGET" if [ "$onlyheader" ]; then - _wget_out="$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)" + _wget_out = "$($_WGET --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" -S -O /dev/null "$url" 2>&1)" if _contains "$_WGET" " -d "; then # Demultiplex wget debug output echo "$_wget_out" >&2 @@ -2313,14 +2077,14 @@ _get() { ret=$? if [ "$ret" = "8" ]; then ret=0 - _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later." + _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." fi if [ "$ret" != "0" ]; then _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret" fi else ret=$? - _err "Neither curl nor wget have been found, cannot make GET request." + _err "Neither curl nor wget is found, can not do GET." fi _debug "ret" "$ret" return $ret @@ -2331,18 +2095,12 @@ _head_n() { } _tail_n() { - if _is_solaris; then + if ! tail -n "$1" 2>/dev/null; then #fix for solaris tail -"$1" - else - tail -n "$1" fi } -_tail_c() { - tail -c "$1" 2>/dev/null || tail -"$1"c -} - # url payload needbase64 keyfile _send_signed_request() { url=$1 @@ -2352,7 +2110,6 @@ _send_signed_request() { if [ -z "$keyfile" ]; then keyfile="$ACCOUNT_KEY_PATH" fi - _debug "=======Sending Signed Request=======" _debug url "$url" _debug payload "$payload" @@ -2396,8 +2153,9 @@ _send_signed_request() { _debug2 _headers "$_headers" _CACHED_NONCE="$(echo "$_headers" | grep -i "Replay-Nonce:" | _head_n 1 | tr -d "\r\n " | cut -d ':' -f 2)" fi + _debug2 _CACHED_NONCE "$_CACHED_NONCE" if [ "$?" != "0" ]; then - _err "Cannot connect to $nonceurl to get nonce." + _err "Can not connect to $nonceurl to get nonce." return 1 fi else @@ -2440,7 +2198,7 @@ _send_signed_request() { _CACHED_NONCE="" if [ "$?" != "0" ]; then - _err "Cannot make POST request to $url" + _err "Can not post to $url" return 1 fi @@ -2465,28 +2223,14 @@ _send_signed_request() { _debug3 _body "$_body" fi - _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') - if [ "$code" = '503' ]; then - _sleep_overload_retry_sec=$_retryafter - if [ -z "$_sleep_overload_retry_sec" ]; then - _sleep_overload_retry_sec=5 - fi - if [ $_sleep_overload_retry_sec -le 600 ]; then - _info "It seems the CA server is currently overloaded, let's wait and retry. Sleeping for $_sleep_overload_retry_sec seconds." - _sleep $_sleep_overload_retry_sec - continue - else - _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore." - fi - fi if _contains "$_body" "JWS has invalid anti-replay nonce" || _contains "$_body" "JWS has an invalid anti-replay nonce"; then - _info "It seems the CA server is busy now, let's wait and retry. Sleeping for $_sleep_retry_sec seconds." + _info "It seems the CA server is busy now, let's wait and retry. Sleeping $_sleep_retry_sec seconds." _CACHED_NONCE="" _sleep $_sleep_retry_sec continue fi if _contains "$_body" "The Replay Nonce is not recognized"; then - _info "The replay nonce is not valid, let's get a new one. Sleeping for $_sleep_retry_sec seconds." + _info "The replay Nonce is not valid, let's get a new one, Sleeping $_sleep_retry_sec seconds." _CACHED_NONCE="" _sleep $_sleep_retry_sec continue @@ -2512,9 +2256,8 @@ _setopt() { fi if [ ! -f "$__conf" ]; then touch "$__conf" - chmod 600 "$__conf" fi - if [ -n "$(_tail_c 1 <"$__conf")" ]; then + if [ -n "$(tail -c 1 <"$__conf")" ]; then echo >>"$__conf" fi @@ -2559,7 +2302,7 @@ _save_conf() { if [ "$_s_c_f" ]; then _setopt "$_s_c_f" "$_sdkey" "=" "'$_sdvalue'" else - _err "Config file is empty, cannot save $_sdkey=$_sdvalue" + _err "config file is empty, can not save $_sdkey=$_sdvalue" fi } @@ -2569,9 +2312,9 @@ _clear_conf() { _sdkey="$2" if [ "$_c_c_f" ]; then _conf_data="$(cat "$_c_c_f")" - echo "$_conf_data" | sed "/^$_sdkey *=.*$/d" >"$_c_c_f" + echo "$_conf_data" | sed "s/^$_sdkey *=.*$//" >"$_c_c_f" else - _err "Config file is empty, cannot clear" + _err "config file is empty, can not clear" fi } @@ -2589,7 +2332,7 @@ _read_conf() { fi printf "%s" "$_sdv" else - _debug "Config file is empty, cannot read $_sdkey" + _debug "config file is empty, can not read $_sdkey" fi } @@ -2609,31 +2352,6 @@ _readdomainconf() { _read_conf "$DOMAIN_CONF" "$1" } -#_migratedomainconf oldkey newkey base64encode -_migratedomainconf() { - _old_key="$1" - _new_key="$2" - _b64encode="$3" - _old_value=$(_readdomainconf "$_old_key") - _cleardomainconf "$_old_key" - if [ -z "$_old_value" ]; then - return 1 # migrated failed: old value is empty - fi - _new_value=$(_readdomainconf "$_new_key") - if [ -n "$_new_value" ]; then - _debug "Domain config new key exists, old key $_old_key='$_old_value' has been removed." - return 1 # migrated failed: old value replaced by new value - fi - _savedomainconf "$_new_key" "$_old_value" "$_b64encode" - _debug "Domain config $_old_key has been migrated to $_new_key." -} - -#_migratedeployconf oldkey newkey base64encode -_migratedeployconf() { - _migratedomainconf "$1" "SAVED_$2" "$3" || - _migratedomainconf "SAVED_$1" "SAVED_$2" "$3" # try only when oldkey itself is not found -} - #key value base64encode _savedeployconf() { _savedomainconf "SAVED_$1" "$2" "$3" @@ -2641,13 +2359,6 @@ _savedeployconf() { _cleardomainconf "$1" } -#key -_cleardeployconf() { - _cleardomainconf "SAVED_$1" - #remove later - _cleardomainconf "$1" -} - #key _getdeployconf() { _rac_key="$1" @@ -2655,14 +2366,12 @@ _getdeployconf() { if [ "$_rac_value" ]; then if _startswith "$_rac_value" '"' && _endswith "$_rac_value" '"'; then _debug2 "trim quotation marks" - eval $_rac_key=$_rac_value - export $_rac_key + eval "export $_rac_key=$_rac_value" fi return 0 # do nothing fi - _saved="$(_readdomainconf "SAVED_$_rac_key")" - eval $_rac_key=\$_saved - export $_rac_key + _saved=$(_readdomainconf "SAVED_$_rac_key") + eval "export $_rac_key=\"\$_saved\"" } #_saveaccountconf key value base64encode @@ -2715,21 +2424,6 @@ _clearcaconf() { _clear_conf "$CA_CONF" "$1" } -#Starts a socat listener in the background, the pid is set to _socat_pid. -#It uses the content, _content_len, _NC and _SOCAT_ERR of _startserver. -#options -_startsocat() { - _socat_opts="$1" - _debug "_NC" "$_NC $_socat_opts" - $_NC $_socat_opts SYSTEM:"sleep 1; \ -echo 'HTTP/1.0 200 OK'; \ -echo 'Content-Length\: $_content_len'; \ -echo ''; \ -printf '%s' '$content';" 2>>"$_SOCAT_ERR" & - _socat_pid="$!" - _debug "_socat_pid" "$_socat_pid" -} - # content localaddress _startserver() { content="$1" @@ -2743,130 +2437,43 @@ _startserver() { _debug Le_Listen_V4 "$Le_Listen_V4" _debug Le_Listen_V6 "$Le_Listen_V6" - _serverproc_v6="" - if _exists "socat"; then - _NC="socat" - SOCAT_OPTIONS6="" - if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then - _NC="$_NC -6" - SOCAT_OPTIONS=TCP6-LISTEN - elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then - _NC="$_NC -4" - SOCAT_OPTIONS=TCP4-LISTEN - elif [ "$ncaddr" ]; then - #a single local address belongs to a single family, let socat pick it - SOCAT_OPTIONS=TCP-LISTEN - else - #listen on both ipv4 and ipv6, with one socket for each family: - #ipv4-mapped ipv6 addresses are not available everywhere. - SOCAT_OPTIONS=TCP4-LISTEN - SOCAT_OPTIONS6=TCP6-LISTEN - fi - - if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then - _NC="$_NC -d -d -v" - fi - - SOCAT_OPTIONS=$SOCAT_OPTIONS:$Le_HTTPPort,crlf,reuseaddr,fork - if [ "$SOCAT_OPTIONS6" ]; then - #ipv6only keeps this socket from colliding with the ipv4 one - SOCAT_OPTIONS6=$SOCAT_OPTIONS6:$Le_HTTPPort,crlf,reuseaddr,fork,ipv6only=1 - fi - - #Adding bind to local-address - if [ "$ncaddr" ]; then - SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}" - fi - - _content_len="$(printf "%s" "$content" | wc -c)" - _debug _content_len "$_content_len" - export _SOCAT_ERR="$(_mktemp)" - _startsocat "$SOCAT_OPTIONS" - serverproc="$_socat_pid" - if [ "$SOCAT_OPTIONS6" ]; then - #best effort, the host may have no ipv6 support at all - _startsocat "$SOCAT_OPTIONS6" - _serverproc_v6="$_socat_pid" - fi - else - _PYTHON="" - if _exists "python3"; then - _PYTHON="python3" - elif _exists "python2"; then - _PYTHON="python2" - elif _exists "python"; then - _PYTHON="python" - fi - if [ "$_PYTHON" ]; then - _debug "Using python: $_PYTHON" - #a comma separated list of addresses to listen on, one socket for each - _BIND_ADDR="0.0.0.0,::" - if [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then - _BIND_ADDR="::" - elif [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then - _BIND_ADDR="0.0.0.0" - fi - if [ "$ncaddr" ]; then - _BIND_ADDR="$ncaddr" - fi - _debug "_BIND_ADDR" "$_BIND_ADDR" - export _SOCAT_ERR="$(_mktemp)" - $_PYTHON -c "import socket,sys,select -res='HTTP/1.0 200 OK\r\nContent-Length: '+str(len(sys.argv[3]))+'\r\n\r\n'+sys.argv[3] -ads=sys.argv[2].split(',') -ls=[] -for ad in ads: - try: - sk=socket.socket(socket.AF_INET6 if ':' in ad else socket.AF_INET,socket.SOCK_STREAM) - sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) - if ':' in ad and len(ads)>1: - sk.setsockopt(socket.IPPROTO_IPV6,socket.IPV6_V6ONLY,1) - sk.bind((ad,int(sys.argv[1]))) - sk.listen(5) - ls.append(sk) - except Exception: - sys.stderr.write(str(sys.exc_info()[1])+'\n') -if not ls: - sys.exit(1) -while True: - for sk in select.select(ls,[],[])[0]: - c,a=sk.accept() - c.sendall(res.encode() if hasattr(res, 'encode') else res) - c.close()" "$Le_HTTPPort" "$_BIND_ADDR" "$content" 2>"$_SOCAT_ERR" & - serverproc="$!" - _NC="$_PYTHON" - else - _err "Please install socat or python first for standalone mode." - return 1 - fi + _NC="socat" + if [ "$Le_Listen_V4" ]; then + _NC="$_NC -4" + elif [ "$Le_Listen_V6" ]; then + _NC="$_NC -6" fi - if [ -f "$_SOCAT_ERR" ]; then - if grep "Permission denied" "$_SOCAT_ERR" >/dev/null; then - _err "$_NC: $(cat $_SOCAT_ERR)" - _err "Can not listen for user: $(whoami)" - _err "Maybe try with root again?" - rm -f "$_SOCAT_ERR" - return 1 - fi + if [ "$DEBUG" ] && [ "$DEBUG" -gt "1" ]; then + _NC="$_NC -d -d -v" fi + + SOCAT_OPTIONS=TCP-LISTEN:$Le_HTTPPort,crlf,reuseaddr,fork + + #Adding bind to local-address + if [ "$ncaddr" ]; then + SOCAT_OPTIONS="$SOCAT_OPTIONS,bind=${ncaddr}" + fi + + _content_len="$(printf "%s" "$content" | wc -c)" + _debug _content_len "$_content_len" + _debug "_NC" "$_NC $SOCAT_OPTIONS" + $_NC $SOCAT_OPTIONS SYSTEM:"sleep 1; \ +echo 'HTTP/1.0 200 OK'; \ +echo 'Content-Length\: $_content_len'; \ +echo ''; \ +printf '%s' '$content';" & + serverproc="$!" } _stopserver() { pid="$1" _debug "pid" "$pid" - if [ "$_serverproc_v6" ]; then - _debug "_serverproc_v6" "$_serverproc_v6" - kill $_serverproc_v6 >/dev/null 2>&1 - _serverproc_v6="" - fi if [ -z "$pid" ]; then - rm -f "$_SOCAT_ERR" return fi kill $pid - rm -f "$_SOCAT_ERR" } @@ -2904,7 +2511,7 @@ _starttlsserver() { #create key TLS_KEY if ! _createkey "2048" "$TLS_KEY"; then - _err "Error creating TLS validation key." + _err "Create tls validation key error." return 1 fi @@ -2914,13 +2521,13 @@ _starttlsserver() { alt="$alt,$san_b" fi if ! _createcsr "tls.acme.sh" "$alt" "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$acmeValidationv1"; then - _err "Error creating TLS validation CSR." + _err "Create tls validation csr error." return 1 fi #self signed if ! _signcsr "$TLS_KEY" "$TLS_CSR" "$TLS_CONF" "$TLS_CERT"; then - _err "Error creating TLS validation cert." + _err "Create tls validation cert error." return 1 fi @@ -2933,11 +2540,9 @@ _starttlsserver() { _debug Le_Listen_V4 "$Le_Listen_V4" _debug Le_Listen_V6 "$Le_Listen_V6" - #openssl s_server binds a single socket, so both options together can only - #mean: do not force a family, same as when neither of them is given. - if [ "$Le_Listen_V4" ] && [ -z "$Le_Listen_V6" ]; then + if [ "$Le_Listen_V4" ]; then __S_OPENSSL="$__S_OPENSSL -4" - elif [ "$Le_Listen_V6" ] && [ -z "$Le_Listen_V4" ]; then + elif [ "$Le_Listen_V6" ]; then __S_OPENSSL="$__S_OPENSSL -6" fi @@ -2976,7 +2581,7 @@ _conapath() { __initHome() { if [ -z "$_SCRIPT_HOME" ]; then if _exists readlink && _exists dirname; then - _debug "Let's find the script directory." + _debug "Lets find script dir." _debug "_SCRIPT_" "$_SCRIPT_" _script="$(_readlink "$_SCRIPT_")" _debug "_script" "$_script" @@ -2985,7 +2590,7 @@ __initHome() { if [ -d "$_script_home" ]; then export _SCRIPT_HOME="$_script_home" else - _err "It seems the script home is not correct: $_script_home" + _err "It seems the script home is not correct:$_script_home" fi fi fi @@ -3000,48 +2605,17 @@ __initHome() { # fi if [ -z "$LE_WORKING_DIR" ]; then - _debug "Using default home: $DEFAULT_INSTALL_HOME" + _debug "Using default home:$DEFAULT_INSTALL_HOME" LE_WORKING_DIR="$DEFAULT_INSTALL_HOME" fi - # Convert a relative --home to an absolute path: later code cd's around - # (e.g. installOnline extracts and enters the archive dir), where a - # relative path would point into the wrong directory. - # https://github.com/acmesh-official/acme.sh/issues/6477 - case "$LE_WORKING_DIR" in - /*) ;; - *) - if [ -d "$LE_WORKING_DIR" ]; then - LE_WORKING_DIR="$(cd "$LE_WORKING_DIR" && pwd)" - fi - ;; - esac export LE_WORKING_DIR if [ -z "$LE_CONFIG_HOME" ]; then LE_CONFIG_HOME="$LE_WORKING_DIR" fi - case "$LE_CONFIG_HOME" in - /*) ;; - *) - if [ -d "$LE_CONFIG_HOME" ]; then - LE_CONFIG_HOME="$(cd "$LE_CONFIG_HOME" && pwd)" - fi - ;; - esac - _debug "Using config home: $LE_CONFIG_HOME" + _debug "Using config home:$LE_CONFIG_HOME" export LE_CONFIG_HOME - # Paths with whitespace break the unquoted $_CURL/$_WGET command expansion, - # so fail early with a clear error instead of a cryptic curl/wget failure. - # https://github.com/acmesh-official/acme.sh/issues/2163 - case "$LE_WORKING_DIR$LE_CONFIG_HOME" in - *" "*) - _err "The --home or --config-home path can not contain spaces: '$LE_WORKING_DIR'" - _err "Please install $PROJECT_NAME to a path without spaces." - exit 1 - ;; - esac - _DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf" if [ -z "$ACCOUNT_CONF_PATH" ]; then @@ -3071,24 +2645,23 @@ _clearAPI() { ACME_REVOKE_CERT="" ACME_NEW_NONCE="" ACME_AGREEMENT="" - ACME_RENEWAL_INFO="" } #server _initAPI() { _api_server="${1:-$ACME_DIRECTORY}" - _debug "_init API for server: $_api_server" + _debug "_init api for server: $_api_server" MAX_API_RETRY_TIMES=10 _sleep_retry_sec=10 _request_retry_times=0 while [ -z "$ACME_NEW_ACCOUNT" ] && [ "${_request_retry_times}" -lt "$MAX_API_RETRY_TIMES" ]; do _request_retry_times=$(_math "$_request_retry_times" + 1) - response=$(_get "$_api_server" "" 10) + response=$(_get "$_api_server") if [ "$?" != "0" ]; then _debug2 "response" "$response" - _info "Cannot init API for: $_api_server." - _info "Sleeping for $_sleep_retry_sec seconds and retrying." + _info "Can not init api for: $_api_server." + _info "Sleep $_sleep_retry_sec and retry." _sleep "$_sleep_retry_sec" continue fi @@ -3116,9 +2689,6 @@ _initAPI() { ACME_AGREEMENT=$(echo "$response" | _egrep_o 'termsOfService" *: *"[^"]*"' | cut -d '"' -f 3) export ACME_AGREEMENT - ACME_RENEWAL_INFO=$(echo "$response" | _egrep_o 'renewalInfo" *: *"[^"]*"' | cut -d '"' -f 3) - export ACME_RENEWAL_INFO - _debug "ACME_KEY_CHANGE" "$ACME_KEY_CHANGE" _debug "ACME_NEW_AUTHZ" "$ACME_NEW_AUTHZ" _debug "ACME_NEW_ORDER" "$ACME_NEW_ORDER" @@ -3126,22 +2696,16 @@ _initAPI() { _debug "ACME_REVOKE_CERT" "$ACME_REVOKE_CERT" _debug "ACME_AGREEMENT" "$ACME_AGREEMENT" _debug "ACME_NEW_NONCE" "$ACME_NEW_NONCE" - _debug "ACME_RENEWAL_INFO" "$ACME_RENEWAL_INFO" if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then return 0 fi - _info "Sleeping for $_sleep_retry_sec seconds and retrying." + _info "Sleep $_sleep_retry_sec and retry." _sleep "$_sleep_retry_sec" done if [ "$ACME_NEW_ACCOUNT" ] && [ "$ACME_NEW_ORDER" ]; then return 0 fi - _err "Cannot init API for $_api_server" - if [ "$_api_server" = "$CA_ZEROSSL" ]; then - _info "$(__green "If this host is IPv6-only: ZeroSSL currently has no IPv6 endpoint.")" - _info "$(__green "Try another CA, e.g.: $PROJECT_ENTRY --set-default-ca --server letsencrypt")" - _info "See: $(__green "https://github.com/acmesh-official/acme.sh/issues/6872")" - fi + _err "Can not init api, for $_api_server" return 1 } @@ -3271,14 +2835,12 @@ _initpath() { if _isEccKey "$_ilength"; then DOMAIN_PATH="$domainhomeecc" - elif [ -z "$__SELECTED_RSA_KEY" ]; then + else if [ ! -d "$domainhome" ] && [ -d "$domainhomeecc" ]; then - _info "The domain '$domain' seems to already have an ECC cert, let's use it." - DOMAIN_PATH="$domainhomeecc" + _info "The domain '$domain' seems to have a ECC cert already, please add '$(__red "--ecc")' parameter if you want to use that cert." fi fi _debug DOMAIN_PATH "$DOMAIN_PATH" - export DOMAIN_PATH fi if [ -z "$DOMAIN_BACKUP_PATH" ]; then @@ -3330,19 +2892,36 @@ _initpath() { } +_exec() { + if [ -z "$_EXEC_TEMP_ERR" ]; then + _EXEC_TEMP_ERR="$(_mktemp)" + fi + + if [ "$_EXEC_TEMP_ERR" ]; then + eval "$@ 2>>$_EXEC_TEMP_ERR" + else + eval "$@" + fi +} + +_exec_err() { + [ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" && echo "" >"$_EXEC_TEMP_ERR" +} + _apachePath() { _APACHECTL="apachectl" if ! _exists apachectl; then if _exists apache2ctl; then _APACHECTL="apache2ctl" else - _err "'apachectl not found. It seems that Apache is not installed or you are not root.'" + _err "'apachectl not found. It seems that apache is not installed, or you are not root user.'" _err "Please use webroot mode to try again." return 1 fi fi - if ! $_APACHECTL -V >/dev/null; then + if ! _exec $_APACHECTL -V >/dev/null; then + _exec_err return 1 fi @@ -3355,7 +2934,7 @@ _apachePath() { _debug httpdconfname "$httpdconfname" if [ -z "$httpdconfname" ]; then - _err "Cannot read Apache config file." + _err "Can not read apache config file." return 1 fi @@ -3372,7 +2951,7 @@ _apachePath() { _debug httpdconf "$httpdconf" _debug httpdconfname "$httpdconfname" if [ ! -f "$httpdconf" ]; then - _err "Apache config file not found" "$httpdconf" + _err "Apache Config file not found" "$httpdconf" return 1 fi return 0 @@ -3394,8 +2973,9 @@ _restoreApache() { cat "$APACHE_CONF_BACKUP_DIR/$httpdconfname" >"$httpdconf" _debug "Restored: $httpdconf." - if ! $_APACHECTL -t; then - _err "Sorry, there's been an error restoring the Apache config. Please ask for support on $PROJECT." + if ! _exec $_APACHECTL -t; then + _exec_err + _err "Sorry, restore apache config error, please contact me." return 1 fi _debug "Restored successfully." @@ -3410,26 +2990,27 @@ _setApache() { fi #test the conf first - _info "Checking if there is an error in the Apache config file before starting." + _info "Checking if there is an error in the apache config file before starting." - if ! $_APACHECTL -t >/dev/null; then - _err "The Apache config file has errors, please fix them first then try again." - _err "Don't worry, no changes to your system have been made." + if ! _exec "$_APACHECTL" -t >/dev/null; then + _exec_err + _err "The apache config file has error, please fix it first, then try again." + _err "Don't worry, there is nothing changed to your system." return 1 else _info "OK" fi #backup the conf - _debug "Backing up Apache config file" "$httpdconf" + _debug "Backup apache config file" "$httpdconf" if ! cp "$httpdconf" "$APACHE_CONF_BACKUP_DIR/"; then - _err "Cannot backup Apache config file, aborting. Don't worry, the Apache config has not been changed." - _err "This might be an $PROJECT_NAME bug, please open an issue on $PROJECT" + _err "Can not backup apache config file, so abort. Don't worry, the apache config is not changed." + _err "This might be a bug of $PROJECT_NAME , please report issue: $PROJECT" return 1 fi - _info "Config file $httpdconf has been backed up to $APACHE_CONF_BACKUP_DIR/$httpdconfname" - _info "In case an error causes it to not be restored automatically, you can restore it yourself." - _info "You do not need to do anything on success, as the backup file will automatically be deleted." + _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname" + _info "In case there is an error that can not be restored automatically, you may try restore it yourself." + _info "The backup file will be deleted on success, just forget it." #add alias @@ -3459,11 +3040,11 @@ Allow from all _msg="$($_APACHECTL -t 2>&1)" if [ "$?" != "0" ]; then - _err "Sorry, an Apache config error has occurred" + _err "Sorry, apache config error" if _restoreApache; then - _err "The Apache config file has been restored." + _err "The apache config file is restored." else - _err "Sorry, the Apache config file cannot be restored, please open an issue on $PROJECT." + _err "Sorry, the apache config file can not be restored, please report bug." fi return 1 fi @@ -3473,8 +3054,9 @@ Allow from all chmod 755 "$ACME_DIR" fi - if ! $_APACHECTL graceful; then - _err "$_APACHECTL graceful error, please open an issue on $PROJECT." + if ! _exec "$_APACHECTL" graceful; then + _exec_err + _err "$_APACHECTL graceful error, please contact me." _restoreApache return 1 fi @@ -3498,18 +3080,18 @@ _setNginx() { _start_f="$(echo "$_croot" | cut -d : -f 2)" _debug _start_f "$_start_f" if [ -z "$_start_f" ]; then - _debug "Finding config using the nginx command" + _debug "find start conf from nginx command" if [ -z "$NGINX_CONF" ]; then if ! _exists "nginx"; then - _err "nginx command not found." + _err "nginx command is not found." return 1 fi - NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "\-\-conf-path=[^ ]* " | tr -d " ")" + NGINX_CONF="$(nginx -V 2>&1 | _egrep_o "--conf-path=[^ ]* " | tr -d " ")" _debug NGINX_CONF "$NGINX_CONF" NGINX_CONF="$(echo "$NGINX_CONF" | cut -d = -f 2)" _debug NGINX_CONF "$NGINX_CONF" if [ -z "$NGINX_CONF" ]; then - _err "Cannot find nginx config." + _err "Can not find nginx conf." NGINX_CONF="" return 1 fi @@ -3518,16 +3100,16 @@ _setNginx() { NGINX_CONF="" return 1 fi - _debug "Found nginx config file: $NGINX_CONF" + _debug "Found nginx conf file:$NGINX_CONF" fi _start_f="$NGINX_CONF" fi - _debug "Detecting nginx conf for $_d from: $_start_f" + _debug "Start detect nginx conf for $_d from:$_start_f" if ! _checkConf "$_d" "$_start_f"; then - _err "Cannot find config file for domain $d" + _err "Can not find conf file for domain $d" return 1 fi - _info "Found config file: $FOUND_REAL_NGINX_CONF" + _info "Found conf file: $FOUND_REAL_NGINX_CONF" _ln=$FOUND_REAL_NGINX_CONF_LN _debug "_ln" "$_ln" @@ -3537,7 +3119,7 @@ _setNginx() { _start_tag="$(sed -n "$_lnn,${_lnn}p" "$FOUND_REAL_NGINX_CONF")" _debug "_start_tag" "$_start_tag" if [ "$_start_tag" = "$NGINX_START" ]; then - _info "The domain $_d is already configured, skipping" + _info "The domain $_d is already configured, skip" FOUND_REAL_NGINX_CONF="" return 0 fi @@ -3546,60 +3128,57 @@ _setNginx() { _backup_conf="$DOMAIN_BACKUP_PATH/$_d.nginx.conf" _debug _backup_conf "$_backup_conf" BACKUP_NGINX_CONF="$_backup_conf" - _info "Backing $FOUND_REAL_NGINX_CONF up to $_backup_conf" + _info "Backup $FOUND_REAL_NGINX_CONF to $_backup_conf" if ! cp "$FOUND_REAL_NGINX_CONF" "$_backup_conf"; then - _err "Backup error." + _err "backup error." FOUND_REAL_NGINX_CONF="" return 1 fi if ! _exists "nginx"; then - _err "nginx command not found." + _err "nginx command is not found." return 1 fi - _info "Checking the nginx config before setting up." - if ! nginx -t >/dev/null 2>&1; then - _err "It seems that the nginx config is not correct, cannot continue." + _info "Check the nginx conf before setting up." + if ! _exec "nginx -t" >/dev/null; then + _exec_err return 1 fi - _info "OK, setting up the nginx config file" + _info "OK, Set up nginx config file" if ! sed -n "1,${_ln}p" "$_backup_conf" >"$FOUND_REAL_NGINX_CONF"; then cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" - _err "Error writing nginx config. Restoring it to its original version." + _err "write nginx conf error, but don't worry, the file is restored to the original version." return 1 fi echo "$NGINX_START -location ^~ /.well-known/acme-challenge/ { - # the ^~ prefix wins over regex-skipping blocks like \"location ^~ /\", - # the nested regex location still captures the token as \$1 - location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" { - default_type text/plain; - return 200 \"\$1.$_thumbpt\"; - } - return 404; +location ~ \"^/\.well-known/acme-challenge/([-_a-zA-Z0-9]+)\$\" { + default_type text/plain; + return 200 \"\$1.$_thumbpt\"; } #NGINX_START " >>"$FOUND_REAL_NGINX_CONF" if ! sed -n "${_lnn},99999p" "$_backup_conf" >>"$FOUND_REAL_NGINX_CONF"; then cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" - _err "Error writing nginx config. Restoring it to its original version." + _err "write nginx conf error, but don't worry, the file is restored." return 1 fi _debug3 "Modified config:$(cat $FOUND_REAL_NGINX_CONF)" - _info "nginx config has been written, let's check it again." - if ! nginx -t >/dev/null 2>&1; then - _err "There seems to be a problem with the nginx config, let's restore it to its original version." + _info "nginx conf is done, let's check it again." + if ! _exec "nginx -t" >/dev/null; then + _exec_err + _err "It seems that nginx conf was broken, let's restore." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 fi - _info "Reloading nginx" - if ! nginx -s reload >/dev/null 2>&1; then - _err "There seems to be a problem with the nginx config, let's restore it to its original version." + _info "Reload nginx" + if ! _exec "nginx -s reload" >/dev/null; then + _exec_err + _err "It seems that nginx reload error, let's restore." cat "$_backup_conf" >"$FOUND_REAL_NGINX_CONF" return 1 fi @@ -3611,7 +3190,7 @@ location ^~ /.well-known/acme-challenge/ { _checkConf() { _d="$1" _c_file="$2" - _debug "Starting _checkConf from: $_c_file" + _debug "Start _checkConf from:$_c_file" if [ ! -f "$2" ] && ! echo "$2" | grep '*$' >/dev/null && echo "$2" | grep '*' >/dev/null; then _debug "wildcard" for _w_f in $2; do @@ -3624,14 +3203,14 @@ _checkConf() { elif [ -f "$2" ]; then _debug "single" if _isRealNginxConf "$1" "$2"; then - _debug "$2 found." + _debug "$2 is found." FOUND_REAL_NGINX_CONF="$2" return 0 fi if cat "$2" | tr "\t" " " | grep "^ *include *.*;" >/dev/null; then - _debug "Trying include files" + _debug "Try include files" for included in $(cat "$2" | tr "\t" " " | grep "^ *include *.*;" | sed "s/include //" | tr -d " ;"); do - _debug "Checking included $included" + _debug "check included $included" if ! _startswith "$included" "/" && _exists dirname; then _relpath="$(dirname "$2")" _debug "_relpath" "$_relpath" @@ -3707,7 +3286,7 @@ _isRealNginxConf() { #restore all the nginx conf _restoreNginx() { if [ -z "$NGINX_RESTORE_VLIST" ]; then - _debug "No need to restore nginx config, skipping." + _debug "No need to restore nginx, skip." return fi _debug "_restoreNginx" @@ -3722,9 +3301,10 @@ _restoreNginx() { cat "$_ngbackupconf" >"$_ngconf" done - _info "Reloading nginx" - if ! nginx -s reload >/dev/null 2>&1; then - _err "An error occurred while reloading nginx, please open an issue on $PROJECT." + _info "Reload nginx" + if ! _exec "nginx -s reload" >/dev/null; then + _exec_err + _err "It seems that nginx reload error, please report bug." return 1 fi return 0 @@ -3749,7 +3329,7 @@ _clearupdns() { _debug "dns_entries" "$dns_entries" if [ -z "$dns_entries" ]; then - _debug "Skipping dns." + _debug "skip dns." return fi _info "Removing DNS records." @@ -3772,7 +3352,7 @@ _clearupdns() { fi if [ -z "$d_api" ]; then - _info "Domain API file was not found: $d_api" + _info "Not Found domain api file: $d_api" continue fi @@ -3782,21 +3362,21 @@ _clearupdns() { ( if ! . "$d_api"; then - _err "Error loading file $d_api. Please check your API file and try again." + _err "Load file $d_api error. Please check your api file and try again." return 1 fi rmcommand="${_currentRoot}_rm" if ! _exists "$rmcommand"; then - _err "It seems that your API file doesn't define $rmcommand" + _err "It seems that your api file doesn't define $rmcommand" return 1 fi _info "Removing txt: $txt for domain: $txtdomain" if ! $rmcommand "$txtdomain" "$txt"; then - _err "Error removing txt for domain: $txtdomain" + _err "Error removing txt for domain:$txtdomain" return 1 fi - _info "Successfully removed" + _info "Removed: Success" ) done @@ -3806,7 +3386,7 @@ _clearupdns() { _clearupwebbroot() { __webroot="$1" if [ -z "$__webroot" ]; then - _debug "No webroot specified, skipping" + _debug "no webroot specified, skip" return 0 fi @@ -3818,12 +3398,12 @@ _clearupwebbroot() { elif [ "$2" = '3' ]; then _rmpath="$__webroot/.well-known/acme-challenge/$3" else - _debug "Skipping for removelevel: $2" + _debug "Skip for removelevel:$2" fi if [ "$_rmpath" ]; then if [ "$DEBUG" ]; then - _debug "Debugging, not removing: $_rmpath" + _debug "Debugging, skip removing: $_rmpath" else rm -rf "$_rmpath" fi @@ -3844,20 +3424,20 @@ _on_before_issue() { _debug _chk_alt_domains "$_chk_alt_domains" #run pre hook if [ "$_chk_pre_hook" ]; then - _info "Running pre hook:'$_chk_pre_hook'" + _info "Run pre hook:'$_chk_pre_hook'" if ! ( export Le_Domain="$_chk_main_domain" export Le_Alt="$_chk_alt_domains" cd "$DOMAIN_PATH" && eval "$_chk_pre_hook" ); then - _err "Error occurred when running pre hook." + _err "Error when run pre hook." return 1 fi fi - if _hasfield "$_chk_web_roots" "$NO_VALUE" && [ "$_chk_web_roots" = "$NO_VALUE" ]; then - if ! _exists "socat" && ! _exists "python" && ! _exists "python2" && ! _exists "python3"; then - _err "Please install socat or python tools first." + if _hasfield "$_chk_web_roots" "$NO_VALUE"; then + if ! _exists "socat"; then + _err "Please install socat tools first." return 1 fi fi @@ -3875,7 +3455,7 @@ _on_before_issue() { if [ -z "$d" ]; then break fi - _debug "Checking for domain" "$d" + _debug "Check for domain" "$d" _currentRoot="$(_getfield "$_chk_web_roots" $_index)" _debug "_currentRoot" "$_currentRoot" _index=$(_math $_index + 1) @@ -3922,7 +3502,7 @@ _on_before_issue() { if _hasfield "$_chk_web_roots" "apache"; then if ! _setApache; then - _err "Error setting up Apache. Please open an issue on $PROJECT." + _err "set up apache error. Report error to me." return 1 fi else @@ -3939,17 +3519,17 @@ _on_issue_err() { if [ "$LOG_FILE" ]; then _err "Please check log file for more details: $LOG_FILE" else - _err "Please add '--debug' or '--log' to see more information." + _err "Please add '--debug' or '--log' to check more details." _err "See: $_DEBUG_WIKI" fi #run the post hook if [ "$_chk_post_hook" ]; then - _info "Running post hook: '$_chk_post_hook'" + _info "Run post hook:'$_chk_post_hook'" if ! ( cd "$DOMAIN_PATH" && eval "$_chk_post_hook" ); then - _err "Error encountered while running post hook." + _err "Error when run post hook." return 1 fi fi @@ -3988,7 +3568,7 @@ _on_issue_success() { #run the post hook if [ "$_chk_post_hook" ]; then - _info "Running post hook:'$_chk_post_hook'" + _info "Run post hook:'$_chk_post_hook'" if ! ( export CERT_PATH export CERT_KEY_PATH @@ -3997,14 +3577,14 @@ _on_issue_success() { export Le_Domain="$_main_domain" cd "$DOMAIN_PATH" && eval "$_chk_post_hook" ); then - _err "Error encountered while running post hook." + _err "Error when run post hook." return 1 fi fi #run renew hook if [ "$_ACME_IS_RENEW" ] && [ "$_chk_renew_hook" ]; then - _info "Running renew hook: '$_chk_renew_hook'" + _info "Run renew hook:'$_chk_renew_hook'" if ! ( export CERT_PATH export CERT_KEY_PATH @@ -4013,7 +3593,7 @@ _on_issue_success() { export Le_Domain="$_main_domain" cd "$DOMAIN_PATH" && eval "$_chk_renew_hook" ); then - _err "Error encountered while running renew hook." + _err "Error when run renew hook." return 1 fi fi @@ -4027,10 +3607,10 @@ _on_issue_success() { #account_key_length eab-kid eab-hmac-key registeraccount() { _account_key_length="$1" - _eab_kid="$2" + _eab_id="$2" _eab_hmac_key="$3" _initpath - _regAccount "$_account_key_length" "$_eab_kid" "$_eab_hmac_key" + _regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key" } __calcAccountKeyHash() { @@ -4041,16 +3621,6 @@ __calc_account_thumbprint() { printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace } -#Reads a comma- or space-separated email list from stdin and prints -#the ACME contact list items: "mailto:a@example.com","mailto:b@example.com" -_mailto_contacts() { - _mc_out="" - for _mc_m in $(tr ',' ' '); do - _mc_out="$_mc_out,\"mailto:$_mc_m\"" - done - echo "$_mc_out" | cut -c 2- -} - _getAccountEmail() { if [ "$ACCOUNT_EMAIL" ]; then echo "$ACCOUNT_EMAIL" @@ -4070,16 +3640,16 @@ _getAccountEmail() { _regAccount() { _initpath _reg_length="$1" - _eab_kid="$2" + _eab_id="$2" _eab_hmac_key="$3" _debug3 _regAccount "$_regAccount" _initAPI mkdir -p "$CA_DIR" - if [ ! -s "$ACCOUNT_KEY_PATH" ]; then + if [ ! -f "$ACCOUNT_KEY_PATH" ]; then if ! _create_account_key "$_reg_length"; then - _err "Error creating account key." + _err "Create account key error." return 1 fi fi @@ -4087,13 +3657,13 @@ _regAccount() { if ! _calcjwk "$ACCOUNT_KEY_PATH"; then return 1 fi - if [ "$_eab_kid" ] && [ "$_eab_hmac_key" ]; then - _savecaconf CA_EAB_KEY_ID "$_eab_kid" + if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then + _savecaconf CA_EAB_KEY_ID "$_eab_id" _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" fi - _eab_kid=$(_readcaconf "CA_EAB_KEY_ID") + _eab_id=$(_readcaconf "CA_EAB_KEY_ID") _eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY") - _secure_debug3 _eab_kid "$_eab_kid" + _secure_debug3 _eab_id "$_eab_id" _secure_debug3 _eab_hmac_key "$_eab_hmac_key" _email="$(_getAccountEmail)" if [ "$_email" ]; then @@ -4101,8 +3671,8 @@ _regAccount() { fi if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then - if [ -z "$_eab_kid" ] || [ -z "$_eab_hmac_key" ]; then - _info "No EAB credentials found for ZeroSSL, let's obtain them" + if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then + _info "No EAB credentials found for ZeroSSL, let's get one" if [ -z "$_email" ]; then _info "$(__green "$PROJECT_NAME is using ZeroSSL as default CA now.")" _info "$(__green "Please update your account with an email address first.")" @@ -4110,33 +3680,31 @@ _regAccount() { _info "See: $(__green "$_ZEROSSL_WIKI")" return 1 fi - #the ZeroSSL EAB endpoint takes a single address, use the first one - _eab_email="$(echo "$_email" | tr ',' ' ' | awk '{print $1}')" - _eabresp=$(_post "email=$_eab_email" $_ZERO_EAB_ENDPOINT) + _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT) if [ "$?" != "0" ]; then _debug2 "$_eabresp" - _err "Cannot get EAB credentials from ZeroSSL." + _err "Can not get EAB credentials from ZeroSSL." return 1 fi _secure_debug2 _eabresp "$_eabresp" - _eab_kid="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')" - _secure_debug2 _eab_kid "$_eab_kid" - if [ -z "$_eab_kid" ]; then - _err "Cannot resolve _eab_kid" + _eab_id="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')" + _secure_debug2 _eab_id "$_eab_id" + if [ -z "$_eab_id" ]; then + _err "Can not resolve _eab_id" return 1 fi _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')" _secure_debug2 _eab_hmac_key "$_eab_hmac_key" if [ -z "$_eab_hmac_key" ]; then - _err "Cannot resolve _eab_hmac_key" + _err "Can not resolve _eab_hmac_key" return 1 fi - _savecaconf CA_EAB_KEY_ID "$_eab_kid" + _savecaconf CA_EAB_KEY_ID "$_eab_id" _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" fi fi - if [ "$_eab_kid" ] && [ "$_eab_hmac_key" ]; then - eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_kid\",\"url\":\"${ACME_NEW_ACCOUNT}\"}" + if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then + eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}" _debug3 eab_protected "$eab_protected" eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace) @@ -4148,12 +3716,8 @@ _regAccount() { eab_sign_t="$eab_protected64.$eab_payload64" _debug3 eab_sign_t "$eab_sign_t" - key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')" + key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 multi | _hex_dump | tr -d ' ')" _debug3 key_hex "$key_hex" - if [ -z "$key_hex" ]; then - _err "Cannot base64-decode the eab-hmac-key. Please check the value, and your openssl version." - return 1 - fi eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace) _debug3 eab_signature "$eab_signature" @@ -4162,14 +3726,14 @@ _regAccount() { _debug3 externalBinding "$externalBinding" fi if [ "$_email" ]; then - email_sg="\"contact\": [$(echo "$_email" | _mailto_contacts)], " + email_sg="\"contact\": [\"mailto:$_email\"], " fi regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}" _info "Registering account: $ACME_DIRECTORY" if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then - _err "Error registering account: $response" + _err "Register account Error: $response" return 1 fi @@ -4180,10 +3744,10 @@ _regAccount() { elif [ "$code" = '409' ] || [ "$code" = '200' ]; then _info "Already registered" elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then - _info "EAB already registered" + _info "Already register EAB." _eabAlreadyBound=1 else - _err "Account registration error: $response" + _err "Register account Error: $response" return 1 fi @@ -4192,13 +3756,13 @@ _regAccount() { _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")" _debug "_accUri" "$_accUri" if [ -z "$_accUri" ]; then - _err "Cannot find account id url." + _err "Can not find account id url." _err "$responseHeaders" return 1 fi _savecaconf "ACCOUNT_URL" "$_accUri" else - _accUri="$(_readcaconf ACCOUNT_URL)" + ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)" fi export ACCOUNT_URL="$_accUri" @@ -4206,10 +3770,8 @@ _regAccount() { _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH" _savecaconf CA_KEY_HASH "$CA_KEY_HASH" - #RFC 8555 sec 7.3.6 requires 401 for requests from a deactivated account, - #but Boulder (Let's Encrypt) historically returns 403. Accept both. - if [ "$code" = '403' ] || [ "$code" = '401' ]; then - _err "It seems that the account key has been deactivated, please use a new account key." + if [ "$code" = '403' ]; then + _err "It seems that the account key is already deactivated, please use a new account key." return 1 fi @@ -4222,7 +3784,7 @@ updateaccount() { _initpath if [ ! -f "$ACCOUNT_KEY_PATH" ]; then - _err "Account key not found at: $ACCOUNT_KEY_PATH" + _err "Account key is not found at: $ACCOUNT_KEY_PATH" return 1 fi @@ -4230,7 +3792,8 @@ updateaccount() { _debug _accUri "$_accUri" if [ -z "$_accUri" ]; then - _err "The account URL is empty, please run '--update-account' first to update the account info, then try again." + _err "The account url is empty, please run '--update-account' first to update the account info first," + _err "Then try again." return 1 fi @@ -4242,7 +3805,7 @@ updateaccount() { _email="$(_getAccountEmail)" if [ "$_email" ]; then - updjson='{"contact": ['$(echo "$_email" | _mailto_contacts)']}' + updjson='{"contact": ["mailto:'$_email'"]}' else updjson='{"contact": []}' fi @@ -4252,114 +3815,18 @@ updateaccount() { if [ "$code" = '200' ]; then echo "$response" >"$ACCOUNT_JSON_PATH" _info "Account update success for $_accUri." - # persist the effective mailbox like _regAccount does; otherwise - # "--update-account -m new@..." updates the CA but the local conf - # keeps showing the old address (issue 4673) - if [ "$_email" ]; then - _savecaconf "CA_EMAIL" "$_email" - fi - - ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)" - _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT" else - _info "An error occurred and the account was not updated." + _info "Error. The account was not updated." return 1 fi } -#Implement account key rollover -updateaccountkey() { - _length="$1" - _initpath - - if [ ! -f "$ACCOUNT_KEY_PATH" ]; then - _err "Account key not found at: $ACCOUNT_KEY_PATH" - return 1 - fi - ACCOUNT_KEY_PATH_NEW="$ACCOUNT_KEY_PATH.new" - - _accUri=$(_readcaconf "ACCOUNT_URL") - _debug _accUri "$_accUri" - - if [ -z "$_accUri" ]; then - _err "The account URL is empty, please run '--update-account' first to update the account info, then try again." - return 1 - fi - if ! _calcjwk "$ACCOUNT_KEY_PATH"; then - return 1 - fi - _inner_payload="{\"account\": \"$_accUri\", \"oldKey\": $jwk}" - - _initAPI - if [ -z "$ACME_KEY_CHANGE" ]; then - _err "Server does not expose keyChange url." - return 1 - fi - - _url="$ACME_KEY_CHANGE" - if _createkey "$_length" "$ACCOUNT_KEY_PATH_NEW"; then - _info "New account key creation OK." - else - _err "New account key creation error." - return 1 - fi - - if ! _calcjwk "$ACCOUNT_KEY_PATH_NEW"; then - rm -f "$ACCOUNT_KEY_PATH_NEW" - return 1 - fi - _inner_protected="{\"url\": \"${_url}$JWK_HEADERPLACE_PART2, \"jwk\": $jwk"'}' - _inner_protected64="$(printf "%s" "$_inner_protected" | _base64 | _url_replace)" - _inner_payload64="$(printf "%s" "$_inner_payload" | _base64 | _url_replace)" - if ! _inner_sig_t="$(printf "%s" "$_inner_protected64.$_inner_payload64" | _sign "$ACCOUNT_KEY_PATH_NEW" "sha256")"; then - _err "Sign request failed." - rm -f "$ACCOUNT_KEY_PATH_NEW" - return 1 - fi - _debug3 _inner_sig_t "$_inner_sig_t" - - _inner_sig="$(printf "%s" "$_inner_sig_t" | _url_replace)" - _debug3 _inner_sig "$_inner_sig" - - _body="{\"protected\": \"$_inner_protected64\", \"payload\": \"$_inner_payload64\", \"signature\": \"$_inner_sig\"}" - - if ! _send_signed_request "$_url" "$_body" "" "$ACCOUNT_KEY_PATH"; then - _err "Error rotating account key: $response." - rm -f "$ACCOUNT_KEY_PATH_NEW" - return 1 - fi - - if [ "$code" = '200' ]; then - echo "$response" >"$ACCOUNT_JSON_PATH" - mv -f "$ACCOUNT_KEY_PATH_NEW" "$ACCOUNT_KEY_PATH" - _info "Account key rotation success for $_accUri." - elif [ "$code" = "409" ]; then - _err "An existing account is using the new key" - rm -f "$ACCOUNT_KEY_PATH_NEW" - return 1 - else - _err "Account key rollover error: $response" - rm -f "$ACCOUNT_KEY_PATH_NEW" - return 1 - fi - - __CACHED_JWK_KEY_FILE="" - _calcjwk "$ACCOUNT_KEY_PATH" - - ACCOUNT_THUMBPRINT="$(__calc_account_thumbprint)" - _info "ACCOUNT_THUMBPRINT" "$ACCOUNT_THUMBPRINT" - - CA_KEY_HASH="$(__calcAccountKeyHash)" - _debug "Calc CA_KEY_HASH" "$CA_KEY_HASH" - _savecaconf CA_KEY_HASH "$CA_KEY_HASH" -} - #Implement deactivate account deactivateaccount() { _initpath if [ ! -f "$ACCOUNT_KEY_PATH" ]; then - _err "Account key not found at: $ACCOUNT_KEY_PATH" + _err "Account key is not found at: $ACCOUNT_KEY_PATH" return 1 fi @@ -4367,7 +3834,8 @@ deactivateaccount() { _debug _accUri "$_accUri" if [ -z "$_accUri" ]; then - _err "The account URL is empty, please run '--update-account' first to update the account info, then try again." + _err "The account url is empty, please run '--update-account' first to update the account info first," + _err "Then try again." return 1 fi @@ -4379,14 +3847,13 @@ deactivateaccount() { _djson="{\"status\":\"deactivated\"}" if _send_signed_request "$_accUri" "$_djson" && _contains "$response" '"deactivated"'; then - _info "Successfully deactivated account $_accUri." + _info "Deactivate account success for $_accUri." _accid=$(echo "$response" | _egrep_o "\"id\" *: *[^,]*," | cut -d : -f 2 | tr -d ' ,') - elif [ "$code" = "403" ] || [ "$code" = "401" ]; then - #RFC 8555 sec 7.3.6: 401 from a deactivated account; Boulder returns 403 + elif [ "$code" = "403" ]; then _info "The account is already deactivated." _accid=$(_getfield "$_accUri" "999" "/") else - _err "Account deactivation failed for $_accUri." + _err "Deactivate: account failed for $_accUri." return 1 fi @@ -4400,7 +3867,7 @@ deactivateaccount() { mv "$ACCOUNT_JSON_PATH" "$_deactivated_account_path/" mv "$ACCOUNT_KEY_PATH" "$_deactivated_account_path/" else - _err "Cannot create dir: $_deactivated_account_path, try to remove the deactivated account key." + _err "Can not create dir: $_deactivated_account_path, try to remove the deactivated account key." rm -f "$CA_CONF" rm -f "$ACCOUNT_JSON_PATH" rm -f "$ACCOUNT_KEY_PATH" @@ -4408,134 +3875,6 @@ deactivateaccount() { fi } -#domain -#Print the Validation Domain Name where the persistent TXT record must be -#published: the "_validation-persist" label prepended to the domain being -#validated (draft-ietf-acme-dns-persist-01 sec 4). -#A wildcard identifier is validated by the record at its base domain, so the -#leading "*." label is dropped: the wildcard scope comes from 'policy=wildcard' -#in the record value, not from a "*" label in the record name (sec 5.1, 10.2). -_dns_persist_txt_name() { - _dpt_domain="$1" - if _startswith "$_dpt_domain" "*."; then - _dpt_domain="$(echo "$_dpt_domain" | sed 's/^\*\.//')" - fi - if [ -z "$_dpt_domain" ]; then - return 1 - fi - echo "_validation-persist.$_dpt_domain" -} - -#domain wildcard ca_name days -#Print the TXT record(s) the user must add to enable persistent DNS validation -#per draft-ietf-acme-dns-persist-01. -makednspersistvalue() { - _mdpv_domain="$1" - _mdpv_wildcard="$2" - _mdpv_ca_name="$3" - _mdpv_days="$4" - - if [ -z "$_mdpv_domain" ]; then - _err "Please specify a domain with -d." - return 1 - fi - - _txt_name="$(_dns_persist_txt_name "$_mdpv_domain")" - if [ -z "$_txt_name" ]; then - _err "Invalid domain: $_mdpv_domain" - return 1 - fi - _debug _txt_name "$_txt_name" - - #A wildcard identifier can only be issued if the record carries - #'policy=wildcard', so don't print a record that is guaranteed to fail. - if _startswith "$_mdpv_domain" "*." && [ "$_mdpv_wildcard" != "1" ]; then - _info "$_mdpv_domain is a wildcard domain, adding 'policy=wildcard' automatically." - _mdpv_wildcard="1" - fi - - if [ -n "$_mdpv_days" ]; then - case "$_mdpv_days" in - '' | *[!0-9]*) - _err "--dns-persist-days must be a positive integer, got: $_mdpv_days" - return 1 - ;; - esac - if [ "$_mdpv_days" -lt 1 ]; then - _err "--dns-persist-days must be at least 1." - return 1 - fi - fi - - _initpath - - _accUri="$(_readcaconf ACCOUNT_URL)" - if [ -z "$_accUri" ]; then - _info "No account is registered for $ACME_DIRECTORY yet, registering one now..." - if ! _regAccount "$DEFAULT_ACCOUNT_KEY_LENGTH"; then - _err "Cannot register account." - return 1 - fi - _accUri="$(_readcaconf ACCOUNT_URL)" - fi - - if [ -z "$_accUri" ]; then - _err "Cannot determine the ACME account URL." - return 1 - fi - _debug "Account URL" "$_accUri" - - _txt_suffix="; accounturi=$_accUri" - if [ "$_mdpv_wildcard" = "1" ]; then - _txt_suffix="$_txt_suffix; policy=wildcard" - fi - if [ -n "$_mdpv_days" ]; then - _persist_until=$(_math "$(_time)" + "$_mdpv_days" \* 86400) - _txt_suffix="$_txt_suffix; persistUntil=$_persist_until" - _info "persistUntil set to $(__green "$(_time2str "$_persist_until")") ($_mdpv_days days from now)" - fi - - if [ -n "$_mdpv_ca_name" ]; then - _info "" - _info "Add the following DNS TXT record to enable persistent DNS validation:" - _info "" - _info "$(printf 'TXT persist domain:%s' "$(__green "$_txt_name")")" - _info "$(printf 'TXT persist value :%s' "$(__green "\"$_mdpv_ca_name$_txt_suffix\"")")" - _info "" - return 0 - fi - - _info "Fetching ACME directory: $ACME_DIRECTORY" - _dir_resp="$(_get "$ACME_DIRECTORY" "" 30)" - if [ "$?" != "0" ] || [ -z "$_dir_resp" ]; then - _err "Cannot fetch ACME directory: $ACME_DIRECTORY" - return 1 - fi - _dir_resp="$(echo "$_dir_resp" | _json_decode)" - _debug2 _dir_resp "$_dir_resp" - - _caa_array="$(echo "$_dir_resp" | tr -d ' \r\n\t' | _egrep_o '"caaIdentities":\[[^]]*\]')" - _debug2 _caa_array "$_caa_array" - _caaids="$(echo "$_caa_array" | sed 's/.*\[//' | sed 's/\].*//' | tr ',' '\n' | tr -d '"')" - _debug2 _caaids "$_caaids" - - if [ -z "$_caaids" ]; then - _err "The directory does not include 'caaIdentities'. Please specify --dns-persist-ca-name explicitly." - return 1 - fi - - _info "" - _info "Add ANY ONE of the following DNS TXT records to enable persistent DNS validation." - _info "(You only need to add one; pick whichever issuer identity you prefer.)" - for _id in $_caaids; do - [ -z "$_id" ] && continue - _info "" - _info "$(printf 'TXT persist domain:%s' "$(__green "$_txt_name")")" - _info "$(printf 'TXT persist value :%s' "$(__green "\"$_id$_txt_suffix\"")")" - done - _info "" -} - # domain folder file _findHook() { _hookdomain="$1" @@ -4571,28 +3910,28 @@ __get_domain_new_authz() { _Max_new_authz_retry_times=5 _authz_i=0 while [ "$_authz_i" -lt "$_Max_new_authz_retry_times" ]; do - _debug "Trying new-authz, attempt number $_authz_i." + _debug "Try new-authz for the $_authz_i time." if ! _send_signed_request "${ACME_NEW_AUTHZ}" "{\"resource\": \"new-authz\", \"identifier\": {\"type\": \"dns\", \"value\": \"$(_idn "$_gdnd")\"}}"; then - _err "Cannot get new authz for domain." + _err "Can not get domain new authz." return 1 fi if _contains "$response" "No registration exists matching provided key"; then - _err "There has been an error, but it might now be resolved, please try again." - _err "If you see this message for a second time, please report this as a bug: $(__green "$PROJECT")" + _err "It seems there is an error, but it's recovered now, please try again." + _err "If you see this message for a second time, please report bug: $(__green "$PROJECT")" _clearcaconf "CA_KEY_HASH" break fi if ! _contains "$response" "An error occurred while processing your request"; then - _info "new-authz request successful." + _info "The new-authz request is ok." break fi _authz_i="$(_math "$_authz_i" + 1)" - _info "The server is busy, sleeping for $_authz_i seconds and retrying." + _info "The server is busy, Sleep $_authz_i to retry." _sleep "$_authz_i" done if [ "$_authz_i" = "$_Max_new_authz_retry_times" ]; then - _err "new-authz has been retried $_Max_new_authz_retry_times times, stopping." + _err "new-authz retry reach the max $_Max_new_authz_retry_times times." fi if [ "$code" ] && [ "$code" != '201' ]; then @@ -4648,7 +3987,7 @@ _ns_lookup_cf() { _ns_purge_cf() { _cf_d="$1" _cf_d_type="$2" - _debug "Purging Cloudflare $_cf_d_type record for domain $_cf_d" + _debug "Cloudflare purge $_cf_d_type record for domain $_cf_d" _cf_purl="https://cloudflare-dns.com/api/v1/purge?domain=$_cf_d&type=$_cf_d_type" response="$(_post "" "$_cf_purl")" _debug2 response "$response" @@ -4656,7 +3995,7 @@ _ns_purge_cf() { #checks if cf server is available _ns_is_available_cf() { - if _get "https://cloudflare-dns.com" "" 10 >/dev/null; then + if _get "https://cloudflare-dns.com" "" 1 >/dev/null 2>&1; then return 0 else return 1 @@ -4664,7 +4003,7 @@ _ns_is_available_cf() { } _ns_is_available_google() { - if _get "https://dns.google" "" 10 >/dev/null; then + if _get "https://dns.google" "" 1 >/dev/null 2>&1; then return 0 else return 1 @@ -4680,7 +4019,7 @@ _ns_lookup_google() { } _ns_is_available_ali() { - if _get "https://dns.alidns.com" "" 10 >/dev/null; then + if _get "https://dns.alidns.com" "" 1 >/dev/null 2>&1; then return 0 else return 1 @@ -4696,7 +4035,7 @@ _ns_lookup_ali() { } _ns_is_available_dp() { - if _get "https://doh.pub" "" 10 >/dev/null; then + if _get "https://doh.pub" "" 1 >/dev/null 2>&1; then return 0 else return 1 @@ -4713,21 +4052,21 @@ _ns_lookup_dp() { _ns_select_doh() { if [ -z "$DOH_USE" ]; then - _debug "Detecting DNS server first." + _debug "Detect dns server first." if _ns_is_available_cf; then - _debug "Using Cloudflare doh server" + _debug "Use cloudflare doh server" export DOH_USE=$DOH_CLOUDFLARE elif _ns_is_available_google; then - _debug "Using Google DOH server" + _debug "Use google doh server" export DOH_USE=$DOH_GOOGLE elif _ns_is_available_ali; then - _debug "Using Aliyun DOH server" + _debug "Use aliyun doh server" export DOH_USE=$DOH_ALI elif _ns_is_available_dp; then - _debug "Using DNS POD DOH server" + _debug "Use dns pod doh server" export DOH_USE=$DOH_DP else - _err "No DOH" + _err "No doh" fi fi } @@ -4744,7 +4083,7 @@ _ns_lookup() { elif [ "$DOH_USE" = "$DOH_DP" ]; then _ns_lookup_dp "$@" else - _err "Unknown DOH provider: DOH_USE=$DOH_USE" + _err "Unknown doh provider: DOH_USE=$DOH_USE" fi } @@ -4770,7 +4109,7 @@ __purge_txt() { if [ "$DOH_USE" = "$DOH_CLOUDFLARE" ] || [ -z "$DOH_USE" ]; then _ns_purge_cf "$_p_txtdomain" "TXT" else - _debug "No purge API for this DOH API, just sleeping for 5 seconds" + _debug "no purge api for this doh api, just sleep 5 secs" _sleep 5 fi @@ -4801,17 +4140,17 @@ _check_dns_entries() { _debug "d_api" "$d_api" _info "Checking $d for $aliasDomain" if _contains "$_success_txt" ",$txt,"; then - _info "Already succeeded, continuing." + _info "Already success, continue next one." continue fi if __check_txt "$txtdomain" "$aliasDomain" "$txt"; then - _info "Success for domain $d '$aliasDomain'." + _info "Domain $d '$aliasDomain' success." _success_txt="$_success_txt,$txt," continue fi _left=1 - _info "Not valid yet, let's wait for 10 seconds then check the next one." + _info "Not valid yet, let's wait 10 seconds and check next one." __purge_txt "$txtdomain" if [ "$txtdomain" != "$aliasDomain" ]; then __purge_txt "$aliasDomain" @@ -4819,10 +4158,10 @@ _check_dns_entries() { _sleep 10 done if [ "$_left" ]; then - _info "Let's wait for 10 seconds and check again". + _info "Let's wait 10 seconds and check again". _sleep 10 else - _info "All checks succeeded" + _info "All success, let's return" return 0 fi done @@ -4885,25 +4224,16 @@ _match_issuer() { #ip _isIPv4() { - #splitting must not glob: a "*" segment would match files in cwd - set -f - _ipv4_saved_ifs="$IFS" - IFS='.' - # shellcheck disable=SC2086 - set -- $1 - IFS="$_ipv4_saved_ifs" - set +f - if [ $# -ne 4 ]; then - return 1 - fi - for _ipv4_seg in "$@"; do - _debug2 _ipv4_seg "$_ipv4_seg" - case "$_ipv4_seg" in - *[!0-9]* | "") return 1 ;; - esac - if [ "${#_ipv4_seg}" -gt 3 ] || [ "$_ipv4_seg" -gt 255 ]; then + for seg in $(echo "$1" | tr '.' ' '); do + _debug2 seg "$seg" + if [ "$(echo "$seg" | tr -d '[0-9]')" ]; then + #not all number return 1 fi + if [ $seg -ge 0 ] && [ $seg -lt 256 ]; then + continue + fi + return 1 done return 0 } @@ -4947,14 +4277,14 @@ _convertValidaty() { elif _endswith "$_dateTo" "d"; then _v_end=$(_math "$_v_begin + 60 * 60 * 24 * $(echo "$_dateTo" | tr -d '+d')") else - _err "Unrecognized format for _dateTo: $_dateTo" + _err "Not recognized format for _dateTo: $_dateTo" return 1 fi _debug2 "_v_end" "$_v_end" _time2str "$_v_end" else if [ "$(_time)" -gt "$(_date2time "$_dateTo")" ]; then - _err "The validity end date is in the past: _dateTo = $_dateTo" + _err "The validaty to is in the past: _dateTo = $_dateTo" return 1 fi echo "$_dateTo" @@ -4996,24 +4326,15 @@ issue() { _preferred_chain="${15}" _valid_from="${16}" _valid_to="${17}" - _certificate_profile="${18}" - _extended_key_usage="${19}" if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" mkdir -p "$DOMAIN_PATH" - elif [ -z "$Le_Vlist" ]; then - # Whether the saved order is resumed is decided by Le_Vlist below, so key - # this on Le_Vlist too. With no pending order to resume a new one is - # created, and a stale order link from the previous issuance must not be - # reused. https://github.com/acmesh-official/acme.sh/issues/3635 + elif ! _hasfield "$_web_roots" "$W_DNS"; then Le_OrderFinalize="" Le_LinkOrder="" + Le_LinkCert="" fi - # Per-run state only: it is set after finalize and never read back from the - # saved domain conf. Carrying it over would make a run that gives up while - # the order is still 'processing' download the previous certificate again. - Le_LinkCert="" if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then _err "$_DNS_MANUAL_ERROR" @@ -5023,11 +4344,11 @@ issue() { if [ -f "$DOMAIN_CONF" ]; then Le_NextRenewTime=$(_readdomainconf Le_NextRenewTime) _debug Le_NextRenewTime "$Le_NextRenewTime" - if [ -z "$FORCE" ] && [ -z "$_ari_should_renew" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then - _valid_to_saved=$(_readdomainconf Le_Valid_To) + if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then + _valid_to_saved=$(_readdomainconf Le_Valid_to) if [ "$_valid_to_saved" ] && ! _startswith "$_valid_to_saved" "+"; then _info "The domain is set to be valid to: $_valid_to_saved" - _info "It cannot be renewed automatically" + _info "It can not be renewed automatically" _info "See: $_VALIDITY_WIKI" return $RENEW_SKIP fi @@ -5043,8 +4364,8 @@ issue() { if [ "$_normized_saved_domains" = "$_normized_domains" ]; then _info "Domains not changed." - _info "Skipping. Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")" - _info "Add '$(__red '--force')' to force renewal." + _info "Skip, Next renewal time is: $(__green "$(_readdomainconf Le_NextRenewTimeStr)")" + _info "Add '$(__red '--force')' to force to renew." return $RENEW_SKIP else _info "Domains have changed." @@ -5075,23 +4396,11 @@ issue() { else _cleardomainconf "Le_ChallengeAlias" fi - # Save Le_DNSSleep unconditionally here: the save inside the dns_entries - # branch is skipped when all authorizations are already valid (e.g. issuing - # the ECC twin of a just-issued RSA cert), which left the setting out of - # that cert's conf. https://github.com/acmesh-official/acme.sh/issues/6986 - if [ "$Le_DNSSleep" ]; then - _savedomainconf "Le_DNSSleep" "$Le_DNSSleep" - fi if [ "$_preferred_chain" ]; then _savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64" else _cleardomainconf "Le_Preferred_Chain" fi - if [ "$_certificate_profile" ]; then - _savedomainconf "Le_Certificate_Profile" "$_certificate_profile" - else - _cleardomainconf "Le_Certificate_Profile" - fi Le_API="$ACME_DIRECTORY" _savedomainconf "Le_API" "$Le_API" @@ -5103,7 +4412,6 @@ issue() { if ! _on_before_issue "$_web_roots" "$_main_domain" "$_alt_domains" "$_pre_hook" "$_local_addr"; then _err "_on_before_issue." - _on_issue_err "$_post_hook" return 1 fi @@ -5111,12 +4419,12 @@ issue() { _debug2 _saved_account_key_hash "$_saved_account_key_hash" if [ -z "$ACCOUNT_URL" ] || [ -z "$_saved_account_key_hash" ] || [ "$_saved_account_key_hash" != "$(__calcAccountKeyHash)" ]; then - if ! _regAccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key"; then + if ! _regAccount "$_accountkeylength"; then _on_issue_err "$_post_hook" return 1 fi else - _debug "_saved_account_key_hash was not changed, skipping account registration." + _debug "_saved_account_key_hash is not changed, skip register account." fi export Le_Next_Domain_Key="$CERT_KEY_PATH.next" @@ -5130,15 +4438,15 @@ issue() { if [ -z "$_key" ]; then _key=2048 fi - _debug "Read key length: $_key" + _debug "Read key length:$_key" if [ ! -f "$CERT_KEY_PATH" ] || [ "$_key_length" != "$_key" ] || [ "$Le_ForceNewDomainKey" = "1" ]; then if [ "$Le_ForceNewDomainKey" = "1" ] && [ -f "$Le_Next_Domain_Key" ]; then - _info "Using pre-generated key: $Le_Next_Domain_Key" + _info "Using pre generated key: $Le_Next_Domain_Key" cat "$Le_Next_Domain_Key" >"$CERT_KEY_PATH" echo "" >"$Le_Next_Domain_Key" else if ! createDomainKey "$_main_domain" "$_key_length"; then - _err "Error creating domain key." + _err "Create domain key error." _clearup _on_issue_err "$_post_hook" return 1 @@ -5146,42 +4454,29 @@ issue() { fi fi if [ "$Le_ForceNewDomainKey" ]; then - _info "Generating next pre-generate key." + _info "Generate next pre-generate key." if [ ! -e "$Le_Next_Domain_Key" ]; then touch "$Le_Next_Domain_Key" chmod 600 "$Le_Next_Domain_Key" fi if ! _createkey "$_key_length" "$Le_Next_Domain_Key"; then - _err "Cannot pre-generate domain key" + _err "Can not pre generate domain key" return 1 fi fi - _keyusage="$_extended_key_usage" - if [ "$Le_API" = "$CA_GOOGLE" ] || [ "$Le_API" = "$CA_GOOGLE_TEST" ]; then - if [ -z "$_keyusage" ]; then - #https://github.com/acmesh-official/acme.sh/issues/6610 - #google accepts serverauth only - _keyusage="serverAuth" - fi - fi - if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF" "" "$_keyusage"; then - _err "Error creating CSR." + if ! _createcsr "$_main_domain" "$_alt_domains" "$CERT_KEY_PATH" "$CSR_PATH" "$DOMAIN_SSL_CONF"; then + _err "Create CSR error." _clearup _on_issue_err "$_post_hook" return 1 fi - if [ "$_extended_key_usage" ]; then - _savedomainconf "Le_ExtKeyUse" "$_extended_key_usage" - else - _cleardomainconf "Le_ExtKeyUse" - fi fi _savedomainconf "Le_Keylength" "$_key_length" vlist="$Le_Vlist" _cleardomainconf "Le_Vlist" - _debug "Getting domain auth token for each domain" + _info "Getting domain auth token for each domain" sep='#' dvsep=',' if [ -z "$vlist" ]; then @@ -5206,7 +4501,7 @@ issue() { _debug2 "_valid_from" "$_valid_from" _notBefore="$(_convertValidaty "" "$_valid_from")" if [ "$?" != "0" ]; then - _err "Cannot parse _valid_from: $_valid_from" + _err "Can not parse _valid_from: $_valid_from" return 1 fi if [ "$(_time)" -gt "$(_date2time "$_notBefore")" ]; then @@ -5222,7 +4517,7 @@ issue() { _savedomainconf "Le_Valid_To" "$_valid_to" _notAfter="$(_convertValidaty "$_notBefore" "$_valid_to")" if [ "$?" != "0" ]; then - _err "Cannot parse _valid_to: $_valid_to" + _err "Can not parse _valid_to: $_valid_to" return 1 fi else @@ -5237,64 +4532,19 @@ issue() { if [ "$_notAfter" ]; then _newOrderObj="$_newOrderObj,\"notAfter\": \"$_notAfter\"" fi - if [ "$_certificate_profile" ]; then - _newOrderObj="$_newOrderObj,\"profile\": \"$_certificate_profile\"" - fi - - # RFC 9773 Section 5: include "replaces" only when this is an actual - # renewal (--renew path), the CA advertises renewalInfo, and a prior - # cert exists. --issue (even with --force) is not a renewal per RFC 9773 - # which speaks of "a clear predecessor certificate" issued by this CA. - # NO_ARI=1 (env, account.conf, or ca.conf) disables ARI entirely, so the - # "replaces" field is also omitted. - _replaces_certID="" - if [ "$NO_ARI" = "1" ]; then - _debug "NO_ARI=1, omitting ARI 'replaces' field from newOrder" - elif [ "$_ACME_IS_RENEW" = "1" ] && [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ]; then - _replaces_certID="$(_getARICertID "$CERT_PATH")" - _debug "Adding ARI replaces" "$_replaces_certID" - fi - - _debug "STEP 1, Ordering a Certificate" - _newOrderReplacesObj="$_newOrderObj" - if [ "$_replaces_certID" ]; then - _newOrderReplacesObj="$_newOrderObj,\"replaces\": \"$_replaces_certID\"" - fi - if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderReplacesObj}"; then - _err "Error creating new order." + if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then + _err "Create new order error." _clearup _on_issue_err "$_post_hook" return 1 fi - # RFC 9773 Section 5 only defines the "alreadyReplaced" error, but real CAs - # (Let's Encrypt) may also reject with a malformed error if the prior cert - # was issued by a different issuer / different CA. Retry without "replaces" - # whenever the failure mentions ARI or the replaces field. - if [ "$_replaces_certID" ] && { _contains "$response" "alreadyReplaced" || _contains "$response" "urn:ietf:params:acme:error:malformed" || _contains "$response" "'replaces'" || _contains "$response" "ARI"; }; then - _info "ARI 'replaces' rejected by CA, retrying newOrder without 'replaces'." - if ! _send_signed_request "$ACME_NEW_ORDER" "$_newOrderObj}"; then - _err "Error creating new order." - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - fi - if _contains "$response" "invalid"; then - if echo "$response" | _normalizeJson | grep '"status":"invalid"' >/dev/null 2>&1; then - _err "Create new order with invalid status." - _err "$response" - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - fi Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n " | cut -d ":" -f 2-)" _debug Le_LinkOrder "$Le_LinkOrder" Le_OrderFinalize="$(echo "$response" | _egrep_o '"finalize" *: *"[^"]*"' | cut -d '"' -f 4)" _debug Le_OrderFinalize "$Le_OrderFinalize" if [ -z "$Le_OrderFinalize" ]; then - _err "Error creating new order. Le_OrderFinalize not found. $response" + _err "Create new order error. Le_OrderFinalize not found. $response" _clearup _on_issue_err "$_post_hook" return 1 @@ -5303,7 +4553,7 @@ issue() { #for dns manual mode _savedomainconf "Le_OrderFinalize" "$Le_OrderFinalize" - _authorizations_seg="$(echo "$response" | _json_decode | _authorizations_from_order)" + _authorizations_seg="$(echo "$response" | _json_decode | _egrep_o '"authorizations" *: *\[[^\[]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" _debug2 _authorizations_seg "$_authorizations_seg" if [ -z "$_authorizations_seg" ]; then _err "_authorizations_seg not found." @@ -5312,16 +4562,14 @@ issue() { return 1 fi - _debug "STEP 2, Get the authorizations of each domain" #domain and authz map _authorizations_map="" for _authz_url in $(echo "$_authorizations_seg" | tr ',' ' '); do _debug2 "_authz_url" "$_authz_url" if ! _send_signed_request "$_authz_url"; then - _err "Error getting authz." + _err "get to authz error." _err "_authorizations_seg" "$_authorizations_seg" _err "_authz_url" "$_authz_url" - _err "$response" _clearup _on_issue_err "$_post_hook" return 1 @@ -5329,23 +4577,14 @@ issue() { response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" - if echo "$response" | grep '"status":"invalid"' >/dev/null 2>&1; then - _err "get authz objec with invalid status, please try again later." - _err "_authorizations_seg" "$_authorizations_seg" - _err "$response" - _clearup - _on_issue_err "$_post_hook" - return 1 - fi _d="$(echo "$response" | _egrep_o '"value" *: *"[^"]*"' | cut -d : -f 2- | tr -d ' "')" if _contains "$response" "\"wildcard\" *: *true"; then _d="*.$_d" fi _debug2 _d "$_d" - _authorizations_map="$_d,$response#$_authz_url + _authorizations_map="$_d,$response $_authorizations_map" done - _debug2 _authorizations_map "$_authorizations_map" _index=0 @@ -5369,9 +4608,7 @@ $_authorizations_map" vtype="$VTYPE_HTTP" #todo, v2 wildcard force to use dns - if [ "$_currentRoot" = "$W_DNS_PERSIST" ]; then - vtype="$VTYPE_DNS_PERSIST" - elif _startswith "$_currentRoot" "$W_DNS"; then + if _startswith "$_currentRoot" "$W_DNS"; then vtype="$VTYPE_DNS" fi @@ -5393,74 +4630,73 @@ $_authorizations_map" response="$(echo "$_candidates" | sed "s/$_idn_d,//")" _debug2 "response" "$response" if [ -z "$response" ]; then - _err "Error getting authz." + _err "get to authz error." _err "_authorizations_map" "$_authorizations_map" _clearup _on_issue_err "$_post_hook" return 1 fi - _authz_url="$(echo "$_candidates" | sed "s/$_idn_d,//" | _egrep_o "#.*" | sed "s/^#//")" - _debug _authz_url "$_authz_url" + if [ -z "$thumbprint" ]; then thumbprint="$(__calc_account_thumbprint)" fi - keyauthorization="" - - if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then - _debug "$d is already valid." - keyauthorization="$STATE_VERIFIED" - _debug keyauthorization "$keyauthorization" - fi - - # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 - entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')" + entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" - - if [ -z "$keyauthorization" -a -z "$entry" ]; then - _err "Cannot get domain token entry $d for $vtype" - _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')" - if [ "$_supported_vtypes" ]; then - _err "Supported validation types are: $_supported_vtypes, but you specified: $vtype" + keyauthorization="" + if [ -z "$entry" ]; then + if ! _startswith "$d" '*.'; then + _debug "Not a wildcard domain, lets check whether the validation is already valid." + if echo "$response" | grep '"status":"valid"' >/dev/null 2>&1; then + _debug "$d is already valid." + keyauthorization="$STATE_VERIFIED" + _debug keyauthorization "$keyauthorization" + fi + fi + if [ -z "$keyauthorization" ]; then + _err "Error, can not get domain token entry $d for $vtype" + _supported_vtypes="$(echo "$response" | _egrep_o "\"challenges\":\[[^]]*]" | tr '{' "\n" | grep type | cut -d '"' -f 4 | tr "\n" ' ')" + if [ "$_supported_vtypes" ]; then + _err "The supported validation types are: $_supported_vtypes, but you specified: $vtype" + fi + _clearup + _on_issue_err "$_post_hook" + return 1 fi - _clearup - _on_issue_err "$_post_hook" - return 1 fi if [ -z "$keyauthorization" ]; then - uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)" - _debug uri "$uri" + token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')" + _debug token "$token" - if [ -z "$uri" ]; then - _err "Cannot get domain URI $entry" + if [ -z "$token" ]; then + _err "Error, can not get domain token $entry" _clearup _on_issue_err "$_post_hook" return 1 fi - if [ "$vtype" = "$VTYPE_DNS_PERSIST" ]; then - # dns-persist-01 challenges have no token; the TXT record is - # provisioned out-of-band. Use a non-empty placeholder so the - # downstream code does not treat this entry as already verified. - keyauthorization="$VTYPE_DNS_PERSIST" - _debug keyauthorization "$keyauthorization" - else - token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')" - _debug token "$token" + uri="$(echo "$entry" | _egrep_o '"url":"[^"]*' | cut -d '"' -f 4 | _head_n 1)" - if [ -z "$token" ]; then - _err "Cannot get domain token $entry" - _clearup - _on_issue_err "$_post_hook" - return 1 - fi - keyauthorization="$token.$thumbprint" + _debug uri "$uri" + + if [ -z "$uri" ]; then + _err "Error, can not get domain uri. $entry" + _clearup + _on_issue_err "$_post_hook" + return 1 + fi + keyauthorization="$token.$thumbprint" + _debug keyauthorization "$keyauthorization" + + if printf "%s" "$response" | grep '"status":"valid"' >/dev/null 2>&1; then + _debug "$d is already verified." + keyauthorization="$STATE_VERIFIED" _debug keyauthorization "$keyauthorization" fi fi - dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot$sep$_authz_url" + dvlist="$d$sep$keyauthorization$sep$uri$sep$vtype$sep$_currentRoot" _debug dvlist "$dvlist" vlist="$vlist$dvlist$dvsep" @@ -5477,10 +4713,9 @@ $_authorizations_map" keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) - _authz_url=$(echo "$ventry" | cut -d "$sep" -f 6) _debug d "$d" if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then - _debug "$d has already been verified, skipping $vtype." + _debug "$d is already verified, skip $vtype." _alias_index="$(_math "$_alias_index" + 1)" continue fi @@ -5493,8 +4728,6 @@ $_authorizations_map" fi _d_alias="$(_getfield "$_challenge_alias" "$_alias_index")" test "$_d_alias" = "$NO_VALUE" && _d_alias="" - # strip the trailing dot of a fully-qualified alias domain - _d_alias="${_d_alias%.}" _alias_index="$(_math "$_alias_index" + 1)" _debug "_d_alias" "$_d_alias" if [ "$_d_alias" ]; then @@ -5519,37 +4752,37 @@ $_authorizations_map" dns_entry="$dns_entry$dvsep$txt${dvsep}$d_api" _debug2 dns_entry "$dns_entry" if [ "$d_api" ]; then - _debug "Found domain API file: $d_api" + _debug "Found domain api file: $d_api" else if [ "$_currentRoot" != "$W_DNS" ]; then - _err "Cannot find DNS API hook for: $_currentRoot" - _info "You need to add the TXT record manually." + _err "Can not find dns api hook for: $_currentRoot" + _info "You need to add the txt record manually." fi _info "$(__red "Add the following TXT record:")" _info "$(__red "Domain: '$(__green "$txtdomain")'")" _info "$(__red "TXT value: '$(__green "$txt")'")" - _info "$(__red "Please make sure to prepend '_acme-challenge.' to your domain")" - _info "$(__red "so that the resulting subdomain is: $txtdomain")" + _info "$(__red "Please be aware that you prepend _acme-challenge. before your domain")" + _info "$(__red "so the resulting subdomain will be: $txtdomain")" continue fi ( if ! . "$d_api"; then - _err "Error loading file $d_api. Please check your API file and try again." + _err "Load file $d_api error. Please check your api file and try again." return 1 fi addcommand="${_currentRoot}_add" if ! _exists "$addcommand"; then - _err "It seems that your API file is incorrect. Make sure it has a function named: $addcommand" + _err "It seems that your api file is not correct, it must have a function named: $addcommand" return 1 fi - _info "Adding TXT value: $txt for domain: $txtdomain" + _info "Adding txt value: $txt for domain: $txtdomain" if ! $addcommand "$txtdomain" "$txt"; then - _err "Error adding TXT record to domain: $txtdomain" + _err "Error add txt for domain:$txtdomain" return 1 fi - _info "The TXT record has been successfully added." + _info "The txt record is added: Success." ) if [ "$?" != "0" ]; then @@ -5566,7 +4799,7 @@ $_authorizations_map" if [ "$dnsadded" = '0' ]; then _savedomainconf "Le_Vlist" "$vlist" - _debug "DNS record not yet added. Will save to $DOMAIN_CONF and exit." + _debug "Dns record not added yet, so, save to $DOMAIN_CONF and exit." _err "Please add the TXT records to the domains, and re-run with --renew." _on_issue_err "$_post_hook" _clearup @@ -5579,23 +4812,23 @@ $_authorizations_map" if [ "$dns_entries" ]; then if [ -z "$Le_DNSSleep" ]; then - _info "Let's check each DNS record now. Sleeping for 20 seconds first." + _info "Let's check each DNS record now. Sleep 20 seconds first." _sleep 20 if ! _check_dns_entries; then - _err "Error checking DNS." + _err "check dns error." _on_issue_err "$_post_hook" _clearup return 1 fi else _savedomainconf "Le_DNSSleep" "$Le_DNSSleep" - _info "Sleeping for $(__green $Le_DNSSleep) seconds to wait for the the TXT records to take effect" + _info "Sleep $(__green $Le_DNSSleep) seconds for the txt records to take effect" _sleep "$Le_DNSSleep" fi fi NGINX_RESTORE_VLIST="" - _debug "OK, let's start verification" + _debug "ok, let's start to verify" _ncIndex=1 ventries=$(echo "$vlist" | tr "$dvsep" ' ') @@ -5605,9 +4838,9 @@ $_authorizations_map" uri=$(echo "$ventry" | cut -d "$sep" -f 3) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) - _authz_url=$(echo "$ventry" | cut -d "$sep" -f 6) + if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then - _info "$d is already verified, skipping $vtype." + _info "$d is already verified, skip $vtype." continue fi @@ -5615,7 +4848,6 @@ $_authorizations_map" _debug "d" "$d" _debug "keyauthorization" "$keyauthorization" _debug "uri" "$uri" - _debug "_authz_url" "$_authz_url" removelevel="" token="$(printf "%s" "$keyauthorization" | cut -d '.' -f 1)" @@ -5635,10 +4867,10 @@ $_authorizations_map" sleep 1 _debug serverproc "$serverproc" elif [ "$_currentRoot" = "$MODE_STATELESS" ]; then - _info "Stateless mode for domain: $d" + _info "Stateless mode for domain:$d" _sleep 1 elif _startswith "$_currentRoot" "$NGINX"; then - _info "Nginx mode for domain: $d" + _info "Nginx mode for domain:$d" #set up nginx server FOUND_REAL_NGINX_CONF="" BACKUP_NGINX_CONF="" @@ -5671,30 +4903,38 @@ $_authorizations_map" _debug wellknown_path "$wellknown_path" - _debug "Writing token: $token to $wellknown_path/$token" + _debug "writing token:$token to $wellknown_path/$token" - # Ensure .well-known is visible to web server user/group - # https://github.com/Neilpang/acme.sh/pull/32 - if ! (umask ugo+rx && - mkdir -p "$wellknown_path" && - printf "%s" "$keyauthorization" >"$wellknown_path/$token"); then - _err "$d: Cannot write token to file: $wellknown_path/$token" + mkdir -p "$wellknown_path" + + if ! printf "%s" "$keyauthorization" >"$wellknown_path/$token"; then + _err "$d:Can not write token to file : $wellknown_path/$token" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi if ! chmod a+r "$wellknown_path/$token"; then - _debug "chmod failed, will just continue." + _debug "chmod failed, but we just continue." fi + if [ ! "$usingApache" ]; then + if webroot_owner=$(_stat "$_currentRoot"); then + _debug "Changing owner/group of .well-known to $webroot_owner" + if ! _exec "chown -R \"$webroot_owner\" \"$_currentRoot/.well-known\""; then + _debug "$(cat "$_EXEC_TEMP_ERR")" + _exec_err >/dev/null 2>&1 + fi + else + _debug "not changing owner/group of webroot" + fi + fi + fi elif [ "$vtype" = "$VTYPE_ALPN" ]; then - _ncaddr="$(_getfield "$_local_addr" "$_ncIndex")" - _ncIndex="$(_math $_ncIndex + 1)" acmevalidationv1="$(printf "%s" "$keyauthorization" | _digest "sha256" "hex")" _debug acmevalidationv1 "$acmevalidationv1" if ! _starttlsserver "$d" "" "$Le_TLSPort" "$keyauthorization" "$_ncaddr" "$acmevalidationv1"; then - _err "Error starting TLS server." + _err "Start tls server error." _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -5703,7 +4943,7 @@ $_authorizations_map" fi if ! __trigger_validation "$uri" "$keyauthorization" "$vtype"; then - _err "$d: Cannot get challenge: $response" + _err "$d:Can not get challenge: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -5712,9 +4952,9 @@ $_authorizations_map" if [ "$code" ] && [ "$code" != '202' ]; then if [ "$code" = '200' ]; then - _debug "Trigger validation code: $code" + _debug "trigger validation code: $code" else - _err "$d: Challenge error: $response" + _err "$d:Challenge error: $response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -5727,11 +4967,10 @@ $_authorizations_map" MAX_RETRY_TIMES=30 fi - _debug "Let's check the authz status" while true; do waittimes=$(_math "$waittimes" + 1) if [ "$waittimes" -ge "$MAX_RETRY_TIMES" ]; then - _err "$d: Timeout" + _err "$d:Timeout" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" @@ -5746,24 +4985,19 @@ $_authorizations_map" status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"') _debug2 status "$status" if _contains "$status" "invalid"; then - error="$(echo "$response" | _egrep_o '"error":[{][^}]*')" + error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')" _debug2 error "$error" errordetail="$(echo "$error" | _egrep_o '"detail": *"[^"]*' | cut -d '"' -f 4)" _debug2 errordetail "$errordetail" if [ "$errordetail" ]; then - _err "$d: Invalid status. Verification error details: $errordetail" + _err "$d:Verify error:$errordetail" else - _err "$d: Invalid status, Verification error: $error" + _err "$d:Verify error:$error" fi if [ "$DEBUG" ]; then if [ "$vtype" = "$VTYPE_HTTP" ]; then - _debug "Debug: GET token URL." - if _isIPv6 "$d"; then - host="[$d]" - else - host="$d" - fi - _get "http://$host/.well-known/acme-challenge/$token" "" 1 + _debug "Debug: get token url." + _get "http://$d/.well-known/acme-challenge/$token" "" 1 fi fi _clearupwebbroot "$_currentRoot" "$removelevel" "$token" @@ -5780,60 +5014,47 @@ $_authorizations_map" break fi - if _contains "$status" "pending"; then - _info "Pending. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)" - elif _contains "$status" "processing"; then - _info "Processing. The CA is processing your order, please wait. ($waittimes/$MAX_RETRY_TIMES)" + if [ "$status" = "pending" ]; then + _info "Pending, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" + elif [ "$status" = "processing" ]; then + _info "Processing, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" else - _err "$d: Unknown status: $status. Verification error: $response" + _err "$d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi - _debug "Sleep 2 seconds before verifying again" + _debug "sleep 2 secs to verify again" _sleep 2 - _debug "Checking" + _debug "checking" - _send_signed_request "$_authz_url" + _send_signed_request "$uri" if [ "$?" != "0" ]; then - _err "$d: Invalid code. Verification error: $response" + _err "$d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearup _on_issue_err "$_post_hook" "$vlist" return 1 fi - _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *: *[0-9]\+ *" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') - _sleep_overload_retry_sec=$_retryafter - if [ "$_sleep_overload_retry_sec" ]; then - if [ $_sleep_overload_retry_sec -le 600 ]; then - _sleep $_sleep_overload_retry_sec - else - _info "The retryafter=$_retryafter value is too large (> 600), will not retry anymore." - _clearupwebbroot "$_currentRoot" "$removelevel" "$token" - _clearup - _on_issue_err "$_post_hook" "$vlist" - return 1 - fi - fi done done _clearup - _info "Verification finished, beginning signing." + _info "Verify finished, start to sign." der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)" - _info "Let's finalize the order." + _info "Lets finalize the order." _info "Le_OrderFinalize" "$Le_OrderFinalize" if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then - _err "Signing failed." + _err "Sign failed." _on_issue_err "$_post_hook" return 1 fi if [ "$code" != "200" ]; then - _err "Signing failed. Finalize code was not 200." + _err "Sign failed, finalize code is not 200." _err "$response" _on_issue_err "$_post_hook" return 1 @@ -5852,48 +5073,38 @@ $_authorizations_map" Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)" _debug Le_LinkCert "$Le_LinkCert" if [ -z "$Le_LinkCert" ]; then - _err "A signing error occurred: could not find Le_LinkCert" + _err "Sign error, can not find Le_LinkCert" _err "$response" _on_issue_err "$_post_hook" return 1 fi break - elif _contains "$response" "\"ready\""; then - _info "Order status is 'ready', let's sleep and retry." - _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') - _debug "_retryafter" "$_retryafter" - if [ "$_retryafter" ] && [ $_retryafter -gt 0 ]; then - _info "Sleeping for $_retryafter seconds then retrying" - _sleep $_retryafter - else - _sleep 2 - fi elif _contains "$response" "\"processing\""; then - _info "Order status is 'processing', let's sleep and retry." + _info "Order status is processing, lets sleep and retry." _retryafter=$(echo "$responseHeaders" | grep -i "^Retry-After *:" | cut -d : -f 2 | tr -d ' ' | tr -d '\r') _debug "_retryafter" "$_retryafter" - if [ "$_retryafter" ] && [ $_retryafter -gt 0 ]; then - _info "Sleeping for $_retryafter seconds then retrying" + if [ "$_retryafter" ]; then + _info "Retry after: $_retryafter" _sleep $_retryafter else _sleep 2 fi else - _err "Signing error: wrong status" + _err "Sign error, wrong status" _err "$response" _on_issue_err "$_post_hook" return 1 fi #the order is processing, so we are going to poll order status if [ -z "$Le_LinkOrder" ]; then - _err "Signing error: could not get order link location header" + _err "Sign error, can not get order link location header" _err "responseHeaders" "$responseHeaders" _on_issue_err "$_post_hook" return 1 fi _info "Polling order status: $Le_LinkOrder" if ! _send_signed_request "$Le_LinkOrder"; then - _err "Signing failed. Could not make POST request to Le_LinkOrder for cert: $Le_LinkOrder." + _err "Sign failed, can not post to Le_LinkOrder cert:$Le_LinkOrder." _err "$response" _on_issue_err "$_post_hook" return 1 @@ -5901,13 +5112,8 @@ $_authorizations_map" _link_cert_retry="$(_math $_link_cert_retry + 1)" done - # cover case where the final poll returned 'valid' - if [ -z "$Le_LinkCert" ] && _contains "$response" "\"status\":\"valid\""; then - Le_LinkCert="$(echo "$response" | _egrep_o '"certificate" *: *"[^"]*"' | cut -d '"' -f 4)" - fi - if [ -z "$Le_LinkCert" ]; then - _err "Signing failed. Could not get Le_LinkCert, and stopped retrying after reaching the retry limit." + _err "Sign failed, can not get Le_LinkCert, retry time limit." _err "$response" _on_issue_err "$_post_hook" return 1 @@ -5915,47 +5121,35 @@ $_authorizations_map" _info "Downloading cert." _info "Le_LinkCert" "$Le_LinkCert" if ! _send_signed_request "$Le_LinkCert"; then - _err "Signing failed. Could not download cert: $Le_LinkCert." + _err "Sign failed, can not download cert:$Le_LinkCert." _err "$response" _on_issue_err "$_post_hook" return 1 fi - if ! _contains "$response" "$BEGIN_CERT"; then - response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)" - _err "Signing failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')" - _on_issue_err "$_post_hook" - return 1 - fi - - echo "$response" | _strip_blank_lines >"$CERT_PATH" + echo "$response" >"$CERT_PATH" _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH" if [ -z "$_preferred_chain" ]; then _preferred_chain=$(_readcaconf DEFAULT_PREFERRED_CHAIN) fi if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then if [ "$DEBUG" ]; then - _debug "Default chain issuers: " "$(_get_chain_issuers "$CERT_FULLCHAIN_PATH")" + _debug "default chain issuers: " "$(_get_chain_issuers "$CERT_FULLCHAIN_PATH")" fi if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)" _debug2 "rels" "$rels" for rel in $rels; do - _info "Trying rel: $rel" + _info "Try rel: $rel" if ! _send_signed_request "$rel"; then - _err "Signing failed, could not download cert: $rel" + _err "Sign failed, can not download cert:$rel" _err "$response" continue fi - - if ! _contains "$response" "$BEGIN_CERT"; then - _debug2 "Skipping alternate cert link due to unexpected response format." - continue - fi _relcert="$CERT_PATH.alt" _relfullchain="$CERT_FULLCHAIN_PATH.alt" _relca="$CA_CERT_PATH.alt" - echo "$response" | _strip_blank_lines >"$_relcert" + echo "$response" >"$_relcert" _split_cert_chain "$_relcert" "$_relfullchain" "$_relca" if [ "$DEBUG" ]; then _debug "rel chain issuers: " "$(_get_chain_issuers "$_relfullchain")" @@ -5982,7 +5176,7 @@ $_authorizations_map" if [ -z "$Le_LinkCert" ] || ! _checkcert "$CERT_PATH"; then response="$(echo "$response" | _dbase64 "multiline" | tr -d '\0' | _normalizeJson)" - _err "Signing failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')" + _err "Sign failed: $(echo "$response" | _egrep_o '"detail":"[^"]*"')" _on_issue_err "$_post_hook" return 1 fi @@ -6004,9 +5198,9 @@ $_authorizations_map" fi [ -f "$CA_CERT_PATH" ] && _info "The intermediate CA cert is in: $(__green "$CA_CERT_PATH")" - [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full-chain cert is in: $(__green "$CERT_FULLCHAIN_PATH")" + [ -f "$CERT_FULLCHAIN_PATH" ] && _info "And the full chain certs is there: $(__green "$CERT_FULLCHAIN_PATH")" if [ "$Le_ForceNewDomainKey" ] && [ -e "$Le_Next_Domain_Key" ]; then - _info "Your pre-generated key for future cert key changes is in: $(__green "$Le_Next_Domain_Key")" + _info "Your pre-generated next key for future cert key change is in: $(__green "$Le_Next_Domain_Key")" fi Le_CertCreateTime=$(_time) @@ -6015,7 +5209,7 @@ $_authorizations_map" Le_CertCreateTimeStr=$(_time2str "$Le_CertCreateTime") _savedomainconf "Le_CertCreateTimeStr" "$Le_CertCreateTimeStr" - if [ -z "$Le_RenewalDays" ]; then + if [ -z "$Le_RenewalDays" ] || [ "$Le_RenewalDays" -lt "0" ]; then Le_RenewalDays="$DEFAULT_RENEW" else _savedomainconf "Le_RenewalDays" "$Le_RenewalDays" @@ -6039,17 +5233,12 @@ $_authorizations_map" _clearaccountconf "HTTPS_INSECURE" fi - if [ "$Le_Listen_V4" ] || [ "$Le_Listen_V6" ]; then - if [ "$Le_Listen_V4" ]; then - _savedomainconf "Le_Listen_V4" "$Le_Listen_V4" - else - _cleardomainconf Le_Listen_V4 - fi - if [ "$Le_Listen_V6" ]; then - _savedomainconf "Le_Listen_V6" "$Le_Listen_V6" - else - _cleardomainconf Le_Listen_V6 - fi + if [ "$Le_Listen_V4" ]; then + _savedomainconf "Le_Listen_V4" "$Le_Listen_V4" + _cleardomainconf Le_Listen_V6 + elif [ "$Le_Listen_V6" ]; then + _savedomainconf "Le_Listen_V6" "$Le_Listen_V6" + _cleardomainconf Le_Listen_V4 fi if [ "$Le_ForceNewDomainKey" = "1" ]; then @@ -6061,94 +5250,32 @@ $_authorizations_map" Le_NextRenewTime=$(_date2time "$_notAfter") Le_NextRenewTimeStr="$_notAfter" if [ "$_valid_to" ] && ! _startswith "$_valid_to" "+"; then - _info "The domain is set to be valid until: $_valid_to" - _info "It cannot be renewed automatically" + _info "The domain is set to be valid to: $_valid_to" + _info "It can not be renewed automatically" _info "See: $_VALIDITY_WIKI" else - Le_NextRenewTime=$(_calc_validto_renew_time "$Le_NextRenewTime" "$Le_RenewalDays" "$(_time)") - Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") - fi - elif [ "$Le_RenewalDays" -lt "0" ]; then - _enddate_value=$(_enddate "$CERT_PATH") - if [ "$?" != "0" ] || [ -z "$_enddate_value" ]; then - _err "Failed to get certificate end date for $CERT_PATH" - return 1 - fi - - _endtime=$(_ssldate2time "$_enddate_value") - if [ "$?" != "0" ] || [ -z "$_endtime" ]; then - _err "Cannot parse _enddate_value: $_enddate_value" - return 1 - fi - Le_NextRenewTime=$(_math "$_endtime" + "$Le_RenewalDays" \* 24 \* 60 \* 60) - Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") - else - _endtime_for_cap="" - _enddate_value=$(_enddate "$CERT_PATH") - if [ "$?" = "0" ] && [ "$_enddate_value" ]; then - _endtime_for_cap=$(_ssldate2time "$_enddate_value") - fi - Le_NextRenewTime=$(_calc_next_renew_time "$Le_CertCreateTime" "$Le_RenewalDays" "$_endtime_for_cap") - Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") - fi - - # RFC 9773 ARI: if the CA exposes renewalInfo, override Le_NextRenewTime - # with a time picked at random within the suggestedWindow. This both gives - # the CA full control over renewal scheduling and disperses renewals across - # the network so all clients don't hit the CA at the same instant. - # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and fall back to - # the legacy time-based renewal calculation. - if [ "$NO_ARI" = "1" ]; then - _debug "NO_ARI=1, skipping ARI suggestedWindow override" - elif [ "$ACME_RENEWAL_INFO" ] && [ -f "$CERT_PATH" ] && [ -z "$_notAfter" ]; then - _ari_resp_new="$(_get_ARI "$CERT_PATH")" - _debug2 "_ari_resp_new" "$_ari_resp_new" - _ari_start_new="$(echo "$_ari_resp_new" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')" - _ari_end_new="$(echo "$_ari_resp_new" | _egrep_o '"end" *: *"[^"]*' | sed 's/.*"//')" - if [ "$_ari_start_new" ] && [ "$_ari_end_new" ]; then - _ari_start_t_new="$(_date2time "$(echo "$_ari_start_new" | sed 's/\.[0-9]*//')")" - _ari_end_t_new="$(_date2time "$(echo "$_ari_end_new" | sed 's/\.[0-9]*//')")" - if [ "$_ari_start_t_new" ] && [ "$_ari_end_t_new" ] && [ "$_ari_end_t_new" -gt "$_ari_start_t_new" ]; then - _ari_window=$(_math "$_ari_end_t_new" - "$_ari_start_t_new") - _ari_offset=$(_math "$(_time)" % "$_ari_window") - Le_NextRenewTime=$(_math "$_ari_start_t_new" + "$_ari_offset") + _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") - _info "ARI suggestedWindow: $(__green "$_ari_start_new") to $(__green "$_ari_end_new")" - _info "Next renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")" fi fi + else + Le_NextRenewTime=$(_math "$Le_CertCreateTime" + "$Le_RenewalDays" \* 24 \* 60 \* 60) + Le_NextRenewTime=$(_math "$Le_NextRenewTime" - 86400) + Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") fi - - # Warn when the scheduled renewal falls after the cert has already expired, - # e.g. a 1-day cert from an internal CA combined with the default 30-day - # schedule, which computes from the creation date and never looks at - # notAfter. Skip the warning for a fixed-date --valid-to: there - # Le_NextRenewTime equals the expiry by design and the non-renewable state - # was already reported above. https://github.com/acmesh-official/acme.sh/issues/6917 - if [ -z "$_valid_to" ] || _startswith "$_valid_to" "+"; then - _renew_chk_enddate="$(_enddate "$CERT_PATH")" - _renew_chk_endtime="$(_ssldate2time "$_renew_chk_enddate")" - if [ "$Le_NextRenewTime" ] && [ "$_renew_chk_endtime" ] && [ "$Le_NextRenewTime" -ge "$_renew_chk_endtime" ]; then - _info "$(__red "WARNING: the cert expires at $_renew_chk_enddate, BEFORE the next scheduled renewal time $Le_NextRenewTimeStr.")" - _info "$(__red "The cert will already be expired when the renewal runs. If your CA issues short-lived certs, use a negative --days value (e.g. --days -1) to renew relative to the expiry time.")" - fi - fi - _savedomainconf "Le_NextRenewTimeStr" "$Le_NextRenewTimeStr" _savedomainconf "Le_NextRenewTime" "$Le_NextRenewTime" - #convert to pkcs12 - Le_PFXPassword="$(_readdomainconf Le_PFXPassword)" - if [ "$Le_PFXPassword" ]; then - _toPkcs "$CERT_PFX_PATH" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$Le_PFXPassword" - fi - - #convert to pkcs8 - Le_PKCS8Password="$(_readdomainconf Le_PKCS8Password)" - if [ "$Le_PKCS8Password" ]; then - _toPkcs8 "$CERT_PKCS8_PATH" "$CERT_KEY_PATH" "$Le_PKCS8Password" - fi - if [ "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then _savedomainconf "Le_RealCertPath" "$_real_cert" _savedomainconf "Le_RealCACertPath" "$_real_ca" @@ -6161,24 +5288,12 @@ $_authorizations_map" fi if ! _on_issue_success "$_post_hook" "$_renew_hook"; then - _err "Error calling hook." + _err "Call hook error." return 1 fi } #in_out_cert out_fullchain out_ca -#Reads a PEM chain from stdin, prints it without the blank lines. -#Some CAs (Let's Encrypt) separate the certificates of a chain with a blank -#line, others (ZeroSSL) don't. The blank lines are valid PEM (RFC 7468), but -#some devices and APIs reject them, so the certs are stored back to back. -#https://github.com/acmesh-official/acme.sh/issues/1940 -_strip_blank_lines() { - #spell out space and tab: Solaris sed treats [[:space:]] as a literal - #bracket set and silently stops matching the blank lines - _sbl_tab="$(printf '\t')" - sed "/^[ $_sbl_tab]*\$/d" -} - _split_cert_chain() { _certf="$1" _fullchainf="$2" @@ -6207,11 +5322,11 @@ renew() { _debug "_renewServer" "$_renewServer" _initpath "$Le_Domain" "$_isEcc" - _info "Renew: $Le_Domain" + _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT} - _info "$(__green "Renewing: '$Le_Domain'")" + _info "$(__green "Renew: '$Le_Domain'")" if [ ! -f "$DOMAIN_CONF" ]; then - _info "'$Le_Domain' is not an issued domain, skipping." + _info "'$Le_Domain' is not an issued domain, skip." return $RENEW_SKIP fi @@ -6222,30 +5337,25 @@ renew() { . "$DOMAIN_CONF" _debug Le_API "$Le_API" - #don't switch it back - # case "$Le_API" in - # "$CA_LETSENCRYPT_V2_TEST") - # _info "Switching back to $CA_LETSENCRYPT_V2" - # Le_API="$CA_LETSENCRYPT_V2" - # ;; - # "$CA_GOOGLE_TEST") - # _info "Switching back to $CA_GOOGLE" - # Le_API="$CA_GOOGLE" - # ;; - # esac + case "$Le_API" in + "$CA_LETSENCRYPT_V2_TEST") + _info "Switching back to $CA_LETSENCRYPT_V2" + Le_API="$CA_LETSENCRYPT_V2" + ;; + "$CA_BUYPASS_TEST") + _info "Switching back to $CA_BUYPASS" + Le_API="$CA_BUYPASS" + ;; + "$CA_GOOGLE_TEST") + _info "Switching back to $CA_GOOGLE" + Le_API="$CA_GOOGLE" + ;; + esac if [ "$_server" ]; then Le_API="$_server" fi - _info "Renewing using Le_API=$Le_API" - - # Honor --local-address given on the renew/renewAll command line: it overrides - # the value saved at issue time (and gets re-saved by issue() below), so certs - # issued before the machine gained multiple addresses can still be renewed. - # https://github.com/acmesh-official/acme.sh/issues/7009 - if [ "$_local_address" ]; then - Le_LocalAddress="$_local_address" - fi + _info "Renew to Le_API=$Le_API" _clearAPI _clearCA @@ -6255,79 +5365,9 @@ renew() { _debug2 "initpath again." _initpath "$Le_Domain" "$_isEcc" - # ARI (RFC 9773): fetch the CA's suggestedWindow on every renewal check. - # If the window has started, renew now even if Le_NextRenewTime is in the future. - # Set NO_ARI=1 (env, account.conf, or ca.conf) to opt out and use only - # Le_NextRenewTime for the renewal decision. - if [ "$NO_ARI" = "1" ]; then - _debug "NO_ARI=1, skipping ARI suggestedWindow check" - elif [ -z "$FORCE" ] && [ -f "$CERT_PATH" ]; then - if _initAPI && [ "$ACME_RENEWAL_INFO" ]; then - _ari_resp="$(_get_ARI "$CERT_PATH")" - _debug2 "_ari_resp" "$_ari_resp" - _ari_start="$(echo "$_ari_resp" | _egrep_o '"start" *: *"[^"]*' | sed 's/.*"//')" - _ari_end="$(echo "$_ari_resp" | _egrep_o '"end" *: *"[^"]*' | sed 's/.*"//')" - _debug "ARI suggestedWindow.start" "$_ari_start" - _debug "ARI suggestedWindow.end" "$_ari_end" - if [ "$_ari_start" ] && [ "$_ari_end" ]; then - _ari_start_t="$(_date2time "$(echo "$_ari_start" | sed 's/\.[0-9]*//')")" - _ari_end_t="$(_date2time "$(echo "$_ari_end" | sed 's/\.[0-9]*//')")" - _ari_explanation_url="$(echo "$_ari_resp" | _egrep_o '"explanationURL" *: *"[^"]*' | sed 's/.*"//')" - _debug "_ari_start_t" "$_ari_start_t" - _debug "_ari_end_t" "$_ari_end_t" - _debug "_ari_explanation_url" "$_ari_explanation_url" - _debug "Le_NextRenewTime" "$Le_NextRenewTime" - # Update ARI if needed - if [ "$_ari_start_t" ] && [ "$_ari_end_t" ] && [ "$Le_NextRenewTime" ] && [ "$_ari_end_t" -gt "$_ari_start_t" ] && ([ "$Le_NextRenewTime" -lt "$_ari_start_t" ] || [ "$Le_NextRenewTime" -gt "$_ari_end_t" ]); then - _ari_old_time_str="$Le_NextRenewTimeStr" - _info "Current renewal time: $(__green "$_ari_old_time_str")" - _ari_window=$(_math "$_ari_end_t" - "$_ari_start_t") - _ari_offset=$(_math "$(_time)" % "$_ari_window") - Le_NextRenewTime=$(_math "$_ari_start_t" + "$_ari_offset") - Le_NextRenewTimeStr=$(_time2str "$Le_NextRenewTime") - _info "ARI suggestedWindow: $(__green "$_ari_start") to $(__green "$_ari_end")" - _info "Updating renewal time picked from ARI window: $(__green "$Le_NextRenewTimeStr")" - _savedomainconf Le_NextRenewTime "$Le_NextRenewTime" - _savedomainconf Le_NextRenewTimeStr "$Le_NextRenewTimeStr" - fi - if [ "$Le_NextRenewTime" ] && [ "$(_time)" -ge "$Le_NextRenewTime" ]; then - _info "ARI suggested renewal has passed ($(__green "$Le_NextRenewTimeStr")), proceeding with renewal." - if [ "$_ari_explanation_url" ]; then - _info "For more information on this renewal: $(__green "$_ari_explanation_url")" - fi - fi - fi - fi - fi - if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then - _renew_retry_fixed="" - res="0" - _ensure_install "$Le_Domain" - res="$?" - if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; then - _ensure_deploy "$Le_Domain" - res="$?" - fi - if [ "$res" != "0" ]; then - if [ -z "$_ACME_IN_RENEWALL" ]; then - if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then - _send_notify "Renew $Le_Domain error" "There is an error." "$NOTIFY_HOOK" 1 - fi - fi - return 1 - fi - if [ "$_renew_retry_fixed" ]; then - _info "Install/deploy retry succeeded, no renewal is needed." - if [ -z "$_ACME_IN_RENEWALL" ]; then - if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then - _send_notify "Renew $Le_Domain success" "Good, the cert install/deploy retry succeeded." "$NOTIFY_HOOK" 0 - fi - fi - return 0 - fi - _info "Skipping. Next renewal time is: $(__green "$Le_NextRenewTimeStr")" - _info "Add '$(__red '--force')' to force renewal." + _info "Skip, Next renewal time is: $(__green "$Le_NextRenewTimeStr")" + _info "Add '$(__red '--force')' to force to renew." if [ -z "$_ACME_IN_RENEWALL" ]; then if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then _send_notify "Renew $Le_Domain skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP" @@ -6337,7 +5377,7 @@ renew() { fi if [ "$_ACME_IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then - _info "Skipping invalid cert for: $Le_Domain" + _info "Skip invalid cert for: $Le_Domain" return $RENEW_SKIP fi @@ -6347,11 +5387,6 @@ renew() { Le_PostHook="$(_readdomainconf Le_PostHook)" Le_RenewHook="$(_readdomainconf Le_RenewHook)" Le_Preferred_Chain="$(_readdomainconf Le_Preferred_Chain)" - Le_Certificate_Profile="$(_readdomainconf Le_Certificate_Profile)" - Le_Valid_From="$(_readdomainconf Le_Valid_From)" - Le_Valid_To="$(_readdomainconf Le_Valid_To)" - Le_ExtKeyUse="$(_readdomainconf Le_ExtKeyUse)" - # When renewing from an old version, the empty Le_Keylength means 2048. # Note, do not use DEFAULT_DOMAIN_KEY_LENGTH as that value may change over # time but an empty value implies 2048 specifically. @@ -6359,17 +5394,13 @@ renew() { if [ -z "$Le_Keylength" ]; then Le_Keylength=2048 fi - if [ "$CA_LETSENCRYPT_V2" = "$Le_API" ]; then - #letsencrypt doesn't support ocsp anymore - if [ "$Le_OCSP_Staple" ]; then - export Le_OCSP_Staple="" - _cleardomainconf Le_OCSP_Staple - fi - fi - issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" "$Le_Certificate_Profile" "$Le_ExtKeyUse" + issue "$Le_Webroot" "$Le_Domain" "$Le_Alt" "$Le_Keylength" "$Le_RealCertPath" "$Le_RealKeyPath" "$Le_RealCACertPath" "$Le_ReloadCmd" "$Le_RealFullChainPath" "$Le_PreHook" "$Le_PostHook" "$Le_RenewHook" "$Le_LocalAddress" "$Le_ChallengeAlias" "$Le_Preferred_Chain" "$Le_Valid_From" "$Le_Valid_To" res="$?" + if [ "$res" != "0" ]; then + return "$res" + fi - if [ "$Le_DeployHook" ] && [ "$res" = "0" ]; then + if [ "$Le_DeployHook" ]; then _deploy "$Le_Domain" "$Le_DeployHook" res="$?" fi @@ -6409,31 +5440,20 @@ renewAll() { _set_level=${NOTIFY_LEVEL:-$NOTIFY_LEVEL_DEFAULT} _debug "_set_level" "$_set_level" export _ACME_IN_RENEWALL=1 - if ! [ -d "$CERT_HOME" ]; then - _err "$CERT_HOME is not a directory, please check your configuration." - return 1 - fi - for di in "${CERT_HOME}"/*.* "${CERT_HOME}"/*:*; do + for di in "${CERT_HOME}"/*.*/; do _debug di "$di" if ! [ -d "$di" ]; then - _debug "Not a directory, skipping: $di" + _debug "Not a directory, skip: $di" continue fi d=$(basename "$di") _debug d "$d" - _d_ari="$di.ari" - _debug _d_ari "$_d_ari" ( if _endswith "$d" "$ECC_SUFFIX"; then _isEcc=$(echo "$d" | cut -d "$ECC_SEP" -f 2) d=$(echo "$d" | cut -d "$ECC_SEP" -f 1) fi renew "$d" "$_isEcc" "$_server" - rc="$?" - if [ "$rc" = "0" ] && [ "$_ari_explanation_url" ]; then - echo "$_ari_explanation_url" >"$_d_ari" - fi - return $rc ) rc="$?" _debug "Return code: $rc" @@ -6448,13 +5468,8 @@ renewAll() { _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0 fi fi - _renewal_explanation="" - if [ -f "$_d_ari" ]; then - _renewal_explanation=" ($(cat "$_d_ari"))" - rm -f "$_d_ari" - fi - _success_msg="${_success_msg} $d$_renewal_explanation + _success_msg="${_success_msg} $d " elif [ "$rc" = "$RENEW_SKIP" ]; then if [ $_error_level -gt $NOTIFY_LEVEL_SKIP ]; then @@ -6486,12 +5501,12 @@ renewAll() { _error_msg="${_error_msg} $d " if [ "$_stopRenewOnError" ]; then - _err "Error renewing $d, stopping." + _err "Error renew $d, stop now." _ret="$rc" break else _ret="$rc" - _err "Error renewing $d." + _err "Error renew $d." fi fi done @@ -6502,13 +5517,13 @@ renewAll() { _msg_subject="Renew" if [ "$_error_msg" ]; then _msg_subject="${_msg_subject} Error" - _msg_data="Errored certs: + _msg_data="Error certs: ${_error_msg} " fi if [ "$_success_msg" ]; then _msg_subject="${_msg_subject} Success" - _msg_data="${_msg_data}Successful certs: + _msg_data="${_msg_data}Success certs: ${_success_msg} " fi @@ -6523,9 +5538,6 @@ ${_skipped_msg} fi fi - if [ "$_TREAT_SKIP_AS_SUCCESS" ] && [ "$_ret" = "$RENEW_SKIP" ]; then - _ret=0 - fi return "$_ret" } @@ -6549,25 +5561,21 @@ signcsr() { _local_addr="${11}" _challenge_alias="${12}" _preferred_chain="${13}" - _valid_f="${14}" - _valid_t="${15}" - _cert_prof="${16}" - _en_key_usage="${17}" _csrsubj=$(_readSubjectFromCSR "$_csrfile") if [ "$?" != "0" ]; then - _err "Cannot read subject from CSR: $_csrfile" + _err "Can not read subject from csr: $_csrfile" return 1 fi _debug _csrsubj "$_csrsubj" if _contains "$_csrsubj" ' ' || ! _contains "$_csrsubj" '.'; then - _info "It seems that the subject $_csrsubj is not a valid domain name. Dropping it." + _info "It seems that the subject: $_csrsubj is not a valid domain name. Drop it." _csrsubj="" fi _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile") if [ "$?" != "0" ]; then - _err "Cannot read domain list from CSR: $_csrfile" + _err "Can not read domain list from csr: $_csrfile" return 1 fi _debug "_csrdomainlist" "$_csrdomainlist" @@ -6580,23 +5588,23 @@ signcsr() { fi if [ -z "$_csrsubj" ]; then - _err "Cannot read subject from CSR: $_csrfile" + _err "Can not read subject from csr: $_csrfile" return 1 fi _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile") if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then - _err "Cannot read key length from CSR: $_csrfile" + _err "Can not read key length from csr: $_csrfile" return 1 fi _initpath "$_csrsubj" "$_csrkeylength" mkdir -p "$DOMAIN_PATH" - _info "Copying CSR to: $CSR_PATH" + _info "Copy csr to: $CSR_PATH" cp "$_csrfile" "$CSR_PATH" - issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" "$_preferred_chain" "$_valid_f" "$_valid_t" "$_cert_prof" "$_en_key_usage" + issue "$_csrW" "$_csrsubj" "$_csrdomainlist" "$_csrkeylength" "$_real_cert" "$_real_key" "$_real_ca" "$_reload_cmd" "$_real_fullchain" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_addr" "$_challenge_alias" "$_preferred_chain" } @@ -6612,18 +5620,18 @@ showcsr() { _csrsubj=$(_readSubjectFromCSR "$_csrfile") if [ "$?" != "0" ]; then - _err "Cannot read subject from CSR: $_csrfile" + _err "Can not read subject from csr: $_csrfile" return 1 fi if [ -z "$_csrsubj" ]; then - _info "The subject is empty" + _info "The Subject is empty" fi _info "Subject=$_csrsubj" _csrdomainlist=$(_readSubjectAltNamesFromCSR "$_csrfile") if [ "$?" != "0" ]; then - _err "Cannot read domain list from CSR: $_csrfile" + _err "Can not read domain list from csr: $_csrfile" return 1 fi _debug "_csrdomainlist" "$_csrdomainlist" @@ -6632,7 +5640,7 @@ showcsr() { _csrkeylength=$(_readKeyLengthFromCSR "$_csrfile") if [ "$?" != "0" ] || [ -z "$_csrkeylength" ]; then - _err "Cannot read key length from CSR: $_csrfile" + _err "Can not read key length from csr: $_csrfile" return 1 fi _info "KeyLength=$_csrkeylength" @@ -6647,10 +5655,9 @@ list() { _sep="|" if [ "$_raw" ]; then if [ -z "$_domain" ]; then - printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Profile${_sep}CA${_sep}Created${_sep}Renew" + printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew" fi - for di in "${CERT_HOME}"/*.* "${CERT_HOME}"/*:*; do - [ -d "$di" ] || continue + for di in "${CERT_HOME}"/*.*/; do d=$(basename "$di") _debug d "$d" ( @@ -6663,7 +5670,7 @@ list() { . "$DOMAIN_CONF" _ca="$(_getCAShortName "$Le_API")" if [ -z "$_domain" ]; then - printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$Le_Certificate_Profile${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr" + printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr" else if [ "$_domain" = "$d" ]; then cat "$DOMAIN_CONF" @@ -6682,48 +5689,6 @@ list() { } -list_profiles() { - _initpath - _initAPI - - _l_server_url="$ACME_DIRECTORY" - _l_server_name="$(_getCAShortName "$_l_server_url")" - _info "Fetching profiles from $_l_server_name ($_l_server_url)..." - - response=$(_get "$_l_server_url" "" 10) - if [ "$?" != "0" ]; then - _err "Failed to connect to CA directory: $_l_server_url" - return 1 - fi - - normalized_response=$(echo "$response" | _normalizeJson) - profiles_json=$(echo "$normalized_response" | _egrep_o '"profiles" *: *[{][^}]*[}]') - - if [ -z "$profiles_json" ]; then - _info "The CA '$_l_server_name' does not publish certificate profiles via its directory endpoint." - return 0 - fi - - # Strip the outer layer to get the key-value pairs - profiles_kv=$(echo "$profiles_json" | sed 's/"profiles" *: *{//' | sed 's/}$//' | tr ',' '\n') - - printf "\n%-15s %s\n" "name" "info" - printf -- "--------------------------------------------------------------------\n" - - _old_IFS="$IFS" - IFS=' -' - for pair in $profiles_kv; do - # Trim quotes and whitespace - _name=$(echo "$pair" | cut -d: -f1 | tr -d '" \t') - _info_url=$(echo "$pair" | cut -d: -f2- | sed 's/^ *//' | tr -d '"') - printf "%-15s %s\n" "$_name" "$_info_url" - done - IFS="$_old_IFS" - - return 0 -} - _deploy() { _d="$1" _hooks="$2" @@ -6731,73 +5696,34 @@ _deploy() { for _d_api in $(echo "$_hooks" | tr ',' " "); do _deployApi="$(_findHook "$_d" $_SUB_FOLDER_DEPLOY "$_d_api")" if [ -z "$_deployApi" ]; then - _err "The deploy hook $_d_api was not found." + _err "The deploy hook $_d_api is not found." return 1 fi _debug _deployApi "$_deployApi" if ! ( if ! . "$_deployApi"; then - _err "Error loading file $_deployApi. Please check your API file and try again." + _err "Load file $_deployApi error. Please check your api file and try again." return 1 fi d_command="${_d_api}_deploy" if ! _exists "$d_command"; then - _err "It seems that your API file is not correct. Make sure it has a function named: $d_command" + _err "It seems that your api file is not correct, it must have a function named: $d_command" return 1 fi - if ! $d_command "$_d" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CERT_PFX_PATH"; then - _err "Error deploying for domain: $_d" + if ! $d_command "$_d" "$CERT_KEY_PATH" "$CERT_PATH" "$CA_CERT_PATH" "$CERT_FULLCHAIN_PATH"; then + _err "Error deploy for domain:$_d" return 1 fi ); then - _err "Error encountered while deploying." + _err "Deploy error." return 1 else _info "$(__green Success)" fi done - - _deploy_success_time="$(_time)" - _savedomainconf "Le_DeploySuccessTime" "$_deploy_success_time" - _savedomainconf "Le_DeploySuccessTimeStr" "$(_time2str "$_deploy_success_time")" -} - -_ensure_deploy() { - _d="$1" - if [ -z "$Le_DeployHook" ]; then - return 0 - fi - if [ -z "$Le_CertCreateTime" ]; then - return 0 - fi - - _deploy_success_time="$(_readdomainconf Le_DeploySuccessTime)" - if [ -z "$_deploy_success_time" ]; then - _debug "Le_DeploySuccessTime is empty, skip deploy retry check." - return 0 - fi - case "$_deploy_success_time$Le_CertCreateTime" in - *[!0-9]*) - _debug "Le_DeploySuccessTime or Le_CertCreateTime is not a number, skip deploy retry check." - return 0 - ;; - esac - - if [ "$_deploy_success_time" -lt "$Le_CertCreateTime" ]; then - _info "The cert was created after the last successful deploy, retrying deploy hooks." - if _deploy "$_d" "$Le_DeployHook"; then - _info "Deploy retry succeeded." - _renew_retry_fixed=1 - return 0 - fi - _err "Deploy retry failed." - return 1 - fi - - return 0 } #domain hooks @@ -6813,18 +5739,11 @@ deploy() { _initpath "$_d" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then _err "The domain '$_d' is not a cert name. You must use the cert name to specify the cert to install." - _err "Cannot find path: '$DOMAIN_PATH'" + _err "Can not find path:'$DOMAIN_PATH'" return 1 fi - _debug2 DOMAIN_CONF "$DOMAIN_CONF" - # The cert dir may exist without a domain conf (e.g. the conf was deleted, or - # the cert was placed here manually). Deploy can still proceed using env-provided - # settings, and _savedomainconf below will recreate the conf, so only source it - # when present instead of failing on a missing file. - if [ -f "$DOMAIN_CONF" ]; then - . "$DOMAIN_CONF" - fi + . "$DOMAIN_CONF" _savedomainconf Le_DeployHook "$_hooks" @@ -6848,7 +5767,7 @@ installcert() { _initpath "$_main_domain" "$_isEcc" if [ ! -d "$DOMAIN_PATH" ]; then _err "The domain '$_main_domain' is not a cert name. You must use the cert name to specify the cert to install." - _err "Cannot find path: '$DOMAIN_PATH'" + _err "Can not find path:'$DOMAIN_PATH'" return 1 fi @@ -6943,7 +5862,7 @@ _installcert() { fi if [ "$_reload_cmd" ]; then - _info "Running reload cmd: $_reload_cmd" + _info "Run reload cmd: $_reload_cmd" if ( export CERT_PATH export CERT_KEY_PATH @@ -6954,57 +5873,12 @@ _installcert() { export Le_Next_Domain_Key cd "$DOMAIN_PATH" && eval "$_reload_cmd" ); then - _info "$(__green "Reload successful")" + _info "$(__green "Reload success")" else - _err "Reload error for: $_main_domain" - return 1 + _err "Reload error for :$Le_Domain" fi fi - _installcert_success_time="$(_time)" - _savedomainconf "Le_InstallCertSuccessTime" "$_installcert_success_time" - _savedomainconf "Le_InstallCertSuccessTimeStr" "$(_time2str "$_installcert_success_time")" -} - -_ensure_install() { - _d="$1" - if [ -z "$Le_CertCreateTime" ]; then - return 0 - fi - - _real_cert="$(_readdomainconf Le_RealCertPath)" - _real_key="$(_readdomainconf Le_RealKeyPath)" - _real_ca="$(_readdomainconf Le_RealCACertPath)" - _reload_cmd="$(_readdomainconf Le_ReloadCmd)" - _real_fullchain="$(_readdomainconf Le_RealFullChainPath)" - if [ -z "$_real_cert$_real_key$_real_ca$_reload_cmd$_real_fullchain" ]; then - return 0 - fi - - _installcert_success_time="$(_readdomainconf Le_InstallCertSuccessTime)" - if [ -z "$_installcert_success_time" ]; then - _debug "Le_InstallCertSuccessTime is empty, skip install retry check." - return 0 - fi - case "$_installcert_success_time$Le_CertCreateTime" in - *[!0-9]*) - _debug "Le_InstallCertSuccessTime or Le_CertCreateTime is not a number, skip install retry check." - return 0 - ;; - esac - - if [ "$_installcert_success_time" -lt "$Le_CertCreateTime" ]; then - _info "The cert was created after the last successful install, retrying install cert." - if _installcert "$_d" "$_real_cert" "$_real_key" "$_real_ca" "$_real_fullchain" "$_reload_cmd"; then - _info "Install cert retry succeeded." - _renew_retry_fixed=1 - return 0 - fi - _err "Install cert retry failed." - return 1 - fi - - return 0 } __read_password() { @@ -7024,48 +5898,45 @@ _install_win_taskscheduler() { _lesh="$1" _centry="$2" _randomminute="$3" - _randomhour="$4" if ! _exists cygpath; then _err "cygpath not found" return 1 fi if ! _exists schtasks; then - _err "schtasks.exe was not found, are you on Windows?" + _err "schtasks.exe is not found, are you on Windows?" return 1 fi _winbash="$(cygpath -w $(which bash))" _debug _winbash "$_winbash" if [ -z "$_winbash" ]; then - _err "Cannot find bash path" + _err "can not find bash path" return 1 fi _myname="$(whoami)" _debug "_myname" "$_myname" if [ -z "$_myname" ]; then - _err "Can not find own username" + _err "can not find my user name" return 1 fi _debug "_lesh" "$_lesh" - _info "To install the scheduler task to your Windows account, you must input your Windows password." - _info "$PROJECT_NAME will not save your password." + _info "To install scheduler task in your Windows account, you must input your windows password." + _info "$PROJECT_NAME doesn't save your password." _info "Please input your Windows password for: $(__green "$_myname")" _password="$(__read_password)" - #schtasks.exe /ST requires the HH:mm format, so the minute must be zero-padded (issue 4950) - _st_minute="$(printf "%02d" "$_randomminute")" - #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_st_minute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null - echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_st_minute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null + #SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'" >/dev/null + echo SCHTASKS.exe '/create' '/SC' 'DAILY' '/TN' "$_WINDOWS_SCHEDULER_NAME" '/F' '/ST' "00:$_randomminute" '/RU' "$_myname" '/RP' "$_password" '/TR' "\"$_winbash -l -c '$_lesh --cron --home \"$LE_WORKING_DIR\" $_centry'\"" | cmd.exe >/dev/null echo } _uninstall_win_taskscheduler() { if ! _exists schtasks; then - _err "schtasks.exe was not found, are you on Windows?" + _err "schtasks.exe is not found, are you on Windows?" return 1 fi if ! echo SCHTASKS /query /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null; then - _debug "scheduler $_WINDOWS_SCHEDULER_NAME was not found." + _debug "scheduler $_WINDOWS_SCHEDULER_NAME is not found." else _info "Removing $_WINDOWS_SCHEDULER_NAME" echo SCHTASKS /delete /f /tn "$_WINDOWS_SCHEDULER_NAME" | cmd.exe >/dev/null @@ -7087,7 +5958,7 @@ installcronjob() { _info "Using the current script from: $_script" lesh="$_script" else - _err "Cannot install cronjob, $PROJECT_ENTRY not found." + _err "Can not install cronjob, $PROJECT_ENTRY not found." return 1 fi fi @@ -7096,7 +5967,6 @@ installcronjob() { fi _t=$(_time) random_minute=$(_math $_t % 60) - random_hour=$(_math $_t / 60 % 6) if ! _exists "$_CRONTAB" && _exists "fcrontab"; then _CRONTAB="fcrontab" @@ -7104,59 +5974,37 @@ installcronjob() { if ! _exists "$_CRONTAB"; then if _exists cygpath && _exists schtasks.exe; then - _info "It seems you are on Windows, let's install the Windows scheduler task." - if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute" "$random_hour"; then - _info "Successfully installed Windows scheduler task." + _info "It seems you are on Windows, let's install Windows scheduler task." + if _install_win_taskscheduler "$lesh" "$_c_entry" "$random_minute"; then + _info "Install Windows scheduler task success." return 0 else - _err "Failed to install Windows scheduler task." + _err "Install Windows scheduler task failed." return 1 fi fi - _err "crontab/fcrontab doesn't exist, so we cannot install cron jobs." - _err "Your certs will not be renewed automatically." - _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' every day." + _err "crontab/fcrontab doesn't exist, so, we can not install cron jobs." + _err "All your certs will not be renewed automatically." + _err "You must add your own cron job to call '$PROJECT_ENTRY --cron' everyday." return 1 fi _info "Installing cron job" - _cron_entry="$random_minute $random_hour,$(_math "$random_hour" + 6),$(_math "$random_hour" + 12),$(_math "$random_hour" + 18) * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" - _cron_entries="$($_CRONTAB -l 2>/dev/null)" - if [ "$?" != "0" ]; then - #when the user has no crontab yet, crontab -l also exits non-zero; - #only that case may proceed with an empty list. Any other listing - #failure must abort: piping an incomplete list back into 'crontab -' - #would wipe the user's existing cron jobs (issue 3079) - _cron_list_err="$($_CRONTAB -l 2>&1 >/dev/null)" - #separate greps: BRE alternation \| is a GNU extension and Solaris - #grep takes only a single -e pattern - if echo "$_cron_list_err" | grep -i "no crontab" >/dev/null || - echo "$_cron_list_err" | grep -i "no fcrontab" >/dev/null || - echo "$_cron_list_err" | grep -i "can't open" >/dev/null; then - _cron_entries="" - else - _err "Can not list the current cron jobs: $_cron_list_err" - _err "Refusing to install the cron job, that could wipe your existing cron jobs." - _err "Please add this cron job manually:" - _err "$_cron_entry" - return 1 - fi - fi - if ! echo "$_cron_entries" | grep "$PROJECT_ENTRY --cron"; then + if ! $_CRONTAB -l | grep "$PROJECT_ENTRY --cron"; then if _exists uname && uname -a | grep SunOS >/dev/null; then - _CRONTAB_STDIN="$_CRONTAB --" + $_CRONTAB -l | { + cat + echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" + } | $_CRONTAB -- else - _CRONTAB_STDIN="$_CRONTAB -" + $_CRONTAB -l | { + cat + echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" + } | $_CRONTAB - fi - { - if [ "$_cron_entries" ]; then - echo "$_cron_entries" - fi - echo "$_cron_entry" - } | $_CRONTAB_STDIN fi if [ "$?" != "0" ]; then - _err "Failed to install cron job. You need to manually renew your certs." - _err "Alternatively, you can add a cron job by yourself:" + _err "Install cron job failed. You need to manually renew your certs." + _err "Or you can add cronjob by yourself:" _err "$lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null" return 1 fi @@ -7170,12 +6018,12 @@ uninstallcronjob() { if ! _exists "$_CRONTAB"; then if _exists cygpath && _exists schtasks.exe; then - _info "It seems you are on Windows, let's uninstall the Windows scheduler task." + _info "It seems you are on Windows, let's uninstall Windows scheduler task." if _uninstall_win_taskscheduler; then - _info "Successfully uninstalled Windows scheduler task." + _info "Uninstall Windows scheduler task success." return 0 else - _err "Failed to uninstall Windows scheduler task." + _err "Uninstall Windows scheduler task failed." return 1 fi fi @@ -7215,12 +6063,12 @@ revoke() { fi _initpath "$Le_Domain" "$_isEcc" if [ ! -f "$DOMAIN_CONF" ]; then - _err "$Le_Domain is not an issued domain, skipping." + _err "$Le_Domain is not a issued domain, skip." return 1 fi if [ ! -f "$CERT_PATH" ]; then - _err "Cert for $Le_Domain $CERT_PATH was not found, skipping." + _err "Cert for $Le_Domain $CERT_PATH is not found, skip." return 1 fi @@ -7244,7 +6092,7 @@ revoke() { cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _url_replace)" if [ -z "$cert" ]; then - _err "Cert for $Le_Domain is empty, skipping." + _err "Cert for $Le_Domain is empty found, skip." return 1 fi @@ -7254,37 +6102,38 @@ revoke() { uri="${ACME_REVOKE_CERT}" - _info "Trying account key first." - if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then - if [ -z "$response" ]; then - _info "Successfully revoked." - rm -f "$CERT_PATH" - cat "$CERT_KEY_PATH" >"$CERT_KEY_PATH.revoked" - cat "$CSR_PATH" >"$CSR_PATH.revoked" - return 0 - else - _err "Error revoking." - _debug "$response" - fi - fi - if [ -f "$CERT_KEY_PATH" ]; then - _info "Trying domain key." + _info "Try domain key first." if _send_signed_request "$uri" "$data" "" "$CERT_KEY_PATH"; then if [ -z "$response" ]; then - _info "Successfully revoked." + _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 "Error revoking using domain key." + _err "Revoke error by domain key." _err "$response" fi fi else _info "Domain key file doesn't exist." fi + + _info "Try account key." + + if _send_signed_request "$uri" "$data" "" "$ACCOUNT_KEY_PATH"; then + 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." + _debug "$response" + fi + fi return 1 } @@ -7302,19 +6151,19 @@ remove() { _removed_conf="$DOMAIN_CONF.removed" if [ ! -f "$DOMAIN_CONF" ]; then if [ -f "$_removed_conf" ]; then - _err "$Le_Domain has already been removed. You can remove the folder by yourself: $DOMAIN_PATH" + _err "$Le_Domain is already removed, You can remove the folder by yourself: $DOMAIN_PATH" else - _err "$Le_Domain is not an issued domain, skipping." + _err "$Le_Domain is not a issued domain, skip." fi return 1 fi if mv "$DOMAIN_CONF" "$_removed_conf"; then - _info "$Le_Domain has been removed. The key and cert files are in $(__green $DOMAIN_PATH)" + _info "$Le_Domain is removed, the key and cert files are in $(__green $DOMAIN_PATH)" _info "You can remove them by yourself." return 0 else - _err "Failed to remove $Le_Domain." + _err "Remove $Le_Domain failed." return 1 fi } @@ -7344,10 +6193,10 @@ _deactivate() { _identifiers="{\"type\":\"$(_getIdType "$_d_domain")\",\"value\":\"$_d_domain\"}" if ! _send_signed_request "$ACME_NEW_ORDER" "{\"identifiers\": [$_identifiers]}"; then - _err "Cannot get new order for domain." + _err "Can not get domain new order." return 1 fi - _authorizations_seg="$(echo "$response" | _json_decode | _authorizations_from_order)" + _authorizations_seg="$(echo "$response" | _egrep_o '"authorizations" *: *\[[^\]*\]' | cut -d '[' -f 2 | tr -d ']' | tr -d '"')" _debug2 _authorizations_seg "$_authorizations_seg" if [ -z "$_authorizations_seg" ]; then _err "_authorizations_seg not found." @@ -7359,7 +6208,7 @@ _deactivate() { authzUri="$_authorizations_seg" _debug2 "authzUri" "$authzUri" if ! _send_signed_request "$authzUri"; then - _err "Error making GET request for authz." + _err "get to authz error." _err "_authorizations_seg" "$_authorizations_seg" _err "authzUri" "$authzUri" _clearup @@ -7379,11 +6228,10 @@ _deactivate() { fi _debug "Trigger validation." vtype="$(_getIdType "$_d_domain")" - # Fix for empty error objects in response which mess up the original code, adapted from fix suggested here: https://github.com/acmesh-official/acme.sh/issues/4933#issuecomment-1870499018 - entry="$(echo "$response" | sed s/'"error":{}'/'"error":null'/ | _egrep_o '[^{]*"type":"'$vtype'"[^}]*')" + entry="$(echo "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" _debug entry "$entry" if [ -z "$entry" ]; then - _err "$d: Cannot get domain token" + _err "Error, can not get domain token $d" return 1 fi token="$(echo "$entry" | _egrep_o '"token":"[^"]*' | cut -d : -f 2 | tr -d '"')" @@ -7401,13 +6249,13 @@ _deactivate() { _d_i=0 _d_max_retry=$(echo "$entries" | wc -l) while [ "$_d_i" -lt "$_d_max_retry" ]; do - _info "Deactivating $_d_domain" + _info "Deactivate: $_d_domain" _d_i="$(_math $_d_i + 1)" entry="$(echo "$entries" | sed -n "${_d_i}p")" _debug entry "$entry" if [ -z "$entry" ]; then - _info "No more valid entries found." + _info "No more valid entry found." break fi @@ -7419,27 +6267,27 @@ _deactivate() { _debug uri "$uri" if [ "$_d_type" ] && [ "$_d_type" != "$_vtype" ]; then - _info "Skipping $_vtype" + _info "Skip $_vtype" continue fi - _info "Deactivating $_vtype" + _info "Deactivate: $_vtype" _djson="{\"status\":\"deactivated\"}" if _send_signed_request "$authzUri" "$_djson" && _contains "$response" '"deactivated"'; then - _info "Successfully deactivated $_vtype." + _info "Deactivate: $_vtype success." else - _err "Could not deactivate $_vtype." + _err "Can not deactivate $_vtype." break fi done _debug "$_d_i" if [ "$_d_i" -eq "$_d_max_retry" ]; then - _info "Successfully deactivated!" + _info "Deactivated success!" else - _err "Deactivation failed." + _err "Deactivate failed." fi } @@ -7464,62 +6312,6 @@ deactivate() { done } -#reads the output of "openssl x509 -text" from stdin, prints the hex AKI -#the value is on the line right after the extension header; "grep -A" is not -#portable (Solaris /usr/bin/grep: "illegal option -- A"), so select from the -#header to EOF and keep the second line of that range -_extractAKI() { - sed -n '/X509v3 Authority Key Identifier/,$p' | _head_n 2 | _tail_n 1 | tr -d ': ' | sed "s/keyid//" -} - -#cert -_getAKI() { - _cert="$1" - ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -text -noout | _extractAKI -} - -#cert -_getSerial() { - _cert="$1" - ${ACME_OPENSSL_BIN:-openssl} x509 -in "$_cert" -serial -noout | cut -d = -f 2 -} - -#cert -#Compute the ARI/replaces certID for a cert: base64url(AKI).base64url(Serial) -#per RFC 9773 Section 4.1. -_getARICertID() { - _cert="$1" - _aki=$(_getAKI "$_cert") - _ser=$(_getSerial "$_cert") - _debug2 "_aki" "$_aki" - _debug2 "_ser" "$_ser" - - # RFC 9773 Section 4.1 requires the DER-encoded INTEGER value bytes of - # serialNumber. When the high bit of the first byte is set (>= 0x80) DER - # prepends a 0x00 sign byte to keep the integer positive; openssl's hex - # output strips that, so add it back. Boulder (LE) accepts either form, - # but Sectigo (ZeroSSL) is strict and rejects newOrder with HTTP 401 - # "replaces field does not identify a certificate" if the byte is missing. - case "$_ser" in - [89aAbBcCdDeEfF]*) _ser="00$_ser" ;; - esac - - _akiurl="$(echo "$_aki" | _h2b | _base64 | _url_replace)" - _debug2 "_akiurl" "$_akiurl" - _serurl="$(echo "$_ser" | _h2b | _base64 | _url_replace)" - _debug2 "_serurl" "$_serurl" - - printf "%s.%s" "$_akiurl" "$_serurl" -} - -#cert -_get_ARI() { - _cert="$1" - _ari_certID="$(_getARICertID "$_cert")" - _ARI_URL="$ACME_RENEWAL_INFO/$_ari_certID" - _get "$_ARI_URL" -} - # Detect profile file if not specified as environment variable _detect_profile() { if [ -n "$PROFILE" -a -f "$PROFILE" ]; then @@ -7568,7 +6360,6 @@ _initconf() { #NO_TIMESTAMP=1 " >"$ACCOUNT_CONF_PATH" - chmod 600 "$ACCOUNT_CONF_PATH" fi } @@ -7577,17 +6368,17 @@ _precheck() { _nocron="$1" if ! _exists "curl" && ! _exists "wget"; then - _err "Please install curl or wget first to enable access to HTTP resources." + _err "Please install curl or wget first, we need to access http resources." return 1 fi if [ -z "$_nocron" ]; then if ! _exists "crontab" && ! _exists "fcrontab"; then if _exists cygpath && _exists schtasks.exe; then - _info "It seems you are on Windows, we will install the Windows scheduler task." + _info "It seems you are on Windows, we will install Windows scheduler task." else - _err "It is recommended to install crontab first. Try to install 'cron', 'crontab', 'crontabs' or 'vixie-cron'." - _err "We need to set a cron job to renew the certs automatically." + _err "It is recommended to install crontab first. try to install 'cron, crontab, crontabs or vixie-cron'." + _err "We need to set cron job to renew the certs automatically." _err "Otherwise, your certs will not be able to be renewed automatically." if [ -z "$FORCE" ]; then _err "Please add '--force' and try install again to go without crontab." @@ -7604,10 +6395,10 @@ _precheck() { return 1 fi - if ! _exists "socat" && ! _exists "python" && ! _exists "python2" && ! _exists "python3"; then - _info "It is recommended to install socat or python first." - _info "We use socat or python for the standalone server, which is used for standalone mode." - _info "If you don't want to use standalone mode, you may ignore this warning." + if ! _exists "socat"; then + _err "It is recommended to install socat first." + _err "We use socat for standalone server if you use standalone mode." + _err "If you don't use standalone mode, just ignore this warning." fi return 0 @@ -7631,15 +6422,6 @@ _installalias() { _c_home="$1" _initpath - _alias_bin="$LE_WORKING_DIR/$PROJECT_ENTRY" - if [ ! -f "$_alias_bin" ]; then - #ACME_PACKAGED install: no copy in LE_WORKING_DIR, alias the current script - _script="$(_readlink "$_SCRIPT_")" - if [ -f "$_script" ]; then - _alias_bin="$_script" - fi - fi - _envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env" if [ "$_upgrading" ] && [ "$_upgrading" = "1" ]; then echo "$(cat "$_envfile")" | sed "s|^LE_WORKING_DIR.*$||" >"$_envfile" @@ -7657,20 +6439,16 @@ _installalias() { else _sed_i "/^export LE_CONFIG_HOME/d" "$_envfile" fi - _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$_alias_bin$_c_entry\"" - if [ -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" ]; then - #the completion file does nothing when sourced by a non-bash shell - _setopt "$_envfile" ". \"$LE_WORKING_DIR/$PROJECT_ENTRY.completion\"" - fi + _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _profile="$(_detect_profile)" if [ "$_profile" ]; then _debug "Found profile: $_profile" _info "Installing alias to '$_profile'" _setopt "$_profile" ". \"$_envfile\"" - _info "Close and reopen your terminal to start using $PROJECT_NAME" + _info "OK, Close and reopen your terminal to start using $PROJECT_NAME" else - _info "No profile has been found, you will need to change your working directory to $LE_WORKING_DIR to use $PROJECT_NAME" + _info "No profile is found, you will need to go into $LE_WORKING_DIR to use $PROJECT_NAME" fi #for csh @@ -7684,7 +6462,7 @@ _installalias() { else _sed_i "/^setenv LE_CONFIG_HOME/d" "$_cshfile" fi - _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$_alias_bin$_c_entry\"" + _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_csh_profile" "source \"$_cshfile\"" fi @@ -7696,7 +6474,7 @@ _installalias() { if [ "$_c_home" ]; then _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\"" fi - _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$_alias_bin$_c_entry\"" + _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_tcsh_profile" "source \"$_cshfile\"" fi @@ -7719,12 +6497,12 @@ install() { return 1 fi if [ "$_nocron" ]; then - _debug "Skipping cron job installation" + _debug "Skip install cron job" fi if [ "$_ACME_IN_CRON" != "1" ]; then if ! _precheck "$_nocron"; then - _err "Pre-check failed, cannot install." + _err "Pre-check failed, can not install." return 1 fi fi @@ -7754,7 +6532,7 @@ install() { if [ ! -d "$LE_WORKING_DIR" ]; then if ! mkdir -p "$LE_WORKING_DIR"; then - _err "Cannot create working dir: $LE_WORKING_DIR" + _err "Can not create working dir: $LE_WORKING_DIR" return 1 fi @@ -7763,45 +6541,32 @@ install() { if [ ! -d "$LE_CONFIG_HOME" ]; then if ! mkdir -p "$LE_CONFIG_HOME"; then - _err "Cannot create config dir: $LE_CONFIG_HOME" + _err "Can not create config dir: $LE_CONFIG_HOME" return 1 fi chmod 700 "$LE_CONFIG_HOME" fi - if [ "$ACME_PACKAGED" ]; then - #the script and its hooks are managed by a system package manager, - #do not copy them into LE_WORKING_DIR. https://github.com/acmesh-official/acme.sh/issues/7135 - _info "ACME_PACKAGED is set, skipping the script copy." - else - cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY" + cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY" - if [ "$?" != "0" ]; then - _err "Installation failed, cannot copy $PROJECT_ENTRY" - return 1 - fi - - _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" - - if [ -f "$PROJECT_ENTRY.completion" ]; then - cp "$PROJECT_ENTRY.completion" "$LE_WORKING_DIR/" - _debug "Installed bash completion to $LE_WORKING_DIR/$PROJECT_ENTRY.completion" - fi + if [ "$?" != "0" ]; then + _err "Install failed, can not copy $PROJECT_ENTRY" + return 1 fi + _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" + if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then _installalias "$_c_home" fi - if [ -z "$ACME_PACKAGED" ]; then - for subf in $_SUB_FOLDERS; do - if [ -d "$subf" ]; then - mkdir -p "$LE_WORKING_DIR/$subf" - cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/ - fi - done - fi + for subf in $_SUB_FOLDERS; do + if [ -d "$subf" ]; then + mkdir -p "$LE_WORKING_DIR/$subf" + cp "$subf"/* "$LE_WORKING_DIR"/"$subf"/ + fi + done if [ ! -f "$ACCOUNT_CONF_PATH" ]; then _initconf @@ -7813,12 +6578,6 @@ install() { if [ "$_DEFAULT_CERT_HOME" != "$CERT_HOME" ]; then _saveaccountconf "CERT_HOME" "$CERT_HOME" - # Create the custom cert home now instead of on first issuance, so the - # user can see --install honored it. - # https://github.com/acmesh-official/acme.sh/issues/4756 - if [ ! -d "$CERT_HOME" ]; then - mkdir -p "$CERT_HOME" - fi fi if [ "$_DEFAULT_ACCOUNT_KEY_PATH" != "$ACCOUNT_KEY_PATH" ]; then @@ -7829,7 +6588,7 @@ install() { installcronjob "$_c_home" fi - if [ -z "$NO_DETECT_SH" ] && [ -z "$ACME_PACKAGED" ]; then + if [ -z "$NO_DETECT_SH" ]; then #Modify shebang if _exists bash; then _bash_path="$(bash -c "command -v bash 2>/dev/null")" @@ -7838,7 +6597,7 @@ install() { fi fi if [ "$_bash_path" ]; then - _info "bash has been found. Changing the shebang to use bash as preferred." + _info "Good, bash is found, so change the shebang to use bash as preferred." _shebang='#!'"$_bash_path" _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang" for subf in $_SUB_FOLDERS; do @@ -7854,9 +6613,7 @@ install() { if [ "$_accountemail" ]; then _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail" fi - if [ -z "$ACME_PACKAGED" ]; then - _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)" - fi + _saveaccountconf "UPGRADE_HASH" "$(_getUpgradeHash)" _info OK } @@ -7870,13 +6627,8 @@ uninstall() { _uninstallalias - if [ -z "$ACME_PACKAGED" ]; then - #don't remove the script when it is managed by a system package manager, - #LE_WORKING_DIR may point to the packaged files - rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" - rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY.completion" - fi - _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\". You can remove them by yourself." + rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" + _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\", you can remove them by yourself." } @@ -7911,26 +6663,21 @@ cron() { _initpath _info "$(__green "===Starting cron===")" if [ "$AUTO_UPGRADE" = "1" ]; then - if [ "$ACME_PACKAGED" ]; then - _info "ACME_PACKAGED is set, skipping the auto upgrade." - else - export LE_WORKING_DIR - ( - if ! upgrade; then - _err "Cron: Upgrade failed!" - return 1 - fi - ) - . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null - - if [ -t 1 ]; then - __INTERACTIVE="1" + export LE_WORKING_DIR + ( + if ! upgrade; then + _err "Cron:Upgrade failed!" + return 1 fi + ) + . "$LE_WORKING_DIR/$PROJECT_ENTRY" >/dev/null - _info "Automatically upgraded to: $VER" + if [ -t 1 ]; then + __INTERACTIVE="1" fi + + _info "Auto upgraded to: $VER" fi - _TREAT_SKIP_AS_SUCCESS="1" renewAll _ret="$?" _ACME_IN_CRON="" @@ -7951,59 +6698,44 @@ _send_notify() { _nerror="$4" if [ "$NOTIFY_LEVEL" = "$NOTIFY_LEVEL_DISABLE" ]; then - _debug "The NOTIFY_LEVEL is $NOTIFY_LEVEL, which means it's disabled, so will just return." + _debug "The NOTIFY_LEVEL is $NOTIFY_LEVEL, disabled, just return." return 0 fi if [ -z "$_nhooks" ]; then - _debug "The NOTIFY_HOOK is empty, will just return." + _debug "The NOTIFY_HOOK is empty, just return." return 0 fi - _nsource="$NOTIFY_SOURCE" - if [ -z "$_nsource" ]; then - _nsource="$(uname -n)" - fi - - _nsubject="$_nsubject by $_nsource" - _send_err=0 for _n_hook in $(echo "$_nhooks" | tr ',' " "); do _n_hook_file="$(_findHook "" $_SUB_FOLDER_NOTIFY "$_n_hook")" _info "Sending via: $_n_hook" _debug "Found $_n_hook_file for $_n_hook" if [ -z "$_n_hook_file" ]; then - _err "Cannot find the hook file for $_n_hook" + _err "Can not find the hook file for $_n_hook" continue fi if ! ( - # The dns/deploy hooks export _H1.._H5 in the main process, so the - # values are inherited here. Clear them: a stale Authorization header - # from another service must not leak into the notify request. - export _H1="" - export _H2="" - export _H3="" - export _H4="" - export _H5="" if ! . "$_n_hook_file"; then - _err "Error loading file $_n_hook_file. Please check your API file and try again." + _err "Load file $_n_hook_file error. Please check your api file and try again." return 1 fi d_command="${_n_hook}_send" if ! _exists "$d_command"; then - _err "It seems that your API file is not correct. Make sure it has a function named: $d_command" + _err "It seems that your api file is not correct, it must have a function named: $d_command" return 1 fi if ! $d_command "$_nsubject" "$_ncontent" "$_nerror"; then - _err "Error sending message using $d_command" + _err "Error send message by $d_command" return 1 fi return 0 ); then - _err "Error setting $_n_hook_file." + _err "Set $_n_hook_file error." _send_err=1 else _info "$_n_hook $(__green Success)" @@ -8029,12 +6761,11 @@ setnotify() { _nhook="$1" _nlevel="$2" _nmode="$3" - _nsource="$4" _initpath - if [ -z "$_nhook$_nlevel$_nmode$_nsource" ]; then - _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook ] [--notify-level <0|1|2|3>] [--notify-mode <0|1>] [--notify-source ]" + if [ -z "$_nhook$_nlevel$_nmode" ]; then + _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook ] [--notify-level <0|1|2|3>] [--notify-mode <0|1>]" _usage "$_NOTIFY_WIKI" return 1 fi @@ -8051,16 +6782,10 @@ setnotify() { _saveaccountconf "NOTIFY_MODE" "$NOTIFY_MODE" fi - if [ "$_nsource" ]; then - _info "Set notify source to: $_nsource" - export "NOTIFY_SOURCE=$_nsource" - _saveaccountconf "NOTIFY_SOURCE" "$NOTIFY_SOURCE" - fi - if [ "$_nhook" ]; then _info "Set notify hook to: $_nhook" if [ "$_nhook" = "$NO_VALUE" ]; then - _info "Clearing notify hook" + _info "Clear notify hook" _clearaccountconf "NOTIFY_HOOK" else if _set_notify_hook "$_nhook"; then @@ -8068,7 +6793,7 @@ setnotify() { _saveaccountconf "NOTIFY_HOOK" "$NOTIFY_HOOK" return 0 else - _err "Cannot set notify hook to: $_nhook" + _err "Can not set notify hook to: $_nhook" return 1 fi fi @@ -8088,7 +6813,7 @@ Commands: --upgrade Upgrade $PROJECT_NAME to the latest code from $PROJECT. --issue Issue a cert. --deploy Deploy the cert to your server. - -i, --install-cert Install the issued cert to Apache/nginx or any other server. + -i, --install-cert Install the issued cert to apache/nginx or any other server. -r, --renew Renew a cert. --renew-all Renew all the certs. --revoke Revoke a cert. @@ -8102,11 +6827,8 @@ Commands: -ccr, --create-csr Create CSR, professional use. --create-domain-key Create an domain private key, professional use. --update-account Update account info. - --update-account-key Rotate account key. --register-account Register account key. --deactivate-account Deactivate the account. - --make-dns-persist-value Print the DNS TXT record(s) to enable persistent DNS validation - (draft-ietf-acme-dns-persist-01). Use with -d . --create-account-key Create an account private key, professional use. --install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. --uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically. @@ -8131,9 +6853,6 @@ Parameters: If no match, the default offered chain will be used. (default: empty) See: $_PREFERRED_CHAIN_WIKI - --cert-profile, --certificate-profile If the CA offers profiles, select the desired profile - See: $_PROFILESELECTION_WIKI - --valid-to Request the NotAfter field of the cert. See: $_VALIDITY_WIKI --valid-from Request the NotBefore field of the cert. @@ -8141,7 +6860,7 @@ Parameters: -f, --force Force install, force cert renewal or override sudo restrictions. --staging, --test Use staging server, for testing. - --debug [0|1|2|3] Output debug info. Defaults to $DEBUG_LEVEL_DEFAULT if argument is omitted. + --debug [0|1|2|3] Output debug info. Defaults to 1 if argument is omitted. --output-insecure Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for security. -w, --webroot Specifies the web root folder for web root mode. @@ -8150,42 +6869,24 @@ Parameters: --stateless Use stateless mode. See: $_STATELESS_WIKI - --apache Use Apache mode. + --apache Use apache mode. --dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted. See: $_DNS_API_WIKI - --dns-persist Use dns-persist-01 validation (draft-ietf-acme-dns-persist-01). - Requires the persistent _validation-persist TXT record to already - exist. Use '--make-dns-persist-value' to print the value to add. - --dnssleep The time in seconds to wait for all the txt records to propagate in dns api mode. It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically. - -k, --keylength Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256 (default), ec-384, ec-521. - -ak, --accountkeylength Specifies the account key length: 2048, 3072, 4096, 8192 or ec-256 (default), ec-384, ec-521. + -k, --keylength Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521. + -ak, --accountkeylength Specifies the account key length: 2048, 3072, 4096 --log [file] Specifies the log file. Defaults to \"$DEFAULT_LOG_FILE\" if argument is omitted. - --log-level <1|2> Specifies the log level, default is $DEFAULT_LOG_LEVEL. + --log-level <1|2> Specifies the log level, default is 1. --syslog <0|3|6|7> Syslog level, 0: disable syslog, 3: error, 6: info, 7: debug. --eab-kid Key Identifier for External Account Binding. --eab-hmac-key HMAC key for External Account Binding. - --dns-persist-wildcard Used with '--make-dns-persist-value'. Adds 'policy=wildcard' to the - generated TXT record so the issuer is also authorized for wildcards - and subdomains (draft-ietf-acme-dns-persist-01). It is implied when - the domain given to -d is a wildcard (e.g. '*.example.com'); the - record itself is always published at the base domain. - --dns-persist-ca-name Used with '--make-dns-persist-value'. Use the given CA identity domain - (e.g. 'ssl.com') as the issuer-domain-name in the TXT record. If - omitted, the identities are read from the ACME directory's - 'caaIdentities' field and one record is printed per identity. - --dns-persist-days Used with '--make-dns-persist-value'. Add a 'persistUntil' field to - the TXT record so the record self-expires N days from now (the CA - will refuse new validations against the record after that time). - If omitted, the record has no expiry. + These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert: - These parameters are to install the cert to nginx/Apache or any other server after issue/renew a cert: - - --cert-file Path to copy the cert file to after issue/renew. + --cert-file Path to copy the cert file to after issue/renew.. --key-file Path to copy the key file to after issue/renew. --ca-file Path to copy the intermediate cert file to after issue/renew. --fullchain-file Path to copy the fullchain cert file to after issue/renew. @@ -8196,21 +6897,17 @@ Parameters: --accountconf Specifies a customized account config file. --home Specifies the home dir for $PROJECT_NAME. - --cert-home Specifies the home dir to save all the certs. + --cert-home Specifies the home dir to save all the certs, only valid for '--install' command. --config-home Specifies the home dir to save all the configurations. --useragent Specifies the user agent string. it will be saved for future use too. -m, --email Specifies the account email, only valid for the '--install' and '--update-account' command. - Multiple emails can be given as a comma-separated list: 'a@example.com,b@example.com' --accountkey Specifies the account key path, only valid for the '--install' command. --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. - A negative value renews that many days before the cert expiry. - Negative values could be used to specify a number of days relative to the expiration date of the certificate. --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses. --listraw Only used for '--list' command, list the certs in raw format. -se, --stop-renew-on-error Only valid for '--renew-all' command. Stop if one cert has error in renewal. - --treat-skip-as-success Only valid for '--renew-all' command. Treat skipped certs as success, return 0 instead of $RENEW_SKIP. --insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted. --ca-bundle Specifies the path to the CA certificate bundle to verify api server's certificate. --ca-path Specifies directory containing CA certificates in PEM format, used by wget or curl. @@ -8219,22 +6916,17 @@ Parameters: --no-profile Only valid for '--install' command, which means: do not install aliases to user profile. --no-color Do not output color text. --force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails. - --ecc Specifies use of the ECC cert. Only valid for '--install-cert', '--renew', '--remove ', '--revoke', - '--deploy', '--to-pkcs8', '--to-pkcs12' and '--create-csr'. + --ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--to-pkcs12' and '--create-csr' --csr Specifies the input csr. --pre-hook Command to be run before obtaining any certificates. --post-hook Command to be run after attempting to obtain/renew certificates. Runs regardless of whether obtain/renew succeeded or failed. --renew-hook Command to be run after each successfully renewed certificate. --deploy-hook The hook file to deploy cert - --extended-key-usage Manually define the CSR extended key usage value. The default is serverAuth,clientAuth. --ocsp, --ocsp-must-staple Generate OCSP-Must-Staple extension. --always-force-new-domain-key Generate new domain key on renewal. Otherwise, the domain key is not changed by default. --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted. - --listen-v4 Force standalone/tls server to listen at ipv4 only. - By default the standalone server listens on both ipv4 and ipv6. - --listen-v6 Force standalone/tls server to listen at ipv6 only. - --request-v4 Force client requests to use ipv4 to connect to the CA server. - --request-v6 Force client requests to use ipv6 to connect to the CA server. + --listen-v4 Force standalone/tls server to listen at ipv4. + --listen-v6 Force standalone/tls server to listen at ipv6. --openssl-bin Specifies a custom openssl bin location. --use-wget Force to use wget, if you have both curl and wget installed. --yes-I-know-dns-manual-mode-enough-go-ahead-please Force use of dns manual mode. @@ -8250,21 +6942,16 @@ Parameters: 0: Bulk mode. Send all the domain's notifications in one message(mail). 1: Cert mode. Send a message for every single cert. --notify-hook Set the notify hook - --notify-source Set the server name in the notification message --revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command. See: $_REVOKE_WIKI - --password Add a password to the exported pfx or pkcs8 file. Use with '--to-pkcs12' or '--to-pkcs8'. + --password Add a password to exported pfx file. Use with --to-pkcs12. " } installOnline() { - if [ "$ACME_PACKAGED" ]; then - _err "ACME_PACKAGED is set: acme.sh is managed by the system package manager, please use it to upgrade." - return 1 - fi _info "Installing from online archive." _branch="$BRANCH" @@ -8288,9 +6975,7 @@ installOnline() { cd "$PROJECT_NAME-$_branch" chmod +x $PROJECT_ENTRY - ./$PROJECT_ENTRY --install "$@" - _install_rc="$?" - if [ "$_install_rc" = "0" ]; then + if ./$PROJECT_ENTRY --install "$@"; then _info "Install success!" fi @@ -8298,17 +6983,14 @@ installOnline() { rm -rf "$PROJECT_NAME-$_branch" rm -f "$localname" - # Propagate the install result so a failed upgrade is not reported as - # success. https://github.com/acmesh-official/acme.sh/issues/6477 - exit "$_install_rc" ) } _getRepoHash() { _hash_path=$1 shift - _hash_url="${PROJECT_API:-https://api.github.com/repos/acmesh-official}/$PROJECT_NAME/git/refs/$_hash_path" - _get "$_hash_url" "" 30 | tr -d "\r\n" | tr '{},' '\n\n\n' | grep '"sha":' | cut -d '"' -f 4 + _hash_url="https://api.github.com/repos/acmesh-official/$PROJECT_NAME/git/refs/$_hash_path" + _get $_hash_url | tr -d "\r\n" | tr '{},' '\n\n\n' | grep '"sha":' | cut -d '"' -f 4 } _getUpgradeHash() { @@ -8322,18 +7004,14 @@ _getUpgradeHash() { } upgrade() { - if [ "$ACME_PACKAGED" ]; then - _err "ACME_PACKAGED is set: acme.sh is managed by the system package manager, please use it to upgrade." - exit 1 - fi if ( _initpath - [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already up to date!" && exit 0 + [ -z "$FORCE" ] && [ "$(_getUpgradeHash)" = "$(_readaccountconf "UPGRADE_HASH")" ] && _info "Already uptodate!" && exit 0 export LE_WORKING_DIR cd "$LE_WORKING_DIR" installOnline "--nocron" "--noprofile" ); then - _info "Upgrade successful!" + _info "Upgrade success!" exit 0 else _err "Upgrade failed!" @@ -8366,28 +7044,10 @@ _processAccountConf() { _saveaccountconf "ACME_USE_WGET" "$ACME_USE_WGET" fi - if [ "$_request_v6" ]; then - _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$_request_v6" - _clearaccountconf "ACME_USE_IPV4_REQUESTS" - ACME_USE_IPV4_REQUESTS= - elif [ "$_request_v4" ]; then - _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$_request_v4" - _clearaccountconf "ACME_USE_IPV6_REQUESTS" - ACME_USE_IPV6_REQUESTS= - elif [ "$ACME_USE_IPV6_REQUESTS" ]; then - _saveaccountconf "ACME_USE_IPV6_REQUESTS" "$ACME_USE_IPV6_REQUESTS" - _clearaccountconf "ACME_USE_IPV4_REQUESTS" - ACME_USE_IPV4_REQUESTS= - elif [ "$ACME_USE_IPV4_REQUESTS" ]; then - _saveaccountconf "ACME_USE_IPV4_REQUESTS" "$ACME_USE_IPV4_REQUESTS" - _clearaccountconf "ACME_USE_IPV6_REQUESTS" - ACME_USE_IPV6_REQUESTS= - fi - } _checkSudo() { - if [ -z "$__INTERACTIVE" ]; then + if [ -z "__INTERACTIVE" ]; then #don't check if it's not in an interactive shell return 0 fi @@ -8397,16 +7057,9 @@ _checkSudo() { return 0 fi if [ -n "$SUDO_COMMAND" ]; then - #The SUDO_* env vars are often inherited into shells that were not - #started as `sudo acme.sh` at all (e.g. `sudo su - user`, or - #`sudo pct enter ` on Proxmox, which copies them into the - #container). Only warn when sudo was used to run acme.sh itself; - #anything else means the sudo happened further up and is fine. - #https://github.com/acmesh-official/acme.sh/issues/6400 - if _contains "$SUDO_COMMAND" "$PROJECT_ENTRY"; then - return 1 - fi - return 0 + #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s`, or `sudo su acmeuser1` + _endswith "$SUDO_COMMAND" /bin/su || _contains "$SUDO_COMMAND" "/bin/su " || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1 + return $? fi #otherwise return 1 @@ -8446,9 +7099,7 @@ _selectServer() { _getCAShortName() { caurl="$1" if [ -z "$caurl" ]; then - #use letsencrypt as default value if the Le_API is empty - #this case can only come from the old upgrading. - caurl="$CA_LETSENCRYPT_V2" + caurl="$DEFAULT_CA" fi if [ "$CA_SSLCOM_ECC" = "$caurl" ]; then caurl="$CA_SSLCOM_RSA" #just hack to get the short name @@ -8474,7 +7125,7 @@ _getCAShortName() { #set default ca to $ACME_DIRECTORY setdefaultca() { if [ -z "$ACME_DIRECTORY" ]; then - _err "Please provide a --server parameter." + _err "Please give a --server parameter." return 1 fi _saveaccountconf "DEFAULT_ACME_SERVER" "$ACME_DIRECTORY" @@ -8486,7 +7137,7 @@ setdefaultchain() { _initpath _preferred_chain="$1" if [ -z "$_preferred_chain" ]; then - _err "Please provide a value for '--preferred-chain'." + _err "Please give a '--preferred-chain value' value." return 1 fi mkdir -p "$CA_DIR" @@ -8556,8 +7207,6 @@ _process() { _local_address="" _log_level="" _auto_upgrade="" - _request_v4="" - _request_v6="" _listen_v4="" _listen_v6="" _openssl_bin="" @@ -8567,18 +7216,12 @@ _process() { _notify_hook="" _notify_level="" _notify_mode="" - _notify_source="" _revoke_reason="" _eab_kid="" _eab_hmac_key="" _preferred_chain="" _valid_from="" _valid_to="" - _certificate_profile="" - _extended_key_usage="" - _dns_persist_wildcard="" - _dns_persist_ca_name="" - _dns_persist_days="" while [ ${#} -gt 0 ]; do case "${1}" in @@ -8667,29 +7310,12 @@ _process() { --update-account | --updateaccount) _CMD="updateaccount" ;; - --update-account-key | --updateaccountkey) - _CMD="updateaccountkey" - ;; --register-account | --registeraccount) _CMD="registeraccount" ;; --deactivate-account) _CMD="deactivateaccount" ;; - --make-dns-persist-value | --makednspersistvalue) - _CMD="makednspersistvalue" - ;; - --dns-persist-wildcard | --dnspersistwildcard) - _dns_persist_wildcard="1" - ;; - --dns-persist-ca-name | --dnspersistcaname) - _dns_persist_ca_name="$2" - shift - ;; - --dns-persist-days | --dnspersistdays) - _dns_persist_days="$2" - shift - ;; --set-notify) _CMD="setnotify" ;; @@ -8699,9 +7325,6 @@ _process() { --set-default-chain) _CMD="setdefaultchain" ;; - --list-profiles) - _CMD="list_profiles" - ;; -d | --domain) _dvalue="$2" @@ -8711,7 +7334,7 @@ _process() { return 1 fi if _is_idn "$_dvalue" && ! _exists idn; then - _err "It seems that $_dvalue is an IDN (Internationalized Domain Names), please install the 'idn' command first." + _err "It seems that $_dvalue is an IDN( Internationalized Domain Names), please install 'idn' command first." return 1 fi @@ -8732,9 +7355,6 @@ _process() { -f | --force) FORCE="1" ;; - --treat-skip-as-success | --treatskipassuccess) - _TREAT_SKIP_AS_SUCCESS="1" - ;; --staging | --test) STAGE="1" ;; @@ -8833,14 +7453,6 @@ _process() { _webroot="$_webroot,$wvalue" fi ;; - --dns-persist) - wvalue="$W_DNS_PERSIST" - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; --dnssleep) _dnssleep="$2" Le_DNSSleep="$_dnssleep" @@ -8849,9 +7461,6 @@ _process() { --keylength | -k) _keylength="$2" shift - if [ "$_keylength" ] && ! _isEccKey "$_keylength"; then - export __SELECTED_RSA_KEY=1 - fi ;; -ak | --accountkeylength) _accountkeylength="$2" @@ -8887,7 +7496,7 @@ _process() { shift ;; --home) - export LE_WORKING_DIR="$(echo "$2" | sed 's|/$||')" + export LE_WORKING_DIR="$2" shift ;; --cert-home | --certhome) @@ -8928,10 +7537,6 @@ _process() { _valid_to="$2" shift ;; - --certificate-profile | --cert-profile) - _certificate_profile="$2" - shift - ;; --httpport) _httpport="$2" Le_HTTPPort="$_httpport" @@ -9001,10 +7606,6 @@ _process() { _deploy_hook="$_deploy_hook$2," shift ;; - --extended-key-usage) - _extended_key_usage="$2" - shift - ;; --ocsp-must-staple | --ocsp) Le_OCSP_Staple="1" ;; @@ -9055,18 +7656,6 @@ _process() { fi AUTO_UPGRADE="$_auto_upgrade" ;; - --request-v4) - _request_v4="1" - ACME_USE_IPV4_REQUESTS="1" - _request_v6="" - ACME_USE_IPV6_REQUESTS="" - ;; - --request-v6) - _request_v6="1" - ACME_USE_IPV6_REQUESTS="1" - _request_v4="" - ACME_USE_IPV4_REQUESTS="" - ;; --listen-v4) _listen_v4="1" Le_Listen_V4="$_listen_v4" @@ -9104,7 +7693,7 @@ _process() { --notify-level) _nlevel="$2" if _startswith "$_nlevel" "-"; then - _err "'$_nlevel' is not an integer for '$1'" + _err "'$_nlevel' is not a integer for '$1'" return 1 fi _notify_level="$_nlevel" @@ -9113,25 +7702,16 @@ _process() { --notify-mode) _nmode="$2" if _startswith "$_nmode" "-"; then - _err "'$_nmode' is not an integer for '$1'" + _err "'$_nmode' is not a integer for '$1'" return 1 fi _notify_mode="$_nmode" shift ;; - --notify-source) - _nsource="$2" - if _startswith "$_nsource" "-"; then - _err "'$_nsource' is not a valid host name for '$1'" - return 1 - fi - _notify_source="$_nsource" - shift - ;; --revoke-reason) _revoke_reason="$2" if _startswith "$_revoke_reason" "-"; then - _err "'$_revoke_reason' is not an integer for '$1'" + _err "'$_revoke_reason' is not a integer for '$1'" return 1 fi shift @@ -9149,7 +7729,7 @@ _process() { shift ;; *) - _err "Unknown parameter: $1" + _err "Unknown parameter : $1" return 1 ;; esac @@ -9166,7 +7746,7 @@ _process() { if [ "$__INTERACTIVE" ] && ! _checkSudo; then if [ -z "$FORCE" ]; then #Use "echo" here, instead of _info. it's too early - echo "It seems that you are using sudo, please read this page first:" + echo "It seems that you are using sudo, please read this link first:" echo "$_SUDO_WIKI" return 1 fi @@ -9196,7 +7776,7 @@ _process() { fi SYS_LOG="$_syslog" else - _err "The 'logger' command was not found, cannot enable syslog." + _err "The 'logger' command is not found, can not enable syslog." _clearaccountconf "SYS_LOG" SYS_LOG="" fi @@ -9207,22 +7787,6 @@ _process() { _debug2 LE_WORKING_DIR "$LE_WORKING_DIR" - # --valid-to pins the cert lifetime, so a creation-anchored (positive) - # --days schedule can not apply and is rejected. A negative --days is - # anchored to the expiry and composes with a relative --valid-to: the - # cert renews that many days before the expiry. - if [ "$_days" ] && [ "$_valid_to" ]; then - if ! _startswith "$_valid_to" "+"; then - _err "--days can not be used together with a fixed-date --valid-to: such a cert can not be renewed automatically." - return 1 - fi - if ! _startswith "$_days" "-"; then - _err "A positive --days can not be used together with --valid-to, the renewal time is derived from the expiry time." - _err "Use a negative --days to renew that many days before the expiry, or omit --days to renew 1 day before the expiry." - return 1 - fi - fi - if [ "$DEBUG" ]; then version if [ "$_server" ]; then @@ -9235,13 +7799,13 @@ _process() { uninstall) uninstall "$_nocron" ;; upgrade) upgrade ;; issue) - issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" "$_valid_from" "$_valid_to" "$_certificate_profile" "$_extended_key_usage" + issue "$_webroot" "$_domain" "$_altdomains" "$_keylength" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" "$_valid_from" "$_valid_to" ;; deploy) deploy "$_domain" "$_deploy_hook" "$_ecc" ;; signcsr) - signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" "$_valid_from" "$_valid_to" "$_certificate_profile" "$_extended_key_usage" + signcsr "$_csr" "$_webroot" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_pre_hook" "$_post_hook" "$_renew_hook" "$_local_address" "$_challenge_alias" "$_preferred_chain" ;; showcsr) showcsr "$_csr" "$_domain" @@ -9270,15 +7834,9 @@ _process() { updateaccount) updateaccount ;; - updateaccountkey) - updateaccountkey "$_accountkeylength" - ;; deactivateaccount) deactivateaccount ;; - makednspersistvalue) - makednspersistvalue "$_domain" "$_dns_persist_wildcard" "$_dns_persist_ca_name" "$_dns_persist_days" - ;; list) list "$_listraw" "$_domain" ;; @@ -9292,7 +7850,7 @@ _process() { toPkcs "$_domain" "$_password" "$_ecc" ;; toPkcs8) - toPkcs8 "$_domain" "$_password" "$_ecc" + toPkcs8 "$_domain" "$_ecc" ;; createAccountKey) createAccountKey "$_accountkeylength" @@ -9301,10 +7859,10 @@ _process() { createDomainKey "$_domain" "$_keylength" ;; createCSR) - createCSR "$_domain" "$_altdomains" "$_ecc" "$_extended_key_usage" + createCSR "$_domain" "$_altdomains" "$_ecc" ;; setnotify) - setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" "$_notify_source" + setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" ;; setdefaultca) setdefaultca @@ -9312,9 +7870,6 @@ _process() { setdefaultchain) setdefaultchain "$_preferred_chain" ;; - list_profiles) - list_profiles - ;; *) if [ "$_CMD" ]; then _err "Invalid command: $_CMD" @@ -9348,7 +7903,7 @@ _process() { _saveaccountconf "SYS_LOG" "$_syslog" fi else - _err "The 'logger' command was not found, cannot enable syslog." + _err "The 'logger' command is not found, can not enable syslog." _clearaccountconf "SYS_LOG" SYS_LOG="" fi diff --git a/acme.sh.completion b/acme.sh.completion deleted file mode 100644 index 26cb88da..00000000 --- a/acme.sh.completion +++ /dev/null @@ -1,341 +0,0 @@ -# Bash completion for acme.sh: https://github.com/acmesh-official/acme.sh -# -# "acme.sh --install" copies this file to the acme.sh home dir and wires -# it into acme.sh.env, so the completion is loaded automatically in new -# bash sessions after installation. -# -# To use it without installing acme.sh, source it from ~/.bashrc, or copy -# it to /usr/share/bash-completion/completions/acme.sh -# -# Zsh users can load it with: -# autoload -U +X bashcompinit && bashcompinit -# . /path/to/acme.sh.completion - -# This file may also be sourced by non-bash shells via acme.sh.env, -# so silently do nothing if the "complete" builtin is not available. -if ! command -v complete >/dev/null 2>&1; then - return 0 2>/dev/null || exit 0 -fi - -# Add each word of $1 that starts with $cur to COMPREPLY. -# The words are read line by line, so that candidates like a wildcard -# domain "*.example.com" are never glob-expanded against the cwd. -_acme_sh_add_matches() { - local _word - while read -r _word; do - [ -n "$_word" ] || continue - case "$_word" in - "$cur"*) COMPREPLY=("${COMPREPLY[@]}" "$_word") ;; - esac - done </dev/null 2>&1; then - compopt -o filenames 2>/dev/null - fi - return 0 -} - -_acme_sh_dirs() { - local _dir - while IFS= read -r _dir; do - [ -n "$_dir" ] || continue - COMPREPLY=("${COMPREPLY[@]}" "$_dir") - done </dev/null 2>&1; then - compopt -o filenames 2>/dev/null - fi - return 0 -} - -# Complete the domains that already have a cert: every directory in the -# config home that contains a ".conf" file ("_ecc" suffix stripped). -_acme_sh_domains() { - local _dir _name _domains="" - [ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null - for _dir in "$_acme_conf_home"/*/; do - [ -d "$_dir" ] || continue - _name="${_dir%/}" - _name="${_name##*/}" - _name="${_name%_ecc}" - if [ -f "${_dir}${_name}.conf" ]; then - case " $_domains " in - *" $_name "*) ;; - *) _domains="$_domains $_name" ;; - esac - fi - done - _acme_sh_add_matches "$_domains" -} - -# Complete hook names from a subfolder of the acme.sh home dir. -# $1: subfolder (dnsapi/deploy/notify), $2: file name prefix or empty. -_acme_sh_hooks() { - local _file _hooks="" - [ -n "${ZSH_VERSION:-}" ] && setopt localoptions nonomatch 2>/dev/null - for _file in "$_acme_home/$1/$2"*.sh; do - [ -f "$_file" ] || continue - _file="${_file##*/}" - _hooks="$_hooks ${_file%.sh}" - done - _acme_sh_add_matches "$_hooks" -} - -_acme_sh_completion() { - local cur prev _acme_home _acme_conf_home - COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="" - if [ "$COMP_CWORD" -gt 0 ]; then - prev="${COMP_WORDS[COMP_CWORD - 1]}" - fi - _acme_home="${LE_WORKING_DIR:-$HOME/.acme.sh}" - _acme_conf_home="${LE_CONFIG_HOME:-$_acme_home}" - - # The first argument is the command. - if [ "$COMP_CWORD" -eq 1 ]; then - _acme_sh_add_matches " - --help - --version - --install - --install-online - --uninstall - --upgrade - --issue - --deploy - --sign-csr - --show-csr - --install-cert - --renew - --renew-all - --revoke - --remove - --list - --list-profiles - --info - --to-pkcs12 - --to-pkcs8 - --create-account-key - --create-domain-key - --create-csr - --deactivate - --update-account - --register-account - --deactivate-account - --make-dns-persist-value - --install-cronjob - --uninstall-cronjob - --cron - --set-notify - --set-default-ca - --set-default-chain - " - return 0 - fi - - # Complete the value of the previous option. - case "$prev" in - -d | --domain | --challenge-alias | --domain-alias) - _acme_sh_domains - return 0 - ;; - --dns) - # The dns hook argument is optional, keep completing options if the - # current word already looks like one. - case "$cur" in - -*) ;; - *) - _acme_sh_hooks "dnsapi" "dns_" - return 0 - ;; - esac - ;; - --deploy-hook) - _acme_sh_hooks "deploy" "" - return 0 - ;; - --notify-hook) - _acme_sh_hooks "notify" "" - return 0 - ;; - --server) - _acme_sh_add_matches "letsencrypt letsencrypt_test zerossl sslcom google google_test actalis" - return 0 - ;; - -k | --keylength | -ak | --accountkeylength) - _acme_sh_add_matches "2048 3072 4096 8192 ec-256 ec-384 ec-521" - return 0 - ;; - --debug) - # Optional argument. - case "$cur" in - -*) ;; - *) - _acme_sh_add_matches "0 1 2 3" - return 0 - ;; - esac - ;; - --log) - # Optional argument. - case "$cur" in - -*) ;; - *) - _acme_sh_files - return 0 - ;; - esac - ;; - --nginx) - # Optional argument. - case "$cur" in - -*) ;; - *) - _acme_sh_files - return 0 - ;; - esac - ;; - --auto-upgrade | --always-force-new-domain-key) - # Optional argument. - case "$cur" in - -*) ;; - *) - _acme_sh_add_matches "0 1" - return 0 - ;; - esac - ;; - --log-level) - _acme_sh_add_matches "1 2" - return 0 - ;; - --syslog) - _acme_sh_add_matches "0 3 6 7" - return 0 - ;; - --notify-level) - _acme_sh_add_matches "0 1 2 3" - return 0 - ;; - --notify-mode) - _acme_sh_add_matches "0 1" - return 0 - ;; - --revoke-reason) - _acme_sh_add_matches "0 1 2 3 4 5 6 7 8 9 10" - return 0 - ;; - --cert-file | --key-file | --ca-file | --fullchain-file | --csr | --accountconf | --accountkey | --ca-bundle | --openssl-bin) - _acme_sh_files - return 0 - ;; - -w | --webroot | --home | --cert-home | --config-home | --ca-path) - _acme_sh_dirs - return 0 - ;; - -m | --email | --password | --useragent | --days | --valid-from | --valid-to | --httpport | --tlsport | --local-address | --dnssleep | --pre-hook | --post-hook | --renew-hook | --reloadcmd | --extended-key-usage | -b | --branch | --notify-source | --eab-kid | --eab-hmac-key | --preferred-chain | --cert-profile | --certificate-profile | --dns-persist-ca-name | --dns-persist-days) - # These options take a free-form value, offer nothing. - return 0 - ;; - esac - - # Complete the parameters. - _acme_sh_add_matches " - --accountconf - --accountkey - --accountkeylength - --alpn - --always-force-new-domain-key - --apache - --auto-upgrade - --branch - --ca-bundle - --ca-file - --ca-path - --cert-file - --cert-home - --cert-profile - --challenge-alias - --config-home - --csr - --days - --debug - --deploy-hook - --dns - --dns-persist - --dns-persist-ca-name - --dns-persist-days - --dns-persist-wildcard - --dnssleep - --domain - --domain-alias - --eab-hmac-key - --eab-kid - --ecc - --email - --extended-key-usage - --force - --force-color - --fullchain-file - --home - --httpport - --insecure - --key-file - --keylength - --listen-v4 - --listen-v6 - --listraw - --local-address - --log - --log-level - --nginx - --no-color - --no-cron - --no-profile - --notify-hook - --notify-level - --notify-mode - --notify-source - --ocsp-must-staple - --openssl-bin - --output-insecure - --password - --post-hook - --pre-hook - --preferred-chain - --reloadcmd - --renew-hook - --revoke-reason - --server - --staging - --standalone - --stateless - --stop-renew-on-error - --syslog - --tlsport - --treat-skip-as-success - --use-wget - --useragent - --valid-from - --valid-to - --webroot - --yes-I-know-dns-manual-mode-enough-go-ahead-please - " - return 0 -} - -complete -F _acme_sh_completion acme.sh diff --git a/deploy/ali_cdn.sh b/deploy/ali_cdn.sh deleted file mode 100644 index 3c28674e..00000000 --- a/deploy/ali_cdn.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034,SC2154 - -# Script to create certificate to Alibaba Cloud CDN -# -# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun -# -# This deployment required following variables -# export Ali_Key="ALIACCESSKEY" -# export Ali_Secret="ALISECRETKEY" -# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi -# -# To specify the CDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates -# export DEPLOY_ALI_CDN_DOMAIN="cdn.example.com" -# If you have multiple CDN domains using the same certificate, just -# export DEPLOY_ALI_CDN_DOMAIN="cdn1.example.com cdn2.example.com" -# -# For DCDN, see ali_dcdn deploy hook - -Ali_CDN_API="https://cdn.aliyuncs.com/" - -ali_cdn_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - # Load dnsapi/dns_ali.sh to reduce the duplicated codes - # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 - dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)" - # shellcheck source=/dev/null - if ! . "$dnsapi_ali"; then - _err "Error loading file $dnsapi_ali. Please check your API file and try again." - return 1 - fi - - _prepare_ali_credentials || return 1 - - _getdeployconf DEPLOY_ALI_CDN_DOMAIN - if [ "$DEPLOY_ALI_CDN_DOMAIN" ]; then - _savedeployconf DEPLOY_ALI_CDN_DOMAIN "$DEPLOY_ALI_CDN_DOMAIN" - else - DEPLOY_ALI_CDN_DOMAIN="$_cdomain" - fi - - # read cert and key files and urlencode both - _cert=$(_url_encode upper-hex <"$_cfullchain") - _key=$(_url_encode upper-hex <"$_ckey") - - _debug2 _cert "$_cert" - _debug2 _key "$_key" - - ## update domain ssl config - for domain in $DEPLOY_ALI_CDN_DOMAIN; do - _set_cdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key" - if _ali_rest "Set CDN domain SSL certificate for $domain" "" POST; then - _info "Domain $domain certificate has been deployed successfully" - fi - done - - return 0 -} - -# domain pub pri -_set_cdn_domain_ssl_certificate_query() { - endpoint=$Ali_CDN_API - query='' - query=$query'AccessKeyId='$Ali_Key - query=$query'&Action=SetCdnDomainSSLCertificate' - query=$query'&CertType=upload' - query=$query'&DomainName='$1 - query=$query'&Format=json' - query=$query'&SSLPri='$3 - query=$query'&SSLProtocol=on' - query=$query'&SSLPub='$2 - query=$query'&SignatureMethod=HMAC-SHA1' - query=$query"&SignatureNonce=$(_ali_nonce)" - query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_ali_timestamp) - query=$query'&Version=2018-05-10' -} diff --git a/deploy/ali_dcdn.sh b/deploy/ali_dcdn.sh deleted file mode 100644 index 27d3a726..00000000 --- a/deploy/ali_dcdn.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034,SC2154 - -# Script to create certificate to Alibaba Cloud DCDN -# -# Docs: https://github.com/acmesh-official/acme.sh/wiki/deployhooks#33-deploy-your-certificate-to-cdn-or-dcdn-of-alibaba-cloud-aliyun -# -# This deployment required following variables -# export Ali_Key="ALIACCESSKEY" -# export Ali_Secret="ALISECRETKEY" -# The credentials are shared with all the Alibaba Cloud deploy hooks and dnsapi -# -# To specify the DCDN domain that is different from the certificate CN, usually used for multi-domain or wildcard certificates -# export DEPLOY_ALI_DCDN_DOMAIN="dcdn.example.com" -# If you have multiple CDN domains using the same certificate, just -# export DEPLOY_ALI_DCDN_DOMAIN="dcdn1.example.com dcdn2.example.com" -# -# For regular CDN, see ali_cdn deploy hook - -Ali_DCDN_API="https://dcdn.aliyuncs.com/" - -ali_dcdn_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - # Load dnsapi/dns_ali.sh to reduce the duplicated codes - # https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 - dnsapi_ali="$(_findHook "$_cdomain" "$_SUB_FOLDER_DNSAPI" dns_ali)" - # shellcheck source=/dev/null - if ! . "$dnsapi_ali"; then - _err "Error loading file $dnsapi_ali. Please check your API file and try again." - return 1 - fi - - _prepare_ali_credentials || return 1 - - _getdeployconf DEPLOY_ALI_DCDN_DOMAIN - if [ "$DEPLOY_ALI_DCDN_DOMAIN" ]; then - _savedeployconf DEPLOY_ALI_DCDN_DOMAIN "$DEPLOY_ALI_DCDN_DOMAIN" - else - DEPLOY_ALI_DCDN_DOMAIN="$_cdomain" - fi - - # read cert and key files and urlencode both - _cert=$(_url_encode upper-hex <"$_cfullchain") - _key=$(_url_encode upper-hex <"$_ckey") - - _debug2 _cert "$_cert" - _debug2 _key "$_key" - - ## update domain ssl config - for domain in $DEPLOY_ALI_DCDN_DOMAIN; do - _set_dcdn_domain_ssl_certificate_query "$domain" "$_cert" "$_key" - if _ali_rest "Set DCDN domain SSL certificate for $domain" "" POST; then - _info "Domain $domain certificate has been deployed successfully" - fi - done - - return 0 -} - -# domain pub pri -_set_dcdn_domain_ssl_certificate_query() { - endpoint=$Ali_DCDN_API - query='' - query=$query'AccessKeyId='$Ali_Key - query=$query'&Action=SetDcdnDomainSSLCertificate' - query=$query'&CertType=upload' - query=$query'&DomainName='$1 - query=$query'&Format=json' - query=$query'&SSLPri='$3 - query=$query'&SSLProtocol=on' - query=$query'&SSLPub='$2 - query=$query'&SignatureMethod=HMAC-SHA1' - query=$query"&SignatureNonce=$(_ali_nonce)" - query=$query'&SignatureVersion=1.0' - query=$query'&Timestamp='$(_ali_timestamp) - query=$query'&Version=2018-01-15' -} diff --git a/deploy/baidu_cdn.sh b/deploy/baidu_cdn.sh deleted file mode 100644 index 7fe31f9b..00000000 --- a/deploy/baidu_cdn.sh +++ /dev/null @@ -1,222 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034,SC2154 - -# Deploy hook: Baidu Cloud CDN -# -# Code generated by GitHub Copilot with Claude Sonnet 4.6 and OpenAI Codex with GPT-5.6 Sol -# -# API Doc: https://cloud.baidu.com/doc/CDN/s/Zkna2r57w -# -# Uses the same credential variables as dnsapi/dns_baidu.sh: -# export Baidu_AK="your-access-key-id" -# export Baidu_SK="your-secret-access-key" -# -# To deploy to a CDN domain different from the certificate CN -# (e.g. wildcard or multi-domain certs): -# export DEPLOY_BAIDU_CDN_DOMAIN="cdn.example.com" -# -# Multiple CDN domains sharing the same certificate: -# export DEPLOY_BAIDU_CDN_DOMAIN="cdn1.example.com cdn2.example.com" - -BAIDU_CDN_HOST="cdn.baidubce.com" -_BAIDU_CDN_BCE_AUTH_RESULT="" - -baidu_cdn_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - if ! _baidu_cdn_load_credentials; then - return 1 - fi - - _getdeployconf DEPLOY_BAIDU_CDN_DOMAIN - if [ "$DEPLOY_BAIDU_CDN_DOMAIN" ]; then - _savedeployconf DEPLOY_BAIDU_CDN_DOMAIN "$DEPLOY_BAIDU_CDN_DOMAIN" - else - DEPLOY_BAIDU_CDN_DOMAIN="$_cdomain" - fi - - # Build JSON "domains" array from space-separated domain list - _domains_json="" - for _d in $DEPLOY_BAIDU_CDN_DOMAIN; do - _d_e="$(_baidu_cdn_json_escape "$_d")" - if [ -z "$_domains_json" ]; then - _domains_json="\"${_d_e}\"" - else - _domains_json="${_domains_json},\"${_d_e}\"" - fi - done - - # Build a valid cert name: must start with a letter, allow [A-Za-z0-9-/.], max 65 chars - _cert_name="$(printf "%s" "$_cdomain" | sed 's/\*\./wildcard./g;s/[^A-Za-z0-9./]/-/g' | cut -c 1-65)" - case "$_cert_name" in - [A-Za-z]*) ;; - *) _cert_name="c${_cert_name}" ;; - esac - - # PEM content is already Base64 inside the -----BEGIN/END----- wrappers. - # The API expects the raw PEM as a JSON string, so newlines must be escaped as \n. - _cert_pem="$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n')" - _key_pem="$(sed 's/$/\\n/' "$_ckey" | tr -d '\n')" - - _debug2 _cert_name "$_cert_name" - _debug2 _domains_json "[$_domains_json]" - - # Build JSON payload - _payload="{\"domains\":[${_domains_json}],\"certificate\":{\"certName\":\"${_cert_name}\",\"certServerData\":\"${_cert_pem}\",\"certPrivateData\":\"${_key_pem}\"}}" - - # Generate BCE v1 authorization header (query string included in canonical request) - _cdn_path="/v2/domain/certificate" - _cdn_query="action=put" - _ts="$(_utc_date | sed 's/ /T/')Z" - _content_type="application/json; charset=utf-8" - _payload_hash="$(printf "%s" "$_payload" | _digest sha256 hex)" - - if ! _baidu_cdn_bce_auth "POST" "$_cdn_path" "$_cdn_query" "$BAIDU_CDN_HOST" "$_ts" "3600" "$_content_type" "$_payload_hash"; then - _err "Failed to sign request" - return 1 - fi - - _H1="Authorization: $_BAIDU_CDN_BCE_AUTH_RESULT" - _H2="x-bce-date: $_ts" - _H3="x-bce-content-sha256: $_payload_hash" - _H4="Host: $BAIDU_CDN_HOST" - _H5="" - - _url="https://${BAIDU_CDN_HOST}${_cdn_path}?${_cdn_query}" - response="$(_post "$_payload" "$_url" "" "POST" "$_content_type")" - if [ "$?" != "0" ]; then - _err "Failed to call Baidu Cloud CDN API" - return 1 - fi - - _debug2 response "$response" - - if _contains "$response" "\"certId\""; then - _info "Certificate deployed to Baidu Cloud CDN for: $DEPLOY_BAIDU_CDN_DOMAIN" - return 0 - fi - - _err "Failed to deploy certificate to Baidu Cloud CDN: $response" - return 1 -} - -# BCE v1 signing with canonical query string support. -# The CDN endpoint uses ?action=put so it must be included in the canonical request. -_baidu_cdn_bce_auth() { - _method="$1" - _uri="$2" - _query="$3" - _host="$4" - _ts="$5" - _expire="$6" - _ct="$7" - _payload_hash="$8" - - _BAIDU_CDN_BCE_AUTH_RESULT="" - - _auth_prefix="bce-auth-v1/${Baidu_AK}/${_ts}/${_expire}" - _signed_headers="content-type;host;x-bce-content-sha256;x-bce-date" - _canonical_uri="$(_baidu_cdn_bce_encode_path "$_uri")" - - _host_e="$(printf "%s" "$_host" | _url_encode upper-hex)" - _date_e="$(printf "%s" "$_ts" | _url_encode upper-hex)" - _ct_e="$(printf "%s" "$_ct" | _url_encode upper-hex)" - _hash_e="$(printf "%s" "$_payload_hash" | _url_encode upper-hex)" - - _canonical_headers="content-type:${_ct_e} -host:${_host_e} -x-bce-content-sha256:${_hash_e} -x-bce-date:${_date_e}" - - _canonical_request="${_method} -${_canonical_uri} -${_query} -${_canonical_headers}" - - _sk_hex="$(printf "%s" "$Baidu_SK" | _hex_dump | tr -d " ")" - _signing_key="$(_baidu_cdn_hmac_sha256_hexkey "$_sk_hex" "$_auth_prefix")" - _signing_key_hex="$(printf "%s" "$_signing_key" | _hex_dump | tr -d " ")" - _signature="$(_baidu_cdn_hmac_sha256_hexkey "$_signing_key_hex" "$_canonical_request")" - - _BAIDU_CDN_BCE_AUTH_RESULT="${_auth_prefix}/${_signed_headers}/${_signature}" -} - -_baidu_cdn_load_credentials() { - Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}" - Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}" - - Baidu_AK="$(_baidu_cdn_trim_ws "$Baidu_AK")" - Baidu_SK="$(_baidu_cdn_trim_ws "$Baidu_SK")" - - if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then - _err "Baidu_AK and Baidu_SK are required" - return 1 - fi - - _saveaccountconf_mutable Baidu_AK "$Baidu_AK" - _saveaccountconf_mutable Baidu_SK "$Baidu_SK" - - return 0 -} - -_baidu_cdn_bce_encode_path() { - _p="$1" - _out="" - if [ "${_p#"/"}" != "$_p" ]; then - _out="/" - fi - - _rest="${_p#/}" - while [ -n "$_rest" ]; do - _seg="${_rest%%/*}" - if [ "$_seg" ]; then - if [ -z "$_out" ] || [ "$_out" = "/" ]; then - _out="${_out}$(printf "%s" "$_seg" | _url_encode upper-hex)" - else - _out="${_out}/$(printf "%s" "$_seg" | _url_encode upper-hex)" - fi - fi - if [ "${_rest#*/}" = "$_rest" ]; then - break - fi - _rest="${_rest#*/}" - done - - if [ -z "$_out" ]; then - _out="/" - fi - printf "%s" "$_out" -} - -_baidu_cdn_trim_ws() { - printf "%s" "$1" | tr '\r\n\t' ' ' | tr -s ' ' | sed 's/^ *//;s/ *$//' -} - -_baidu_cdn_json_escape() { - _s="$1" - _s="$(printf "%s" "$_s" | tr -d '\r\n')" - printf "%s" "$_s" | - sed 's/\\/\\\\/g; s/ /\\t/g' | - _baidu_cdn_json_encode -} - -_baidu_cdn_json_encode() { - _j_str="$(sed 's/"/\\"/g' | sed "s/\r/\\r/g")" - printf "%s" "$_j_str" | _hex_dump | _lower_case | sed 's/0a/5c 6e/g' | tr -d ' ' | _h2b | tr -d "\r\n" -} - -_baidu_cdn_hmac_sha256_hexkey() { - _key_hex="$1" - _msg="$2" - printf "%s" "$_msg" | _hmac sha256 "$_key_hex" hex -} diff --git a/deploy/byteplus_alb.sh b/deploy/byteplus_alb.sh deleted file mode 100644 index 394b431f..00000000 --- a/deploy/byteplus_alb.sh +++ /dev/null @@ -1,394 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034,SC2154 -# -# acme.sh deploy hook: BytePlus Application Load Balancer (ALB) -# https://github.com/acmesh-official/acme.sh/wiki/deployhooks -# -# Deploys SSL/TLS certificates issued by acme.sh to BytePlus ALB. -# Supports automatic renewal with zero-downtime certificate rotation -# for certificates that have already been uploaded and have a saved -# BytePlus CertificateId. -# -# ┌─────────────────────────────────────────────────────────────────────┐ -# │ FIRST TIME (new domain) │ -# │ 1. acme.sh --issue -d example.com -w /var/www/html/ │ -# │ 2. Upload/import the certificate to BytePlus ALB manually │ -# │ 3. Save/configure the existing CertificateId for this hook │ -# │ 4. Manually assign cert to ALB Listener (one-time only) │ -# │ │ -# │ RENEWAL (fully automatic after CertificateId is configured) │ -# │ acme.sh cron triggers renew → deploy hook runs automatically │ -# │ → ReplaceCertificate (UpdateMode=new) — single API call │ -# │ → All attached listeners updated, old cert auto-deleted │ -# └─────────────────────────────────────────────────────────────────────┘ -# -# Required environment variables: -# export BYTEPLUS_ACCESS_KEY="AKAPxxxxxxxxxx" -# export BYTEPLUS_SECRET_KEY="your-secret-key" -# -# Optional environment variables: -# export BYTEPLUS_REGION="ap-southeast-3" # default: ap-southeast-3 -# export BYTEPLUS_HOST="alb.ap-southeast-3.byteplusapi.com" # custom API host -# export BYTEPLUS_PROJECT_NAME="live" # default: "default" project -# export BYTEPLUS_CERT_NAME="" # default: acme-{domain}-{YYYYMMDD-HHMM} -# export BYTEPLUS_CERT_DESCRIPTION="" # default: empty -# export BYTEPLUS_DELETE_OLD_CERT="true" # default: true — auto-delete after replace -# -# API notes: -# - All BytePlus ALB APIs use GET with query string parameters -# - Request signing: HMAC-SHA256 with signed headers host;x-date -# - PublicKey/PrivateKey are URL-encoded (RFC 3986) in query string -# - ReplaceCertificate with UpdateMode=new uploads + replaces in 1 call -# -# Dependencies: curl, openssl, awk (standard on most Linux) -# -# Docs: -# Signing — https://docs.byteplus.com/en/docs/byteplus-platform/reference-how-to-calculate-a-signature -# ALB API — https://docs.byteplus.com/en/docs/byteplus-alb - -# ══════════════════════════════════════════════════════════════════════════════ -# Constants -# ══════════════════════════════════════════════════════════════════════════════ - -# SHA-256 hash of empty string (used for GET requests with no body) -_BYTEPLUS_EMPTY_HASH="e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" - -# ══════════════════════════════════════════════════════════════════════════════ -# Main deploy function — called by acme.sh -# ══════════════════════════════════════════════════════════════════════════════ - -byteplus_alb_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - # ── 1. Load & validate credentials ────────────────────────────────────────── - - # Preserve environment values before _getdeployconf (which may reset them) - _env_project_name="${BYTEPLUS_PROJECT_NAME:-}" - _env_delete_old="${BYTEPLUS_DELETE_OLD_CERT:-}" - - _getdeployconf BYTEPLUS_ACCESS_KEY - _getdeployconf BYTEPLUS_SECRET_KEY - _getdeployconf BYTEPLUS_REGION - _getdeployconf BYTEPLUS_HOST - _getdeployconf BYTEPLUS_PROJECT_NAME - _getdeployconf BYTEPLUS_DELETE_OLD_CERT - _getdeployconf BYTEPLUS_CERT_NAME - - # Restore from environment if _getdeployconf cleared them - if [ -z "$BYTEPLUS_PROJECT_NAME" ] && [ -n "$_env_project_name" ]; then - _debug "Restoring BYTEPLUS_PROJECT_NAME from environment" - BYTEPLUS_PROJECT_NAME="$_env_project_name" - fi - if [ -z "$BYTEPLUS_DELETE_OLD_CERT" ] && [ -n "$_env_delete_old" ]; then - BYTEPLUS_DELETE_OLD_CERT="$_env_delete_old" - fi - - # Validate required credentials - if [ -z "$BYTEPLUS_ACCESS_KEY" ]; then - _err "BYTEPLUS_ACCESS_KEY is not set." - _err "Please run: export BYTEPLUS_ACCESS_KEY=\"your-access-key\"" - return 1 - fi - if [ -z "$BYTEPLUS_SECRET_KEY" ]; then - _err "BYTEPLUS_SECRET_KEY is not set." - _err "Please run: export BYTEPLUS_SECRET_KEY=\"your-secret-key\"" - return 1 - fi - - # Save credentials for future runs - _savedeployconf BYTEPLUS_ACCESS_KEY "$BYTEPLUS_ACCESS_KEY" - _savedeployconf BYTEPLUS_SECRET_KEY "$BYTEPLUS_SECRET_KEY" - - # Region (default: ap-southeast-3) - BYTEPLUS_REGION="${BYTEPLUS_REGION:-ap-southeast-3}" - _savedeployconf BYTEPLUS_REGION "$BYTEPLUS_REGION" - - # Project name - if [ -n "$BYTEPLUS_PROJECT_NAME" ]; then - _savedeployconf BYTEPLUS_PROJECT_NAME "$BYTEPLUS_PROJECT_NAME" - _info "Using project: $BYTEPLUS_PROJECT_NAME" - else - _info "WARNING: BYTEPLUS_PROJECT_NAME is not set. Cert will go to 'default' project." - fi - - # Delete old cert toggle (default: true) - BYTEPLUS_DELETE_OLD_CERT="${BYTEPLUS_DELETE_OLD_CERT:-true}" - _savedeployconf BYTEPLUS_DELETE_OLD_CERT "$BYTEPLUS_DELETE_OLD_CERT" - - # API host — custom override or auto-build from region - if [ -n "$BYTEPLUS_HOST" ]; then - _BYTEPLUS_HOST="$BYTEPLUS_HOST" - _savedeployconf BYTEPLUS_HOST "$BYTEPLUS_HOST" - else - _BYTEPLUS_HOST="alb.${BYTEPLUS_REGION}.byteplusapi.com" - fi - _info "Using API host: $_BYTEPLUS_HOST" - _BYTEPLUS_SERVICE="alb" - - # ── 2. Build certificate name ──────────────────────────────────────────────── - - _date_tag=$(date -u +%Y%m%d-%H%M) - # Replace wildcard * and dots for a valid cert name - _safe_domain=$(echo "$_cdomain" | sed 's/\*\.//g' | sed 's/\./-/g') - # Safe identifier version for deployconf keys: map all non [A-Za-z0-9_] to _ - _conf_key=$(echo "$_cdomain" | sed 's/^\*\.//' | sed 's/[^A-Za-z0-9_]/_/g') - - if [ -z "$BYTEPLUS_CERT_NAME" ]; then - BYTEPLUS_CERT_NAME="acme-${_safe_domain}-${_date_tag}" - fi - - # Enforce BytePlus naming rules: start with letter, max 128 chars - BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | sed 's/[^A-Za-z0-9._-]/-/g') - case "$BYTEPLUS_CERT_NAME" in - [A-Za-z]*) ;; - - *) - BYTEPLUS_CERT_NAME="a$BYTEPLUS_CERT_NAME" - ;; - esac - BYTEPLUS_CERT_NAME=$(echo "$BYTEPLUS_CERT_NAME" | cut -c1-128) - - _info "Certificate name: $BYTEPLUS_CERT_NAME" - - # ── 3. Read cert and key ───────────────────────────────────────────────────── - # BytePlus requires NO blank lines between PEM blocks in the certificate chain - - _public_key=$(_strip_blank_lines <"$_cfullchain" | tr -d '\r') - _private_key=$(_strip_blank_lines <"$_ckey" | tr -d '\r') - - if [ -z "$_public_key" ] || [ -z "$_private_key" ]; then - _err "Failed to read certificate or key file." - return 1 - fi - - # ── 4. Deploy: first-time upload or renewal replace ───────────────────────── - - _getdeployconf "BYTEPLUS_CERT_ID_${_conf_key}" - _old_cert_id=$(eval echo "\$BYTEPLUS_CERT_ID_${_conf_key}") - - if [ -z "$_old_cert_id" ]; then - _byteplus_first_time_deploy - else - _byteplus_renewal_deploy - fi - - # Check if deploy step set _new_cert_id - if [ -z "$_new_cert_id" ]; then - return 1 - fi - - # ── 5. Save new CertificateId for next renewal ─────────────────────────────── - - _savedeployconf "BYTEPLUS_CERT_ID_${_conf_key}" "$_new_cert_id" - _info "Saved CertificateId '$_new_cert_id' for domain '$_cdomain'." - - return 0 -} - -# ══════════════════════════════════════════════════════════════════════════════ -# Deploy: First time — UploadCertificate -# ══════════════════════════════════════════════════════════════════════════════ - -_byteplus_first_time_deploy() { - _info "No previous CertificateId found." - _err "Refusing to upload certificate material because this hook passes PublicKey/PrivateKey as request parameters." - _err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings." - _err "Please upload the certificate to BytePlus manually for the initial deployment, set BYTEPLUS_CERT_ID_${_conf_key} to that CertificateId, and rerun." - _err "This hook stores CertificateId values per domain using deployconf, so the variable name must include the current domain-specific suffix." - _err "This hook must be updated to send PublicKey and PrivateKey in a POST body before automatic first-time upload can be enabled safely." - return 1 -} - -# ══════════════════════════════════════════════════════════════════════════════ -# Deploy: Renewal — ReplaceCertificate (UpdateMode=new) -# ══════════════════════════════════════════════════════════════════════════════ - -_byteplus_renewal_deploy() { - _info "Replacing old certificate '$_old_cert_id' (UpdateMode=new)..." - _err "Refusing to replace certificate material because this hook passes PublicKey/PrivateKey as request parameters." - _err "Uploading a private key in the request URL can leak it via logs, proxies, and process listings." - _err "Please replace the certificate in BytePlus manually for renewal until this hook is updated to send PublicKey and PrivateKey in a POST body safely." - return 1 -} - -# ══════════════════════════════════════════════════════════════════════════════ -# Delete old certificate (with retry) -# ══════════════════════════════════════════════════════════════════════════════ - -_byteplus_delete_old_cert() { - _del_cert_id="$1" - - _info "Waiting 5s for cert status to settle..." - _sleep 5 - - _info "Deleting old certificate '$_del_cert_id'..." - _del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}") - - if echo "$_del_response" | grep -q '"Error"'; then - _info "Delete failed, retrying in 10s..." - _sleep 10 - _del_response=$(_byteplus_alb_api "DeleteCertificate" "CertificateId=${_del_cert_id}") - - if echo "$_del_response" | grep -q '"Error"'; then - _info "Warning: Could not delete old certificate '$_del_cert_id'." - _info "Error: $(_byteplus_extract_error "$_del_response")" - _info "Please remove it manually from BytePlus Console." - else - _info "Old certificate '$_del_cert_id' deleted (retry succeeded)." - fi - else - _info "Old certificate '$_del_cert_id' deleted." - fi -} - -# ══════════════════════════════════════════════════════════════════════════════ -# JSON response helpers -# ══════════════════════════════════════════════════════════════════════════════ - -# Extract CertificateId from API response JSON -_byteplus_extract_cert_id() { - echo "$1" | _egrep_o '"CertificateId"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"' -} - -# Extract error message from API response JSON -_byteplus_extract_error() { - _code=$(echo "$1" | _egrep_o '"Code"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"') - _msg=$(echo "$1" | _egrep_o '"Message"\s*:\s*"[^"]*"' | head -1 | _egrep_o '"[^"]*"$' | tr -d '"') - if [ -n "$_code" ]; then - printf '%s — %s' "$_code" "$_msg" - else - printf '%s' "$1" - fi -} - -# ══════════════════════════════════════════════════════════════════════════════ -# BytePlus ALB API caller -# ══════════════════════════════════════════════════════════════════════════════ - -# Usage: _byteplus_alb_api ACTION [param1=val1] [param2=val2] ... -# All parameters sent via GET query string. Signing: HMAC-SHA256, host;x-date. -_byteplus_alb_api() { - _action="$1" - shift - - # Build query string — all params go in URL - _query_params="Action=${_action}&Version=2020-04-01" - - for _param in "$@"; do - _pname="${_param%%=*}" - _pval="${_param#*=}" - _query_params="${_query_params}&${_pname}=$(_byteplus_urlencode "$_pval")" - done - - # Timestamps - _x_date=$(date -u +%Y%m%dT%H%M%SZ) - _date_only=$(date -u +%Y%m%d) - - # Sort query params for canonical request - _sorted_query=$(echo "$_query_params" | tr '&' '\n' | LC_ALL=C sort | tr '\n' '&' | sed 's/&$//') - - # Canonical headers — only host and x-date - _canonical_headers="host:${_BYTEPLUS_HOST} -x-date:${_x_date} -" - _signed_headers="host;x-date" - - # Canonical request - _canonical_request="GET -/ -${_sorted_query} -${_canonical_headers} -${_signed_headers} -${_BYTEPLUS_EMPTY_HASH}" - - # Do not log _canonical_request because the query string may contain - # URL-encoded certificate or private key material. - - # Hash of canonical request - # _digest is provided by acme.sh and works across OpenSSL versions. - _cr_hash=$(printf '%s' "$_canonical_request" | _digest sha256 hex) - - # Credential scope - _credential_scope="${_date_only}/${BYTEPLUS_REGION}/${_BYTEPLUS_SERVICE}/request" - - # String to sign - _string_to_sign="HMAC-SHA256 -${_x_date} -${_credential_scope} -${_cr_hash}" - - _debug2 _string_to_sign "$_string_to_sign" - - # Signing key derivation (HMAC chain) - # _hmac reads data from stdin and returns a hex digest. - # acme.sh's _hmac abstracts away OpenSSL version differences, so this works - # on both modern (-mac HMAC -macopt hexkey:) and older (-hmac) OpenSSL builds. - # - # The first step seeds the chain from the raw secret key, so we convert it - # to hex first with _hex_dump (also an acme.sh built-in). - _secret_hex=$(printf '%s' "$BYTEPLUS_SECRET_KEY" | _hex_dump | tr -d ' \n') - _k_date=$(printf '%s' "$_date_only" | _hmac sha256 "$_secret_hex" hex) - _k_region=$(printf '%s' "$BYTEPLUS_REGION" | _hmac sha256 "$_k_date" hex) - _k_service=$(printf '%s' "$_BYTEPLUS_SERVICE" | _hmac sha256 "$_k_region" hex) - _k_signing=$(printf '%s' "request" | _hmac sha256 "$_k_service" hex) - - # Final signature - _signature=$(printf '%s' "$_string_to_sign" | _hmac sha256 "$_k_signing" hex) - - # Authorization header - _auth="HMAC-SHA256 Credential=${BYTEPLUS_ACCESS_KEY}/${_credential_scope}, SignedHeaders=${_signed_headers}, Signature=${_signature}" - - _secure_debug2 _auth "$_auth" - - # Send request parameters in the POST body instead of the URL query string. - # This avoids exposing sensitive or large values in debug-logged URLs and - # reduces the risk of exceeding URL length limits. - _url="https://${_BYTEPLUS_HOST}/" - _body="$_sorted_query" - - _saved_H1="${_H1:-}" - _saved_H2="${_H2:-}" - _saved_H3="${_H3:-}" - _saved_H4="${_H4:-}" - _saved_H5="${_H5:-}" - - _H1="Authorization: ${_auth}" - _H2="X-Date: ${_x_date}" - _H3="Host: ${_BYTEPLUS_HOST}" - _H4="Content-Type: application/x-www-form-urlencoded" - _H5="" - - _response="$(_post "$_body" "$_url" "" "POST")" - _request_ret="$?" - - _H1="$_saved_H1" - _H2="$_saved_H2" - _H3="$_saved_H3" - _H4="$_saved_H4" - _H5="$_saved_H5" - - if [ "$_request_ret" != "0" ]; then - _err "byteplus_alb_api request failed for [$_action]" - return 1 - fi - _debug2 "_byteplus_alb_api response [$_action]" "$_response" - printf '%s' "$_response" -} - -# ══════════════════════════════════════════════════════════════════════════════ -# URL encode (RFC 3986) -# ══════════════════════════════════════════════════════════════════════════════ - -_byteplus_urlencode() { - printf '%s' "$1" | _url_encode -} diff --git a/deploy/cachefly.sh b/deploy/cachefly.sh deleted file mode 100644 index 7841b20b..00000000 --- a/deploy/cachefly.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/usr/bin/env sh - -# Script to deploy certificate to CacheFly -# https://api.cachefly.com/api/2.5/docs#tag/Certificates/paths/~1certificates/post - -# This deployment required following variables -# export CACHEFLY_TOKEN="Your CacheFly API Token" - -# returns 0 means success, otherwise error. - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -CACHEFLY_API_BASE="https://api.cachefly.com/api/2.5" - -cachefly_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - if [ -z "$CACHEFLY_TOKEN" ]; then - _err "CACHEFLY_TOKEN is not defined." - return 1 - else - _savedomainconf CACHEFLY_TOKEN "$CACHEFLY_TOKEN" - fi - - _info "Deploying certificate to CacheFly..." - - ## upload certificate - string_fullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n') - string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') - - _request_body="{\"certificate\":\"$string_fullchain\",\"certificateKey\":\"$string_key\"}" - _debug _request_body "$_request_body" - _debug CACHEFLY_TOKEN "$CACHEFLY_TOKEN" - export _H1="Authorization: Bearer $CACHEFLY_TOKEN" - _response=$(_post "$_request_body" "$CACHEFLY_API_BASE/certificates" "" "POST" "application/json") - - if _contains "$_response" "message"; then - _err "Error in deploying $_cdomain certificate to CacheFly." - _err "$_response" - return 1 - fi - _debug response "$_response" - _info "Domain $_cdomain certificate successfully deployed to CacheFly." - return 0 -} diff --git a/deploy/cpanel_uapi.sh b/deploy/cpanel_uapi.sh index 02ef6b3e..e5381b61 100644 --- a/deploy/cpanel_uapi.sh +++ b/deploy/cpanel_uapi.sh @@ -52,15 +52,7 @@ cpanel_uapi_deploy() { # read cert and key files and urlencode both _cert=$(_url_encode <"$_ccert") - # with --signcsr the private key was never handed to acme.sh, so the key - # file does not exist; skip it instead of spilling a shell redirection - # error on every renewal (cPanel keeps using the already-installed key) - if [ -f "$_ckey" ]; then - _key=$(_url_encode <"$_ckey") - else - _debug "Key file $_ckey does not exist (csr mode), not sending a key." - _key="" - fi + _key=$(_url_encode <"$_ckey") _debug2 _cert "$_cert" _debug2 _key "$_key" @@ -87,11 +79,7 @@ cpanel_uapi_deploy() { # Auto mode if [ "$DEPLOY_CPANEL_AUTO_ENABLED" = "true" ]; then # call API for site config - if [ -n "$_uapi_user" ]; then - _response=$(uapi --user="$_uapi_user" DomainInfo list_domains) - else - _response=$(uapi DomainInfo list_domains) - fi + _response=$(uapi DomainInfo list_domains) # exit if error in response if [ -z "$_response" ] || [ "${_response#*"$uapi_error_response"}" != "$_response" ]; then _err "Error in deploying certificate - cannot retrieve sitelist:" @@ -206,8 +194,7 @@ __cpanel_parse_response() { printf("%s%s=%s\n", prefix, $2, $3); } }' | - sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' | - sed -e 's/^"//' -e 's/"$//' # YAML double-quotes values starting with '*' (wildcard subdomains) + sed -En -e 's/^result\/data\/(main_domain|sub_domains\/-|addon_domains\/-|parked_domains\/-)=(.*)$/\2/p' } # Load parameter by prefix+name - fallback to default if not set, and save to config diff --git a/deploy/directadmin.sh b/deploy/directadmin.sh deleted file mode 100644 index 3f60a088..00000000 --- a/deploy/directadmin.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env sh - -# Script to deploy certificate to DirectAdmin -# https://docs.directadmin.com/directadmin/customizing-workflow/api-all-about.html#creating-a-login-key -# https://docs.directadmin.com/changelog/version-1.24.4.html#cmd-api-catch-all-pop-passwords-frontpage-protected-dirs-ssl-certs - -# This deployment required following variables -# export DirectAdmin_SCHEME="https" # Optional, https or http, defaults to https -# export DirectAdmin_ENDPOINT="example.com:2222" -# export DirectAdmin_USERNAME="Your DirectAdmin Username" -# export DirectAdmin_KEY="Your DirectAdmin Login Key or Password" -# export DirectAdmin_MAIN_DOMAIN="Your DirectAdmin Main Domain, NOT Subdomain" - -# returns 0 means success, otherwise error. - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -directadmin_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - if [ -z "$DirectAdmin_ENDPOINT" ]; then - _err "DirectAdmin_ENDPOINT is not defined." - return 1 - else - _savedomainconf DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT" - fi - if [ -z "$DirectAdmin_USERNAME" ]; then - _err "DirectAdmin_USERNAME is not defined." - return 1 - else - _savedomainconf DirectAdmin_USERNAME "$DirectAdmin_USERNAME" - fi - if [ -z "$DirectAdmin_KEY" ]; then - _err "DirectAdmin_KEY is not defined." - return 1 - else - _savedomainconf DirectAdmin_KEY "$DirectAdmin_KEY" - fi - if [ -z "$DirectAdmin_MAIN_DOMAIN" ]; then - _err "DirectAdmin_MAIN_DOMAIN is not defined." - return 1 - else - _savedomainconf DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN" - fi - - # Optional SCHEME - _getdeployconf DirectAdmin_SCHEME - # set default values for DirectAdmin_SCHEME - [ -n "${DirectAdmin_SCHEME}" ] || DirectAdmin_SCHEME="https" - - _info "Deploying certificate to DirectAdmin..." - - # upload certificate - string_cfullchain=$(sed 's/$/\\n/' "$_cfullchain" | tr -d '\n') - string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') - - _request_body="{\"domain\":\"$DirectAdmin_MAIN_DOMAIN\",\"action\":\"save\",\"type\":\"paste\",\"certificate\":\"$string_key\n$string_cfullchain\n\"}" - _debug _request_body "$_request_body" - _debug DirectAdmin_ENDPOINT "$DirectAdmin_ENDPOINT" - _debug DirectAdmin_USERNAME "$DirectAdmin_USERNAME" - _debug DirectAdmin_KEY "$DirectAdmin_KEY" - _debug DirectAdmin_MAIN_DOMAIN "$DirectAdmin_MAIN_DOMAIN" - _response=$(_post "$_request_body" "$DirectAdmin_SCHEME://$DirectAdmin_USERNAME:$DirectAdmin_KEY@$DirectAdmin_ENDPOINT/CMD_API_SSL" "" "POST" "application/json") - - if _contains "$_response" "error=1"; then - _err "Error in deploying $_cdomain certificate to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN." - _err "$_response" - return 1 - fi - - _info "$_response" - _info "Domain $_cdomain certificate successfully deployed to DirectAdmin Domain $DirectAdmin_MAIN_DOMAIN." - - return 0 -} diff --git a/deploy/docker.sh b/deploy/docker.sh index 276172aa..3aa1b2cd 100755 --- a/deploy/docker.sh +++ b/deploy/docker.sh @@ -3,8 +3,6 @@ #DEPLOY_DOCKER_CONTAINER_LABEL="xxxxxxx" #DEPLOY_DOCKER_CONTAINER_KEY_FILE="/path/to/key.pem" -#DEPLOY_DOCKER_CONTAINER_KEY_MODE="0640" -#DEPLOY_DOCKER_CONTAINER_KEY_OWNER="1000:1000" #DEPLOY_DOCKER_CONTAINER_CERT_FILE="/path/to/cert.pem" #DEPLOY_DOCKER_CONTAINER_CA_FILE="/path/to/ca.pem" #DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE="/path/to/fullchain.pem" @@ -20,7 +18,6 @@ docker_deploy() { _ccert="$3" _cca="$4" _cfullchain="$5" - _cpfx="$6" _debug _cdomain "$_cdomain" _getdeployconf DEPLOY_DOCKER_CONTAINER_LABEL _debug2 DEPLOY_DOCKER_CONTAINER_LABEL "$DEPLOY_DOCKER_CONTAINER_LABEL" @@ -73,18 +70,6 @@ docker_deploy() { _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_FILE "$DEPLOY_DOCKER_CONTAINER_KEY_FILE" fi - _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE - _debug2 DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" - if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then - _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_MODE "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" - fi - - _getdeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER - _debug2 DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" - if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then - _savedeployconf DEPLOY_DOCKER_CONTAINER_KEY_OWNER "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" - fi - _getdeployconf DEPLOY_DOCKER_CONTAINER_CERT_FILE _debug2 DEPLOY_DOCKER_CONTAINER_CERT_FILE "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then @@ -103,12 +88,6 @@ docker_deploy() { _savedeployconf DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE "$DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE" fi - _getdeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE - _debug2 DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" - if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then - _savedeployconf DEPLOY_DOCKER_CONTAINER_PFX_FILE "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" - fi - _getdeployconf DEPLOY_DOCKER_CONTAINER_RELOAD_CMD _debug2 DEPLOY_DOCKER_CONTAINER_RELOAD_CMD "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then @@ -126,20 +105,6 @@ docker_deploy() { if ! _docker_cp "$_cid" "$_ckey" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then return 1 fi - if [ "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" ]; then - _info "Setting key file owner to $DEPLOY_DOCKER_CONTAINER_KEY_OWNER" - if ! _docker_exec "$_cid" chown "$DEPLOY_DOCKER_CONTAINER_KEY_OWNER" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then - _err "Can not change owner of key file in container" - return 1 - fi - fi - if [ "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" ]; then - _info "Setting key file mode to $DEPLOY_DOCKER_CONTAINER_KEY_MODE" - if ! _docker_exec "$_cid" chmod "$DEPLOY_DOCKER_CONTAINER_KEY_MODE" "$DEPLOY_DOCKER_CONTAINER_KEY_FILE"; then - _err "Can not change mode of key file in container" - return 1 - fi - fi fi if [ "$DEPLOY_DOCKER_CONTAINER_CERT_FILE" ]; then @@ -160,12 +125,6 @@ docker_deploy() { fi fi - if [ "$DEPLOY_DOCKER_CONTAINER_PFX_FILE" ]; then - if ! _docker_cp "$_cid" "$_cpfx" "$DEPLOY_DOCKER_CONTAINER_PFX_FILE"; then - return 1 - fi - fi - if [ "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" ]; then _info "Reloading: $DEPLOY_DOCKER_CONTAINER_RELOAD_CMD" if ! _docker_exec "$_cid" "$DEPLOY_DOCKER_CONTAINER_RELOAD_CMD"; then @@ -217,22 +176,10 @@ _docker_exec() { _debug2 cjson "$cjson" execid="$(echo "$cjson" | cut -d '"' -f 4)" _debug execid "$execid" - #Detach:true is required for podman's docker-compatible API: with - #Detach:false it streams the command output on the connection, so the - #non-empty response was misread as an error (issue #4977). The real - #result is checked via the exec inspect ExitCode below instead. - ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": true,\"Tty\": false}")" + ejson="$(_curl_unix_sock "$_DOCKER_SOCK" POST "/exec/$execid/start" "{\"Detach\": false,\"Tty\": false}")" _debug2 ejson "$ejson" - _et=0 - ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")" - while _contains "$ijson" "\"Running\":true" && [ "$_et" -lt 10 ]; do - sleep 1 - _et="$(_math "$_et" + 1)" - ijson="$(_curl_unix_sock "$_DOCKER_SOCK" GET "/exec/$execid/json")" - done - _debug2 ijson "$ijson" - if ! echo "$ijson" | _egrep_o "\"ExitCode\": *0[,}]" >/dev/null 2>&1; then - _err "docker exec error: $ijson" + if [ "$ejson" ]; then + _err "$ejson" return 1 fi else @@ -326,27 +273,16 @@ _check_curl_version() { _minor="$(_getfield "$_cversion" 2 '.')" _debug2 "_minor" "$_minor" - if [ "$_major" -ge "8" ]; then - #ok - return 0 - fi - if [ "$_major" = "7" ]; then - if [ "$_minor" -lt "40" ]; then - _err "curl v$_cversion doesn't support unit socket" - _err "Please upgrade to curl 7.40 or later." - return 1 - fi - if [ "$_minor" -lt "50" ]; then - _debug "Use short host name" - export _CURL_NO_HOST=1 - else - export _CURL_NO_HOST= - fi - return 0 - else + if [ "$_major$_minor" -lt "740" ]; then _err "curl v$_cversion doesn't support unit socket" _err "Please upgrade to curl 7.40 or later." return 1 fi - + if [ "$_major$_minor" -lt "750" ]; then + _debug "Use short host name" + export _CURL_NO_HOST=1 + else + export _CURL_NO_HOST= + fi + return 0 } diff --git a/deploy/edgio.sh b/deploy/edgio.sh deleted file mode 100644 index 1acd0c8f..00000000 --- a/deploy/edgio.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env sh - -# Here is a script to deploy cert to edgio using its API -# https://docs.edg.io/guides/v7/develop/rest_api/authentication -# https://docs.edg.io/rest_api/#tag/tls-certs/operation/postConfigV01TlsCerts - -# This deployment required following variables -# export EDGIO_CLIENT_ID="Your Edgio Client ID" -# export EDGIO_CLIENT_SECRET="Your Edgio Client Secret" -# export EDGIO_ENVIRONMENT_ID="Your Edgio Environment ID" - -# If have more than one Environment ID -# export EDGIO_ENVIRONMENT_ID="ENVIRONMENT_ID_1 ENVIRONMENT_ID_2" - -# returns 0 means success, otherwise error. - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -edgio_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - if [ -z "$EDGIO_CLIENT_ID" ]; then - _err "EDGIO_CLIENT_ID is not defined." - return 1 - else - _savedomainconf EDGIO_CLIENT_ID "$EDGIO_CLIENT_ID" - fi - - if [ -z "$EDGIO_CLIENT_SECRET" ]; then - _err "EDGIO_CLIENT_SECRET is not defined." - return 1 - else - _savedomainconf EDGIO_CLIENT_SECRET "$EDGIO_CLIENT_SECRET" - fi - - if [ -z "$EDGIO_ENVIRONMENT_ID" ]; then - _err "EDGIO_ENVIRONMENT_ID is not defined." - return 1 - else - _savedomainconf EDGIO_ENVIRONMENT_ID "$EDGIO_ENVIRONMENT_ID" - fi - - _info "Getting access token" - _data="client_id=$EDGIO_CLIENT_ID&client_secret=$EDGIO_CLIENT_SECRET&grant_type=client_credentials&scope=app.config" - _debug Get_access_token_data "$_data" - _response=$(_post "$_data" "https://id.edgio.app/connect/token" "" "POST" "application/x-www-form-urlencoded") - _debug Get_access_token_response "$_response" - _access_token=$(echo "$_response" | _json_decode | _egrep_o '"access_token":"[^"]*' | cut -d : -f 2 | tr -d '"') - _debug _access_token "$_access_token" - if [ -z "$_access_token" ]; then - _err "Error in getting access token" - return 1 - fi - - _info "Uploading certificate" - string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n') - string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n') - string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') - - for ENVIRONMENT_ID in $EDGIO_ENVIRONMENT_ID; do - _data="{\"environment_id\":\"$ENVIRONMENT_ID\",\"primary_cert\":\"$string_ccert\",\"intermediate_cert\":\"$string_cca\",\"private_key\":\"$string_key\"}" - _debug Upload_certificate_data "$_data" - _H1="Authorization: Bearer $_access_token" - _response=$(_post "$_data" "https://edgioapis.com/config/v0.1/tls-certs" "" "POST" "application/json") - if _contains "$_response" "message"; then - _err "Error in deploying $_cdomain certificate to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID." - _err "$_response" - return 1 - fi - _debug Upload_certificate_response "$_response" - _info "Domain $_cdomain certificate successfully deployed to Edgio ENVIRONMENT_ID $ENVIRONMENT_ID." - done - - return 0 -} diff --git a/deploy/exim4.sh b/deploy/exim4.sh index cf664d79..260b8798 100644 --- a/deploy/exim4.sh +++ b/deploy/exim4.sh @@ -109,5 +109,6 @@ exim4_deploy() { fi return 1 fi + return 0 } diff --git a/deploy/fortigate.sh b/deploy/fortigate.sh deleted file mode 100644 index f00ca1cb..00000000 --- a/deploy/fortigate.sh +++ /dev/null @@ -1,175 +0,0 @@ -#!/usr/bin/env sh -# Script to deploy a certificate to FortiGate via API and set it as the current web GUI certificate. -# -# FortiGate's native ACME integration does not support wildcard certificates or domain validation, -# and is not supported if you have a custom management web port (eg. DNAT web traffic). -# -# REQUIRED: -# export FGT_HOST="fortigate_hostname-or-ip" -# export FGT_TOKEN="fortigate_api_token" -# -# OPTIONAL: -# export FGT_PORT="10443" # Custom HTTPS port (defaults to 443 if not set) -# -# Run `acme.sh --deploy -d example.com --deploy-hook fortigate --insecure` to use this script. -# `--insecure` is required on first run if not already using a valid SSL certificate on firewall. - -# Function to parse a FortiGate API response -_fortigate_parse_response() { - _fortigate_response="$1" - _fortigate_func="$2" - _fortigate_status=$(echo "$_fortigate_response" | _egrep_o '"status":[ ]*"[^"]*"' | cut -d '"' -f 4) - - if [ "$_fortigate_status" != "success" ]; then - _err "[$_fortigate_func] Operation failed. Deploy with --insecure if current certificate is invalid. Try deploying with --debug to troubleshoot." - return 1 - fi - - _debug "[$_fortigate_func] Operation successful." - return 0 -} - -# Function to deploy a base64-encoded certificate to the firewall -_fortigate_deployer() { - _fortigate_cert_base64=$(_base64 <"$_fortigate_cfullchain" | tr -d '\n') - _fortigate_key_base64=$(_base64 <"$_fortigate_ckey" | tr -d '\n') - _fortigate_payload=$( - cat < # Public domain, 2019 -# Update by DreamOfIce in 2023 #export DEPLOY_GCORE_CDN_USERNAME=myusername #export DEPLOY_GCORE_CDN_PASSWORD=mypassword @@ -57,7 +56,7 @@ gcore_cdn_deploy() { _request="{\"username\":\"$Le_Deploy_gcore_cdn_username\",\"password\":\"$Le_Deploy_gcore_cdn_password\"}" _debug _request "$_request" export _H1="Content-Type:application/json" - _response=$(_post "$_request" "https://api.gcore.com/iam/auth/jwt/login") + _response=$(_post "$_request" "https://api.gcdn.co/auth/jwt/login") _debug _response "$_response" _regex=".*\"access\":\"\([-._0-9A-Za-z]*\)\".*$" _debug _regex "$_regex" @@ -70,8 +69,8 @@ gcore_cdn_deploy() { fi _info "Find CDN resource with cname $_cdomain" - export _H2="Authorization:Bearer $_token" - _response=$(_get "https://api.gcore.com/cdn/resources") + export _H2="Authorization:Token $_token" + _response=$(_get "https://api.gcdn.co/resources") _debug _response "$_response" _regex="\"primary_resource\":null}," _debug _regex "$_regex" @@ -103,7 +102,7 @@ gcore_cdn_deploy() { _date=$(date "+%d.%m.%Y %H:%M:%S") _request="{\"name\":\"$_cdomain ($_date)\",\"sslCertificate\":\"$_fullchain\",\"sslPrivateKey\":\"$_key\"}" _debug _request "$_request" - _response=$(_post "$_request" "https://api.gcore.com/cdn/sslData") + _response=$(_post "$_request" "https://api.gcdn.co/sslData") _debug _response "$_response" _regex=".*\"id\":\([0-9]*\).*$" _debug _regex "$_regex" @@ -118,7 +117,7 @@ gcore_cdn_deploy() { _info "Update CDN resource" _request="{\"originGroup\":$_originGroup,\"sslData\":$_sslDataAdd}" _debug _request "$_request" - _response=$(_post "$_request" "https://api.gcore.com/cdn/resources/$_resourceId" '' "PUT") + _response=$(_post "$_request" "https://api.gcdn.co/resources/$_resourceId" '' "PUT") _debug _response "$_response" _regex=".*\"sslData\":\([0-9]*\).*$" _debug _regex "$_regex" @@ -134,7 +133,7 @@ gcore_cdn_deploy() { _info "Not found old SSL certificate" else _info "Delete old SSL certificate" - _response=$(_post '' "https://api.gcore.com/cdn/sslData/$_sslDataOld" '' "DELETE") + _response=$(_post '' "https://api.gcdn.co/sslData/$_sslDataOld" '' "DELETE") _debug _response "$_response" fi diff --git a/deploy/haproxy.sh b/deploy/haproxy.sh index 9736e6ff..c255059d 100644 --- a/deploy/haproxy.sh +++ b/deploy/haproxy.sh @@ -36,20 +36,6 @@ # Note: This functionality requires HAProxy was compiled against # a version of OpenSSL that supports this. # -# export DEPLOY_HAPROXY_HOT_UPDATE="yes" -# export DEPLOY_HAPROXY_STATS_SOCKET="UNIX:/run/haproxy/admin.sock" -# -# OPTIONAL: Deploy the certificate over the HAProxy stats socket without -# needing to reload HAProxy. Default is "no". -# -# Require the socat binary. DEPLOY_HAPROXY_STATS_SOCKET variable uses the socat -# address format. The certificate can be deployed to a comma separated ',' list -# of hosts ("TCP4:10.0.0.1:1999,TCP4:10.0.0.2:1999") -# -# export DEPLOY_HAPROXY_MASTER_CLI="UNIX:/run/haproxy-master.sock" -# -# OPTIONAL: To use the master CLI with DEPLOY_HAPROXY_HOT_UPDATE="yes" instead -# of a stats socket, use this variable. ######## Public functions ##################### @@ -60,7 +46,6 @@ haproxy_deploy() { _ccert="$3" _cca="$4" _cfullchain="$5" - _cmdpfx="" # Some defaults DEPLOY_HAPROXY_PEM_PATH_DEFAULT="/etc/haproxy" @@ -68,8 +53,6 @@ haproxy_deploy() { DEPLOY_HAPROXY_BUNDLE_DEFAULT="no" DEPLOY_HAPROXY_ISSUER_DEFAULT="no" DEPLOY_HAPROXY_RELOAD_DEFAULT="true" - DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT="no" - DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT="UNIX:/run/haproxy/admin.sock" _debug _cdomain "${_cdomain}" _debug _ckey "${_ckey}" @@ -103,11 +86,6 @@ haproxy_deploy() { _savedomainconf Le_Deploy_haproxy_pem_name "${Le_Deploy_haproxy_pem_name}" elif [ -z "${Le_Deploy_haproxy_pem_name}" ]; then Le_Deploy_haproxy_pem_name="${DEPLOY_HAPROXY_PEM_NAME_DEFAULT}" - # We better not have '*' as the first character - if [ "${Le_Deploy_haproxy_pem_name%%"${Le_Deploy_haproxy_pem_name#?}"}" = '*' ]; then - # removes the first characters and add a _ instead - Le_Deploy_haproxy_pem_name="_${Le_Deploy_haproxy_pem_name#?}" - fi fi # BUNDLE is optional. If not provided then assume "${DEPLOY_HAPROXY_BUNDLE_DEFAULT}" @@ -140,36 +118,6 @@ haproxy_deploy() { Le_Deploy_haproxy_reload="${DEPLOY_HAPROXY_RELOAD_DEFAULT}" fi - # HOT_UPDATE is optional. If not provided then assume "${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}" - _getdeployconf DEPLOY_HAPROXY_HOT_UPDATE - _debug2 DEPLOY_HAPROXY_HOT_UPDATE "${DEPLOY_HAPROXY_HOT_UPDATE}" - if [ -n "${DEPLOY_HAPROXY_HOT_UPDATE}" ]; then - Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE}" - _savedomainconf Le_Deploy_haproxy_hot_update "${Le_Deploy_haproxy_hot_update}" - elif [ -z "${Le_Deploy_haproxy_hot_update}" ]; then - Le_Deploy_haproxy_hot_update="${DEPLOY_HAPROXY_HOT_UPDATE_DEFAULT}" - fi - - # STATS_SOCKET is optional. If not provided then assume "${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}" - _getdeployconf DEPLOY_HAPROXY_STATS_SOCKET - _debug2 DEPLOY_HAPROXY_STATS_SOCKET "${DEPLOY_HAPROXY_STATS_SOCKET}" - if [ -n "${DEPLOY_HAPROXY_STATS_SOCKET}" ]; then - Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET}" - _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}" - elif [ -z "${Le_Deploy_haproxy_stats_socket}" ]; then - Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_STATS_SOCKET_DEFAULT}" - fi - - # MASTER_CLI is optional. No defaults are used. When the master CLI is used, - # all commands are sent with a prefix. - _getdeployconf DEPLOY_HAPROXY_MASTER_CLI - _debug2 DEPLOY_HAPROXY_MASTER_CLI "${DEPLOY_HAPROXY_MASTER_CLI}" - if [ -n "${DEPLOY_HAPROXY_MASTER_CLI}" ]; then - Le_Deploy_haproxy_stats_socket="${DEPLOY_HAPROXY_MASTER_CLI}" - _savedomainconf Le_Deploy_haproxy_stats_socket "${Le_Deploy_haproxy_stats_socket}" - _cmdpfx="@1 " # command prefix used for master CLI only. - fi - # Set the suffix depending if we are creating a bundle or not if [ "${Le_Deploy_haproxy_bundle}" = "yes" ]; then _info "Bundle creation requested" @@ -199,7 +147,7 @@ haproxy_deploy() { # Create a temporary PEM file _temppem="$(_mktemp)" _debug _temppem "${_temppem}" - cat "${_ccert}" "${_cca}" "${_ckey}" | grep . >"${_temppem}" + cat "${_ckey}" "${_ccert}" "${_cca}" >"${_temppem}" _ret="$?" # Check that we could create the temporary file @@ -272,18 +220,12 @@ haproxy_deploy() { _cafile_argument="" fi _debug _cafile_argument "${_cafile_argument}" - # OpenSSL 1.1+ expects -header Host=value (one argument), while - # LibreSSL keeps the old two-argument form -header Host value at any - # version (3.x/4.x), so it must be detected by name, not by number. - _openssl_name=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f1) + # if OpenSSL/LibreSSL is v1.1 or above, the format for the -header option has changed _openssl_version=$(${ACME_OPENSSL_BIN:-openssl} version | cut -d' ' -f2) - _debug _openssl_name "${_openssl_name}" _debug _openssl_version "${_openssl_version}" _openssl_major=$(echo "${_openssl_version}" | cut -d '.' -f1) _openssl_minor=$(echo "${_openssl_version}" | cut -d '.' -f2) - if [ "${_openssl_name}" = "LibreSSL" ]; then - _header_sep=" " - elif [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then + if [ "${_openssl_major}" -eq "1" ] && [ "${_openssl_minor}" -ge "1" ] || [ "${_openssl_major}" -ge "2" ]; then _header_sep="=" else _header_sep=" " @@ -323,91 +265,15 @@ haproxy_deploy() { fi fi - if [ "${Le_Deploy_haproxy_hot_update}" = "yes" ]; then - # set the socket name for messages - if [ -n "${_cmdpfx}" ]; then - _socketname="master CLI" - else - _socketname="stats socket" - fi - - # Update certificate over HAProxy stats socket or master CLI. - if _exists socat; then - IFS=',' - for _statssock in ${Le_Deploy_haproxy_stats_socket}; do - # look for the certificate on the stats socket, to choose between updating or creating one - _socat_cert_cmd="echo '${_cmdpfx}show ssl cert' | socat '${_statssock}' - | grep -q '^${_pem}$'" - _debug _socat_cert_cmd "${_socat_cert_cmd}" - eval "${_socat_cert_cmd}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _newcert="1" - _info "Creating new certificate '${_pem}' over HAProxy ${_socketname}." - # certificate wasn't found, it's a new one. We should check if the crt-list exists and creates/inserts the certificate. - _socat_crtlist_show_cmd="echo '${_cmdpfx}show ssl crt-list' | socat '${_statssock}' - | grep -q '^${Le_Deploy_haproxy_pem_path}$'" - _debug _socat_crtlist_show_cmd "${_socat_crtlist_show_cmd}" - eval "${_socat_crtlist_show_cmd}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Couldn't find '${Le_Deploy_haproxy_pem_path}' in haproxy 'show ssl crt-list'" - return "${_ret}" - fi - # create a new certificate - _socat_new_cmd="echo '${_cmdpfx}new ssl cert ${_pem}' | socat '${_statssock}' - | grep -q 'New empty'" - _debug _socat_new_cmd "${_socat_new_cmd}" - eval "${_socat_new_cmd}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Couldn't create '${_pem}' in haproxy" - return "${_ret}" - fi - else - _info "Update existing certificate '${_pem}' over HAProxy ${_socketname}." - fi - # printf %b, not "echo -e": dash's echo has no -e and sends a literal "-e " to the socket. - # "Transaction updated" is replied instead of "created" when an uncommitted transaction exists. - _socat_cert_set_cmd="printf '%b\n' '${_cmdpfx}set ssl cert ${_pem} <<\n$(cat "${_pem}")\n' | socat '${_statssock}' - | grep -qE 'Transaction (created|updated)'" - _secure_debug _socat_cert_set_cmd "${_socat_cert_set_cmd}" - eval "${_socat_cert_set_cmd}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Can't update '${_pem}' in haproxy" - return "${_ret}" - fi - _socat_cert_commit_cmd="echo '${_cmdpfx}commit ssl cert ${_pem}' | socat '${_statssock}' - | grep -q '^Success!$'" - _debug _socat_cert_commit_cmd "${_socat_cert_commit_cmd}" - eval "${_socat_cert_commit_cmd}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Can't commit '${_pem}' in haproxy" - return ${_ret} - fi - if [ "${_newcert}" = "1" ]; then - # if this is a new certificate, it needs to be inserted into the crt-list` - _socat_cert_add_cmd="echo '${_cmdpfx}add ssl crt-list ${Le_Deploy_haproxy_pem_path} ${_pem}' | socat '${_statssock}' - | grep -q 'Success!'" - _debug _socat_cert_add_cmd "${_socat_cert_add_cmd}" - eval "${_socat_cert_add_cmd}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Can't update '${_pem}' in haproxy" - return "${_ret}" - fi - fi - done - else - _err "'socat' is not available, couldn't update over ${_socketname}" - fi + # Reload HAProxy + _debug _reload "${_reload}" + eval "${_reload}" + _ret=$? + if [ "${_ret}" != "0" ]; then + _err "Error code ${_ret} during reload" + return ${_ret} else - # Reload HAProxy - _debug _reload "${_reload}" - eval "${_reload}" - _ret=$? - if [ "${_ret}" != "0" ]; then - _err "Error code ${_ret} during reload" - return ${_ret} - else - _info "Reload successful" - fi + _info "Reload successful" fi return 0 diff --git a/deploy/ikuai.sh b/deploy/ikuai.sh deleted file mode 100644 index fa0926dc..00000000 --- a/deploy/ikuai.sh +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env sh - -# Here is a script to deploy cert to ikuai using curl -# -# it requires following environment variables: -# -# IKUAI_SCHEME="http" - http or https , defaults to "http" -# IKUAI_HOSTNAME="localhost" - host , defaults to "192.168.9.1" -# IKUAI_PORT="80" - port , defaults to "80" -# IKUAI_USERNAME="admin" - username , defaults to "admin" -# IKUAI_PASSWORD="yourPassword" - password -# IKUAI_CERT_ID=1 - ikuai cert id , defaults to 1, and only 1 is supported for now !!! -# -#returns 0 means success, otherwise error. -# -######## Public functions ##################### -# -#domain keyfile certfile cafile fullchain -ikuai_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - # Get deploy conf - _getdeployconf IKUAI_SCHEME - _getdeployconf IKUAI_HOSTNAME - _getdeployconf IKUAI_PORT - _getdeployconf IKUAI_USERNAME - _getdeployconf IKUAI_PASSWORD - _getdeployconf IKUAI_CERT_ID - - # Use default if not provided - [ -n "$IKUAI_SCHEME" ] || IKUAI_SCHEME="http" - [ -n "$IKUAI_HOSTNAME" ] || IKUAI_HOSTNAME="192.168.9.1" - [ -n "$IKUAI_PORT" ] || IKUAI_PORT=80 - [ -n "$IKUAI_USERNAME" ] || IKUAI_USERNAME="admin" - [ -n "$IKUAI_CERT_ID" ] || IKUAI_CERT_ID=1 - - if [ -z "$IKUAI_PASSWORD" ]; then - _err "please define IKUAI_PASSWORD." - return 1 - fi - - _debug2 IKUAI_SCHEME "$IKUAI_SCHEME" - _debug2 IKUAI_HOSTNAME "$IKUAI_HOSTNAME" - _debug2 IKUAI_PORT "$IKUAI_PORT" - _debug2 IKUAI_USERNAME "$IKUAI_USERNAME" - _secure_debug2 IKUAI_PASSWORD "$IKUAI_PASSWORD" - - _info "Login to ikuai ..." - _ikuai_url="$IKUAI_SCHEME://$IKUAI_HOSTNAME:$IKUAI_PORT" - _pass_md5="$(printf "%s" "$IKUAI_PASSWORD" | _digest md5 hex | _lower_case)" - _pass_salt="$(printf "salt_11%s" "$IKUAI_PASSWORD" | _base64)" - _debug2 _ikuai_url "$_ikuai_url" - - _login_req="{\"username\":\"$IKUAI_USERNAME\",\"passwd\":\"$_pass_md5\",\"pass\":\"$_pass_salt\",\"remember_password\":\"\"}" - _response=$(_post "$_login_req" "$_ikuai_url/Action/login" "" "POST" "application/json") - - _err_msg="$(printf "%s" "$_response" | _normalizeJson | _egrep_o '"ErrMsg":"[^"]*"' | cut -d'"' -f 4)" - # check ErrMsg - if [ "$_err_msg" != "Success" ]; then - _err "Failed to login to ikuai: $_err_msg" - return 1 - fi - # check cookie - _cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2 | sed 's/;.*//')" - if [ -z "$_cookie" ]; then - _err "Fail to get the cookie." - return 1 - fi - - # Set cookie header - _H1="Cookie: $_cookie; username=$IKUAI_USERNAME; login=1" - - _info "Deploy the cert to ikuai ... " - - # Should replace \n to @ ," " to # - _cert_content_single_line="$(tr <"$_cfullchain" '\n' '@' | tr ' ' '#')" - _key_content_single_line="$(tr <"$_ckey" '\n' '@' | tr ' ' '#')" - - _debug2 _cert_content_single_line "$_cert_content_single_line" - _secure_debug2 _key_content_single_line "$_key_content_single_line" - - _key_manager_req="{\"func_name\":\"key_manager\",\"action\":\"save\",\"param\":{\"ca\":\"$_cert_content_single_line\",\"key\":\"$_key_content_single_line\",\"id\":$IKUAI_CERT_ID,\"enabled\":\"yes\",\"comment\":\"\"}}" - _response=$(_post "$_key_manager_req" "$_ikuai_url/Action/call" "" "POST" "application/json") - - _err_msg="$(printf "%s" "$_response" | _normalizeJson | _egrep_o '"ErrMsg":"[^"]*"' | cut -d'"' -f 4)" - # check ErrMsg - if [ "$_err_msg" != "Success" ]; then - _err "Failed to deploy the cert to ikuai: $_err_msg" - return 1 - fi - - _info "Save the deploy config ... " - # Save the config - _savedeployconf IKUAI_SCHEME "$IKUAI_SCHEME" - _savedeployconf IKUAI_HOSTNAME "$IKUAI_HOSTNAME" - _savedeployconf IKUAI_PORT "$IKUAI_PORT" - _savedeployconf IKUAI_USERNAME "$IKUAI_USERNAME" - _savedeployconf IKUAI_PASSWORD "$IKUAI_PASSWORD" - _savedeployconf IKUAI_CERT_ID "$IKUAI_CERT_ID" - - _info "Successfully deployed certificate to ikuai. Enjoy! :>" - - return 0 -} diff --git a/deploy/kemplm.sh b/deploy/kemplm.sh deleted file mode 100755 index 4cdfcbbe..00000000 --- a/deploy/kemplm.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env sh - -#Here is a script to deploy cert to a Kemp Loadmaster. - -#returns 0 means success, otherwise error. - -#DEPLOY_KEMP_TOKEN="token" -#DEPLOY_KEMP_URL="https://kemplm.example.com" - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -kemplm_deploy() { - _domain="$1" - _key_file="$2" - _cert_file="$3" - _ca_file="$4" - _fullchain_file="$5" - - _debug _domain "$_domain" - _debug _key_file "$_key_file" - _debug _cert_file "$_cert_file" - _debug _ca_file "$_ca_file" - _debug _fullchain_file "$_fullchain_file" - - if ! _exists jq; then - _err "jq not found" - return 1 - fi - - # Rename wildcard certs, kemp accepts only alphanumeric names so we delete '*.' from filename - _kemp_domain=$(echo "${_domain}" | sed 's/\*\.//') - _debug _kemp_domain "$_kemp_domain" - - # Read config from saved values or env - _getdeployconf DEPLOY_KEMP_TOKEN - _getdeployconf DEPLOY_KEMP_URL - - _debug DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL" - _secure_debug DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN" - - if [ -z "$DEPLOY_KEMP_TOKEN" ]; then - _err "Kemp Loadmaster token is not found, please define DEPLOY_KEMP_TOKEN." - return 1 - fi - if [ -z "$DEPLOY_KEMP_URL" ]; then - _err "Kemp Loadmaster URL is not found, please define DEPLOY_KEMP_URL." - return 1 - fi - - # Save current values - _savedeployconf DEPLOY_KEMP_TOKEN "$DEPLOY_KEMP_TOKEN" - _savedeployconf DEPLOY_KEMP_URL "$DEPLOY_KEMP_URL" - - # Check if certificate is already installed - _info "Check if certificate is already present" - _list_request="{\"cmd\": \"listcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\"}" - _debug3 _list_request "${_list_request}" - _kemp_cert_count=$(HTTPS_INSECURE=1 _post "${_list_request}" "${DEPLOY_KEMP_URL}/accessv2" | jq -r '.cert[] | .name' | grep -c "^${_kemp_domain}$") - _debug2 _kemp_cert_count "${_kemp_cert_count}" - - _kemp_replace_cert=1 - if [ "${_kemp_cert_count}" -eq 0 ]; then - _kemp_replace_cert=0 - _info "Certificate does not exist on Kemp Loadmaster" - else - _info "Certificate already exists on Kemp Loadmaster" - fi - _debug _kemp_replace_cert "${_kemp_replace_cert}" - - # Upload new certificate to Kemp Loadmaster - _kemp_upload_cert=$(_mktemp) - cat "${_fullchain_file}" "${_key_file}" | base64 | tr -d '\n' >"${_kemp_upload_cert}" - - _info "Uploading certificate to Kemp Loadmaster" - _add_data=$(cat "${_kemp_upload_cert}") - _add_request="{\"cmd\": \"addcert\", \"apikey\": \"${DEPLOY_KEMP_TOKEN}\", \"replace\": ${_kemp_replace_cert}, \"cert\": \"${_kemp_domain}\", \"data\": \"${_add_data}\"}" - _debug3 _add_request "${_add_request}" - _kemp_post_result=$(HTTPS_INSECURE=1 _post "${_add_request}" "${DEPLOY_KEMP_URL}/accessv2") - _retval=$? - _debug2 _kemp_post_result "${_kemp_post_result}" - if [ "${_retval}" -eq 0 ]; then - _kemp_post_status=$(echo "${_kemp_post_result}" | jq -r '.status') - _kemp_post_message=$(echo "${_kemp_post_result}" | jq -r '.message') - if [ "${_kemp_post_status}" = "ok" ]; then - _info "Upload successful" - else - _err "Upload failed: ${_kemp_post_message}" - _retval=1 - fi - else - _err "Upload failed" - _retval=1 - fi - - rm "${_kemp_upload_cert}" - - return $_retval -} diff --git a/deploy/keyhelp.sh b/deploy/keyhelp.sh deleted file mode 100644 index f66d27ce..00000000 --- a/deploy/keyhelp.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/usr/bin/env sh - -# Script to deploy certificate to KeyHelp -# This deployment required following variables -# export DEPLOY_KEYHELP_BASEURL="https://keyhelp.example.com" -# export DEPLOY_KEYHELP_USERNAME="Your KeyHelp Username" -# export DEPLOY_KEYHELP_PASSWORD="Your KeyHelp Password" -# export DEPLOY_KEYHELP_DOMAIN_ID="Depoly certificate to this Domain ID" - -# Open the 'Edit domain' page, and you will see id=xxx at the end of the URL. This is the Domain ID. -# https://DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=xxx - -# If have more than one domain name -# export DEPLOY_KEYHELP_DOMAIN_ID="111 222 333" - -keyhelp_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - if [ -z "$DEPLOY_KEYHELP_BASEURL" ]; then - _err "DEPLOY_KEYHELP_BASEURL is not defined." - return 1 - else - _savedomainconf DEPLOY_KEYHELP_BASEURL "$DEPLOY_KEYHELP_BASEURL" - fi - - if [ -z "$DEPLOY_KEYHELP_USERNAME" ]; then - _err "DEPLOY_KEYHELP_USERNAME is not defined." - return 1 - else - _savedomainconf DEPLOY_KEYHELP_USERNAME "$DEPLOY_KEYHELP_USERNAME" - fi - - if [ -z "$DEPLOY_KEYHELP_PASSWORD" ]; then - _err "DEPLOY_KEYHELP_PASSWORD is not defined." - return 1 - else - _savedomainconf DEPLOY_KEYHELP_PASSWORD "$DEPLOY_KEYHELP_PASSWORD" - fi - - if [ -z "$DEPLOY_KEYHELP_DOMAIN_ID" ]; then - _err "DEPLOY_KEYHELP_DOMAIN_ID is not defined." - return 1 - else - _savedomainconf DEPLOY_KEYHELP_DOMAIN_ID "$DEPLOY_KEYHELP_DOMAIN_ID" - fi - - # Optional DEPLOY_KEYHELP_ENFORCE_HTTPS - _getdeployconf DEPLOY_KEYHELP_ENFORCE_HTTPS - # set default values for DEPLOY_KEYHELP_ENFORCE_HTTPS - [ -n "${DEPLOY_KEYHELP_ENFORCE_HTTPS}" ] || DEPLOY_KEYHELP_ENFORCE_HTTPS="1" - - _info "Logging in to keyhelp panel" - username_encoded="$(printf "%s" "${DEPLOY_KEYHELP_USERNAME}" | _url_encode)" - password_encoded="$(printf "%s" "${DEPLOY_KEYHELP_PASSWORD}" | _url_encode)" - _H1="Content-Type: application/x-www-form-urlencoded" - _response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?submit=1&username=$username_encoded&password=$password_encoded" "TRUE") - _cookie="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2)" - - # If cookies is not empty then logon successful - if [ -z "$_cookie" ]; then - _err "Fail to get cookie." - return 1 - fi - _debug "cookie" "$_cookie" - - _info "Uploading certificate" - _date=$(date +"%Y%m%d") - encoded_key="$(_url_encode <"$_ckey")" - encoded_ccert="$(_url_encode <"$_ccert")" - encoded_cca="$(_url_encode <"$_cca")" - certificate_name="$_cdomain-$_date" - - _request_body="submit=1&certificate_name=$certificate_name&add_type=upload&text_private_key=$encoded_key&text_certificate=$encoded_ccert&text_ca_certificate=$encoded_cca" - _H1="Cookie: $_cookie" - _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=ssl_certificates&action=add" "" "POST") - _message=$(echo "$_response" | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') - _info "_message" "$_message" - if [ -z "$_message" ]; then - _err "Fail to upload certificate." - return 1 - fi - - for DOMAIN_ID in $DEPLOY_KEYHELP_DOMAIN_ID; do - _info "Apply certificate to domain id $DOMAIN_ID" - _response=$(_get "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit&id=$DOMAIN_ID") - cert_value=$(echo "$_response" | grep "$certificate_name" | sed -n 's/.*value="\([^"]*\).*/\1/p') - target_type=$(echo "$_response" | grep 'target_type' | grep 'checked' | sed -n 's/.*value="\([^"]*\).*/\1/p') - if [ "$target_type" = "directory" ]; then - path=$(echo "$_response" | awk '/name="path"/{getline; print}' | sed -n 's/.*value="\([^"]*\).*/\1/p') - fi - echo "$_response" | grep "is_prefer_https" | grep "checked" >/dev/null - if [ $? -eq 0 ]; then - is_prefer_https=1 - else - is_prefer_https=0 - fi - echo "$_response" | grep "hsts_enabled" | grep "checked" >/dev/null - if [ $? -eq 0 ]; then - hsts_enabled=1 - else - hsts_enabled=0 - fi - _debug "cert_value" "$cert_value" - if [ -z "$cert_value" ]; then - _err "Fail to get certificate id." - return 1 - fi - - _request_body="submit=1&id=$DOMAIN_ID&target_type=$target_type&path=$path&is_prefer_https=$is_prefer_https&hsts_enabled=$hsts_enabled&certificate_type=custom&certificate_id=$cert_value&enforce_https=$DEPLOY_KEYHELP_ENFORCE_HTTPS" - _response=$(_post "$_request_body" "$DEPLOY_KEYHELP_BASEURL/index.php?page=domains&action=edit" "" "POST") - _message=$(echo "$_response" | sed -n '/
/,/<\/div>/{//!p;}' | sed 's/<[^>]*>//g' | sed 's/^ *//;s/ *$//') - _info "_message" "$_message" - if [ -z "$_message" ]; then - _err "Fail to apply certificate." - return 1 - fi - done - - _info "Domain $_cdomain certificate successfully deployed to KeyHelp Domain ID $DEPLOY_KEYHELP_DOMAIN_ID." - return 0 -} diff --git a/deploy/keyhelp_api.sh b/deploy/keyhelp_api.sh deleted file mode 100644 index 75e9d951..00000000 --- a/deploy/keyhelp_api.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env sh - -keyhelp_api_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - - # Read config from saved values or env - _getdeployconf DEPLOY_KEYHELP_HOST - _getdeployconf DEPLOY_KEYHELP_API_KEY - - _debug DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST" - _secure_debug DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY" - - if [ -z "$DEPLOY_KEYHELP_HOST" ]; then - _err "KeyHelp host not found, please define DEPLOY_KEYHELP_HOST." - return 1 - fi - if [ -z "$DEPLOY_KEYHELP_API_KEY" ]; then - _err "KeyHelp api key not found, please define DEPLOY_KEYHELP_API_KEY." - return 1 - fi - - # Save current values - _savedeployconf DEPLOY_KEYHELP_HOST "$DEPLOY_KEYHELP_HOST" - _savedeployconf DEPLOY_KEYHELP_API_KEY "$DEPLOY_KEYHELP_API_KEY" - - _request_key="$(tr '\n' ':' <"$_ckey" | sed 's/:/\\n/g')" - _request_cert="$(tr '\n' ':' <"$_ccert" | sed 's/:/\\n/g')" - _request_ca="$(tr '\n' ':' <"$_cca" | sed 's/:/\\n/g')" - - _request_body="{ - \"name\": \"$_cdomain\", - \"components\": { - \"private_key\": \"$_request_key\", - \"certificate\": \"$_request_cert\", - \"ca_certificate\": \"$_request_ca\" - } - }" - - _hosts="$(echo "$DEPLOY_KEYHELP_HOST" | tr "," " ")" - _keys="$(echo "$DEPLOY_KEYHELP_API_KEY" | tr "," " ")" - _i=1 - - for _host in $_hosts; do - _key="$(_getfield "$_keys" "$_i" " ")" - _i="$(_math "$_i" + 1)" - - export _H1="X-API-Key: $_key" - - _put_url="$_host/api/v2/certificates/name/$_cdomain" - if _post "$_request_body" "$_put_url" "" "PUT" "application/json" >/dev/null; then - _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" - else - _err "Cannot make PUT request to $_put_url" - return 1 - fi - - if [ "$_code" = "404" ]; then - _info "$_cdomain not found, creating new entry at $_host" - - _post_url="$_host/api/v2/certificates" - if _post "$_request_body" "$_post_url" "" "POST" "application/json" >/dev/null; then - _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" - else - _err "Cannot make POST request to $_post_url" - return 1 - fi - fi - - if _startswith "$_code" "2"; then - _info "$_cdomain set at $_host" - else - _err "HTTP status code is $_code" - return 1 - fi - done - - return 0 -} diff --git a/deploy/localcopy.sh b/deploy/localcopy.sh deleted file mode 100644 index 9a1a0fcf..00000000 --- a/deploy/localcopy.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env sh - -# Deploy-hook to very simply copy files to set directories and then -# execute whatever reloadcmd the admin needs afterwards. This can be -# useful for configurations where the "multideploy" hook (in development) -# is used or when an admin wants ACME.SH to renew certs but needs to -# manually configure deployment via an external script -# (e.g. The deploy-freenas script for TrueNAS Core/Scale -# https://github.com/danb35/deploy-freenas/ ) -# -# If the same file is configured for the certificate key -# and the certificate and/or full chain, a combined PEM file will -# be output instead. -# -# Environment variables to be utilized are as follows: -# -# DEPLOY_LOCALCOPY_CERTKEY - /path/to/target/cert.key -# DEPLOY_LOCALCOPY_CERTIFICATE - /path/to/target/cert.cer -# DEPLOY_LOCALCOPY_FULLCHAIN - /path/to/target/fullchain.cer -# DEPLOY_LOCALCOPY_CA - /path/to/target/ca.cer -# DEPLOY_LOCALCOPY_PFX - /path/to/target/cert.pfx -# DEPLOY_LOCALCOPY_RELOADCMD - "echo 'this is my cmd'" - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -localcopy_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - _cpfx="$6" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - _debug _cpfx "$_cpfx" - - _getdeployconf DEPLOY_LOCALCOPY_CERTIFICATE - _getdeployconf DEPLOY_LOCALCOPY_CERTKEY - _getdeployconf DEPLOY_LOCALCOPY_FULLCHAIN - _getdeployconf DEPLOY_LOCALCOPY_CA - _getdeployconf DEPLOY_LOCALCOPY_RELOADCMD - _getdeployconf DEPLOY_LOCALCOPY_PFX - _combined_target="" - _combined_srccert="" - - # Create PEM file - if [ "$DEPLOY_LOCALCOPY_CERTKEY" ] && - { [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ] || - [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; }; then - - _combined_target="$DEPLOY_LOCALCOPY_CERTKEY" - _savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY" - if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then - _combined_srccert="$_ccert" - _savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE" - DEPLOY_LOCALCOPY_CERTIFICATE="" - fi - if [ "$DEPLOY_LOCALCOPY_CERTKEY" = "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then - _combined_srccert="$_cfullchain" - _savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN" - DEPLOY_LOCALCOPY_FULLCHAIN="" - fi - DEPLOY_LOCALCOPY_CERTKEY="" - _info "Creating combined PEM" - _debug "Creating combined PEM at $_combined_target" - if ! [ -f "$_combined_target" ]; then - touch "$_combined_target" || return 1 - chmod 600 "$_combined_target" - fi - if ! cat "$_combined_srccert" "$_ckey" >"$_combined_target"; then - _err "Failed to create PEM file" - return 1 - fi - fi - - if [ "$DEPLOY_LOCALCOPY_CERTIFICATE" ]; then - _info "Copying certificate" - _debug "Copying $_ccert to $DEPLOY_LOCALCOPY_CERTIFICATE" - if ! cat "$_ccert" >"$DEPLOY_LOCALCOPY_CERTIFICATE"; then - _err "Failed to copy certificate, aborting." - return 1 - fi - _savedeployconf DEPLOY_LOCALCOPY_CERTIFICATE "$DEPLOY_LOCALCOPY_CERTIFICATE" - fi - - if [ "$DEPLOY_LOCALCOPY_CERTKEY" ]; then - _info "Copying certificate key" - _debug "Copying $_ckey to $DEPLOY_LOCALCOPY_CERTKEY" - if ! [ -f "$DEPLOY_LOCALCOPY_CERTKEY" ]; then - touch "$DEPLOY_LOCALCOPY_CERTKEY" || return 1 - chmod 600 "$DEPLOY_LOCALCOPY_CERTKEY" - fi - if ! cat "$_ckey" >"$DEPLOY_LOCALCOPY_CERTKEY"; then - _err "Failed to copy certificate key, aborting." - return 1 - fi - _savedeployconf DEPLOY_LOCALCOPY_CERTKEY "$DEPLOY_LOCALCOPY_CERTKEY" - fi - - if [ "$DEPLOY_LOCALCOPY_FULLCHAIN" ]; then - _info "Copying fullchain" - _debug "Copying $_cfullchain to $DEPLOY_LOCALCOPY_FULLCHAIN" - if ! cat "$_cfullchain" >"$DEPLOY_LOCALCOPY_FULLCHAIN"; then - _err "Failed to copy fullchain, aborting." - return 1 - fi - _savedeployconf DEPLOY_LOCALCOPY_FULLCHAIN "$DEPLOY_LOCALCOPY_FULLCHAIN" - fi - - if [ "$DEPLOY_LOCALCOPY_CA" ]; then - _info "Copying CA" - _debug "Copying $_cca to $DEPLOY_LOCALCOPY_CA" - if ! cat "$_cca" >"$DEPLOY_LOCALCOPY_CA"; then - _err "Failed to copy CA, aborting." - return 1 - fi - _savedeployconf DEPLOY_LOCALCOPY_CA "$DEPLOY_LOCALCOPY_CA" - fi - - if [ "$DEPLOY_LOCALCOPY_PFX" ]; then - _info "Copying PFX" - _debug "Copying $_cpfx to $DEPLOY_LOCALCOPY_PFX" - if ! [ -f "$DEPLOY_LOCALCOPY_PFX" ]; then - touch "$DEPLOY_LOCALCOPY_PFX" || return 1 - chmod 600 "$DEPLOY_LOCALCOPY_PFX" - fi - if ! cat "$_cpfx" >"$DEPLOY_LOCALCOPY_PFX"; then - _err "Failed to copy PFX, aborting." - return 1 - fi - _savedeployconf DEPLOY_LOCALCOPY_PFX "$DEPLOY_LOCALCOPY_PFX" - fi - - _reload=$DEPLOY_LOCALCOPY_RELOADCMD - _debug "Running reloadcmd $_reload" - - if [ -z "$_reload" ]; then - _info "Reloadcmd not provided, skipping." - else - _info "Reloading" - if eval "$_reload"; then - _info "Reload successful." - _savedeployconf DEPLOY_LOCALCOPY_RELOADCMD "$DEPLOY_LOCALCOPY_RELOADCMD" "base64" - else - _err "Reload failed." - return 1 - fi - fi - - _info "$(__green "'localcopy' deploy success")" - return 0 -} diff --git a/deploy/multideploy.sh b/deploy/multideploy.sh deleted file mode 100644 index 4a8c9dc9..00000000 --- a/deploy/multideploy.sh +++ /dev/null @@ -1,287 +0,0 @@ -#!/usr/bin/env sh - -################################################################################ -# ACME.sh 3rd party deploy plugin for multiple (same) services -################################################################################ -# Authors: tomo2403 (creator), https://github.com/tomo2403 -# Updated: 2025-03-01 -# Issues: https://github.com/acmesh-official/acme.sh/issues and mention @tomo2403 -################################################################################ -# Usage (shown values are the examples): -# 1. Set optional environment variables -# - export MULTIDEPLOY_FILENAME="multideploy.yaml" - "multideploy.yml" will be automatically used if not set" -# A name without a leading '/' is looked up in the certificate directory -# of the domain. An absolute path is used as is, so a single deploy file -# can be shared by all domains, e.g. -# - export MULTIDEPLOY_FILENAME="/etc/acme/multideploy.yml" -# -# 2. Run command: -# acme.sh --deploy --deploy-hook multideploy -d example.com -################################################################################ -# Dependencies: -# - yq -################################################################################ -# Return value: -# 0 means success, otherwise error. -################################################################################ - -MULTIDEPLOY_VERSION="1.0" - -# Description: This function handles the deployment of certificates to multiple services. -# It processes the provided certificate files and deploys them according to the -# configuration specified in the multideploy file. -# -# Parameters: -# _cdomain - The domain name for which the certificate is issued. -# _ckey - The private key file for the certificate. -# _ccert - The certificate file. -# _cca - The CA (Certificate Authority) file. -# _cfullchain - The full chain certificate file. -# _cpfx - The PFX (Personal Information Exchange) file. -multideploy_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - _cpfx="$6" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - _debug _cpfx "$_cpfx" - - _getdeployconf MULTIDEPLOY_FILENAME - if [ -z "$MULTIDEPLOY_FILENAME" ]; then - MULTIDEPLOY_FILENAME="multideploy.yml" - _info "MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'." - else - _savedeployconf "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME" - _debug2 "MULTIDEPLOY_FILENAME" "$MULTIDEPLOY_FILENAME" - fi - - if ! file=$(_preprocess_deployfile "$MULTIDEPLOY_FILENAME"); then - _err "Failed to preprocess deploy file." - return 1 - fi - _debug3 "File" "$file" - - # Deploy to services - _deploy_services "$file" - _exitCode="$?" - - return "$_exitCode" -} - -# Description: -# This function preprocesses the deploy file by checking if 'yq' is installed, -# verifying the existence of the deploy file, and ensuring only one deploy file is present. -# Arguments: -# $@ - Posible deploy file names. A name starting with '/' is treated as an -# absolute path, any other name is relative to the domain directory. -# Usage: -# _preprocess_deployfile "" "?" -_preprocess_deployfile() { - # Check if yq is installed - if ! command -v yq >/dev/null 2>&1; then - _err "yq is not installed! Please install yq and try again." - return 1 - fi - _debug3 "yq is installed." - - # Check if deploy file exists - found_file="" - for file in "$@"; do - if _startswith "$file" "/"; then - _multideploy_path="$file" - else - _multideploy_path="$DOMAIN_PATH/$file" - fi - _debug3 "Checking file" "$_multideploy_path" - if [ -f "$_multideploy_path" ]; then - _debug3 "File found" - if [ -n "$found_file" ]; then - _err "Multiple deploy files found. Please keep only one deploy file." - return 1 - fi - found_file="$_multideploy_path" - else - _debug3 "File not found" - fi - done - - if [ -z "$found_file" ]; then - _err "Deploy file not found. Go to https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks to see how to create one." - return 1 - fi - if ! _check_deployfile "$found_file"; then - _err "Deploy file is not valid: $found_file" - return 1 - fi - - echo "$found_file" -} - -# Description: -# This function checks the deploy file for version compatibility and the existence of the specified configuration and services. -# Arguments: -# $1 - The path to the deploy configuration file. -# $2 - The name of the deploy configuration to use. -# Usage: -# _check_deployfile "" -_check_deployfile() { - _deploy_file="$1" - _debug2 "check: Deploy file" "$_deploy_file" - - # Check version - _deploy_file_version=$(yq -r '.version' "$_deploy_file") - if [ "$MULTIDEPLOY_VERSION" != "$_deploy_file_version" ]; then - _err "As of $PROJECT_NAME $VER, the deploy file needs version $MULTIDEPLOY_VERSION! Your current deploy file is of version $_deploy_file_version." - return 1 - fi - _debug2 "check: Deploy file version is compatible: $_deploy_file_version" - - # Extract all services from config - _services=$(yq -r '.services[].name' "$_deploy_file") - - if [ -z "$_services" ]; then - _err "Config does not have any services to deploy to." - return 1 - fi - _debug2 "check: Config has services." - echo "$_services" | while read -r _service; do - _debug3 " - $_service" - done - - # Check if extracted services exist in services list - echo "$_services" | while read -r _service; do - _debug2 "check: Checking service: $_service" - # Check if service exists - _service_config=$(yq -r ".services[] | select(.name == \"$_service\")" "$_deploy_file") - if [ -z "$_service_config" ] || [ "$_service_config" = "null" ]; then - _err "Service '$_service' not found." - return 1 - fi - - _service_hook=$(echo "$_service_config" | yq -r ".hook" -) - if [ -z "$_service_hook" ] || [ "$_service_hook" = "null" ]; then - _err "Service '$_service' does not have a hook." - return 1 - fi - - _service_environment=$(echo "$_service_config" | yq -r ".environment" -) - if [ -z "$_service_environment" ] || [ "$_service_environment" = "null" ]; then - _err "Service '$_service' does not have an environment." - return 1 - fi - done -} - -# Description: This function takes a list of environment variables in YAML format, -# parses them, and exports each key-value pair as environment variables. -# Arguments: -# $1 - A string containing the list of environment variables in YAML format. -# Usage: -# _export_envs "$env_list" -_export_envs() { - _env_list="$1" - - _secure_debug3 "Exporting envs" "$_env_list" - - echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value' | while IFS='=' read -r _key _value; do - # Using eval to expand nested variables in the configuration file - _value=$(eval 'echo "'"$_value"'"') - _savedeployconf "$_key" "$_value" - _secure_debug3 "Saved $_key" "$_value" - done -} - -# Description: -# This function takes a YAML formatted string of environment variables, parses it, -# and clears each environment variable. It logs the process of clearing each variable. -# -# Note: Environment variables for a hook may be optional and differ between -# services using the same hook. -# If one service sets optional environment variables and another does not, the -# variables may persist and affect subsequent deployments. -# Clearing these variables after each service ensures that only the -# environment variables explicitly specified for each service in the deploy -# file are used. -# Arguments: -# $1 - A YAML formatted string containing environment variable key-value pairs. -# Usage: -# _clear_envs "" -_clear_envs() { - _env_list="$1" - - _secure_debug3 "Clearing envs" "$_env_list" - env_pairs=$(echo "$_env_list" | yq -r 'to_entries | .[] | .key + "=" + .value') - - echo "$env_pairs" | while IFS='=' read -r _key _value; do - _debug3 "Deleting key" "$_key" - _cleardeployconf "$_key" - unset -v "$_key" - done -} - -# Description: -# This function deploys services listed in the deploy configuration file. -# Arguments: -# $1 - The path to the deploy configuration file. -# $2 - The list of services to deploy. -# Usage: -# _deploy_services "" "" -_deploy_services() { - _deploy_file="$1" - _debug3 "Deploy file" "$_deploy_file" - - _tempfile=$(mktemp) - trap 'rm -f $_tempfile' EXIT - - yq -r '.services[].name' "$_deploy_file" >"$_tempfile" - _debug3 "Services" "$(cat "$_tempfile")" - - _failedServices="" - _failedCount=0 - while read -r _service <&3; do - _debug2 "Service" "$_service" - _hook=$(yq -r ".services[] | select(.name == \"$_service\").hook" "$_deploy_file") - _envs=$(yq -r ".services[] | select(.name == \"$_service\").environment" "$_deploy_file") - - _export_envs "$_envs" - if ! _deploy_service "$_service" "$_hook"; then - _failedServices="$_service, $_failedServices" - _failedCount=$((_failedCount + 1)) - fi - _clear_envs "$_envs" - done 3<"$_tempfile" - - _debug3 "Failed services" "$_failedServices" - _debug2 "Failed count" "$_failedCount" - if [ -n "$_failedServices" ]; then - _info "$(__red "Deployment failed") for services: $_failedServices" - else - _debug "All services deployed successfully." - fi - - return "$_failedCount" -} - -# Description: Deploys a service using the specified hook. -# Arguments: -# $1 - The name of the service to deploy. -# $2 - The hook to use for deployment. -# Usage: -# _deploy_service -_deploy_service() { - _name="$1" - _hook="$2" - - _debug2 "SERVICE" "$_name" - _debug2 "HOOK" "$_hook" - - _info "$(__green "Deploying") to '$_name' using '$_hook'" - _deploy "$_cdomain" "$_hook" -} diff --git a/deploy/mydevil.sh b/deploy/mydevil.sh index 8954f822..bd9868aa 100755 --- a/deploy/mydevil.sh +++ b/deploy/mydevil.sh @@ -54,8 +54,6 @@ mydevil_deploy() { # Usage: ip=$(mydevil_get_ip domain.com) # echo $ip mydevil_get_ip() { - # tr squeezes runs of blanks into one tab so plain cut works everywhere; - # cut -w is BSD-only and unknown to GNU coreutils - devil dns list "$1" | tr -s ' \t' '\t' | cut -s -f 3,7 | grep "^A$(printf '\t')" | cut -s -f 2 || return 1 + devil dns list "$1" | cut -w -s -f 3,7 | grep "^A$(printf '\t')" | cut -w -s -f 2 || return 1 return 0 } diff --git a/deploy/netlify.sh b/deploy/netlify.sh deleted file mode 100644 index 8d25f74c..00000000 --- a/deploy/netlify.sh +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env sh - -# Script to deploy certificate to Netlify -# https://docs.netlify.com/api/get-started/#authentication -# https://open-api.netlify.com/#tag/sniCertificate - -# This deployment required following variables -# export Netlify_ACCESS_TOKEN="Your Netlify Access Token" -# export Netlify_SITE_ID="Your Netlify Site ID" - -# If have more than one SITE ID -# export Netlify_SITE_ID="SITE_ID_1 SITE_ID_2" - -# returns 0 means success, otherwise error. - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -netlify_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - if [ -z "$Netlify_ACCESS_TOKEN" ]; then - _err "Netlify_ACCESS_TOKEN is not defined." - return 1 - else - _savedomainconf Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN" - fi - if [ -z "$Netlify_SITE_ID" ]; then - _err "Netlify_SITE_ID is not defined." - return 1 - else - _savedomainconf Netlify_SITE_ID "$Netlify_SITE_ID" - fi - - _info "Deploying certificate to Netlify..." - - ## upload certificate - string_ccert=$(sed 's/$/\\n/' "$_ccert" | tr -d '\n') - string_cca=$(sed 's/$/\\n/' "$_cca" | tr -d '\n') - string_key=$(sed 's/$/\\n/' "$_ckey" | tr -d '\n') - - for SITE_ID in $Netlify_SITE_ID; do - _request_body="{\"certificate\":\"$string_ccert\",\"key\":\"$string_key\",\"ca_certificates\":\"$string_cca\"}" - _debug _request_body "$_request_body" - _debug Netlify_ACCESS_TOKEN "$Netlify_ACCESS_TOKEN" - export _H1="Authorization: Bearer $Netlify_ACCESS_TOKEN" - _response=$(_post "$_request_body" "https://api.netlify.com/api/v1/sites/$SITE_ID/ssl" "" "POST" "application/json") - - if _contains "$_response" "\"error\""; then - _err "Error in deploying $_cdomain certificate to Netlify SITE_ID $SITE_ID." - _err "$_response" - return 1 - fi - _debug response "$_response" - _info "Domain $_cdomain certificate successfully deployed to Netlify SITE_ID $SITE_ID." - done - - return 0 -} diff --git a/deploy/panos.sh b/deploy/panos.sh index fcfd6fb5..ef622ded 100644 --- a/deploy/panos.sh +++ b/deploy/panos.sh @@ -7,27 +7,13 @@ # # Firewall admin with superuser and IP address is required. # -# REQUIRED: -# export PANOS_HOST="" -# export PANOS_USER="" #User *MUST* have Commit and Import Permissions in XML API for Admin Role -# export PANOS_PASS="" -# -# OPTIONAL -# export PANOS_TEMPLATE="" # Template Name of panorama managed devices -# export PANOS_TEMPLATE_STACK="" # set a Template Stack if certificate should also be pushed automatically -# export PANOS_VSYS="Shared" # name of the vsys to import the certificate -# export PANOS_CERTNAME="" # use a custom certificate name to work around Panorama's 31-character limit -# -# The script will automatically generate a new API key if -# no key is found, or if a saved key has expired or is invalid. +# export PANOS_USER="" # required +# export PANOS_PASS="" # required +# export PANOS_HOST="" # required -_COMMIT_WAIT_INTERVAL=30 # query commit status every 30 seconds -_COMMIT_WAIT_ITERATIONS=20 # query commit status 20 times (20*30 = 600 seconds = 10 minutes) - -# This function is to parse the XML response from the firewall +# This function is to parse the XML parse_response() { type=$2 - _debug "API Response: $1" if [ "$type" = 'keygen' ]; then status=$(echo "$1" | sed 's/^.*\(['\'']\)\([a-z]*\)'\''.*/\2/g') if [ "$status" = "success" ]; then @@ -37,52 +23,25 @@ parse_response() { message="PAN-OS Key could not be set." fi else - if [ "$type" = 'commit' ]; then - job_id=$(echo "$1" | sed 's/^.*\(\)\(.*\)<\/job>.*/\2/g') - _commit_job_id=$job_id - elif [ "$type" = 'job_status' ]; then - job_status=$(echo "$1" | tr -d '\n' | sed 's/^.*\([^<]*\)<\/result>.*/\1/g') - _commit_job_status=$job_status - fi - status=$(echo "$1" | tr -d '\n' | sed 's/^.*"\([a-z]*\)".*/\1/g') - message=$(echo "$1" | tr -d '\n' | sed 's/.*\(\|\|\)\([^<]*\).*/\2/g') - _debug "Firewall message: $message" - if [ "$type" = 'keytest' ] && [ "$status" != "success" ]; then - _debug "**** API Key has EXPIRED or is INVALID ****" - unset _panos_key - fi + status=$(echo "$1" | sed 's/^.*"\([a-z]*\)".*/\1/g') + message=$(echo "$1" | sed 's/^.*\(.*\)<\/result.*/\1/g') fi return 0 } -#This function is used to deploy to the firewall deployer() { content="" - type=$1 # Types are keytest, keygen, cert, key, commit, job_status, push + type=$1 # Types are keygen, cert, key, commit + _debug "**** Deploying $type *****" panos_url="https://$_panos_host/api/" - export _H1="Content-Type: application/x-www-form-urlencoded" - - #Test API Key by performing a lookup - if [ "$type" = 'keytest' ]; then - _debug "**** Testing saved API Key ****" - # Get Version Info to test key - content="type=version&key=$_panos_key" - ## Exclude all scopes for the empty commit - #_exclude_scope="excludedexcluded" - #content="type=commit&action=partial&key=$_panos_key&cmd=$_exclude_scope$_panos_user" - fi - - # Generate API Key if [ "$type" = 'keygen' ]; then - _debug "**** Generating new API Key ****" + _H1="Content-Type: application/x-www-form-urlencoded" content="type=keygen&user=$_panos_user&password=$_panos_pass" # content="$content${nl}--$delim${nl}Content-Disposition: form-data; type=\"keygen\"; user=\"$_panos_user\"; password=\"$_panos_pass\"${nl}Content-Type: application/octet-stream${nl}${nl}" fi - # Deploy Cert or Key if [ "$type" = 'cert' ] || [ "$type" = 'key' ]; then - _debug "**** Deploying $type ****" - #Generate DELIM + #Generate DEIM delim="-----MultipartDelimiter$(date "+%s%N")" nl="\015\012" #Set Header @@ -90,31 +49,19 @@ deployer() { if [ "$type" = 'cert' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\ncertificate" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" - if [ "$_panos_template" ]; then - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" - fi - if [ "$_panos_vsys" ]; then - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" - fi fi if [ "$type" = 'key' ]; then panos_url="${panos_url}?type=import" content="--$delim${nl}Content-Disposition: form-data; name=\"category\"\r\n\r\nprivate-key" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_panos_certname" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"certificate-name\"\r\n\r\n$_cdomain" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"\r\n\r\n$_panos_key" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"format\"\r\n\r\npem" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"passphrase\"\r\n\r\n123456" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_panos_certname.key")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" - if [ "$_panos_template" ]; then - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl\"\r\n\r\n$_panos_template" - fi - if [ "$_panos_vsys" ]; then - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"target-tpl-vsys\"\r\n\r\n$_panos_vsys" - fi + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"file\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" fi #Close multipart content="$content${nl}--$delim--${nl}${nl}" @@ -122,43 +69,20 @@ deployer() { content=$(printf %b "$content") fi - # Commit changes if [ "$type" = 'commit' ]; then - _debug "**** Committing changes ****" - #Check for force commit - will commit ALL uncommited changes to the firewall. Use with caution! - if [ "$FORCE" ]; then - _debug "Force switch detected. Committing ALL changes to the firewall." - cmd=$(printf "%s" "$_panos_user" | _url_encode) - else - cmd=$(printf "%s" "$_panos_user" | _url_encode) - fi - content="type=commit&action=partial&key=$_panos_key&cmd=$cmd" + export _H1="Content-Type: application/x-www-form-urlencoded" + cmd=$(printf "%s" "<$_panos_user>" | _url_encode) + content="type=commit&key=$_panos_key&cmd=$cmd" fi - - # Query job status - if [ "$type" = 'job_status' ]; then - echo "**** Querying job $_commit_job_id status ****" - cmd=$(printf "%s" "$_commit_job_id" | _url_encode) - content="type=op&key=$_panos_key&cmd=$cmd" - fi - - # Push changes - if [ "$type" = 'push' ]; then - echo "**** Pushing changes ****" - cmd=$(printf "%s" "$_panos_template_stack$_panos_user" | _url_encode) - content="type=commit&action=all&key=$_panos_key&cmd=$cmd" - fi - response=$(_post "$content" "$panos_url" "" "POST") parse_response "$response" "$type" # Saving response to variables response_status=$status + #DEBUG _debug response_status "$response_status" if [ "$response_status" = "success" ]; then _debug "Successfully deployed $type" return 0 - elif [ "$_commit_job_status" ]; then - _debug "Commit Job Status = $_commit_job_status" else _err "Deploy of type $type failed. Try deploying with --debug to troubleshoot." _debug "$message" @@ -168,162 +92,48 @@ deployer() { # This is the main function that will call the other functions to deploy everything. panos_deploy() { - _cdomain=$(echo "$1" | sed 's/*/WILDCARD_/g') #Wildcard Safe Filename + _cdomain="$1" _ckey="$2" _cfullchain="$5" - - # VALID FILE CHECK - if [ ! -f "$_ckey" ] || [ ! -f "$_cfullchain" ]; then - _err "Unable to find a valid key and/or cert. If this is an ECDSA/ECC cert, use the --ecc flag when deploying." - return 1 - fi - - # PANOS_HOST - if [ "$PANOS_HOST" ]; then - _debug "Detected ENV variable PANOS_HOST. Saving to file." - _savedeployconf PANOS_HOST "$PANOS_HOST" 1 - else - _debug "Attempting to load variable PANOS_HOST from file." - _getdeployconf PANOS_HOST - fi - - # PANOS USER - if [ "$PANOS_USER" ]; then - _debug "Detected ENV variable PANOS_USER. Saving to file." - _savedeployconf PANOS_USER "$PANOS_USER" 1 - else - _debug "Attempting to load variable PANOS_USER from file." + # PANOS ENV VAR check + if [ -z "$PANOS_USER" ] || [ -z "$PANOS_PASS" ] || [ -z "$PANOS_HOST" ]; then + _debug "No ENV variables found lets check for saved variables" _getdeployconf PANOS_USER - fi - - # PANOS_PASS - if [ "$PANOS_PASS" ]; then - _debug "Detected ENV variable PANOS_PASS. Saving to file." - _savedeployconf PANOS_PASS "$PANOS_PASS" 1 - else - _debug "Attempting to load variable PANOS_PASS from file." _getdeployconf PANOS_PASS - fi - - # PANOS_KEY - if [ "$PANOS_KEY" ]; then - _debug "Detected ENV variable PANOS_KEY. Saving to file." - _savedeployconf PANOS_KEY "$PANOS_KEY" 1 - else - _debug "Attempting to load variable PANOS_KEY from file." - _getdeployconf PANOS_KEY - fi - - # PANOS_TEMPLATE - if [ "$PANOS_TEMPLATE" ]; then - _debug "Detected ENV variable PANOS_TEMPLATE. Saving to file." - _savedeployconf PANOS_TEMPLATE "$PANOS_TEMPLATE" 1 - else - _debug "Attempting to load variable PANOS_TEMPLATE from file." - _getdeployconf PANOS_TEMPLATE - fi - - # PANOS_TEMPLATE_STACK - if [ "$PANOS_TEMPLATE_STACK" ]; then - _debug "Detected ENV variable PANOS_TEMPLATE_STACK. Saving to file." - _savedeployconf PANOS_TEMPLATE_STACK "$PANOS_TEMPLATE_STACK" 1 - else - _debug "Attempting to load variable PANOS_TEMPLATE_STACK from file." - _getdeployconf PANOS_TEMPLATE_STACK - fi - - # PANOS_TEMPLATE_STACK - if [ "$PANOS_VSYS" ]; then - _debug "Detected ENV variable PANOS_VSYS. Saving to file." - _savedeployconf PANOS_VSYS "$PANOS_VSYS" 1 - else - _debug "Attempting to load variable PANOS_VSYS from file." - _getdeployconf PANOS_VSYS - fi - - # PANOS_CERTNAME - if [ "$PANOS_CERTNAME" ]; then - _debug "Detected ENV variable PANOS_CERTNAME. Saving to file." - _savedeployconf PANOS_CERTNAME "$PANOS_CERTNAME" 1 - else - _debug "Attempting to load variable PANOS_CERTNAME from file." - _getdeployconf PANOS_CERTNAME - fi - - #Store variables - _panos_host=$PANOS_HOST - _panos_user=$PANOS_USER - _panos_pass=$PANOS_PASS - _panos_key=$PANOS_KEY - _panos_template=$PANOS_TEMPLATE - _panos_template_stack=$PANOS_TEMPLATE_STACK - _panos_vsys=$PANOS_VSYS - _panos_certname=$PANOS_CERTNAME - - #Test API Key if found. If the key is invalid, the variable _panos_key will be unset. - if [ "$_panos_host" ] && [ "$_panos_key" ]; then - _debug "**** Testing API KEY ****" - deployer keytest - fi - - # Check for valid variables - if [ -z "$_panos_host" ]; then - _err "No host found. If this is your first time deploying, please set PANOS_HOST in ENV variables. You can delete it after you have successfully deployed the certs." - return 1 - else - # Use certificate name based on the first domain on the certificate if no custom certificate name is set - if [ -z "$_panos_certname" ]; then - _panos_certname="$_cdomain" - _savedeployconf PANOS_CERTNAME "$_panos_certname" 1 - fi - - # Generate a new API key if no valid API key is found - if [ -z "$_panos_key" ]; then - if [ -z "$_panos_user" ]; then - _err "No user found. If this is your first time deploying, please set PANOS_USER in ENV variables. You can delete it after you have successfully deployed the certs." - return 1 - elif [ -z "$_panos_pass" ]; then - _err "No password found. If this is your first time deploying, please set PANOS_PASS in ENV variables. You can delete it after you have successfully deployed the certs." - return 1 - fi - _debug "**** Generating new PANOS API KEY ****" - deployer keygen - _savedeployconf PANOS_KEY "$_panos_key" 1 - fi - - # Confirm that a valid key was generated - if [ -z "$_panos_key" ]; then - _err "Unable to generate an API key. The user and pass may be invalid or not authorized to generate a new key. Please check the PANOS_USER and PANOS_PASS credentials and try again" + _getdeployconf PANOS_HOST + _panos_user=$PANOS_USER + _panos_pass=$PANOS_PASS + _panos_host=$PANOS_HOST + if [ -z "$_panos_user" ] && [ -z "$_panos_pass" ] && [ -z "$_panos_host" ]; then + _err "No host, user and pass found.. If this is the first time deploying please set PANOS_HOST, PANOS_USER and PANOS_PASS in environment variables. Delete them after you have succesfully deployed certs." return 1 else - # A commit of a failed import would leave a mismatched cert/key pair - # on the firewall and can lock the admin out of the management - # interface, see https://github.com/acmesh-official/acme.sh/issues/4716 - if ! deployer cert; then - _err "Cert import failed. Aborting without committing." - return 1 - fi - if ! deployer key; then - _err "Key import failed. Aborting without committing. Warning: the firewall now has an uncommitted mismatched cert/key pair in its candidate config." - return 1 - fi - if ! deployer commit; then - return 1 - fi - if [ "$_panos_template_stack" ]; then - # try to get job status for 20 times in 30 sec interval - i=0 - while [ "$i" -lt $_COMMIT_WAIT_ITERATIONS ]; do - deployer job_status - if [ "$_commit_job_status" = "OK" ]; then - echo "Commit finished!" - break - fi - sleep $_COMMIT_WAIT_INTERVAL - i=$((i + 1)) - done - deployer push - fi + _debug "Using saved env variables." + fi + else + _debug "Detected ENV variables to be saved to the deploy conf." + # Encrypt and save user + _savedeployconf PANOS_USER "$PANOS_USER" 1 + _savedeployconf PANOS_PASS "$PANOS_PASS" 1 + _savedeployconf PANOS_HOST "$PANOS_HOST" 1 + _panos_user="$PANOS_USER" + _panos_pass="$PANOS_PASS" + _panos_host="$PANOS_HOST" + fi + _debug "Let's use username and pass to generate token." + if [ -z "$_panos_user" ] || [ -z "$_panos_pass" ] || [ -z "$_panos_host" ]; then + _err "Please pass username and password and host as env variables PANOS_USER, PANOS_PASS and PANOS_HOST" + return 1 + else + _debug "Getting PANOS KEY" + deployer keygen + if [ -z "$_panos_key" ]; then + _err "Missing apikey." + return 1 + else + deployer cert + deployer key + deployer commit fi fi } diff --git a/deploy/proxmoxbs.sh b/deploy/proxmoxbs.sh deleted file mode 100644 index 30599a44..00000000 --- a/deploy/proxmoxbs.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env sh - -# Deploy certificates to a proxmox backup server using the API. -# -# Environment variables that can be set are: -# `DEPLOY_PROXMOXBS_SERVER`: The hostname of the proxmox backup server. Defaults to -# _cdomain. -# `DEPLOY_PROXMOXBS_SERVER_PORT`: The port number the management interface is on. -# Defaults to 8007. -# `DEPLOY_PROXMOXBS_USER`: The user we'll connect as. Defaults to root. -# `DEPLOY_PROXMOXBS_USER_REALM`: The authentication realm the user authenticates -# with. Defaults to pam. -# `DEPLOY_PROXMOXBS_API_TOKEN_NAME`: The name of the API token created for the -# user account. Defaults to acme. -# `DEPLOY_PROXMOXBS_API_TOKEN_KEY`: The API token. Required. - -proxmoxbs_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_PROXMOXBS_SERVER - if [ -z "$DEPLOY_PROXMOXBS_SERVER" ]; then - _target_hostname="$_cdomain" - else - _target_hostname="$DEPLOY_PROXMOXBS_SERVER" - _savedeployconf DEPLOY_PROXMOXBS_SERVER "$DEPLOY_PROXMOXBS_SERVER" - fi - _debug2 DEPLOY_PROXMOXBS_SERVER "$_target_hostname" - - _getdeployconf DEPLOY_PROXMOXBS_SERVER_PORT - if [ -z "$DEPLOY_PROXMOXBS_SERVER_PORT" ]; then - _target_port="8007" - else - _target_port="$DEPLOY_PROXMOXBS_SERVER_PORT" - _savedeployconf DEPLOY_PROXMOXBS_SERVER_PORT "$DEPLOY_PROXMOXBS_SERVER_PORT" - fi - _debug2 DEPLOY_PROXMOXBS_SERVER_PORT "$_target_port" - - # Complete URL. - _target_url="https://${_target_hostname}:${_target_port}/api2/json/nodes/localhost/certificates/custom" - _debug TARGET_URL "$_target_url" - - # More "sane" defaults. - _getdeployconf DEPLOY_PROXMOXBS_USER - if [ -z "$DEPLOY_PROXMOXBS_USER" ]; then - _proxmoxbs_user="root" - else - _proxmoxbs_user="$DEPLOY_PROXMOXBS_USER" - _savedeployconf DEPLOY_PROXMOXBS_USER "$DEPLOY_PROXMOXBS_USER" - fi - _debug2 DEPLOY_PROXMOXBS_USER "$_proxmoxbs_user" - - _getdeployconf DEPLOY_PROXMOXBS_USER_REALM - if [ -z "$DEPLOY_PROXMOXBS_USER_REALM" ]; then - _proxmoxbs_user_realm="pam" - else - _proxmoxbs_user_realm="$DEPLOY_PROXMOXBS_USER_REALM" - _savedeployconf DEPLOY_PROXMOXBS_USER_REALM "$DEPLOY_PROXMOXBS_USER_REALM" - fi - _debug2 DEPLOY_PROXMOXBS_USER_REALM "$_proxmoxbs_user_realm" - - _getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME - if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" ]; then - _proxmoxbs_api_token_name="acme" - else - _proxmoxbs_api_token_name="$DEPLOY_PROXMOXBS_API_TOKEN_NAME" - _savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_NAME "$DEPLOY_PROXMOXBS_API_TOKEN_NAME" - fi - _debug2 DEPLOY_PROXMOXBS_API_TOKEN_NAME "$_proxmoxbs_api_token_name" - - # This is required. - _getdeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY - if [ -z "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" ]; then - _err "API key not provided." - return 1 - else - _proxmoxbs_api_token_key="$DEPLOY_PROXMOXBS_API_TOKEN_KEY" - _savedeployconf DEPLOY_PROXMOXBS_API_TOKEN_KEY "$DEPLOY_PROXMOXBS_API_TOKEN_KEY" - fi - _debug2 DEPLOY_PROXMOXBS_API_TOKEN_KEY "$_proxmoxbs_api_token_key" - - # PBS API Token header value. Used in "Authorization: PBSAPIToken". - _proxmoxbs_header_api_token="${_proxmoxbs_user}@${_proxmoxbs_user_realm}!${_proxmoxbs_api_token_name}:${_proxmoxbs_api_token_key}" - _debug2 "Auth Header" "$_proxmoxbs_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 < -# -# ```sh -# acme.sh --deploy -d ruckus.example.com --deploy-hook ruckus -# ``` -# -# Then you need to set the environment variables for the -# deploy script to work. -# -# ```sh -# export RUCKUS_HOST=myruckus.example.com -# export RUCKUS_USER=myruckususername -# export RUCKUS_PASS=myruckuspassword -# -# acme.sh --deploy -d myruckus.example.com --deploy-hook ruckus -# ``` -# -# returns 0 means success, otherwise error. - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -ruckus_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - _err_code=0 - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - _getdeployconf RUCKUS_HOST - _getdeployconf RUCKUS_USER - _getdeployconf RUCKUS_PASS - - if [ -z "$RUCKUS_HOST" ]; then - _debug "Using _cdomain as RUCKUS_HOST, please set if not correct." - RUCKUS_HOST="$_cdomain" - fi - - if [ -z "$RUCKUS_USER" ]; then - _err "Need to set the env variable RUCKUS_USER" - return 1 - fi - - if [ -z "$RUCKUS_PASS" ]; then - _err "Need to set the env variable RUCKUS_PASS" - return 1 - fi - - _savedeployconf RUCKUS_HOST "$RUCKUS_HOST" - _savedeployconf RUCKUS_USER "$RUCKUS_USER" - _savedeployconf RUCKUS_PASS "$RUCKUS_PASS" - - _debug RUCKUS_HOST "$RUCKUS_HOST" - _debug RUCKUS_USER "$RUCKUS_USER" - _secure_debug RUCKUS_PASS "$RUCKUS_PASS" - - export ACME_HTTP_NO_REDIRECTS=1 - - _info "Discovering the login URL" - _get "https://$RUCKUS_HOST" >/dev/null - _login_url="$(_response_header 'Location')" - if [ -n "$_login_url" ]; then - _login_path=$(echo "$_login_url" | sed 's|https\?://[^/]\+||') - if [ -z "$_login_path" ]; then - # redirect was to a different host - _err "Connection failed: redirected to a different host. Configure Unleashed with a Preferred Master or Management Interface." - return 1 - fi - fi - - if [ -z "${_login_url}" ]; then - _err "Connection failed: couldn't find login page." - return 1 - fi - - _base_url=$(dirname "$_login_url") - _login_page=$(basename "$_login_url") - - if [ "$_login_page" = "index.html" ]; then - _err "Connection temporarily unavailable: Unleashed Rebuilding." - return 1 - fi - - if [ "$_login_page" = "wizard.jsp" ]; then - _err "Connection failed: Setup Wizard not complete." - return 1 - fi - - _info "Login" - _username_encoded="$(printf "%s" "$RUCKUS_USER" | _url_encode)" - _password_encoded="$(printf "%s" "$RUCKUS_PASS" | _url_encode)" - _login_query="$(printf "%s" "username=${_username_encoded}&password=${_password_encoded}&ok=Log+In")" - _post "$_login_query" "$_login_url" >/dev/null - - _login_code="$(_response_code)" - if [ "$_login_code" = "200" ]; then - _err "Login failed: incorrect credentials." - return 1 - fi - - _info "Collect Session Cookie" - _H1="Cookie: $(_response_cookie)" - export _H1 - _info "Collect CSRF Token" - _H2="X-CSRF-Token: $(_response_header 'HTTP_X_CSRF_TOKEN')" - export _H2 - - if _isRSA "$_ckey" >/dev/null 2>&1; then - _debug "Using RSA certificate." - else - _info "Verifying ECC certificate support." - - _ul_version="$(_get_unleashed_version)" - if [ -z "$_ul_version" ]; then - _err "Your controller doesn't support ECC certificates. Please deploy an RSA certificate." - return 1 - fi - - _ul_version_major="$(echo "$_ul_version" | cut -d . -f 1)" - _ul_version_minor="$(echo "$_ul_version" | cut -d . -f 2)" - if [ "$_ul_version_major" -lt "200" ]; then - _err "ZoneDirector doesn't support ECC certificates. Please deploy an RSA certificate." - return 1 - elif [ "$_ul_version_minor" -lt "13" ]; then - _err "Unleashed $_ul_version_major.$_ul_version_minor doesn't support ECC certificates. Please deploy an RSA certificate or upgrade to Unleashed 200.13+." - return 1 - fi - - _debug "ECC certificates OK for Unleashed $_ul_version_major.$_ul_version_minor." - fi - - _info "Uploading certificate" - _post_upload "uploadcert" "$_cfullchain" - - _info "Uploading private key" - _post_upload "uploadprivatekey" "$_ckey" - - _info "Replacing certificate" - _replace_cert_ajax='' - _post "$_replace_cert_ajax" "$_base_url/_cmdstat.jsp" >/dev/null - - _info "Rebooting" - _cert_reboot_ajax='' - _post "$_cert_reboot_ajax" "$_base_url/_cmdstat.jsp" >/dev/null - - return 0 -} - -_response_code() { - _egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n" | _egrep_o "^[0-9]*" -} - -_response_header() { - grep <"$HTTP_HEADER" -i "^$1:" | cut -d ':' -f 2- | tr -d "\r\n\t " -} - -_response_cookie() { - _response_header 'Set-Cookie' | sed 's/;.*//' -} - -_get_unleashed_version() { - _post '' "$_base_url/_cmdstat.jsp" | _egrep_o "version-num=\"[^\"]*\"" | cut -d '"' -f 2 -} - -_post_upload() { - _post_action="$1" - _post_file="$2" - - _post_boundary="----FormBoundary$(date "+%s%N")" - - _post_data="$({ - printf -- "--%s\r\n" "$_post_boundary" - printf -- "Content-Disposition: form-data; name=\"u\"; filename=\"%s\"\r\n" "$_post_action" - printf -- "Content-Type: application/octet-stream\r\n\r\n" - printf -- "%s\r\n" "$(cat "$_post_file")" - - printf -- "--%s\r\n" "$_post_boundary" - printf -- "Content-Disposition: form-data; name=\"action\"\r\n\r\n" - printf -- "%s\r\n" "$_post_action" - - printf -- "--%s\r\n" "$_post_boundary" - printf -- "Content-Disposition: form-data; name=\"callback\"\r\n\r\n" - printf -- "%s\r\n" "uploader_$_post_action" - - printf -- "--%s--\r\n\r\n" "$_post_boundary" - })" - - _post "$_post_data" "$_base_url/_upload.jsp?request_type=xhr" "" "" "multipart/form-data; boundary=$_post_boundary" >/dev/null -} diff --git a/deploy/shelly.sh b/deploy/shelly.sh deleted file mode 100644 index dbdab346..00000000 --- a/deploy/shelly.sh +++ /dev/null @@ -1,280 +0,0 @@ -#!/usr/bin/env sh - -# Here is a script to deploy cert to a Shelly Gen3+ device. -# Deploy the HTTPS server certificate to a Shelly device on the local network. -# -# ```sh -# export SHELLY_HOST=192.168.1.100 -# export SHELLY_PASSWORD=mysecret # only if auth is enabled on the device -# acme.sh --deploy -d shelly.example.com --deploy-hook shelly -# ``` -# -# Environment variables: -# SHELLY_HOST (required) IP or hostname of the Shelly device -# SHELLY_PASSWORD (optional) Admin password for digest authentication. -# Omit if auth is disabled on the device. -# SHELLY_USER (optional) Username for auth. Default: admin -# SHELLY_REBOOT (optional) Set to "0" to skip auto-reboot. -# Default: 1 (reboot after upload) -# -# Requirements: -# - Shelly Gen3+ device (Gen4 recommended) -# - Firmware 2.0.0+ for HTTPS server certificate support -# - curl or wget -# - openssl (for SHA-256 digest and random cnonce) -# -# The device must be reachable via HTTP on the local network. -# The hook uploads the fullchain.pem and private key, -# then reboots the device to apply the new certificate. -# -# Authentication uses standard RFC 7616 HTTP Digest (SHA-256) since -# firmware 2.0.0. The JSON-RPC auth object is not used for HTTP transport. -# -# returns 0 means success, otherwise error. - -######## Public functions ##################### - -#domain keyfile certfile cafile fullchain -shelly_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - _getdeployconf SHELLY_HOST - _getdeployconf SHELLY_PASSWORD - _getdeployconf SHELLY_USER - _getdeployconf SHELLY_REBOOT - - _debug SHELLY_HOST "$SHELLY_HOST" - _debug SHELLY_USER "$SHELLY_USER" - _secure_debug SHELLY_PASSWORD "$SHELLY_PASSWORD" - _debug SHELLY_REBOOT "$SHELLY_REBOOT" - - if [ -z "$SHELLY_HOST" ]; then - _err "SHELLY_HOST is required. Please set the IP or hostname of your Shelly device." - return 1 - fi - - SHELLY_USER="${SHELLY_USER:-admin}" - SHELLY_REBOOT="${SHELLY_REBOOT:-1}" - - _savedeployconf SHELLY_HOST "$SHELLY_HOST" - _savedeployconf SHELLY_PASSWORD "$SHELLY_PASSWORD" - _savedeployconf SHELLY_USER "$SHELLY_USER" - _savedeployconf SHELLY_REBOOT "$SHELLY_REBOOT" - - # --- Auth handshake (only if password is set) --- - _shelly_auth_header="" - if [ -n "$SHELLY_PASSWORD" ]; then - _info "Authenticating to Shelly device at $SHELLY_HOST" - if ! _shelly_handshake; then - _err "Authentication handshake failed. Check SHELLY_PASSWORD and device accessibility." - return 1 - fi - _info "Authentication successful" - fi - - # --- Upload certificate --- - _info "Uploading certificate to Shelly device at $SHELLY_HOST" - if ! _shelly_upload_cert; then - _err "Certificate upload failed" - return 1 - fi - - # --- Upload key --- - _info "Uploading private key to Shelly device" - if ! _shelly_upload_key; then - _err "Private key upload failed" - return 1 - fi - - _info "Certificate and key uploaded successfully" - - # --- Reboot --- - if [ "$SHELLY_REBOOT" != "0" ]; then - _info "Rebooting Shelly device to apply certificate" - # Reboot may close the connection before sending a response - _shelly_rpc "Shelly.Reboot" '{}' || _debug "Reboot may have closed connection (expected)" - _info "Reboot command sent. Device will restart shortly." - else - _info "Skipping reboot (SHELLY_REBOOT=0). Certificate will apply on next restart." - fi - - # Clear auth header so it does not leak to other hooks - export _H1="" - - return 0 -} - -# --- Helper functions --- - -# Perform RFC 7616 HTTP Digest auth handshake. -# Sets _shelly_auth_header on success (the Authorization header value). -_shelly_handshake() { - _inithttp - - _debug "Probing device for auth challenge" - - # Use a protected method (Shelly.GetStatus) to trigger 401. - # Shelly.GetDeviceInfo is excluded from auth and would miss the challenge. - _post '{"id":1,"method":"Shelly.GetStatus"}' \ - "http://${SHELLY_HOST}/rpc" "" "" "application/json" - - # Detect auth from HTTP status line rather than response body - if ! _shelly_has_auth_challenge "$HTTP_HEADER"; then - # No auth challenge — device accepted the request without credentials - _debug "Device responded without auth challenge. Proceeding without auth." - return 0 - fi - - _shelly_realm="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*realm="//;s/".*//')" - _shelly_nonce="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*nonce="//;s/".*//')" - _shelly_qop="$(grep -i '^WWW-Authenticate:' "$HTTP_HEADER" | sed 's/.*qop="//;s/".*//')" - - if [ -z "$_shelly_nonce" ]; then - _err "Failed to extract nonce from WWW-Authenticate header. Is SHELLY_PASSWORD correct?" - return 1 - fi - - _shelly_qop="${_shelly_qop:-auth}" - - _debug "Shelly realm: $_shelly_realm" - _debug "Shelly qop: $_shelly_qop" - _secure_debug "Shelly nonce" "$_shelly_nonce" - - # ha1 = SHA256(username:realm:password) - _shelly_ha1="$(printf '%s' "${SHELLY_USER}:${_shelly_realm}:${SHELLY_PASSWORD}" | _digest sha256 hex)" - _secure_debug "Shelly ha1" "$_shelly_ha1" - - # Generate client nonce (openssl is required for _digest, so always available) - _shelly_cnonce="$(${ACME_OPENSSL_BIN:-openssl} rand -hex 8 2>/dev/null)" - _debug "Shelly cnonce: $_shelly_cnonce" - - # Build the digest Authorization header value (stored for reuse) - _shelly_nc=1 - _shelly_build_auth_header - - return 0 -} - -# Check whether the HTTP response headers contain a digest auth challenge. -# Returns 0 (true) if a 401 with WWW-Authenticate is present. -_shelly_has_auth_challenge() { - _shelly_headers_file="$1" - _shelly_status="$(grep -i '^HTTP/' "$_shelly_headers_file" | _tail_n 1 | awk '{print $2}')" - [ "$_shelly_status" = "401" ] && grep -qi '^WWW-Authenticate:' "$_shelly_headers_file" -} - -# Build or rebuild the RFC 7616 Authorization header. -# Uses: _shelly_ha1, _shelly_nonce, _shelly_cnonce, _shelly_qop, _shelly_realm, _shelly_nc -# Sets: _shelly_auth_header -_shelly_build_auth_header() { - _shelly_nc_hex="$(printf '%08x' "$_shelly_nc")" - - # ha2 = SHA256(POST:/rpc) - _shelly_ha2="$(printf '%s' "POST:/rpc" | _digest sha256 hex)" - - # response = SHA256(ha1:nonce:nc:cnonce:qop:ha2) - _shelly_digest_response="$(printf '%s' "${_shelly_ha1}:${_shelly_nonce}:${_shelly_nc_hex}:${_shelly_cnonce}:${_shelly_qop}:${_shelly_ha2}" | _digest sha256 hex)" - - # Build the Authorization header value (without the "Authorization: " prefix) - _shelly_auth_header="Digest username=\"${SHELLY_USER}\", realm=\"${_shelly_realm}\", nonce=\"${_shelly_nonce}\", uri=\"/rpc\", qop=${_shelly_qop}, nc=${_shelly_nc_hex}, cnonce=\"${_shelly_cnonce}\", response=\"${_shelly_digest_response}\", algorithm=SHA-256" - - _secure_debug "Authorization header" "$_shelly_auth_header" -} - -# Make a Shelly JSON-RPC call. -# Usage: _shelly_rpc -# Returns 0 on success, 1 on error. -_shelly_rpc() { - _shelly_method="$1" - _shelly_params="$2" - - _shelly_body='{"id":1,"method":"'"$_shelly_method"'","params":'"$_shelly_params"'}' - - _debug "RPC method: $_shelly_method" - _debug2 "RPC body: $_shelly_body" - - # shellcheck disable=SC2090 - if [ -n "$_shelly_auth_header" ]; then - export _H1="Authorization: $_shelly_auth_header" - else - export _H1="" - fi - - _post "$_shelly_body" "http://${SHELLY_HOST}/rpc" "" "" "application/json" - _shelly_ret=$? - - if [ "$_shelly_ret" != "0" ]; then - _err "HTTP request failed for $_shelly_method (curl/wget error $_shelly_ret)" - return 1 - fi - - # Empty response means something went wrong (auth required but not provided, etc.) - if [ -z "$response" ]; then - _err "Empty response from Shelly device. If authentication is enabled on the device, set SHELLY_PASSWORD." - return 1 - fi - - # Validate response looks like a Shelly JSON-RPC response. - # Catches non-JSON responses such as HTTP 429 "Too Many Requests" which - # would otherwise pass the empty and "error" checks below. - if ! _startswith "$response" '{' || ! _contains "$response" '"id"'; then - _err "Invalid response from Shelly device: $response" - return 1 - fi - - # Check for JSON-RPC error in response - if _contains "$response" '"error"'; then - _err "RPC error from Shelly: $response" - return 1 - fi - - _debug "RPC response: $response" - - # Increment nonce counter and rebuild auth header for next request - if [ -n "$_shelly_auth_header" ]; then - _shelly_nc=$((_shelly_nc + 1)) - _shelly_build_auth_header - fi - - return 0 -} - -# Upload the certificate to the device. -# Note: We do NOT clear the existing certificate first, because the Shelly -# auto-removes all three files (cert, key, CA) when any one is cleared. -# Uploading overwrites in place — no clearing needed. -_shelly_upload_cert() { - _shelly_cert_data="$(_json_encode <"$_cfullchain")" - - _debug "Uploading certificate" - if ! _shelly_rpc "Shelly.PutHTTPServerCert" '{"data":"'"$_shelly_cert_data"'"}'; then - _err "Failed to upload certificate to device" - return 1 - fi - - return 0 -} - -# Upload the private key to the device. -# Note: Do not clear first — see _shelly_upload_cert for rationale. -_shelly_upload_key() { - _shelly_key_data="$(_json_encode <"$_ckey")" - - _debug "Uploading key" - if ! _shelly_rpc "Shelly.PutHTTPServerKey" '{"data":"'"$_shelly_key_data"'"}'; then - _err "Failed to upload key to device" - return 1 - fi - - return 0 -} diff --git a/deploy/ssh.sh b/deploy/ssh.sh index 0bf3ee48..89962621 100644 --- a/deploy/ssh.sh +++ b/deploy/ssh.sh @@ -14,7 +14,7 @@ # The following examples are for QNAP NAS running QTS 4.2 # export DEPLOY_SSH_CMD="" # defaults to "ssh -T" # export DEPLOY_SSH_USER="admin" # required -# export DEPLOY_SSH_SERVER="host1 host2:8022 192.168.0.1:9022" # defaults to domain name, support multiple servers with optional port +# export DEPLOY_SSH_SERVER="qnap" # defaults to domain name # export DEPLOY_SSH_KEYFILE="/etc/stunnel/stunnel.pem" # export DEPLOY_SSH_CERTFILE="/etc/stunnel/stunnel.pem" # export DEPLOY_SSH_CAFILE="/etc/stunnel/uca.pem" @@ -23,10 +23,7 @@ # export DEPLOY_SSH_BACKUP="" # yes or no, default to yes or previously saved value # export DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" # path on remote system. Defaults to .acme_ssh_deploy # export DEPLOY_SSH_MULTI_CALL="" # yes or no, default to no or previously saved value -# export DEPLOY_SSH_USE_SCP="" yes or no, default to no -# export DEPLOY_SSH_SCP_CMD="" defaults to "scp -q" -# export DEPLOY_SSH_REMOTE_SHELL="" # defaults to sh -c -# export DEPLOY_SSH_REMOTE_CMD_QUOTE="" # yes or no, defaults to yes +# ######## Public functions ##################### #domain keyfile certfile cafile fullchain @@ -45,160 +42,74 @@ ssh_deploy() { _debug _cfullchain "$_cfullchain" # USER is required to login by SSH to remote host. - _migratedeployconf Le_Deploy_ssh_user DEPLOY_SSH_USER _getdeployconf DEPLOY_SSH_USER _debug2 DEPLOY_SSH_USER "$DEPLOY_SSH_USER" if [ -z "$DEPLOY_SSH_USER" ]; then - _err "DEPLOY_SSH_USER not defined." - return 1 + if [ -z "$Le_Deploy_ssh_user" ]; then + _err "DEPLOY_SSH_USER not defined." + return 1 + fi + else + Le_Deploy_ssh_user="$DEPLOY_SSH_USER" + _savedomainconf Le_Deploy_ssh_user "$Le_Deploy_ssh_user" fi - _savedeployconf DEPLOY_SSH_USER "$DEPLOY_SSH_USER" # SERVER is optional. If not provided then use _cdomain - _migratedeployconf Le_Deploy_ssh_server DEPLOY_SSH_SERVER _getdeployconf DEPLOY_SSH_SERVER _debug2 DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER" - if [ -z "$DEPLOY_SSH_SERVER" ]; then - DEPLOY_SSH_SERVER="$_cdomain" + if [ -n "$DEPLOY_SSH_SERVER" ]; then + Le_Deploy_ssh_server="$DEPLOY_SSH_SERVER" + _savedomainconf Le_Deploy_ssh_server "$Le_Deploy_ssh_server" + elif [ -z "$Le_Deploy_ssh_server" ]; then + Le_Deploy_ssh_server="$_cdomain" fi - _savedeployconf DEPLOY_SSH_SERVER "$DEPLOY_SSH_SERVER" # CMD is optional. If not provided then use ssh - _migratedeployconf Le_Deploy_ssh_cmd DEPLOY_SSH_CMD _getdeployconf DEPLOY_SSH_CMD _debug2 DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD" - if [ -z "$DEPLOY_SSH_CMD" ]; then - DEPLOY_SSH_CMD="ssh -T" + if [ -n "$DEPLOY_SSH_CMD" ]; then + Le_Deploy_ssh_cmd="$DEPLOY_SSH_CMD" + _savedomainconf Le_Deploy_ssh_cmd "$Le_Deploy_ssh_cmd" + elif [ -z "$Le_Deploy_ssh_cmd" ]; then + Le_Deploy_ssh_cmd="ssh -T" fi - _savedeployconf DEPLOY_SSH_CMD "$DEPLOY_SSH_CMD" - - # REMOTE_SHELL is optional. If not provided then use sh - _migratedeployconf Le_Deploy_ssh_remote_shell DEPLOY_SSH_REMOTE_SHELL - _getdeployconf DEPLOY_SSH_REMOTE_SHELL - _debug2 DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL" - if [ -z "$DEPLOY_SSH_REMOTE_SHELL" ]; then - DEPLOY_SSH_REMOTE_SHELL="sh -c" - fi - _savedeployconf DEPLOY_SSH_REMOTE_SHELL "$DEPLOY_SSH_REMOTE_SHELL" - - # REMOTE_CMD_QUOTE is optional. If not provided then yes - _migratedeployconf Le_Deploy_ssh_remote_cmd_quote DEPLOY_SSH_REMOTE_CMD_QUOTE - _getdeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE - _debug2 DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE" - if [ -z "$DEPLOY_SSH_REMOTE_CMD_QUOTE" ]; then - DEPLOY_SSH_REMOTE_CMD_QUOTE="yes" - fi - _savedeployconf DEPLOY_SSH_REMOTE_CMD_QUOTE "$DEPLOY_SSH_REMOTE_CMD_QUOTE" # BACKUP is optional. If not provided then default to previously saved value or yes. - _migratedeployconf Le_Deploy_ssh_backup DEPLOY_SSH_BACKUP _getdeployconf DEPLOY_SSH_BACKUP _debug2 DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP" - if [ -z "$DEPLOY_SSH_BACKUP" ]; then - DEPLOY_SSH_BACKUP="yes" + if [ "$DEPLOY_SSH_BACKUP" = "no" ]; then + Le_Deploy_ssh_backup="no" + elif [ -z "$Le_Deploy_ssh_backup" ] || [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then + Le_Deploy_ssh_backup="yes" fi - _savedeployconf DEPLOY_SSH_BACKUP "$DEPLOY_SSH_BACKUP" + _savedomainconf Le_Deploy_ssh_backup "$Le_Deploy_ssh_backup" # BACKUP_PATH is optional. If not provided then default to previously saved value or .acme_ssh_deploy - _migratedeployconf Le_Deploy_ssh_backup_path DEPLOY_SSH_BACKUP_PATH _getdeployconf DEPLOY_SSH_BACKUP_PATH _debug2 DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH" - if [ -z "$DEPLOY_SSH_BACKUP_PATH" ]; then - DEPLOY_SSH_BACKUP_PATH=".acme_ssh_deploy" + if [ -n "$DEPLOY_SSH_BACKUP_PATH" ]; then + Le_Deploy_ssh_backup_path="$DEPLOY_SSH_BACKUP_PATH" + elif [ -z "$Le_Deploy_ssh_backup_path" ]; then + Le_Deploy_ssh_backup_path=".acme_ssh_deploy" fi - _savedeployconf DEPLOY_SSH_BACKUP_PATH "$DEPLOY_SSH_BACKUP_PATH" + _savedomainconf Le_Deploy_ssh_backup_path "$Le_Deploy_ssh_backup_path" # MULTI_CALL is optional. If not provided then default to previously saved # value (which may be undefined... equivalent to "no"). - _migratedeployconf Le_Deploy_ssh_multi_call DEPLOY_SSH_MULTI_CALL _getdeployconf DEPLOY_SSH_MULTI_CALL _debug2 DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL" - if [ -z "$DEPLOY_SSH_MULTI_CALL" ]; then - DEPLOY_SSH_MULTI_CALL="no" - fi - _savedeployconf DEPLOY_SSH_MULTI_CALL "$DEPLOY_SSH_MULTI_CALL" - - # KEYFILE is optional. - # If provided then private key will be copied to provided filename. - _migratedeployconf Le_Deploy_ssh_keyfile DEPLOY_SSH_KEYFILE - _getdeployconf DEPLOY_SSH_KEYFILE - _debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE" - if [ -n "$DEPLOY_SSH_KEYFILE" ]; then - _savedeployconf DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE" + if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then + Le_Deploy_ssh_multi_call="yes" + _savedomainconf Le_Deploy_ssh_multi_call "$Le_Deploy_ssh_multi_call" + elif [ "$DEPLOY_SSH_MULTI_CALL" = "no" ]; then + Le_Deploy_ssh_multi_call="" + _cleardomainconf Le_Deploy_ssh_multi_call fi - # CERTFILE is optional. - # If provided then certificate will be copied or appended to provided filename. - _migratedeployconf Le_Deploy_ssh_certfile DEPLOY_SSH_CERTFILE - _getdeployconf DEPLOY_SSH_CERTFILE - _debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE" - if [ -n "$DEPLOY_SSH_CERTFILE" ]; then - _savedeployconf DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE" - fi - - # CAFILE is optional. - # If provided then CA intermediate certificate will be copied or appended to provided filename. - _migratedeployconf Le_Deploy_ssh_cafile DEPLOY_SSH_CAFILE - _getdeployconf DEPLOY_SSH_CAFILE - _debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE" - if [ -n "$DEPLOY_SSH_CAFILE" ]; then - _savedeployconf DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE" - fi - - # FULLCHAIN is optional. - # If provided then fullchain certificate will be copied or appended to provided filename. - _migratedeployconf Le_Deploy_ssh_fullchain DEPLOY_SSH_FULLCHAIN - _getdeployconf DEPLOY_SSH_FULLCHAIN - _debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN" - if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then - _savedeployconf DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN" - fi - - # REMOTE_CMD is optional. - # If provided then this command will be executed on remote host. - _migratedeployconf Le_Deploy_ssh_remote_cmd DEPLOY_SSH_REMOTE_CMD - _getdeployconf DEPLOY_SSH_REMOTE_CMD - _debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD" - if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then - _savedeployconf DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD" - fi - - # USE_SCP is optional. If not provided then default to previously saved - # value (which may be undefined... equivalent to "no"). - _getdeployconf DEPLOY_SSH_USE_SCP - _debug2 DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP" - if [ -z "$DEPLOY_SSH_USE_SCP" ]; then - DEPLOY_SSH_USE_SCP="no" - fi - _savedeployconf DEPLOY_SSH_USE_SCP "$DEPLOY_SSH_USE_SCP" - - # SCP_CMD is optional. If not provided then use scp - _getdeployconf DEPLOY_SSH_SCP_CMD - _debug2 DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD" - if [ -z "$DEPLOY_SSH_SCP_CMD" ]; then - DEPLOY_SSH_SCP_CMD="scp -q" - fi - _savedeployconf DEPLOY_SSH_SCP_CMD "$DEPLOY_SSH_SCP_CMD" - - if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then - DEPLOY_SSH_MULTI_CALL="yes" - _info "Using scp as alternate method for copying files. Multicall Mode is implicit" - elif [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - _info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host" - else - _info "Required commands batched and sent in single call to remote host" - fi - - _returnCode=0 - _deploy_ssh_servers="$DEPLOY_SSH_SERVER" - for DEPLOY_SSH_SERVER in $_deploy_ssh_servers; do - if ! _ssh_deploy; then - # in case of an error, remember it, but keep going for the remaining servers - _returnCode=1 - fi + _deploy_ssh_servers=$Le_Deploy_ssh_server + for Le_Deploy_ssh_server in $_deploy_ssh_servers; do + _ssh_deploy done - - return $_returnCode } _ssh_deploy() { @@ -206,25 +117,16 @@ _ssh_deploy() { _cmdstr="" _backupprefix="" _backupdir="" - _local_cert_file="" - _local_ca_file="" - _local_full_file="" - case $DEPLOY_SSH_SERVER in - *:*) - _host=${DEPLOY_SSH_SERVER%:*} - _port=${DEPLOY_SSH_SERVER##*:} - ;; - *) - _host=$DEPLOY_SSH_SERVER - _port= - ;; - esac + _info "Deploy certificates to remote server $Le_Deploy_ssh_user@$Le_Deploy_ssh_server" + if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then + _info "Using MULTI_CALL mode... Required commands sent in multiple calls to remote host" + else + _info "Required commands batched and sent in single call to remote host" + fi - _info "Deploy certificates to remote server $DEPLOY_SSH_USER@$_host:$_port" - - if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then - _backupprefix="$DEPLOY_SSH_BACKUP_PATH/$_cdomain-backup" + if [ "$Le_Deploy_ssh_backup" = "yes" ]; then + _backupprefix="$Le_Deploy_ssh_backup_path/$_cdomain-backup" _backupdir="$_backupprefix-$(_utc_date | tr ' ' '-')" # run cleanup on the backup directory, erase all older # than 180 days (15552000 seconds). @@ -236,7 +138,7 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d _cmdstr="mkdir -p $_backupdir; $_cmdstr" _info "Backup of old certificate files will be placed in remote directory $_backupdir" _info "Backup directories erased after 180 days." - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then + if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi @@ -244,186 +146,129 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d fi fi + # KEYFILE is optional. + # If provided then private key will be copied to provided filename. + _getdeployconf DEPLOY_SSH_KEYFILE + _debug2 DEPLOY_SSH_KEYFILE "$DEPLOY_SSH_KEYFILE" if [ -n "$DEPLOY_SSH_KEYFILE" ]; then - if [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then + Le_Deploy_ssh_keyfile="$DEPLOY_SSH_KEYFILE" + _savedomainconf Le_Deploy_ssh_keyfile "$Le_Deploy_ssh_keyfile" + fi + if [ -n "$Le_Deploy_ssh_keyfile" ]; then + if [ "$Le_Deploy_ssh_backup" = "yes" ]; then # backup file we are about to overwrite. - _cmdstr="$_cmdstr cp $DEPLOY_SSH_KEYFILE $_backupdir >/dev/null;" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="$_cmdstr cp $Le_Deploy_ssh_keyfile $_backupdir >/dev/null;" fi - - # copy new key into file. - if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then - # scp the file - if ! _scp_remote_cmd "$_ckey" "$DEPLOY_SSH_KEYFILE"; then + # copy new certificate into file. + _cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $Le_Deploy_ssh_keyfile;" + _info "will copy private key to remote file $Le_Deploy_ssh_keyfile" + if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then + if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi - else - # If file doesn't exist, create it and change its permissions. - _cmdstr="$_cmdstr test ! -f $DEPLOY_SSH_KEYFILE && touch $DEPLOY_SSH_KEYFILE && chmod 600 $DEPLOY_SSH_KEYFILE;" - # ssh echo to the file - _cmdstr="$_cmdstr echo \"$(cat "$_ckey")\" > $DEPLOY_SSH_KEYFILE;" - _info "will copy private key to remote file $DEPLOY_SSH_KEYFILE" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="" fi fi + # CERTFILE is optional. + # If provided then certificate will be copied or appended to provided filename. + _getdeployconf DEPLOY_SSH_CERTFILE + _debug2 DEPLOY_SSH_CERTFILE "$DEPLOY_SSH_CERTFILE" if [ -n "$DEPLOY_SSH_CERTFILE" ]; then + Le_Deploy_ssh_certfile="$DEPLOY_SSH_CERTFILE" + _savedomainconf Le_Deploy_ssh_certfile "$Le_Deploy_ssh_certfile" + fi + if [ -n "$Le_Deploy_ssh_certfile" ]; then _pipe=">" - if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then + if [ "$Le_Deploy_ssh_certfile" = "$Le_Deploy_ssh_keyfile" ]; then # if filename is same as previous file then append. _pipe=">>" - elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then + elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then # backup file we are about to overwrite. - _cmdstr="$_cmdstr cp $DEPLOY_SSH_CERTFILE $_backupdir >/dev/null;" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="$_cmdstr cp $Le_Deploy_ssh_certfile $_backupdir >/dev/null;" fi - # copy new certificate into file. - if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then - # scp the file - _local_cert_file=$(_mktemp) - if [ "$DEPLOY_SSH_CERTFILE" = "$DEPLOY_SSH_KEYFILE" ]; then - cat "$_ckey" >>"$_local_cert_file" - fi - cat "$_ccert" >>"$_local_cert_file" - if ! _scp_remote_cmd "$_local_cert_file" "$DEPLOY_SSH_CERTFILE"; then + _cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $Le_Deploy_ssh_certfile;" + _info "will copy certificate to remote file $Le_Deploy_ssh_certfile" + if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then + if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi - else - # ssh echo to the file - _cmdstr="$_cmdstr echo \"$(cat "$_ccert")\" $_pipe $DEPLOY_SSH_CERTFILE;" - _info "will copy certificate to remote file $DEPLOY_SSH_CERTFILE" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="" fi fi + # CAFILE is optional. + # If provided then CA intermediate certificate will be copied or appended to provided filename. + _getdeployconf DEPLOY_SSH_CAFILE + _debug2 DEPLOY_SSH_CAFILE "$DEPLOY_SSH_CAFILE" if [ -n "$DEPLOY_SSH_CAFILE" ]; then + Le_Deploy_ssh_cafile="$DEPLOY_SSH_CAFILE" + _savedomainconf Le_Deploy_ssh_cafile "$Le_Deploy_ssh_cafile" + fi + if [ -n "$Le_Deploy_ssh_cafile" ]; then _pipe=">" - if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ] || - [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then + if [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_keyfile" ] || + [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_certfile" ]; then # if filename is same as previous file then append. _pipe=">>" - elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then + elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then # backup file we are about to overwrite. - _cmdstr="$_cmdstr cp $DEPLOY_SSH_CAFILE $_backupdir >/dev/null;" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="$_cmdstr cp $Le_Deploy_ssh_cafile $_backupdir >/dev/null;" fi - # copy new certificate into file. - if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then - # scp the file - _local_ca_file=$(_mktemp) - if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_KEYFILE" ]; then - cat "$_ckey" >>"$_local_ca_file" - fi - if [ "$DEPLOY_SSH_CAFILE" = "$DEPLOY_SSH_CERTFILE" ]; then - cat "$_ccert" >>"$_local_ca_file" - fi - cat "$_cca" >>"$_local_ca_file" - if ! _scp_remote_cmd "$_local_ca_file" "$DEPLOY_SSH_CAFILE"; then + _cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $Le_Deploy_ssh_cafile;" + _info "will copy CA file to remote file $Le_Deploy_ssh_cafile" + if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then + if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi - else - # ssh echo to the file - _cmdstr="$_cmdstr echo \"$(cat "$_cca")\" $_pipe $DEPLOY_SSH_CAFILE;" - _info "will copy CA file to remote file $DEPLOY_SSH_CAFILE" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="" fi fi + # FULLCHAIN is optional. + # If provided then fullchain certificate will be copied or appended to provided filename. + _getdeployconf DEPLOY_SSH_FULLCHAIN + _debug2 DEPLOY_SSH_FULLCHAIN "$DEPLOY_SSH_FULLCHAIN" if [ -n "$DEPLOY_SSH_FULLCHAIN" ]; then + Le_Deploy_ssh_fullchain="$DEPLOY_SSH_FULLCHAIN" + _savedomainconf Le_Deploy_ssh_fullchain "$Le_Deploy_ssh_fullchain" + fi + if [ -n "$Le_Deploy_ssh_fullchain" ]; then _pipe=">" - if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ] || - [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ] || - [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then + if [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_keyfile" ] || + [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_certfile" ] || + [ "$Le_Deploy_ssh_fullchain" = "$Le_Deploy_ssh_cafile" ]; then # if filename is same as previous file then append. _pipe=">>" - elif [ "$DEPLOY_SSH_BACKUP" = "yes" ]; then + elif [ "$Le_Deploy_ssh_backup" = "yes" ]; then # backup file we are about to overwrite. - _cmdstr="$_cmdstr cp $DEPLOY_SSH_FULLCHAIN $_backupdir >/dev/null;" - if [ "$DEPLOY_SSH_FULLCHAIN" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="$_cmdstr cp $Le_Deploy_ssh_fullchain $_backupdir >/dev/null;" fi - # copy new certificate into file. - if [ "$DEPLOY_SSH_USE_SCP" = "yes" ]; then - # scp the file - _local_full_file=$(_mktemp) - if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_KEYFILE" ]; then - cat "$_ckey" >>"$_local_full_file" - fi - if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CERTFILE" ]; then - cat "$_ccert" >>"$_local_full_file" - fi - if [ "$DEPLOY_SSH_FULLCHAIN" = "$DEPLOY_SSH_CAFILE" ]; then - cat "$_cca" >>"$_local_full_file" - fi - cat "$_cfullchain" >>"$_local_full_file" - if ! _scp_remote_cmd "$_local_full_file" "$DEPLOY_SSH_FULLCHAIN"; then + _cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $Le_Deploy_ssh_fullchain;" + _info "will copy fullchain to remote file $Le_Deploy_ssh_fullchain" + if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then + if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi - else - # ssh echo to the file - _cmdstr="$_cmdstr echo \"$(cat "$_cfullchain")\" $_pipe $DEPLOY_SSH_FULLCHAIN;" - _info "will copy fullchain to remote file $DEPLOY_SSH_FULLCHAIN" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then - if ! _ssh_remote_cmd "$_cmdstr"; then - return $_err_code - fi - _cmdstr="" - fi + _cmdstr="" fi fi - # cleanup local files if any - if [ -f "$_local_cert_file" ]; then - rm -f "$_local_cert_file" - fi - if [ -f "$_local_ca_file" ]; then - rm -f "$_local_ca_file" - fi - if [ -f "$_local_full_file" ]; then - rm -f "$_local_full_file" - fi - + # REMOTE_CMD is optional. + # If provided then this command will be executed on remote host. + _getdeployconf DEPLOY_SSH_REMOTE_CMD + _debug2 DEPLOY_SSH_REMOTE_CMD "$DEPLOY_SSH_REMOTE_CMD" if [ -n "$DEPLOY_SSH_REMOTE_CMD" ]; then - _cmdstr="$_cmdstr $DEPLOY_SSH_REMOTE_CMD;" - _info "Will execute remote command $DEPLOY_SSH_REMOTE_CMD" - if [ "$DEPLOY_SSH_MULTI_CALL" = "yes" ]; then + Le_Deploy_ssh_remote_cmd="$DEPLOY_SSH_REMOTE_CMD" + _savedomainconf Le_Deploy_ssh_remote_cmd "$Le_Deploy_ssh_remote_cmd" + fi + if [ -n "$Le_Deploy_ssh_remote_cmd" ]; then + _cmdstr="$_cmdstr $Le_Deploy_ssh_remote_cmd;" + _info "Will execute remote command $Le_Deploy_ssh_remote_cmd" + if [ "$Le_Deploy_ssh_multi_call" = "yes" ]; then if ! _ssh_remote_cmd "$_cmdstr"; then return $_err_code fi @@ -437,29 +282,17 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d return $_err_code fi fi - # cleanup in case all is ok return 0 } #cmd _ssh_remote_cmd() { _cmd="$1" - - _ssh_cmd="$DEPLOY_SSH_CMD" - if [ -n "$_port" ]; then - _ssh_cmd="$_ssh_cmd -p $_port" - fi - _secure_debug "Remote commands to execute: $_cmd" - _info "Submitting sequence of commands to remote server by $_ssh_cmd" - - if [ "$DEPLOY_SSH_REMOTE_CMD_QUOTE" = "yes" ]; then - # quotations in bash cmd below intended. Squash travis spellcheck error - # shellcheck disable=SC2029 - $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "'$_cmd'" - else - $_ssh_cmd "$DEPLOY_SSH_USER@$_host" "$DEPLOY_SSH_REMOTE_SHELL" "$_cmd" - fi + _info "Submitting sequence of commands to remote server by ssh" + # quotations in bash cmd below intended. Squash travis spellcheck error + # shellcheck disable=SC2029 + $Le_Deploy_ssh_cmd "$Le_Deploy_ssh_user@$Le_Deploy_ssh_server" sh -c "'$_cmd'" _err_code="$?" if [ "$_err_code" != "0" ]; then @@ -468,26 +301,3 @@ _ssh_remote_cmd() { return $_err_code } - -# cmd scp -_scp_remote_cmd() { - _src=$1 - _dest=$2 - - _scp_cmd="$DEPLOY_SSH_SCP_CMD" - if [ -n "$_port" ]; then - _scp_cmd="$_scp_cmd -P $_port" - fi - - _secure_debug "Remote copy source $_src to destination $_dest" - _info "Submitting secure copy by $_scp_cmd" - - $_scp_cmd "$_src" "$DEPLOY_SSH_USER"@"$_host":"$_dest" - _err_code="$?" - - if [ "$_err_code" != "0" ]; then - _err "Error code $_err_code returned from scp" - fi - - return $_err_code -} diff --git a/deploy/strongswan.sh b/deploy/strongswan.sh index 80353c54..3d5f1b34 100644 --- a/deploy/strongswan.sh +++ b/deploy/strongswan.sh @@ -10,89 +10,46 @@ #domain keyfile certfile cafile fullchain strongswan_deploy() { - _cdomain="${1}" - _ckey="${2}" - _ccert="${3}" - _cca="${4}" - _cfullchain="${5}" + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + _info "Using strongswan" - if _exists ipsec; then - _ipsec=ipsec - elif _exists strongswan; then - _ipsec=strongswan - fi - if _exists swanctl; then - _swanctl=swanctl - fi - # For legacy stroke mode - if [ -n "${_ipsec}" ]; then - _info "${_ipsec} command detected" - _confdir=$(${_ipsec} --confdir) - if [ -z "${_confdir}" ]; then - _err "no strongswan --confdir is detected" - return 1 - fi - _info _confdir "${_confdir}" - __deploy_cert "stroke" "${_confdir}" "$@" - ${_ipsec} reload - fi - # For modern vici mode - if [ -n "${_swanctl}" ]; then - _info "${_swanctl} command detected" - for _dir in /usr/local/etc/swanctl /etc/swanctl /etc/strongswan/swanctl; do - if [ -d ${_dir} ]; then - _confdir=${_dir} - _info _confdir "${_confdir}" - break - fi - done - if [ -z "${_confdir}" ]; then - _err "no swanctl config dir is found" - return 1 - fi - __deploy_cert "vici" "${_confdir}" "$@" - ${_swanctl} --load-creds - fi - if [ -z "${_swanctl}" ] && [ -z "${_ipsec}" ]; then - _err "no strongswan or ipsec command is detected" - _err "no swanctl is detected" - return 1 - fi -} -#################### Private functions below ################################## - -__deploy_cert() { - _swan_mode="${1}" - _confdir="${2}" - _cdomain="${3}" - _ckey="${4}" - _ccert="${5}" - _cca="${6}" - _cfullchain="${7}" - _debug _cdomain "${_cdomain}" - _debug _ckey "${_ckey}" - _debug _ccert "${_ccert}" - _debug _cca "${_cca}" - _debug _cfullchain "${_cfullchain}" - _debug _swan_mode "${_swan_mode}" - _debug _confdir "${_confdir}" - if [ "${_swan_mode}" = "vici" ]; then - _dir_private="private" - _dir_cert="x509" - _dir_ca="x509ca" - elif [ "${_swan_mode}" = "stroke" ]; then - _dir_private="ipsec.d/private" - _dir_cert="ipsec.d/certs" - _dir_ca="ipsec.d/cacerts" + if [ -x /usr/sbin/ipsec ]; then + _ipsec=/usr/sbin/ipsec + elif [ -x /usr/sbin/strongswan ]; then + _ipsec=/usr/sbin/strongswan + elif [ -x /usr/local/sbin/ipsec ]; then + _ipsec=/usr/local/sbin/ipsec else - _err "unknown StrongSwan mode ${_swan_mode}" + _err "no strongswan or ipsec command is detected" return 1 fi - cat "${_ckey}" >"${_confdir}/${_dir_private}/$(basename "${_ckey}")" - cat "${_ccert}" >"${_confdir}/${_dir_cert}/$(basename "${_ccert}")" - cat "${_cca}" >"${_confdir}/${_dir_ca}/$(basename "${_cca}")" - if [ "${_swan_mode}" = "stroke" ]; then - cat "${_cfullchain}" >"${_confdir}/${_dir_ca}/$(basename "${_cfullchain}")" + + _info _ipsec "$_ipsec" + + _confdir=$($_ipsec --confdir) + if [ $? -ne 0 ] || [ -z "$_confdir" ]; then + _err "no strongswan --confdir is detected" + return 1 fi + + _info _confdir "$_confdir" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + cat "$_ckey" >"${_confdir}/ipsec.d/private/$(basename "$_ckey")" + cat "$_ccert" >"${_confdir}/ipsec.d/certs/$(basename "$_ccert")" + cat "$_cca" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cca")" + cat "$_cfullchain" >"${_confdir}/ipsec.d/cacerts/$(basename "$_cfullchain")" + + $_ipsec reload + } diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index 336980a5..c31a5df0 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -1,53 +1,34 @@ #!/usr/bin/env sh -################################################################################ -# ACME.sh 3rd party deploy plugin for Synology DSM -################################################################################ -# Authors: Brian Hartvigsen (creator), https://github.com/tresni -# Martin Arndt (contributor), https://troublezone.net/ -# Updated: 2023-07-03 -# Issues: https://github.com/acmesh-official/acme.sh/issues/2727 -################################################################################ -# Usage (shown values are the examples): -# 1. Set required environment variables: -# - use automatically created temp admin user to authenticate -# export SYNO_USE_TEMP_ADMIN=1 -# - or provide your own admin user credential to authenticate -# 1. export SYNO_USERNAME="adminUser" -# 2. export SYNO_PASSWORD="adminPassword" -# 2. Set optional environment variables -# - common optional variables -# - export SYNO_SCHEME="http" - defaults to "http" -# - export SYNO_HOSTNAME="localhost" - defaults to "localhost" -# - export SYNO_PORT="5000" - defaults to "5000" -# - export SYNO_CREATE=1 - to allow creating the cert if it doesn't exist -# - export SYNO_CERTIFICATE="" - to replace a specific cert by its -# description -# - temp admin optional variables -# - export SYNO_LOCAL_HOSTNAME=1 - if set to 1, force to treat hostname is -# targeting current local machine (since -# this method only locally supported) -# - exsiting admin 2FA-OTP optional variables -# - export SYNO_OTP_CODE="XXXXXX" - if set, script won't require to -# interactive input the OTP code -# - export SYNO_DEVICE_NAME="CertRenewal" - if set, script won't require to -# interactive input the device name -# - export SYNO_DEVICE_ID="" - (deprecated, auth with OTP code instead) -# required for omitting 2FA-OTP -# 3. Run command: -# acme.sh --deploy --deploy-hook synology_dsm -d example.com -################################################################################ +# Here is a script to deploy cert to Synology DSM +# +# It requires following environment variables: +# +# SYNO_Username - Synology Username to login (must be an administrator) +# SYNO_Password - Synology Password to login +# SYNO_Certificate - Certificate description to target for replacement +# +# The following environmental variables may be set if you don't like their +# default values: +# +# SYNO_Scheme - defaults to http +# SYNO_Hostname - defaults to localhost +# SYNO_Port - defaults to 5000 +# SYNO_DID - device ID to skip OTP - defaults to empty +# SYNO_TOTP_SECRET - TOTP secret to generate OTP - defaults to empty +# # Dependencies: -# - curl -# - synouser & synogroup & synosetkeyvalue (Required for SYNO_USE_TEMP_ADMIN=1) -################################################################################ -# Return value: -# 0 means success, otherwise error. -################################################################################ +# ------------- +# - jq and curl +# - oathtool (When using 2 Factor Authentication and SYNO_TOTP_SECRET is set) +# +#returns 0 means success, otherwise error. + +######## Public functions ##################### -########## Public functions #################################################### #domain keyfile certfile cafile fullchain synology_dsm_deploy() { + _cdomain="$1" _ckey="$2" _ccert="$3" @@ -55,390 +36,148 @@ synology_dsm_deploy() { _debug _cdomain "$_cdomain" - # Get username and password, but don't save until we authenticated successfully - _migratedeployconf SYNO_Username SYNO_USERNAME - _migratedeployconf SYNO_Password SYNO_PASSWORD - _migratedeployconf SYNO_Device_ID SYNO_DEVICE_ID - _migratedeployconf SYNO_Device_Name SYNO_DEVICE_NAME - _getdeployconf SYNO_USERNAME - _getdeployconf SYNO_PASSWORD - _getdeployconf SYNO_DEVICE_ID - _getdeployconf SYNO_DEVICE_NAME - - # Prepare to use temp admin if SYNO_USE_TEMP_ADMIN is set - _getdeployconf SYNO_USE_TEMP_ADMIN - _check2cleardeployconfexp SYNO_USE_TEMP_ADMIN - _debug2 SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN" - - if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then - if ! _exists synouser || ! _exists synogroup || ! _exists synosetkeyvalue; then - _err "Missing required tools to create temp admin user, please set SYNO_USERNAME and SYNO_PASSWORD instead." - _err "Notice: temp admin user authorization method only supports local deployment on DSM." - return 1 - fi - if synouser --help 2>&1 | grep -q 'Permission denied'; then - _err "For creating temp admin user, the deploy script must be run as root." - return 1 - fi - - [ -n "$SYNO_USERNAME" ] || _savedeployconf SYNO_USERNAME "" - [ -n "$SYNO_PASSWORD" ] || _savedeployconf SYNO_PASSWORD "" - - _debug "Setting temp admin user credential..." - SYNO_USERNAME=sc-acmesh-tmp - SYNO_PASSWORD=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 16) - # Set 2FA-OTP settings to empty consider they won't be needed. - SYNO_DEVICE_ID= - SYNO_DEVICE_NAME= - SYNO_OTP_CODE= - else - _debug2 SYNO_USERNAME "$SYNO_USERNAME" - _secure_debug2 SYNO_PASSWORD "$SYNO_PASSWORD" - _debug2 SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME" - _secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID" - fi - - if [ -z "$SYNO_USERNAME" ] || [ -z "$SYNO_PASSWORD" ]; then - _err "You must set either SYNO_USE_TEMP_ADMIN, or set both SYNO_USERNAME and SYNO_PASSWORD." + # Get Username and Password, but don't save until we successfully authenticate + _getdeployconf SYNO_Username + _getdeployconf SYNO_Password + _getdeployconf SYNO_Create + _getdeployconf SYNO_DID + _getdeployconf SYNO_TOTP_SECRET + if [ -z "${SYNO_Username:-}" ] || [ -z "${SYNO_Password:-}" ]; then + _err "SYNO_Username & SYNO_Password must be set" return 1 fi + _debug2 SYNO_Username "$SYNO_Username" + _secure_debug2 SYNO_Password "$SYNO_Password" - # Optional scheme, hostname and port for Synology DSM - _migratedeployconf SYNO_Scheme SYNO_SCHEME - _migratedeployconf SYNO_Hostname SYNO_HOSTNAME - _migratedeployconf SYNO_Port SYNO_PORT - _getdeployconf SYNO_SCHEME - _getdeployconf SYNO_HOSTNAME - _getdeployconf SYNO_PORT + # Optional scheme, hostname, and port for Synology DSM + _getdeployconf SYNO_Scheme + _getdeployconf SYNO_Hostname + _getdeployconf SYNO_Port - # Default values for scheme, hostname and port - # Defaulting to localhost and http, because it's localhost… - [ -n "$SYNO_SCHEME" ] || SYNO_SCHEME=http - [ -n "$SYNO_HOSTNAME" ] || SYNO_HOSTNAME=localhost - [ -n "$SYNO_PORT" ] || SYNO_PORT=5000 - _savedeployconf SYNO_SCHEME "$SYNO_SCHEME" - _savedeployconf SYNO_HOSTNAME "$SYNO_HOSTNAME" - _savedeployconf SYNO_PORT "$SYNO_PORT" - _debug2 SYNO_SCHEME "$SYNO_SCHEME" - _debug2 SYNO_HOSTNAME "$SYNO_HOSTNAME" - _debug2 SYNO_PORT "$SYNO_PORT" + # default vaules for scheme, hostname, and port + # defaulting to localhost and http because it's localhost... + [ -n "${SYNO_Scheme}" ] || SYNO_Scheme="http" + [ -n "${SYNO_Hostname}" ] || SYNO_Hostname="localhost" + [ -n "${SYNO_Port}" ] || SYNO_Port="5000" - # Get the certificate description, but don't save it until we verify it's real - _migratedeployconf SYNO_Certificate SYNO_CERTIFICATE "base64" - _getdeployconf SYNO_CERTIFICATE - _check2cleardeployconfexp SYNO_CERTIFICATE - _debug SYNO_CERTIFICATE "${SYNO_CERTIFICATE:-}" + _savedeployconf SYNO_Scheme "$SYNO_Scheme" + _savedeployconf SYNO_Hostname "$SYNO_Hostname" + _savedeployconf SYNO_Port "$SYNO_Port" + + _debug2 SYNO_Scheme "$SYNO_Scheme" + _debug2 SYNO_Hostname "$SYNO_Hostname" + _debug2 SYNO_Port "$SYNO_Port" + + # Get the certificate description, but don't save it until we verfiy it's real + _getdeployconf SYNO_Certificate + _debug SYNO_Certificate "${SYNO_Certificate:-}" # shellcheck disable=SC1003 # We are not trying to escape a single quote - if printf "%s" "$SYNO_CERTIFICATE" | grep '\\'; then + if printf "%s" "$SYNO_Certificate" | grep '\\'; then _err "Do not use a backslash (\) in your certificate description" return 1 fi - _debug "Getting API version..." - _base_url="$SYNO_SCHEME://$SYNO_HOSTNAME:$SYNO_PORT" + _base_url="$SYNO_Scheme://$SYNO_Hostname:$SYNO_Port" _debug _base_url "$_base_url" + + _debug "Getting API version" response=$(_get "$_base_url/webapi/query.cgi?api=SYNO.API.Info&version=1&method=query&query=SYNO.API.Auth") - api_path=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"path" *: *"\([^"]*\)".*/\1/p') api_version=$(echo "$response" | grep "SYNO.API.Auth" | sed -n 's/.*"maxVersion" *: *\([0-9]*\).*/\1/p') _debug3 response "$response" - _debug3 api_path "$api_path" _debug3 api_version "$api_version" - # Login, get the session ID and SynoToken from JSON - _info "Logging into $SYNO_HOSTNAME:$SYNO_PORT..." - encoded_username="$(printf "%s" "$SYNO_USERNAME" | _url_encode)" - encoded_password="$(printf "%s" "$SYNO_PASSWORD" | _url_encode)" - - # ## START ## - DEPRECATED, for backward compatibility - _getdeployconf SYNO_TOTP_SECRET + # Login, get the token from JSON and session id from cookie + _info "Logging into $SYNO_Hostname:$SYNO_Port" + encoded_username="$(printf "%s" "$SYNO_Username" | _url_encode)" + encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)" + otp_code="" if [ -n "$SYNO_TOTP_SECRET" ]; then - _info "WARNING: Usage of SYNO_TOTP_SECRET is deprecated!" - _info " See synology_dsm.sh script or ACME.sh Wiki page for details:" - _info " https://github.com/acmesh-official/acme.sh/wiki/Synology-NAS-Guide" - if ! _exists oathtool; then + if _exists oathtool; then + otp_code="$(oathtool --base32 --totp "${SYNO_TOTP_SECRET}" 2>/dev/null)" + else _err "oathtool could not be found, install oathtool to use SYNO_TOTP_SECRET" return 1 fi - DEPRECATED_otp_code="$(oathtool --base32 --totp "$SYNO_TOTP_SECRET" 2>/dev/null)" - - if [ -z "$SYNO_DEVICE_ID" ]; then - _getdeployconf SYNO_DID - [ -n "$SYNO_DID" ] || SYNO_DEVICE_ID="$SYNO_DID" - fi - if [ -n "$SYNO_DEVICE_ID" ]; then - _H1="Cookie: did=$SYNO_DEVICE_ID" - export _H1 - _debug3 H1 "${_H1}" - fi - - response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$DEPRECATED_otp_code&device_name=certrenewal&device_id=$SYNO_DEVICE_ID" "$_base_url/webapi/$api_path?enable_syno_token=yes") - _debug3 response "$response" - # ## END ## - DEPRECATED, for backward compatibility - # If SYNO_DEVICE_ID or SYNO_OTP_CODE is set, we treat current account enabled 2FA-OTP. - # Notice that if SYNO_USE_TEMP_ADMIN=1, both variables will be unset - else - if [ -n "$SYNO_DEVICE_ID" ] || [ -n "$SYNO_OTP_CODE" ]; then - response='{"error":{"code":403}}' - # Assume the current account disabled 2FA-OTP, try to log in right away. - else - if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then - _getdeployconf SYNO_LOCAL_HOSTNAME - _debug SYNO_LOCAL_HOSTNAME "${SYNO_LOCAL_HOSTNAME:-}" - if [ "$SYNO_HOSTNAME" != "localhost" ] && [ "$SYNO_HOSTNAME" != "127.0.0.1" ]; then - if [ "$SYNO_LOCAL_HOSTNAME" != "1" ]; then - _err "SYNO_USE_TEMP_ADMIN=1 only support local deployment, though if you are sure that the hostname $SYNO_HOSTNAME is targeting to your **current local machine**, execute 'export SYNO_LOCAL_HOSTNAME=1' then rerun." - return 1 - fi - fi - _debug "Creating temp admin user in Synology DSM..." - if synogroup --help | grep -q '\-\-memberadd '; then - _temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD" - synogroup --memberadd administrators "$SYNO_USERNAME" >/dev/null - elif synogroup --help | grep -q '\-\-member '; then - # For supporting DSM 6.x which only has `--member` parameter. - cur_admins=$(synogroup --get administrators | awk -F '[][]' '/Group Members/,0{if(NF>1)printf "%s ", $2}') - if [ -n "$cur_admins" ]; then - _temp_admin_create "$SYNO_USERNAME" "$SYNO_PASSWORD" - _secure_debug3 admin_users "$cur_admins$SYNO_USERNAME" - # shellcheck disable=SC2086 - synogroup --member administrators $cur_admins $SYNO_USERNAME >/dev/null - else - _err "The tool synogroup may be broken, please set SYNO_USERNAME and SYNO_PASSWORD instead." - return 1 - fi - else - _err "Unsupported synogroup tool detected, please set SYNO_USERNAME and SYNO_PASSWORD instead." - return 1 - fi - # havig a workaround to temporary disable enforce 2FA-OTP, will restore - # it soon (after a single request), though if any accident occurs like - # unexpected interruption, this setting can be easily reverted manually. - otp_enforce_option=$(synogetkeyvalue /etc/synoinfo.conf otp_enforce_option) - if [ -n "$otp_enforce_option" ] && [ "${otp_enforce_option:-"none"}" != "none" ]; then - synosetkeyvalue /etc/synoinfo.conf otp_enforce_option none - _info "Enforcing 2FA-OTP has been disabled to complete temp admin authentication." - _info "Notice: it will be restored soon, if not, you can restore it manually via Control Panel." - _info "previous_otp_enforce_option" "$otp_enforce_option" - else - otp_enforce_option="" - fi - fi - response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes") - if [ -n "$SYNO_USE_TEMP_ADMIN" ] && [ -n "$otp_enforce_option" ]; then - synosetkeyvalue /etc/synoinfo.conf otp_enforce_option "$otp_enforce_option" - _info "Restored previous enforce 2FA-OTP option." - fi - _debug3 response "$response" - fi fi - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') - _debug2 error_code "$error_code" - # Account has 2FA-OTP enabled, since error 403 reported. - # https://global.download.synology.com/download/Document/Software/DeveloperGuide/Os/DSM/All/enu/DSM_Login_Web_API_Guide_enu.pdf - if [ "$error_code" = "403" ]; then - if [ -z "$SYNO_DEVICE_NAME" ]; then - printf "Enter device name or leave empty for default (CertRenewal): " - read -r SYNO_DEVICE_NAME - [ -n "$SYNO_DEVICE_NAME" ] || SYNO_DEVICE_NAME="CertRenewal" - fi - - if [ -n "$SYNO_DEVICE_ID" ]; then - # Omit OTP code with SYNO_DEVICE_ID. - response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_name=$SYNO_DEVICE_NAME&device_id=$SYNO_DEVICE_ID") - _secure_debug3 response "$response" - else - # Require the OTP code if still unset. - if [ -z "$SYNO_OTP_CODE" ]; then - printf "Enter OTP code for user '%s': " "$SYNO_USERNAME" - read -r SYNO_OTP_CODE - fi - _secure_debug SYNO_OTP_CODE "${SYNO_OTP_CODE:-}" - - if [ -z "$SYNO_OTP_CODE" ]; then - response='{"error":{"code":404}}' - else - response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=login&format=sid&account=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&enable_device_token=yes&device_name=$SYNO_DEVICE_NAME&otp_code=$SYNO_OTP_CODE") - _secure_debug3 response "$response" - - id_property='device_id' - [ "${api_version}" -gt '6' ] || id_property='did' - SYNO_DEVICE_ID=$(echo "$response" | grep "$id_property" | sed -n 's/.*"'$id_property'" *: *"\([^"]*\).*/\1/p') - _secure_debug2 SYNO_DEVICE_ID "$SYNO_DEVICE_ID" - fi - fi - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') - _debug2 error_code "$error_code" + if [ -n "$SYNO_DID" ]; then + _H1="Cookie: did=$SYNO_DID" + export _H1 + _debug3 H1 "${_H1}" fi - if [ -n "$error_code" ]; then - if [ "$error_code" = "403" ] && [ -n "$SYNO_DEVICE_ID" ]; then - _cleardeployconf SYNO_DEVICE_ID - _err "Failed to authenticate with SYNO_DEVICE_ID (may be expired or invalid), please try again in a new terminal window." - elif [ "$error_code" = "404" ]; then - _err "Failed to authenticate with provided 2FA-OTP code, please try again in a new terminal window." - elif [ "$error_code" = "406" ]; then - if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then - _err "Failed with unexcepted error, please report this by providing full log with '--debug 3'." - else - _err "Enforce auth with 2FA-OTP enabled, please configure the user to enable 2FA-OTP to continue." - fi - elif [ "$error_code" = "400" ]; then - _err "Failed to authenticate, no such account or incorrect password." - elif [ "$error_code" = "401" ]; then - _err "Failed to authenticate with a non-existent account." - elif [ "$error_code" = "408" ] || [ "$error_code" = "409" ] || [ "$error_code" = "410" ]; then - _err "Failed to authenticate, the account password has expired or must be changed." - else - _err "Failed to authenticate with error: $error_code." - fi - _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" - return 1 - fi - - sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p') + response=$(_post "method=login&account=$encoded_username&passwd=$encoded_password&api=SYNO.API.Auth&version=$api_version&enable_syno_token=yes&otp_code=$otp_code&device_name=certrenewal&device_id=$SYNO_DID" "$_base_url/webapi/auth.cgi?enable_syno_token=yes") token=$(echo "$response" | grep "synotoken" | sed -n 's/.*"synotoken" *: *"\([^"]*\).*/\1/p') - _debug "Session ID" "$sid" - _debug SynoToken "$token" - if [ -z "$sid" ] || [ -z "$token" ]; then - # Still can't get necessary info even got no errors, may Synology have API updated? - _err "Unable to authenticate to $_base_url, you may report this by providing full log with '--debug 3'." - _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" + _debug3 response "$response" + _debug token "$token" + + if [ -z "$token" ]; then + _err "Unable to authenticate to $SYNO_Hostname:$SYNO_Port using $SYNO_Scheme." + _err "Check your username and password." + _err "If two-factor authentication is enabled for the user, set SYNO_TOTP_SECRET." return 1 fi + sid=$(echo "$response" | grep "sid" | sed -n 's/.*"sid" *: *"\([^"]*\).*/\1/p') _H1="X-SYNO-TOKEN: $token" export _H1 _debug2 H1 "${_H1}" - # Now that we know the username and password are good, save them if not in temp admin mode. - if [ -n "$SYNO_USE_TEMP_ADMIN" ]; then - _cleardeployconf SYNO_USERNAME - _cleardeployconf SYNO_PASSWORD - _cleardeployconf SYNO_DEVICE_ID - _cleardeployconf SYNO_DEVICE_NAME - _savedeployconf SYNO_USE_TEMP_ADMIN "$SYNO_USE_TEMP_ADMIN" - _savedeployconf SYNO_LOCAL_HOSTNAME "$SYNO_LOCAL_HOSTNAME" - else - _savedeployconf SYNO_USERNAME "$SYNO_USERNAME" "base64" - _savedeployconf SYNO_PASSWORD "$SYNO_PASSWORD" "base64" - _savedeployconf SYNO_DEVICE_ID "$SYNO_DEVICE_ID" - _savedeployconf SYNO_DEVICE_NAME "$SYNO_DEVICE_NAME" - fi + # Now that we know the username and password are good, save them + _savedeployconf SYNO_Username "$SYNO_Username" + _savedeployconf SYNO_Password "$SYNO_Password" + _savedeployconf SYNO_DID "$SYNO_DID" + _savedeployconf SYNO_TOTP_SECRET "$SYNO_TOTP_SECRET" - _info "Getting certificates in Synology DSM..." + _info "Getting certificates in Synology DSM" response=$(_post "api=SYNO.Core.Certificate.CRT&method=list&version=1&_sid=$sid" "$_base_url/webapi/entry.cgi") _debug3 response "$response" - escaped_certificate="$(printf "%s" "$SYNO_CERTIFICATE" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')" + escaped_certificate="$(printf "%s" "$SYNO_Certificate" | sed 's/\([].*^$[]\)/\\\1/g;s/"/\\\\"/g')" _debug escaped_certificate "$escaped_certificate" id=$(echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\"id\":\"\([^\"]*\).*/\1/p") _debug2 id "$id" - error_code=$(echo "$response" | grep '"error":' | grep -o '"code":[0-9]*' | grep -Eo '[0-9]+') - _debug2 error_code "$error_code" - if [ -n "$error_code" ]; then - if [ "$error_code" -eq 105 ]; then - _err "Current user is not administrator and does not have sufficient permission for deploying." - else - _err "Failed to fetch certificate info: $error_code, please try again or contact Synology to learn more." - fi - _logout - _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" + if [ -z "$id" ] && [ -z "${SYNO_Create:-}" ]; then + _err "Unable to find certificate: $SYNO_Certificate and \$SYNO_Create is not set" return 1 fi - _migratedeployconf SYNO_Create SYNO_CREATE - _getdeployconf SYNO_CREATE - _debug2 SYNO_CREATE "$SYNO_CREATE" + # we've verified this certificate description is a thing, so save it + _savedeployconf SYNO_Certificate "$SYNO_Certificate" "base64" - if [ -z "$id" ] && [ -z "$SYNO_CREATE" ]; then - _err "Unable to find certificate: $SYNO_CERTIFICATE and \$SYNO_CREATE is not set." - _logout - _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" - return 1 - fi - - # We've verified this certificate description is a thing, so save it - _savedeployconf SYNO_CERTIFICATE "$SYNO_CERTIFICATE" "base64" - - _info "Generating form POST request..." + _info "Generate form POST request" nl="\0015\0012" delim="--------------------------$(_utc_date | tr -d -- '-: ')" content="--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")\0012" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_ccert")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ccert")\0012" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"inter_cert\"; filename=\"$(basename "$_cca")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cca")\0012" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"id\"${nl}${nl}$id" - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_CERTIFICATE}" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"desc\"${nl}${nl}${SYNO_Certificate}" if echo "$response" | sed -n "s/.*\"desc\":\"$escaped_certificate\",\([^{]*\).*/\1/p" | grep -- 'is_default":true' >/dev/null; then - _debug2 default "This is the default certificate" + _debug2 default "this is the default certificate" content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"as_default\"${nl}${nl}true" else - _debug2 default "This is NOT the default certificate" + _debug2 default "this is NOT the default certificate" fi content="$content${nl}--$delim--${nl}" content="$(printf "%b_" "$content")" content="${content%_}" # protect trailing \n - _info "Upload certificate to the Synology DSM." + _info "Upload certificate to the Synology DSM" response=$(_post "$content" "$_base_url/webapi/entry.cgi?api=SYNO.Core.Certificate&method=import&version=1&SynoToken=$token&_sid=$sid" "" "POST" "multipart/form-data; boundary=${delim}") _debug3 response "$response" if ! echo "$response" | grep '"error":' >/dev/null; then if echo "$response" | grep '"restart_httpd":true' >/dev/null; then - _info "Restart HTTP services succeeded." + _info "http services were restarted" else - _info "Restart HTTP services not necessary." + _info "http services were NOT restarted" fi - _logout - _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" return 0 else - _err "Unable to update certificate, got error response: $response." - _logout - _temp_admin_cleanup "$SYNO_USE_TEMP_ADMIN" "$SYNO_USERNAME" + _err "Unable to update certificate, error code $response" return 1 fi } - -#################### Private functions below ################################## -_logout() { - # Logout CERT user only to not occupy a permanent session, e.g. in DSM's "Connected Users" widget (based on previous variables) - # Must be called before _temp_admin_cleanup: once the temp admin is deleted, its session can no longer be logged out. - # Note: this overwrites $response, so print any error message that needs it before calling. - response=$(_get "$_base_url/webapi/$api_path?api=SYNO.API.Auth&version=$api_version&method=logout&_sid=$sid") - _debug3 response "$response" -} - -_temp_admin_create() { - _username="$1" - _password="$2" - synouser --del "$_username" >/dev/null 2>/dev/null - synouser --add "$_username" "$_password" "" 0 "" 0 >/dev/null -} - -_temp_admin_cleanup() { - _flag=$1 - _username=$2 - - if [ -n "${_flag}" ]; then - _debug "Cleanuping temp admin info..." - synouser --del "$_username" >/dev/null - fi -} - -# key -_check2cleardeployconfexp() { - _key="$1" - _clear_key="CLEAR_$_key" - # Clear saved settings if explicitly requested - if [ -n "$(eval echo \$"$_clear_key")" ]; then - _debug2 "$_key: value cleared from config, exported value will be ignored." - _cleardeployconf "$_key" - eval "$_key"= - export "$_key"= - eval SAVED_"$_key"= - export SAVED_"$_key"= - fi -} diff --git a/deploy/truenas.sh b/deploy/truenas.sh index 6a008bd7..84cfd5f4 100644 --- a/deploy/truenas.sh +++ b/deploy/truenas.sh @@ -9,7 +9,7 @@ # # Following environment variables must be set: # -# export DEPLOY_TRUENAS_APIKEY="" +# export DEPLOY_TRUENAS_APIKEY="/ui/apikeys -# export DEPLOY_TRUENAS_APIKEY="" -# Optional: -# export DEPLOY_TRUENAS_HOSTNAME="" -# export DEPLOY_TRUENAS_PROTOCOL="wss" # ws or wss -# export DEPLOY_TRUENAS_PORT="443" # optional, e.g. 80, 443, 8443 - -# - -### Private functions - -# Call websocket method -# Usage: -# _ws_response=$(_ws_call "math.dummycalc" "'{"x": 4, "y": 5}'") -# _info "$_ws_response" -# -# Output: -# {"z": 9} -# -# Arguments: -# $@ - midclt arguments for call -# -# Returns: -# JSON/JOBID -_ws_call() { - _debug "_ws_call arg1" "$1" - _debug "_ws_call arg2" "$2" - _debug "_ws_call arg3" "$3" - if [ $# -eq 3 ]; then - _ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2" "$3") - fi - if [ $# -eq 2 ]; then - _ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1" "$2") - fi - if [ $# -eq 1 ]; then - _ws_response=$(midclt --uri "$_ws_uri" -K "$DEPLOY_TRUENAS_APIKEY" call "$1") - fi - _debug "_ws_response" "$_ws_response" - printf "%s" "$_ws_response" - return 0 -} - -# Upload certificate with webclient api -_ws_upload_cert() { - - /usr/bin/env python - </dev/null 2>&1 # fail quietly if we're not running as root - fi - - # Update unifi service for certificate cipher compatibility - _unifi_system_properties="${DEPLOY_UNIFI_SYSTEM_PROPERTIES:-/usr/lib/unifi/data/system.properties}" - if ${ACME_OPENSSL_BIN:-openssl} pkcs12 \ - -in "$_import_pkcs12" \ - -password pass:aircontrolenterprise \ - -nokeys | ${ACME_OPENSSL_BIN:-openssl} x509 -text \ - -noout | grep -i "signature" | grep -iq ecdsa >/dev/null 2>&1; then - if [ -f "$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties" ]; then - _unifi_system_properties="$(dirname "${DEPLOY_UNIFI_KEYSTORE}")/system.properties" - else - _unifi_system_properties="/usr/lib/unifi/data/system.properties" - fi - if [ -f "${_unifi_system_properties}" ]; then - cp -f "${_unifi_system_properties}" "${_unifi_system_properties}"_original - _info "Updating system configuration for cipher compatibility." - _info "Saved original system config to ${_unifi_system_properties}_original" - sed -i '/unifi\.https\.ciphers/d' "${_unifi_system_properties}" - echo "unifi.https.ciphers=ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES128-GCM-SHA256" >>"${_unifi_system_properties}" - sed -i '/unifi\.https\.sslEnabledProtocols/d' "${_unifi_system_properties}" - echo "unifi.https.sslEnabledProtocols=TLSv1.3,TLSv1.2" >>"${_unifi_system_properties}" - _info "System configuration updated." - fi - fi - - rm "$_import_pkcs12" - - # Restarting unifi-core will bring up unifi, doing it out of order results in - # a certificate error, and breaks wifiman. - # Restart if we aren't doing Unifi OS (e.g. unifi-core service), otherwise stop for later restart. - _unifi_reload="${DEPLOY_UNIFI_RELOAD:-systemctl restart unifi}" - if [ ! -f "${DEPLOY_UNIFI_CORE_CONFIG:-/data/unifi-core/config}/unifi-core.key" ]; then - _reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload" - else - _info "Stopping Unifi Controller for later restart." - _unifi_stop=$(echo "${_unifi_reload}" | sed -e 's/restart/stop/') - $_unifi_stop - _reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_reload" - _info "Unifi Controller stopped." + if systemctl -q is-active unifi; then + _reload_cmd="${_reload_cmd:+$_reload_cmd && }service unifi restart" fi _services_updated="${_services_updated} unifi" _info "Install Unifi Controller certificate success!" @@ -209,24 +134,13 @@ unifi_deploy() { return 1 fi # Cloud Key expects to load the keystore from /etc/ssl/private/unifi.keystore.jks. - # It appears that unifi won't start if this is a symlink, so we'll copy it instead. - - # if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then - # _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'" - # return 1 - # fi - - _info "Updating ${_cloudkey_certdir}/unifi.keystore.jks" - if [ -e "${_cloudkey_certdir}/unifi.keystore.jks" ]; then - if [ -L "${_cloudkey_certdir}/unifi.keystore.jks" ]; then - rm -f "${_cloudkey_certdir}/unifi.keystore.jks" - else - mv "${_cloudkey_certdir}/unifi.keystore.jks" "${_cloudkey_certdir}/unifi.keystore.jks_original" - fi + # Normally /usr/lib/unifi/data/keystore is a symlink there (so the keystore was + # updated above), but if not, we don't know how to handle this installation: + if ! cmp -s "$_unifi_keystore" "${_cloudkey_certdir}/unifi.keystore.jks"; then + _err "Unsupported Cloud Key configuration: keystore not found at '${_cloudkey_certdir}/unifi.keystore.jks'" + return 1 fi - cp "${_unifi_keystore}" "${_cloudkey_certdir}/unifi.keystore.jks" - cat "$_cfullchain" >"${_cloudkey_certdir}/cloudkey.crt" cat "$_ckey" >"${_cloudkey_certdir}/cloudkey.key" (cd "$_cloudkey_certdir" && tar -cf cert.tar cloudkey.crt cloudkey.key unifi.keystore.jks) @@ -251,17 +165,12 @@ unifi_deploy() { return 1 fi - # Save the existing certs in case something goes wrong. - cp -f "${_unifi_core_config}"/unifi-core.crt "${_unifi_core_config}"/unifi-core_original.crt - cp -f "${_unifi_core_config}"/unifi-core.key "${_unifi_core_config}"/unifi-core_original.key - _info "Previous certificate and key saved to ${_unifi_core_config}/unifi-core_original.crt.key." - cat "$_cfullchain" >"${_unifi_core_config}/unifi-core.crt" cat "$_ckey" >"${_unifi_core_config}/unifi-core.key" - _unifi_os_reload="${DEPLOY_UNIFI_OS_RELOAD:-systemctl restart unifi-core}" - _reload_cmd="${_reload_cmd:+$_reload_cmd && }$_unifi_os_reload" - + if systemctl -q is-active unifi-core; then + _reload_cmd="${_reload_cmd:+$_reload_cmd && }systemctl restart unifi-core" + fi _info "Install UnifiOS certificate success!" _services_updated="${_services_updated} unifi-core" elif [ "$DEPLOY_UNIFI_CORE_CONFIG" ]; then @@ -300,8 +209,6 @@ unifi_deploy() { _savedeployconf DEPLOY_UNIFI_CLOUDKEY_CERTDIR "$DEPLOY_UNIFI_CLOUDKEY_CERTDIR" _savedeployconf DEPLOY_UNIFI_CORE_CONFIG "$DEPLOY_UNIFI_CORE_CONFIG" _savedeployconf DEPLOY_UNIFI_RELOAD "$DEPLOY_UNIFI_RELOAD" - _savedeployconf DEPLOY_UNIFI_OS_RELOAD "$DEPLOY_UNIFI_OS_RELOAD" - _savedeployconf DEPLOY_UNIFI_SYSTEM_PROPERTIES "$DEPLOY_UNIFI_SYSTEM_PROPERTIES" return 0 } diff --git a/deploy/unifios.sh b/deploy/unifios.sh deleted file mode 100644 index 82b65c69..00000000 --- a/deploy/unifios.sh +++ /dev/null @@ -1,307 +0,0 @@ -#!/usr/bin/env sh -# Deploy hook for UniFi OS Server (self-hosted). -# -# Supports: -# - UniFi OS Server on macOS -# - UniFi OS Server on Linux -# - UniFi OS Server on Windows should also work (runs under WSL2), but -# has not been tested. -# -# Tested on: Ubuntu 26.04 (remote) and macOS 26.6 (local). -# -# This is a different product from the Cloud Key / UDM hardware and -# self-hosted Unifi Controller covered by the `unifi` deploy hook above -# (that hook already covers Cloud Key running UnifiOS v2.0.0+/Gen2/2+) -- -# this hook targets the separately-installed, self-hosted "UniFi OS Server" -# application instead, which stores certificates in its own Postgres -# database via a REST API rather than a Java keystore, so the `unifi` -# hook's approach does not apply here. -# -# UniFi OS Server exposes a REST API on its management port (default -# 11443) that its own web UI uses for certificate management: -# POST /api/auth/login - session login (cookie + JWT) -# GET /api/userCertificates - list uploaded certificates -# POST /api/userCertificates - upload a new certificate -# DELETE /api/userCertificates/{id} - remove a certificate -# PUT /api/userCertificates/{id}/status - activate/deactivate a certificate -# -# This was reverse-engineered from the browser's Network tab while using the -# real GUI upload/activate/delete flow -- it is undocumented but is the same -# code path the UI uses, so it's far more robust than editing settings.yaml, -# http/local-certs.conf, or the underlying Postgres user_certificates table -# directly (all of which are also touched by this API, but only as a result -# of the app's own internal logic, which handles cert parsing, active-cert -# bookkeeping, and nginx config regeneration correctly on its own). -# -# Auth: POST /api/auth/login returns a `TOKEN` cookie containing a JWT whose -# payload has a `csrfToken` claim. That value must be echoed back as the -# `x-csrf-token` header on every subsequent state-changing request (a classic -# double-submit CSRF pattern). No other cookies were found to be necessary. -# -# Uses core acme.sh helpers throughout (_post/_get, _json_encode, -# _durl_replace_base64, _dbase64, _egrep_o) rather than raw curl -k or -# python3, so the wget fallback, --debug tracing, and CA_BUNDLE are all -# honored the same as every other hook. The management API's cert is -# self-signed (it's a management-only port, not meant for public exposure), -# so this hook sets HTTPS_INSECURE=1 itself, scoped to its own subshell (see -# acme.sh's per-hook sourcing in _deploy) -- it does not weaken TLS -# verification for the rest of the acme.sh run, e.g. the connection to the -# ACME CA. -# -# Design: This hook does not save a certificate ID between renewals. Each -# upload gets a name unique to that run: the domain name plus a timestamp. -# This name never collides with an entry from a previous deploy. This is -# true even if that entry is still active. The hook uploads and activates -# the new certificate before it removes any old entries. If a failure -# occurs during this process, the server still has a valid, active -# certificate. The hook removes old entries only after activation is -# complete. It removes only entries whose name starts with the domain name, -# because this is the hook's own naming convention. As a result, this step -# can only affect entries that this hook created for this domain. It can -# never affect a certificate that a user uploaded manually, and it can -# never affect a self-signed certificate. -# -# Settings: -# DEPLOY_UNIFIOS_HOST - base URL of the management API -# (default: "https://localhost:11443") -# DEPLOY_UNIFIOS_USERNAME - UniFi OS Server admin username (required) -# DEPLOY_UNIFIOS_PASSWORD - UniFi OS Server admin password (required) -# -# Example: -# export DEPLOY_UNIFIOS_USERNAME="acmeuser" -# export DEPLOY_UNIFIOS_PASSWORD="xxxxx" -# acme.sh --deploy -d example.com --deploy-hook unifios -# -# Please report bugs to https://github.com/acmesh-official/acme.sh/issues/7182 - -_uos_response_code() { - # tr strips the trailing newline along with form feeds; re-terminate - # before the second _egrep_o, whose sed fallback (used wherever egrep -o - # is unavailable) drops an unterminated final line on some platforms. - _uos_code="$(_egrep_o <"$HTTP_HEADER" "^HTTP[^ ]* .*$" | cut -d " " -f 2-100 | tr -d "\f\n")" - printf '%s\n' "$_uos_code" | _egrep_o "^[0-9][0-9]*" -} - -_uos_response_cookie() { - # $1 = cookie name - grep <"$HTTP_HEADER" -i "^Set-Cookie: *$1=" | _tail_n 1 | _egrep_o "$1=[^;]*" | _head_n 1 -} - -unifios_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - # Scoped to this hook's own subshell -- does not affect the rest of the - # acme.sh run (e.g. the connection to the ACME CA). - export HTTPS_INSECURE=1 - - _getdeployconf DEPLOY_UNIFIOS_HOST - DEPLOY_UNIFIOS_HOST="${DEPLOY_UNIFIOS_HOST:-https://localhost:11443}" - _savedeployconf DEPLOY_UNIFIOS_HOST "$DEPLOY_UNIFIOS_HOST" - _debug DEPLOY_UNIFIOS_HOST "$DEPLOY_UNIFIOS_HOST" - - _getdeployconf DEPLOY_UNIFIOS_USERNAME - _getdeployconf DEPLOY_UNIFIOS_PASSWORD - - if [ -z "$DEPLOY_UNIFIOS_USERNAME" ] || [ -z "$DEPLOY_UNIFIOS_PASSWORD" ]; then - _err "DEPLOY_UNIFIOS_USERNAME and DEPLOY_UNIFIOS_PASSWORD must be set." - return 1 - fi - _debug DEPLOY_UNIFIOS_USERNAME "$DEPLOY_UNIFIOS_USERNAME" - _secure_debug DEPLOY_UNIFIOS_PASSWORD "$DEPLOY_UNIFIOS_PASSWORD" - - _info "Logging in to UniFi OS Server API at $DEPLOY_UNIFIOS_HOST..." - - # _json_encode always appends a trailing "\n" escape, even to input with - # no trailing newline (it normalizes via `echo`, unconditionally adding - # one). That's harmless for the key/cert file content below, which - # legitimately ends in a real newline anyway, but wrong for these plain - # strings -- strip the spurious escape it leaves behind. - _uos_user_json="$(printf '%s' "$DEPLOY_UNIFIOS_USERNAME" | _json_encode)" - _uos_user_json="${_uos_user_json%\\n}" - _uos_pass_json="$(printf '%s' "$DEPLOY_UNIFIOS_PASSWORD" | _json_encode)" - _uos_pass_json="${_uos_pass_json%\\n}" - _login_body="{\"username\":\"$_uos_user_json\",\"password\":\"$_uos_pass_json\",\"token\":\"\",\"rememberMe\":false}" - - _login_json="$(_post "$_login_body" "$DEPLOY_UNIFIOS_HOST/api/auth/login" "" "POST" "application/json")" - _login_code="$(_uos_response_code)" - - if [ "$_login_code" != "200" ]; then - _err "Login failed (HTTP $_login_code)." - _err "Response: $_login_json" - return 1 - fi - - # Credentials are proven correct now -- save them, rather than only at the - # very end, so a later step failing doesn't discard a working login. - # base64-encoded: _save_conf wraps values in single quotes with no - # escaping, so a literal "'" in the password would otherwise corrupt the - # domain conf (see deploy/synology_dsm.sh for the same pattern). - _savedeployconf DEPLOY_UNIFIOS_USERNAME "$DEPLOY_UNIFIOS_USERNAME" "base64" - _savedeployconf DEPLOY_UNIFIOS_PASSWORD "$DEPLOY_UNIFIOS_PASSWORD" "base64" - - _uos_token="$(_uos_response_cookie TOKEN)" - if [ -z "$_uos_token" ]; then - _err "Login succeeded but no TOKEN cookie was returned." - return 1 - fi - - _H1="Cookie: $_uos_token" - export _H1 - - _uos_jwt_payload="$(echo "$_uos_token" | cut -d '=' -f 2- | cut -d '.' -f 2)" - _uos_csrf="$(_durl_replace_base64 "$_uos_jwt_payload" | _dbase64 | _egrep_o '"csrfToken":"[^"]*"' | cut -d '"' -f 4)" - if [ -z "$_uos_csrf" ]; then - _err "Could not extract csrfToken from session token." - return 1 - fi - - _H2="x-csrf-token: $_uos_csrf" - export _H2 - - _info "Uploading new certificate..." - # "name" is a purely cosmetic label -- the server never validates it - # against the certificate's actual CN/SAN, and accepts arbitrary text - # including spaces (confirmed: a cert for example.com served correctly - # after being uploaded under the unrelated name "totally unrelated label"). - # The only constraint that matters here is uniqueness: the server rejects - # a second entry with a name it already has, so a bare domain name would - # collide with the previous deploy's entry on every renewal after the - # first. A full human-readable timestamp would make that obvious in the - # UI, but the certificate list's name column is fixed-width and doesn't - # wrap (confirmed against the real UI: a long name overlaps the Expires - # column and makes both unreadable), so keep the suffix short instead -- - # Unix epoch seconds are still unique enough for this purpose. - _uos_name="$_cdomain $(_time)" - _uos_key_json="$(_json_encode <"$_ckey")" - _uos_cert_json="$(_json_encode <"$_cfullchain")" - _create_body="{\"name\":\"$_uos_name\",\"key\":\"$_uos_key_json\",\"cert\":\"$_uos_cert_json\"}" - - _create_json="$(_post "$_create_body" "$DEPLOY_UNIFIOS_HOST/api/userCertificates" "" "POST" "application/json")" - _create_code="$(_uos_response_code)" - - if [ "$_create_code" = "201" ]; then - _new_id="$(echo "$_create_json" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)" - if [ -z "$_new_id" ]; then - _err "Could not determine new certificate ID from upload response." - return 1 - fi - elif [ "$_create_code" = "400" ] && echo "$_create_json" | grep -q "USER_CERTIFICATE_DUPLICATE"; then - # HTTP 400 alone just means "bad request" -- it's the USER_CERTIFICATE_DUPLICATE - # code in the response body, checked above, that actually confirms this. - # The name above is unique to this run, so a duplicate here can only be - # the server's other uniqueness constraint: this exact certificate (by - # fingerprint) already exists as some other entry -- most likely a retry - # after a prior run already uploaded it (a real renewal always produces a - # new fingerprint, so this shouldn't happen in normal cron use). The - # response body doesn't include the existing entry's id, so look it up - # by fingerprint instead. - # The API's own fingerprint field is SHA-1 (20 bytes), not SHA-256 -- - # confirmed against a real response, e.g. - # "fingerprint":"FC:02:50:9C:3B:3F:B7:79:9D:CA:4D:7C:AC:92:E7:D5:EA:F1:3A:29" - # (20 colon-separated groups). _fingerprint (core helper) strips the - # colons that field has, so re-insert them rather than stripping the - # JSON's own colons, which would also remove the ones separating every - # key from its value. - _uos_fingerprint="$(_fingerprint "$_cfullchain" sha1)" - if [ -z "$_uos_fingerprint" ]; then - _err "Could not compute the certificate's fingerprint." - return 1 - fi - _uos_fingerprint="$(echo "$_uos_fingerprint" | sed 's/\(..\)/\1:/g; s/:$//')" - - _list_json="$(_get "$DEPLOY_UNIFIOS_HOST/api/userCertificates")" - _list_code="$(_uos_response_code)" - if [ "$_list_code" != "200" ]; then - _err "Failed to list existing certificates (HTTP $_list_code)." - _err "Response: $_list_json" - return 1 - fi - # _normalizeJson collapses the response to one predictable line (no stray - # whitespace around colons, no embedded CR/LF the server might emit) but - # also strips the trailing newline entirely -- re-terminate before the - # split below, since some sed implementations drop an unterminated final - # line rather than processing it. - _list_json="$(echo "$_list_json" | _normalizeJson)" - # A literal embedded newline (not the two-character "\n", which GNU sed - # treats as a newline in the replacement but POSIX doesn't define and BSD - # sed emits literally) splits it one JSON object per line so grep can - # match a single certificate entry at a time. - _list_json="$( - printf '%s\n' "$_list_json" | sed 's/},{/},\ -{/g' - )" - _new_id="$(echo "$_list_json" | grep -F "\"fingerprint\":\"$_uos_fingerprint\"" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)" - if [ -z "$_new_id" ]; then - _err "Certificate upload rejected as a duplicate (server reported USER_CERTIFICATE_DUPLICATE), but no existing entry matching this fingerprint was found." - _err "Response: $_create_json" - return 1 - fi - # Reusing the existing entry rather than deleting it and re-uploading - # under today's name+timestamp: the served content is identical either - # way, so replacing it would only cost an extra delete+create round trip - # for no functional benefit. The tradeoff is cosmetic -- this entry keeps - # whatever name it was given whenever it was originally uploaded, so it - # won't reflect today's date in the UI. - _info "Certificate already present as entry $_new_id; reusing it." - else - _err "Certificate upload failed (HTTP $_create_code)." - _err "Response: $_create_json" - return 1 - fi - - _info "Activating certificate $_new_id..." - _activate_json="$(_post '{"active":true}' "$DEPLOY_UNIFIOS_HOST/api/userCertificates/$_new_id/status" "" "PUT" "application/json")" - _activate_code="$(_uos_response_code)" - - if [ "$_activate_code" != "200" ]; then - _err "Failed to activate new certificate (HTTP $_activate_code)." - _err "Response: $_activate_json" - return 1 - fi - - # UniFi OS Server activation is exclusive server-wide. Tests against the - # real API confirm this: activation of one entry deactivates whichever - # other entry was active before, no matter its name or domain. As a - # result, the server serves the certificate that this hook just activated. - # This certificate is already live. If the removal of old entries below - # fails, the hook logs the failure. The deploy does not fail because of - # this. - _info "Checking for old certificate entries to remove..." - _list_json="$(_get "$DEPLOY_UNIFIOS_HOST/api/userCertificates")" - _list_code="$(_uos_response_code)" - if [ "$_list_code" != "200" ]; then - _err "Failed to list certificates for cleanup (HTTP $_list_code) -- leaving old entries in place." - else - _list_json="$(echo "$_list_json" | _normalizeJson)" - _list_json="$( - printf '%s\n' "$_list_json" | sed 's/},{/},\ -{/g' - )" - # The pattern below matches the domain name followed by a space. If the - # space is missing, the pattern can also match a different domain that - # starts with the same text as this domain. - _old_ids="$(echo "$_list_json" | grep -F "\"name\":\"$_cdomain " | _egrep_o '"id":"[^"]*"' | cut -d '"' -f 4 | grep -v "^$_new_id$")" - for _old_id in $_old_ids; do - _info "Removing old certificate entry $_old_id..." - _del_json="$(_post "" "$DEPLOY_UNIFIOS_HOST/api/userCertificates/$_old_id" "" "DELETE")" - _del_code="$(_uos_response_code)" - if [ "$_del_code" != "204" ] && [ "$_del_code" != "200" ]; then - _err "Failed to delete old certificate $_old_id (HTTP $_del_code) -- leaving it in place." - _err "Response: $_del_json" - fi - done - fi - - _info "UniFi OS Server certificate deployed and activated successfully." - return 0 -} diff --git a/deploy/vault.sh b/deploy/vault.sh index 89994f4b..399abaee 100644 --- a/deploy/vault.sh +++ b/deploy/vault.sh @@ -7,16 +7,13 @@ # # VAULT_PREFIX - this contains the prefix path in vault # VAULT_ADDR - vault requires this to find your vault server -# VAULT_SAVE_TOKEN - set to anything if you want to save the token -# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying -# VAULT_KV_V2 - set to anything if you are using v2 of the kv engine # # additionally, you need to ensure that VAULT_TOKEN is avialable # to access the vault server #returns 0 means success, otherwise error. -######## Public functions ##################### +######## Public functions ##################### #domain keyfile certfile cafile fullchain vault_deploy() { @@ -48,154 +45,34 @@ vault_deploy() { fi _savedeployconf VAULT_ADDR "$VAULT_ADDR" - _getdeployconf VAULT_SAVE_TOKEN - _savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN" - - _getdeployconf VAULT_RENEW_TOKEN - _savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN" - - _getdeployconf VAULT_KV_V2 - _savedeployconf VAULT_KV_V2 "$VAULT_KV_V2" - - _getdeployconf VAULT_TOKEN - if [ -z "$VAULT_TOKEN" ]; then - _err "VAULT_TOKEN needs to be defined" - return 1 - fi - if [ -n "$VAULT_SAVE_TOKEN" ]; then - _savedeployconf VAULT_TOKEN "$VAULT_TOKEN" - fi - - _migratedeployconf FABIO VAULT_FABIO_MODE - # JSON does not allow multiline strings. # So replacing new-lines with "\n" here - _ckey=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$2") - _ccert=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$3") - _cca=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$4") - _cfullchain=$(sed -e ':a' -e N -e '$ ! ba' -e 's/\n/\\n/g' <"$5") - - export _H1="X-Vault-Token: $VAULT_TOKEN" - - if [ -n "$VAULT_RENEW_TOKEN" ]; then - URL="$VAULT_ADDR/v1/auth/token/renew-self" - _info "Renew the Vault token to default TTL" - _response=$(_post "" "$URL") - if [ "$?" != "0" ]; then - _err "Failed to renew the Vault token" - return 1 - fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Failed to renew the Vault token: $_response" - return 1 - fi - fi + _ckey=$(sed -z 's/\n/\\n/g' <"$2") + _ccert=$(sed -z 's/\n/\\n/g' <"$3") + _cca=$(sed -z 's/\n/\\n/g' <"$4") + _cfullchain=$(sed -z 's/\n/\\n/g' <"$5") URL="$VAULT_ADDR/v1/$VAULT_PREFIX/$_cdomain" + export _H1="X-Vault-Token: $VAULT_TOKEN" - if [ -n "$VAULT_FABIO_MODE" ]; then - _info "Writing certificate and key to $URL in Fabio mode" + if [ -n "$FABIO" ]; then if [ -n "$VAULT_KV_V2" ]; then - _response=$(_post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error: $_response" - return 1 - fi + _post "{ \"data\": {\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"} }" "$URL" else - _response=$(_post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error: $_response" - return 1 - fi + _post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" fi else if [ -n "$VAULT_KV_V2" ]; then - _info "Writing certificate to $URL/cert.pem" - _response=$(_post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing cert.pem: $_response" - return 1 - fi - - _info "Writing key to $URL/cert.key" - _response=$(_post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing cert.key: $_response" - return 1 - fi - - _info "Writing CA certificate to $URL/ca.pem" - _response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/ca.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing ca.pem: $_response" - return 1 - fi - - _info "Writing full-chain certificate to $URL/fullchain.pem" - _response=$(_post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing fullchain.pem: $_response" - return 1 - fi + _post "{\"data\": {\"value\": \"$_ccert\"}}" "$URL/cert.pem" + _post "{\"data\": {\"value\": \"$_ckey\"}}" "$URL/cert.key" + _post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem" + _post "{\"data\": {\"value\": \"$_cfullchain\"}}" "$URL/fullchain.pem" else - _info "Writing certificate to $URL/cert.pem" - _response=$(_post "{\"value\": \"$_ccert\"}" "$URL/cert.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing cert.pem: $_response" - return 1 - fi - - _info "Writing key to $URL/cert.key" - _response=$(_post "{\"value\": \"$_ckey\"}" "$URL/cert.key") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing cert.key: $_response" - return 1 - fi - - _info "Writing CA certificate to $URL/ca.pem" - _response=$(_post "{\"value\": \"$_cca\"}" "$URL/ca.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing ca.pem: $_response" - return 1 - fi - - _info "Writing full-chain certificate to $URL/fullchain.pem" - _response=$(_post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing fullchain.pem: $_response" - return 1 - fi - fi - - # To make it compatible with the wrong ca path `chain.pem` which was used in former versions - if _contains "$(_get "$URL/chain.pem")" "-----BEGIN CERTIFICATE-----"; then - _err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning" - _info "Updating CA certificate to $URL/chain.pem for backward compatibility" - if [ -n "$VAULT_KV_V2" ]; then - _response=$(_post "{\"data\": {\"value\": \"$_cca\"}}" "$URL/chain.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing chain.pem: $_response" - return 1 - fi - else - _response=$(_post "{\"value\": \"$_cca\"}" "$URL/chain.pem") - if [ "$?" != "0" ]; then return 1; fi - if echo "$_response" | grep -q '"errors":\['; then - _err "Vault error writing chain.pem: $_response" - return 1 - fi - fi + _post "{\"value\": \"$_ccert\"}" "$URL/cert.pem" + _post "{\"value\": \"$_ckey\"}" "$URL/cert.key" + _post "{\"value\": \"$_cca\"}" "$URL/chain.pem" + _post "{\"value\": \"$_cfullchain\"}" "$URL/fullchain.pem" fi fi + } diff --git a/deploy/vault_cli.sh b/deploy/vault_cli.sh index 3ebb8074..cbb8cc59 100644 --- a/deploy/vault_cli.sh +++ b/deploy/vault_cli.sh @@ -8,8 +8,6 @@ # # VAULT_PREFIX - this contains the prefix path in vault # VAULT_ADDR - vault requires this to find your vault server -# VAULT_SAVE_TOKEN - set to anything if you want to save the token -# VAULT_RENEW_TOKEN - set to anything if you want to renew the token to default TTL before deploying # # additionally, you need to ensure that VAULT_TOKEN is avialable or # `vault auth` has applied the appropriate authorization for the vault binary @@ -35,36 +33,15 @@ vault_cli_deploy() { _debug _cfullchain "$_cfullchain" # validate required env vars - _getdeployconf VAULT_PREFIX if [ -z "$VAULT_PREFIX" ]; then _err "VAULT_PREFIX needs to be defined (contains prefix path in vault)" return 1 fi - _savedeployconf VAULT_PREFIX "$VAULT_PREFIX" - _getdeployconf VAULT_ADDR if [ -z "$VAULT_ADDR" ]; then _err "VAULT_ADDR needs to be defined (contains vault connection address)" return 1 fi - _savedeployconf VAULT_ADDR "$VAULT_ADDR" - - _getdeployconf VAULT_SAVE_TOKEN - _savedeployconf VAULT_SAVE_TOKEN "$VAULT_SAVE_TOKEN" - - _getdeployconf VAULT_RENEW_TOKEN - _savedeployconf VAULT_RENEW_TOKEN "$VAULT_RENEW_TOKEN" - - _getdeployconf VAULT_TOKEN - if [ -z "$VAULT_TOKEN" ]; then - _err "VAULT_TOKEN needs to be defined" - return 1 - fi - if [ -n "$VAULT_SAVE_TOKEN" ]; then - _savedeployconf VAULT_TOKEN "$VAULT_TOKEN" - fi - - _migratedeployconf FABIO VAULT_FABIO_MODE VAULT_CMD=$(command -v vault) if [ ! $? ]; then @@ -72,33 +49,13 @@ vault_cli_deploy() { return 1 fi - if [ -n "$VAULT_RENEW_TOKEN" ]; then - _info "Renew the Vault token to default TTL" - if ! $VAULT_CMD token renew; then - _err "Failed to renew the Vault token" - return 1 - fi - fi - - if [ -n "$VAULT_FABIO_MODE" ]; then - _info "Writing certificate and key to ${VAULT_PREFIX}/${_cdomain} in Fabio mode" + if [ -n "$FABIO" ]; then $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}" cert=@"$_cfullchain" key=@"$_ckey" || return 1 else - _info "Writing certificate to ${VAULT_PREFIX}/${_cdomain}/cert.pem" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.pem" value=@"$_ccert" || return 1 - _info "Writing key to ${VAULT_PREFIX}/${_cdomain}/cert.key" $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/cert.key" value=@"$_ckey" || return 1 - _info "Writing CA certificate to ${VAULT_PREFIX}/${_cdomain}/ca.pem" - $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/ca.pem" value=@"$_cca" || return 1 - _info "Writing full-chain certificate to ${VAULT_PREFIX}/${_cdomain}/fullchain.pem" + $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/fullchain.pem" value=@"$_cfullchain" || return 1 - - # To make it compatible with the wrong ca path `chain.pem` which was used in former versions - if $VAULT_CMD kv get "${VAULT_PREFIX}/${_cdomain}/chain.pem" >/dev/null; then - _err "The CA certificate has moved from chain.pem to ca.pem, if you don't depend on chain.pem anymore, you can delete it to avoid this warning" - _info "Updating CA certificate to ${VAULT_PREFIX}/${_cdomain}/chain.pem for backward compatibility" - $VAULT_CMD kv put "${VAULT_PREFIX}/${_cdomain}/chain.pem" value=@"$_cca" || return 1 - fi fi } diff --git a/deploy/vsftpd.sh b/deploy/vsftpd.sh index 570495cc..8cf24e4f 100644 --- a/deploy/vsftpd.sh +++ b/deploy/vsftpd.sh @@ -106,5 +106,5 @@ vsftpd_deploy() { fi return 1 fi - + return 0 } diff --git a/deploy/windows_rdp.sh b/deploy/windows_rdp.sh deleted file mode 100644 index e708e9a7..00000000 --- a/deploy/windows_rdp.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env sh - -# install a certificate on a Windows host over OpenSSH and bind it to the Remote -# Desktop listener (RDP-Tcp). -# -# One ssh invocation does the whole job: -# * the PFX is built locally, base64'd, and embedded as a string literal -# inside a generated PowerShell script; -# * the script is piped to `powershell.exe -Command -` over ssh. No scp, -# no temp files on the Windows host. -# -# First run: -# export DEPLOY_WIN_RDP_HOST=winserver.example.com -# acme.sh --deploy -d winserver.example.com --deploy-hook windows_rdp -# -# Available variables: -# DEPLOY_WIN_RDP_HOST required SSH host -# DEPLOY_WIN_RDP_USER optional SSH user, must be a local administrator (can also by set via ssh_config) -# DEPLOY_WIN_RDP_PORT optional SSH port, default 22 -# DEPLOY_WIN_RDP_SSH_OPTS optional extra ssh options, e.g. -# "-i /root/.ssh/win_id_ed25519 -o StrictHostKeyChecking=yes" -# DEPLOY_WIN_RDP_LISTENER optional RDP listener name, default RDP-Tcp -# DEPLOY_WIN_RDP_RESTART optional "1" to restart TermService after install. -# Active RDP sessions will drop! - -windows_rdp_deploy() { - _cdomain="$1" - _ckey="$2" - _ccert="$3" - _cca="$4" - _cfullchain="$5" - - _debug _cdomain "$_cdomain" - _debug _ckey "$_ckey" - _debug _ccert "$_ccert" - _debug _cca "$_cca" - _debug _cfullchain "$_cfullchain" - - if ! _exists "ssh"; then - _err "ssh is required but was not found in PATH." - return 1 - fi - - # ---- configuration ------------------------------------------------------ - _getdeployconf DEPLOY_WIN_RDP_HOST - _getdeployconf DEPLOY_WIN_RDP_USER - _getdeployconf DEPLOY_WIN_RDP_PORT - _getdeployconf DEPLOY_WIN_RDP_SSH_OPTS - _getdeployconf DEPLOY_WIN_RDP_LISTENER - _getdeployconf DEPLOY_WIN_RDP_RESTART - - if [ -z "$DEPLOY_WIN_RDP_HOST" ]; then - _err "DEPLOY_WIN_RDP_HOST must be set." - return 1 - fi - - _savedeployconf DEPLOY_WIN_RDP_HOST "$DEPLOY_WIN_RDP_HOST" - [ -n "$DEPLOY_WIN_RDP_USER" ] && _savedeployconf DEPLOY_WIN_RDP_USER "$DEPLOY_WIN_RDP_USER" - [ -n "$DEPLOY_WIN_RDP_PORT" ] && _savedeployconf DEPLOY_WIN_RDP_PORT "$DEPLOY_WIN_RDP_PORT" - [ -n "$DEPLOY_WIN_RDP_SSH_OPTS" ] && _savedeployconf DEPLOY_WIN_RDP_SSH_OPTS "$DEPLOY_WIN_RDP_SSH_OPTS" - [ -n "$DEPLOY_WIN_RDP_LISTENER" ] && _savedeployconf DEPLOY_WIN_RDP_LISTENER "$DEPLOY_WIN_RDP_LISTENER" - [ -n "$DEPLOY_WIN_RDP_RESTART" ] && _savedeployconf DEPLOY_WIN_RDP_RESTART "$DEPLOY_WIN_RDP_RESTART" - - _port="${DEPLOY_WIN_RDP_PORT:-22}" - _listener="${DEPLOY_WIN_RDP_LISTENER:-RDP-Tcp}" - if [ -n "$DEPLOY_WIN_RDP_USER" ]; then - _target="$DEPLOY_WIN_RDP_USER@$DEPLOY_WIN_RDP_HOST" - else - _target="$DEPLOY_WIN_RDP_HOST" - fi - _pfx_pass="acme" - - # ---- build thumbprint + PFX locally ------------------------------------ - _thumb="$(_fingerprint "$_ccert" 'sha1')" - if [ -z "$_thumb" ]; then - _err "Failed to compute certificate thumbprint." - return 1 - fi - _debug "Thumbprint: $_thumb" - - _debug "Building PFX at $_pfx_file" - _pfx_file="$(_mktemp)" - if ! _toPkcs "$_pfx_file" "$_ckey" "$_ccert" "$_cca" "$_pfx_pass"; then - _err "Failed to build PFX archive." - rm -f "$_pfx_file" - return 1 - fi - _pfx_b64=$(_base64 "multiline" <"$_pfx_file") - rm -f "$_pfx_file" - - # ---- build installer script -------------------------------------------- - if [ "$DEPLOY_WIN_RDP_RESTART" = "1" ]; then - _restart_ps='Restart-Service -Name TermService -Force' - else - _restart_ps='# New RdP connections will pick up the new cert automatically.' - fi - - # Escape every literal `$` with `\$` so the shell does not expand it. - # Values substituted from shell: $_pfx_b64, $_pfx_pass, $_thumb, $_listener. - _ps1=$( - cat <&1) - test_login_page_exitcode="$?" - _debug3 "Test Login Response: ${test_login_response}" - if [ "$test_login_page_exitcode" -ne "0" ]; then - if { [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "60" ]; } || { [ "${ACME_USE_WGET:-0}" = "1" ] && [ "$test_login_page_exitcode" = "5" ]; }; then - _err "The SSL certificate at $_zyxel_switch_base_uri could not be validated." - _err "Please double check your hostname, port, and that you are actually connecting to your switch." - _err "If the problem persists then please ensure that the certificate is not self-signed, has not" - _err "expired, and matches the switch hostname. If you expect validation to fail then you can disable" - _err "certificate validation by running with --insecure." - return 1 - elif [ "${ACME_USE_WGET:-0}" = "0" ] && [ "$test_login_page_exitcode" = "56" ]; then - _debug3 "Intentionally ignore curl exit code 56 in our precheck" - else - _err "Failed to submit the initial login attempt to $_zyxel_switch_base_uri." - return 1 - fi - fi -} - -_zyxel_gs1900_login() { - # Login to the switch and set the appropriate auth cookie in _H1 - username_encoded=$(printf "%s" "$DEPLOY_ZYXEL_SWITCH_USER" | _url_encode) - password_encoded=$(_zyxel_gs1900_password_obfuscate "$DEPLOY_ZYXEL_SWITCH_PASSWORD" | _url_encode) - - login_response=$(_post "username=${username_encoded}&password=${password_encoded}&login=true;" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n') - auth_response=$(_post "authId=${login_response}&login_chk=true" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=0.html" '' "POST" "application/x-www-form-urlencoded" | tr -d '\n') - if [ "$auth_response" != "OK" ]; then - _err "Login failed due to invalid credentials." - _err "Please double check the configured username and password and try again." - return 1 - fi - - sessionid=$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'HTTPS_XSSID=[^;]*;' | tr -d ';') - _secure_debug2 "sessionid" "$sessionid" - - export _H1="Cookie: $sessionid" - _secure_debug2 "_H1" "$_H1" - - return 0 -} - -_zyxel_gs1900_validate_device_compatibility() { - # Check the switches model and firmware version and throw errors - # if this script isn't compatible. - device_info_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=12" | tr -d '\n') - - model_name=$(_zyxel_gs1900_get_model "$device_info_html") - _debug2 "model_name" "$model_name" - if [ -z "$model_name" ]; then - _err "Could not find the switch model name." - _err "Please re-run with --debug and report a bug." - return $? - fi - - if ! expr "$model_name" : "GS1900-" >/dev/null; then - _err "Switch is an unsupported model: $model_name" - return 1 - fi - - firmware_version=$(_zyxel_gs1900_get_firmware_version "$device_info_html") - _debug2 "firmware_version" "$firmware_version" - if [ -z "$firmware_version" ]; then - _err "Could not find the switch firmware version." - _err "Please re-run with --debug and report a bug." - return $? - fi - - _debug2 "_zyxel_gs1900_minimum_firmware_version" "$_zyxel_gs1900_minimum_firmware_version" - minimum_major_version=$(_zyxel_gs1900_parse_major_version "$_zyxel_gs1900_minimum_firmware_version") - _debug2 "minimum_major_version" "$minimum_major_version" - minimum_minor_version=$(_zyxel_gs1900_parse_minor_version "$_zyxel_gs1900_minimum_firmware_version") - _debug2 "minimum_minor_version" "$minimum_minor_version" - - _debug2 "firmware_version" "$firmware_version" - firmware_major_version=$(_zyxel_gs1900_parse_major_version "$firmware_version") - _debug2 "firmware_major_version" "$firmware_major_version" - firmware_minor_version=$(_zyxel_gs1900_parse_minor_version "$firmware_version") - _debug2 "firmware_minor_version" "$firmware_minor_version" - - _ret=0 - if [ "$firmware_major_version" -lt "$minimum_major_version" ]; then - _ret=1 - elif [ "$firmware_major_version" -eq "$minimum_major_version" ] && [ "$firmware_minor_version" -lt "$minimum_minor_version" ]; then - _ret=1 - fi - - if [ "$_ret" != "0" ]; then - _err "Unsupported firmware version $firmware_version. Please upgrade to at least version $_zyxel_gs1900_minimum_firmware_version." - fi - - return $? -} - -_zyxel_gs1900_should_update() { - # Get the remote certificate serial number - _remote_cert=$(${ACME_OPENSSL_BIN:-openssl} s_client -showcerts -connect "${DEPLOY_ZYXEL_SWITCH}:443" 2>/dev/null "${upload_post_request}" - - _info "Upload certificate to the switch" - - # Unfortunately we cannot rely upon the switch response across switch models - # to return a consistent body return - so we cannot inspect the result of this - # upload to determine success. - upload_response=$(_zyxel_upload_pkcs12 "${upload_post_request}" "${upload_post_boundary}" 2>&1) - _debug3 "Upload response: ${upload_response}" - rm "${upload_post_request}" - - # Pause for a few seconds to give the switch a chance to process the certificate - # For some reason I've found this to be necessary on my GS1900-24E - _debug2 "Waiting 4 seconds for the switch to process the newly uploaded certificate." - sleep "4" - - # Check to see whether or not our update was successful - _ret=0 - _zyxel_gs1900_should_update - if [ "$?" != "0" ]; then - _info "The certificate was updated successfully" - else - _ret=1 - _err "The certificate upload does not appear to have worked." - _err "The remote certificate does not match the certificate we tried to upload." - _err "Please re-run with --debug 2 and review for unexpected errors. If none can be found please submit a bug." - fi - - # ensure the temporary files are cleaned up - [ -f "${temp_pkcs12}" ] && rm -f "${temp_pkcs12}" - - return $_ret -} - -# make the certificate upload request using either -# --data binary with @ for file access in CURL -# or using --post-file for wget to ensure we upload -# the pkcs12 without getting tripped up on null bytes -# -# Usage _zyxel_upload_pkcs12 [body file name] [post boundary marker] -_zyxel_upload_pkcs12() { - bodyfilename="$1" - multipartformmarker="$2" - _post_url="${_zyxel_switch_base_uri}/cgi-bin/httpuploadcert.cgi" - httpmethod="POST" - _postContentType="multipart/form-data; boundary=${multipartformmarker}" - - if [ -z "$httpmethod" ]; then - httpmethod="POST" - fi - _debug $httpmethod - _debug "_post_url" "$_post_url" - _debug2 "bodyfilename" "$bodyfilename" - _debug2 "_postContentType" "$_postContentType" - - _inithttp - - if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then - _CURL="$_ACME_CURL" - if [ "$HTTPS_INSECURE" ]; then - _CURL="$_CURL --insecure " - fi - if [ "$httpmethod" = "HEAD" ]; then - _CURL="$_CURL -I " - fi - _debug "_CURL" "$_CURL" - - response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data-binary "@${bodyfilename}" "$_post_url")" - - _ret="$?" - if [ "$_ret" != "0" ]; then - _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret" - if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then - _err "Here is the curl dump log:" - _err "$(cat "$_CURL_DUMP")" - fi - fi - elif [ "$_ACME_WGET" ]; then - _WGET="$_ACME_WGET" - if [ "$HTTPS_INSECURE" ]; then - _WGET="$_WGET --no-check-certificate " - fi - _debug "_WGET" "$_WGET" - - response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-file="${bodyfilename}" "$_post_url" 2>"$HTTP_HEADER")" - - _ret="$?" - if [ "$_ret" = "8" ]; then - _ret=0 - _debug "wget returned 8 as the server returned a 'Bad Request' response. Let's process the response later." - fi - if [ "$_ret" != "0" ]; then - _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret" - fi - if _contains "$_WGET" " -d "; then - # Demultiplex wget debug output - cat "$HTTP_HEADER" >&2 - _sed_i '/^[^ ][^ ]/d; /^ *$/d' "$HTTP_HEADER" - fi - # remove leading whitespaces from header to match curl format - _sed_i 's/^ //g' "$HTTP_HEADER" - else - _ret="$?" - _err "Neither curl nor wget have been found, cannot make $httpmethod request." - fi - _debug "_ret" "$_ret" - printf "%s" "$response" - return $_ret -} - -_zyxel_gs1900_trigger_reboot() { - # Trigger a reboot via the management reboot page in the web ui - reboot_page_html=$(_get "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi?cmd=5888" | tr -d '\n') - reboot_xss_value=$(printf "%s" "$reboot_page_html" | _egrep_o 'name="XSSID"\s*value="[^"]+"' | sed 's/^.*="\([^"]\{1,\}\)"$/\1/g') - _secure_debug2 "reboot_xss_value" "$reboot_xss_value" - - reboot_response_html=$(_post "XSSID=${reboot_xss_value}&cmd=5889&sysSubmit=Reboot" "${_zyxel_switch_base_uri}/cgi-bin/dispatcher.cgi" '' "POST" "application/x-www-form-urlencoded") - reboot_message=$(printf "%s" "$reboot_response_html" | tr -d '\t\r\n\v\f' | _egrep_o "Rebooting now...") - - if [ -z "$reboot_message" ]; then - _err "Failed to trigger switch reboot!" - return 1 - fi - - return 0 -} - -# password -_zyxel_gs1900_password_obfuscate() { - # Return the password obfuscated via the same method used by the - # switch's web UI login process - echo "$1" | awk '{ - encoded = ""; - password = $1; - allowed = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - len = length($1); - pwi = length($1); - - for (i=1; i <= (321 - pwi); i++) - { - if (0 == i % 5 && pwi > 0) - { - encoded = (encoded)(substr(password, pwi--, 1)); - } - else if (i == 123) - { - if (len < 10) - { - encoded = (encoded)(0); - } - else - { - encoded = (encoded)(int(len / 10)); - } - } - else if (i == 289) - { - encoded = (encoded)(len % 10) - } - else - { - encoded = (encoded)(substr(allowed, int(rand() * length(allowed)), 1)) - } - } - printf("%s", encoded); - }' -} - -# html label -_zyxel_html_table_lookup() { - # Look up a value in the html representing the status page of the switch - # when provided with the html of the page and the label (i.e. "Model Name:") - html="$1" - label=$(printf "%s" "$2" | tr -d ' ') - lookup_result=$(printf "%s" "$html" | tr -d "\t\r\n\v\f" | sed 's//\n/g' | sed 's/]*>//g' | tr -d ' ' | grep -i "$label" | sed "s/$label<\/td>\([^<]\{1,\}\)<\/td><\/tr>/\1/i") - printf "%s" "$lookup_result" - return 0 -} - -# html -_zyxel_gs1900_get_model() { - html="$1" - model_name=$(_zyxel_html_table_lookup "$html" "Model Name:") - printf "%s" "$model_name" -} - -# html -_zyxel_gs1900_get_firmware_version() { - html="$1" - firmware_version=$(_zyxel_html_table_lookup "$html" "Firmware Version:" | _egrep_o "V[^.]+.[^(]+") - printf "%s" "$firmware_version" -} - -# version_number -_zyxel_gs1900_parse_major_version() { - printf "%s" "$1" | sed 's/^V\([0-9]\{1,\}\).\{1,\}$/\1/gi' -} - -# version_number -_zyxel_gs1900_parse_minor_version() { - printf "%s" "$1" | sed 's/^.\{1,\}\.\([0-9]\{1,\}\)$/\1/gi' -} diff --git a/dnsapi/dns_1984hosting.sh b/dnsapi/dns_1984hosting.sh index 8ed9b8ef..6accc597 100755 --- a/dnsapi/dns_1984hosting.sh +++ b/dnsapi/dns_1984hosting.sh @@ -1,43 +1,46 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_1984hosting_info='1984.hosting -Domains: 1984.is -Site: 1984.hosting -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_1984hosting -Options: - One984HOSTING_Username Username - One984HOSTING_Password Password - One984HOSTING_TOTP_Secret Base32 TOTP shared secret. Required only if the account has 2FA enabled. Requires oathtool. Used to mint the OTP code automatically at login so cron renewals keep working. -Issues: github.com/acmesh-official/acme.sh/issues/2851 -Author: Adrian Fedoreanu -' +#This file name is "dns_1984hosting.sh" +#So, here must be a method dns_1984hosting_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. -######## Public functions ##################### +#Author: Adrian Fedoreanu +#Report Bugs here: https://github.com/acmesh-official/acme.sh +# or here... https://github.com/acmesh-official/acme.sh/issues/2851 +# +######## Public functions ##################### -# Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -# Add a text record. +# Export 1984HOSTING username and password in following variables +# +# One984HOSTING_Username=username +# One984HOSTING_Password=password +# +# sessionid cookie is saved in ~/.acme.sh/account.conf +# username/password need to be set only when changed. + +#Usage: dns_1984hosting_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_1984hosting_add() { fulldomain=$1 txtvalue=$2 - _info "Add TXT record using 1984Hosting." + _info "Add TXT record using 1984Hosting" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" if ! _1984hosting_login; then - _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file." + _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file" return 1 fi - _debug "First detect the root zone." + _debug "First detect the root zone" if ! _get_root "$fulldomain"; then - _err "Invalid domain '$fulldomain'." + _err "invalid domain" "$fulldomain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _debug "Add TXT record $fulldomain with value '$txtvalue'." + _debug "Add TXT record $fulldomain with value '$txtvalue'" value="$(printf '%s' "$txtvalue" | _url_encode)" url="https://1984.hosting/domains/entry/" @@ -50,131 +53,102 @@ dns_1984hosting_add() { _debug2 postdata "$postdata" _authpost "$postdata" "$url" - if _contains "$_response" '"haserrors": true'; then - _err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post." + response="$(echo "$_response" | _normalizeJson)" + _debug2 response "$response" + + if _contains "$response" '"haserrors": true'; then + _err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post" return 1 - elif _contains "$_response" "html>"; then - _err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file." + elif _contains "$response" "html>"; then + _err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file" return 1 - elif _contains "$_response" '"auth": false'; then - _err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie." + elif _contains "$response" '"auth": false'; then + _err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie" return 1 fi - _info "Added acme challenge TXT record for $fulldomain at 1984Hosting." + _info "Added acme challenge TXT record for $fulldomain at 1984Hosting" return 0 } -# Usage: fulldomain txtvalue -# Remove the txt record after validation. +#Usage: fulldomain txtvalue +#Remove the txt record after validation. dns_1984hosting_rm() { fulldomain=$1 txtvalue=$2 - _info "Delete TXT record using 1984Hosting." + _info "Delete TXT record using 1984Hosting" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" if ! _1984hosting_login; then - _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file." + _err "1984Hosting login failed for user $One984HOSTING_Username. Check $HTTP_HEADER file" return 1 fi - _debug "First detect the root zone." + _debug "First detect the root zone" if ! _get_root "$fulldomain"; then - _err "Invalid domain '$fulldomain'." + _err "invalid domain" "$fulldomain" return 1 fi _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _debug "Delete $fulldomain TXT record." + _debug "Delete $fulldomain TXT record" url="https://1984.hosting/domains" if ! _get_zone_id "$url" "$_domain"; then - _err "Invalid zone '$_domain'." + _err "invalid zone" "$_domain" return 1 fi _htmlget "$url/$_zone_id" "$txtvalue" + _debug2 _response "$_response" entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')" _debug2 entry_id "$entry_id" if [ -z "$entry_id" ]; then - _err "Error getting TXT entry_id for $1." + _err "Error getting TXT entry_id for $1" return 1 fi _authpost "entry=$entry_id" "$url/delentry/" - if ! _contains "$_response" '"ok": true'; then - _err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post." + response="$(echo "$_response" | _normalizeJson)" + _debug2 response "$response" + + if ! _contains "$response" '"ok": true'; then + _err "1984Hosting failed to delete TXT record for $entry_id bad RC from _post" return 1 fi - _info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting." + _info "Deleted acme challenge TXT record for $fulldomain at 1984Hosting" return 0 } #################### Private functions below ################################## + +# usage: _1984hosting_login username password +# returns 0 success _1984hosting_login() { if ! _check_credentials; then return 1; fi if _check_cookies; then - _debug "Already logged in." + _debug "Already logged in" return 0 fi - _debug "Login to 1984Hosting as user $One984HOSTING_Username." + _debug "Login to 1984Hosting as user $One984HOSTING_Username" username=$(printf '%s' "$One984HOSTING_Username" | _url_encode) password=$(printf '%s' "$One984HOSTING_Password" | _url_encode) + url="https://1984.hosting/accounts/checkuserauth/" - # When 2FA is enabled, mint a fresh TOTP code from the stored shared secret. - # Empty otpkey is accepted by the server when 2FA is off. - otpkey="" - if [ -n "$One984HOSTING_TOTP_Secret" ]; then - if ! _exists oathtool; then - _err "oathtool is required to use One984HOSTING_TOTP_Secret for 2FA. Please install it." - return 1 - fi - otpcode="$(oathtool --base32 --totp "$One984HOSTING_TOTP_Secret" 2>/dev/null)" - if [ -z "$otpcode" ]; then - _err "Failed to generate TOTP code from One984HOSTING_TOTP_Secret." - return 1 - fi - otpkey="$(printf '%s' "$otpcode" | _url_encode)" - fi - - # Fetch the login page to obtain CSRF and session cookies. - # Note: _get sets the global 'url', so assign the auth URL afterwards. - _get "https://1984.hosting/accounts/login/" >/dev/null - csrftoken="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')" - sessionid="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')" - - if [ -z "$csrftoken" ] || [ -z "$sessionid" ]; then - _err "One or more cookies are empty: '$csrftoken', '$sessionid'." - return 1 - fi - - export _H1="Cookie: $csrftoken; $sessionid" - export _H2="Referer: https://1984.hosting/accounts/login/" - csrf_header=$(echo "$csrftoken" | sed 's/csrftoken=//' | _head_n 1) - export _H3="X-CSRFToken: $csrf_header" - - url="https://1984.hosting/api/auth/" - response="$(_post "username=$username&password=$password&otpkey=$otpkey" "$url")" + response="$(_post "username=$username&password=$password&otpkey=" $url)" response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" if _contains "$response" '"loggedin": true'; then - One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'cookie1984nammnamm=[^;]*;' | _head_n 1 | tr -d ';')" - One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | _head_n 1 | tr -d ';')" + One984HOSTING_SESSIONID_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'sessionid=[^;]*;' | tr -d ';')" + One984HOSTING_CSRFTOKEN_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _egrep_o 'csrftoken=[^;]*;' | tr -d ';')" export One984HOSTING_SESSIONID_COOKIE export One984HOSTING_CSRFTOKEN_COOKIE - _saveaccountconf_mutable One984HOSTING_Username "$One984HOSTING_Username" - _saveaccountconf_mutable One984HOSTING_Password "$One984HOSTING_Password" - if [ -n "$One984HOSTING_TOTP_Secret" ]; then - _saveaccountconf_mutable One984HOSTING_TOTP_Secret "$One984HOSTING_TOTP_Secret" - else - _clearaccountconf_mutable One984HOSTING_TOTP_Secret - fi _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE" _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE" return 0 @@ -183,14 +157,9 @@ _1984hosting_login() { } _check_credentials() { - One984HOSTING_Username="${One984HOSTING_Username:-$(_readaccountconf_mutable One984HOSTING_Username)}" - One984HOSTING_Password="${One984HOSTING_Password:-$(_readaccountconf_mutable One984HOSTING_Password)}" - One984HOSTING_TOTP_Secret="${One984HOSTING_TOTP_Secret:-$(_readaccountconf_mutable One984HOSTING_TOTP_Secret)}" if [ -z "$One984HOSTING_Username" ] || [ -z "$One984HOSTING_Password" ]; then One984HOSTING_Username="" One984HOSTING_Password="" - _clearaccountconf_mutable One984HOSTING_Username - _clearaccountconf_mutable One984HOSTING_Password _err "You haven't specified 1984Hosting username or password yet." _err "Please export as One984HOSTING_Username / One984HOSTING_Password and try again." return 1 @@ -202,43 +171,42 @@ _check_cookies() { One984HOSTING_SESSIONID_COOKIE="${One984HOSTING_SESSIONID_COOKIE:-$(_readaccountconf_mutable One984HOSTING_SESSIONID_COOKIE)}" One984HOSTING_CSRFTOKEN_COOKIE="${One984HOSTING_CSRFTOKEN_COOKIE:-$(_readaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE)}" if [ -z "$One984HOSTING_SESSIONID_COOKIE" ] || [ -z "$One984HOSTING_CSRFTOKEN_COOKIE" ]; then - _debug "No cached cookie(s) found." + _debug "No cached cookie(s) found" return 1 fi - _authget "https://1984.hosting/api/auth/" - if _contains "$_response" '"ok": true'; then - _debug "Cached cookies still valid." + _authget "https://1984.hosting/accounts/loginstatus/" + if _contains "$response" '"ok": true'; then + _debug "Cached cookies still valid" return 0 fi - - _debug "Cached cookies no longer valid. Clearing cookies." + _debug "Cached cookies no longer valid" One984HOSTING_SESSIONID_COOKIE="" One984HOSTING_CSRFTOKEN_COOKIE="" - _clearaccountconf_mutable One984HOSTING_SESSIONID_COOKIE - _clearaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE + _saveaccountconf_mutable One984HOSTING_SESSIONID_COOKIE "$One984HOSTING_SESSIONID_COOKIE" + _saveaccountconf_mutable One984HOSTING_CSRFTOKEN_COOKIE "$One984HOSTING_CSRFTOKEN_COOKIE" return 1 } -# _acme-challenge.www.domain.com -# Returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com _get_root() { domain="$1" i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) - # not valid if [ -z "$h" ]; then + #not valid return 1 fi - _authget "https://1984.hosting/domains/zonestatus/$h/?cached=no" - if _contains "$_response" '"ok": true'; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _authget "https://1984.hosting/domains/soacheck/?zone=$h&nameserver=ns0.1984.is." + if _contains "$_response" "serial" && ! _contains "$_response" "null"; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi @@ -248,52 +216,46 @@ _get_root() { return 1 } -# Usage: _get_zone_id url domain.com -# Returns zone id for domain.com -# Memoized per-domain so add/rm don't re-fetch the same zone list within a run. -# Keyed on domain (not url) since the url is always the domains listing. +#usage: _get_zone_id url domain.com +#returns zone id for domain.com _get_zone_id() { url=$1 domain=$2 - if [ "$_zone_id_for" = "$domain" ] && [ -n "$_zone_id" ]; then - _debug2 _zone_id "$_zone_id (cached)" - return 0 - fi _htmlget "$url" "$domain" + _debug2 _response "$_response" _zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+' | _head_n 1)" _debug2 _zone_id "$_zone_id" if [ -z "$_zone_id" ]; then - _err "Error getting _zone_id for $2." + _err "Error getting _zone_id for $2" return 1 fi - _zone_id_for="$domain" return 0 } -# Add extra headers to request +# add extra headers to request _authget() { - export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE" + export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE" _response=$(_get "$1" | _normalizeJson) _debug2 _response "$_response" } -# Truncate huge HTML response +# truncate huge HTML response +# echo: Argument list too long _htmlget() { - export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE" + export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE" _response=$(_get "$1" | grep "$2") if _contains "$_response" "@$2"; then _response=$(echo "$_response" | grep -v "[@]" | _head_n 1) fi - _debug2 _response "$_response" } -# Add extra headers to request +# add extra headers to request _authpost() { - _get_zone_id "https://1984.hosting/domains" "$_domain" - csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | sed 's/csrftoken=//' | _head_n 1)" - export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE; $One984HOSTING_SESSIONID_COOKIE" + url="https://1984.hosting/domains" + _get_zone_id "$url" "$_domain" + csrf_header="$(echo "$One984HOSTING_CSRFTOKEN_COOKIE" | _egrep_o "=[^=][0-9a-zA-Z]*" | tr -d "=")" + export _H1="Cookie: $One984HOSTING_CSRFTOKEN_COOKIE;$One984HOSTING_SESSIONID_COOKIE" export _H2="Referer: https://1984.hosting/domains/$_zone_id" export _H3="X-CSRFToken: $csrf_header" - _response="$(_post "$1" "$2" | _normalizeJson)" - _debug2 _response "$_response" + _response=$(_post "$1" "$2") } diff --git a/dnsapi/dns_acmedns.sh b/dnsapi/dns_acmedns.sh index a21f8ef0..057f9742 100755 --- a/dnsapi/dns_acmedns.sh +++ b/dnsapi/dns_acmedns.sh @@ -1,18 +1,18 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_acmedns_info='acme-dns Server API - The acme-dns is a limited DNS server with RESTful API to handle ACME DNS challenges. -Site: github.com/joohoi/acme-dns -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_acmedns -Options: - ACMEDNS_USERNAME Username. Optional. - ACMEDNS_PASSWORD Password. Optional. - ACMEDNS_SUBDOMAIN Subdomain. Optional. - ACMEDNS_BASE_URL API endpoint. Default: "https://auth.acme-dns.io". -Issues: github.com/dampfklon/acme.sh -Author: Wolfgang Ebner, Sven Neubuaer -' - +# +#Author: Wolfgang Ebner +#Author: Sven Neubuaer +#Report Bugs here: https://github.com/dampfklon/acme.sh +# +# Usage: +# export ACMEDNS_BASE_URL="https://auth.acme-dns.io" +# +# You can optionally define an already existing account: +# +# export ACMEDNS_USERNAME="" +# export ACMEDNS_PASSWORD="" +# export ACMEDNS_SUBDOMAIN="" +# ######## Public functions ##################### #Usage: dns_acmedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -37,16 +37,6 @@ dns_acmedns_add() { ACMEDNS_PASSWORD="${ACMEDNS_PASSWORD:-$(_readdomainconf ACMEDNS_PASSWORD)}" ACMEDNS_SUBDOMAIN="${ACMEDNS_SUBDOMAIN:-$(_readdomainconf ACMEDNS_SUBDOMAIN)}" - #for compatibility: old versions stored ACMEDNS_UPDATE_URL in the account - #conf (issue 3899). Do not clear it here: it must stay available for the - #other domains that have not migrated to their domain conf yet. - if [ -z "$ACMEDNS_BASE_URL" ]; then - _acmedns_update_url="$(_readaccountconf_mutable ACMEDNS_UPDATE_URL)" - if [ "$_acmedns_update_url" ]; then - ACMEDNS_BASE_URL="$(echo "$_acmedns_update_url" | sed 's#/update$##')" - fi - fi - if [ "$ACMEDNS_BASE_URL" = "" ]; then ACMEDNS_BASE_URL="https://auth.acme-dns.io" fi @@ -81,7 +71,7 @@ dns_acmedns_add() { data="{\"subdomain\":\"$ACMEDNS_SUBDOMAIN\", \"txt\": \"$txtvalue\"}" _debug data "$data" - response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST" "application/json")" + response="$(_post "$data" "$ACMEDNS_UPDATE_URL" "" "POST")" _debug response "$response" if ! echo "$response" | grep "\"$txtvalue\"" >/dev/null; then diff --git a/dnsapi/dns_acmeproxy.sh b/dnsapi/dns_acmeproxy.sh old mode 100755 new mode 100644 index a699f645..9d5533f9 --- a/dnsapi/dns_acmeproxy.sh +++ b/dnsapi/dns_acmeproxy.sh @@ -1,17 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_acmeproxy_info='AcmeProxy Server API - AcmeProxy can be used to as a single host in your network to request certificates through a DNS API. - Clients can connect with the one AcmeProxy host so you do not need to store DNS API credentials on every single host. -Site: github.com/mdbraber/acmeproxy -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_acmeproxy -Options: - ACMEPROXY_ENDPOINT API Endpoint - ACMEPROXY_USERNAME Username - ACMEPROXY_PASSWORD Password -Issues: github.com/acmesh-official/acme.sh/issues/2251 -Author: Maarten den Braber -' + +## Acmeproxy DNS provider to be used with acmeproxy (https://github.com/mdbraber/acmeproxy) +## API integration by Maarten den Braber +## +## Report any bugs via https://github.com/mdbraber/acme.sh dns_acmeproxy_add() { fulldomain="${1}" diff --git a/dnsapi/dns_active24.sh b/dnsapi/dns_active24.sh index 0f24c53a..862f734f 100755 --- a/dnsapi/dns_active24.sh +++ b/dnsapi/dns_active24.sh @@ -1,17 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_active24_info='Active24.cz -Site: Active24.cz -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_active24 -Options: - Active24_ApiKey API Key. Called "Identifier" in the Active24 Admin - Active24_ApiSecret API Secret. Called "Secret key" in the Active24 Admin -Issues: github.com/acmesh-official/acme.sh/issues/2059 -' -Active24_Api="https://rest.active24.cz" -# export Active24_ApiKey=ak48l3h7-ak5d-qn4t-p8gc-b6fs8c3l -# export Active24_ApiSecret=ajvkeo3y82ndsu2smvxy3o36496dcascksldncsq +#ACTIVE24_Token="sdfsdfsdfljlbjkljlkjsdfoiwje" + +ACTIVE24_Api="https://api.active24.com" + +######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record @@ -22,8 +15,8 @@ dns_active24_add() { _active24_init _info "Adding txt record" - if _active24_rest POST "/v2/service/$_service_id/dns/record" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":300}"; then - if _contains "$response" "error"; then + if _active24_rest POST "dns/$_domain/txt/v1" "{\"name\":\"$_sub_domain\",\"text\":\"$txtvalue\",\"ttl\":0}"; then + if _contains "$response" "errors"; then _err "Add txt record error." return 1 else @@ -31,7 +24,6 @@ dns_active24_add() { return 0 fi fi - _err "Add txt record error." return 1 } @@ -45,25 +37,19 @@ dns_active24_rm() { _active24_init _debug "Getting txt records" - # The API needs to send data in body in order the filter to work - # TODO: web can also add content $txtvalue to filter and then get the id from response - _active24_rest GET "/v2/service/$_service_id/dns/record" "{\"page\":1,\"descending\":true,\"sortBy\":\"name\",\"rowsPerPage\":100,\"totalRecords\":0,\"filters\":{\"type\":[\"TXT\"],\"name\":\"${_sub_domain}\"}}" - #_active24_rest GET "/v2/service/$_service_id/dns/record?rowsPerPage=100" + _active24_rest GET "dns/$_domain/records/v1" - if _contains "$response" "error"; then + if _contains "$response" "errors"; then _err "Error" return 1 fi - # Note: it might never be more than one record actually, NEEDS more INVESTIGATION - record_ids=$(printf "%s" "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2) - _debug2 record_ids "$record_ids" + hash_ids=$(echo "$response" | _egrep_o "[^{]+${txtvalue}[^}]+" | _egrep_o "hashId\":\"[^\"]+" | cut -c10-) - for redord_id in $record_ids; do - _debug "Removing record_id" "$redord_id" - _debug "txtvalue" "$txtvalue" - if _active24_rest DELETE "/v2/service/$_service_id/dns/record/$redord_id" ""; then - if _contains "$response" "error"; then + for hash_id in $hash_ids; do + _debug "Removing hash_id" "$hash_id" + if _active24_rest DELETE "dns/$_domain/$hash_id/v1" ""; then + if _contains "$response" "errors"; then _err "Unable to remove txt record." return 1 else @@ -77,17 +63,23 @@ dns_active24_rm() { return 1 } +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 - i=1 - p=1 - if ! _active24_rest GET "/v1/user/self/service"; then + if ! _active24_rest GET "dns/domains/v1"; then return 1 fi + i=2 + p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug "h" "$h" if [ -z "$h" ]; then #not valid @@ -95,7 +87,7 @@ _get_root() { fi if _contains "$response" "\"$h\"" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi @@ -105,98 +97,21 @@ _get_root() { return 1 } -_active24_init() { - Active24_ApiKey="${Active24_ApiKey:-$(_readaccountconf_mutable Active24_ApiKey)}" - Active24_ApiSecret="${Active24_ApiSecret:-$(_readaccountconf_mutable Active24_ApiSecret)}" - #Active24_ServiceId="${Active24_ServiceId:-$(_readaccountconf_mutable Active24_ServiceId)}" - - if [ -z "$Active24_ApiKey" ] || [ -z "$Active24_ApiSecret" ]; then - Active24_ApiKey="" - Active24_ApiSecret="" - _err "You don't specify Active24 api key and ApiSecret yet." - _err "Please create your key and try again." - return 1 - fi - - #save the credentials to the account conf file. - _saveaccountconf_mutable Active24_ApiKey "$Active24_ApiKey" - _saveaccountconf_mutable Active24_ApiSecret "$Active24_ApiSecret" - - _debug "A24 API CHECK" - if ! _active24_rest GET "/v2/check"; then - _err "A24 API check failed with: $response" - return 1 - fi - - if ! echo "$response" | tr -d " " | grep \"verified\":true >/dev/null; then - _err "A24 API check failed with: $response" - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - _active24_get_service_id "$_domain" - _debug _service_id "$_service_id" -} - -_active24_get_service_id() { - _d=$1 - if ! _active24_rest GET "/v1/user/self/zone/${_d}"; then - return 1 - else - response=$(echo "$response" | _json_decode) - _service_id=$(echo "$response" | _egrep_o '"id" *: *[^,]+' | cut -d ':' -f 2) - fi -} - _active24_rest() { m=$1 - ep_qs=$2 # with query string - # ep=$2 - ep=$(printf "%s" "$ep_qs" | cut -d '?' -f1) # no query string + ep="$2" data="$3" + _debug "$ep" - _debug "A24 $ep" - _debug "A24 $Active24_ApiKey" - _debug "A24 $Active24_ApiSecret" - - timestamp=$(_time) - datez=$(date -u +"%Y%m%dT%H%M%SZ") - canonicalRequest="${m} ${ep} ${timestamp}" - signature=$(printf "%s" "$canonicalRequest" | _hmac sha1 "$(printf "%s" "$Active24_ApiSecret" | _hex_dump | tr -d " ")" hex) - authorization64="$(printf "%s:%s" "$Active24_ApiKey" "$signature" | _base64)" - - export _H1="Date: ${datez}" - export _H2="Accept: application/json" - export _H3="Content-Type: application/json" - export _H4="Authorization: Basic ${authorization64}" - - _debug2 H1 "$_H1" - _debug2 H2 "$_H2" - _debug2 H3 "$_H3" - _debug2 H4 "$_H4" - - # _sleep 1 + export _H1="Authorization: Bearer $ACTIVE24_Token" if [ "$m" != "GET" ]; then - _debug2 "${m} $Active24_Api${ep_qs}" _debug "data" "$data" - response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")" + response="$(_post "$data" "$ACTIVE24_Api/$ep" "" "$m" "application/json")" else - if [ -z "$data" ]; then - _debug2 "GET $Active24_Api${ep_qs}" - response="$(_get "$Active24_Api${ep_qs}")" - else - _debug2 "GET $Active24_Api${ep_qs} with data: ${data}" - response="$(_post "$data" "$Active24_Api${ep_qs}" "" "$m" "application/json")" - fi + response="$(_get "$ACTIVE24_Api/$ep")" fi + if [ "$?" != "0" ]; then _err "error $ep" return 1 @@ -204,3 +119,23 @@ _active24_rest() { _debug2 response "$response" return 0 } + +_active24_init() { + ACTIVE24_Token="${ACTIVE24_Token:-$(_readaccountconf_mutable ACTIVE24_Token)}" + if [ -z "$ACTIVE24_Token" ]; then + ACTIVE24_Token="" + _err "You didn't specify a Active24 api token yet." + _err "Please create the token and try again." + return 1 + fi + + _saveaccountconf_mutable ACTIVE24_Token "$ACTIVE24_Token" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" +} diff --git a/dnsapi/dns_ad.sh b/dnsapi/dns_ad.sh index 850af5b4..fc4a664b 100755 --- a/dnsapi/dns_ad.sh +++ b/dnsapi/dns_ad.sh @@ -1,13 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ad_info='AlwaysData.com -Site: AlwaysData.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ad -Options: - AD_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/pull/503 -Author: Paul Koppen -' + +# +#AD_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" + +#This is the Alwaysdata api wrapper for acme.sh +# +#Author: Paul Koppen +#Report Bugs here: https://github.com/wpk-/acme.sh AD_API_URL="https://$AD_API_KEY:@api.alwaysdata.com/v1" @@ -95,7 +94,7 @@ _get_root() { if _ad_rest GET "domain/"; then response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -106,7 +105,7 @@ _get_root() { if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_ali.sh b/dnsapi/dns_ali.sh index b8ca9169..c2105672 100755 --- a/dnsapi/dns_ali.sh +++ b/dnsapi/dns_ali.sh @@ -1,56 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ali_info='AlibabaCloud.com -Domains: Aliyun.com -Site: AlibabaCloud.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ali -Options: - Ali_Key API Key - Ali_Secret API Secret -' -# NOTICE: -# This file is referenced by Alibaba Cloud Services deploy hooks -# https://github.com/acmesh-official/acme.sh/pull/5205#issuecomment-2357867276 -# Be careful when modifying this file, especially when making breaking changes for common functions +Ali_API="https://alidns.aliyuncs.com/" -Ali_DNS_API="https://alidns.aliyuncs.com/" +#Ali_Key="LTqIA87hOKdjevsf5" +#Ali_Secret="0p5EYueFNq501xnCPzKNbx6K51qPH2" #Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_ali_add() { - # the API only accepts punycode for IDN domains, and a raw UTF-8 domain - # also breaks the request signature (issue 4733) - fulldomain=$(_idn "$1") + fulldomain=$1 txtvalue=$2 - _prepare_ali_credentials || return 1 - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - return 1 - fi - - _debug "Add record" - _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record" -} - -dns_ali_rm() { - fulldomain=$(_idn "$1") - txtvalue=$2 - Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" - Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - return 1 - fi - - _clean -} - -#################### Alibaba Cloud common functions below #################### - -_prepare_ali_credentials() { Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then @@ -63,74 +22,38 @@ _prepare_ali_credentials() { #save the api key and secret to the account conf file. _saveaccountconf_mutable Ali_Key "$Ali_Key" _saveaccountconf_mutable Ali_Secret "$Ali_Secret" -} -# act ign mtd -_ali_rest() { - act="$1" - ign="$2" - mtd="${3:-GET}" - - signature=$(printf "%s" "$mtd&%2F&$(printf "%s" "$query" | _ali_urlencode_upper)" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64) - signature=$(printf "%s" "$signature" | _ali_urlencode_upper) - url="$endpoint?Signature=$signature" - - if [ "$mtd" = "GET" ]; then - url="$url&$query" - response="$(_get "$url")" - else - response="$(_post "$query" "$url" "" "$mtd" "application/x-www-form-urlencoded")" - fi - - _ret="$?" - _debug2 response "$response" - if [ "$_ret" != "0" ]; then - _err "Error <$act>" + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then return 1 fi - if [ -z "$ign" ]; then - message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" - if [ "$message" ]; then - _err "$message" - return 1 - fi + _debug "Add record" + _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record" +} + +dns_ali_rm() { + fulldomain=$1 + txtvalue=$2 + Ali_Key="${Ali_Key:-$(_readaccountconf_mutable Ali_Key)}" + Ali_Secret="${Ali_Secret:-$(_readaccountconf_mutable Ali_Secret)}" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + return 1 fi + + _clean } -# stdin stdout -# The Aliyun signature requires percent-encoding with upper-case hex. -# Do not use "_url_encode upper-hex" here: this file is also bundled by -# third parties (e.g. Proxmox VE proxmox-acme) whose older copies of the -# acme.sh function library ignore the upper-hex argument and output -# lower-case hex, which invalidates the signature. -# https://github.com/acmesh-official/acme.sh/issues/6272 -_ali_urlencode_upper() { - { - _url_encode - echo - } | sed 's/%a/%A/g;s/%b/%B/g;s/%c/%C/g;s/%d/%D/g;s/%e/%E/g;s/%f/%F/g;s/%\(.\)a/%\1A/g;s/%\(.\)b/%\1B/g;s/%\(.\)c/%\1C/g;s/%\(.\)d/%\1D/g;s/%\(.\)e/%\1E/g;s/%\(.\)f/%\1F/g' -} - -_ali_nonce() { - if [ "$ACME_OPENSSL_BIN" ]; then - "$ACME_OPENSSL_BIN" rand -hex 16 2>/dev/null && return 0 - fi - printf "%s" "$(date +%s)$$$(date +%N)" | _digest sha256 hex | cut -c 1-32 -} - -_ali_timestamp() { - date -u +"%Y-%m-%dT%H%%3A%M%%3A%SZ" -} - -#################### Private functions below #################### +#################### Private functions below ################################## _get_root() { domain=$1 - i=1 + i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -142,7 +65,7 @@ _get_root() { fi if _contains "$response" "PageNumber"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _debug _sub_domain "$_sub_domain" _domain="$h" _debug _domain "$_domain" @@ -154,10 +77,52 @@ _get_root() { return 1 } +_ali_rest() { + signature=$(printf "%s" "GET&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64) + signature=$(_ali_urlencode "$signature") + url="$Ali_API?$query&Signature=$signature" + + if ! response="$(_get "$url")"; then + _err "Error <$1>" + return 1 + fi + + _debug2 response "$response" + if [ -z "$2" ]; then + message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" + if [ "$message" ]; then + _err "$message" + return 1 + fi + fi +} + +_ali_urlencode() { + _str="$1" + _str_len=${#_str} + _u_i=1 + while [ "$_u_i" -le "$_str_len" ]; do + _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")" + case $_str_c in [a-zA-Z0-9.~_-]) + printf "%s" "$_str_c" + ;; + *) + printf "%%%02X" "'$_str_c" + ;; + esac + _u_i="$(_math "$_u_i" + 1)" + done +} + +_ali_nonce() { + #_head_n 1 "$HTTP_HEADER" - fi -} - -# -# Usage: _ab_call_is_success -# -# Check whether a call's response http status is one of 200, 201, 202 or 204 (other 2xx are not handled) -# -# Variables -# _status -# _http_status -# _success_http_codes -# HTTP_HEADER -# -_ab_call_is_success() { - _success_http_codes="200 201 202 204" - if [ -f "$HTTP_HEADER" ]; then - _http_status=$(_egrep_o "^HTTP[\/0-9. ]*" <"$HTTP_HEADER" | _head_n 1 | cut -d " " -f 2) - for _status in $_success_http_codes; do - if [ "$_status" = "$_http_status" ]; then - return 0 - fi - done - fi - - return 1 -} diff --git a/dnsapi/dns_arvan.sh b/dnsapi/dns_arvan.sh index cbe6dc1f..4c9217e5 100644 --- a/dnsapi/dns_arvan.sh +++ b/dnsapi/dns_arvan.sh @@ -1,16 +1,11 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_arvan_info='ArvanCloud.ir -Site: ArvanCloud.ir -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_arvan -Options: - Arvan_Token API Token -Issues: github.com/acmesh-official/acme.sh/issues/2796 -Author: Vahid Fardi -' -ARVAN_API_URL="https://napi.arvancloud.ir/cdn/4.0/domains" +#Arvan_Token="Apikey xxxx" +ARVAN_API_URL="https://napi.arvancloud.com/cdn/4.0/domains" +#Author: Vahid Fardi +#Report Bugs here: https://github.com/Neilpang/acme.sh +# ######## Public functions ##################### #Usage: dns_arvan_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -23,7 +18,7 @@ dns_arvan_add() { if [ -z "$Arvan_Token" ]; then _err "You didn't specify \"Arvan_Token\" token yet." - _err "You can get yours from here https://npanel.arvancloud.ir/profile/api-keys" + _err "You can get yours from here https://npanel.arvancloud.com/profile/api-keys" return 1 fi #save the api token to the account conf file. @@ -45,7 +40,7 @@ dns_arvan_add() { _info "response id is $response" _info "Added, OK" return 0 - elif _contains "$response" "Record Data is duplicate"; then + elif _contains "$response" "Record Data is Duplicated"; then _info "Already exists, OK" return 0 else @@ -107,7 +102,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -120,7 +115,7 @@ _get_root() { if _contains "$response" "\"domain\":\"$h\""; then _domain_id=$(echo "$response" | cut -d : -f 3 | cut -d , -f 1 | tr -d \") if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi @@ -146,7 +141,6 @@ _arvan_rest() { response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")" elif [ "$mtd" = "POST" ]; then export _H2="Content-Type: application/json" - export _H3="Accept: application/json" _debug data "$data" response="$(_post "$data" "$ARVAN_API_URL/$ep" "" "$mtd")" else diff --git a/dnsapi/dns_aurora.sh b/dnsapi/dns_aurora.sh index 110ef0fa..00f44739 100644 --- a/dnsapi/dns_aurora.sh +++ b/dnsapi/dns_aurora.sh @@ -1,15 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_aurora_info='versio.nl AuroraDNS -Domains: pcextreme.nl -Site: versio.nl -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_aurora -Options: - AURORA_Key API Key - AURORA_Secret API Secret -Issues: github.com/acmesh-official/acme.sh/issues/3459 -Author: Jasper Zonneveld -' + +# +#AURORA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#AURORA_Secret="sdfsdfsdfljlbjkljlkjsdfoiwje" AURORA_Api="https://api.auroradns.eu" @@ -117,7 +111,7 @@ _get_root() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -132,7 +126,7 @@ _get_root() { _domain_id=$(echo "$response" | _normalizeJson | tr -d "{}" | tr "," "\n" | grep "\"id\": *\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_autodns.sh b/dnsapi/dns_autodns.sh index e26d699b..92534489 100644 --- a/dnsapi/dns_autodns.sh +++ b/dnsapi/dns_autodns.sh @@ -1,15 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_autodns_info='InternetX autoDNS - InternetX autoDNS XML API -Site: InternetX.com/autodns/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_autodns -Options: - AUTODNS_USER Username - AUTODNS_PASSWORD Password - AUTODNS_CONTEXT Context -Author: -' +# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*- + +# This is the InternetX autoDNS xml api wrapper for acme.sh +# Author: auerswald@gmail.com +# Created: 2018-01-14 +# +# export AUTODNS_USER="username" +# export AUTODNS_PASSWORD="password" +# export AUTODNS_CONTEXT="context" +# +# Usage: +# acme.sh --issue --dns dns_autodns -d example.com AUTODNS_API="https://gateway.autodns.com" @@ -110,7 +111,7 @@ _get_autodns_zone() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then @@ -128,7 +129,7 @@ _get_autodns_zone() { if _contains "$autodns_response" "1" >/dev/null; then _zone="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" _system_ns="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) return 0 fi @@ -139,21 +140,12 @@ _get_autodns_zone() { return 1 } -# Escape the XML special characters (& < > ' ") so that credentials -# containing them do not break the request document (issue 5317). -_autodns_xml_encode() { - sed "s/&/\&/g;s//\>/g;s/'/\'/g;s/\"/\"/g" -} - _build_request_auth_xml() { - _autodns_user_xml="$(printf "%s" "$AUTODNS_USER" | _autodns_xml_encode)" - _autodns_password_xml="$(printf "%s" "$AUTODNS_PASSWORD" | _autodns_xml_encode)" - _autodns_context_xml="$(printf "%s" "$AUTODNS_CONTEXT" | _autodns_xml_encode)" printf " %s %s %s - " "$_autodns_user_xml" "$_autodns_password_xml" "$_autodns_context_xml" + " "$AUTODNS_USER" "$AUTODNS_PASSWORD" "$AUTODNS_CONTEXT" } # Arguments: diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 1face1c8..50c93260 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -1,18 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_aws_info='Amazon AWS Route53 domain API -Site: docs.aws.amazon.com/route53/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_aws -Options: - AWS_ACCESS_KEY_ID API Key ID - AWS_SECRET_ACCESS_KEY API Secret -' -# All `_sleep` commands are included to avoid Route53 throttling, see -# https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests +# +#AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#AWS_SECRET_ACCESS_KEY="xxxxxxx" -# Updated from "route53.amazonaws.com" -AWS_HOST="route53.global.api.aws" +#This is the Amazon Route53 api wrapper for acme.sh +#All `_sleep` commands are included to avoid Route53 throttling, see +#https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/DNSLimitations.html#limits-api-requests + +AWS_HOST="route53.amazonaws.com" AWS_URL="https://$AWS_HOST" AWS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Amazon-Route53-API" @@ -148,6 +145,7 @@ dns_aws_rm() { fi _sleep 1 return 1 + } #################### Private functions below ################################## @@ -159,10 +157,10 @@ _get_root() { # iterate over names (a.b.c.d -> b.c.d -> c.d -> d) while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100 | sed 's/\./\\./g') + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug "Checking domain: $h" if [ -z "$h" ]; then - _err "invalid domain" + _error "invalid domain" return 1 fi @@ -175,7 +173,7 @@ _get_root() { if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o ".*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>") if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi @@ -209,40 +207,24 @@ _use_container_role() { } _use_instance_role() { - _instance_role_name_url="http://169.254.169.254/latest/meta-data/iam/security-credentials/" - - if _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 401; then - _debug "Using IMDSv2" - _token_url="http://169.254.169.254/latest/api/token" - export _H1="X-aws-ec2-metadata-token-ttl-seconds: 21600" - _token="$(_post "" "$_token_url" "" "PUT")" - _secure_debug3 "_token" "$_token" - if [ -z "$_token" ]; then - _debug "Unable to fetch IMDSv2 token from instance metadata" - return 1 - fi - export _H1="X-aws-ec2-metadata-token: $_token" - fi - - if ! _get "$_instance_role_name_url" true 1 | _head_n 1 | grep -Fq 200; then + _url="http://169.254.169.254/latest/meta-data/iam/security-credentials/" + _debug "_url" "$_url" + if ! _get "$_url" true 1 | _head_n 1 | grep -Fq 200; then _debug "Unable to fetch IAM role from instance metadata" return 1 fi - - _instance_role_name=$(_get "$_instance_role_name_url" "" 1) - _debug "_instance_role_name" "$_instance_role_name" - _use_metadata "$_instance_role_name_url$_instance_role_name" "$_token" - + _aws_role=$(_get "$_url" "" 1) + _debug "_aws_role" "$_aws_role" + _use_metadata "$_url$_aws_role" } _use_metadata() { - export _H1="X-aws-ec2-metadata-token: $2" _aws_creds="$( _get "$1" "" 1 | _normalizeJson | tr '{,}' '\n' | while read -r _line; do - _key="$(echo "${_line%%:*}" | tr -d '\"')" + _key="$(echo "${_line%%:*}" | tr -d '"')" _value="${_line#*:}" _debug3 "_key" "$_key" _secure_debug3 "_value" "$_value" diff --git a/dnsapi/dns_azion.sh b/dnsapi/dns_azion.sh index 1375e32f..f215686d 100644 --- a/dnsapi/dns_azion.sh +++ b/dnsapi/dns_azion.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_azion_info='Azion.om -Site: Azion.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_azion -Options: - AZION_Email Email - AZION_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/3555 -' + +# +#AZION_Email="" +#AZION_Password="" +# AZION_Api="https://api.azionapi.net" @@ -100,7 +96,7 @@ _get_root() { fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then # not valid @@ -111,7 +107,7 @@ _get_root() { _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"domain\":\"$h\"" | _egrep_o "\"id\":[0-9]*" | _head_n 1 | cut -d : -f 2 | tr -d \") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh index f9d84706..1c33c13a 100644 --- a/dnsapi/dns_azure.sh +++ b/dnsapi/dns_azure.sh @@ -1,25 +1,13 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_azure_info='Azure -Site: Azure.microsoft.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_azure -Options: - AZUREDNS_SUBSCRIPTIONID Subscription ID - AZUREDNS_TENANTID Tenant ID - AZUREDNS_APPID App ID. App ID of the service principal - AZUREDNS_CLIENTSECRET Client Secret. Secret from creating the service principal - AZUREDNS_MANAGEDIDENTITY Use Managed Identity. Use Managed Identity assigned to a resource instead of a service principal. "true"/"false" - AZUREDNS_BEARERTOKEN Bearer Token. Used instead of service principal credentials or managed identity. Optional. -' -wiki=https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS +WIKI="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-Azure-DNS" ######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record # -# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/create-or-update?view=rest-dns-2018-05-01&tabs=HTTP +# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/createorupdate # dns_azure_add() { @@ -32,7 +20,6 @@ dns_azure_add() { AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Subscription ID" return 1 fi @@ -47,45 +34,37 @@ dns_azure_add() { _saveaccountconf_mutable AZUREDNS_TENANTID "" _saveaccountconf_mutable AZUREDNS_APPID "" _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "" - _saveaccountconf_mutable AZUREDNS_BEARERTOKEN "" else - _info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token" + _info "You didn't ask to use Azure managed identity, checking service principal credentials" AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}" AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}" AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}" - AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}" - if [ -z "$AZUREDNS_BEARERTOKEN" ]; then - if [ -z "$AZUREDNS_TENANTID" ]; then - AZUREDNS_SUBSCRIPTIONID="" - AZUREDNS_TENANTID="" - AZUREDNS_APPID="" - AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" - _err "You didn't specify the Azure Tenant ID " - return 1 - fi - if [ -z "$AZUREDNS_APPID" ]; then - AZUREDNS_SUBSCRIPTIONID="" - AZUREDNS_TENANTID="" - AZUREDNS_APPID="" - AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" - _err "You didn't specify the Azure App ID" - return 1 - fi + if [ -z "$AZUREDNS_TENANTID" ]; then + AZUREDNS_SUBSCRIPTIONID="" + AZUREDNS_TENANTID="" + AZUREDNS_APPID="" + AZUREDNS_CLIENTSECRET="" + _err "You didn't specify the Azure Tenant ID " + return 1 + fi - if [ -z "$AZUREDNS_CLIENTSECRET" ]; then - AZUREDNS_SUBSCRIPTIONID="" - AZUREDNS_TENANTID="" - AZUREDNS_APPID="" - AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" - _err "You didn't specify the Azure Client Secret" - return 1 - fi - else - _info "Using provided bearer token" + if [ -z "$AZUREDNS_APPID" ]; then + AZUREDNS_SUBSCRIPTIONID="" + AZUREDNS_TENANTID="" + AZUREDNS_APPID="" + AZUREDNS_CLIENTSECRET="" + _err "You didn't specify the Azure App ID" + return 1 + fi + + if [ -z "$AZUREDNS_CLIENTSECRET" ]; then + AZUREDNS_SUBSCRIPTIONID="" + AZUREDNS_TENANTID="" + AZUREDNS_APPID="" + AZUREDNS_CLIENTSECRET="" + _err "You didn't specify the Azure Client Secret" + return 1 fi #save account details to account conf file, don't opt in for azure manages identity check. @@ -93,14 +72,9 @@ dns_azure_add() { _saveaccountconf_mutable AZUREDNS_TENANTID "$AZUREDNS_TENANTID" _saveaccountconf_mutable AZUREDNS_APPID "$AZUREDNS_APPID" _saveaccountconf_mutable AZUREDNS_CLIENTSECRET "$AZUREDNS_CLIENTSECRET" - _saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$AZUREDNS_BEARERTOKEN" fi - if [ -z "$AZUREDNS_BEARERTOKEN" ]; then - accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") - else - accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g") - fi + accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then _err "invalid domain" @@ -150,7 +124,7 @@ dns_azure_add() { # Usage: fulldomain txtvalue # Used to remove the txt record after validation # -# Ref: https://learn.microsoft.com/en-us/rest/api/dns/record-sets/delete?view=rest-dns-2018-05-01&tabs=HTTP +# Ref: https://docs.microsoft.com/en-us/rest/api/dns/recordsets/delete # dns_azure_rm() { fulldomain=$1 @@ -162,7 +136,6 @@ dns_azure_rm() { AZUREDNS_TENANTID="" AZUREDNS_APPID="" AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" _err "You didn't specify the Azure Subscription ID " return 1 fi @@ -171,51 +144,40 @@ dns_azure_rm() { if [ "$AZUREDNS_MANAGEDIDENTITY" = true ]; then _info "Using Azure managed identity" else - _info "You didn't ask to use Azure managed identity, checking service principal credentials or provided bearer token" + _info "You didn't ask to use Azure managed identity, checking service principal credentials" AZUREDNS_TENANTID="${AZUREDNS_TENANTID:-$(_readaccountconf_mutable AZUREDNS_TENANTID)}" AZUREDNS_APPID="${AZUREDNS_APPID:-$(_readaccountconf_mutable AZUREDNS_APPID)}" AZUREDNS_CLIENTSECRET="${AZUREDNS_CLIENTSECRET:-$(_readaccountconf_mutable AZUREDNS_CLIENTSECRET)}" - AZUREDNS_BEARERTOKEN="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}" - if [ -z "$AZUREDNS_BEARERTOKEN" ]; then - if [ -z "$AZUREDNS_TENANTID" ]; then - AZUREDNS_SUBSCRIPTIONID="" - AZUREDNS_TENANTID="" - AZUREDNS_APPID="" - AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" - _err "You didn't specify the Azure Tenant ID " - return 1 - fi - if [ -z "$AZUREDNS_APPID" ]; then - AZUREDNS_SUBSCRIPTIONID="" - AZUREDNS_TENANTID="" - AZUREDNS_APPID="" - AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" - _err "You didn't specify the Azure App ID" - return 1 - fi + if [ -z "$AZUREDNS_TENANTID" ]; then + AZUREDNS_SUBSCRIPTIONID="" + AZUREDNS_TENANTID="" + AZUREDNS_APPID="" + AZUREDNS_CLIENTSECRET="" + _err "You didn't specify the Azure Tenant ID " + return 1 + fi - if [ -z "$AZUREDNS_CLIENTSECRET" ]; then - AZUREDNS_SUBSCRIPTIONID="" - AZUREDNS_TENANTID="" - AZUREDNS_APPID="" - AZUREDNS_CLIENTSECRET="" - AZUREDNS_BEARERTOKEN="" - _err "You didn't specify the Azure Client Secret" - return 1 - fi - else - _info "Using provided bearer token" + if [ -z "$AZUREDNS_APPID" ]; then + AZUREDNS_SUBSCRIPTIONID="" + AZUREDNS_TENANTID="" + AZUREDNS_APPID="" + AZUREDNS_CLIENTSECRET="" + _err "You didn't specify the Azure App ID" + return 1 + fi + + if [ -z "$AZUREDNS_CLIENTSECRET" ]; then + AZUREDNS_SUBSCRIPTIONID="" + AZUREDNS_TENANTID="" + AZUREDNS_APPID="" + AZUREDNS_CLIENTSECRET="" + _err "You didn't specify the Azure Client Secret" + return 1 fi fi - if [ -z "$AZUREDNS_BEARERTOKEN" ]; then - accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") - else - accesstoken=$(echo "$AZUREDNS_BEARERTOKEN" | sed "s/Bearer //g") - fi + accesstoken=$(_azure_getaccess_token "$AZUREDNS_MANAGEDIDENTITY" "$AZUREDNS_TENANTID" "$AZUREDNS_APPID" "$AZUREDNS_CLIENTSECRET") if ! _get_root "$fulldomain" "$AZUREDNS_SUBSCRIPTIONID" "$accesstoken"; then _err "invalid domain" @@ -294,10 +256,10 @@ _azure_rest() { if [ "$_code" = "401" ]; then # we have an invalid access token set to expired _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "0" - _err "Access denied. Invalid access token. Make sure your Azure settings are correct. See: $wiki" + _err "access denied make sure your Azure settings are correct. See $WIKI" return 1 fi - # See https://learn.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes + # See https://docs.microsoft.com/en-us/azure/architecture/best-practices/retry-service-specific#general-rest-and-retry-guidelines for retryable HTTP codes if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "408" ] || [ "$_code" = "500" ] || [ "$_code" = "503" ] || [ "$_code" = "504" ]; then _request_retry_times="$(_math "$_request_retry_times" + 1)" _info "REST call error $_code retrying $ep in $_request_retry_times s" @@ -315,14 +277,14 @@ _azure_rest() { return 0 } -## Ref: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#request-an-access-token +## Ref: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-service-to-service#request-an-access-token _azure_getaccess_token() { managedIdentity=$1 tenantID=$2 clientID=$3 clientSecret=$4 - accesstoken="${AZUREDNS_ACCESSTOKEN:-$(_readaccountconf_mutable AZUREDNS_ACCESSTOKEN)}" + accesstoken="${AZUREDNS_BEARERTOKEN:-$(_readaccountconf_mutable AZUREDNS_BEARERTOKEN)}" expires_on="${AZUREDNS_TOKENVALIDTO:-$(_readaccountconf_mutable AZUREDNS_TOKENVALIDTO)}" # can we reuse the bearer token? @@ -339,18 +301,9 @@ _azure_getaccess_token() { _debug "getting new bearer token" if [ "$managedIdentity" = true ]; then - # https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http - if [ -n "$IDENTITY_ENDPOINT" ]; then - # Some Azure environments may set IDENTITY_ENDPOINT (formerly MSI_ENDPOINT) to have an alternative metadata endpoint - url="$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=https://management.azure.com/" - headers="X-IDENTITY-HEADER: $IDENTITY_HEADER" - else - url="http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/" - headers="Metadata: true" - fi - - export _H1="$headers" - response="$(_get "$url")" + # https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http + export _H1="Metadata: true" + response="$(_get http://169.254.169.254/metadata/identity/oauth2/token\?api-version=2018-02-01\&resource=https://management.azure.com/)" response="$(echo "$response" | _normalizeJson)" accesstoken=$(echo "$response" | _egrep_o "\"access_token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") expires_on=$(echo "$response" | _egrep_o "\"expires_on\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") @@ -368,14 +321,14 @@ _azure_getaccess_token() { fi if [ -z "$accesstoken" ]; then - _err "No acccess token received. Check your Azure settings. See: $wiki" + _err "no acccess token received. Check your Azure settings see $WIKI" return 1 fi if [ "$_ret" != "0" ]; then _err "error $response" return 1 fi - _saveaccountconf_mutable AZUREDNS_ACCESSTOKEN "$accesstoken" + _saveaccountconf_mutable AZUREDNS_BEARERTOKEN "$accesstoken" _saveaccountconf_mutable AZUREDNS_TOKENVALIDTO "$expires_on" printf "%s" "$accesstoken" return 0 @@ -388,18 +341,15 @@ _get_root() { i=1 p=1 - ## Ref: https://learn.microsoft.com/en-us/rest/api/dns/zones/list?view=rest-dns-2018-05-01&tabs=HTTP - ## returns up to 100 zones in one response. Handling more results is not implemented - ## (ZoneListResult with continuation token for the next page of results) - ## - ## TODO: handle more than 100 results, as per: - ## https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-dns-limits - ## The new limit is 250 Public DNS zones per subscription, while the old limit was only 100 + ## Ref: https://docs.microsoft.com/en-us/rest/api/dns/zones/list + ## returns up to 100 zones in one response therefore handling more results is not not implemented + ## (ZoneListResult with continuation token for the next page of results) + ## Per https://docs.microsoft.com/en-us/azure/azure-subscription-service-limits#dns-limits you are limited to 100 Zone/subscriptions anyways ## _azure_rest GET "https://management.azure.com/subscriptions/$subscriptionId/providers/Microsoft.Network/dnszones?\$top=500&api-version=2017-09-01" "" "$accesstoken" # Find matching domain name in Json response while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug2 "Checking domain: $h" if [ -z "$h" ]; then #not valid @@ -414,7 +364,7 @@ _get_root() { #create the record at the domain apex (@) if only the domain name was provided as --domain-alias _sub_domain="@" else - _sub_domain=$(echo "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(echo "$domain" | cut -d . -f 1-$p) fi _domain=$h return 0 diff --git a/dnsapi/dns_baidu.sh b/dnsapi/dns_baidu.sh deleted file mode 100644 index dfad8eeb..00000000 --- a/dnsapi/dns_baidu.sh +++ /dev/null @@ -1,775 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 - -# Global variables for returning results (avoid stdout pollution from logging) -_BAIDU_FIND_RESULT="" -_BAIDU_BCE_AUTH_RESULT="" - -: "${BAIDU_LOG_LEVEL:=2}" - -_baidu_log_ts() { - date -} - -_baidu_log_ge() { - _want="$1" - [ "${BAIDU_LOG_LEVEL:-0}" -ge "$_want" ] -} - -_baidu_log() { - _lvl="$1" - _tag="$2" - _msg="$3" - if [ "$_lvl" = "0" ] || _baidu_log_ge "$_lvl"; then - printf -- "[%s] %s %s\n" "$(_baidu_log_ts)" "$_tag" "$_msg" - fi -} - -_baidu_err() { - _baidu_log 0 "baidu_bcd.err" "$1" - return 1 -} - -_baidu_info() { - _baidu_log 1 "baidu_bcd.info" "$1" - return 0 -} - -_baidu_debug() { - _baidu_log 2 "$1" "$2" - return 0 -} - -dns_baidu_info='Baidu Cloud BCD DNS -Site: cloud.baidu.com -Docs: https://cloud.baidu.com/doc/BCD/ -Signature: https://cloud.baidu.com/doc/Reference/s/njwvz1yfu -Options: - Baidu_AK AccessKeyId - Baidu_SK SecretAccessKey -OptionsAlt: - Baidu_BCD_Host API host, default: bcd.baidubce.com - Baidu_DNS_Host New DNS API host, default: dns.baidubce.com - Baidu_API_Preference Engine preference, default: auto - Baidu_BCD_Version API version number, default: 1 - Baidu_BCD_Expire Signature expiration seconds, default: 3600 - Baidu_View Resolve view, default: DEFAULT - Baidu_Line New DNS line, default: default - Baidu_TTL Resolve ttl seconds, default: 300 - Baidu_RM_Max Max records to delete in one run, default: 20 -' - -BAIDU_BCD_DEFAULT_HOST="bcd.baidubce.com" -BAIDU_DNS_DEFAULT_HOST="dns.baidubce.com" - -# --- Public API --- -dns_baidu_add() { - fulldomain=$(_idn "$1") - txtvalue=$2 - - if ! _baidu_run_with_fallback "add" "$fulldomain" "$txtvalue"; then - _baidu_err "all baidu api engines failed for add: $fulldomain" - return 1 - fi - - return 0 -} - -dns_baidu_rm() { - fulldomain=$(_idn "$1") - txtvalue=$2 - - if ! _baidu_run_with_fallback "rm" "$fulldomain" "$txtvalue"; then - _baidu_err "all baidu api engines failed for delete: $fulldomain" - return 1 - fi - - return 0 -} - -_baidu_run_with_fallback() { - _action="$1" - _fulldomain="$2" - _txtvalue="$3" - - if ! _baidu_load_credentials; then - _baidu_err "baidu_load_credentials failed" - return 1 - fi - - for _baidu_api_engine in $(_baidu_engine_order); do - if ! _baidu_prepare_record "$_fulldomain"; then - _baidu_info "prepare failed for engine: $_baidu_api_engine" - continue - fi - - if [ "$_action" = "add" ]; then - if _baidu_add_record "$_txtvalue"; then - return 0 - fi - else - if _baidu_rm_record "$_txtvalue"; then - return 0 - fi - fi - - _baidu_info "engine failed, try next if available: $_baidu_api_engine" - done - - return 1 -} - -_baidu_engine_order() { - _pref="$(_lower_case "$(_baidu_trim_ws "${Baidu_API_Preference:-auto}")")" - case "$_pref" in - legacy) - printf "%s" "legacy new" - ;; - new) - printf "%s" "new legacy" - ;; - *) - printf "%s" "new legacy" - ;; - esac -} - -_baidu_add_record() { - _txtvalue="$1" - - if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then - _baidu_err "baidu_find_record_ids failed for add: $_record_domain.$_zone_name" - return 1 - fi - _existing_ids="$_BAIDU_FIND_RESULT" - if [ "$_existing_ids" ]; then - _baidu_info "txt exists, skip add: $_record_domain.$_zone_name" - return 0 - fi - - _ttl="${Baidu_TTL:-300}" - _ttl="$(_baidu_trim_ws "$_ttl")" - case "$_ttl" in - "" | *[!0-9]*) - _ttl="300" - ;; - esac - - txtvalue="$(_baidu_trim_ws "$_txtvalue")" - _record_domain="$(_baidu_trim_ws "$_record_domain")" - _zone_name="$(_baidu_trim_ws "$_zone_name")" - - if [ "$_baidu_api_engine" = "new" ]; then - _line="$(_baidu_trim_ws "${Baidu_Line:-default}")" - if [ -z "$_line" ]; then - _line="default" - fi - _body="$(_baidu_payload_add_txt_dns "$_record_domain" "$txtvalue" "$_ttl" "$_line")" - if ! _baidu_dns_call "POST" "/v1/dns/zone/${_zone_name}/record" "$_body"; then - _baidu_err "baidu_dns_call failed: add record" - return 1 - fi - else - _view="$(_baidu_trim_ws "${Baidu_View:-DEFAULT}")" - _body="$(_baidu_payload_add_txt "$_zone_name" "$_record_domain" "$txtvalue" "$_ttl" "$_view")" - if ! _baidu_bcd_post "/domain/resolve/add" "$_body"; then - _baidu_err "baidu_bcd_post failed: add record" - return 1 - fi - fi - - if _baidu_is_api_error "$response"; then - _baidu_err "$response" - return 1 - fi - - return 0 -} - -_baidu_rm_record() { - _txtvalue="$1" - - if ! _baidu_find_record_ids_current "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then - _baidu_err "baidu_find_record_ids failed for delete: $_record_domain.$_zone_name" - return 1 - fi - _ids="$_BAIDU_FIND_RESULT" - if [ -z "$_ids" ]; then - _baidu_info "no matching txt to delete: $_record_domain.$_zone_name" - return 0 - fi - - _rm_max="${Baidu_RM_Max:-20}" - _rm_max="$(_baidu_trim_ws "$_rm_max")" - case "$_rm_max" in - "" | *[!0-9]*) - _rm_max="20" - ;; - esac - _rm_cnt="$(printf "%s\n" "$_ids" | sed '/^$/d' | wc -l | tr -d ' ')" - if [ "$_rm_cnt" ] && [ "$_rm_cnt" -gt "$_rm_max" ]; then - _baidu_err "Refusing to delete $_rm_cnt records (limit: $_rm_max)" - return 1 - fi - - for _rid in $_ids; do - if [ "$_baidu_api_engine" = "new" ]; then - if ! _baidu_dns_call "DELETE" "/v1/dns/zone/${_zone_name}/record/${_rid}" ""; then - _baidu_err "baidu_dns_call failed: delete recordId=$_rid" - return 1 - fi - else - _body="$(_baidu_payload_delete "$_zone_name" "$_rid")" - if ! _baidu_bcd_post "/domain/resolve/delete" "$_body"; then - _baidu_err "baidu_bcd_post failed: delete recordId=$_rid" - return 1 - fi - if _baidu_is_api_error "$response"; then - _baidu_err "$response" - return 1 - fi - fi - done - - if [ "$_baidu_api_engine" = "legacy" ]; then - if ! _baidu_find_record_ids "$_zone_name" "$_record_domain" "TXT" "$_txtvalue"; then - _baidu_err "baidu_find_record_ids failed for delete verify: $_record_domain.$_zone_name" - return 1 - fi - _left_ids="$_BAIDU_FIND_RESULT" - if [ -z "$_left_ids" ]; then - return 0 - fi - if [ -n "$_left_ids" ]; then - _baidu_err "delete verification failed: $_record_domain.$_zone_name still has TXT records" - return 1 - fi - fi - - return 0 -} - -# --- Config / Record Context --- -_baidu_load_credentials() { - Baidu_AK="${Baidu_AK:-$(_readaccountconf_mutable Baidu_AK)}" - Baidu_SK="${Baidu_SK:-$(_readaccountconf_mutable Baidu_SK)}" - - Baidu_AK="$(_baidu_trim_ws "$Baidu_AK")" - Baidu_SK="$(_baidu_trim_ws "$Baidu_SK")" - - if [ -z "$Baidu_AK" ] || [ -z "$Baidu_SK" ]; then - _baidu_err "Baidu_AK and Baidu_SK are required" - return 1 - fi - - _saveaccountconf_mutable Baidu_AK "$Baidu_AK" - _saveaccountconf_mutable Baidu_SK "$Baidu_SK" - - BAIDU_BCD_HOST="${Baidu_BCD_Host:-$BAIDU_BCD_DEFAULT_HOST}" - BAIDU_DNS_HOST="${Baidu_DNS_Host:-$BAIDU_DNS_DEFAULT_HOST}" - BAIDU_BCD_VERSION="${Baidu_BCD_Version:-1}" - - return 0 -} - -_baidu_prepare_record() { - _fulldomain="$1" - if [ "$_baidu_api_engine" = "new" ]; then - if ! _baidu_get_root_dns "$_fulldomain"; then - _baidu_err "Could not find zone by new dns api for $_fulldomain" - return 1 - fi - else - if ! _baidu_get_root "$_fulldomain"; then - _baidu_err "Could not find zone by legacy bcd api for $_fulldomain" - return 1 - fi - fi - _record_domain="$_sub_domain" - _zone_name="$_domain" - return 0 -} - -# --- Zone / Records --- -_baidu_get_root() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - _baidu_err "invalid domain: $domain" - return 1 - fi - - if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$h" 1 1)"; then - _baidu_err "baidu_bcd_post failed: list zones" - return 1 - fi - if ! _baidu_is_api_error "$response" && (_contains "$response" "\"totalCount\"" || _contains "$response" "\"result\""); then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - if [ "$_sub_domain" = "$_domain" ]; then - _sub_domain="@" - fi - _baidu_info "zone matched: $_domain (host: $_sub_domain)" - return 0 - fi - - p=$i - i=$(_math "$i" + 1) - done -} - -_baidu_get_root_dns() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - _baidu_err "invalid domain: $domain" - return 1 - fi - - if ! _baidu_dns_call "GET" "/v1/dns/zone/${h}/record" ""; then - _baidu_info "baidu_dns_call failed: list zones" - elif ! _baidu_is_api_error "$response" && (_contains "$response" "\"records\"" || _contains "$response" "\"maxKeys\""); then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - if [ "$_sub_domain" = "$_domain" ]; then - _sub_domain="@" - fi - _baidu_info "zone matched by dns api: $_domain (host: $_sub_domain)" - return 0 - fi - - p=$i - i=$(_math "$i" + 1) - done -} - -_baidu_find_record_ids_current() { - if [ "$_baidu_api_engine" = "new" ]; then - _baidu_find_record_ids_dns "$@" - else - _baidu_find_record_ids "$@" - fi -} - -_baidu_find_record_ids() { - _zone_name="$1" - _record_domain="$2" - _rdtype="$3" - _rdata="$4" - - # Reset global result variable - _BAIDU_FIND_RESULT="" - - _zone_name_e="$(_baidu_json_escape "$_zone_name")" - _record_domain_e="$(_baidu_json_escape "$_record_domain")" - _rdtype_e="$(_baidu_json_escape "$_rdtype")" - _rdata_e="$(_baidu_json_escape "$_rdata")" - - _page=1 - _page_size=100 - _ids="" - - _max_page="" - while true; do - if ! _baidu_bcd_post "/domain/resolve/list" "$(_baidu_payload_list "$_zone_name" "$_page" "$_page_size")"; then - _baidu_err "baidu_bcd_post failed: list records" - return 1 - fi - - if _baidu_is_api_error "$response"; then - _baidu_err "baidu_bcd error: $(_baidu_json_get_str "$response" "code") $(_baidu_json_get_str "$response" "message")" - return 1 - fi - - _normalized="$( - printf "%s" "$response" | _normalizeJson - )" - - if [ -z "$_max_page" ]; then - _total="$(_baidu_parse_totalcount "$_normalized")" - _max_page="$(_baidu_calc_max_page "$_total" "$_page_size")" - fi - - _records=$(printf "%s" "$_normalized" | sed 's/},{/}\n{/g') - while IFS= read -r _line; do - _id="$(_baidu_match_record_id "$_line" "$_record_domain_e" "$_rdtype_e" "$_rdata_e")" - if [ "$_id" ]; then - _ids="$_ids $_id" - fi - done <...." - _debug2 "add response" "$response" - return 1 - fi - - return 0 -} - -# Usage: dns_bhosted_rm _acme-challenge.www.example.com "txt-value" -dns_bhosted_rm() { - fulldomain="$1" - txtvalue="$2" - - _debug "fulldomain" "$fulldomain" - _debug "txtvalue" "$txtvalue" - - _bhosted_load_credentials || return 1 - _bhosted_get_root "$fulldomain" || return 1 - - _hash="$(_bhosted_cache_hash "$fulldomain" "$txtvalue")" - _rec_id="$(_bhosted_mem_get_id "$_hash")" - - if [ -z "$_rec_id" ]; then - _err "No cached bHosted record id found for cleanup." - _err "Please delete TXT manually in bHosted DNS for: ${_bhosted_name}.${_domain}" - return 1 - fi - - _info "Removing TXT record id=${_rec_id}: ${_bhosted_name}.${_domain}" - _bhosted_api_del_record "$_bhosted_sld" "$_bhosted_tld" "$_rec_id" || return 1 - - return 0 -} - -######## Private functions ##################### - -_bhosted_load_credentials() { - BHOSTED_Username="${BHOSTED_Username:-$(_readaccountconf_mutable BHOSTED_Username)}" - BHOSTED_Password="${BHOSTED_Password:-$(_readaccountconf_mutable BHOSTED_Password)}" - - if [ -z "$BHOSTED_Username" ] || [ -z "$BHOSTED_Password" ]; then - BHOSTED_Username="" - BHOSTED_Password="" - _err "You didn't specify bHosted credentials." - _err "Please export BHOSTED_Username and BHOSTED_Password (MD5 hash)." - return 1 - fi - - _saveaccountconf_mutable BHOSTED_Username "$BHOSTED_Username" - _saveaccountconf_mutable BHOSTED_Password "$BHOSTED_Password" - - return 0 -} - -# Determine root zone and host part -# Supports simple domains automatically (example.com, example.nl) -# For multi-part TLDs (example.co.uk), set: -# BHOSTED_SLD=example -# BHOSTED_TLD=co.uk -_bhosted_get_root() { - domain="$1" - - BHOSTED_SLD="${BHOSTED_SLD:-$(_readdomainconf BHOSTED_SLD)}" - BHOSTED_TLD="${BHOSTED_TLD:-$(_readdomainconf BHOSTED_TLD)}" - - if [ -n "$BHOSTED_SLD" ] && [ -n "$BHOSTED_TLD" ]; then - _savedomainconf BHOSTED_SLD "$BHOSTED_SLD" - _savedomainconf BHOSTED_TLD "$BHOSTED_TLD" - - _domain="${BHOSTED_SLD}.${BHOSTED_TLD}" - case "$domain" in - *."$_domain") ;; - "$_domain") ;; - *) - _err "BHOSTED_SLD/BHOSTED_TLD do not match requested domain: $domain" - return 1 - ;; - esac - - _bhosted_sld="$BHOSTED_SLD" - _bhosted_tld="$BHOSTED_TLD" - _bhosted_name="${domain%."$_domain"}" - if [ "$_bhosted_name" = "$domain" ]; then - _bhosted_name="" - fi - - [ -n "$_bhosted_name" ] || _bhosted_name="@" - - _debug "_domain" "$_domain" - _debug "_bhosted_sld" "$_bhosted_sld" - _debug "_bhosted_tld" "$_bhosted_tld" - _debug "_bhosted_name" "$_bhosted_name" - return 0 - fi - - # Auto-parse: assume last label = tld, label before = sld - # Works for .nl / .com / .org etc. - _bhosted_tld="$(printf "%s" "$domain" | awk -F. '{print $NF}')" - _bhosted_sld="$(printf "%s" "$domain" | awk -F. '{print $(NF-1)}')" - - if [ -z "$_bhosted_sld" ] || [ -z "$_bhosted_tld" ]; then - _err "Could not parse SLD/TLD from domain: $domain" - return 1 - fi - - _domain="${_bhosted_sld}.${_bhosted_tld}" - _bhosted_name="${domain%."$_domain"}" - if [ "$_bhosted_name" = "$domain" ]; then - _bhosted_name="" - fi - - [ -n "$_bhosted_name" ] || _bhosted_name="@" - - _debug "_domain" "$_domain" - _debug "_bhosted_sld" "$_bhosted_sld" - _debug "_bhosted_tld" "$_bhosted_tld" - _debug "_bhosted_name" "$_bhosted_name" - - return 0 -} - -_bhosted_api_add_txt() { - _sld="$1" - _tld="$2" - _name="$3" - _content="$4" - _ttl="$5" - - _u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)" - _u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)" - _u_sld="$(printf "%s" "$_sld" | _url_encode)" - _u_tld="$(printf "%s" "$_tld" | _url_encode)" - _u_name="$(printf "%s" "$_name" | _url_encode)" - _u_content="$(printf "%s" "$_content" | _url_encode)" - _u_ttl="$(printf "%s" "$_ttl" | _url_encode)" - - _data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&type=TXT&name=${_u_name}&content=${_u_content}&ttl=${_u_ttl}" - - _debug "bHosted add endpoint" "${BHOSTED_API_ROOT}/addrecord" - response="$(_post "$_data" "${BHOSTED_API_ROOT}/addrecord")" - _ret="$?" - - _debug2 "bHosted add response" "$response" - - if [ "$_ret" != "0" ]; then - _err "bHosted addrecord request failed" - return 1 - fi - - if _bhosted_response_has_error "$response"; then - _err "bHosted addrecord returned an error" - _debug2 "response" "$response" - return 1 - fi - - return 0 -} - -_bhosted_api_del_record() { - _sld="$1" - _tld="$2" - _id="$3" - - _u_user="$(printf "%s" "$BHOSTED_Username" | _url_encode)" - _u_pass="$(printf "%s" "$BHOSTED_Password" | _url_encode)" - _u_sld="$(printf "%s" "$_sld" | _url_encode)" - _u_tld="$(printf "%s" "$_tld" | _url_encode)" - _u_id="$(printf "%s" "$_id" | _url_encode)" - - _url="${BHOSTED_API_ROOT}/delrecord" - _data="user=${_u_user}&password=${_u_pass}&tld=${_u_tld}&sld=${_u_sld}&id=${_u_id}" - - _debug "bHosted delete endpoint" "$_url" - response="$(_post "$_data" "$_url")" - _ret="$?" - - _debug2 "bHosted delete response" "$response" - - if [ "$_ret" != "0" ]; then - _err "bHosted delrecord request failed" - return 1 - fi - - if _bhosted_response_has_error "$response"; then - _err "bHosted delrecord returned an error" - _debug2 "response" "$response" - return 1 - fi - - return 0 -} - -# Extract XML tag value from response, e.g. 12345 -_bhosted_xml_value() { - _tag="$1" - _resp="$2" - - # Flatten response to simplify parsing - _flat="$(printf "%s" "$_resp" | tr -d '\r\n\t')" - printf "%s" "$_flat" | sed -n "s:.*<${_tag}>\\([^<]*\\).*:\\1:p" | _head_n 1 -} - -# Return code convention: -# return 0 => response HAS error -# return 1 => response has NO error (success) -_bhosted_response_has_error() { - _resp="$1" - - # Empty response = error - if [ -z "$_resp" ]; then - _debug "Empty API response" - return 0 - fi - - # Prefer explicit bHosted XML response fields - if _contains "$_resp" ""; then - _errors="$(_bhosted_xml_value "errors" "$_resp")" - _done="$(_bhosted_xml_value "done" "$_resp")" - _subcommand="$(_bhosted_xml_value "subcommand" "$_resp")" - _id="$(_bhosted_xml_value "id" "$_resp")" - - _debug "bHosted XML subcommand" "$_subcommand" - _debug "bHosted XML id" "$_id" - _debug "bHosted XML errors" "$_errors" - _debug "bHosted XML done" "$_done" - - # Success according to provided format - if [ "$_errors" = "0" ] && [ "$_done" = "true" ]; then - return 1 - fi - - _debug "bHosted XML indicates failure" - return 0 - fi - - # Fallback for unexpected/non-XML responses - _resp_lc="$(_lower_case "$_resp")" - - if _contains "$_resp_lc" "error"; then - _debug "Detected 'error' in response" - return 0 - fi - if _contains "$_resp_lc" "fout"; then - _debug "Detected 'fout' in response" - return 0 - fi - if _contains "$_resp_lc" "invalid"; then - _debug "Detected 'invalid' in response" - return 0 - fi - if _contains "$_resp_lc" "failed"; then - _debug "Detected 'failed' in response" - return 0 - fi - if _contains "$_resp_lc" "denied"; then - _debug "Detected 'denied' in response" - return 0 - fi - - # If no explicit error markers found, assume success - return 1 -} - -# Extract record id from response -# Supports bHosted XML first, then generic fallbacks -_bhosted_extract_id() { - _resp="$1" - - # bHosted XML: 12345 - _id="$(_bhosted_xml_value "id" "$_resp" | tr -cd '0-9')" - if [ -n "$_id" ]; then - printf "%s" "$_id" - return 0 - fi - - # JSON: "id":12345 - _id="$(printf "%s" "$_resp" | _egrep_o '"id"[ ]*:[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')" - if [ -n "$_id" ]; then - printf "%s" "$_id" - return 0 - fi - - # key=value: id=12345 - _id="$(printf "%s" "$_resp" | _egrep_o '(^|[^0-9a-zA-Z])id[ ]*=[ ]*[0-9]+' | _head_n 1 | tr -cd '0-9')" - if [ -n "$_id" ]; then - printf "%s" "$_id" - return 0 - fi - - # "record id 12345" / "recordid 12345" - _id="$(printf "%s" "$_resp" | _egrep_o '(record[ ]*id|recordid)[^0-9]*[0-9]+' | _head_n 1 | tr -cd '0-9')" - if [ -n "$_id" ]; then - printf "%s" "$_id" - return 0 - fi - - return 1 -} - -# Create a unique config key for cached record ids -_bhosted_cache_hash() { - _fd="$1" - _tv="$2" - # md5 hex of fulldomain|txtvalue - printf "%s|%s" "$_fd" "$_tv" | _digest md5 hex -} - -_bhosted_cache_key() { - _hash="$1" - printf "%s" "BHOSTED_TXT_ID_${_hash}" -} - -_bhosted_mem_set_id() { - _hash="$1" - _id="$2" - _key="$(_bhosted_cache_key "$_hash")" - _savedomainconf "$_key" "$_id" -} - -_bhosted_mem_get_id() { - _hash="$1" - _key="$(_bhosted_cache_key "$_hash")" - _readdomainconf "$_key" -} diff --git a/dnsapi/dns_bookmyname.sh b/dnsapi/dns_bookmyname.sh deleted file mode 100644 index cf3f1e3e..00000000 --- a/dnsapi/dns_bookmyname.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_bookmyname_info='BookMyName.com -Site: BookMyName.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bookmyname -Options: - BOOKMYNAME_USERNAME Username - BOOKMYNAME_PASSWORD Password -Issues: github.com/acmesh-official/acme.sh/issues/3209 -Author: @Neilpang -' - -######## Public functions ##################### - -# BookMyName urls: -# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=add&value="XXXXXXXX"' -# https://BOOKMYNAME_USERNAME:BOOKMYNAME_PASSWORD@www.bookmyname.com/dyndns/?hostname=_acme-challenge.domain.tld&type=txt&ttl=300&do=remove&value="XXXXXXXX"' - -# Output: -#good: update done, cid 123456, domain id 456789, type txt, ip XXXXXXXX -#good: remove done 1, cid 123456, domain id 456789, ttl 300, type txt, ip XXXXXXXX - -# Be careful, BMN DNS servers can be slow to pick up changes; using dnssleep is thus advised. - -# Usage: -# export BOOKMYNAME_USERNAME="ABCDE-FREE" -# export BOOKMYNAME_PASSWORD="MyPassword" -# /usr/local/ssl/acme.sh/acme.sh --dns dns_bookmyname --dnssleep 600 --issue -d domain.tld - -#Usage: dns_bookmyname_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_bookmyname_add() { - fulldomain=$1 - txtvalue=$2 - _info "Using bookmyname" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}" - BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}" - - if [ -z "$BOOKMYNAME_USERNAME" ] || [ -z "$BOOKMYNAME_PASSWORD" ]; then - BOOKMYNAME_USERNAME="" - BOOKMYNAME_PASSWORD="" - _err "You didn't specify BookMyName username and password yet." - _err "Please specify them and try again." - return 1 - fi - - #save the credentials to the account conf file. - _saveaccountconf_mutable BOOKMYNAME_USERNAME "$BOOKMYNAME_USERNAME" - _saveaccountconf_mutable BOOKMYNAME_PASSWORD "$BOOKMYNAME_PASSWORD" - - uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/" - data="?hostname=${fulldomain}&type=TXT&ttl=300&do=add&value=${txtvalue}" - result="$(_get "${uri}${data}")" - _debug "Result: $result" - - if ! _startswith "$result" 'good: update done, cid '; then - _err "Can't add $fulldomain" - return 1 - fi - -} - -#Usage: fulldomain txtvalue -#Remove the txt record after validation. -dns_bookmyname_rm() { - fulldomain=$1 - txtvalue=$2 - _info "Using bookmyname" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - BOOKMYNAME_USERNAME="${BOOKMYNAME_USERNAME:-$(_readaccountconf_mutable BOOKMYNAME_USERNAME)}" - BOOKMYNAME_PASSWORD="${BOOKMYNAME_PASSWORD:-$(_readaccountconf_mutable BOOKMYNAME_PASSWORD)}" - - uri="https://${BOOKMYNAME_USERNAME}:${BOOKMYNAME_PASSWORD}@www.bookmyname.com/dyndns/" - data="?hostname=${fulldomain}&type=TXT&ttl=300&do=remove&value=${txtvalue}" - result="$(_get "${uri}${data}")" - _debug "Result: $result" - - if ! _startswith "$result" 'good: remove done 1, cid '; then - _info "Can't remove $fulldomain" - fi - -} - -#################### Private functions below ################################## diff --git a/dnsapi/dns_bunny.sh b/dnsapi/dns_bunny.sh index 780198e1..a9b1ea5a 100644 --- a/dnsapi/dns_bunny.sh +++ b/dnsapi/dns_bunny.sh @@ -1,13 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_bunny_info='Bunny.net -Site: Bunny.net/dns/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_bunny -Options: - BUNNY_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/4296 -Author: -' + +## Will be called by acme.sh to add the TXT record via the Bunny DNS API. +## returns 0 means success, otherwise error. + +## Author: nosilver4u +## GitHub: https://github.com/nosilver4u/acme.sh + +## +## Environment Variables Required: +## +## BUNNY_API_KEY="75310dc4-ca77-9ac3-9a19-f6355db573b49ce92ae1-2655-3ebd-61ac-3a3ae34834cc" +## ##################### Public functions ##################### @@ -196,7 +199,7 @@ _get_base_domain() { _debug2 domain_list "$domain_list" i=1 - while [ "$i" -gt 0 ]; do + while [ $i -gt 0 ]; do ## get next longest domain _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") ## check we got something back from our cut (or are we at the end) @@ -208,7 +211,7 @@ _get_base_domain() { ## check if it exists if [ -n "$found" ]; then ## exists - exit loop returning the parts - sub_point=$(_math "$i" - 1) + sub_point=$(_math $i - 1) _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") _domain_id="$(echo "$found" | _egrep_o "Id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" _debug _domain_id "$_domain_id" @@ -218,11 +221,11 @@ _get_base_domain() { return 0 fi ## increment cut point $i - i=$(_math "$i" + 1) + i=$(_math $i + 1) done if [ -z "$found" ]; then - page=$(_math "$page" + 1) + page=$(_math $page + 1) nextpage="https://api.bunny.net/dnszone?page=$page" ## Find the next page if we don't have a match. hasnextpage="$(echo "$domain_list" | _egrep_o "\"HasMoreItems\"\s*:\s*true")" diff --git a/dnsapi/dns_calrissia.sh b/dnsapi/dns_calrissia.sh deleted file mode 100644 index 01ca3092..00000000 --- a/dnsapi/dns_calrissia.sh +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_calrissia_info='Calrissia.be DNS API -Site: calrissia.be -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_calrissia -Options: - CALRISSIA_TOKEN Personal access token -Issues: github.com/acmesh-official/acme.sh/issues/6809 -Author: Ward Hus -' - -CALRISSIA_API="https://my.calrissia.com/api" - -dns_calrissia_add() { - fulldomain="$1" - txtvalue="$2" - - _calrissia_load_token || return 1 - - if ! _calrissia_get_root "$fulldomain"; then - _err "Unable to find domain in Calrissia account for: $fulldomain" - return 1 - fi - - _debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'" - _info "Adding TXT record for $fulldomain" - - _body="{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120,\"prio\":0}" - _response="$(_calrissia_request POST "/domain/$_domain_id/record" "$_body")" - - if ! _contains "$_response" '"id"'; then - _err "Failed to create TXT record: $_response" - return 1 - fi - - return 0 -} - -dns_calrissia_rm() { - fulldomain="$1" - txtvalue="$2" - - _calrissia_load_token || return 1 - - if ! _calrissia_get_root "$fulldomain"; then - _err "Unable to find domain in Calrissia account for: $fulldomain" - return 1 - fi - - _debug "domain='$_domain' id='$_domain_id' sub='$_sub_domain'" - - # Look the record up from the API instead of relying on local state. - # The record list is embedded in the domain object. - _response="$(_calrissia_request GET "/domain/$_domain_id")" - _debug2 "Response: $_response" - - # Split the record objects onto separate lines, then match on both the - # subdomain name and the TXT value to find the record id to delete. - _record_id="$(printf "%s" "$_response" | - tr '{}' '\n' | - grep "\"name\" *: *\"$_sub_domain\"" | - grep "\"content\" *: *\"$txtvalue\"" | - _egrep_o '"id" *: *[0-9]+' | - _head_n 1 | - _egrep_o '[0-9]+')" - - if [ -z "$_record_id" ]; then - _info "No matching TXT record found for $fulldomain; nothing to remove" - return 0 - fi - - _info "Removing TXT record id=$_record_id from domain id=$_domain_id" - if ! _response="$(_calrissia_request DELETE "/domain/$_domain_id/record/$_record_id")" || _contains "$_response" '"error"'; then - _err "Failed to remove TXT record: $_response" - return 1 - fi - return 0 -} - -#################### -# Private helpers # -#################### - -_calrissia_load_token() { - CALRISSIA_TOKEN="${CALRISSIA_TOKEN:-$(_readaccountconf_mutable CALRISSIA_TOKEN)}" - if [ -z "$CALRISSIA_TOKEN" ]; then - _err "CALRISSIA_TOKEN is not set. Generate one at https://identity.calrissia.com under API Keys." - return 1 - fi - _saveaccountconf_mutable CALRISSIA_TOKEN "$CALRISSIA_TOKEN" -} - -# Sets _domain, _domain_id, _sub_domain for a given FQDN. -_calrissia_get_root() { - _fqdn="$1" - - i=1 - while true; do - _candidate="$(printf "%s" "$_fqdn" | cut -d . -f "$i"-)" - [ -z "$_candidate" ] && return 1 - - _debug "Trying root domain: $_candidate" - _response="$(_calrissia_request GET "/domain?full_domain_name=$_candidate")" - _debug2 "Response: $_response" - - _domain_id="$(printf "%s" "$_response" | - _egrep_o '"id" *: *[0-9]+' | - _head_n 1 | - _egrep_o '[0-9]+')" - - if [ -n "$_domain_id" ]; then - if [ "$i" = "1" ]; then - # The FQDN itself is the zone apex, e.g. a challenge-alias domain. - _sub_domain="" - else - _sub_domain="$(printf "%s" "$_fqdn" | cut -d . -f "1-$((i - 1))")" - fi - _domain="$_candidate" - return 0 - fi - - i=$((i + 1)) - done -} - -_calrissia_request() { - _method="$1" - _path="$2" - _body="$3" - export _H1="Authorization: Bearer $CALRISSIA_TOKEN" - export _H2="Accept: application/json" - if [ "$_method" = "GET" ]; then - _get "$CALRISSIA_API$_path" - else - _post "$_body" "$CALRISSIA_API$_path" "" "$_method" "application/json" - fi -} diff --git a/dnsapi/dns_cdmon.sh b/dnsapi/dns_cdmon.sh deleted file mode 100644 index 470fb5fe..00000000 --- a/dnsapi/dns_cdmon.sh +++ /dev/null @@ -1,137 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 - -dns_cdmon_info='cdmon -Site: www.cdmon.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cdmon -Options: - CDMON_Key API Key -' - -CDMON_Api="https://api-domains.cdmon.services/api-domains" - -######## Public functions ##################### -# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -# Used to add txt record -dns_cdmon_add() { - fulldomain=$1 - txtvalue=$2 - - CDMON_Key="${CDMON_Key:-$(_readaccountconf_mutable CDMON_Key)}" - - if [ -z "$CDMON_Key" ]; then - CDMON_Key="" - _err "You didn't specify your cdmon api key yet." - _err "Please create your key and try again." - return 1 - fi - - _saveaccountconf_mutable CDMON_Key "$CDMON_Key" - - _debug "First, we detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - _info "Adding record" - if _cdmon_rest "dnsrecords/create" "{\"data\":{\"type\":\"TXT\",\"domain\":\"$_domain\",\"value\":\"$txtvalue\",\"ttl\":120,\"host\":\"$_sub_domain\"}}"; then - if _contains "$response" "\"status\":\"ok\""; then - _info "Added, OK" - return 0 - else - _err "Add txt record error." - return 1 - fi - fi - _err "Add txt record error." - return 1 -} - -# Usage: fulldomain txtvalue -# Used to remove the txt record after validation -dns_cdmon_rm() { - fulldomain=$1 - txtvalue=$2 - - CDMON_Key="${CDMON_Key:-$(_readaccountconf_mutable CDMON_Key)}" - _debug "First, we detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _info "Removing record" - if _cdmon_rest "dnsrecords/delete" "{\"data\":{\"value\":\"$txtvalue\",\"type\":\"TXT\",\"domain\":\"$_domain\",\"host\":\"$_sub_domain\"}}"; then - if _contains "$response" "\"status\":\"ok\""; then - _info "Deleted, OK" - return 0 - else - _err "Delete txt record error." - return 1 - fi - fi - _err "Delete txt record error." - return 1 -} - -#################### Private functions below ################################## -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -_get_root() { - domain=$1 - i=1 - p=1 - - if ! _cdmon_rest "domains/list"; then - return 1 - fi - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - #not valid - return 1 - fi - if _contains "$response" "\"domain\":\"$h\""; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 - fi - p=$i - i=$(_math "$i" + 1) - done - return 1 -} - -_cdmon_rest() { - ep="$1" - data="$2" - _debug "$ep" - - key_trimmed=$(echo "$CDMON_Key" | tr -d '"') - - export _H1="Content-Type: application/json" - export _H2="apikey: $key_trimmed" - - _debug data "$data" - response="$(_post "$data" "$CDMON_Api/$ep")" - _ret="$?" - - unset _H1 _H2 - - if [ "$_ret" != "0" ]; then - _err "error $ep" - return 1 - fi - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_cf.sh b/dnsapi/dns_cf.sh index 7b383c43..cd8d9a8d 100755 --- a/dnsapi/dns_cf.sh +++ b/dnsapi/dns_cf.sh @@ -1,16 +1,13 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_cf_info='CloudFlare -Site: CloudFlare.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cf -Options: - CF_Key API Key - CF_Email Your account email -OptionsAlt: - CF_Token API Token - CF_Account_ID Account ID - CF_Zone_ID Zone ID. Optional. -' + +# +#CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#CF_Email="xxxx@sss.com" + +#CF_Token="xxxx" +#CF_Account_ID="xxxx" +#CF_Zone_ID="xxxx" CF_Api="https://api.cloudflare.com/client/v4" @@ -92,9 +89,7 @@ dns_cf_add() { if _contains "$response" "$txtvalue"; then _info "Added, OK" return 0 - elif _contains "$response" "The record already exists" || - _contains "$response" "An identical record already exists." || - _contains "$response" '"code":81058'; then + elif _contains "$response" "The record already exists"; then _info "Already exists, OK" return 0 else @@ -188,7 +183,7 @@ _get_root() { fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -208,7 +203,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\"" || _contains "$response" '"total_count":1'; then _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ") if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_clouddns.sh b/dnsapi/dns_clouddns.sh index b78d70a4..31ae4ee9 100755 --- a/dnsapi/dns_clouddns.sh +++ b/dnsapi/dns_clouddns.sh @@ -1,15 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_clouddns_info='vshosting.cz CloudDNS -Site: github.com/vshosting/clouddns -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_clouddns -Options: - CLOUDDNS_EMAIL Email - CLOUDDNS_PASSWORD Password - CLOUDDNS_CLIENT_ID Client ID -Issues: github.com/acmesh-official/acme.sh/issues/2699 -Author: Radek Sprta -' + +# Author: Radek Sprta + +#CLOUDDNS_EMAIL=XXXXX +#CLOUDDNS_PASSWORD="YYYYYYYYY" +#CLOUDDNS_CLIENT_ID=XXXXX CLOUDDNS_API='https://admin.vshosting.cloud/clouddns' CLOUDDNS_LOGIN_API='https://admin.vshosting.cloud/api/public/auth/login' diff --git a/dnsapi/dns_cloudns.sh b/dnsapi/dns_cloudns.sh index 2c543271..b03fd579 100755 --- a/dnsapi/dns_cloudns.sh +++ b/dnsapi/dns_cloudns.sh @@ -1,15 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_cloudns_info='ClouDNS.net -Site: ClouDNS.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cloudns -Options: - CLOUDNS_AUTH_ID Regular auth ID - CLOUDNS_SUB_AUTH_ID Sub auth ID - CLOUDNS_AUTH_PASSWORD Auth Password -Author: Boyan Peychev -' +# Author: Boyan Peychev +# Repository: https://github.com/ClouDNS/acme.sh/ +# Editor: I Komang Suryadana + +#CLOUDNS_AUTH_ID=XXXXX +#CLOUDNS_SUB_AUTH_ID=XXXXX +#CLOUDNS_AUTH_PASSWORD="YYYYYYYYY" CLOUDNS_API="https://api.cloudns.net" DOMAIN_TYPE= DOMAIN_MASTER= @@ -81,7 +78,7 @@ dns_cloudns_rm() { return 1 fi - for i in $(echo "$response" | tr '{' "\n" | grep -- "$record"); do + for i in $(echo "$response" | tr '{' "\n" | grep "$record"); do record_id=$(echo "$i" | tr ',' "\n" | grep -E '^"id"' | sed -re 's/^\"id\"\:\"([0-9]+)\"$/\1/g') if [ -n "$record_id" ]; then @@ -135,7 +132,7 @@ _dns_cloudns_init_check() { _dns_cloudns_http_api_call "dns/login.json" "" if ! _contains "$response" "\"status\":\"Success\""; then - _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Server response: $response" + _err "Invalid CLOUDNS_AUTH_ID or CLOUDNS_AUTH_PASSWORD. Please check your login credentials." return 1 fi @@ -164,7 +161,7 @@ _dns_cloudns_get_zone_info() { _dns_cloudns_get_zone_name() { i=2 while true; do - zoneForCheck=$(printf "%s" "$1" | cut -d . -f "$i"-100) + zoneForCheck=$(printf "%s" "$1" | cut -d . -f $i-100) if [ -z "$zoneForCheck" ]; then return 1 @@ -197,11 +194,10 @@ _dns_cloudns_http_api_call() { auth_user="auth-id=$CLOUDNS_AUTH_ID" fi - encoded_password=$(echo "$CLOUDNS_AUTH_PASSWORD" | tr -d "\n\r" | _url_encode) if [ -z "$2" ]; then - data="$auth_user&auth-password=$encoded_password" + data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD" else - data="$auth_user&auth-password=$encoded_password&$2" + data="$auth_user&auth-password=$CLOUDNS_AUTH_PASSWORD&$2" fi response="$(_get "$CLOUDNS_API/$method?$data")" diff --git a/dnsapi/dns_cn.sh b/dnsapi/dns_cn.sh index e06a2be6..38d1f4aa 100644 --- a/dnsapi/dns_cn.sh +++ b/dnsapi/dns_cn.sh @@ -1,22 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_cn_info='Core-Networks.de -Site: beta.api.Core-Networks.de/doc/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cn -Options: - CN_User User - CN_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/2142 -Author: 5ll, francis -' + +# DNS API for acme.sh for Core-Networks (https://beta.api.core-networks.de/doc/). +# created by 5ll and francis CN_API="https://beta.api.core-networks.de" ######## Public functions ##################### dns_cn_add() { - # Core-Networks API requires punycode for IDN domains - fulldomain=$(_idn "$1") + fulldomain=$1 txtvalue=$2 if ! _cn_login; then @@ -59,8 +51,7 @@ dns_cn_add() { } dns_cn_rm() { - # Core-Networks API requires punycode for IDN domains - fulldomain=$(_idn "$1") + fulldomain=$1 txtvalue=$2 if ! _cn_login; then @@ -133,7 +124,7 @@ _cn_get_root() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" _debug _H1 "${_H1}" @@ -151,7 +142,7 @@ _cn_get_root() { fi if _contains "$_cn_zonelist" "\"name\":\"$h\"" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 else diff --git a/dnsapi/dns_comlaude.sh b/dnsapi/dns_comlaude.sh deleted file mode 100644 index 2aa2dba9..00000000 --- a/dnsapi/dns_comlaude.sh +++ /dev/null @@ -1,248 +0,0 @@ -#!/usr/bin/env sh - -# shellcheck disable=SC2034 -dns_comlaude_info='comlaude.com -Site: comlaude.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_comlaude -Options: - COMLAUDE_USERNAME User account - COMLAUDE_PASSWORD User password - COMLAUDE_API_KEY generated API key - COMLAUDE_GROUP_ID Group ID in comlaude user profile - Get it from the https://www.comlaude.com -Issues: github.com/acmesh-official/acme.sh/issues/7112 -' -# ===== CONFIG ===== -COMLAUDE_API="https://api.comlaude.com" - -########## AUTH ########## - -_comlaude_auth() { - _debug "Checking cached ComLaude token" - - # Try to get token from account.conf - if [ -z "$COMLAUDE_ACCESS_TOKEN" ]; then - COMLAUDE_ACCESS_TOKEN="$(_readaccountconf_mutable COMLAUDE_ACCESS_TOKEN)" - COMLAUDE_TOKEN_EXPIRY="$(_readaccountconf_mutable COMLAUDE_TOKEN_EXPIRY)" - fi - - _now=$(_time) - if [ -n "$COMLAUDE_ACCESS_TOKEN" ] && [ -n "$COMLAUDE_TOKEN_EXPIRY" ] && [ "$_now" -lt "$COMLAUDE_TOKEN_EXPIRY" ]; then - _debug "Using cached ComLaude token (valid ${COMLAUDE_TOKEN_EXPIRY} > ${_now})" - return 0 - fi - - _info "ComLaude auth..." - _comlaude_body="{\"username\":\"$COMLAUDE_USERNAME\",\"password\":\"$COMLAUDE_PASSWORD\",\"api_key\":\"$COMLAUDE_API_KEY\"}" - _comlaude_response="$(_post "$_comlaude_body" "$COMLAUDE_API/api_login" "" "POST" "application/json")" - - if ! _contains "$_comlaude_response" "access_token"; then - _err "Auth failed: $_comlaude_response" - return 1 - fi - - COMLAUDE_ACCESS_TOKEN=$(echo "$_comlaude_response" | _egrep_o '"access_token":"[^"]*"' | cut -d'"' -f4) - # store expiracy from api reply l'API ("expires_in" in seconds) - _comlaude_expires_in=$(echo "$_comlaude_response" | _egrep_o '"expires_in":[0-9]*' | cut -d: -f2) - [ -z "$_comlaude_expires_in" ] && _comlaude_expires_in=3000 # fallback if no info - - COMLAUDE_TOKEN_EXPIRY=$(($(_time) + _comlaude_expires_in - 60)) # margin of 60s to secure renew - - _saveaccountconf_mutable COMLAUDE_ACCESS_TOKEN "$COMLAUDE_ACCESS_TOKEN" - _saveaccountconf_mutable COMLAUDE_TOKEN_EXPIRY "$COMLAUDE_TOKEN_EXPIRY" - - return 0 -} - -########## DOMAIN RESOLUTION ########## - -_comlaude_get_root() { - COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}" - if [ -z "$COMLAUDE_GROUP_ID" ]; then - _err "Missing COMLAUDE_GROUP_ID" - return 1 - fi - - _comlaude_input_domain="$1" - _comlaude_input_domain="${_comlaude_input_domain#_acme-challenge.}" - case "$_comlaude_input_domain" in - \*.*) _comlaude_input_domain="${_comlaude_input_domain#*.}" ;; - esac - - _debug "Normalized domain: $_comlaude_input_domain" - - _comlaude_i=1 - while true; do - _comlaude_d=$(printf "%s" "$_comlaude_input_domain" | cut -d . -f "$_comlaude_i-") - [ -z "$_comlaude_d" ] && { - _debug "No matching domain found for $_comlaude_input_domain" - return 1 - } - - # don't test unnecessary levels - # registered domain : TLD only (no dot after cut). - case "$_comlaude_d" in - *.*) : ;; - *) - _debug "Skipping bare TLD candidate: $_comlaude_d" - _comlaude_i=$((_comlaude_i + 1)) - continue - ;; - esac - - _debug "Checking domain: $_comlaude_d" - - _comlaude_retry=0 - _comlaude_max_retry=3 # to avoid network errors - _comlaude_DOM_ID="" - _comlaude_Z_ID="" - - while [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ]; do - export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN" - _debug "Full URL: $COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone" - _comlaude_response="$(_get "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/domains?filter[name]=$_comlaude_d&fields=id,name,active_zone")" - _H1="" - - _debug "RAW response for $_comlaude_d (try $((_comlaude_retry + 1))): $_comlaude_response" - - # If empty -> true network issue, we retry - if [ -z "$_comlaude_response" ]; then - _comlaude_retry=$((_comlaude_retry + 1)) - [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2 - continue - fi - - # 404 -> domain not found in that level. no retry : continue - if echo "$_comlaude_response" | grep -q '"status_code":404'; then - _debug "404 for $_comlaude_d, moving to next level (not retrying)" - break - fi - - # Domain missing (200 reply, data empty) -> continue - if echo "$_comlaude_response" | grep -q '"data":\[\]'; then - _debug "Empty data for $_comlaude_d, moving to next level" - break - fi - - # Extraction via _egrep_o - _comlaude_DOM_ID="$(echo "$_comlaude_response" | _egrep_o '"id":"[^"]*"' | head -n1 | cut -d':' -f2 | tr -d '"')" - _comlaude_Z_ID="$(echo "$_comlaude_response" | _egrep_o '"active_zone":\{"id":"[^"]*"' | _egrep_o '"id":"[^"]*"$' | cut -d':' -f2 | tr -d '"')" - - if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then - break - fi - - # 200 reply but malformed data / noid -> retry transport - _comlaude_retry=$((_comlaude_retry + 1)) - [ "$_comlaude_retry" -lt "$_comlaude_max_retry" ] && sleep 2 - done - - _debug "_comlaude_DOM_ID=$_comlaude_DOM_ID" - _debug "_comlaude_Z_ID=$_comlaude_Z_ID" - - if [ -n "$_comlaude_DOM_ID" ] && [ -n "$_comlaude_Z_ID" ]; then - _comlaude_domain="$_comlaude_d" - _comlaude_domain_id="$_comlaude_DOM_ID" - _comlaude_zone_id="$_comlaude_Z_ID" - return 0 - fi - - _comlaude_i=$((_comlaude_i + 1)) - done -} -########## ADD TXT ########## - -dns_comlaude_add() { - fulldomain="$1" - txtvalue="$2" - - COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}" - COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}" - COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}" - COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}" - - if [ -z "$COMLAUDE_USERNAME" ] || [ -z "$COMLAUDE_PASSWORD" ] || [ -z "$COMLAUDE_API_KEY" ]; then - _err "You didn't specify ComLaude credentials (COMLAUDE_USERNAME, COMLAUDE_PASSWORD, COMLAUDE_API_KEY)." - return 1 - fi - - # Backup variable after validation - _saveaccountconf_mutable COMLAUDE_USERNAME "$COMLAUDE_USERNAME" - _saveaccountconf_mutable COMLAUDE_PASSWORD "$COMLAUDE_PASSWORD" - _saveaccountconf_mutable COMLAUDE_API_KEY "$COMLAUDE_API_KEY" - _saveaccountconf_mutable COMLAUDE_GROUP_ID "$COMLAUDE_GROUP_ID" - - _info "Adding TXT: $fulldomain" - _comlaude_auth || return 1 - _comlaude_get_root "$fulldomain" || return 1 - - _debug "Root: $_comlaude_domain" - - _comlaude_data="{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"value\":\"$txtvalue\",\"ttl\":60}" - - export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN" - export _H2="Content-Type: application/json" - - _comlaude_response="$(_post "$_comlaude_data" "$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records")" - - _H1="" - _H2="" - if ! echo "$_comlaude_response" | grep -q '"id"'; then - _err "Failed to create TXT" - _debug "$_comlaude_response" - return 1 - fi - - return 0 -} - -########## REMOVE TXT ########## - -dns_comlaude_rm() { - fulldomain="$1" - txtvalue="$2" - - COMLAUDE_USERNAME="${COMLAUDE_USERNAME:-$(_readaccountconf_mutable COMLAUDE_USERNAME)}" - COMLAUDE_PASSWORD="${COMLAUDE_PASSWORD:-$(_readaccountconf_mutable COMLAUDE_PASSWORD)}" - COMLAUDE_API_KEY="${COMLAUDE_API_KEY:-$(_readaccountconf_mutable COMLAUDE_API_KEY)}" - COMLAUDE_GROUP_ID="${COMLAUDE_GROUP_ID:-$(_readaccountconf_mutable COMLAUDE_GROUP_ID)}" - - _info "Removing TXT: $fulldomain" - - _comlaude_auth || return 1 - _comlaude_get_root "$fulldomain" || return 1 - - export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN" - _comlaude_encoded_name="$(printf '%s' "$fulldomain" | _url_encode)" - _comlaude_encoded_value="$(printf '%s' "$txtvalue" | _url_encode)" - _comlaude_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records?filter[type]=TXT&filter[name]=$_comlaude_encoded_name&filter[value]=$_comlaude_encoded_value" - _comlaude_response="$(_get "$_comlaude_url")" - _H1="" - - _debug "Filtered records response: $_comlaude_response" - - # first "id" top-level of reply (record itself, - # always on first position of each data[] object) - _comlaude_record_id="$(echo "$_comlaude_response" | _egrep_o '"data":\[\{"id":"[^"]*"' | _egrep_o '"[^"]*"$' | tr -d '"')" - - if [ -z "$_comlaude_record_id" ]; then - _info "No matching TXT record found to delete for $fulldomain / $txtvalue" - return 0 - fi - - _debug "Deleting record $_comlaude_record_id" - - export _H1="Authorization: Bearer $COMLAUDE_ACCESS_TOKEN" - _comlaude_del_url="$COMLAUDE_API/groups/$COMLAUDE_GROUP_ID/zones/$_comlaude_zone_id/records/$_comlaude_record_id" - _comlaude_del_resp="$(_post "" "$_comlaude_del_url" "" "DELETE")" - _H1="" - - if echo "$_comlaude_del_resp" | grep -q '"error"'; then - _err "Delete failed for $_comlaude_record_id" - _debug "$_comlaude_del_resp" - return 1 - fi - - _info "Deleted record $_comlaude_record_id" - return 0 -} diff --git a/dnsapi/dns_conoha.sh b/dnsapi/dns_conoha.sh index ecd56fc8..ddc32074 100755 --- a/dnsapi/dns_conoha.sh +++ b/dnsapi/dns_conoha.sh @@ -1,15 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_conoha_info='ConoHa.jp -Domains: ConoHa.io -Site: ConoHa.jp -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_conoha -Options: - CONOHA_Username Username - CONOHA_Password Password - CONOHA_TenantId TenantId - CONOHA_IdentityServiceApi Identity Service API. E.g. "https://identity.xxxx.conoha.io/v2.0" -' CONOHA_DNS_EP_PREFIX_REGEXP="https://dns-service\." @@ -237,7 +226,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100). + h=$(printf "%s" "$domain" | cut -d . -f $i-100). _debug h "$h" if [ -z "$h" ]; then #not valid @@ -251,7 +240,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_constellix.sh b/dnsapi/dns_constellix.sh index 7251f8b2..69d216f0 100644 --- a/dnsapi/dns_constellix.sh +++ b/dnsapi/dns_constellix.sh @@ -1,16 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_constellix_info='Constellix.com -Site: Constellix.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_constellix -Options: - CONSTELLIX_Key API Key - CONSTELLIX_Secret API Secret -Issues: github.com/acmesh-official/acme.sh/issues/2724 -Author: Wout Decre -' + +# Author: Wout Decre CONSTELLIX_Api="https://api.dns.constellix.com/v1" +#CONSTELLIX_Key="XXX" +#CONSTELLIX_Secret="XXX" ######## Public functions ##################### @@ -117,12 +111,12 @@ dns_constellix_rm() { #################### Private functions below ################################## _get_root() { - domain=$(echo "$1" | _lower_case) + domain=$1 i=2 p=1 _debug "Detecting root zone" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then return 1 fi @@ -134,7 +128,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\""; then _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[0-9]*" | cut -d ':' -f 2) if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-$p) _domain="$h" _debug _domain_id "$_domain_id" @@ -156,9 +150,6 @@ _constellix_rest() { data="$3" _debug "$ep" - # Prevent rate limit - _sleep 2 - rdate=$(date +"%s")"000" hmac=$(printf "%s" "$rdate" | _hmac sha1 "$(printf "%s" "$CONSTELLIX_Secret" | _hex_dump | tr -d ' ')" | _base64) diff --git a/dnsapi/dns_cpanel.sh b/dnsapi/dns_cpanel.sh index 6939c3f1..f6126bcb 100755 --- a/dnsapi/dns_cpanel.sh +++ b/dnsapi/dns_cpanel.sh @@ -1,18 +1,18 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_cpanel_info='cPanel Server API - Manage DNS via cPanel Dashboard. -Site: cPanel.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_cpanel -Options: - cPanel_Username Username - cPanel_Apitoken API Token - cPanel_Hostname Server URL. E.g. "https://hostname:port" -Issues: github.com/acmesh-official/acme.sh/issues/3732 -Author: Bjarne Saltbaek -' - +# +#Author: Bjarne Saltbaek +#Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/3732 +# +# ######## Public functions ##################### +# +# Export CPANEL username,api token and hostname in the following variables +# +# cPanel_Username=username +# cPanel_Apitoken=apitoken +# cPanel_Hostname=hostname +# +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record dns_cpanel_add() { @@ -38,7 +38,7 @@ dns_cpanel_add() { fi # adding entry _info "Adding the entry" - stripped_fulldomain="${fulldomain%."$_domain"}" + stripped_fulldomain=$(echo "$fulldomain" | sed "s/.$_domain//") _debug "Adding $stripped_fulldomain to $_domain zone" _myget "json-api/cpanel?cpanel_jsonapi_apiversion=2&cpanel_jsonapi_module=ZoneEdit&cpanel_jsonapi_func=add_zone_record&domain=$_domain&name=$stripped_fulldomain&type=TXT&txtdata=$txtvalue&ttl=1" if _successful_update; then return 0; fi @@ -128,27 +128,13 @@ _get_root() { _err "Primary domain list not found!" return 1 fi - # Pick the LONGEST matching zone, dot-anchored: with both domain.tld and - # sub.domain.tld zones on the account, cPanel stores the record in the - # most specific zone, so add and rm must both resolve to that one. - _domain="" - for d in $_domains; do - _debug "Checking if $fulldomain ends with $d" - # case with quoted patterns gives an exact literal suffix match; - # _endswith treats the needle as a regex, so its dots would let - # xdomain.tld wrongly match zone domain.tld - case "$fulldomain" in - "$d" | *".$d") - if [ "${#d}" -gt "${#_domain}" ]; then - _domain="$d" - fi - ;; - esac + for _domain in $_domains; do + _debug "Checking if $fulldomain ends with $_domain" + if (_endswith "$fulldomain" "$_domain"); then + _debug "Root domain: $_domain" + return 0 + fi done - if [ -n "$_domain" ]; then - _debug "Root domain: $_domain" - return 0 - fi return 1 } diff --git a/dnsapi/dns_cpanel_uapi.sh b/dnsapi/dns_cpanel_uapi.sh deleted file mode 100755 index 02a777ae..00000000 --- a/dnsapi/dns_cpanel_uapi.sh +++ /dev/null @@ -1,269 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_cpanel_uapi_info='cPanel UAPI - Manage DNS via cPanel UAPI. Works with API tokens and Two-Factor Authentication. -Site: cpanel.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_cpanel_uapi -Options: - cPanel_Username Username - cPanel_Apitoken API Token - cPanel_Hostname Server URL. E.g. "https://hostname:port" - cPanel_TTL optional TXT record TTL in seconds. Default: 120 -Issues: github.com/acmesh-official/acme.sh/issues/6877 -Author: Adam Bodnar -' - -######## Public functions ##################### - -# Used to add txt record -dns_cpanel_uapi_add() { - fulldomain=$1 - txtvalue=$2 - - _info "Adding TXT record via cPanel UAPI" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _cpanel_uapi_get_root; then - _err "No matching root domain for $fulldomain found" - return 1 - fi - - # Build the record name relative to the zone - _escaped_domain=$(echo "$_domain" | sed 's/\./\\./g') - _record_name=$(echo "$fulldomain" | sed "s/\.${_escaped_domain}$//") - _debug "Record name: $_record_name in zone $_domain" - - # Get the current SOA serial (required by mass_edit_zone) - if ! _cpanel_uapi_get_serial "$_domain"; then - _err "Failed to get zone serial for $_domain" - return 1 - fi - _debug "Zone serial: $_serial" - - # Use configurable TTL, default 120 seconds - _ttl="${cPanel_TTL:-$(_readaccountconf_mutable cPanel_TTL)}" - case "$_ttl" in - "") - _ttl=120 - ;; - *[!0-9]*) - _debug "Invalid cPanel_TTL provided, falling back to default 120" - _ttl=120 - ;; - esac - - # Build JSON and URL-encode it for the add parameter - _add_json=$(printf '{"dname":"%s","ttl":%s,"record_type":"TXT","data":["%s"]}' "$_record_name" "$_ttl" "$txtvalue") - _debug "add_json: $_add_json" - _add_json_encoded=$(printf '%s' "$_add_json" | _url_encode) - _debug "add_json (encoded): $_add_json_encoded" - - if ! _cpanel_uapi_request "execute/DNS/mass_edit_zone?zone=${_domain}&serial=${_serial}&add=${_add_json_encoded}"; then - _err "Request to add TXT record failed for zone $_domain" - return 1 - fi - _debug "_result: $_result" - - if _contains "$_result" '"status":1'; then - _info "TXT record added successfully" - return 0 - fi - _err "Failed to add TXT record." - _err "Response: $_result" - return 1 -} - -# Used to remove the txt record after validation -dns_cpanel_uapi_rm() { - fulldomain=$1 - txtvalue=$2 - - _info "Removing TXT record via cPanel UAPI" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _cpanel_uapi_get_root; then - _err "No matching root domain for $fulldomain found" - return 1 - fi - - if ! _cpanel_uapi_findentry; then - _info "Entry doesn't exist, nothing to delete" - return 0 - fi - - _debug "Deleting record with line_index=$_line_index" - if ! _cpanel_uapi_get_serial "$_domain"; then - _err "Failed to get zone serial for $_domain" - return 1 - fi - if ! _cpanel_uapi_request "execute/DNS/mass_edit_zone?zone=${_domain}&serial=${_serial}&remove=${_line_index}"; then - _err "Request to remove TXT record failed for zone $_domain" - return 1 - fi - _debug "_result: $_result" - - if _contains "$_result" '"status":1'; then - _info "TXT record removed successfully" - return 0 - fi - _err "Failed to remove TXT record." - _err "Response: $_result" - return 1 -} - -#################### Private functions below ################################## - -_cpanel_uapi_checkcredentials() { - cPanel_Username="${cPanel_Username:-$(_readaccountconf_mutable cPanel_Username)}" - cPanel_Apitoken="${cPanel_Apitoken:-$(_readaccountconf_mutable cPanel_Apitoken)}" - cPanel_Hostname="${cPanel_Hostname:-$(_readaccountconf_mutable cPanel_Hostname)}" - - if [ -z "$cPanel_Username" ] || [ -z "$cPanel_Apitoken" ] || [ -z "$cPanel_Hostname" ]; then - cPanel_Username="" - cPanel_Apitoken="" - cPanel_Hostname="" - _err "You haven't specified cPanel_Username, cPanel_Apitoken, and cPanel_Hostname." - return 1 - fi - - # Remove trailing slash from hostname if present - cPanel_Hostname=$(echo "$cPanel_Hostname" | sed 's|/$||') - - _saveaccountconf_mutable cPanel_Username "$cPanel_Username" - _saveaccountconf_mutable cPanel_Apitoken "$cPanel_Apitoken" - _saveaccountconf_mutable cPanel_Hostname "$cPanel_Hostname" - - if [ -n "$cPanel_TTL" ]; then - case "$cPanel_TTL" in - *[!0-9]*) - _info "Ignoring invalid cPanel_TTL: $cPanel_TTL" - cPanel_TTL="" - ;; - *) - _saveaccountconf_mutable cPanel_TTL "$cPanel_TTL" - ;; - esac - fi - return 0 -} - -_cpanel_uapi_request() { - export _H1="Authorization: cpanel $cPanel_Username:$cPanel_Apitoken" - _result=$(_get "$cPanel_Hostname/$1") - return $? -} - -_cpanel_uapi_get_root() { - if ! _cpanel_uapi_checkcredentials; then return 1; fi - - if ! _cpanel_uapi_request "execute/DomainInfo/list_domains"; then - _err "Request to cPanel API failed while listing domains" - return 1 - fi - _debug "DomainInfo response length: ${#_result}" - - if ! _contains "$_result" '"status":1'; then - _err "cPanel UAPI request failed. Is the API token correct?" - _debug "Response: $_result" - return 1 - fi - - # Extract main_domain - _main_domain=$(echo "$_result" | _egrep_o '"main_domain":"[^"]*"' | _head_n 1 | sed 's/.*"main_domain":"//;s/"//') - _debug "main_domain: $_main_domain" - - # Extract addon_domains (array of strings) - _addon_domains=$(echo "$_result" | _egrep_o '"addon_domains":\[[^]]*\]' | sed 's/.*"addon_domains":\[//;s/\]$//' | _egrep_o '"[a-zA-Z0-9._-]+"' | sed 's/"//g') - _debug "addon_domains: $_addon_domains" - - # Build list of all domains to check - _all_domains="$_main_domain $_addon_domains" - _debug "All domains: $_all_domains" - - # Find the matching root domain (prefer longest match) - _best_match="" - _best_len=0 - for _check_domain in $_all_domains; do - if [ -z "$_check_domain" ]; then continue; fi - if _endswith "$fulldomain" "$_check_domain"; then - _len=${#_check_domain} - if [ "$_len" -gt "$_best_len" ]; then - _best_match="$_check_domain" - _best_len="$_len" - fi - fi - done - - if [ -n "$_best_match" ]; then - _domain="$_best_match" - _debug "Root domain: $_domain" - return 0 - fi - return 1 -} - -_cpanel_uapi_get_serial() { - _zone="$1" - if ! _cpanel_uapi_request "execute/DNS/parse_zone?zone=${_zone}"; then - _err "Request to parse zone failed for $_zone" - return 1 - fi - - # Split JSON records onto separate lines using a POSIX-portable sed literal newline - # (\\n in sed replacement is a GNU/BusyBox extension; a backslash-newline works everywhere) - _soa_line=$(echo "$_result" | sed 's/},{/},\ -{/g' | grep '"record_type":"SOA"' | _head_n 1) - _debug "SOA line: $_soa_line" - - if [ -z "$_soa_line" ]; then - _err "SOA record not found for zone $_zone" - _debug "parse_zone response: $_result" - return 1 - fi - - # Extract the third element from data_b64 array (serial is index 2, 0-based) - # data_b64 format: ["ns","admin","SERIAL","refresh","retry","expire","minimum"] - _serial_b64=$(echo "$_soa_line" | _egrep_o '"data_b64":\[[^]]*\]' | sed 's/"data_b64":\[//;s/\]//' | sed 's/"//g' | cut -d',' -f3) - _debug "serial_b64: $_serial_b64" - - if [ -z "$_serial_b64" ]; then - _err "Could not extract serial from SOA record" - return 1 - fi - - _serial=$(printf '%s' "$_serial_b64" | _dbase64) - _debug "Decoded serial: $_serial" - - if [ -z "$_serial" ]; then - _err "Failed to decode serial" - return 1 - fi - return 0 -} - -_cpanel_uapi_findentry() { - _debug "Finding TXT entry for $fulldomain with value $txtvalue" - - if ! _cpanel_uapi_request "execute/DNS/parse_zone?zone=${_domain}"; then - _err "Request to parse zone failed for $_domain" - return 1 - fi - _debug "parse_zone result length: ${#_result}" - - # Base64-encode the txtvalue to match against data_b64 in the response - _b64_txtvalue=$(printf '%s' "$txtvalue" | _base64) - _debug "b64_txtvalue: $_b64_txtvalue" - - # Split records onto separate lines, find matching TXT record by base64 value - _line_index=$(echo "$_result" | sed 's/},{/},\ -{/g' | grep '"record_type":"TXT"' | grep -F "$_b64_txtvalue" | _egrep_o '"line_index":[0-9]+' | _head_n 1 | cut -d: -f2) - _debug "line_index: $_line_index" - - if [ -n "$_line_index" ]; then - _debug "Entry found with line_index=$_line_index" - return 0 - fi - return 1 -} diff --git a/dnsapi/dns_creoline.sh b/dnsapi/dns_creoline.sh deleted file mode 100644 index f4d76f8e..00000000 --- a/dnsapi/dns_creoline.sh +++ /dev/null @@ -1,181 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_creoline_info='creoline -Site: https://www.creoline.com/de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_creoline -Help: https://help.creoline.com -Options: - creolineApiToken - creolineApiSecret -Issues: github.com/acmesh-official/acme.sh/issues/7103 -' - -creolineApi="https://api.creoline.com/v1" - -######## Public functions ##################### - -# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPB8" -dns_creoline_add() { - fulldomain=$1 - txtvalue=$2 - - creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}" - creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}" - - if [ -z "$creolineApiToken" ] || [ -z "$creolineApiSecret" ]; then - _err "Error required creoline API Token or creoline API Secret not specified." - _err "Please set it with the Command 'export creolineApiToken=' and 'export creolineApiSecret='." - return 1 - else - _saveaccountconf_mutable creolineApiToken "$creolineApiToken" - _saveaccountconf_mutable creolineApiSecret "$creolineApiSecret" - fi - - _debug "Detecting the root dns zone." - if ! _get_root "$fulldomain"; then - _err "Error on detecting the root dns zone." - return 1 - fi - - _info "Adding record" - if _creoline_rest POST "dns/zone/$_domain/record" "{\"type\":\"TXT\",\"host\":\"$_sub_domain\",\"record\":\"$txtvalue\",\"ttl\":\"60\"}"; then - if _contains "$response" "$txtvalue"; then - _info "Added, OK" - return 0 - else - _err "Add txt record error." - return 1 - fi - fi - _err "Add txt record error." - return 1 -} - -#fulldomain txtvalue -dns_creoline_rm() { - fulldomain=$1 - txtvalue=$2 - - creolineApiToken="${creolineApiToken:-$(_readaccountconf_mutable creolineApiToken)}" - creolineApiSecret="${creolineApiSecret:-$(_readaccountconf_mutable creolineApiSecret)}" - - _debug "Detecting the root dns zone." - if ! _get_root "$fulldomain"; then - _err "Error on detecting the root dns zone." - return 1 - fi - - _info "Getting earlier created txt record." - if ! _creoline_rest GET "dns/zone/$_domain/record/type/TXT/record/$txtvalue"; then - if _contains "$response" "errors" || _contains "$response" "message"; then - _err "Error on getting earlier created txt record." - return 1 - fi - _err "Error on getting earlier created txt record." - return 1 - fi - - record_id=$(echo "$response" | _egrep_o "\"id\"[ ]*:[ ]*[0-9]+" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") - _debug "record_id" "$record_id" - - if [ -z "$record_id" ]; then - _err "Error on deleting earlier created txt record. No record id found in response." - return 1 - fi - - _info "Deleting earlier created txt record." - if ! _creoline_rest DELETE "dns/zone/$_domain/record/$record_id"; then - if _contains "$response" "errors" || _contains "$response" "message"; then - _err "Error on deleting earlier created txt record." - return 1 - fi - _err "Error on deleting earlier created txt record." - return 1 - fi - - _info "Deleted, OK" - return 0 -} - -#################### Private functions below ################################## -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -_get_root() { - domain=$1 - if ! _creoline_rest GET "dns/zone/root/$domain"; then - return 1 - fi - - _sub_domain=$(echo "$response" | _egrep_o "\"subDomain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") - _debug _sub_domain "$_sub_domain" - - _domain=$(echo "$response" | _egrep_o "\"domain\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | _head_n 1 | tr -d " ") - _debug _domain "$_domain" - - if [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then - return 1 - fi -} - -_creoline_rest() { - method=$1 - uri="$2" - data="$3" - timestamp=$(_time) - canonical_request="${timestamp}.${creolineApi}/${uri}" - signature_hash=$(printf "%s" "$canonical_request" | _hmac sha256 "$(printf "%s" "$creolineApiSecret" | _hex_dump | tr -d " ")" hex) - - _debug method "$method" - _debug uri "$uri" - _debug data "$data" - - _debug2 timestamp "$timestamp" - _debug2 canonical_request "$canonical_request" - _debug2 signature_hash "$signature_hash" - - token_trimmed=$(echo "$creolineApiToken" | tr -d '"') - hmac_trimmed=$(echo "$signature_hash" | tr -d '"') - - export _H1="Content-Type: application/json" - - if [ "$token_trimmed" ]; then - export _H2="X-Api-Token: $token_trimmed" - fi - - if [ "$hmac_trimmed" ]; then - export _H3="X-Creoline-Api-Signature: $hmac_trimmed" - fi - - if [ "$timestamp" ]; then - export _H4="X-Creoline-Api-Timestamp: $timestamp" - fi - - if [ "$method" != "GET" ]; then - response="$(_post "$data" "$creolineApi/$uri" "" "$method")" - else - response="$(_get "$creolineApi/$uri")" - fi - - if [ "$?" != "0" ]; then - _err "error $uri" - return 1 - fi - - _debug response "$response" - - if _contains "$response" "errors"; then - error=$(echo "$response" | _egrep_o "\"errors\":[[]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \" | tr -d "[") - _err "Error: $error" - _err "URI:$uri" - return 1 - elif _contains "$response" "message"; then - message=$(echo "$response" | _egrep_o "\"message\"[ ]*:[ ]*\"[^\"]+\"" | cut -d : -f 2 | tr -d \") - _err "Error: $message" - _err "URI:$uri" - return 1 - fi - - return 0 -} diff --git a/dnsapi/dns_curanet.sh b/dnsapi/dns_curanet.sh index 0ef03fea..4b39f365 100644 --- a/dnsapi/dns_curanet.sh +++ b/dnsapi/dns_curanet.sh @@ -1,21 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_curanet_info='Curanet.dk -Domains: scannet.dk wannafind.dk dandomain.dk -Site: Curanet.dk -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_curanet -Options: - CURANET_AUTHCLIENTID Auth ClientID. Requires scope dns - CURANET_AUTHSECRET Auth Secret -Issues: github.com/acmesh-official/acme.sh/issues/3933 -Author: Peter L. Hansen -' + +#Script to use with curanet.dk, scannet.dk, wannafind.dk, dandomain.dk DNS management. +#Requires api credentials with scope: dns +#Author: Peter L. Hansen +#Version 1.0 CURANET_REST_URL="https://api.curanet.dk/dns/v1/Domains" CURANET_AUTH_URL="https://apiauth.dk.team.blue/auth/realms/Curanet/protocol/openid-connect/token" CURANET_ACCESS_TOKEN="" -######## Public functions #################### +######## Public functions ##################### #Usage: dns_curanet_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_curanet_add() { @@ -142,7 +136,7 @@ _get_root() { i=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -154,7 +148,7 @@ _get_root() { export _H3="Authorization: Bearer $CURANET_ACCESS_TOKEN" response="$(_get "$CURANET_REST_URL/$h/Records" "" "")" - if [ ! "$(echo "$response" | _egrep_o "Entity not found|Bad Request")" ]; then + if [ ! "$(echo "$response" | _egrep_o "Entity not found")" ]; then _domain=$h return 0 fi diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 6677b32f..830e8831 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -1,15 +1,21 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_cyon_info='cyon.ch -Site: cyon.ch -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_cyon -Options: - CY_Username Username - CY_Password API Token - CY_OTP_Secret OTP token. Only required if using 2FA -Issues: github.com/noplanman/cyon-api/issues -Author: Armando Lüscher -' + +######## +# Custom cyon.ch DNS API for use with [acme.sh](https://github.com/acmesh-official/acme.sh) +# +# Usage: acme.sh --issue --dns dns_cyon -d www.domain.com +# +# Dependencies: +# ------------- +# - oathtool (When using 2 Factor Authentication) +# +# Issues: +# ------- +# Any issues / questions / suggestions can be posted here: +# https://github.com/noplanman/cyon-api/issues +# +# Author: Armando Lüscher +######## dns_cyon_add() { _cyon_load_credentials && @@ -101,8 +107,6 @@ _cyon_load_parameters() { # This header is required for curl calls. _H1="X-Requested-With: XMLHttpRequest" export _H1 - _H3="User-Agent: cyon-dns-acmesh/1.0" - export _H3 } _cyon_print_header() { @@ -127,11 +131,7 @@ _cyon_print_header() { } _cyon_get_cookie_header() { - # Extract all cookies from the response headers (case-insensitive) - _cookies="$(grep -i "^set-cookie:" "$HTTP_HEADER" | sed 's/^[Ss]et-[Cc]ookie: //' | sed 's/;.*//' | tr '\n' '; ' | sed 's/; $//')" - if [ -n "$_cookies" ]; then - printf "Cookie: %s" "$_cookies" - fi + printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')" } _cyon_login() { @@ -161,12 +161,7 @@ _cyon_login() { _get "https://my.cyon.ch/" >/dev/null - # Update cookie after loading main page (only if new cookies are set) - _new_cookies="$(_cyon_get_cookie_header)" - if [ -n "$_new_cookies" ]; then - _H2="$_new_cookies" - export _H2 - fi + # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. # 2FA authentication with OTP? if [ -n "${CY_OTP_Secret}" ]; then @@ -195,13 +190,6 @@ _cyon_login() { fi _info " success" - - # Update cookie after 2FA (only if new cookies are set) - _new_cookies="$(_cyon_get_cookie_header)" - if [ -n "$_new_cookies" ]; then - _H2="$_new_cookies" - export _H2 - fi fi _info "" @@ -223,17 +211,7 @@ _cyon_change_domain_env() { domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')" _debug "Changing domain environment to ${domain_env}" - domain_page_response="$(_get "https://my.cyon.ch/domain/")" - _debug domain_page_response "${domain_page_response}" - - # Check if we got an error response (JSON) instead of HTML - if printf "%s" "${domain_page_response}" | grep -q '"iserror":true'; then - _err " $(printf "%s" "${domain_page_response}" | _cyon_get_response_message)" - _err "" - return 1 - fi - - gloo_item_key="$(printf "%s" "${domain_page_response}" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")" + gloo_item_key="$(_get "https://my.cyon.ch/domain/" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")" _debug gloo_item_key "${gloo_item_key}" domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}" @@ -243,8 +221,10 @@ _cyon_change_domain_env() { if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi + domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)" + # Bail if domain environment change fails. - if [ "$(printf "%s" "${domain_env_response}" | _cyon_get_environment_change_status)" != "true" ]; then + if [ "${domain_env_success}" != "true" ]; then _err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)" _err "" return 1 @@ -258,7 +238,7 @@ _cyon_add_txt() { _info " - Adding DNS TXT entry..." add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async" - add_txt_data="name=${fulldomain_idn}.&ttl=900&type=TXT&dnscontent=${txtvalue}" + add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}" add_txt_response="$(_post "$add_txt_data" "$add_txt_url")" _debug add_txt_response "${add_txt_response}" @@ -267,10 +247,9 @@ _cyon_add_txt() { add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)" add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)" - add_txt_validation="$(printf "%s" "${add_txt_response}" | _cyon_get_validation_status)" # Bail if adding TXT entry fails. - if [ "${add_txt_status}" != "true" ] || [ "${add_txt_validation}" != "true" ]; then + if [ "${add_txt_status}" != "true" ]; then _err " ${add_txt_message}" _err "" return 1 @@ -285,15 +264,15 @@ _cyon_delete_txt() { list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async" - list_txt_response="$(_get "${list_txt_url}")" + list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')" _debug list_txt_response "${list_txt_response}" if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi # Find and delete all acme challenge entries for the $fulldomain. - _dns_entries="$(printf "%s\n" "${list_txt_response}" | _egrep_o 'data-hash=\\"[^"]*\\" data-identifier=\\"[^"]*\\"' | sed 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\"/\1 \2/')" + _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')" - printf "%s\n" "${_dns_entries}" | while read -r _hash _identifier; do + printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)" dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)" @@ -332,21 +311,13 @@ _cyon_get_response_message() { } _cyon_get_response_status() { - _egrep_o '"status":[a-zA-Z0-9]*' | cut -d : -f 2 -} - -_cyon_get_validation_status() { - _egrep_o '"valid":[a-zA-Z0-9]*' | cut -d : -f 2 + _egrep_o '"status":\w*' | cut -d : -f 2 } _cyon_get_response_success() { _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' } -_cyon_get_environment_change_status() { - _egrep_o '"authenticated":[a-zA-Z0-9]*' | cut -d : -f 2 -} - _cyon_check_if_2fa_missed() { # Did we miss the 2FA? if test "${1#*multi_factor_form}" != "${1}"; then diff --git a/dnsapi/dns_czechia.sh b/dnsapi/dns_czechia.sh deleted file mode 100644 index 6ad60442..00000000 --- a/dnsapi/dns_czechia.sh +++ /dev/null @@ -1,204 +0,0 @@ -#!/usr/bin/env sh - -# dns_czechia.sh - CZECHIA.COM/ZONER DNS API for acme.sh (DNS-01) -# -# Documentation: https://api.czechia.com/swagger/index.html - -#shellcheck disable=SC2034 -dns_czechia_info='[ - {"name":"CZ_AuthorizationToken","usage":"Your API token from CZECHIA.COM/Zoner administration.","required":"1"}, - {"name":"CZ_Zones","usage":"Managed zones separated by comma or space (e.g. \"example.com\").","required":"1"}, - {"name":"CZ_API_BASE","usage":"Defaults to https://api.czechia.com","required":"0"} -]' - -dns_czechia_add() { - fulldomain="$1" - txtvalue="$2" - - _debug "dns_czechia_add fulldomain='$fulldomain'" - - if [ -z "$fulldomain" ] || [ -z "$txtvalue" ]; then - _err "dns_czechia_add: missing fulldomain or txtvalue" - return 1 - fi - - _czechia_load_conf || return 1 - - _current_zone=$(_czechia_pick_zone "$fulldomain") - if [ -z "$_current_zone" ]; then - _err "No matching zone found for $fulldomain. Please check CZ_Zones." - return 1 - fi - - _czechia_tab="$(printf '\t')" - _cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//") - _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//") - - if [ -z "$_cz" ] || [ -z "$_tk" ]; then - _err "Missing zone or CZ_AuthorizationToken." - return 1 - fi - - _url="$CZ_API_BASE/api/DNS/$_cz/TXT" - _fd=$(printf "%s" "$fulldomain" | _lower_case | sed 's/\.$//') - - if [ "$_fd" = "$_cz" ]; then - _h="@" - else - # Remove the literal "." suffix from _fd, if present - _h=${_fd%."$_cz"} - [ "$_h" = "$_fd" ] && _h="@" - fi - [ -z "$_h" ] && _h="@" - - _info "Adding TXT record for $_h in zone $_cz" - - _h_esc=$(printf "%s" "$_h" | sed 's/\\/\\\\/g; s/"/\\"/g') - _txt_esc=$(printf "%s" "$txtvalue" | sed 's/\\/\\\\/g; s/"/\\"/g') - _body="{\"hostName\":\"$_h_esc\",\"text\":\"$_txt_esc\",\"ttl\":300,\"publishZone\":1}" - - _debug "URL: $_url" - _debug "Body: $_body" - - export _H1="Content-Type: application/json" - export _H2="AuthorizationToken: $_tk" - - _res="$(_post "$_body" "$_url" "" "POST")" - _post_exit="$?" - _debug2 "Response: $_res" - - if [ "$_post_exit" -ne 0 ]; then - _err "API request failed. exit code $_post_exit" - return 1 - fi - - if _contains "$_res" "already exists"; then - _info "Record already exists, skipping." - return 0 - fi - - _nres="$(printf '%s' "$_res" | _normalizeJson)" - if [ "$?" -ne 0 ] || [ -z "$_nres" ]; then - _nres="$_res" - fi - - if _contains "$_nres" "\"status\":4" || _contains "$_nres" "\"status\":5" || _contains "$_nres" "\"errors\""; then - _err "API error: $_res" - return 1 - fi - - return 0 -} - -dns_czechia_rm() { - fulldomain="$1" - txtvalue="$2" - - _debug "dns_czechia_rm fulldomain='$fulldomain'" - - if [ -z "$fulldomain" ] || [ -z "$txtvalue" ]; then - _err "dns_czechia_rm: missing fulldomain or txtvalue" - return 1 - fi - - _czechia_load_conf || return 1 - - _current_zone=$(_czechia_pick_zone "$fulldomain") - if [ -z "$_current_zone" ]; then - _err "No matching zone found for $fulldomain. Please check CZ_Zones configuration." - return 1 - fi - - _czechia_tab="$(printf '\t')" - _cz=$(printf "%s" "$_current_zone" | _lower_case | sed "s/[ $_czechia_tab]//g; s/\.\$//") - _tk=$(printf "%s" "$CZ_AuthorizationToken" | sed "s/^[ $_czechia_tab]*//; s/[ $_czechia_tab]*\$//") - - if [ -z "$_cz" ] || [ -z "$_tk" ]; then - _err "Missing zone or CZ_AuthorizationToken." - return 1 - fi - - _url="$CZ_API_BASE/api/DNS/$_cz/TXT" - _fd=$(printf "%s" "$fulldomain" | _lower_case | sed 's/\.$//') - - if [ "$_fd" = "$_cz" ]; then - _h="@" - else - _h=$(printf "%s" "$_fd" | sed "s/\.$_cz$//") - [ "$_h" = "$_fd" ] && _h="@" - fi - [ -z "$_h" ] && _h="@" - - _h_esc=$(printf "%s" "$_h" | sed 's/\\/\\\\/g; s/"/\\"/g') - _txt_esc=$(printf "%s" "$txtvalue" | sed 's/\\/\\\\/g; s/"/\\"/g') - _body="{\"hostName\":\"$_h_esc\",\"text\":\"$_txt_esc\",\"ttl\":300,\"publishZone\":1}" - - _debug "URL: $_url" - _debug "Body: $_body" - - export _H1="Content-Type: application/json" - export _H2="AuthorizationToken: $_tk" - - _res="$(_post "$_body" "$_url" "" "DELETE")" - _post_exit="$?" - _debug2 "Response: $_res" - - if [ "$_post_exit" -ne 0 ]; then - _err "CZECHIA DNS API DELETE request failed for $_fd: exit code $_post_exit, response: $_res" - return 1 - fi - - _res_normalized=$(printf '%s' "$_res" | _normalizeJson) - - if _contains "$_res_normalized" '"isError":true'; then - _err "CZECHIA DNS API reported an error while deleting TXT for $_fd: $_res" - return 1 - fi - - return 0 -} - -_czechia_load_conf() { - CZ_AuthorizationToken="${CZ_AuthorizationToken:-$(_readaccountconf_mutable CZ_AuthorizationToken)}" - if [ -z "$CZ_AuthorizationToken" ]; then - _err "Missing CZ_AuthorizationToken" - return 1 - fi - - CZ_Zones="${CZ_Zones:-$(_readaccountconf_mutable CZ_Zones)}" - if [ -z "$CZ_Zones" ]; then - _err "Missing CZ_Zones" - return 1 - fi - - CZ_API_BASE="${CZ_API_BASE:-$(_readaccountconf_mutable CZ_API_BASE)}" - [ -z "$CZ_API_BASE" ] && CZ_API_BASE="https://api.czechia.com" - - _saveaccountconf_mutable CZ_AuthorizationToken "$CZ_AuthorizationToken" - _saveaccountconf_mutable CZ_Zones "$CZ_Zones" - _saveaccountconf_mutable CZ_API_BASE "$CZ_API_BASE" - - return 0 -} - -_czechia_pick_zone() { - _czechia_pz_tab="$(printf '\t')" - _fd=$(printf "%s" "$1" | _lower_case | sed 's/\.$//') - _best_zone="" - - _zones_space=$(printf "%s" "$CZ_Zones" | sed 's/,/ /g') - for _z in $_zones_space; do - _clean_z=$(printf "%s" "$_z" | _lower_case | sed "s/[ $_czechia_pz_tab]//g; s/\.\$//") - [ -z "$_clean_z" ] && continue - - case "$_fd" in - "$_clean_z" | *."$_clean_z") - if [ ${#_clean_z} -gt ${#_best_zone} ]; then - _best_zone="$_clean_z" - fi - ;; - esac - done - - printf "%s" "$_best_zone" -} diff --git a/dnsapi/dns_da.sh b/dnsapi/dns_da.sh index d9cf6247..4d3e09b1 100755 --- a/dnsapi/dns_da.sh +++ b/dnsapi/dns_da.sh @@ -1,14 +1,31 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_da_info='DirectAdmin Server API -Site: DirectAdmin.com/api.php -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_da -Options: - DA_Api API Server URL. E.g. "https://remoteUser:remotePassword@da.domain.tld:8443". Special characters in the user/password must be percent-encoded, e.g. "@" -> "%40". - DA_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept -Issues: github.com/TigerP/acme.sh/issues -' - +# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*- +# vim: et ts=2 sw=2 +# +# DirectAdmin 1.41.0 API +# The DirectAdmin interface has it's own Let's encrypt functionality, but this +# script can be used to generate certificates for names which are not hosted on +# DirectAdmin +# +# User must provide login data and URL to DirectAdmin incl. port. +# You can create login key, by using the Login Keys function +# ( https://da.example.com:8443/CMD_LOGIN_KEYS ), which only has access to +# - CMD_API_DNS_CONTROL +# - CMD_API_SHOW_DOMAINS +# +# See also https://www.directadmin.com/api.php and +# https://www.directadmin.com/features.php?id=1298 +# +# Report bugs to https://github.com/TigerP/acme.sh/issues +# +# Values to export: +# export DA_Api="https://remoteUser:remotePassword@da.example.com:8443" +# export DA_Api_Insecure=1 +# +# Set DA_Api_Insecure to 1 for insecure and 0 for secure -> difference is +# whether ssl cert is checked for validity (0) or whether it is just accepted +# (1) +# ######## Public functions ##################### # Usage: dns_myapi_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -61,7 +78,7 @@ _get_root() { # response will contain "list[]=example.com&list[]=example.org" _da_api CMD_API_SHOW_DOMAINS "" "${domain}" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then # not valid @@ -69,7 +86,7 @@ _get_root() { return 1 fi if _contains "$response" "$h" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_ddnss.sh b/dnsapi/dns_ddnss.sh index 0ac353d4..b9da33ff 100644 --- a/dnsapi/dns_ddnss.sh +++ b/dnsapi/dns_ddnss.sh @@ -1,13 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ddnss_info='DDNSS.de -Site: DDNSS.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ddnss -Options: - DDNSS_Token API Token -Issues: github.com/acmesh-official/acme.sh/issues/2230 -Author: @helbgd, @mod242 -' + +#Created by RaidenII, to use DuckDNS's API to add/remove text records +#modified by helbgd @ 03/13/2018 to support ddnss.de +#modified by mod242 @ 04/24/2018 to support different ddnss domains +#Please note: the Wildcard Feature must be turned on for the Host record +#and the checkbox for TXT needs to be enabled + +# Pass credentials before "acme.sh --issue --dns dns_ddnss ..." +# -- +# export DDNSS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" +# -- +# DDNSS_DNS_API="https://ddnss.de/upd.php" diff --git a/dnsapi/dns_desec.sh b/dnsapi/dns_desec.sh index e5e4809a..495a6780 100644 --- a/dnsapi/dns_desec.sh +++ b/dnsapi/dns_desec.sh @@ -1,13 +1,11 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_desec_info='deSEC.io -Site: desec.readthedocs.io/en/latest/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_desec -Options: - DEDYN_TOKEN API Token -Issues: github.com/acmesh-official/acme.sh/issues/2180 -Author: Zheng Qian -' +# +# deSEC.io Domain API +# +# Author: Zheng Qian +# +# deSEC API doc +# https://desec.readthedocs.io/en/latest/ REST_API="https://desec.io/api/v1/domains" @@ -39,7 +37,6 @@ dns_desec_add() { _err "invalid domain" return 1 fi - _sub_domain=$(echo "$_sub_domain" | _lower_case) _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" @@ -49,7 +46,7 @@ dns_desec_add() { _desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/" if [ "$_code" = "200" ]; then - oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"[^ ]*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")" + oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")" _debug "existing TXT found" _debug oldtxtvalues "$oldtxtvalues" if [ -n "$oldtxtvalues" ]; then @@ -101,7 +98,7 @@ dns_desec_rm() { _err "invalid domain" return 1 fi - _sub_domain=$(echo "$_sub_domain" | _lower_case) + _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" @@ -111,7 +108,7 @@ dns_desec_rm() { _desec_rest GET "$REST_API/$_domain/rrsets/$_sub_domain/TXT/" if [ "$_code" = "200" ]; then - oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"[^ ]*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")" + oldtxtvalues="$(echo "$response" | _egrep_o "\"records\":\\[\"\\S*\"\\]" | cut -d : -f 2 | tr -d "[]\\\\\"" | sed "s/,/ /g")" _debug "existing TXT found" _debug oldtxtvalues "$oldtxtvalues" if [ -n "$oldtxtvalues" ]; then @@ -151,8 +148,6 @@ _desec_rest() { if [ "$m" != "GET" ]; then _secure_debug2 data "$data" response="$(_post "$data" "$ep" "" "$m")" - _info "Sleeping 1s to respect deSEC write rate limit" - _sleep 1 else response="$(_get "$ep")" fi @@ -179,7 +174,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -191,7 +186,7 @@ _get_root() { fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_df.sh b/dnsapi/dns_df.sh index 513e350c..c0499ddf 100644 --- a/dnsapi/dns_df.sh +++ b/dnsapi/dns_df.sh @@ -1,15 +1,18 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_df_info='DynDnsFree.de -Domains: dynup.de -Site: DynDnsFree.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_df -Options: - DF_user Username - DF_password Password -Issues: github.com/acmesh-official/acme.sh/issues/2897 -Author: Thilo Gass -' + +######################################################################## +# https://dyndnsfree.de hook script for acme.sh +# +# Environment variables: +# +# - $DF_user (your dyndnsfree.de username) +# - $DF_password (your dyndnsfree.de password) +# +# Author: Thilo Gass +# Git repo: https://github.com/ThiloGa/acme.sh + +#-- dns_df_add() - Add TXT record -------------------------------------- +# Usage: dns_df_add _acme-challenge.subdomain.domain.com "XyZ123..." dyndnsfree_api="https://dynup.de/acme.php" diff --git a/dnsapi/dns_dgon.sh b/dnsapi/dns_dgon.sh index cb887cfa..afe1b32e 100755 --- a/dnsapi/dns_dgon.sh +++ b/dnsapi/dns_dgon.sh @@ -1,12 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dgon_info='DigitalOcean.com -Site: DigitalOcean.com/help/api/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dgon -Options: - DO_API_KEY API Key -Author: -' + +## Will be called by acme.sh to add the txt record to your api system. +## returns 0 means success, otherwise error. + +## Author: thewer +## GitHub: https://github.com/gitwer/acme.sh + +## +## Environment Variables Required: +## +## DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc" +## ##################### Public functions ##################### @@ -203,7 +207,7 @@ _get_base_domain() { _debug2 domain_list "$domain_list" i=1 - while [ "$i" -gt 0 ]; do + while [ $i -gt 0 ]; do ## get next longest domain _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") ## check we got something back from our cut (or are we at the end) @@ -215,14 +219,14 @@ _get_base_domain() { ## check if it exists if [ -n "$found" ]; then ## exists - exit loop returning the parts - sub_point=$(_math "$i" - 1) + sub_point=$(_math $i - 1) _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") _debug _domain "$_domain" _debug _sub_domain "$_sub_domain" return 0 fi ## increment cut point $i - i=$(_math "$i" + 1) + i=$(_math $i + 1) done if [ -z "$found" ]; then diff --git a/dnsapi/dns_dnsexit.sh b/dnsapi/dns_dnsexit.sh deleted file mode 100644 index b92482cf..00000000 --- a/dnsapi/dns_dnsexit.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dnsexit_info='DNSExit.com -Site: DNSExit.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsexit -Options: - DNSEXIT_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/4719 -Author: Samuel Jimenez -' - -DNSEXIT_API_URL="https://api.dnsexit.com/dns/" - -######## Public functions ##################### -#Usage: dns_dnsexit_add _acme-challenge.*.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_dnsexit_add() { - fulldomain=$1 - txtvalue=$2 - _info "Using DNSExit.com" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - _debug 'Load account auth' - if ! get_account_info; then - return 1 - fi - - _dnsexit_zone_op add ',"ttl":1,"overwrite":false' -} - -#Usage: fulldomain txtvalue -#Remove the txt record after validation. -dns_dnsexit_rm() { - fulldomain=$1 - txtvalue=$2 - _info "Using DNSExit.com" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - _debug 'Load account auth' - if ! get_account_info; then - return 1 - fi - - _dnsexit_zone_op delete '' -} - -#################### Private functions below ################################## -# The legacy zone-detection endpoint (update.dnsexit.com/ipupdate/hosts.jsp) -# was shut down by DNSExit and now returns 503, and the JSON API offers no -# zone-list call. So find the root zone by attempting the actual operation at -# each domain level: the API answers "code":0 only when the domain matches a -# zone of the account. https://github.com/acmesh-official/acme.sh/issues/6914 -#Usage: _dnsexit_zone_op -_dnsexit_zone_op() { - _op="$1" - _extra="$2" - i=1 - while true; do - _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) - _debug _domain "$_domain" - if [ -z "$_domain" ]; then - _err "Could not find the root zone of $fulldomain in your DNSExit account" - return 1 - fi - - _sub_domain="$(printf "%s" "$fulldomain" | sed "s/\\.$_domain\$//")" - if [ "$_sub_domain" = "$fulldomain" ]; then - _sub_domain="" - fi - _debug _sub_domain "$_sub_domain" - - if _dnsexit_rest "{\"domain\":\"$_domain\",\"$_op\":{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\"$_extra}}"; then - if _contains "$response" "\"code\":0" || _contains "$response" "\"code\": 0"; then - _debug2 _response "$response" - return 0 - fi - _debug "Zone $_domain was not accepted, trying the next level" "$response" - fi - i=$(_math "$i" + 1) - done -} - -_dnsexit_rest() { - m=POST - ep="" - data="$1" - _debug _dnsexit_rest "$ep" - _debug data "$data" - - api_key_trimmed=$(echo "$DNSEXIT_API_KEY" | tr -d '"') - - export _H1="apikey: $api_key_trimmed" - export _H2='Content-Type: application/json' - - if [ "$m" != "GET" ]; then - _debug data "$data" - response="$(_post "$data" "$DNSEXIT_API_URL/$ep" "" "$m")" - else - response="$(_get "$DNSEXIT_API_URL/$ep")" - fi - - if [ "$?" != "0" ]; then - _err "Error $ep" - return 1 - fi - - _debug2 response "$response" - return 0 -} - -get_account_info() { - DNSEXIT_API_KEY="${DNSEXIT_API_KEY:-$(_readaccountconf_mutable DNSEXIT_API_KEY)}" - if test -z "$DNSEXIT_API_KEY"; then - DNSEXIT_API_KEY='' - _err 'DNSEXIT_API_KEY was not exported' - return 1 - fi - - _saveaccountconf_mutable DNSEXIT_API_KEY "$DNSEXIT_API_KEY" - - return 0 -} diff --git a/dnsapi/dns_dnshome.sh b/dnsapi/dns_dnshome.sh index 6d583246..99608769 100755 --- a/dnsapi/dns_dnshome.sh +++ b/dnsapi/dns_dnshome.sh @@ -1,14 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dnshome_info='dnsHome.de -Site: dnsHome.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnshome -Options: - DNSHOME_Subdomain Subdomain - DNSHOME_SubdomainPassword Subdomain Password -Issues: github.com/acmesh-official/acme.sh/issues/3819 -Author: @dnsHome-de -' + +# dnsHome.de API for acme.sh +# +# This Script adds the necessary TXT record to a Subdomain +# +# Author dnsHome.de (https://github.com/dnsHome-de) +# +# Report Bugs to https://github.com/acmesh-official/acme.sh/issues/3819 +# +# export DNSHOME_Subdomain="" +# export DNSHOME_SubdomainPassword="" # Usage: add subdomain.ddnsdomain.tld "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record diff --git a/dnsapi/dns_dnsimple.sh b/dnsapi/dns_dnsimple.sh index 257549b4..d831eb2b 100644 --- a/dnsapi/dns_dnsimple.sh +++ b/dnsapi/dns_dnsimple.sh @@ -1,13 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dnsimple_info='DNSimple.com -Site: DNSimple.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dnsimple -Options: - DNSimple_OAUTH_TOKEN OAuth Token - DNSimple_ACCOUNT_ID Account ID. Optional, only needed when the token can access multiple accounts. -Issues: github.com/pho3nixf1re/acme.sh/issues -' + +# DNSimple domain api +# https://github.com/pho3nixf1re/acme.sh/issues +# +# This is your oauth token which can be acquired on the account page. Please +# note that this must be an _account_ token and not a _user_ token. +# https://dnsimple.com/a//account/access_tokens +# DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" DNSimple_API="https://api.dnsimple.com/v2" @@ -18,7 +17,6 @@ dns_dnsimple_add() { fulldomain=$1 txtvalue=$2 - DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}" if [ -z "$DNSimple_OAUTH_TOKEN" ]; then DNSimple_OAUTH_TOKEN="" _err "You have not set the dnsimple oauth token yet." @@ -27,10 +25,10 @@ dns_dnsimple_add() { fi # save the oauth token for later - _saveaccountconf_mutable DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN" + _saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN" if ! _get_account_id; then - _err "failed to retrieve account id" + _err "failed to retrive account id" return 1 fi @@ -58,14 +56,8 @@ dns_dnsimple_add() { dns_dnsimple_rm() { fulldomain=$1 - DNSimple_OAUTH_TOKEN="${DNSimple_OAUTH_TOKEN:-$(_readaccountconf_mutable DNSimple_OAUTH_TOKEN)}" - if [ -z "$DNSimple_OAUTH_TOKEN" ]; then - _err "You have not set the dnsimple oauth token yet." - return 1 - fi - if ! _get_account_id; then - _err "failed to retrieve account id" + _err "failed to retrive account id" return 1 fi @@ -100,7 +92,7 @@ _get_root() { i=2 previous=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then # not valid return 1 @@ -113,7 +105,7 @@ _get_root() { if _contains "$response" 'not found'; then _debug "$h not found" else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$previous") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$previous) _domain="$h" _debug _domain "$_domain" @@ -130,16 +122,13 @@ _get_root() { # returns _account_id _get_account_id() { - DNSimple_ACCOUNT_ID="${DNSimple_ACCOUNT_ID:-$(_readaccountconf_mutable DNSimple_ACCOUNT_ID)}" - if [ "$DNSimple_ACCOUNT_ID" ]; then - _saveaccountconf_mutable DNSimple_ACCOUNT_ID "$DNSimple_ACCOUNT_ID" - _account_id="$DNSimple_ACCOUNT_ID" - _debug _account_id "$_account_id" - return 0 + _debug "retrive account id" + if ! _dnsimple_rest GET "whoami"; then + return 1 fi - _debug "retrieve account id" - if ! _dnsimple_rest GET "whoami"; then + if _contains "$response" "\"account\":null"; then + _err "no account associated with this token" return 1 fi @@ -148,25 +137,7 @@ _get_account_id() { return 1 fi - if _contains "$response" "\"account\":null"; then - # the whoami of a user token (dnsimple_u_*) carries no account, - # so list the accounts the token can access instead - # https://github.com/acmesh-official/acme.sh/issues/6491 - if ! _dnsimple_rest GET "accounts"; then - return 1 - fi - fi - _account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1) - if [ -z "$_account_id" ]; then - _err "no account associated with this token" - return 1 - fi - if [ "$(echo "$_account_id" | wc -l)" -gt 1 ]; then - _err "The token has access to multiple accounts, please pick one and set it explicitly:" - _err "export DNSimple_ACCOUNT_ID=" - return 1 - fi _debug _account_id "$_account_id" return 0 diff --git a/dnsapi/dns_dnsservices.sh b/dnsapi/dns_dnsservices.sh index 44cc6f45..008153a4 100755 --- a/dnsapi/dns_dnsservices.sh +++ b/dnsapi/dns_dnsservices.sh @@ -1,15 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dnsservices_info='DNS.Services -Site: DNS.Services -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dnsservices -Options: - DnsServices_Username Username - DnsServices_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/4152 -Author: Bjarke Bruun -' +#This file name is "dns_dnsservices.sh" +#Script for Danish DNS registra and DNS hosting provider https://dns.services + +#Author: Bjarke Bruun +#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 ##################### diff --git a/dnsapi/dns_do.sh b/dnsapi/dns_do.sh new file mode 100755 index 00000000..3850890c --- /dev/null +++ b/dnsapi/dns_do.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env sh + +# DNS API for Domain-Offensive / Resellerinterface / Domainrobot + +# Report bugs at https://github.com/seidler2547/acme.sh/issues + +# set these environment variables to match your customer ID and password: +# DO_PID="KD-1234567" +# DO_PW="cdfkjl3n2" + +DO_URL="https://soap.resellerinterface.de/" + +######## Public functions ##################### + +#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_do_add() { + fulldomain=$1 + txtvalue=$2 + if _dns_do_authenticate; then + _info "Adding TXT record to ${_domain} as ${fulldomain}" + _dns_do_soap createRR origin "${_domain}" name "${fulldomain}" type TXT data "${txtvalue}" ttl 300 + if _contains "${response}" '>success<'; then + return 0 + fi + _err "Could not create resource record, check logs" + fi + return 1 +} + +#fulldomain +dns_do_rm() { + fulldomain=$1 + if _dns_do_authenticate; then + if _dns_do_list_rrs; then + _dns_do_had_error=0 + for _rrid in ${_rr_list}; do + _info "Deleting resource record $_rrid for $_domain" + _dns_do_soap deleteRR origin "${_domain}" rrid "${_rrid}" + if ! _contains "${response}" '>success<'; then + _dns_do_had_error=1 + _err "Could not delete resource record for ${_domain}, id ${_rrid}" + fi + done + return $_dns_do_had_error + fi + fi + return 1 +} + +#################### Private functions below ################################## +_dns_do_authenticate() { + _info "Authenticating as ${DO_PID}" + _dns_do_soap authPartner partner "${DO_PID}" password "${DO_PW}" + if _contains "${response}" '>success<'; then + _get_root "$fulldomain" + _debug "_domain $_domain" + return 0 + else + _err "Authentication failed, are DO_PID and DO_PW set correctly?" + fi + return 1 +} + +_dns_do_list_rrs() { + _dns_do_soap getRRList origin "${_domain}" + if ! _contains "${response}" 'SOAP-ENC:Array'; then + _err "getRRList origin ${_domain} failed" + return 1 + fi + _rr_list="$(echo "${response}" | + tr -d "\n\r\t" | + sed -e 's//\n/g' | + grep ">$(_regexcape "$fulldomain")" | + sed -e 's/<\/item>/\n/g' | + grep '>id[0-9]{1,16}<' | + tr -d '><')" + [ "${_rr_list}" ] +} + +_dns_do_soap() { + func="$1" + shift + # put the parameters to xml + body="" + while [ "$1" ]; do + _k="$1" + shift + _v="$1" + shift + body="$body<$_k>$_v" + done + body="$body" + _debug2 "SOAP request ${body}" + + # build SOAP XML + _xml=' + + '"$body"' +' + + # set SOAP headers + export _H1="SOAPAction: ${DO_URL}#${func}" + + if ! response="$(_post "${_xml}" "${DO_URL}")"; then + _err "Error <$1>" + return 1 + fi + _debug2 "SOAP response $response" + + # retrieve cookie header + _H2="$(_egrep_o 'Cookie: [^;]+' <"$HTTP_HEADER" | _head_n 1)" + export _H2 + + return 0 +} + +_get_root() { + domain=$1 + i=1 + + _dns_do_soap getDomainList + _all_domains="$(echo "${response}" | + tr -d "\n\r\t " | + _egrep_o 'domain]+>[^<]+' | + sed -e 's/^domain<\/key>]*>//g')" + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + if [ -z "$h" ]; then + return 1 + fi + + if _contains "${_all_domains}" "^$(_regexcape "$h")\$"; then + _domain="$h" + return 0 + fi + + i=$(_math $i + 1) + done + _debug "$domain not found" + + return 1 +} + +_regexcape() { + echo "$1" | sed -e 's/\([]\.$*^[]\)/\\\1/g' +} diff --git a/dnsapi/dns_doapi.sh b/dnsapi/dns_doapi.sh index 0804f2e6..a001d52c 100755 --- a/dnsapi/dns_doapi.sh +++ b/dnsapi/dns_doapi.sh @@ -1,16 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_doapi_info='Domain-Offensive do.de - Official LetsEncrypt API for do.de / Domain-Offensive. - This API is also available to private customers/individuals. -Site: do.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_doapi -Options: - DO_LETOKEN LetsEncrypt Token -Issues: github.com/acmesh-official/acme.sh/issues/2057 -' -DO_API="https://my.do.de/api/letsencrypt" +# Official Let's Encrypt API for do.de / Domain-Offensive +# +# This is different from the dns_do adapter, because dns_do is only usable for enterprise customers +# This API is also available to private customers/individuals +# +# Provide the required LetsEncrypt token like this: +# DO_LETOKEN="FmD408PdqT1E269gUK57" + +DO_API="https://www.do.de/api/letsencrypt" ######## Public functions ##################### diff --git a/dnsapi/dns_domeneshop.sh b/dnsapi/dns_domeneshop.sh index 925ca335..9a3791f4 100644 --- a/dnsapi/dns_domeneshop.sh +++ b/dnsapi/dns_domeneshop.sh @@ -1,13 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_domeneshop_info='DomeneShop.no -Site: DomeneShop.no -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_domeneshop -Options: - DOMENESHOP_Token Token - DOMENESHOP_Secret Secret -Issues: github.com/acmesh-official/acme.sh/issues/2457 -' DOMENESHOP_Api_Endpoint="https://api.domeneshop.no/v0" @@ -93,7 +84,7 @@ _get_domainid() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug "h" "$h" if [ -z "$h" ]; then #not valid @@ -102,7 +93,7 @@ _get_domainid() { if _contains "$response" "\"$h\"" >/dev/null; then # We have found the domain name. - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h _domainid=$(printf "%s" "$response" | _egrep_o "[^{]*\"domain\":\"$_domain\"[^}]*" | _egrep_o "\"id\":[0-9]+" | cut -d : -f 2) return 0 diff --git a/dnsapi/dns_dp.sh b/dnsapi/dns_dp.sh index 7bc331e2..9b8b7a8b 100755 --- a/dnsapi/dns_dp.sh +++ b/dnsapi/dns_dp.sh @@ -1,12 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dp_info='DNSPod.cn -Site: DNSPod.cn -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dp -Options: - DP_Id Id - DP_Key Key -' + +# Dnspod.cn Domain api +# +#DP_Id="1234" +# +#DP_Key="sADDsdasdgdsf" REST_API="https://dnsapi.cn" @@ -109,7 +107,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -123,7 +121,7 @@ _get_root() { _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _debug _sub_domain "$_sub_domain" _domain="$h" _debug _domain "$_domain" diff --git a/dnsapi/dns_dpi.sh b/dnsapi/dns_dpi.sh index e8b9b5a5..2955effd 100755 --- a/dnsapi/dns_dpi.sh +++ b/dnsapi/dns_dpi.sh @@ -1,12 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dpi_info='DNSPod.com -Site: DNSPod.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dpi -Options: - DPI_Id Id - DPI_Key Key -' + +# Dnspod.com Domain api +# +#DPI_Id="1234" +# +#DPI_Key="sADDsdasdgdsf" REST_API="https://api.dnspod.com" @@ -109,7 +107,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -123,7 +121,7 @@ _get_root() { _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _debug _sub_domain "$_sub_domain" _domain="$h" _debug _domain "$_domain" diff --git a/dnsapi/dns_dreamhost.sh b/dnsapi/dns_dreamhost.sh index ce4fff87..a4017938 100644 --- a/dnsapi/dns_dreamhost.sh +++ b/dnsapi/dns_dreamhost.sh @@ -1,14 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dreamhost_info='DreamHost.com -Site: DreamHost.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dreamhost -Options: - DH_API_KEY API Key -Issues: github.com/RhinoLance/acme.sh -Author: RhinoLance -' +#Author: RhinoLance +#Report Bugs here: https://github.com/RhinoLance/acme.sh +# + +#define the api endpoint DH_API_ENDPOINT="https://api.dreamhost.com/" querystring="" diff --git a/dnsapi/dns_duckdns.sh b/dnsapi/dns_duckdns.sh index 33d401b0..d6e1dbdc 100755 --- a/dnsapi/dns_duckdns.sh +++ b/dnsapi/dns_duckdns.sh @@ -1,12 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_duckdns_info='DuckDNS.org -Site: www.DuckDNS.org -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_duckdns -Options: - DuckDNS_Token API Token -Author: @RaidenII -' + +#Created by RaidenII, to use DuckDNS's API to add/remove text records +#06/27/2017 + +# Pass credentials before "acme.sh --issue --dns dns_duckdns ..." +# -- +# export DuckDNS_Token="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee" +# -- +# +# Due to the fact that DuckDNS uses StartSSL as cert provider, --insecure may need to be used with acme.sh DuckDNS_API="https://www.duckdns.org/update" diff --git a/dnsapi/dns_durabledns.sh b/dnsapi/dns_durabledns.sh index d71f0ccb..677ae24d 100644 --- a/dnsapi/dns_durabledns.sh +++ b/dnsapi/dns_durabledns.sh @@ -1,13 +1,7 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_durabledns_info='DurableDNS.com -Site: DurableDNS.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_durabledns -Options: - DD_API_User API User - DD_API_Key API Key -Issues: github.com/acmesh-official/acme.sh/issues/2281 -' + +#DD_API_User="xxxxx" +#DD_API_Key="xxxxxx" _DD_BASE="https://durabledns.com/services/dns" @@ -110,7 +104,7 @@ _get_root() { i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -118,7 +112,7 @@ _get_root() { fi if _contains "$response" ">$h."; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_dyn.sh b/dnsapi/dns_dyn.sh index 9b1a97a2..024e0a38 100644 --- a/dnsapi/dns_dyn.sh +++ b/dnsapi/dns_dyn.sh @@ -1,16 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dyn_info='Dyn.com -Domains: dynect.net -Site: Dyn.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dyn -Options: - DYN_Customer Customer - DYN_Username API Username - DYN_Password Secret -Author: Gerd Naschenweng <@magicdude4eva> -' - +# +# Dyn.com Domain API +# +# Author: Gerd Naschenweng +# https://github.com/magicdude4eva +# # Dyn Managed DNS API # https://help.dyn.com/dns-api-knowledge-base/ # @@ -26,6 +20,13 @@ Author: Gerd Naschenweng <@magicdude4eva> # ZoneRemoveNode # ZonePublish # -- +# +# Pass credentials before "acme.sh --issue --dns dns_dyn ..." +# -- +# export DYN_Customer="customer" +# export DYN_Username="apiuser" +# export DYN_Password="secret" +# -- DYN_API="https://api.dynect.net/REST" diff --git a/dnsapi/dns_dynu.sh b/dnsapi/dns_dynu.sh index 3ac5c2f1..406ef17d 100644 --- a/dnsapi/dns_dynu.sh +++ b/dnsapi/dns_dynu.sh @@ -1,21 +1,20 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dynu_info='Dynu.com -Site: Dynu.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_dynu -Options: - Dynu_ClientId Client ID - Dynu_Secret Secret -Issues: github.com/shar0119/acme.sh -Author: Dynu Systems Inc -' +#Client ID +#Dynu_ClientId="0b71cae7-a099-4f6b-8ddf-94571cdb760d" +# +#Secret +#Dynu_Secret="aCUEY4BDCV45KI8CSIC3sp2LKQ9" +# #Token Dynu_Token="" # #Endpoint Dynu_EndPoint="https://api.dynu.com/v2" - +# +#Author: Dynu Systems, Inc. +#Report Bugs here: https://github.com/shar0119/acme.sh +# ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -23,8 +22,6 @@ dns_dynu_add() { fulldomain=$1 txtvalue=$2 - Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}" - Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}" if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then Dynu_ClientId="" Dynu_Secret="" @@ -34,8 +31,8 @@ dns_dynu_add() { fi #save the client id and secret to the account conf file. - _saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId" - _saveaccountconf_mutable Dynu_Secret "$Dynu_Secret" + _saveaccountconf Dynu_ClientId "$Dynu_ClientId" + _saveaccountconf Dynu_Secret "$Dynu_Secret" if [ -z "$Dynu_Token" ]; then _info "Getting Dynu token." @@ -71,8 +68,6 @@ dns_dynu_rm() { fulldomain=$1 txtvalue=$2 - Dynu_ClientId="${Dynu_ClientId:-$(_readaccountconf_mutable Dynu_ClientId)}" - Dynu_Secret="${Dynu_Secret:-$(_readaccountconf_mutable Dynu_Secret)}" if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then Dynu_ClientId="" Dynu_Secret="" @@ -82,8 +77,8 @@ dns_dynu_rm() { fi #save the client id and secret to the account conf file. - _saveaccountconf_mutable Dynu_ClientId "$Dynu_ClientId" - _saveaccountconf_mutable Dynu_Secret "$Dynu_Secret" + _saveaccountconf Dynu_ClientId "$Dynu_ClientId" + _saveaccountconf Dynu_Secret "$Dynu_Secret" if [ -z "$Dynu_Token" ]; then _info "Getting Dynu token." @@ -130,7 +125,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -144,7 +139,7 @@ _get_root() { if _contains "$response" "\"domainName\":\"$h\"" >/dev/null; then dnsId=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 2 | cut -d : -f 2) _domain_name=$h - _node=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _node=$(printf "%s" "$domain" | cut -d . -f 1-$p) return 0 fi p=$i @@ -218,11 +213,11 @@ _dynu_authentication() { response="$(_get "$Dynu_EndPoint/oauth2/token")" if [ "$?" != "0" ]; then - _err "Authentication failed: no response from $Dynu_EndPoint/oauth2/token" + _err "Authentication failed." return 1 fi if _contains "$response" "Authentication Exception"; then - _err "Authentication failed. Server response: $response" + _err "Authentication failed." return 1 fi if _contains "$response" "access_token"; then diff --git a/dnsapi/dns_dynv6.sh b/dnsapi/dns_dynv6.sh index 3e7ce8d6..90814b1b 100644 --- a/dnsapi/dns_dynv6.sh +++ b/dnsapi/dns_dynv6.sh @@ -1,23 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_dynv6_info='DynV6.com -Site: DynV6.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_dynv6 -Options: - DYNV6_TOKEN REST API token. Get from https://DynV6.com/keys -OptionsAlt: - KEY Path to SSH private key file. E.g. "/root/.ssh/dynv6" -Issues: github.com/acmesh-official/acme.sh/issues/2702 -Author: @StefanAbl -' +#Author StefanAbl +#Usage specify a private keyfile to use with dynv6 'export KEY="path/to/keyfile"' +#or use the HTTP REST API by by specifying a token 'export DYNV6_TOKEN="value" +#if no keyfile is specified, you will be asked if you want to create one in /home/$USER/.ssh/dynv6 and /home/$USER/.ssh/dynv6.pub dynv6_api="https://dynv6.com/api/v2" ######## Public functions ##################### # Please Read this guide first: https://github.com/Neilpang/acme.sh/wiki/DNS-API-Dev-Guide #Usage: dns_dynv6_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dynv6_add() { - fulldomain="$(echo "$1" | _lower_case)" - txtvalue="$2" + fulldomain=$1 + txtvalue=$2 _info "Using dynv6 api" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" @@ -43,14 +36,15 @@ dns_dynv6_add() { _err "Something went wrong! it does not seem like the record was added successfully" return 1 fi + return 1 fi - + return 1 } #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_dynv6_rm() { - fulldomain="$(echo "$1" | _lower_case)" - txtvalue="$2" + fulldomain=$1 + txtvalue=$2 _info "Using dynv6 API" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" @@ -107,7 +101,7 @@ _get_domain() { return 0 fi done - _err "Either there is no such host on your dynv6 account, or it cannot be accessed with this key" + _err "Either their is no such host on your dnyv6 account or it cannot be accessed with this key" return 1 } @@ -179,8 +173,8 @@ _dns_dynv6_rm_http() { fi } -#Usage: _get_zone_id $record #get the zoneid for a specifc record or zone +#usage: _get_zone_id §record #where $record is the record to get the id for #returns _zone_id the id of the zone _get_zone_id() { @@ -189,6 +183,7 @@ _get_zone_id() { _dynv6_rest GET zones zones="$(echo "$response" | tr '}' '\n' | tr ',' '\n' | grep name | sed 's/\[//g' | tr -d '{' | tr -d '"')" + #echo $zones selected="" for z in $zones; do @@ -204,7 +199,7 @@ _get_zone_id() { return 1 fi - zone_id="$(echo "$response" | tr '}' '\n' | grep "$selected" | tr ',' '\n' | grep '"id":' | tr -d '"')" + zone_id="$(echo "$response" | tr '}' '\n' | grep "$selected" | tr ',' '\n' | grep id | tr -d '"')" _zone_id="${zone_id#id:}" _debug "zone id: $_zone_id" } @@ -216,9 +211,9 @@ _get_zone_name() { _zone_name="${_zone_name#name:}" } -#usage _get_record_id $zone_id $record -# where zone_id is the value returned by _get_zone_id -# and record is in the form _acme.www for an fqdn of _acme.www.example.com +#usaage _get_record_id $zone_id $record +# where zone_id is thevalue returned by _get_zone_id +# and record ist in the form _acme.www for an fqdn of _acme.www.example.com # returns _record_id _get_record_id() { _zone_id="$1" @@ -233,7 +228,8 @@ _get_record_id() { _get_record_id_from_response() { response="$1" - _record_id="$(echo "$response" | tr '}' '\n' | grep "\"name\":\"$record\"" | grep "\"data\":\"$value\"" | tr ',' '\n' | grep '"id":' | tr -d '"' | tr -d 'id:' | tr -d '{')" + _record_id="$(echo "$response" | tr '}' '\n' | grep "\"name\":\"$record\"" | grep "\"data\":\"$value\"" | tr ',' '\n' | grep id | tr -d '"' | tr -d 'id:')" + #_record_id="${_record_id#id:}" if [ -z "$_record_id" ]; then _err "no such record: $record found in zone $_zone_id" return 1 diff --git a/dnsapi/dns_easydns.sh b/dnsapi/dns_easydns.sh index 423def2b..ab47a0bc 100644 --- a/dnsapi/dns_easydns.sh +++ b/dnsapi/dns_easydns.sh @@ -1,17 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_easydns_info='easyDNS.net -Site: easyDNS.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_easydns -Options: - EASYDNS_Token API Token - EASYDNS_Key API Key -Issues: github.com/acmesh-official/acme.sh/issues/2647 -Author: @Neilpang, wurzelpanzer -' +####################################################### +# +# easyDNS REST API for acme.sh by Neilpang based on dns_cf.sh +# # API Documentation: https://sandbox.rest.easydns.net:3001/ - +# +# Author: wurzelpanzer [wurzelpanzer@maximolider.net] +# Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/2647 +# #################### Public functions ################# #EASYDNS_Key="xxxxxxxxxxxxxxxxxxxxxxxx" @@ -121,7 +118,7 @@ _get_root() { i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -133,7 +130,7 @@ _get_root() { fi if _contains "$response" "\"status\":200"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_edgecenter.sh b/dnsapi/dns_edgecenter.sh deleted file mode 100644 index 8f4ad171..00000000 --- a/dnsapi/dns_edgecenter.sh +++ /dev/null @@ -1,163 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_edgecenter_info='EdgeCenter.ru -Site: EdgeCenter.ru -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgecenter -Options: - EDGECENTER_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/6313 -Author: Konstantin Ruchev -' - -EDGECENTER_API="https://api.edgecenter.ru" -DOMAIN_TYPE= -DOMAIN_MASTER= - -######## Public functions ##################### - -#Usage: dns_edgecenter_add _acme-challenge.www.domain.com "TXT_RECORD_VALUE" -dns_edgecenter_add() { - fulldomain="$1" - txtvalue="$2" - - _info "Using EdgeCenter DNS API" - - if ! _dns_edgecenter_init_check; then - return 1 - fi - - _debug "Detecting root zone for $fulldomain" - if ! _get_root "$fulldomain"; then - return 1 - fi - - subdomain="${fulldomain%."$_zone"}" - subdomain=${subdomain%.} - - _debug "Zone: $_zone" - _debug "Subdomain: $subdomain" - _debug "TXT value: $txtvalue" - - payload='{"resource_records": [ { "content": ["'"$txtvalue"'"] } ], "ttl": 60 }' - _dns_edgecenter_http_api_call "post" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$payload" - - if _contains "$response" '"error":"rrset is already exists"'; then - _debug "RRSet exists, merging values" - _dns_edgecenter_http_api_call "get" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" - current="$response" - newlist="" - for v in $(echo "$current" | sed -n 's/.*"content":\["\([^"]*\)"\].*/\1/p'); do - newlist="$newlist {\"content\":[\"$v\"]}," - done - newlist="$newlist{\"content\":[\"$txtvalue\"]}" - putdata="{\"resource_records\":[${newlist}]} -" - _dns_edgecenter_http_api_call "put" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" "$putdata" - _info "Updated existing RRSet with new TXT value." - return 0 - fi - - if _contains "$response" '"exception":'; then - _err "Record cannot be added." - return 1 - fi - - _info "TXT record added successfully." - return 0 -} - -#Usage: dns_edgecenter_rm _acme-challenge.www.domain.com "TXT_RECORD_VALUE" -dns_edgecenter_rm() { - fulldomain="$1" - txtvalue="$2" - - _info "Removing TXT record for $fulldomain" - - if ! _dns_edgecenter_init_check; then - return 1 - fi - - if ! _get_root "$fulldomain"; then - return 1 - fi - - subdomain="${fulldomain%."$_zone"}" - subdomain=${subdomain%.} - - _dns_edgecenter_http_api_call "delete" "dns/v2/zones/$_zone/$subdomain.$_zone/txt" - - if [ -z "$response" ]; then - _info "TXT record deleted successfully." - else - _info "TXT record may not have been deleted: $response" - fi - return 0 -} - -#################### Private functions below ################################## - -_dns_edgecenter_init_check() { - EDGECENTER_API_KEY="${EDGECENTER_API_KEY:-$(_readaccountconf_mutable EDGECENTER_API_KEY)}" - if [ -z "$EDGECENTER_API_KEY" ]; then - _err "EDGECENTER_API_KEY was not exported." - return 1 - fi - - _saveaccountconf_mutable EDGECENTER_API_KEY "$EDGECENTER_API_KEY" - export _H1="Authorization: APIKey $EDGECENTER_API_KEY" - - _dns_edgecenter_http_api_call "get" "dns/v2/clients/me/features" - if ! _contains "$response" '"id":'; then - _err "Invalid API key." - return 1 - fi - return 0 -} - -_get_root() { - domain="$1" - i=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-) - if [ -z "$h" ]; then - return 1 - fi - _dns_edgecenter_http_api_call "get" "dns/v2/zones/$h" - if ! _contains "$response" 'zone is not found'; then - _zone="$h" - return 0 - fi - i=$((i + 1)) - done - return 1 -} - -_dns_edgecenter_http_api_call() { - mtd="$1" - endpoint="$2" - data="$3" - - export _H1="Authorization: APIKey $EDGECENTER_API_KEY" - - case "$mtd" in - get) - response="$(_get "$EDGECENTER_API/$endpoint")" - ;; - post) - response="$(_post "$data" "$EDGECENTER_API/$endpoint")" - ;; - delete) - response="$(_post "" "$EDGECENTER_API/$endpoint" "" "DELETE")" - ;; - put) - response="$(_post "$data" "$EDGECENTER_API/$endpoint" "" "PUT")" - ;; - *) - _err "Unknown HTTP method $mtd" - return 1 - ;; - esac - - _debug "HTTP $mtd response: $response" - return 0 -} diff --git a/dnsapi/dns_edgedns.sh b/dnsapi/dns_edgedns.sh index 9ff1cc06..27650eb1 100755 --- a/dnsapi/dns_edgedns.sh +++ b/dnsapi/dns_edgedns.sh @@ -1,15 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_edgedns_info='Akamai.com Edge DNS -Site: techdocs.Akamai.com/edge-dns/reference/edge-dns-api -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_edgedns -Options: Specify individual credentials - AKAMAI_HOST Host - AKAMAI_ACCESS_TOKEN Access token - AKAMAI_CLIENT_TOKEN Client token - AKAMAI_CLIENT_SECRET Client secret -Issues: github.com/acmesh-official/acme.sh/issues/3157 -' # Akamai Edge DNS v2 API # User must provide Open Edgegrid API credentials to the EdgeDNS installation. The remote user in EdgeDNS must have CRUD access to @@ -17,10 +6,18 @@ Issues: github.com/acmesh-official/acme.sh/issues/3157 # Report bugs to https://control.akamai.com/apps/support-ui/#/contact-support +# Values to export: +# --EITHER-- # *** TBD. NOT IMPLEMENTED YET *** -# Specify Edgegrid credentials file and section. -# AKAMAI_EDGERC Edge RC. Full file path -# AKAMAI_EDGERC_SECTION Edge RC Section. E.g. "default" +# specify Edgegrid credentials file and section +# AKAMAI_EDGERC= +# AKAMAI_EDGERC_SECTION="default" +## --OR-- +# specify indiviual credentials +# export AKAMAI_HOST = +# export AKAMAI_ACCESS_TOKEN = +# export AKAMAI_CLIENT_TOKEN = +# export AKAMAI_CLIENT_SECRET = ACME_EDGEDNS_VERSION="0.1.0" @@ -363,12 +360,17 @@ _edgedns_rest() { _edgedns_eg_timestamp() { _debug "Generating signature Timestamp" - #Akamai accepts a clock skew of +/-30s, so use the system clock directly. - #The previous code fetched the Date header from www.ntp.org, which is not - #a reliable time source (it served a wrong time for hours, issue 3973), - #cost an extra https round-trip for every API request, and combined the - #remote time of day with the LOCAL date, breaking around UTC midnight. - _eg_timestamp="$(date -u "+%Y%m%dT%H:%M:%S+0000")" + _debug3 "Retriving ntp time" + _timeheaders="$(_get "https://www.ntp.org" "onlyheader")" + _debug3 "_timeheaders" "$_timeheaders" + _ntpdate="$(echo "$_timeheaders" | grep -i "Date:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n")" + _debug3 "_ntpdate" "$_ntpdate" + _ntpdate="$(echo "${_ntpdate}" | sed -e 's/^[[:space:]]*//')" + _debug3 "_NTPDATE" "$_ntpdate" + _ntptime="$(echo "${_ntpdate}" | _head_n 1 | cut -d " " -f 5 | tr -d "\r\n")" + _debug3 "_ntptime" "$_ntptime" + _eg_timestamp=$(date -u "+%Y%m%dT") + _eg_timestamp="$(printf "%s%s+0000" "$_eg_timestamp" "$_ntptime")" _debug "_eg_timestamp" "$_eg_timestamp" } diff --git a/dnsapi/dns_efficientip.sh b/dnsapi/dns_efficientip.sh deleted file mode 100755 index a485849a..00000000 --- a/dnsapi/dns_efficientip.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_efficientip_info='efficientip.com -Site: https://efficientip.com/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_efficientip -Options: - EfficientIP_Creds HTTP Basic Authentication credentials. E.g. "username:password" - EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. - EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. - EfficientIP_View Name of the DNS view hosting the zone. Optional. -OptionsAlt: - EfficientIP_Token_Key Alternative API token key, prefered over basic authentication. - EfficientIP_Token_Secret Alternative API token secret, required when using a token key. - EfficientIP_Server EfficientIP SOLIDserver Management IP address or FQDN. - EfficientIP_DNS_Name Name of the DNS smart or server hosting the zone. Optional. - EfficientIP_View Name of the DNS view hosting the zone. Optional. -Issues: github.com/acmesh-official/acme.sh/issues/6325 -Author: EfficientIP-Labs -' - -dns_efficientip_add() { - fulldomain=$1 - txtvalue=$2 - - _info "Using EfficientIP API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if { [ -z "${EfficientIP_Creds}" ] && { [ -z "${EfficientIP_Token_Key}" ] || [ -z "${EfficientIP_Token_Secret}" ]; }; } || [ -z "${EfficientIP_Server}" ]; then - EfficientIP_Creds="" - EfficientIP_Token_Key="" - EfficientIP_Token_Secret="" - EfficientIP_Server="" - _err "You didn't specify any EfficientIP credentials or token or server (EfficientIP_Creds; EfficientIP_Token_Key; EfficientIP_Token_Secret; EfficientIP_Server)." - _err "Please set them via EXPORT EfficientIP_Creds=username:password or EXPORT EfficientIP_server=ip/hostname" - _err "or if you want to use Token instead EXPORT EfficientIP_Token_Key=yourkey" - _err "and EXPORT EfficientIP_Token_Secret=yoursecret" - _err "then try again." - return 1 - fi - - if [ -z "${EfficientIP_DNS_Name}" ]; then - EfficientIP_DNS_Name="" - fi - - EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) - - if [ -z "${EfficientIP_View}" ]; then - EfficientIP_View="" - fi - - EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) - - _saveaccountconf EfficientIP_Creds "${EfficientIP_Creds}" - _saveaccountconf EfficientIP_Token_Key "${EfficientIP_Token_Key}" - _saveaccountconf EfficientIP_Token_Secret "${EfficientIP_Token_Secret}" - _saveaccountconf EfficientIP_Server "${EfficientIP_Server}" - _saveaccountconf EfficientIP_DNS_Name "${EfficientIP_DNS_Name}" - _saveaccountconf EfficientIP_View "${EfficientIP_View}" - - export _H1="Accept-Language:en-US" - baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_add?rr_type=TXT&rr_ttl=300&rr_name=${fulldomain}&rr_value1=${txtvalue}" - - if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then - baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" - fi - - if [ "${EfficientIP_ViewEncoded}" != "" ]; then - baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" - fi - - if [ -z "${EfficientIP_Token_Secret}" ] || [ -z "${EfficientIP_Token_Key}" ]; then - EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) - export _H2="Authorization: Basic ${EfficientIP_CredsEncoded}" - else - TS=$(date +%s) - Sig=$(printf "%b\n$TS\nPOST\n$baseurlnObject" "${EfficientIP_Token_Secret}" | _digest sha3-256 hex) - EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig") - export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" - export _H3="X-SDS-TS: ${TS}" - fi - - result="$(_post "" "${baseurlnObject}" "" "POST")" - - if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "DNS record successfully created" - return 0 - else - _err "Error creating DNS record" - _err "${result}" - return 1 - fi -} - -dns_efficientip_rm() { - fulldomain=$1 - txtvalue=$2 - - _info "Using EfficientIP API" - _debug fulldomain "${fulldomain}" - _debug txtvalue "${txtvalue}" - - EfficientIP_ViewEncoded=$(printf "%b" "${EfficientIP_View}" | _url_encode) - EfficientIP_DNSNameEncoded=$(printf "%b" "${EfficientIP_DNS_Name}" | _url_encode) - EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) - - export _H1="Accept-Language:en-US" - - baseurlnObject="https://${EfficientIP_Server}/rest/dns_rr_delete?rr_type=TXT&rr_name=$fulldomain&rr_value1=$txtvalue" - if [ "${EfficientIP_DNSNameEncoded}" != "" ]; then - baseurlnObject="${baseurlnObject}&dns_name=${EfficientIP_DNSNameEncoded}" - fi - - if [ "${EfficientIP_ViewEncoded}" != "" ]; then - baseurlnObject="${baseurlnObject}&dnsview_name=${EfficientIP_ViewEncoded}" - fi - - if [ -z "$EfficientIP_Token_Secret" ] || [ -z "$EfficientIP_Token_Key" ]; then - EfficientIP_CredsEncoded=$(printf "%b" "${EfficientIP_Creds}" | _base64) - export _H2="Authorization: Basic $EfficientIP_CredsEncoded" - else - TS=$(date +%s) - Sig=$(printf "%b\n$TS\nDELETE\n${baseurlnObject}" "${EfficientIP_Token_Secret}" | _digest sha3-256 hex) - EfficientIP_CredsEncoded=$(printf "%b:%b" "${EfficientIP_Token_Key}" "$Sig") - export _H2="Authorization: SDS ${EfficientIP_CredsEncoded}" - export _H3="X-SDS-TS: $TS" - fi - - result="$(_post "" "${baseurlnObject}" "" "DELETE")" - - if [ "$(echo "${result}" | _egrep_o "ret_oid")" ]; then - _info "DNS Record successfully deleted" - return 0 - else - _err "Error deleting DNS record" - _err "${result}" - return 1 - fi -} diff --git a/dnsapi/dns_eurodns.sh b/dnsapi/dns_eurodns.sh deleted file mode 100644 index 0fac4cb5..00000000 --- a/dnsapi/dns_eurodns.sh +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_eurodns_info='EuroDNS -Site: eurodns.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_eurodns -Options: - EURODNS_APP_ID Application ID - EURODNS_API_KEY API Key - EURODNS_TTL TTL. Default: "600". -Issues: github.com/acmesh-official/acme.sh/issues -Author: Nicolas Santorelli -' - -# -# EuroDNS DNS API -# -# EuroDNS API documentation: -# https://docapi.eurodns.com -# -# Usage: -# export EURODNS_APP_ID="your-app-id" -# export EURODNS_API_KEY="your-api-key" -# acme.sh --issue --dns dns_eurodns -d example.com -d *.example.com -# -# The credentials will be saved in ~/.acme.sh/account.conf -# -# Optional: -# export EURODNS_API_URL="https://rest-api.eurodns.com" # Default API URL -# export EURODNS_TTL=600 # Default TTL (minimum 600 for EuroDNS) -# - -EURODNS_API_DEFAULT="https://rest-api.eurodns.com" -EURODNS_TTL_DEFAULT=600 - -######## Public functions ##################### - -#Usage: dns_eurodns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_eurodns_add() { - fulldomain="$(echo "$1" | _lower_case)" - txtvalue=$2 - - _info "Using EuroDNS DNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - EURODNS_APP_ID="${EURODNS_APP_ID:-$(_readaccountconf_mutable EURODNS_APP_ID)}" - EURODNS_API_KEY="${EURODNS_API_KEY:-$(_readaccountconf_mutable EURODNS_API_KEY)}" - EURODNS_API_URL="${EURODNS_API_URL:-$(_readaccountconf_mutable EURODNS_API_URL)}" - EURODNS_API_URL="${EURODNS_API_URL:-$EURODNS_API_DEFAULT}" - EURODNS_TTL="${EURODNS_TTL:-$(_readaccountconf_mutable EURODNS_TTL)}" - EURODNS_TTL="${EURODNS_TTL:-$EURODNS_TTL_DEFAULT}" - - if [ -z "$EURODNS_APP_ID" ] || [ -z "$EURODNS_API_KEY" ]; then - EURODNS_APP_ID="" - EURODNS_API_KEY="" - _err "You didn't specify EuroDNS App ID and API Key." - _err "Please export EURODNS_APP_ID and EURODNS_API_KEY and try again." - return 1 - fi - - _saveaccountconf_mutable EURODNS_APP_ID "$EURODNS_APP_ID" - _saveaccountconf_mutable EURODNS_API_KEY "$EURODNS_API_KEY" - if [ "$EURODNS_API_URL" != "$EURODNS_API_DEFAULT" ]; then - _saveaccountconf_mutable EURODNS_API_URL "$EURODNS_API_URL" - fi - if [ "$EURODNS_TTL" != "$EURODNS_TTL_DEFAULT" ]; then - _saveaccountconf_mutable EURODNS_TTL "$EURODNS_TTL" - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "Invalid domain" - return 1 - fi - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - - _info "Adding TXT record" - if _eurodns_add_txt_record "$_domain" "$_sub_domain" "$txtvalue"; then - _info "Added TXT record successfully." - return 0 - else - _err "Failed to add TXT record." - return 1 - fi -} - -#Usage: fulldomain txtvalue -dns_eurodns_rm() { - fulldomain="$(echo "$1" | _lower_case)" - txtvalue=$2 - - _info "Using EuroDNS DNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - EURODNS_APP_ID="${EURODNS_APP_ID:-$(_readaccountconf_mutable EURODNS_APP_ID)}" - EURODNS_API_KEY="${EURODNS_API_KEY:-$(_readaccountconf_mutable EURODNS_API_KEY)}" - EURODNS_API_URL="${EURODNS_API_URL:-$(_readaccountconf_mutable EURODNS_API_URL)}" - EURODNS_API_URL="${EURODNS_API_URL:-$EURODNS_API_DEFAULT}" - - if [ -z "$EURODNS_APP_ID" ] || [ -z "$EURODNS_API_KEY" ]; then - EURODNS_APP_ID="" - EURODNS_API_KEY="" - _err "You didn't specify EuroDNS App ID and API Key." - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "Invalid domain" - return 1 - fi - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - - _info "Removing TXT record" - if _eurodns_rm_txt_record "$_domain" "$_sub_domain" "$txtvalue"; then - _info "Removed TXT record successfully." - return 0 - else - _err "Failed to remove TXT record." - return 1 - fi -} - -#################### Private functions below ################################## - -# _sub_domain=_acme-challenge.www -# _domain=domain.com -_get_root() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - return 1 - fi - - _eurodns_rest GET "dns-zones/$h" - if [ "$?" != "0" ]; then - if [ "$_code" = "404" ]; then - _debug "Zone $h not found, continuing..." - else - _err "API error looking up zone $h" - return 1 - fi - p=$i - i=$(_math "$i" + 1) - continue - fi - - if _contains "$response" '"name"'; then - if [ "$i" = "1" ]; then - _sub_domain="@" - else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - fi - _domain=$h - return 0 - fi - - p=$i - i=$(_math "$i" + 1) - done - - return 1 -} - -_eurodns_add_txt_record() { - domain=$1 - subdomain=$2 - txtvalue=$3 - - data='[{"type":"TXT","host":"'"$subdomain"'","rdata":"'"$txtvalue"'","ttl":'"$EURODNS_TTL"'}]' - - _debug "Adding TXT record via API" - if _eurodns_rest POST "dns-zones/$domain/dns-records" "$data"; then - if _contains "$response" "$txtvalue"; then - return 0 - fi - fi - _err "Failed to add TXT record" - return 1 -} - -_eurodns_rm_txt_record() { - domain=$1 - subdomain=$2 - txtvalue=$3 - - _debug "Getting current zone data for $domain" - - if ! _eurodns_rest GET "dns-zones/$domain"; then - _err "Failed to get zone data" - return 1 - fi - - zone_data=$(echo "$response" | _normalizeJson) - _debug2 zone_data "$zone_data" - - # Find the record ID matching our TXT record - record_id=$(echo "$zone_data" | tr '{' '\n' | grep -F '"TXT"' | grep -F "\"$subdomain\"" | grep -F "\"$txtvalue\"" | _egrep_o '"id" *: *[0-9]+' | cut -d : -f 2 | _head_n 1) - _debug record_id "$record_id" - - if [ -z "$record_id" ]; then - _info "TXT record not found or already removed" - return 0 - fi - - _debug "Deleting TXT record $record_id" - if ! _eurodns_rest DELETE "dns-zones/$domain/dns-records/$record_id"; then - _err "Failed to delete TXT record" - return 1 - fi - - return 0 -} - -# Usage: _eurodns_rest METHOD ENDPOINT [DATA] -_eurodns_rest() { - method=$1 - endpoint=$2 - data="$3" - - export _H1="X-APP-ID: $EURODNS_APP_ID" - export _H2="X-API-KEY: $EURODNS_API_KEY" - export _H3="Content-Type: application/json" - - url="$EURODNS_API_URL/$endpoint" - - _debug2 url "$url" - _debug2 method "$method" - _debug2 data "$data" - - : >"$HTTP_HEADER" - - if [ "$method" = "GET" ]; then - response="$(_get "$url")" - else - response="$(_post "$data" "$url" "" "$method")" - fi - - _ret="$?" - unset _H1 _H2 _H3 - _debug2 response "$response" - - _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" - _debug2 _code "$_code" - - if [ "$_ret" != "0" ]; then - _err "Error calling API: $endpoint" - return 1 - fi - - if [ "$_code" != "200" ] && [ "$_code" != "201" ] && [ "$_code" != "204" ]; then - if [ "$_code" != "404" ]; then - _err "API error (HTTP $_code): $response" - fi - return 1 - fi - - return 0 -} diff --git a/dnsapi/dns_euserv.sh b/dnsapi/dns_euserv.sh index 744f6ca6..cfb4b814 100644 --- a/dnsapi/dns_euserv.sh +++ b/dnsapi/dns_euserv.sh @@ -1,14 +1,18 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_euserv_info='EUserv.com -Domains: EUserv.eu -Site: EUserv.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_euserv -Options: - EUSERV_Username Username - EUSERV_Password Password -Author: Michael Brueckner -' + +#This is the euserv.eu api wrapper for acme.sh +# +#Author: Michael Brueckner +#Report Bugs: https://www.github.com/initit/acme.sh or mbr@initit.de + +# +#EUSERV_Username="username" +# +#EUSERV_Password="password" +# +# Dependencies: +# ------------- +# - none - EUSERV_Api="https://api.euserv.net" @@ -151,7 +155,7 @@ _get_root() { response="$_euserv_domain_orders" while true; do - h=$(echo "$domain" | cut -d . -f "$i"-100) + h=$(echo "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -159,7 +163,7 @@ _get_root() { fi if _contains "$response" "$h"; then - _sub_domain=$(echo "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(echo "$domain" | cut -d . -f 1-$p) _domain="$h" if ! _euserv_get_domain_id "$_domain"; then _err "invalid domain" diff --git a/dnsapi/dns_exoscale.sh b/dnsapi/dns_exoscale.sh old mode 100644 new mode 100755 index ddd526a4..ccf05fc5 --- a/dnsapi/dns_exoscale.sh +++ b/dnsapi/dns_exoscale.sh @@ -1,16 +1,8 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_exoscale_info='Exoscale.com -Site: Exoscale.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_exoscale -Options: - EXOSCALE_API_KEY API Key - EXOSCALE_SECRET_KEY API Secret key -' -EXOSCALE_API="https://api-ch-gva-2.exoscale.com/v2" +EXOSCALE_API=https://api.exoscale.com/dns/v1 -######## Public functions ######## +######## Public functions ##################### # Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" # Used to add txt record @@ -18,197 +10,159 @@ dns_exoscale_add() { fulldomain=$1 txtvalue=$2 - _debug "Using Exoscale DNS v2 API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _check_auth; then + if ! _checkAuth; then return 1 fi - root_domain_id=$(_get_root_domain_id "$fulldomain") - if [ -z "$root_domain_id" ]; then - _err "Unable to determine root domain ID for $fulldomain" + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" return 1 fi - _debug root_domain_id "$root_domain_id" - # Always get the subdomain part first - sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id") - _debug sub_domain "$sub_domain" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" - # Build the record name properly - if [ -z "$sub_domain" ]; then - record_name="_acme-challenge" - else - record_name="_acme-challenge.$sub_domain" + _info "Adding record" + if _exoscale_rest POST "domains/$_domain_id/records" "{\"record\":{\"name\":\"$_sub_domain\",\"record_type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}}" "$_domain_token"; then + if _contains "$response" "$txtvalue"; then + _info "Added, OK" + return 0 + fi fi + _err "Add txt record error." + return 1 - payload=$(printf '{"name":"%s","type":"TXT","content":"%s","ttl":120}' "$record_name" "$txtvalue") - _debug payload "$payload" - - response=$(_exoscale_rest POST "/dns-domain/${root_domain_id}/record" "$payload") - if _contains "$response" "\"id\""; then - _info "TXT record added successfully." - return 0 - else - _err "Error adding TXT record: $response" - return 1 - fi } +# Usage: fulldomain txtvalue +# Used to remove the txt record after validation dns_exoscale_rm() { fulldomain=$1 + txtvalue=$2 - _debug "Using Exoscale DNS v2 API for removal" - _debug fulldomain "$fulldomain" - - if ! _check_auth; then + if ! _checkAuth; then return 1 fi - root_domain_id=$(_get_root_domain_id "$fulldomain") - if [ -z "$root_domain_id" ]; then - _err "Unable to determine root domain ID for $fulldomain" + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" return 1 fi - record_name="_acme-challenge" - sub_domain=$(_get_sub_domain "$fulldomain" "$root_domain_id") - if [ -n "$sub_domain" ]; then - record_name="_acme-challenge.$sub_domain" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + _exoscale_rest GET "domains/${_domain_id}/records?type=TXT&name=$_sub_domain" "" "$_domain_token" + if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then + _record_id=$(echo "$response" | tr '{' "\n" | grep "\"content\":\"$txtvalue\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") fi - record_id=$(_find_record_id "$root_domain_id" "$record_name") - if [ -z "$record_id" ]; then - _err "TXT record not found for deletion." + if [ -z "$_record_id" ]; then + _err "Can not get record id to remove." return 1 fi - response=$(_exoscale_rest DELETE "/dns-domain/$root_domain_id/record/$record_id") - if _contains "$response" "\"state\":\"success\""; then - _info "TXT record deleted successfully." - return 0 - else - _err "Error deleting TXT record: $response" + _debug "Deleting record $_record_id" + + if ! _exoscale_rest DELETE "domains/$_domain_id/records/$_record_id" "" "$_domain_token"; then + _err "Delete record error." return 1 fi -} -######## Private helpers ######## - -_check_auth() { - EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}" - EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}" - if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then - _err "EXOSCALE_API_KEY and EXOSCALE_SECRET_KEY must be set." - return 1 - fi - _saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY" - _saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY" return 0 } -_get_root_domain_id() { +#################### Private functions below ################################## + +_checkAuth() { + EXOSCALE_API_KEY="${EXOSCALE_API_KEY:-$(_readaccountconf_mutable EXOSCALE_API_KEY)}" + EXOSCALE_SECRET_KEY="${EXOSCALE_SECRET_KEY:-$(_readaccountconf_mutable EXOSCALE_SECRET_KEY)}" + + if [ -z "$EXOSCALE_API_KEY" ] || [ -z "$EXOSCALE_SECRET_KEY" ]; then + EXOSCALE_API_KEY="" + EXOSCALE_SECRET_KEY="" + _err "You don't specify Exoscale application key and application secret yet." + _err "Please create you key and try again." + return 1 + fi + + _saveaccountconf_mutable EXOSCALE_API_KEY "$EXOSCALE_API_KEY" + _saveaccountconf_mutable EXOSCALE_SECRET_KEY "$EXOSCALE_SECRET_KEY" + + return 0 +} + +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +# _domain_token=sdjkglgdfewsdfg +_get_root() { + + if ! _exoscale_rest GET "domains"; then + return 1 + fi + domain=$1 - i=1 + i=2 + p=1 while true; do - candidate=$(printf "%s" "$domain" | cut -d . -f "${i}-100") - [ -z "$candidate" ] && return 1 - _debug "Trying root domain candidate: $candidate" - domains=$(_exoscale_rest GET "/dns-domain") - # Extract from dns-domains array - result=$(echo "$domains" | _egrep_o '"dns-domains":\[.*\]' | _egrep_o '\{"id":"[^"]*","created-at":"[^"]*","unicode-name":"[^"]*"\}' | while read -r item; do - name=$(echo "$item" | _egrep_o '"unicode-name":"[^"]*"' | cut -d'"' -f4) - id=$(echo "$item" | _egrep_o '"id":"[^"]*"' | cut -d'"' -f4) - if [ "$name" = "$candidate" ]; then - echo "$id" - break - fi - done) - if [ -n "$result" ]; then - echo "$result" - return 0 + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 fi + + if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"id\":[^,]+" | _head_n 1 | cut -d : -f 2 | tr -d \") + _domain_token=$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o "\"token\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") + if [ "$_domain_token" ] && [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i i=$(_math "$i" + 1) done + return 1 } -_get_sub_domain() { - fulldomain=$1 - root_id=$2 - root_info=$(_exoscale_rest GET "/dns-domain/$root_id") - _debug root_info "$root_info" - root_name=$(echo "$root_info" | _egrep_o "\"unicode-name\":\"[^\"]*\"" | cut -d\" -f4) - sub=${fulldomain%%."$root_name"} - - if [ "$sub" = "_acme-challenge" ]; then - echo "" - else - # Remove _acme-challenge. prefix to get the actual subdomain - echo "${sub#_acme-challenge.}" - fi -} - -_find_record_id() { - root_id=$1 - name=$2 - records=$(_exoscale_rest GET "/dns-domain/$root_id/record") - - # Convert search name to lowercase for case-insensitive matching - name_lower=$(echo "$name" | tr '[:upper:]' '[:lower:]') - - echo "$records" | _egrep_o '\{[^}]*"name":"[^"]*"[^}]*\}' | while read -r record; do - record_name=$(echo "$record" | _egrep_o '"name":"[^"]*"' | cut -d'"' -f4) - record_name_lower=$(echo "$record_name" | tr '[:upper:]' '[:lower:]') - if [ "$record_name_lower" = "$name_lower" ]; then - echo "$record" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d'"' -f4 - break - fi - done -} - -_exoscale_sign() { - k=$1 - shift - hex_key=$(printf %b "$k" | _hex_dump | tr -d ' ') - printf %s "$@" | _hmac sha256 "$hex_key" -} - +# returns response _exoscale_rest() { method=$1 - path=$2 - data=$3 - - url="${EXOSCALE_API}${path}" - expiration=$(_math "$(date +%s)" + 300) # 5m from now - - # Build the message with the actual body or empty line - message=$(printf "%s %s\n%s\n\n\n%s" "$method" "/v2$path" "$data" "$expiration") - signature=$(_exoscale_sign "$EXOSCALE_SECRET_KEY" "$message" | _base64) - auth="EXO2-HMAC-SHA256 credential=${EXOSCALE_API_KEY},expires=${expiration},signature=${signature}" - - _debug "API request: $method $url" - _debug "Signed message: [$message]" - _debug "Authorization header: [$auth]" + path="$2" + data="$3" + token="$4" + request_url="$EXOSCALE_API/$path" + _debug "$path" export _H1="Accept: application/json" - export _H2="Authorization: ${auth}" + + if [ "$token" ]; then + export _H2="X-DNS-Domain-Token: $token" + else + export _H2="X-DNS-Token: $EXOSCALE_API_KEY:$EXOSCALE_SECRET_KEY" + fi if [ "$data" ] || [ "$method" = "DELETE" ]; then export _H3="Content-Type: application/json" _debug data "$data" - response="$(_post "$data" "$url" "" "$method")" + response="$(_post "$data" "$request_url" "" "$method")" else - response="$(_get "$url" "" "" "$method")" + response="$(_get "$request_url" "" "" "$method")" fi - # shellcheck disable=SC2181 - if [ "$?" -ne 0 ]; then - _err "error $url" + if [ "$?" != "0" ]; then + _err "error $request_url" return 1 fi _debug2 response "$response" - echo "$response" return 0 } diff --git a/dnsapi/dns_firestorm.sh b/dnsapi/dns_firestorm.sh deleted file mode 100644 index 808c2b89..00000000 --- a/dnsapi/dns_firestorm.sh +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_firestorm_info='Firestorm.ch -Site: firestorm.ch -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_firestorm -Options: - FST_Key Customer ID - FST_Secret API Secret - FST_Url API URL. Optional. Default "https://api.firestorm.ch/acme-dns". -Issues: github.com/acmesh-official/acme.sh/issues/6839 -Author: FireStorm GmbH -' - -FST_Url_DEFAULT="https://api.firestorm.ch/acme-dns" - -######## Public functions ##################### - -# Usage: dns_firestorm_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_firestorm_add() { - fulldomain=$1 - txtvalue=$2 - - FST_Key="${FST_Key:-$(_readaccountconf_mutable FST_Key)}" - FST_Secret="${FST_Secret:-$(_readaccountconf_mutable FST_Secret)}" - FST_Url="${FST_Url:-$(_readaccountconf_mutable FST_Url)}" - - if [ -z "$FST_Key" ] || [ -z "$FST_Secret" ]; then - _err "FST_Key and FST_Secret must be set" - _err "Get your API credentials at https://admin.firestorm.ch" - return 1 - fi - - FST_Url="${FST_Url:-$FST_Url_DEFAULT}" - - _saveaccountconf_mutable FST_Key "$FST_Key" - _saveaccountconf_mutable FST_Secret "$FST_Secret" - if [ "$FST_Url" != "$FST_Url_DEFAULT" ]; then - _saveaccountconf_mutable FST_Url "$FST_Url" - else - _clearaccountconf_mutable FST_Url - fi - - subdomain=$(printf "%s" "$fulldomain" | sed 's/^_acme-challenge\.//') - - _info "Adding TXT record for $fulldomain" - _debug "Subdomain" "$subdomain" - _debug "TXT value" "$txtvalue" - - body="{\"subdomain\":\"$(_json_safe "$subdomain")\",\"txt\":\"$(_json_safe "$txtvalue")\"}" - - response="$(_firestorm_api "update" "$body")" - - if _contains "$response" "$txtvalue"; then - _info "TXT record added successfully" - return 0 - fi - - _err "Failed to add TXT record: $response" - return 1 -} - -# Usage: dns_firestorm_rm _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_firestorm_rm() { - fulldomain=$1 - txtvalue=$2 - - FST_Key="${FST_Key:-$(_readaccountconf_mutable FST_Key)}" - FST_Secret="${FST_Secret:-$(_readaccountconf_mutable FST_Secret)}" - FST_Url="${FST_Url:-$(_readaccountconf_mutable FST_Url)}" - FST_Url="${FST_Url:-$FST_Url_DEFAULT}" - - if [ -z "$FST_Key" ] || [ -z "$FST_Secret" ]; then - _err "FST_Key and FST_Secret must be set" - return 1 - fi - - subdomain=$(printf "%s" "$fulldomain" | sed 's/^_acme-challenge\.//') - - _info "Removing TXT record for $fulldomain" - - body="{\"subdomain\":\"$(_json_safe "$subdomain")\",\"txt\":\"$(_json_safe "$txtvalue")\"}" - - response="$(_firestorm_api "remove" "$body")" - - if _contains "$response" "removed"; then - _info "TXT record removed" - return 0 - fi - - _err "Failed to remove TXT record: $response" - return 1 -} - -#################### Private functions below ################################## - -# Escape special characters for safe JSON string interpolation -_json_safe() { - printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g' -} - -_firestorm_api() { - action=$1 - data=$2 - - export _H1="X-Api-User: $FST_Key" - export _H2="X-Api-Key: $FST_Secret" - export _H3="Content-Type: application/json" - - _post "$data" "$FST_Url/$action" "" "POST" -} diff --git a/dnsapi/dns_fornex.sh b/dnsapi/dns_fornex.sh index dcaa2297..53be307a 100644 --- a/dnsapi/dns_fornex.sh +++ b/dnsapi/dns_fornex.sh @@ -1,15 +1,8 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_fornex_info='Fornex.com -Site: Fornex.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_fornex -Options: - FORNEX_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/3998 -Author: Timur Umarov -' -FORNEX_API_URL="https://fornex.com/api" +#Author: Timur Umarov + +FORNEX_API_URL="https://fornex.com/api/dns/v0.1" ######## Public functions ##################### @@ -30,10 +23,12 @@ dns_fornex_add() { fi _info "Adding record" - if _rest POST "dns/domain/$_domain/entry_set/" "{\"host\" : \"${fulldomain}\" , \"type\" : \"TXT\" , \"value\" : \"${txtvalue}\" , \"ttl\" : null}"; then + if _rest POST "$_domain/entry_set/add/" "host=$fulldomain&type=TXT&value=$txtvalue&apikey=$FORNEX_API_KEY"; then _debug _response "$response" - _info "Added, OK" - return 0 + if _contains "$response" '"ok": true' || _contains "$response" 'Такая запись уже существует.'; then + _info "Added, OK" + return 0 + fi fi _err "Add txt record error." return 1 @@ -56,21 +51,21 @@ dns_fornex_rm() { fi _debug "Getting txt records" - _rest GET "dns/domain/$_domain/entry_set?type=TXT&q=$fulldomain" + _rest GET "$_domain/entry_set.json?apikey=$FORNEX_API_KEY" if ! _contains "$response" "$txtvalue"; then _err "Txt record not found" return 1 fi - _record_id="$(echo "$response" | _egrep_o "\{[^\{]*\"value\"*:*\"$txtvalue\"[^\}]*\}" | sed -n -e 's#.*"id":\([0-9]*\).*#\1#p')" + _record_id="$(echo "$response" | _egrep_o "{[^{]*\"value\"*:*\"$txtvalue\"[^}]*}" | sed -n -e 's#.*"id": \([0-9]*\).*#\1#p')" _debug "_record_id" "$_record_id" if [ -z "$_record_id" ]; then _err "can not find _record_id" return 1 fi - if ! _rest DELETE "dns/domain/$_domain/entry_set/$_record_id/"; then + if ! _rest POST "$_domain/entry_set/$_record_id/delete/" "apikey=$FORNEX_API_KEY"; then _err "Delete record error." return 1 fi @@ -88,18 +83,18 @@ _get_root() { i=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi - if ! _rest GET "dns/domain/?q=$h"; then + if ! _rest GET "domain_list.json?q=$h&apikey=$FORNEX_API_KEY"; then return 1 fi - if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + if _contains "$response" "\"$h\"" >/dev/null; then _domain=$h return 0 else @@ -132,9 +127,7 @@ _rest() { data="$3" _debug "$ep" - export _H1="Authorization: Api-Key $FORNEX_API_KEY" - export _H2="Content-Type: application/json" - export _H3="Accept: application/json" + export _H1="Accept: application/json" if [ "$m" != "GET" ]; then _debug data "$data" diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 8ea86c24..29cee430 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -1,15 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_freedns_info='FreeDNS -Site: FreeDNS.afraid.org -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_freedns -Options: - FREEDNS_User Username - FREEDNS_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/2305 -Author: David Kerr <@dkerr64> -' +#This file name is "dns_freedns.sh" +#So, here must be a method dns_freedns_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: David Kerr +#Report Bugs here: https://github.com/dkerr64/acme.sh +#or here... https://github.com/acmesh-official/acme.sh/issues/2305 +# ######## Public functions ##################### # Export FreeDNS userid and password in following variables... @@ -305,7 +304,7 @@ _freedns_domain_id() { fi domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' | - grep -E "$search_domain|$search_domain\(.*\)" | + grep "$search_domain\|$search_domain(.*)" | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' | cut -d = -f 2)" # The above beauty extracts domain ID from the html page... diff --git a/dnsapi/dns_freemyip.sh b/dnsapi/dns_freemyip.sh deleted file mode 100644 index 18d8e7f9..00000000 --- a/dnsapi/dns_freemyip.sh +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_freemyip_info='FreeMyIP.com -Site: FreeMyIP.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_freemyip -Options: - FREEMYIP_Token API Token -Issues: github.com/acmesh-official/acme.sh/issues/6247 -Author: Recolic Keghart , @Giova96, ExtremeFiretop -' - -FREEMYIP_DNS_API="https://freemyip.com/update?" - -################ Public functions ################ - -#Usage: dns_freemyip_add fulldomain txtvalue -dns_freemyip_add() { - fulldomain="$1" - txtvalue="$2" - - _info "Add TXT record $txtvalue for $fulldomain using freemyip.com api" - - FREEMYIP_Token="${FREEMYIP_Token:-$(_readaccountconf_mutable FREEMYIP_Token)}" - if [ -z "$FREEMYIP_Token" ]; then - FREEMYIP_Token="" - _err "You don't specify FREEMYIP_Token yet." - _err "Please specify your token and try again." - return 1 - fi - - #save the credentials to the account conf file. - _saveaccountconf_mutable FREEMYIP_Token "$FREEMYIP_Token" - - if _is_root_domain_published "$fulldomain"; then - _err "freemyip API don't allow you to set multiple TXT record for the same subdomain!" - _err "You must apply certificate for only one domain at a time!" - _err "====" - _err "For example, aaa.yourdomain.freemyip.com and bbb.yourdomain.freemyip.com and yourdomain.freemyip.com ALWAYS share the same TXT record. They will overwrite each other if you apply multiple domain at the same time." - _debug "If you are testing this workflow in github pipeline or acmetest, please set TEST_DNS_NO_SUBDOMAIN=1 and TEST_DNS_NO_WILDCARD=1" - return 1 - fi - - # txtvalue must be url-encoded. But it's not necessary for acme txt value. - _freemyip_get_until_ok "${FREEMYIP_DNS_API}token=$FREEMYIP_Token&domain=$fulldomain&txt=$txtvalue" 2>&1 - return $? -} - -#Usage: dns_freemyip_rm fulldomain txtvalue -dns_freemyip_rm() { - fulldomain="$1" - txtvalue="$2" - - _info "Delete TXT record $txtvalue for $fulldomain using freemyip.com api" - - FREEMYIP_Token="${FREEMYIP_Token:-$(_readaccountconf_mutable FREEMYIP_Token)}" - if [ -z "$FREEMYIP_Token" ]; then - FREEMYIP_Token="" - _err "You don't specify FREEMYIP_Token yet." - _err "Please specify your token and try again." - return 1 - fi - - #save the credentials to the account conf file. - _saveaccountconf_mutable FREEMYIP_Token "$FREEMYIP_Token" - - # Leave the TXT record as empty or "null" to delete the record. - _freemyip_get_until_ok "${FREEMYIP_DNS_API}token=$FREEMYIP_Token&domain=$fulldomain&txt=" 2>&1 - return $? -} - -################ Private functions below ################ -_get_root() { - _fmi_d="$1" - - echo "$_fmi_d" | sed 's/.*\.\([^.]*\.[^.]*\.[^.]*\)$/\1/' -} - -# There is random failure while calling freemyip API too fast. This function automatically retry until success. -_freemyip_get_until_ok() { - _fmi_url="$1" - _fmi_i=1 - while [ "$_fmi_i" -le 8 ]; do - _debug "HTTP GET freemyip.com API '$_fmi_url', retry $_fmi_i/8..." - _fmi_response="$(_get "$_fmi_url")" - printf '%s\n' "$_fmi_response" >&2 - - if _contains "$_fmi_response" "OK"; then - return 0 - fi - - _sleep 1 # DO NOT send the request too fast - _fmi_i=$((_fmi_i + 1)) - done - _err "Failed to request freemyip API. Server does not say 'OK'" - return 1 -} - -# Verify in public dns if domain is already there. -_is_root_domain_published() { - _fmi_d="$1" - _webroot="$(_get_root "$_fmi_d")" - - _info "Verifying '""$_fmi_d""' freemyip webroot (""$_webroot"") is not published yet" - _fmi_i=1 - while [ "$_fmi_i" -le 3 ]; do - _debug "'$_webroot' ns lookup, retry $_fmi_i/3..." - - if [ "$(_ns_lookup "$_fmi_d" TXT)" ]; then - _debug "'$_webroot' already has a TXT record published!" - return 0 - fi - _sleep 10 # Give it some time to propagate the TXT record - _fmi_i=$((_fmi_i + 1)) - done - return 1 -} diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh index aaef07bf..931da883 100644 --- a/dnsapi/dns_gandi_livedns.sh +++ b/dnsapi/dns_gandi_livedns.sh @@ -1,44 +1,31 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_gandi_livedns_info='Gandi.net LiveDNS -Site: Gandi.net/domain/dns -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gandi_livedns -Options: - GANDI_LIVEDNS_KEY API Key -Issues: github.com/fcrozat/acme.sh -Author: Frédéric Crozat , Dominik Röttsches -' # Gandi LiveDNS v5 API -# https://api.gandi.net/docs/livedns/ -# https://api.gandi.net/docs/authentication/ for token + apikey (deprecated) authentication +# https://doc.livedns.gandi.net/ # currently under beta - +# +# Requires GANDI API KEY set in GANDI_LIVEDNS_KEY set as environment variable +# +#Author: Frédéric Crozat +# Dominik Röttsches +#Report Bugs here: https://github.com/fcrozat/acme.sh +# ######## Public functions ##################### -GANDI_LIVEDNS_API="https://api.gandi.net/v5/livedns" +GANDI_LIVEDNS_API="https://dns.api.gandi.net/api/v5" #Usage: dns_gandi_livedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_gandi_livedns_add() { fulldomain=$1 txtvalue=$2 - GANDI_LIVEDNS_KEY="${GANDI_LIVEDNS_KEY:-$(_readaccountconf_mutable GANDI_LIVEDNS_KEY)}" - GANDI_LIVEDNS_TOKEN="${GANDI_LIVEDNS_TOKEN:-$(_readaccountconf_mutable GANDI_LIVEDNS_TOKEN)}" - if [ -z "$GANDI_LIVEDNS_KEY" ] && [ -z "$GANDI_LIVEDNS_TOKEN" ]; then - _err "No Token or API key (deprecated) specified for Gandi LiveDNS." - _err "Create your token or key and export it as GANDI_LIVEDNS_KEY or GANDI_LIVEDNS_TOKEN respectively" + if [ -z "$GANDI_LIVEDNS_KEY" ]; then + _err "No API key specified for Gandi LiveDNS." + _err "Create your key and export it as GANDI_LIVEDNS_KEY" return 1 fi - # Keep only one secret in configuration - if [ -n "$GANDI_LIVEDNS_TOKEN" ]; then - _saveaccountconf_mutable GANDI_LIVEDNS_TOKEN "$GANDI_LIVEDNS_TOKEN" - _clearaccountconf_mutable GANDI_LIVEDNS_KEY - elif [ -n "$GANDI_LIVEDNS_KEY" ]; then - _saveaccountconf_mutable GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" - _clearaccountconf_mutable GANDI_LIVEDNS_TOKEN - fi + _saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -83,7 +70,7 @@ dns_gandi_livedns_rm() { _gandi_livedns_rest PUT \ "domains/$_domain/records/$_sub_domain/TXT" \ "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" && - _contains "$response" '{"message":"DNS Record Created"}' && + _contains "$response" '{"message": "DNS Record Created"}' && _info "Removing record $(__green "success")" } @@ -97,7 +84,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -114,7 +101,7 @@ _get_root() { elif _contains "$response" '"code": 404'; then _debug "$h not found" else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi @@ -139,7 +126,7 @@ _dns_gandi_append_record() { _debug new_rrset_values "$_rrset_values" _gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \ "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" && - _contains "$response" '{"message":"DNS Record Created"}' && + _contains "$response" '{"message": "DNS Record Created"}' && _info "Adding record $(__green "success")" } @@ -149,11 +136,11 @@ _dns_gandi_existing_rrset_values() { if ! _gandi_livedns_rest GET "domains/$domain/records/$sub_domain"; then return 1 fi - if ! _contains "$response" '"rrset_type":"TXT"'; then + if ! _contains "$response" '"rrset_type": "TXT"'; then _debug "Does not have a _acme-challenge TXT record yet." return 1 fi - if _contains "$response" '"rrset_values":\[\]'; then + if _contains "$response" '"rrset_values": \[\]'; then _debug "Empty rrset_values for TXT record, no previous TXT record." return 1 fi @@ -170,12 +157,7 @@ _gandi_livedns_rest() { _debug "$ep" export _H1="Content-Type: application/json" - - if [ -n "$GANDI_LIVEDNS_TOKEN" ]; then - export _H2="Authorization: Bearer $GANDI_LIVEDNS_TOKEN" - else - export _H2="Authorization: Apikey $GANDI_LIVEDNS_KEY" - fi + export _H2="X-Api-Key: $GANDI_LIVEDNS_KEY" if [ "$m" = "GET" ]; then response="$(_get "$GANDI_LIVEDNS_API/$ep")" diff --git a/dnsapi/dns_gcloud.sh b/dnsapi/dns_gcloud.sh index a6016abc..2788ad59 100755 --- a/dnsapi/dns_gcloud.sh +++ b/dnsapi/dns_gcloud.sh @@ -1,12 +1,6 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_gcloud_info='Google Cloud DNS -Site: Cloud.Google.com/dns -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcloud -Options: - CLOUDSDK_ACTIVE_CONFIG_NAME Active config name. E.g. "default" -Author: Janos Lenart -' + +# Author: Janos Lenart ######## Public functions ##################### @@ -48,7 +42,7 @@ dns_gcloud_rm() { echo "$rrdatas" | grep -F -v -- "\"$txtvalue\"" | _dns_gcloud_add_rrs || return $? _dns_gcloud_execute_tr || return $? - _info "$fulldomain record removed" + _info "$fulldomain record added" } #################### Private functions below ################################## diff --git a/dnsapi/dns_gcore.sh b/dnsapi/dns_gcore.sh deleted file mode 100755 index fbdba7ee..00000000 --- a/dnsapi/dns_gcore.sh +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_gcore_info='Gcore.com -Site: Gcore.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gcore -Options: - GCORE_Key API Key -Issues: github.com/acmesh-official/acme.sh/issues/4460 -' - -GCORE_Api="https://api.gcore.com/dns/v2" -GCORE_Doc="https://api.gcore.com/docs/dns" - -######## Public functions ##################### - -#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_gcore_add() { - fulldomain=$1 - txtvalue=$2 - - GCORE_Key="${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}" - - if [ -z "$GCORE_Key" ]; then - GCORE_Key="" - _err "You didn't specify a Gcore api key yet." - _err "You can get yours from here $GCORE_Doc" - return 1 - fi - - #save the api key to the account conf file. - _saveaccountconf_mutable GCORE_Key "$GCORE_Key" "base64" - - _debug "First detect the zone name" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - _debug _zone_name "$_zone_name" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting txt records" - _gcore_rest GET "zones/$_zone_name/$fulldomain/TXT" - payload="" - - if echo "$response" | grep "record is not found" >/dev/null; then - _info "Record doesn't exists" - payload="{\"resource_records\":[{\"content\":[\"$txtvalue\"],\"enabled\":true}],\"ttl\":120}" - elif echo "$response" | grep "$txtvalue" >/dev/null; then - _info "Already exists, OK" - return 0 - elif echo "$response" | tr -d " " | grep \"name\":\""$fulldomain"\",\"type\":\"TXT\" >/dev/null; then - _info "Record with mismatch txtvalue, try update it" - payload=$(echo "$response" | tr -d " " | sed 's/"updated_at":[0-9]\+,//g' | sed 's/"meta":{}}]}/"meta":{}},{"content":['\""$txtvalue"\"'],"enabled":true}]}/') - fi - - # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so - # we can not use updating anymore. - # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) - # _debug count "$count" - # if [ "$count" = "0" ]; then - _info "Adding record" - if _gcore_rest PUT "zones/$_zone_name/$fulldomain/TXT" "$payload"; then - if _contains "$response" "$txtvalue"; then - _info "Added, OK" - return 0 - elif _contains "$response" "rrset is already exists"; then - _info "Already exists, OK" - return 0 - else - _err "Add txt record error." - return 1 - fi - fi - _err "Add txt record error." - return 1 -} - -#fulldomain txtvalue -dns_gcore_rm() { - fulldomain=$1 - txtvalue=$2 - - GCORE_Key="${GCORE_Key:-$(_readaccountconf_mutable GCORE_Key)}" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - _debug _zone_name "$_zone_name" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting txt records" - _gcore_rest GET "zones/$_zone_name/$fulldomain/TXT" - - if echo "$response" | grep "record is not found" >/dev/null; then - _info "No such txt recrod" - return 0 - fi - - if ! echo "$response" | tr -d " " | grep \"name\":\""$fulldomain"\",\"type\":\"TXT\" >/dev/null; then - _err "Error: $response" - return 1 - fi - - if ! echo "$response" | tr -d " " | grep \""$txtvalue"\" >/dev/null; then - _info "No such txt recrod" - return 0 - fi - - count="$(echo "$response" | grep -o "content" | wc -l)" - - if [ "$count" = "1" ]; then - if ! _gcore_rest DELETE "zones/$_zone_name/$fulldomain/TXT"; then - _err "Delete record error. $response" - return 1 - fi - return 0 - fi - - payload="$(echo "$response" | tr -d " " | sed 's/"updated_at":[0-9]\+,//g' | sed 's/{"id":[0-9]\+,"content":\["'"$txtvalue"'"\],"enabled":true,"meta":{}}//' | sed 's/\[,/\[/' | sed 's/,,/,/' | sed 's/,\]/\]/')" - if ! _gcore_rest PUT "zones/$_zone_name/$fulldomain/TXT" "$payload"; then - _err "Delete record error. $response" - fi -} - -#################### Private functions below ################################## -#_acme-challenge.sub.domain.com -#returns -# _sub_domain=_acme-challenge.sub or _acme-challenge -# _domain=domain.com -# _zone_name=domain.com or sub.domain.com -_get_root() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - #not valid - return 1 - fi - - if ! _gcore_rest GET "zones/$h"; then - return 1 - fi - - if _contains "$response" "\"name\":\"$h\""; then - _zone_name=$h - if [ "$_zone_name" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 - fi - return 1 - fi - p=$i - i=$(_math "$i" + 1) - done - return 1 -} - -_gcore_rest() { - m=$1 - ep="$2" - data="$3" - _debug "$ep" - - key_trimmed=$(echo "$GCORE_Key" | tr -d '"') - - export _H1="Content-Type: application/json" - export _H2="Authorization: APIKey $key_trimmed" - - if [ "$m" != "GET" ]; then - _debug data "$data" - response="$(_post "$data" "$GCORE_Api/$ep" "" "$m")" - else - response="$(_get "$GCORE_Api/$ep")" - fi - - if [ "$?" != "0" ]; then - _err "error $ep" - return 1 - fi - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_gd.sh b/dnsapi/dns_gd.sh index c92bdfa2..44c3d279 100755 --- a/dnsapi/dns_gd.sh +++ b/dnsapi/dns_gd.sh @@ -1,12 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_gd_info='GoDaddy.com -Site: GoDaddy.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gd -Options: - GD_Key API Key - GD_Secret API Secret -' + +#Godaddy domain api +# Get API key and secret from https://developer.godaddy.com/ +# +# GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# 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" @@ -22,8 +22,8 @@ dns_gd_add() { if [ -z "$GD_Key" ] || [ -z "$GD_Secret" ]; then GD_Key="" GD_Secret="" - _err "You didn't specify godaddy api key and secret yet." - _err "Please create your key and try again." + _err "You don't specify godaddy api key and secret yet." + _err "Please create you key and try again." return 1 fi @@ -46,7 +46,7 @@ dns_gd_add() { fi if _contains "$response" "$txtvalue"; then - _info "This record already exists, skipping" + _info "The record is existing, skip" return 0 fi @@ -69,12 +69,7 @@ dns_gd_add() { return 1 fi - if _contains "$response" "UNKNOWN_DOMAIN"; then - # GoDaddy sometimes returns UNKNOWN_DOMAIN when reading a record back even - # though the PUT above succeeded; skip the local readback check and let - # acme.sh's own DNS propagation check verify the record was published. - _info "GoDaddy API won't allow reading the record back; skipping local verification." - elif ! _contains "$response" "$txtvalue"; then + if ! _contains "$response" "$txtvalue"; then _err "TXT record '${txtvalue}' for '${fulldomain}', value wasn't set!" return 1 fi @@ -150,50 +145,26 @@ dns_gd_rm() { # _domain=domain.com _get_root() { domain=$1 - i=1 - p=0 + i=2 + p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 fi - # The record name is whatever precedes the candidate zone. Do not assume - # _acme-challenge here: with DNS alias mode it can be any name, and the - # record may even sit at the zone apex (name "@"). - if [ "$p" = "0" ]; then - _probe_sub="@" - else - _probe_sub=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - fi - - # Probe with the records endpoint instead of "GET domains/$h": since - # 2024-05 GoDaddy rejects the domain details call for accounts with - # fewer than 10 domains, while record-level calls keep working. - # https://github.com/acmesh-official/acme.sh/issues/4487 - if ! _gd_rest GET "domains/$h/records/TXT/$_probe_sub"; then - return 1 - fi - if _startswith "$response" '\['; then - _sub_domain="$_probe_sub" - _domain="$h" - return 0 - fi - - # Some accounts get UNKNOWN_DOMAIN when reading records of a valid zone - # even though writes succeed (see issue #6517); fall back to the domain - # details call for them. if ! _gd_rest GET "domains/$h"; then return 1 fi - if _contains "$response" '"domainId"'; then - _sub_domain="$_probe_sub" + + if _contains "$response" '"code":"NOT_FOUND"'; then + _debug "$h not found" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi - - _debug "$h not found" p="$i" i=$(_math "$i" + 1) done diff --git a/dnsapi/dns_geoscaling.sh b/dnsapi/dns_geoscaling.sh index 05887c7e..6ccf4daf 100755 --- a/dnsapi/dns_geoscaling.sh +++ b/dnsapi/dns_geoscaling.sh @@ -1,12 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_geoscaling_info='GeoScaling.com -Site: GeoScaling.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_geoscaling -Options: - GEOSCALING_Username Username. This is usually NOT an email address - GEOSCALING_Password Password -' + +######################################################################## +# Geoscaling hook script for acme.sh +# +# Environment variables: +# +# - $GEOSCALING_Username (your Geoscaling username - this is usually NOT an amail address) +# - $GEOSCALING_Password (your Geoscaling password) #-- dns_geoscaling_add() - Add TXT record -------------------------------------- # Usage: dns_geoscaling_add _acme-challenge.subdomain.domain.com "XyZ123..." @@ -202,7 +202,7 @@ find_zone() { # Walk through all possible zone names strip_counter=1 while true; do - attempted_zone=$(echo "${domain}" | cut -d . -f "${strip_counter}"-) + attempted_zone=$(echo "${domain}" | cut -d . -f ${strip_counter}-) # All possible zone names have been tried if [ -z "${attempted_zone}" ]; then diff --git a/dnsapi/dns_glesys.sh b/dnsapi/dns_glesys.sh deleted file mode 100644 index 008abd12..00000000 --- a/dnsapi/dns_glesys.sh +++ /dev/null @@ -1,263 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_glesys_info='Glesys -Site: Glesys.se -Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_glesys -Options: - GLESYS_API_KEY Generated API key. - GLESYS_PROJECT_ID Project ID for the API key (e.g. cl12345). - GLESYS_API API endpoint. Default "https://api.glesys.com/domain". - GLESYS_TTL TXT record TTL. Default 120. -Issues: https://github.com/acmesh-official/acme.sh/issues/7057 -Author: Toni Karppi -' - -GLESYS_API_DEFAULT="https://api.glesys.com/domain" -GLESYS_TTL_DEFAULT="120" - -######## Public functions ##################################################### - -# Usage: -# dns_glesys_add _acme-challenge.www.example.com "txt-value" -dns_glesys_add() { - fulldomain="$1" - txtvalue="$2" - - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - _glesys_init || return 1 - - if ! _glesys_get_root "$fulldomain"; then - _err "Could not find root zone for $fulldomain" - return 1 - fi - - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - - host_value="${_sub_domain:-@}" - _debug _host_value "$host_value" - - data="{\"domainname\":\"$_domain\",\"host\":\"$host_value\",\"type\":\"TXT\",\"data\":\"$txtvalue\",\"ttl\":\"$GLESYS_TTL\"}" - - _debug2 data "$data" - - if ! _glesys_rest POST "/addrecord" "$data"; then - _err "Failed to send HTTP request to add TXT record" - return 1 - fi - - response_code=$( - printf "%s" "$response" | - tr -d '\r\n\t ' | - _egrep_o '"code":"?[0-9]+' | - _egrep_o '[0-9]+$' - ) - - _debug response_code "$response_code" - - if [ "$response_code" != "200" ]; then - _err "GleSYS API responded with an unexpected status when attempting to add TXT record" - _debug2 "API response" "$response" - return 1 - fi - - _info "TXT record added" - - return 0 -} - -# Usage: -# dns_glesys_rm _acme-challenge.www.example.com "txt-value" -dns_glesys_rm() { - fulldomain="$1" - txtvalue="$2" - - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - _glesys_init || return 1 - - if ! _glesys_get_root "$fulldomain"; then - _err "Could not find root zone for $fulldomain" - return 1 - fi - - if ! _glesys_find_record_id "$txtvalue"; then - _info "TXT record not present, skip removal" - return 0 - fi - - _debug _record_id "$_record_id" - - if ! _glesys_rest POST "/deleterecord" "{\"recordid\":$_record_id}"; then - _err "Failed to send HTTP request to remove TXT record" - return 1 - fi - - response_code=$( - printf "%s" "$response" | - tr -d '\r\n\t ' | - _egrep_o '"code":"?[0-9]+' | - _egrep_o '[0-9]+$' - ) - - _debug response_code "$response_code" - - if [ "$response_code" != "200" ]; then - _err "GleSYS API responded with unexpected status when attempting to remove TXT record" - _debug2 "API response" "$response" - return 1 - fi - - _info "TXT record removed" - - return 0 -} - -######## Private functions #################################################### - -_glesys_find_record_id() { - txtvalue="$1" - - _debug txtvalue "$txtvalue" - - if [ -z "$txtvalue" ]; then - return 1 - fi - - _record_id="" - - _debug "Looking for TXT record with value" "$txtvalue" - - if ! _glesys_rest GET "/listrecords?domainname=$_domain"; then - _err "Failed to list DNS records" - return 1 - fi - - records="$( - printf "%s" "$response" | - tr -d '\r\n\t ' | - sed 's/},{/}\ -{/g' - )" - - _debug2 records "$records" - - expected_data="\"data\":\"$txtvalue\"" - - _record_id="$( - printf "%s\n" "$records" | - while IFS= read -r record; do - printf "%s" "$record" | grep -q '"type":"TXT"' || continue - printf "%s" "$record" | grep -Fq "$expected_data" || continue - - printf "%s" "$record" | - grep -E -o '"recordid":"?[0-9]+' | - grep -E -o '[0-9]+$' - - break - done - )" - - _debug _record_id "$_record_id" - - if [ -z "$_record_id" ]; then - return 1 - fi - - return 0 -} - -# Finds: -# _domain example.com -# _sub_domain _acme-challenge.www -_glesys_get_root() { - domain="$1" - i=1 - - while true; do - h="$(printf "%s" "$domain" | cut -d . -f "$i"-100)" - - if [ -z "$h" ]; then - return 1 - fi - - if _glesys_rest GET "/listrecords?domainname=$h"; then - response_code=$( - printf "%s" "$response" | - tr -d '\r\n\t ' | - _egrep_o '"code":"?[0-9]+' | - _egrep_o '[0-9]+$' - ) - - _debug response_code "$response_code" - - if [ "$response_code" = "200" ]; then - cut_len="$((${#domain} - ${#h} - 1))" - _domain="$h" - _sub_domain="$(printf "%s" "$domain" | cut -c "1-$cut_len")" - return 0 - fi - fi - - i="$((i + 1))" - done -} - -_glesys_init() { - [ -z "$GLESYS_API" ] && GLESYS_API="$GLESYS_API_DEFAULT" - [ -z "$GLESYS_TTL" ] && GLESYS_TTL="$GLESYS_TTL_DEFAULT" - - _debug GLESYS_API "$GLESYS_API" - _debug GLESYS_TTL "$GLESYS_TTL" - - GLESYS_API_KEY="${GLESYS_API_KEY:-$(_readaccountconf_mutable GLESYS_API_KEY)}" - GLESYS_PROJECT_ID="${GLESYS_PROJECT_ID:-$(_readaccountconf_mutable GLESYS_PROJECT_ID)}" - - if [ -z "$GLESYS_API_KEY" ] || [ -z "$GLESYS_PROJECT_ID" ]; then - _err "GLESYS_API_KEY and GLESYS_PROJECT_ID must be set for this provider" - return 1 - fi - - _secure_debug GLESYS_API_KEY "$GLESYS_API_KEY" - _secure_debug GLESYS_PROJECT_ID "$GLESYS_PROJECT_ID" - - _glesys_basic_auth="$(printf "%s:%s" "$GLESYS_PROJECT_ID" "$GLESYS_API_KEY" | _base64)" - _secure_debug2 _glesys_basic_auth "$_glesys_basic_auth" - - _saveaccountconf_mutable GLESYS_API_KEY "$GLESYS_API_KEY" - _saveaccountconf_mutable GLESYS_PROJECT_ID "$GLESYS_PROJECT_ID" - - return 0 -} - -_glesys_rest() { - method="$1" - path="$2" - data="$3" - - export _H1="Authorization: Basic $_glesys_basic_auth" - export _H2="Content-Type: application/json" - export _H3="Accept: application/json" - - url="$GLESYS_API$path" - _debug "$method $url" - - if [ "$method" = "GET" ]; then - response="$(_get "$url")" - else - response="$(_post "$data" "$url" "" "$method")" - fi - - ret="$?" - _debug2 response "$response" - _debug ret "$ret" - - if [ "$ret" != "0" ]; then - return 1 - fi - - return 0 -} diff --git a/dnsapi/dns_gname.sh b/dnsapi/dns_gname.sh deleted file mode 100644 index 886b3dc5..00000000 --- a/dnsapi/dns_gname.sh +++ /dev/null @@ -1,303 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_gname_info='GNAME -Site: www.gname.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_gname -Options: - GNAME_APPID Your APPID - GNAME_APPKEY Your APPKEY - GNAME_TTL DNS resolution record TTL value, default 120. -Issues: github.com/acmesh-official/acme.sh/issues/6874 -Author: GNDevProd -' - -GNAME_TLD_Api="https://www.gname.com/request/tlds?lx=all" -GNAME_Api="https://api.gname.com" -GNAME_TLDS_CACHE="" - -######## Public functions ##################### - -#Usage: add _acme-challenge.www.domain.com "T1rxqRBosdIK90xWCG3KLZNf6q_0HG9i01zxXp5CAS3" -dns_gname_add() { - fulldomain=$1 - txtvalue=$(printf "%s" "$2" | _url_encode) - #Compatible with gname API RFC 1738 standard URL encoding - txtvalue=$(printf '%s' "$txtvalue" | sed 's/%20/+/g') - - GNAME_APPID="${GNAME_APPID:-$(_readaccountconf_mutable GNAME_APPID)}" - GNAME_APPKEY="${GNAME_APPKEY:-$(_readaccountconf_mutable GNAME_APPKEY)}" - GNAME_TTL="${GNAME_TTL:-$(_readaccountconf_mutable GNAME_TTL)}" - GNAME_TTL="${GNAME_TTL:-120}" - - if [ -z "$GNAME_APPID" ] || [ -z "$GNAME_APPKEY" ]; then - GNAME_APPID="" - GNAME_APPKEY="" - _err "You have not configured the APPID and APPKEY for the GNAME API." - _err "You can get yours from here https://www.gname.com/domain/api." - return 1 - fi - - _saveaccountconf_mutable GNAME_APPID "$GNAME_APPID" - _saveaccountconf_mutable GNAME_APPKEY "$GNAME_APPKEY" - _saveaccountconf_mutable GNAME_TTL "$GNAME_TTL" - - if ! _extract_domain "$fulldomain"; then - _err "Failed to extract domain. Please check your network or API response." - return 1 - fi - - gntime=$(date +%s) - - #If the hostname is empty, you need to replace it with @. - final_hostname=$(printf "%s" "${ext_hostname:-@}" | _url_encode) - - # Parameters need to be sorted by key - body="appid=$GNAME_APPID&exist=1&gntime=$gntime&jlz=$txtvalue&lang=us&lx=TXT&mx=0&ttl=$GNAME_TTL&xl=0&ym=$ext_domain&zj=$final_hostname" - - _info "Adding TXT record for $ext_domain, host: $final_hostname" - - if _post_to_api "/api/resolution/add" "$body"; then - _info "Successfully added DNS record." - return 0 - else - _err "Failed to add DNS record via Gname API." - return 1 - fi -} - -#Usage: remove _acme-challenge.www.domain.com "T1rxqRBosdIK90xWCG3KLZNf6q_0HG9i01zxXp5CASc" -dns_gname_rm() { - fulldomain=$1 - txtvalue=$2 - - GNAME_APPID="${GNAME_APPID:-$(_readaccountconf_mutable GNAME_APPID)}" - GNAME_APPKEY="${GNAME_APPKEY:-$(_readaccountconf_mutable GNAME_APPKEY)}" - - if [ -z "$GNAME_APPID" ] || [ -z "$GNAME_APPKEY" ]; then - GNAME_APPID="" - GNAME_APPKEY="" - _err "You have not configured the APPID and APPKEY for the GNAME API." - _err "You can get yours from here https://www.gname.com/domain/api." - return 1 - fi - - _saveaccountconf_mutable GNAME_APPID "$GNAME_APPID" - _saveaccountconf_mutable GNAME_APPKEY "$GNAME_APPKEY" - - if ! _extract_domain "$fulldomain"; then - _err "Failed to extract domain. Please check your network or API response." - return 1 - fi - - final_hostname="${ext_hostname:-@}" - - _debug "Query DNS record ID $ext_domain $final_hostname $txtvalue" - - if ! record_id=$(_get_record_id "$ext_domain" "$final_hostname" "$txtvalue"); then - _err "Error occurred during record lookup. Skipping deletion to avoid errors." - return 1 - fi - - if [ -z "$record_id" ]; then - _info "DNS record not found, skip removing." - return 0 - fi - - _debug "DNS record ID:$record_id" - gntime=$(date +%s) - body="appid=$GNAME_APPID&gntime=$gntime&jxid=$record_id&lang=us&ym=$ext_domain" - - if ! _post_to_api "/api/resolution/delete" "$body"; then - _err "DNS record deletion failed" - return 1 - fi - - _info "DNS record deletion successful" - return 0 -} - -# Find the DNS record ID by hostname, record type, and record value. -_get_record_id() { - target_ym="$1" - target_zjt="$2" - target_jxz="$3" - target_lx="TXT" - - GNAME_APPID="${GNAME_APPID:-$(_readaccountconf_mutable GNAME_APPID)}" - GNAME_APPKEY="${GNAME_APPKEY:-$(_readaccountconf_mutable GNAME_APPKEY)}" - gntime=$(date +%s) - body="appid=$GNAME_APPID&gntime=$gntime&limit=1000&lx=$target_lx&page=1&ym=$target_ym" - - if ! _post_to_api "/api/resolution/list" "$body"; then - _err "Query and parsing records failed" - return 1 - fi - - clean_response=$(echo "$post_response" | tr -d '\r') - records=$(echo "$clean_response" | sed 's/.*"data":\[//; s/\],"count".*//; s/},/}\n/g' | grep "^{") - matched_rows=$(echo "$records" | grep -Fi "\"zjt\":\"$target_zjt\"") - - if [ -z "$matched_rows" ]; then - _debug "No records found for host: $target_zjt" - return 0 - fi - - exact_row=$(echo "$matched_rows" | grep -F "\"jxz\":\"$target_jxz\"" | _head_n 1) - dns_record_id="" - if [ -n "$exact_row" ]; then - dns_record_id=$(echo "$exact_row" | _egrep_o "\"id\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '"') - fi - - if [ -n "$dns_record_id" ]; then - _debug "Successfully found exact record ID: $dns_record_id" - printf "%s" "$dns_record_id" - return 0 - fi - - _debug "Can not find exact DNS record match for: $target_zjt" - return 0 -} - -# Request GNAME API,post_response: Response content -_post_to_api() { - uri=$1 - body=$2 - url="$GNAME_Api$uri" - gntoken=$(_gntoken "$body") - body="$body&gntoken=$gntoken" - post_response="$(_post "$body" "$url" "" "POST" "application/x-www-form-urlencoded")" - - http_err_code=$? - if [ "$http_err_code" != "0" ]; then - _err "POST API $url request failed:$http_err_code" - return 1 - fi - - normalized_response="$(echo "$post_response" | _normalizeJson)" - if [ -z "$normalized_response" ]; then - _err "Failed to normalize JSON response for [$uri]" - return 1 - fi - - ret_code=$(echo "$normalized_response" | sed 's/.*"code":\([-0-9]*\).*/\1/') - - if [ "$ret_code" = "1" ]; then - return 0 - fi - - if [ "$uri" = "/api/resolution/add" ]; then - if _contains "$normalized_response" "the same host records and record values"; then - _info "DNS record already exists, treat as success." - return 0 - fi - fi - - ret_msg=$(echo "$normalized_response" | sed 's/.*"msg":"\([^"]*\)".*/\1/') - _err "POST API $url error: [$ret_code] $ret_msg" - _debug "Full response: $normalized_response" - return 1 -} - -# Split the complete domain into a host and a main domain. -# example, www.gname.com can be split into ext_hostname=www,ext_domain=gname.com -_extract_domain() { - - host="$1" - - # Prioritize reading from the cache and reduce network caching - if [ -z "$GNAME_TLDS_CACHE" ]; then - GNAME_TLDS_CACHE=$(_get_suffixes_json) - fi - - if [ -z "$GNAME_TLDS_CACHE" ]; then - _err "The list of domain suffixes is empty after retrieval; cannot extract domain" - return 1 - fi - - main_part=$(echo "$GNAME_TLDS_CACHE" | sed 's/.*"main":\[\([^]]*\)\].*/\1/' | tr -d '"' | tr ',' ' ') - sub_part=$(echo "$GNAME_TLDS_CACHE" | sed 's/.*"sub":\[\([^]]*\)\].*/\1/' | tr -d '"' | tr ',' ' ') - suffix_list=$(echo "$main_part $sub_part" | tr -s ' ' | sed 's/^[ ]//;s/[ ]$//') - - dot_count=$(echo "$host" | _egrep_o "\." | wc -l) - - if [ "$dot_count" -eq 0 ]; then - _err "Invalid domain format: $host (missing dot)" - return 1 - fi - - if [ "$dot_count" -eq 1 ]; then - ext_hostname="" - ext_domain="$host" - - elif [ "$dot_count" -gt 1 ]; then - matched_suffix="" - for suffix in $suffix_list; do - case "$host" in - *".$suffix") - if [ -z "$matched_suffix" ] || [ "${#suffix}" -gt "${#matched_suffix}" ]; then - matched_suffix="$suffix" - fi - ;; - esac - done - - if [ -n "$matched_suffix" ]; then - prefix="${host%."$matched_suffix"}" - main_name="${prefix##*.}" - ext_domain="$main_name.$matched_suffix" - else - _tld="${host##*.}" - _tmp="${host%.*}" - _main="${_tmp##*.}" - ext_domain="$_main.$_tld" - fi - - if [ "$host" = "$ext_domain" ]; then - ext_hostname="" - else - ext_hostname="${host%."$ext_domain"}" - fi - - fi - _debug "ext_hostname:$ext_hostname" - _debug "ext_domain:$ext_domain" - return 0 -} - -# Obtain the list of domain suffixes via API -_get_suffixes_json() { - _debug "GET request URL: $GNAME_TLD_Api Retrieves a list of domain suffixes." - - if ! response="$(_get "$GNAME_TLD_Api")"; then - _err "Failed to retrieve list of domain suffixes" - return 1 - fi - - if [ -z "$response" ]; then - _err "The list of domain suffixes is empty" - return 1 - fi - - normalized_response="$(echo "$response" | _normalizeJson)" - if [ -z "$normalized_response" ]; then - _err "Failed to normalize JSON response for domain suffix list" - return 1 - fi - - if ! _contains "$normalized_response" "\"code\":1"; then - _err "Failed to retrieve list of domain name suffixes; code is not 1" - return 1 - fi - - echo "$normalized_response" - return 0 -} - -# Generate API authentication signature -_gntoken() { - data_to_sign="$1" - full_data="${data_to_sign}${GNAME_APPKEY}" - hash=$(printf "%s" "$full_data" | _digest md5 hex | tr -d ' ') - hash_upper=$(echo "$hash" | _upper_case) - printf "%s" "$hash_upper" -} diff --git a/dnsapi/dns_googledomains.sh b/dnsapi/dns_googledomains.sh deleted file mode 100755 index 07a37e07..00000000 --- a/dnsapi/dns_googledomains.sh +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_googledomains_info='Google Domains -Site: Domains.Google.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_googledomains -Options: - GOOGLEDOMAINS_ACCESS_TOKEN API Access Token - GOOGLEDOMAINS_ZONE Zone -Issues: github.com/acmesh-official/acme.sh/issues/4545 -Author: Alex Leigh -' - -GOOGLEDOMAINS_API="https://acmedns.googleapis.com/v1/acmeChallengeSets" - -######## Public functions ######## - -#Usage: dns_googledomains_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_googledomains_add() { - fulldomain=$1 - txtvalue=$2 - - _info "Invoking Google Domains ACME DNS API." - - if ! _dns_googledomains_setup; then - return 1 - fi - - zone="$(_dns_googledomains_get_zone "$fulldomain")" - if [ -z "$zone" ]; then - _err "Could not find a Google Domains-managed zone containing the requested domain." - return 1 - fi - - _debug zone "$zone" - _debug txtvalue "$txtvalue" - - _info "Adding TXT record for $fulldomain." - if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToAdd\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then - if _contains "$response" "$txtvalue"; then - _info "TXT record added." - return 0 - else - _err "Error adding TXT record." - return 1 - fi - fi - - _err "Error adding TXT record." - return 1 -} - -#Usage: dns_googledomains_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_googledomains_rm() { - fulldomain=$1 - txtvalue=$2 - - _info "Invoking Google Domains ACME DNS API." - - if ! _dns_googledomains_setup; then - return 1 - fi - - zone="$(_dns_googledomains_get_zone "$fulldomain")" - if [ -z "$zone" ]; then - _err "Could not find a Google Domains-managed domain based on request." - return 1 - fi - - _debug zone "$zone" - _debug txtvalue "$txtvalue" - - _info "Removing TXT record for $fulldomain." - if _dns_googledomains_api "$zone" ":rotateChallenges" "{\"accessToken\":\"$GOOGLEDOMAINS_ACCESS_TOKEN\",\"recordsToRemove\":[{\"fqdn\":\"$fulldomain\",\"digest\":\"$txtvalue\"}],\"keepExpiredRecords\":true}"; then - if _contains "$response" "$txtvalue"; then - _err "Error removing TXT record." - return 1 - else - _info "TXT record removed." - return 0 - fi - fi - - _err "Error removing TXT record." - return 1 -} - -######## Private functions ######## - -_dns_googledomains_setup() { - if [ -n "$GOOGLEDOMAINS_SETUP_COMPLETED" ]; then - return 0 - fi - - GOOGLEDOMAINS_ACCESS_TOKEN="${GOOGLEDOMAINS_ACCESS_TOKEN:-$(_readaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN)}" - GOOGLEDOMAINS_ZONE="${GOOGLEDOMAINS_ZONE:-$(_readaccountconf_mutable GOOGLEDOMAINS_ZONE)}" - - if [ -z "$GOOGLEDOMAINS_ACCESS_TOKEN" ]; then - GOOGLEDOMAINS_ACCESS_TOKEN="" - _err "Google Domains access token was not specified." - _err "Please visit Google Domains Security settings to provision an ACME DNS API access token." - return 1 - fi - - if [ "$GOOGLEDOMAINS_ZONE" ]; then - _savedomainconf GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN" - _savedomainconf GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE" - else - _saveaccountconf_mutable GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN" - _clearaccountconf_mutable GOOGLEDOMAINS_ZONE - _clearaccountconf GOOGLEDOMAINS_ZONE - fi - - _debug GOOGLEDOMAINS_ACCESS_TOKEN "$GOOGLEDOMAINS_ACCESS_TOKEN" - _debug GOOGLEDOMAINS_ZONE "$GOOGLEDOMAINS_ZONE" - - GOOGLEDOMAINS_SETUP_COMPLETED=1 - return 0 -} - -_dns_googledomains_get_zone() { - domain=$1 - - # Use zone directly if provided - if [ "$GOOGLEDOMAINS_ZONE" ]; then - if ! _dns_googledomains_api "$GOOGLEDOMAINS_ZONE"; then - return 1 - fi - - echo "$GOOGLEDOMAINS_ZONE" - return 0 - fi - - i=2 - while true; do - curr=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug curr "$curr" - - if [ -z "$curr" ]; then - return 1 - fi - - if _dns_googledomains_api "$curr"; then - echo "$curr" - return 0 - fi - - i=$(_math "$i" + 1) - done - - return 1 -} - -_dns_googledomains_api() { - zone=$1 - apimethod=$2 - data="$3" - - if [ -z "$data" ]; then - response="$(_get "$GOOGLEDOMAINS_API/$zone$apimethod")" - else - _debug data "$data" - export _H1="Content-Type: application/json" - response="$(_post "$data" "$GOOGLEDOMAINS_API/$zone$apimethod")" - fi - - _debug response "$response" - - if [ "$?" != "0" ]; then - _err "Error" - return 1 - fi - - if _contains "$response" "\"error\": {"; then - return 1 - fi - - return 0 -} diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh index a768f352..bf4a5030 100755 --- a/dnsapi/dns_he.sh +++ b/dnsapi/dns_he.sh @@ -1,14 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_he_info='Hurricane Electric HE.net -Site: dns.he.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_he -Options: - HE_Username Username - HE_Password Password -Issues: github.com/angel333/acme.sh/issues/ -Author: Ondrej Simek -' + +######################################################################## +# Hurricane Electric hook script for acme.sh +# +# Environment variables: +# +# - $HE_Username (your dns.he.net username) +# - $HE_Password (your dns.he.net password) +# +# Author: Ondrej Simek +# Git repo: https://github.com/angel333/acme.sh #-- dns_he_add() - Add TXT record -------------------------------------- # Usage: dns_he_add _acme-challenge.subdomain.domain.com "XyZ123..." @@ -143,7 +144,7 @@ _find_zone() { # Walk through all possible zone names _strip_counter=1 while true; do - _attempted_zone=$(echo "$_domain" | cut -d . -f "${_strip_counter}"-) + _attempted_zone=$(echo "$_domain" | cut -d . -f ${_strip_counter}-) # All possible zone names have been tried if [ -z "$_attempted_zone" ]; then diff --git a/dnsapi/dns_he_ddns.sh b/dnsapi/dns_he_ddns.sh deleted file mode 100644 index 1fe9a7fd..00000000 --- a/dnsapi/dns_he_ddns.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_he_ddns_info='Hurricane Electric HE.net DDNS -Site: dns.he.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_he_ddns -Options: - HE_DDNS_KEY The DDNS key -Issues: https://github.com/acmesh-official/acme.sh/issues/5238 -Author: Markku Leiniö -' - -HE_DDNS_URL="https://dyn.dns.he.net/nic/update" - -######## Public functions ##################### - -#Usage: dns_he_ddns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_he_ddns_add() { - fulldomain=$1 - txtvalue=$2 - HE_DDNS_KEY="${HE_DDNS_KEY:-$(_readaccountconf_mutable HE_DDNS_KEY)}" - if [ -z "$HE_DDNS_KEY" ]; then - HE_DDNS_KEY="" - _err "You didn't specify a DDNS key for accessing the TXT record in HE API." - return 1 - fi - #Save the DDNS key to the account conf file. - _saveaccountconf_mutable HE_DDNS_KEY "$HE_DDNS_KEY" - - _info "Using Hurricane Electric DDNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - response="$(_post "hostname=$fulldomain&password=$HE_DDNS_KEY&txt=$txtvalue" "$HE_DDNS_URL")" - _info "Response: $response" - _contains "$response" "good" && return 0 || return 1 -} - -# dns_he_ddns_rm() is not doing anything because the API call always updates the -# contents of the existing record (that the API key gives access to). - -dns_he_ddns_rm() { - fulldomain=$1 - _debug "Delete TXT record called for '${fulldomain}', not doing anything." - return 0 -} diff --git a/dnsapi/dns_hestiacp.sh b/dnsapi/dns_hestiacp.sh deleted file mode 100644 index 13ee6caf..00000000 --- a/dnsapi/dns_hestiacp.sh +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_hestiacp_info='HestiaCP Server API -Site: hestiacp.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hestiacp -Options: - HESTIA_HOST Panel URL. E.g. "https://panel.example.com:8083" - HESTIA_ACCESS API access key - HESTIA_SECRET API secret key - HESTIA_USER Username owning the DNS zones. Default "admin". Optional. -Issues: github.com/acmesh-official/acme.sh/issues/6251 -Author: Radu Malica -' - -######## Public functions ##################### - -# Usage: dns_hestiacp_add fulldomain txtvalue -dns_hestiacp_add() { - fulldomain=$1 - txtvalue=$2 - - if ! _hestia_init; then - return 1 - fi - - _debug "Detecting the root zone for $fulldomain" - if ! _hestia_get_root "$fulldomain"; then - _err "Cannot find a DNS zone for $fulldomain under user $HESTIA_USER" - return 1 - fi - _debug _hestia_domain "$_hestia_domain" - _debug _hestia_sub "$_hestia_sub" - - # _hestia_get_root left the zone record listing in _hestia_response - if _hestia_find_records "$_hestia_sub" "TXT" | grep -F -- "$txtvalue" >/dev/null; then - _info "The TXT record already exists, skipping" - return 0 - fi - - _info "Adding TXT record for $fulldomain" - if ! _hestia_rest "v-add-dns-record" "$HESTIA_USER" "$_hestia_domain" "$_hestia_sub" "TXT" "$txtvalue" "" "" "yes" "600"; then - _err "Error adding TXT record: $_hestia_response" - return 1 - fi - _info "TXT record added successfully" - return 0 -} - -# Usage: dns_hestiacp_rm fulldomain txtvalue -dns_hestiacp_rm() { - fulldomain=$1 - txtvalue=$2 - - if ! _hestia_init; then - return 1 - fi - - _debug "Detecting the root zone for $fulldomain" - if ! _hestia_get_root "$fulldomain"; then - _err "Cannot find a DNS zone for $fulldomain under user $HESTIA_USER" - return 1 - fi - _debug _hestia_domain "$_hestia_domain" - _debug _hestia_sub "$_hestia_sub" - - _hestia_removed=0 - _hestia_failed=0 - while IFS='|' read -r _hestia_id _hestia_value || [ -n "$_hestia_id" ]; do - if [ -z "$_hestia_id" ]; then - continue - fi - if ! _contains "$_hestia_value" "$txtvalue"; then - continue - fi - _info "Deleting TXT record $_hestia_id" - if ! _hestia_rest "v-delete-dns-record" "$HESTIA_USER" "$_hestia_domain" "$_hestia_id" "yes"; then - _err "Error deleting TXT record $_hestia_id: $_hestia_response" - _hestia_failed=$(_math "$_hestia_failed" + 1) - continue - fi - _hestia_removed=$(_math "$_hestia_removed" + 1) - done <"${HTTP_HEADER}" - - if [ "${method}" = "GET" ]; then - response="$(_get "${url}")" - else - if [ -z "${data}" ]; then - data="{}" - fi - response="$(_post "${data}" "${url}" "" "${method}" "application/json")" - fi - ret="${?}" - - _hetznercloud_last_http_code=$(grep "^HTTP" "${HTTP_HEADER}" | _tail_n 1 | cut -d " " -f 2 | tr -d '\r\n') - - if [ "${ret}" != "0" ]; then - return 1 - fi - - if [ "${_hetznercloud_last_http_code}" = "429" ] && [ "${retried}" != "retried" ]; then - retry_after=$(grep -i "^Retry-After" "${HTTP_HEADER}" | _tail_n 1 | cut -d : -f 2 | tr -d ' \r') - if [ -z "${retry_after}" ]; then - retry_after=1 - fi - _info "Hetzner Cloud DNS API rate limit hit; retrying in ${retry_after} seconds." - _sleep "${retry_after}" - if ! _hetznercloud_api "${method}" "${ep}" "${data}" "retried"; then - return 1 - fi - return 0 - fi - - return 0 -} - -_hetznercloud_handle_action_response() { - context="${1}" - if [ -z "${response}" ]; then - return 0 - fi - - normalized=$(printf "%s" "${response}" | _normalizeJson) - - failed_message="" - if failed_message=$(_hetznercloud_extract_failed_action_message "${normalized}"); then - if [ -n "${failed_message}" ]; then - _err "Hetzner Cloud DNS ${context} failed: ${failed_message}" - else - _err "Hetzner Cloud DNS ${context} failed." - fi - return 1 - fi - - action_ids="" - if action_ids=$(_hetznercloud_extract_action_ids "${normalized}"); then - for action_id in ${action_ids}; do - if [ -z "${action_id}" ]; then - continue - fi - if ! _hetznercloud_wait_for_action "${action_id}" "${context}"; then - return 1 - fi - done - fi - - return 0 -} - -_hetznercloud_extract_failed_action_message() { - normalized="${1}" - failed_section=$(printf "%s" "${normalized}" | _egrep_o '"failed_actions":\[[^]]*\]') - if [ -z "${failed_section}" ]; then - return 1 - fi - if _contains "${failed_section}" '"failed_actions":[]'; then - return 1 - fi - message=$(printf "%s" "${failed_section}" | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') - if [ -n "${message}" ]; then - printf "%s" "${message}" - else - printf "%s" "${failed_section}" - fi - return 0 -} - -_hetznercloud_extract_action_ids() { - normalized="${1}" - actions_section=$(printf "%s" "${normalized}" | _egrep_o '"actions":\[[^]]*\]') - if [ -z "${actions_section}" ]; then - return 1 - fi - action_ids=$(printf "%s" "${actions_section}" | _egrep_o '"id":[0-9]*' | cut -d : -f 2 | tr -d '"' | tr '\n' ' ') - action_ids=$(printf "%s" "${action_ids}" | tr -s ' ') - action_ids=$(printf "%s" "${action_ids}" | sed 's/^ //;s/ $//') - if [ -z "${action_ids}" ]; then - return 1 - fi - printf "%s" "${action_ids}" - return 0 -} - -_hetznercloud_wait_for_action() { - action_id="${1}" - context="${2}" - attempts="0" - - while true; do - if ! _hetznercloud_api GET "/actions/${action_id}"; then - return 1 - fi - if [ "${_hetznercloud_last_http_code}" != "200" ]; then - _hetznercloud_log_http_error "Hetzner Cloud DNS action ${action_id} query failed" "${_hetznercloud_last_http_code}" - return 1 - fi - - normalized=$(printf "%s" "${response}" | _normalizeJson) - action_status=$(_hetznercloud_action_status_from_normalized "${normalized}") - - if [ -z "${action_status}" ]; then - _err "Hetzner Cloud DNS ${context} action ${action_id} returned no status." - return 1 - fi - - if [ "${action_status}" = "success" ]; then - return 0 - fi - - if [ "${action_status}" = "error" ]; then - if action_error=$(_hetznercloud_action_error_from_normalized "${normalized}"); then - _err "Hetzner Cloud DNS ${context} action ${action_id} failed: ${action_error}" - else - _err "Hetzner Cloud DNS ${context} action ${action_id} failed." - fi - return 1 - fi - - attempts=$(_math "${attempts}" + 1) - if [ "${attempts}" -ge "${HETZNER_MAX_ATTEMPTS}" ]; then - _err "Hetzner Cloud DNS ${context} action ${action_id} did not complete after ${HETZNER_MAX_ATTEMPTS} attempts." - return 1 - fi - - _sleep 1 - done -} - -_hetznercloud_action_status_from_normalized() { - normalized="${1}" - status=$(printf "%s" "${normalized}" | _egrep_o '"status":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') - printf "%s" "${status}" -} - -_hetznercloud_action_error_from_normalized() { - normalized="${1}" - error_section=$(printf "%s" "${normalized}" | _egrep_o '"error":{[^}]*}') - if [ -z "${error_section}" ]; then - return 1 - fi - message=$(printf "%s" "${error_section}" | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') - if [ -n "${message}" ]; then - printf "%s" "${message}" - return 0 - fi - code=$(printf "%s" "${error_section}" | _egrep_o '"code":"[^"]*"' | _head_n 1 | cut -d : -f 2 | tr -d '"') - if [ -n "${code}" ]; then - printf "%s" "${code}" - return 0 - fi - return 1 -} diff --git a/dnsapi/dns_hexonet.sh b/dnsapi/dns_hexonet.sh index 017641fd..525efe73 100755 --- a/dnsapi/dns_hexonet.sh +++ b/dnsapi/dns_hexonet.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_hexonet_info='Hexonet.com -Site: Hexonet.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hexonet -Options: - Hexonet_Login Login. E.g. "username!roleId" - Hexonet_Password Role Password -Issues: github.com/acmesh-official/acme.sh/issues/2389 -' + +# +# Hexonet_Login="username!roleId" +# +# Hexonet_Password="rolePassword" Hexonet_Api="https://coreapi.1api.net/api/call.cgi" @@ -123,7 +119,7 @@ _get_root() { i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -135,7 +131,7 @@ _get_root() { fi if _contains "$response" "CODE=200"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_hostingde.sh b/dnsapi/dns_hostingde.sh index ed675b42..9e3e5664 100644 --- a/dnsapi/dns_hostingde.sh +++ b/dnsapi/dns_hostingde.sh @@ -1,13 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_hostingde_info='Hosting.de -Site: Hosting.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_hostingde -Options: - HOSTINGDE_ENDPOINT Endpoint. E.g. "https://secure.hosting.de" - HOSTINGDE_APIKEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/2058 -' + +# hosting.de API + +# Values to export: +# export HOSTINGDE_ENDPOINT='https://secure.hosting.de' +# export HOSTINGDE_APIKEY='xxxxx' ######## Public functions ##################### @@ -40,11 +37,6 @@ _hostingde_apiKey() { return 1 fi - # The endpoint is the base URL only; the api path is appended below. - # hosting.de's own docs show the full api URL, so strip it if pasted in. - # https://github.com/acmesh-official/acme.sh/issues/6896 - HOSTINGDE_ENDPOINT="$(echo "$HOSTINGDE_ENDPOINT" | sed 's|/api/dns/v1/json||; s|/*$||')" - _saveaccountconf_mutable HOSTINGDE_APIKEY "$HOSTINGDE_APIKEY" _saveaccountconf_mutable HOSTINGDE_ENDPOINT "$HOSTINGDE_ENDPOINT" } diff --git a/dnsapi/dns_hostinger.sh b/dnsapi/dns_hostinger.sh deleted file mode 100755 index 665c65da..00000000 --- a/dnsapi/dns_hostinger.sh +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_hostinger_info='Hostinger -Site: Hostinger.com -Domains: hostinger.nl -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_hostinger -Options: - HOSTINGER_Token API Key -Issues: https://github.com/acmesh-official/acme.sh/issues/6831 -Author: Sasha Reid -' - -HOSTINGER_Api="https://developers.hostinger.com/api/dns/v1/zones" - -######## Public functions ##################### - -#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_hostinger_add() { - fulldomain=$1 - txtvalue=$2 - - HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}" - - if [ -z "$HOSTINGER_Token" ]; then - HOSTINGER_Token="" - _err "You didn't specify a Hostinger API Key yet." - _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication" - return 1 - fi - _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting existing records" - _hostinger_rest GET "${_domain}" - - if [ -z "$response" ]; then - _err "Error" - return 1 - fi - - # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so - # we can not use updating anymore. - # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) - # _debug count "$count" - # if [ "$count" = "0" ]; then - _info "Adding record" - if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [{\"content\":\"$txtvalue\"}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":false}"; then - if _contains "$response" "Request accepted"; then - _info "Added, OK" - return 0 - elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" || - _contains "$response" 'DNS:4008'; 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_hostinger_rm() { - fulldomain=$1 - txtvalue=$2 - - HOSTINGER_Token="${HOSTINGER_Token:-$(_readaccountconf_mutable HOSTINGER_Token)}" - - if [ -z "$HOSTINGER_Token" ]; then - HOSTINGER_Token="" - _err "You didn't specify a Hostinger API Key yet." - _err "Please read the documentation for the Hostinger API authentication at https://developers.hostinger.com/#description/authentication" - return 1 - fi - _saveaccountconf_mutable HOSTINGER_Token "$HOSTINGER_Token" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting existing records" - _hostinger_rest GET "${_domain}" - - if [ -z "$response" ]; then - _err "Error" - return 1 - fi - - if _contains "$response" "\"name\":\"$_sub_domain\""; then - # Match the record, and make certain it is a TXT record for the domain not another type. Then remove our target record from the list - remaining_records=$(echo "$response" | _normalizeJson | _egrep_o '{"name":"'"$_sub_domain"'","records":\[[^]]+\],"ttl":[0-9]+,"type":"TXT"\}' | _egrep_o "\[.*\]" | sed -E 's#\{"content":"\\"'"$txtvalue"'\\"","is_disabled":false\},?##g') - if [ "$remaining_records" != "[]" ]; then - remaining_json=$(echo "$remaining_records" | _egrep_o '"content":"\\"[^}]+\\""' | sed -E 's/^(.*)$/{\1},/g' | tr -d '\n' | sed 's/,$//') - # We need to set the remaining records back to Hostinger, as we can't partially delete - _info "Removing $txtvalue from $_sub_domain by setting records to ${remaining_json}" - if _hostinger_rest PUT "$_domain" "{\"zone\":[{\"name\": \"$_sub_domain\",\"records\": [${remaining_json}],\"type\":\"TXT\",\"ttl\":\"120\"}],\"overwrite\":true}"; then - if _contains "$response" "Request accepted"; then - _info "Updated remaining records, OK" - return 0 - elif _contains "$response" "DNS resource record is not valid or conflicts with another resource record" || - _contains "$response" 'DNS:4008'; then - _info "Already exists, OK" - return 0 - else - _err "Add txt record error." - return 1 - fi - fi - # Otherwise delete the TXT record that matches the subdomain - else - if ! _hostinger_rest DELETE "$_domain" "{\"filters\":[{\"name\":\"$_sub_domain\",\"type\":\"TXT\"}]}"; then - _err "Delete record error." - return 1 - fi - fi - echo "$response" | grep "Request accepted" >/dev/null - else - _info "Don't need to remove." - fi - -} - -#################### Private functions below ################################## -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -_get_root() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - #not valid - return 1 - fi - - _hostinger_rest GET "$h" - if _contains "$response" "records"; then - if [ "$response" = "[]" ]; then - _debug "Valid subdomains are not the root" - else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 - fi - fi - - p=$i - i=$(_math "$i" + 1) - done - return 1 -} - -_hostinger_rest() { - m=$1 - ep="$2" - data="$3" - _debug "$ep" - - token_trimmed=$(echo "$HOSTINGER_Token" | tr -d '"') - - export _H1="Content-Type: application/json" - export _H2="Authorization: Bearer $token_trimmed" - - if [ "$m" != "GET" ]; then - _debug data "$data" - response="$(_post "$data" "$HOSTINGER_Api/$ep" "" "$m")" - else - response="$(_get "$HOSTINGER_Api/$ep")" - fi - - if [ "$?" != "0" ]; then - _err "error $ep" - return 1 - fi - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_hostup.sh b/dnsapi/dns_hostup.sh deleted file mode 100644 index a3d9174a..00000000 --- a/dnsapi/dns_hostup.sh +++ /dev/null @@ -1,577 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034,SC2154 - -dns_hostup_info='HostUp DNS -Site: hostup.se -Docs: https://developer.hostup.se/ -Options: - HOSTUP_API_KEY Required. HostUp API key with read:dns + write:dns + read:domains scopes. - HOSTUP_API_BASE Optional. Override API base URL (default: https://cloud.hostup.se/api/v2). - HOSTUP_TTL Optional. TTL for TXT records (default: 60 seconds). - HOSTUP_ZONE_ID Optional. Force a specific v2 zone ID (zone_...) and skip auto-detection. -Author: HostUp (https://cloud.hostup.se/contact/en) -' - -HOSTUP_API_BASE_DEFAULT="https://cloud.hostup.se/api/v2" -HOSTUP_DEFAULT_TTL=60 - -# Public: add TXT record -# Usage: dns_hostup_add _acme-challenge.example.com "txt-value" -dns_hostup_add() { - fulldomain="$1" - txtvalue="$2" - hostup_add_txtvalue="$2" - - _info "Using HostUp DNS API" - - if ! _hostup_init; then - return 1 - fi - - if ! _hostup_detect_zone "$fulldomain"; then - _err "Unable to determine HostUp zone for $fulldomain" - return 1 - fi - - record_name="$(_hostup_record_name "$fulldomain" "$HOSTUP_ZONE_DOMAIN")" - record_name="$(_hostup_sanitize_name "$record_name")" - hostup_add_record_value="$(_hostup_json_escape "$hostup_add_txtvalue")" - - raw_ttl="${HOSTUP_TTL:-$HOSTUP_DEFAULT_TTL}" - ttl="$(_hostup_normalize_ttl "$raw_ttl")" - if [ -z "$ttl" ]; then - _err "HOSTUP_TTL must be a whole number between 60 and 86400 seconds." - return 1 - fi - if [ -n "$HOSTUP_TTL" ]; then - HOSTUP_TTL="$ttl" - _saveaccountconf_mutable HOSTUP_TTL "$HOSTUP_TTL" - fi - - _debug "zone_id" "$HOSTUP_ZONE_ID" - _debug "zone_domain" "$HOSTUP_ZONE_DOMAIN" - _debug "record_name" "$record_name" - _debug "ttl" "$ttl" - - record_name_fqdn="$(_hostup_fqdn "$fulldomain")" - if _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$hostup_add_txtvalue"; then - _info "TXT record already exists for $fulldomain" - return 0 - fi - - request_body="{\"name\":\"$record_name\",\"type\":\"TXT\",\"value\":\"$hostup_add_record_value\",\"ttl\":$ttl}" - - if ! _hostup_rest "POST" "/dns-zones/$HOSTUP_ZONE_ID/records" "$request_body"; then - return 1 - fi - - _info "Added TXT record for $fulldomain" - return 0 -} - -# Public: remove TXT record -# Usage: dns_hostup_rm _acme-challenge.example.com "txt-value" -dns_hostup_rm() { - fulldomain="$1" - txtvalue="$2" - - _info "Using HostUp DNS API" - - if ! _hostup_init; then - return 1 - fi - - if ! _hostup_detect_zone "$fulldomain"; then - _err "Unable to determine HostUp zone for $fulldomain" - return 1 - fi - - record_name_fqdn="$(_hostup_fqdn "$fulldomain")" - record_value="$txtvalue" - - if ! _hostup_find_record "$HOSTUP_ZONE_ID" "$record_name_fqdn" "$record_value"; then - _info "TXT record not found for $record_name_fqdn. Skipping removal." - _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_value" - return 0 - fi - - _debug "Deleting record" "$HOSTUP_RECORD_ID" - - if ! _hostup_delete_record_by_id "$HOSTUP_ZONE_ID" "$HOSTUP_RECORD_ID"; then - return 1 - fi - - _info "Deleted TXT record $HOSTUP_RECORD_ID" - _hostup_clear_record_id "$HOSTUP_ZONE_ID" "$fulldomain" "$record_value" - HOSTUP_ZONE_ID="" - return 0 -} - -########################## -# Private helper methods # -########################## - -_hostup_init() { - HOSTUP_API_KEY="${HOSTUP_API_KEY:-$(_readaccountconf_mutable HOSTUP_API_KEY)}" - HOSTUP_API_BASE="${HOSTUP_API_BASE:-$(_readaccountconf_mutable HOSTUP_API_BASE)}" - HOSTUP_TTL="${HOSTUP_TTL:-$(_readaccountconf_mutable HOSTUP_TTL)}" - HOSTUP_ZONE_ID="${HOSTUP_ZONE_ID:-$(_readaccountconf_mutable HOSTUP_ZONE_ID)}" - - if [ -z "$HOSTUP_API_BASE" ]; then - HOSTUP_API_BASE="$HOSTUP_API_BASE_DEFAULT" - fi - HOSTUP_API_BASE="$(_hostup_normalize_api_base "$HOSTUP_API_BASE")" - - if [ -z "$HOSTUP_API_KEY" ]; then - HOSTUP_API_KEY="" - _err "HOSTUP_API_KEY is not set." - _err "Please export your HostUp API key with read:dns, write:dns, and read:domains scopes." - return 1 - fi - - _saveaccountconf_mutable HOSTUP_API_KEY "$HOSTUP_API_KEY" - _saveaccountconf_mutable HOSTUP_API_BASE "$HOSTUP_API_BASE" - - if [ -n "$HOSTUP_ZONE_ID" ]; then - _saveaccountconf_mutable HOSTUP_ZONE_ID "$HOSTUP_ZONE_ID" - fi - - return 0 -} - -_hostup_normalize_api_base() { - api_base="${1%/}" - - case "$api_base" in - */api/v2) - printf "%s" "$api_base" - ;; - */api) - printf "%s/v2" "$api_base" - ;; - *) - printf "%s" "$api_base" - ;; - esac -} - -_hostup_normalize_ttl() { - ttl_value="$1" - - case "$ttl_value" in - "" | *[!0-9]*) - return 1 - ;; - esac - - while [ "${ttl_value#0}" != "$ttl_value" ]; do - ttl_value="${ttl_value#0}" - done - [ -z "$ttl_value" ] && ttl_value=0 - - case "$ttl_value" in - ??????*) - return 1 - ;; - esac - - if [ "$ttl_value" -lt 60 ] || [ "$ttl_value" -gt 86400 ]; then - return 1 - fi - - printf "%s" "$ttl_value" -} - -_hostup_domain_in_zone() { - host="$(printf "%s" "${1%.}" | _lower_case)" - zone="$(printf "%s" "${2%.}" | _lower_case)" - - if [ -z "$host" ] || [ -z "$zone" ]; then - return 1 - fi - - if [ "$host" = "$zone" ]; then - return 0 - fi - - case "$host" in - *."$zone") - return 0 - ;; - esac - - return 1 -} - -_hostup_detect_zone() { - fulldomain="$1" - - if [ -n "$HOSTUP_ZONE_ID" ] && [ -n "$HOSTUP_ZONE_DOMAIN" ]; then - if _hostup_domain_in_zone "$fulldomain" "$HOSTUP_ZONE_DOMAIN"; then - return 0 - fi - _debug "hostup_cached_zone_mismatch" "$HOSTUP_ZONE_DOMAIN" - HOSTUP_ZONE_ID="" - HOSTUP_ZONE_DOMAIN="" - fi - - HOSTUP_ZONE_DOMAIN="" - _debug "hostup_full_domain" "$fulldomain" - - if [ -n "$HOSTUP_ZONE_ID" ] && [ -z "$HOSTUP_ZONE_DOMAIN" ]; then - # Attempt to fetch domain name for provided zone ID - if _hostup_fetch_zone_details "$HOSTUP_ZONE_ID"; then - if _hostup_domain_in_zone "$fulldomain" "$HOSTUP_ZONE_DOMAIN"; then - return 0 - fi - _debug "hostup_forced_zone_mismatch" "$HOSTUP_ZONE_DOMAIN" - fi - HOSTUP_ZONE_ID="" - HOSTUP_ZONE_DOMAIN="" - fi - - _domain_candidate="$(printf "%s" "${fulldomain%.}" | _lower_case)" - _debug "hostup_initial_candidate" "$_domain_candidate" - - while [ -n "$_domain_candidate" ]; do - _debug "hostup_zone_candidate" "$_domain_candidate" - if _hostup_lookup_zone "$_domain_candidate"; then - HOSTUP_ZONE_DOMAIN="$_lookup_zone_domain" - HOSTUP_ZONE_ID="$_lookup_zone_id" - return 0 - fi - - case "$_domain_candidate" in - *.*) ;; - *) break ;; - esac - - _domain_candidate="${_domain_candidate#*.}" - done - - HOSTUP_ZONE_ID="" - return 1 -} - -_hostup_record_name() { - fulldomain="$1" - zonedomain="$2" - - # Remove trailing dot, if any - fulldomain="${fulldomain%.}" - zonedomain="${zonedomain%.}" - - if [ "$fulldomain" = "$zonedomain" ]; then - printf "%s" "@" - return 0 - fi - - suffix=".$zonedomain" - case "$fulldomain" in - *"$suffix") - printf "%s" "${fulldomain%"$suffix"}" - ;; - *) - # Domain not within zone, fall back to full host - printf "%s" "$fulldomain" - ;; - esac -} - -_hostup_sanitize_name() { - name="$1" - - if [ -z "$name" ] || [ "$name" = "." ]; then - printf "%s" "@" - return 0 - fi - - # Remove any trailing dot - name="${name%.}" - printf "%s" "$name" -} - -_hostup_fqdn() { - domain="$1" - printf "%s" "${domain%.}" -} - -_hostup_fetch_zone_details() { - zone_id="$1" - - if ! _hostup_rest "GET" "/dns-zones/$zone_id/records" ""; then - return 1 - fi - - zonedomain="$(_hostup_json_extract "name" "$_hostup_response")" - if [ -n "$zonedomain" ]; then - HOSTUP_ZONE_DOMAIN="$zonedomain" - return 0 - fi - - return 1 -} - -_hostup_load_zones() { - if ! _hostup_rest "GET" "/dns-zones?limit=1000" ""; then - return 1 - fi - - HOSTUP_ZONES_CACHE="" - data="$(printf "%s" "$_hostup_response" | tr '{' '\n')" - - while IFS= read -r line; do - case "$line" in - *'"id"'*'"name"'*) - zone_id="$(_hostup_json_extract "id" "$line")" - zone_domain="$(_hostup_json_extract "name" "$line")" - if [ -n "$zone_id" ] && [ -n "$zone_domain" ]; then - HOSTUP_ZONES_CACHE="${HOSTUP_ZONES_CACHE}${zone_domain}|${zone_id} -" - _debug "hostup_zone_loaded" "$zone_domain|$zone_id" - fi - ;; - esac - done </dev/null - else - _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets/${_record_id}" false "PUT" >/dev/null - fi + _post "${_post_body}" "${dns_api}/v2/zones/${zoneid}/recordsets" >/dev/null _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" if [ "$_code" != "202" ]; then _err "dns_huaweicloud: http code ${_code}" @@ -309,7 +256,6 @@ _get_token() { _username=$1 _password=$2 _domain_name=$3 - _region_name=$4 _debug "Getting Token" body="{ @@ -330,7 +276,7 @@ _get_token() { }, \"scope\": { \"project\": { - \"name\": \"${_region_name}\" + \"name\": \"ap-southeast-1\" } } } diff --git a/dnsapi/dns_infoblox.sh b/dnsapi/dns_infoblox.sh index 27f1e61e..6bfd36ee 100644 --- a/dnsapi/dns_infoblox.sh +++ b/dnsapi/dns_infoblox.sh @@ -1,14 +1,8 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_infoblox_info='Infoblox.com -Site: Infoblox.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_infoblox -Options: - Infoblox_Creds Credentials. E.g. "username:password" - Infoblox_Server Server hostname. IP or FQDN of infoblox appliance -Issues: github.com/jasonkeller/acme.sh -Author: Jason Keller, Elijah Tenai -' + +## Infoblox API integration by Jason Keller and Elijah Tenai +## +## Report any bugs via https://github.com/jasonkeller/acme.sh dns_infoblox_add() { diff --git a/dnsapi/dns_infoblox_uddi.sh b/dnsapi/dns_infoblox_uddi.sh deleted file mode 100644 index 902cc700..00000000 --- a/dnsapi/dns_infoblox_uddi.sh +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_infoblox_uddi_info='Infoblox UDDI -Site: Infoblox.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_infoblox_uddi -Options: - Infoblox_UDDI_Key API Key for Infoblox UDDI - Infoblox_Portal URL, e.g. "csp.infoblox.com" or "csp.eu.infoblox.com" -Issues: github.com/acmesh-official/acme.sh/issues -Author: Stefan Riegel -' - -Infoblox_UDDI_Api="https://" - -######## Public functions ##################### - -#Usage: dns_infoblox_uddi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_infoblox_uddi_add() { - fulldomain=$1 - txtvalue=$2 - - Infoblox_UDDI_Key="${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}" - Infoblox_Portal="${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}" - - _info "Using Infoblox UDDI API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if [ -z "$Infoblox_UDDI_Key" ] || [ -z "$Infoblox_Portal" ]; then - Infoblox_UDDI_Key="" - Infoblox_Portal="" - _err "You didn't specify the Infoblox UDDI key or server (Infoblox_UDDI_Key; Infoblox_Portal)." - _err "Please set them via EXPORT Infoblox_UDDI_Key=your_key, EXPORT Infoblox_Portal=csp.infoblox.com and try again." - return 1 - fi - - _saveaccountconf_mutable Infoblox_UDDI_Key "$Infoblox_UDDI_Key" - _saveaccountconf_mutable Infoblox_Portal "$Infoblox_Portal" - - export _H1="Authorization: Token $Infoblox_UDDI_Key" - export _H2="Content-Type: application/json" - - _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 existing txt records" - _infoblox_rest GET "dns/record?_filter=type%20eq%20'TXT'%20and%20name_in_zone%20eq%20'$_sub_domain'%20and%20zone%20eq%20'$_domain_id'" - - _info "Adding record" - body="{\"type\":\"TXT\",\"name_in_zone\":\"$_sub_domain\",\"zone\":\"$_domain_id\",\"ttl\":120,\"inheritance_sources\":{\"ttl\":{\"action\":\"override\"}},\"rdata\":{\"text\":\"$txtvalue\"}}" - - if _infoblox_rest POST "dns/record" "$body"; then - if _contains "$response" "$txtvalue"; then - _info "Added, OK" - return 0 - elif _contains "$response" '"error"'; then - # Check if record already exists - if _contains "$response" "already exists" || _contains "$response" "duplicate"; then - _info "Already exists, OK" - return 0 - else - _err "Add txt record error." - _err "Response: $response" - return 1 - fi - else - _info "Added, OK" - return 0 - fi - fi - _err "Add txt record error." - return 1 -} - -#Usage: dns_infoblox_uddi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_infoblox_uddi_rm() { - fulldomain=$1 - txtvalue=$2 - - Infoblox_UDDI_Key="${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}" - Infoblox_Portal="${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}" - - if [ -z "$Infoblox_UDDI_Key" ] || [ -z "$Infoblox_Portal" ]; then - _err "Credentials not found" - return 1 - fi - - _info "Using Infoblox UDDI API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - export _H1="Authorization: Token $Infoblox_UDDI_Key" - export _H2="Content-Type: application/json" - - _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 to delete" - # Filter by txtvalue to support wildcard certs (multiple TXT records) - filter="type%20eq%20'TXT'%20and%20name_in_zone%20eq%20'$_sub_domain'%20and%20zone%20eq%20'$_domain_id'%20and%20rdata.text%20eq%20'$txtvalue'" - _infoblox_rest GET "dns/record?_filter=$filter" - - if ! _contains "$response" '"results"'; then - _info "Don't need to remove, record not found." - return 0 - fi - - record_id=$(echo "$response" | _egrep_o '"id":[ ]*"[^"]*"' | _head_n 1 | cut -d '"' -f 4) - _debug "record_id" "$record_id" - - if [ -z "$record_id" ]; then - _info "Don't need to remove, record not found." - return 0 - fi - - # Extract UUID from the full record ID (format: dns/record/uuid) - record_uuid=$(echo "$record_id" | sed 's|.*/||') - _debug "record_uuid" "$record_uuid" - - if ! _infoblox_rest DELETE "dns/record/$record_uuid"; then - _err "Delete record error." - return 1 - fi - - _info "Removed record successfully" - return 0 -} - -#################### Private functions below ################################## - -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -# _domain_id=dns/auth_zone/xxxx-xxxx -_get_root() { - domain=$1 - i=1 - p=1 - - # Remove _acme-challenge prefix if present - domain_no_acme=$(echo "$domain" | sed 's/^_acme-challenge\.//') - - while true; do - h=$(printf "%s" "$domain_no_acme" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - # not valid - return 1 - fi - - # Query for the zone with both trailing dot and without - filter="fqdn%20eq%20'$h.'%20or%20fqdn%20eq%20'$h'" - if ! _infoblox_rest GET "dns/auth_zone?_filter=$filter"; then - # API error - don't continue if we get auth errors - if _contains "$response" "401" || _contains "$response" "Authorization"; then - _err "Authentication failed. Please check your Infoblox_UDDI_Key." - return 1 - fi - # For other errors, continue to parent domain - p=$i - i=$((i + 1)) - continue - fi - - # Check if response contains results (even if empty) - if _contains "$response" '"results"'; then - # Extract zone ID - must match the pattern dns/auth_zone/... - zone_id=$(echo "$response" | _egrep_o '"id":[ ]*"dns/auth_zone/[^"]*"' | _head_n 1 | cut -d '"' -f 4) - if [ -n "$zone_id" ]; then - # Found the zone - _domain="$h" - _domain_id="$zone_id" - - # Calculate subdomain - if [ "$_domain" = "$domain" ]; then - _sub_domain="" - else - _cutlength=$((${#domain} - ${#_domain} - 1)) - _sub_domain=$(printf "%s" "$domain" | cut -c "1-$_cutlength") - fi - - return 0 - fi - fi - - p=$i - i=$((i + 1)) - done - - return 1 -} - -# _infoblox_rest GET "dns/record?_filter=..." -# _infoblox_rest POST "dns/record" "{json body}" -# _infoblox_rest DELETE "dns/record/uuid" -_infoblox_rest() { - method=$1 - ep="$2" - data="$3" - - _debug "$ep" - - # Ensure credentials are available (when called from _get_root) - Infoblox_UDDI_Key="${Infoblox_UDDI_Key:-$(_readaccountconf_mutable Infoblox_UDDI_Key)}" - Infoblox_Portal="${Infoblox_Portal:-$(_readaccountconf_mutable Infoblox_Portal)}" - - Infoblox_UDDI_Api="https://$Infoblox_Portal/api/ddi/v1" - export _H1="Authorization: Token $Infoblox_UDDI_Key" - export _H2="Content-Type: application/json" - - # Debug (masked) - _tok_len=$(printf "%s" "$Infoblox_UDDI_Key" | wc -c | tr -d ' \n') - _debug2 "Auth header set" "Token len=${_tok_len} on $Infoblox_Portal" - - if [ "$method" != "GET" ]; then - _debug data "$data" - response="$(_post "$data" "$Infoblox_UDDI_Api/$ep" "" "$method")" - else - response="$(_get "$Infoblox_UDDI_Api/$ep")" - fi - - _ret="$?" - _debug2 response "$response" - - if [ "$_ret" != "0" ]; then - _err "Error: $ep" - return 1 - fi - - return 0 -} diff --git a/dnsapi/dns_infomaniak.sh b/dnsapi/dns_infomaniak.sh index 6cc8a4a0..a005132c 100755 --- a/dnsapi/dns_infomaniak.sh +++ b/dnsapi/dns_infomaniak.sh @@ -1,22 +1,19 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_infomaniak_info='Infomaniak.com -Site: Infomaniak.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_infomaniak -Options: - INFOMANIAK_API_TOKEN API Token -Issues: github.com/acmesh-official/acme.sh/issues/3188 -' - -# To use this API you need visit the API dashboard of your account. +############################################################################### +# Infomaniak API integration +# +# To use this API you need visit the API dashboard of your account +# once logged into https://manager.infomaniak.com add /api/dashboard to the URL +# +# Please report bugs to +# https://github.com/acmesh-official/acme.sh/issues/3188 +# # Note: the URL looks like this: -# https://manager.infomaniak.com/v3//ng/profile/user/token/list -# Then generate a token with following scopes : -# - domain:read -# - dns:read -# - dns:write +# https://manager.infomaniak.com/v3//api/dashboard +# Then generate a token with the scope Domain # this is given as an environment variable INFOMANIAK_API_TOKEN +############################################################################### # base variables @@ -67,30 +64,33 @@ dns_infomaniak_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" + fqdn=${fulldomain#_acme-challenge.} + # guess which base domain to add record to - zone=$(_get_zone "$fulldomain") - if [ -z "$zone" ]; then - _err "cannot find zone:<${zone}> to modify" + zone_and_id=$(_find_zone "$fqdn") + if [ -z "$zone_and_id" ]; then + _err "cannot find zone to modify" return 1 fi + zone=${zone_and_id% *} + domain_id=${zone_and_id#* } # extract first part of domain key=${fulldomain%."$zone"} - _debug "key:$key" - _debug "txtvalue: $txtvalue" + _debug "zone:$zone id:$domain_id key:$key" # payload data="{\"type\": \"TXT\", \"source\": \"$key\", \"target\": \"$txtvalue\", \"ttl\": $INFOMANIAK_TTL}" # API call - response=$(_post "$data" "${INFOMANIAK_API_URL}/2/zones/${zone}/records") - if _contains "$response" '"result":"success"'; then + response=$(_post "$data" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record") + if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then _info "Record added" - _debug "response: $response" + _debug "Response: $response" return 0 fi - _err "Could not create record." + _err "could not create record" _debug "Response: $response" return 1 } @@ -105,7 +105,7 @@ dns_infomaniak_rm() { if [ -z "$INFOMANIAK_API_TOKEN" ]; then INFOMANIAK_API_TOKEN="" - _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN." + _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN" return 1 fi @@ -129,7 +129,7 @@ dns_infomaniak_rm() { fi export _H1="Authorization: Bearer $INFOMANIAK_API_TOKEN" - export _H2="Content-Type: application/json" + export _H2="ContentType: application/json" fulldomain=$1 txtvalue=$2 @@ -137,56 +137,63 @@ dns_infomaniak_rm() { _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" + fqdn=${fulldomain#_acme-challenge.} + # guess which base domain to add record to - zone=$(_get_zone "$fulldomain") - if [ -z "$zone" ]; then - _err "cannot find zone:<$zone> to modify" + zone_and_id=$(_find_zone "$fqdn") + if [ -z "$zone_and_id" ]; then + _err "cannot find zone to modify" return 1 fi + zone=${zone_and_id% *} + domain_id=${zone_and_id#* } # extract first part of domain key=${fulldomain%."$zone"} - key=$(echo "$key" | _lower_case) - _debug "zone:$zone" - _debug "key:$key" + _debug "zone:$zone id:$domain_id key:$key" # find previous record - # shellcheck disable=SC2086 - response=$(_get "${INFOMANIAK_API_URL}/2/zones/${zone}/records" | sed 's/.*"data":\[\(.*\)\]}/\1/; s/},{/}{/g') - record_id=$(echo "$response" | sed -n 's/.*"id":"*\([0-9]*\)"*.*"source":"'"$key"'".*"target":"\\"'"$txtvalue"'\\"".*/\1/p') - _debug "key: $key" - _debug "txtvalue: $txtvalue" - _debug "record_id: $record_id" - + # shellcheck disable=SC1004 + record_id=$(_get "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record" | sed 's/.*"data":\[\(.*\)\]}/\1/; s/},{/}\ +{/g' | sed -n 's/.*"id":"*\([0-9]*\)"*.*"source_idn":"'"$fulldomain"'".*"target_idn":"'"$txtvalue"'".*/\1/p') if [ -z "$record_id" ]; then _err "could not find record to delete" - _debug "response: $response" return 1 fi + _debug "record_id: $record_id" # API call - response=$(_post "" "${INFOMANIAK_API_URL}/2/zones/${zone}/records/${record_id}" "" DELETE) - if _contains "$response" '"result":"success"'; then + response=$(_post "" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record/$record_id" "" DELETE) + if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then _info "Record deleted" - _debug "response: $response" return 0 fi - _err "Could not delete record." - _debug "Response: $response" + _err "could not delete record" return 1 } #################### Private functions below ################################## -_get_zone() { +_get_domain_id() { domain="$1" - # Whatever the domain is, you can get the fqdn with the following. - response=$(_get "${INFOMANIAK_API_URL}/2/domains/${domain}/zones") - _debug2 "_get_zone response" "$response" - if ! _contains "$response" '"result":"success"'; then - _err "cannot get zones for ${domain}, response: ${response}" - return 1 - fi - echo "$response" | _egrep_o '"fqdn" *: *"[^"]*"' | _head_n 1 | cut -d '"' -f 4 + + # shellcheck disable=SC1004 + _get "${INFOMANIAK_API_URL}/1/product?service_name=domain&customer_name=$domain" | sed 's/.*"data":\[{\(.*\)}\]}/\1/; s/,/\ +/g' | sed -n 's/^"id":\(.*\)/\1/p' +} + +_find_zone() { + zone="$1" + + # find domain in list, removing . parts sequentialy + while _contains "$zone" '\.'; do + _debug "testing $zone" + id=$(_get_domain_id "$zone") + if [ -n "$id" ]; then + echo "$zone $id" + return + fi + zone=${zone#*.} + done } diff --git a/dnsapi/dns_internetbs.sh b/dnsapi/dns_internetbs.sh index 4238bfe4..ae6b9e1e 100755 --- a/dnsapi/dns_internetbs.sh +++ b/dnsapi/dns_internetbs.sh @@ -1,14 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_internetbs_info='InternetBS.net -Site: InternetBS.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_internetbs -Options: - INTERNETBS_API_KEY API Key - INTERNETBS_API_PASSWORD API Password -Issues: github.com/acmesh-official/acme.sh/issues/2261 -Author: Ne-Lexa -' + +#This is the Internet.BS api wrapper for acme.sh +# +#Author: Ne-Lexa +#Report Bugs here: https://github.com/Ne-Lexa/acme.sh + +#INTERNETBS_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" +#INTERNETBS_API_PASSWORD="sdfsdfsdfljlbjkljlkjsdfoiwje" INTERNETBS_API_URL="https://api.internet.bs" @@ -133,7 +131,7 @@ _get_root() { fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "${i}"-100) + h=$(printf "%s" "$domain" | cut -d . -f ${i}-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -141,7 +139,7 @@ _get_root() { fi if _contains "$response" "\"$h\""; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"${p}") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-${p}) _domain=${h} return 0 fi diff --git a/dnsapi/dns_inwx.sh b/dnsapi/dns_inwx.sh index 460d4d28..ba789da9 100755 --- a/dnsapi/dns_inwx.sh +++ b/dnsapi/dns_inwx.sh @@ -1,14 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_inwx_info='INWX.de -Site: INWX.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_inwx -Options: - INWX_User Username - INWX_Password Password - INWX_Shared_Secret 2 Factor Authentication Shared Secret (optional requires oathtool) -' +# +#INWX_User="username" +# +#INWX_Password="password" +# # Dependencies: # ------------- # - oathtool (When using 2 Factor Authentication) @@ -111,17 +107,11 @@ dns_inwx_rm() { %s - - content - - %s - - - ' "$_domain" "$_sub_domain" "$txtvalue") + ' "$_domain" "$_sub_domain") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" if ! _contains "$response" "Command completed successfully"; then @@ -132,7 +122,7 @@ dns_inwx_rm() { if ! printf "%s" "$response" | grep "count" >/dev/null; then _info "Do not need to delete record" else - _record_id=$(printf '%s' "$response" | _egrep_o '.*(record){1}(.*)([0-9]+){1}' | _egrep_o 'id<\/name>[0-9]+' | _egrep_o '[0-9]+') + _record_id=$(printf '%s' "$response" | _egrep_o '.*(record){1}(.*)([0-9]+){1}' | _egrep_o 'id<\/name>[0-9]+' | _egrep_o '[0-9]+') _info "Deleting record" _inwx_delete_record "$_record_id" fi @@ -170,15 +160,6 @@ _inwx_check_cookie() { return 1 } -_htmlEscape() { - _s="$1" - _s=$(echo "$_s" | sed "s/&/&/g") - _s=$(echo "$_s" | sed "s//\>/g") - _s=$(echo "$_s" | sed 's/"/\"/g') - printf -- %s "$_s" -} - _inwx_login() { if _inwx_check_cookie; then @@ -186,8 +167,6 @@ _inwx_login() { return 0 fi - XML_PASS=$(_htmlEscape "$INWX_Password") - xml_content=$(printf ' account.login @@ -211,11 +190,11 @@ _inwx_login() { - ' "$INWX_User" "$XML_PASS") + ' "$INWX_User" "$INWX_Password") response="$(_post "$xml_content" "$INWX_Api" "" "POST")" - INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep -i "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')") + INWX_Cookie=$(printf "Cookie: %s" "$(grep "domrobot=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'domrobot=[^;]*;' | tr -d ';')") _H1=$INWX_Cookie export _H1 export INWX_Cookie @@ -300,39 +279,18 @@ _get_root() { response="$(_post "$xml_content" "$INWX_Api" "" "POST")" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid return 1 fi - # Anchor the match to the XML tag and escape dots so $h is compared - # literally: _contains uses grep, which treats "$h" as a regex, and a - # bare "g.berlight.de" would match "berlight.de" (the 'g' from - # "" plus '.' matching '>'). See issue #5129. - _hregex=$(printf "%s" "$h" | sed 's/\./\\./g') - if _contains "$response" "$_hregex"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + if _contains "$response" "$h"; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi - # IDN fallback: INWX returns Unicode zone names; when $h is ACE/punycode, - # encode each zone name via _idn() and compare -- no python dependency. - if _contains "$h" "xn--"; then - _zone_unicode=$(printf "%s" "$response" | _egrep_o '[^<]*' | - sed 's/<[^>]*>//g' | while IFS= read -r _z; do - if [ "$(_idn "$_z")" = "$h" ]; then - printf "%s" "$_z" - break - fi - done) - if [ -n "$_zone_unicode" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain="$_zone_unicode" - return 0 - fi - fi p=$i i=$(_math "$i" + 1) done @@ -352,7 +310,7 @@ _inwx_delete_record() { id - %s + %s @@ -390,7 +348,7 @@ _inwx_update_record() { id - %s + %s diff --git a/dnsapi/dns_ionos.sh b/dnsapi/dns_ionos.sh index 00662e82..e4ad3318 100755 --- a/dnsapi/dns_ionos.sh +++ b/dnsapi/dns_ionos.sh @@ -1,13 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ionos_info='IONOS.de -Site: IONOS.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ionos -Options: - IONOS_PREFIX Prefix - IONOS_SECRET Secret -Issues: github.com/acmesh-official/acme.sh/issues/3379 -' + +# Supports IONOS DNS API v1.0.1 +# +# Usage: +# Export IONOS_PREFIX and IONOS_SECRET before calling acme.sh: +# +# $ export IONOS_PREFIX="..." +# $ export IONOS_SECRET="..." +# +# $ acme.sh --issue --dns dns_ionos ... IONOS_API="https://api.hosting.ionos.com/dns" IONOS_ROUTE_ZONES="/v1/zones" @@ -16,7 +17,7 @@ IONOS_TXT_TTL=60 # minimum accepted by API IONOS_TXT_PRIO=10 dns_ionos_add() { - fulldomain="$(echo "$1" | _lower_case)" + fulldomain=$1 txtvalue=$2 if ! _ionos_init; then @@ -34,7 +35,7 @@ dns_ionos_add() { } dns_ionos_rm() { - fulldomain="$(echo "$1" | _lower_case)" + fulldomain=$1 txtvalue=$2 if ! _ionos_init; then @@ -87,7 +88,7 @@ _get_root() { _response="$(echo "$_response" | tr -d "\n")" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then return 1 fi @@ -96,7 +97,7 @@ _get_root() { if [ "$_zone" ]; then _zone_id=$(printf "%s\n" "$_zone" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"') if [ "$_zone_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 @@ -146,7 +147,7 @@ _ionos_rest() { if [ "$method" != "GET" ]; then export _H2="Accept: application/json" - export _H3= + export _H3="Content-Type: application/json" _response="$(_post "$data" "$IONOS_API$route" "" "$method" "application/json")" else diff --git a/dnsapi/dns_ionos_cloud.sh b/dnsapi/dns_ionos_cloud.sh deleted file mode 100644 index f255092f..00000000 --- a/dnsapi/dns_ionos_cloud.sh +++ /dev/null @@ -1,147 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ionos_cloud_info='IONOS Cloud DNS -Site: ionos.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ionos_cloud -Options: - IONOS_TOKEN API Token. -Issues: github.com/acmesh-official/acme.sh/issues/5243 -' - -# Supports IONOS Cloud DNS API v1.15.4 - -IONOS_CLOUD_API="https://dns.de-fra.ionos.com" -IONOS_CLOUD_ROUTE_ZONES="/zones" - -dns_ionos_cloud_add() { - fulldomain=$1 - txtvalue=$2 - - if ! _ionos_init; then - return 1 - fi - - _record_name=$(printf "%s" "$fulldomain" | cut -d . -f 1) - _body="{\"properties\":{\"name\":\"$_record_name\", \"type\":\"TXT\", \"content\":\"$txtvalue\"}}" - - if _ionos_cloud_rest POST "$IONOS_CLOUD_ROUTE_ZONES/$_zone_id/records" "$_body" && [ "$_code" = "202" ]; then - _info "TXT record has been created successfully." - return 0 - fi - - return 1 -} - -dns_ionos_cloud_rm() { - fulldomain=$1 - txtvalue=$2 - - if ! _ionos_init; then - return 1 - fi - - if ! _ionos_cloud_get_record "$_zone_id" "$txtvalue" "$fulldomain"; then - _err "Could not find _acme-challenge TXT record." - return 1 - fi - - if _ionos_cloud_rest DELETE "$IONOS_CLOUD_ROUTE_ZONES/$_zone_id/records/$_record_id" && [ "$_code" = "202" ]; then - _info "TXT record has been deleted successfully." - return 0 - fi - - return 1 -} - -_ionos_init() { - IONOS_TOKEN="${IONOS_TOKEN:-$(_readaccountconf_mutable IONOS_TOKEN)}" - - if [ -z "$IONOS_TOKEN" ]; then - _err "You didn't specify an IONOS token yet." - _err "Read https://api.ionos.com/docs/authentication/v1/#tag/tokens/operation/tokensGenerate to learn how to get a token." - _err "You need to set it before calling acme.sh:" - _err "\$ export IONOS_TOKEN=\"...\"" - _err "\$ acme.sh --issue -d ... --dns dns_ionos_cloud" - return 1 - fi - - _saveaccountconf_mutable IONOS_TOKEN "$IONOS_TOKEN" - - if ! _get_cloud_zone "$fulldomain"; then - _err "Cannot find zone $zone in your IONOS account." - return 1 - fi - - return 0 -} - -_get_cloud_zone() { - domain=$1 - zone=$(printf "%s" "$domain" | cut -d . -f 2-) - - if _ionos_cloud_rest GET "$IONOS_CLOUD_ROUTE_ZONES?filter.zoneName=$zone"; then - _response="$(echo "$_response" | tr -d "\n")" - - _zone_list_items=$(echo "$_response" | _egrep_o "\"items\":.*") - - _zone_id=$(printf "%s\n" "$_zone_list_items" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"') - if [ "$_zone_id" ]; then - return 0 - fi - fi - - return 1 -} - -_ionos_cloud_get_record() { - zone_id=$1 - txtrecord=$2 - # this is to transform the domain to lower case - fulldomain=$(printf "%s" "$3" | _lower_case) - # this is to transform record name to lower case - # IONOS Cloud API transforms all record names to lower case - _record_name=$(printf "%s" "$fulldomain" | cut -d . -f 1 | _lower_case) - - if _ionos_cloud_rest GET "$IONOS_CLOUD_ROUTE_ZONES/$zone_id/records"; then - _response="$(echo "$_response" | tr -d "\n")" - - pattern="\{\"id\":\"[a-fA-F0-9\-]*\",\"type\":\"record\",\"href\":\"/zones/$zone_id/records/[a-fA-F0-9\-]*\",\"metadata\":\{\"createdDate\":\"[A-Z0-9\:\.\-]*\",\"lastModifiedDate\":\"[A-Z0-9\:\.\-]*\",\"fqdn\":\"$fulldomain\",\"state\":\"AVAILABLE\",\"zoneId\":\"$zone_id\"\},\"properties\":\{\"content\":\"$txtrecord\",\"enabled\":true,\"name\":\"$_record_name\",\"priority\":[0-9]*,\"ttl\":[0-9]*,\"type\":\"TXT\"\}\}" - - _record="$(echo "$_response" | _egrep_o "$pattern")" - if [ "$_record" ]; then - _record_id=$(printf "%s\n" "$_record" | _egrep_o "\"id\":\"[a-fA-F0-9\-]*\"" | _head_n 1 | cut -d : -f 2 | tr -d '\"') - return 0 - fi - fi - - return 1 -} - -_ionos_cloud_rest() { - method="$1" - route="$2" - data="$3" - - export _H1="Authorization: Bearer $IONOS_TOKEN" - - # clear headers - : >"$HTTP_HEADER" - - if [ "$method" != "GET" ]; then - _response="$(_post "$data" "$IONOS_CLOUD_API$route" "" "$method" "application/json")" - else - _response="$(_get "$IONOS_CLOUD_API$route")" - fi - - _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" - - if [ "$?" != "0" ]; then - _err "Error $route: $_response" - return 1 - fi - - _debug2 "_response" "$_response" - _debug2 "_code" "$_code" - - return 0 -} diff --git a/dnsapi/dns_ipprojects.sh b/dnsapi/dns_ipprojects.sh deleted file mode 100644 index dadd05f0..00000000 --- a/dnsapi/dns_ipprojects.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ipprojects_info='IP-Projects DNS -Site: ip-projects.de/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ipprojects -Options: - IPP_Apikey API Key -Issues: github.com/acmesh-official/acme.sh/issues/6958 -Author: Markus Ebner -' - -IPP_Apikey="${IPP_Apikey:-$(_readaccountconf_mutable IPP_Apikey)}" -IPP_API="https://api.ip-projects.de/v1/dns/acme" - -######## Public functions ######## - -dns_ipprojects_add() { - fulldomain="$1" - txtvalue="$2" - - _info "Using IP-Projects DNS API to add record" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _IPP_load_credentials; then - return 1 - fi - - _IPP_api_request "add" "$fulldomain" "$txtvalue" -} - -dns_ipprojects_rm() { - fulldomain="$1" - txtvalue="$2" - - _info "Using IP-Projects DNS API to remove record" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _IPP_load_credentials; then - return 1 - fi - - _IPP_api_request "remove" "$fulldomain" "$txtvalue" -} - -######## Private helpers ######## - -_IPP_load_credentials() { - IPP_Apikey="${IPP_Apikey:-$(_readaccountconf_mutable IPP_Apikey)}" - - if [ -z "$IPP_Apikey" ]; then - _err "You must export IPP_Apikey" - _err "e.g.: export IPP_Apikey=\"your_api_key\"" - return 1 - fi - - _saveaccountconf_mutable IPP_Apikey "$IPP_Apikey" - return 0 -} - -_IPP_api_request() { - action="$1" - domain="$2" - value="$3" - - url="$IPP_API/$action" - - data="{\"domain\":\"$domain\",\"key\":\"$domain\",\"value\":\"$value\"}" - _debug url "$url" - _debug data "$data" - export _H1="X-API-Key: $IPP_Apikey" - - response="$(_post "$data" "$url" "" "POST" "application/json")" - ret="$?" - _ipprojects_last_http_code=$(grep "^HTTP" "${HTTP_HEADER}" | _tail_n 1 | cut -d " " -f 2 | tr -d '\r\n') - - _debug response "$response" - - if [ "$ret" != "0" ]; then - _err "HTTP request failed" - return 1 - fi - - if [ "$_ipprojects_last_http_code" != "200" ]; then - _err "API returned an error [code: ${_ipprojects_last_http_code}]" - return 1 - fi - - return 0 -} diff --git a/dnsapi/dns_ipv64.sh b/dnsapi/dns_ipv64.sh deleted file mode 100755 index 51025d1e..00000000 --- a/dnsapi/dns_ipv64.sh +++ /dev/null @@ -1,157 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ipv64_info='IPv64.net -Site: IPv64.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_ipv64 -Options: - IPv64_Token API Token -Issues: github.com/acmesh-official/acme.sh/issues/4419 -Author: Roman Lumetsberger -' - -IPv64_API="https://ipv64.net/api" - -######## Public functions ###################### - -#Usage: dns_ipv64_add _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_ipv64_add() { - fulldomain=$1 - txtvalue=$2 - - IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}" - if [ -z "$IPv64_Token" ]; then - _err "You must export variable: IPv64_Token" - _err "The API Key for your IPv64 account is necessary." - _err "You can look it up in your IPv64 account." - return 1 - fi - - # Now save the credentials. - _saveaccountconf_mutable IPv64_Token "$IPv64_Token" - - if ! _get_root "$fulldomain"; then - _err "invalid domain" "$fulldomain" - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - # convert to lower case - _domain="$(echo "$_domain" | _lower_case)" - _sub_domain="$(echo "$_sub_domain" | _lower_case)" - # Now add the TXT record - _info "Trying to add TXT record" - if _ipv64_rest "POST" "add_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then - _info "TXT record has been successfully added." - return 0 - else - _err "Errors happened during adding the TXT record, response=$_response" - return 1 - fi - -} - -#Usage: fulldomain txtvalue -#Usage: dns_ipv64_rm _acme-challenge.domain.ipv64.net "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -#Remove the txt record after validation. -dns_ipv64_rm() { - fulldomain=$1 - txtvalue=$2 - - IPv64_Token="${IPv64_Token:-$(_readaccountconf_mutable IPv64_Token)}" - if [ -z "$IPv64_Token" ]; then - _err "You must export variable: IPv64_Token" - _err "The API Key for your IPv64 account is necessary." - _err "You can look it up in your IPv64 account." - return 1 - fi - - if ! _get_root "$fulldomain"; then - _err "invalid domain" "$fulldomain" - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - # convert to lower case - _domain="$(echo "$_domain" | _lower_case)" - _sub_domain="$(echo "$_sub_domain" | _lower_case)" - # Now delete the TXT record - _info "Trying to delete TXT record" - if _ipv64_rest "DELETE" "del_record=$_domain&praefix=$_sub_domain&type=TXT&content=$txtvalue"; then - _info "TXT record has been successfully deleted." - return 0 - else - _err "Errors happened during deleting the TXT record, response=$_response" - return 1 - fi - -} - -#################### Private functions below ################################## -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -_get_root() { - domain="$1" - i=1 - p=1 - - _ipv64_get "get_domains" - domain_data=$_response - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - #not valid - return 1 - fi - - #if _contains "$domain_data" "\""$h"\"\:"; then - if _contains "$domain_data" "\"""$h""\"\:"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain="$h" - return 0 - fi - p=$i - i=$(_math "$i" + 1) - done - return 1 -} - -#send get request to api -# $1 has to set the api-function -_ipv64_get() { - url="$IPv64_API?$1" - export _H1="Authorization: Bearer $IPv64_Token" - - _response=$(_get "$url") - _response="$(echo "$_response" | _normalizeJson)" - - if _contains "$_response" "429 Too Many Requests"; then - _info "API throttled, sleeping to reset the limit" - _sleep 10 - _response=$(_get "$url") - _response="$(echo "$_response" | _normalizeJson)" - fi -} - -_ipv64_rest() { - url="$IPv64_API" - export _H1="Authorization: Bearer $IPv64_Token" - export _H2="Content-Type: application/x-www-form-urlencoded" - _response=$(_post "$2" "$url" "" "$1") - - if _contains "$_response" "429 Too Many Requests"; then - _info "API throttled, sleeping to reset the limit" - _sleep 10 - _response=$(_post "$2" "$url" "" "$1") - fi - - if ! _contains "$_response" "\"info\":\"success\""; then - return 1 - fi - _debug2 response "$_response" - return 0 -} diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index bd6bfb28..560f073e 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/dnsapi/dns_ispconfig.sh @@ -1,21 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ispconfig_info='ISPConfig Server API -Site: ISPConfig.org -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ispconfig -Options: - ISPC_User Remote User - ISPC_Password Remote Password - ISPC_Api API URL. E.g. "https://ispc.domain.tld:8080/remote/json.php" - ISPC_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept -' # ISPConfig 3.1 API -# User must provide login data and URL to the ISPConfig installation incl. port. -# The remote user in ISPConfig must have access to: +# User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to: # - DNS txt Functions -# - DNS zone functions -# - Client functions + +# Report bugs to https://github.com/sjau/acme.sh + +# Values to export: +# export ISPC_User="remoteUser" +# export ISPC_Password="remotePassword" +# export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" +# export ISPC_Api_Insecure=1 # Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) ######## Public functions ##################### @@ -136,7 +131,7 @@ _ISPC_getZoneInfo() { curResult="$(_post "${curData}" "${ISPC_Api}?client_get_id")" _debug "Calling _ISPC_ClientGetID: '${curData}' '${ISPC_Api}?client_get_id'" _debug "Result of _ISPC_ClientGetID: '$curResult'" - client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | cut -d '[' -f 1 | tr -d '{}') + client_id=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2 | tr -d '{}') _debug "Client ID: '${client_id}'" case "${client_id}" in '' | *[!0-9]*) diff --git a/dnsapi/dns_jd.sh b/dnsapi/dns_jd.sh index 4b9067f2..d0f2a501 100644 --- a/dnsapi/dns_jd.sh +++ b/dnsapi/dns_jd.sh @@ -1,14 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_jd_info='jdcloud.com -Site: jdcloud.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_jd -Options: - JD_ACCESS_KEY_ID Access key ID - JD_ACCESS_KEY_SECRET Access key secret - JD_REGION Region. E.g. "cn-north-1" -Issues: github.com/acmesh-official/acme.sh/issues/2388 -' + +# +#JD_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje" +#JD_ACCESS_KEY_SECRET="xxxxxxx" +#JD_REGION="cn-north-1" _JD_ACCOUNT="https://uc.jdcloud.com/account/accesskey" @@ -135,7 +130,7 @@ _get_root() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug2 "Checking domain: $h" if ! jd_rest GET "domain"; then _err "error get domain list" @@ -153,7 +148,7 @@ _get_root() { if [ "$hostedzone" ]; then _domain_id="$(echo "$hostedzone" | tr ',' '\n' | grep "\"id\":" | cut -d : -f 2)" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_joker.sh b/dnsapi/dns_joker.sh index 0ad80327..78399a1d 100644 --- a/dnsapi/dns_joker.sh +++ b/dnsapi/dns_joker.sh @@ -1,14 +1,27 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_joker_info='Joker.com -Site: Joker.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_joker -Options: - JOKER_USERNAME Username - JOKER_PASSWORD Password -Issues: github.com/acmesh-official/acme.sh/issues/2840 -Author: @aattww -' + +# Joker.com API for acme.sh +# +# This script adds the necessary TXT record to a domain in Joker.com. +# +# You must activate Dynamic DNS in Joker.com DNS configuration first. +# Username and password below refer to Dynamic DNS authentication, +# not your Joker.com login credentials. +# See: https://joker.com/faq/content/11/427/en/what-is-dynamic-dns-dyndns.html +# +# NOTE: This script does not support wildcard certificates, because +# Joker.com API does not support adding two TXT records with the same +# subdomain. Adding the second record will overwrite the first one. +# See: https://joker.com/faq/content/6/496/en/let_s-encrypt-support.html +# "... this request will replace all TXT records for the specified +# label by the provided content" +# +# Author: aattww (https://github.com/aattww/) +# +# Report bugs to https://github.com/acmesh-official/acme.sh/issues/2840 +# +# JOKER_USERNAME="xxxx" +# JOKER_PASSWORD="xxxx" JOKER_API="https://svc.joker.com/nic/replace" @@ -35,28 +48,9 @@ dns_joker_add() { return 1 fi - # Joker's /nic/replace overwrites all TXT records at the label on every call, - # and the API is not readable, so accumulate the values locally (keyed by the - # full record name) and re-send the whole set each time. This is required so a - # wildcard cert (base + *.domain both validating under the same - # _acme-challenge label) does not overwrite its own first challenge value. - _joker_conf_key=$(printf "%s" "JOKER_TXT_${fulldomain}" | tr '.-' '_') - _joker_values=$(_readdomainconf "$_joker_conf_key") - if [ -z "$_joker_values" ]; then - _joker_values="$txtvalue" - elif ! _contains " $_joker_values " " $txtvalue "; then - _joker_values="$_joker_values $txtvalue" - fi - - _joker_value_params="" - for _joker_v in $_joker_values; do - _joker_value_params="$_joker_value_params&value=$_joker_v" - done - _info "Adding TXT record" - if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT$_joker_value_params"; then + if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value=$txtvalue"; then if _startswith "$response" "OK"; then - _savedomainconf "$_joker_conf_key" "$_joker_values" _info "Added, OK" return 0 fi @@ -78,36 +72,10 @@ dns_joker_rm() { return 1 fi - # Remove only this value from the accumulated set and replace the label with - # whatever remains (an empty value clears the label's TXT records entirely). - _joker_conf_key=$(printf "%s" "JOKER_TXT_${fulldomain}" | tr '.-' '_') - _joker_values=$(_readdomainconf "$_joker_conf_key") - _joker_remaining="" - for _joker_v in $_joker_values; do - if [ "$_joker_v" != "$txtvalue" ]; then - _joker_remaining="$_joker_remaining $_joker_v" - fi - done - _joker_remaining=$(printf "%s" "$_joker_remaining" | sed 's/^ *//') - - _joker_value_params="" - for _joker_v in $_joker_remaining; do - _joker_value_params="$_joker_value_params&value=$_joker_v" - done - if [ -z "$_joker_value_params" ]; then - _joker_value_params="&value=" - fi - _info "Removing TXT record" - # TXT record is removed by replacing the label with the remaining values - # (or an empty value, which clears all TXT records at the label). - if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT$_joker_value_params"; then + # TXT record is removed by setting its value to empty. + if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$_domain&label=$_sub_domain&type=TXT&value="; then if _startswith "$response" "OK"; then - if [ -z "$_joker_remaining" ]; then - _cleardomainconf "$_joker_conf_key" - else - _savedomainconf "$_joker_conf_key" "$_joker_remaining" - fi _info "Removed, OK" return 0 fi @@ -125,7 +93,7 @@ _get_root() { fulldomain=$1 i=1 while true; do - h=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then return 1 diff --git a/dnsapi/dns_kappernet.sh b/dnsapi/dns_kappernet.sh index 762ba8b3..83a7e5f8 100644 --- a/dnsapi/dns_kappernet.sh +++ b/dnsapi/dns_kappernet.sh @@ -1,13 +1,13 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_kappernet_info='kapper.net -Site: kapper.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_kappernet -Options: - KAPPERNETDNS_Key API Key - KAPPERNETDNS_Secret API Secret -Issues: github.com/acmesh-official/acme.sh/issues/2977 -' + +# kapper.net domain api +# for further questions please contact: support@kapper.net +# please report issues here: https://github.com/acmesh-official/acme.sh/issues/2977 + +#KAPPERNETDNS_Key="yourKAPPERNETapikey" +#KAPPERNETDNS_Secret="yourKAPPERNETapisecret" + +KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret" ############################################################################### # called with @@ -19,9 +19,10 @@ dns_kappernet_add() { KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}" KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}" - KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret" if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then + KAPPERNETDNS_Key="" + KAPPERNETDNS_Secret="" _err "Please specify your kapper.net api key and secret." _err "If you have not received yours - send your mail to" _err "support@kapper.net to get your key and secret." @@ -40,12 +41,12 @@ dns_kappernet_add() { _debug _domain "DOMAIN: $_domain" _info "Trying to add TXT DNS Record" - data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D" + data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D" if _kappernet_api GET "action=new&subject=$_domain&data=$data"; then if _contains "$response" "{\"OK\":true"; then - _info "Waiting 1 second for DNS to spread the new record" - _sleep 1 + _info "Waiting 120 seconds for DNS to spread the new record" + _sleep 120 return 0 else _err "Error creating a TXT DNS Record: $fullhostname TXT $txtvalue" @@ -65,9 +66,10 @@ dns_kappernet_rm() { KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}" KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}" - KAPPERNETDNS_Api="https://dnspanel.kapper.net/API/1.2?APIKey=$KAPPERNETDNS_Key&APISecret=$KAPPERNETDNS_Secret" if [ -z "$KAPPERNETDNS_Key" ] || [ -z "$KAPPERNETDNS_Secret" ]; then + KAPPERNETDNS_Key="" + KAPPERNETDNS_Secret="" _err "Please specify your kapper.net api key and secret." _err "If you have not received yours - send your mail to" _err "support@kapper.net to get your key and secret." @@ -79,7 +81,7 @@ dns_kappernet_rm() { _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret" _info "Trying to remove the TXT Record: $fullhostname containing $txtvalue" - data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%22300%22%2C%22prio%22%3A%22%22%7D" + data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%223600%22%2C%22prio%22%3A%22%22%7D" if _kappernet_api GET "action=del&subject=$fullhostname&data=$data"; then if _contains "$response" "{\"OK\":true"; then return 0 @@ -102,7 +104,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -113,7 +115,7 @@ _get_root() { if _contains "$response" '"OK":false'; then _debug "$h not found" else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi @@ -139,7 +141,7 @@ _kappernet_api() { if [ "$method" = "GET" ]; then response="$(_get "$url")" else - _err "Unsupported method or missing Secret/Key" + _err "Unsupported method" return 1 fi diff --git a/dnsapi/dns_kas.sh b/dnsapi/dns_kas.sh index 2164a8e8..053abd21 100755 --- a/dnsapi/dns_kas.sh +++ b/dnsapi/dns_kas.sh @@ -1,16 +1,19 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_kas_info='All-inkl Kas Server -Site: kas.all-inkl.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_kas -Options: - KAS_Login API login name - KAS_Authtype API auth type. Default: "plain" - KAS_Authdata API auth data -Issues: github.com/acmesh-official/acme.sh/issues/2715 -Author: squared GmbH , Martin Kammerlander , Marc-Oliver Lange -' - +######################################################################## +# All-inkl Kasserver hook script for acme.sh +# +# Environment variables: +# +# - $KAS_Login (Kasserver API login name) +# - $KAS_Authtype (Kasserver API auth type. Default: plain) +# - $KAS_Authdata (Kasserver API auth data.) +# +# Last update: squared GmbH +# Credits: +# - dns_he.sh. Thanks a lot man! +# - Martin Kammerlander, Phlegx Systems OG +# - Marc-Oliver Lange +# - https://github.com/o1oo11oo/kasapi.sh ######################################################################## KAS_Api_GET="$(_get "https://kasapi.kasserver.com/soap/wsdl/KasApi.wsdl")" KAS_Api="$(echo "$KAS_Api_GET" | tr -d ' ' | grep -i "//g")" @@ -212,7 +215,7 @@ _get_record_id() { return 1 fi - _record_id="$(echo "$response" | tr -d '\n\r' | sed "s//\n/g" | grep -i "$_record_name" | grep -i ">TXT<" | sed "s/record_id<\/key>/=>/g" | grep -i "$_txtvalue" | sed "s/<\/value><\/item>/\n/g" | grep "=>" | sed "s/=>//g")" + _record_id="$(echo "$response" | tr -d '\n\r' | sed "s//\n/g" | grep -i "$_record_name" | grep -i ">TXT<" | sed "s/record_id<\/key>/=>/g" | sed "s/<\/value><\/item>/\n/g" | grep "=>" | sed "s/=>//g")" _debug "[KAS] -> Record Id: " "$_record_id" return 0 } diff --git a/dnsapi/dns_kinghost.sh b/dnsapi/dns_kinghost.sh index 0496008e..f640242f 100644 --- a/dnsapi/dns_kinghost.sh +++ b/dnsapi/dns_kinghost.sh @@ -1,17 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_kinghost_info='King.host -Domains: KingHost.net KingHost.com.br -Site: King.host -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_kinghost -Options: - KINGHOST_Username Username - KINGHOST_Password Password -Author: Felipe Keller Braz -' +############################################################ # KingHost API support # # https://api.kinghost.net/doc/ # +# # +# Author: Felipe Keller Braz # +# Report Bugs here: https://github.com/kinghost/acme.sh # +# # +# Values to export: # +# export KINGHOST_Username="email@provider.com" # +# export KINGHOST_Password="xxxxxxxxxx" # +############################################################ KING_Api="https://api.kinghost.net/acme" diff --git a/dnsapi/dns_knot.sh b/dnsapi/dns_knot.sh index 2b6d8ef4..729a89cb 100644 --- a/dnsapi/dns_knot.sh +++ b/dnsapi/dns_knot.sh @@ -1,15 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_knot_info='Knot Server knsupdate -Site: www.knot-dns.cz/docs/2.5/html/man_knsupdate.html -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_knot -Options: - KNOT_SERVER Server hostname. Default: "localhost". - KNOT_KEY TSIG key data, not a file path. knsupdate "key" statement format: "[alg:]name secret". E.g. "hmac-sha256:acme_key BASE64SECRET=" - KNOT_ZONE Zone name. Optional, set it when the challenge record lives in a delegated subdomain zone. Default: the parent domain of the challenge record. -' - -# See also dns_nsupdate.sh ######## Public functions ##################### @@ -22,9 +11,6 @@ dns_knot_add() { # save the dns server and key to the account.conf file. _saveaccountconf KNOT_SERVER "${KNOT_SERVER}" _saveaccountconf KNOT_KEY "${KNOT_KEY}" - if [ -n "${KNOT_ZONE}" ]; then - _saveaccountconf KNOT_ZONE "${KNOT_ZONE}" - fi if ! _get_root "$fulldomain"; then _err "Domain does not exist." @@ -88,13 +74,6 @@ EOF # _domain=domain.com _get_root() { domain=$1 - # a delegated subdomain zone cannot be derived from the record name; - # let the user name the zone explicitly (issue 2881) - if [ -n "${KNOT_ZONE}" ]; then - _domain="${KNOT_ZONE%.}" - _debug "Using KNOT_ZONE zone" "${_domain}" - return 0 - fi i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" i=$(_math "$i" - 1) diff --git a/dnsapi/dns_la.sh b/dnsapi/dns_la.sh index 9cb6327e..674df410 100644 --- a/dnsapi/dns_la.sh +++ b/dnsapi/dns_la.sh @@ -1,17 +1,8 @@ #!/usr/bin/env sh -# LA_Id="123" -# LA_Sk="456" -# shellcheck disable=SC2034 -dns_la_info='dns.la -Site: dns.la -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_la -Options: - LA_Id APIID - LA_Sk APISecret - LA_Token 用冒号连接 APIID APISecret 再base64生成 -Issues: github.com/acmesh-official/acme.sh/issues/4257 -' +#LA_Id="test123" +#LA_Key="d1j2fdo4dee3948" + LA_Api="https://api.dns.la/api" ######## Public functions ##################### @@ -22,23 +13,18 @@ dns_la_add() { txtvalue=$2 LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" - LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}" - _log "LA_Id=$LA_Id" - _log "LA_Sk=$LA_Sk" + LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}" - if [ -z "$LA_Id" ] || [ -z "$LA_Sk" ]; then + if [ -z "$LA_Id" ] || [ -z "$LA_Key" ]; then LA_Id="" - LA_Sk="" + 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_Sk "$LA_Sk" - - # generate dnsla token - _la_token + _saveaccountconf_mutable LA_Key "$LA_Key" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -50,13 +36,11 @@ dns_la_add() { _debug _domain "$_domain" _info "Adding record" - - # record type is enum in new api, 16 for TXT - if _la_post "{\"domainId\":\"$_domain_id\",\"type\":16,\"host\":\"$_sub_domain\",\"data\":\"$txtvalue\",\"ttl\":600}" "record"; then - if _contains "$response" '"id":'; then + 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" '"msg":"与已有记录冲突"'; then + elif _contains "$response" '"code":532'; then _info "Already exists, OK" return 0 else @@ -64,7 +48,7 @@ dns_la_add() { return 1 fi fi - _err "Add txt record failed." + _err "Add txt record error." return 1 } @@ -75,9 +59,7 @@ dns_la_rm() { txtvalue=$2 LA_Id="${LA_Id:-$(_readaccountconf_mutable LA_Id)}" - LA_Sk="${LA_Sk:-$(_readaccountconf_mutable LA_Sk)}" - - _la_token + LA_Key="${LA_Key:-$(_readaccountconf_mutable LA_Key)}" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -89,29 +71,27 @@ dns_la_rm() { _debug _domain "$_domain" _debug "Getting txt records" - # record type is enum in new api, 16 for TXT - if ! _la_get "recordList?pageIndex=1&pageSize=10&domainId=$_domain_id&host=$_sub_domain&type=16&data=$txtvalue"; then + 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" '"id":'; then + if ! _contains "$response" '"recordid":'; then _info "Don't need to remove." return 0 fi - record_id=$(printf "%s" "$response" | grep '"id":' | _head_n 1 | sed 's/.*"id": *"\([^"]*\)".*/\1/') + 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 - # remove record in new api is RESTful - if ! _la_post "" "record?id=$record_id" "DELETE"; then + 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":200' + _contains "$response" '"code":300' } @@ -127,21 +107,20 @@ _get_root() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 fi - if ! _la_get "domain?domain=$h"; then + if ! _la_rest "domain.ashx?cmd=get&apiid=$LA_Id&apipass=$LA_Key&rtype=json&domain=$h"; then return 1 fi - if _contains "$response" '"domain":'; then - _domain_id=$(echo "$response" | sed -n 's/.*"id":"\([^"]*\)".*/\1/p') - _log "_domain_id" "$_domain_id" + 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") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi @@ -158,21 +137,6 @@ _la_rest() { url="$LA_Api/$1" _debug "$url" - if ! response="$(_get "$url" "Authorization: Basic $LA_Token" | tr -d ' ' | tr "}" ",")"; then - _err "Error: $url" - return 1 - fi - - _debug2 response "$response" - return 0 -} - -_la_get() { - url="$LA_Api/$1" - _debug "$url" - - export _H1="Authorization: Basic $LA_Token" - if ! response="$(_get "$url" | tr -d ' ' | tr "}" ",")"; then _err "Error: $url" return 1 @@ -181,29 +145,3 @@ _la_get() { _debug2 response "$response" return 0 } - -# Usage: _la_post body url [POST|PUT|DELETE] -_la_post() { - body=$1 - url="$LA_Api/$2" - http_method=$3 - _debug "$body" - _debug "$url" - - export _H1="Authorization: Basic $LA_Token" - - if ! response="$(_post "$body" "$url" "" "$http_method")"; then - _err "Error: $url" - return 1 - fi - - _debug2 response "$response" - return 0 -} - -_la_token() { - LA_Token=$(printf "%s:%s" "$LA_Id" "$LA_Sk" | _base64) - _debug "$LA_Token" - - return 0 -} diff --git a/dnsapi/dns_laodc.sh b/dnsapi/dns_laodc.sh deleted file mode 100644 index 9f2103b3..00000000 --- a/dnsapi/dns_laodc.sh +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_laodc_info='LaoDC DNS API Server -Site: laodc.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_laodc -Options: - LaoDC_Key API Key -Issues: github.com/acmesh-official/acme.sh/issues/6973 -Author: @laodc -' - -# Usage: -# export LaoDC_Key="your-api-key" -# acme.sh --issue --dns dns_laodc -d example.la -d *.example.la --dnssleep 120 -# -# The credentials will be saved in ~/.acme.sh/account.conf - -LAODC_VER="0.1.2" -LAODC_API_ENDPOINT="https://dns.laodc.com/v1" - -######## Public functions ##################### - -# Usage: dns_laodc_add _acme-challenge.example.la ZPXvna6tBhq7XQMH7_t2WC2sg0F-BdmtmmpUJiK6Ho -dns_laodc_add() { - fulldomain=$1 - txtvalue=$2 - - _info "Using LaoDC DNS API" - - _laodc_validate_key || return 1 - - _debug "Checking root zone exists for [$fulldomain]" - if ! _get_root "$fulldomain"; then - _err "Invalid domain" - return 1 - fi - - domain_hash=$(echo "$response" | _egrep_o "\"hash\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - _debug _domain_hash "$domain_hash" - - _info "Adding acme record" - if _laodc_api "POST" "$domain_hash" "$_sub_domain" "$txtvalue"; then - if [ "$_code" = "201" ]; then - _info "Added, OK" - return 0 - else - _err "Add TXT record error, invalid code. Code: $_code" - return 1 - fi - fi - - _err "Add TXT record error." - return 1 -} - -dns_laodc_rm() { - fulldomain=$1 - txtvalue=$2 - - _laodc_validate_key || return 1 - - _debug "Checking root zone exists for [$fulldomain]" - if ! _get_root "$fulldomain"; then - _err "Invalid domain" - return 1 - fi - - domain_hash=$(echo "$response" | _egrep_o "\"hash\":\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \") - _debug _root_domain "$_domain" - _debug _sub_domain "$_sub_domain" - _debug _domain_hash "$domain_hash" - - _info "Deleting acme record" - if _laodc_api "DELETE" "$domain_hash" "$_sub_domain" "$txtvalue"; then - if [ "$_code" = "204" ]; then - _info "Deleted, OK" - return 0 - else - _err "Delete TXT record error, invalid code. Code: $_code" - return 1 - fi - fi - - _err "Delete TXT record error." - return 1 -} - -#################### Private functions below ################################## -# _acme-challenge.www.domain.com -# returns -# _domain=domain.com -# _sub_domain=www -_get_root() { - fqdn=$1 - p=1 - i=1 - - while true; do - h=$(printf "%s" "$fqdn" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - return 1 # not valid domain - fi - - # Check API if domain exists - if _laodc_api "GET" "$h"; then - if [ "$_code" = "200" ]; then - _domain="$h" - - # DNS alias mode - @ is alias for fqdn - _sub_domain=$(printf "%s" "$fqdn" | cut -d . -f 1-"$p") - if [ "$i" = "1" ]; then - _sub_domain="@" - fi - - return 0 - fi - fi - - p="$i" - i=$(_math "$i" + 1) - done - - return 1 -} - -_laodc_validate_key() { - LaoDC_Key="${LaoDC_Key:-$(_readaccountconf_mutable LaoDC_Key)}" - - if [ -z "$LaoDC_Key" ]; then - LaoDC_Key="" - _err "You didn't specify a LaoDC API Key yet." - _err "Please export LaoDC_Key and try again." - return 1 - fi - - # Save the api key to the account conf file. - _saveaccountconf_mutable LaoDC_Key "$LaoDC_Key" -} - -_laodc_api() { - method=$1 - domain=$2 - subdomain=$3 - value=$4 - - export _H1="Content-Type: application/json" - export _H2="User-Agent: acme.sh/$VER laodc-dns-acme-sh/$LAODC_VER" - export _H3="Authorization: Bearer $LaoDC_Key" - - case $method in - GET) - if [ -n "$subdomain" ]; then - response="$(_get "$LAODC_API_ENDPOINT/$domain/$subdomain?type=TXT")" - else - response="$(_get "$LAODC_API_ENDPOINT/$domain")" - fi - ;; - POST) - # Sanitize value input - value=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g') - data="{ \"type\": \"TXT\", \"value\": \"$value\", \"ttl\": \"60\" }" - response="$(_post "$data" "$LAODC_API_ENDPOINT/$domain/$subdomain" "" "POST" "application/json")" - ;; - DELETE) - # Sanitize value input - value=$(printf '%s' "$value" | sed 's/\\/\\\\/g; s/"/\\"/g') - data="{ \"type\": \"TXT\", \"value\": \"$value\" }" - response="$(_post "$data" "$LAODC_API_ENDPOINT/$domain/$subdomain" "" "DELETE" "application/json")" - ;; - esac - - _ret=$? - - # Unset immediately after request to prevent leaks - export _H1= - export _H2= - export _H3= - - if [ "$_ret" != "0" ]; then - _err "Error $domain" - return 1 - fi - - responseHeaders="$(cat "$HTTP_HEADER")" - - if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then - response="$(echo "$response" | _json_decode | _normalizeJson)" - fi - - _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" - - _debug "http response code $_code" - _debug response "$response" - return 0 -} diff --git a/dnsapi/dns_leaseweb.sh b/dnsapi/dns_leaseweb.sh index 66b1f61f..63f81869 100644 --- a/dnsapi/dns_leaseweb.sh +++ b/dnsapi/dns_leaseweb.sh @@ -1,18 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_leaseweb_info='Leaseweb.com -Site: Leaseweb.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_leaseweb -Options: - LSW_Key API Key -Issues: github.com/acmesh-official/acme.sh/issues/2558 -Author: Rolph Haspers -' +#Author: Rolph Haspers +#Utilize leaseweb.com API to finish dns-01 verifications. +#Requires a Leaseweb API Key (export LSW_Key="Your Key") #See https://developer.leaseweb.com for more information. ######## Public functions ##################### -LSW_API="https://api.leaseweb.com/hosting/v2/domains" +LSW_API="https://api.leaseweb.com/hosting/v2/domains/" #Usage: dns_leaseweb_add _acme-challenge.www.domain.com dns_leaseweb_add() { diff --git a/dnsapi/dns_level27.sh b/dnsapi/dns_level27.sh deleted file mode 100644 index 3fbaf810..00000000 --- a/dnsapi/dns_level27.sh +++ /dev/null @@ -1,197 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_level27_info='Level27 -Site: Level27.be -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_level27 -Options: - LEVEL27_API_KEY API key. Get one from the Level27 control panel (https://app.level27.eu/account/profile/security). -OptionsAlt: - LEVEL27_API API base URL. Optional. Default "https://api.level27.eu/v1". -Issues: github.com/acmesh-official/acme.sh/issues -Author: Jeroen Moors -' - -LEVEL27_API_DEFAULT="https://api.level27.eu/v1" - -######## Public functions ##################### - -# Usage: dns_level27_add _acme-challenge.www.example.com "TXT-value" -dns_level27_add() { - fulldomain="$(_idn "$1")" - txtvalue="$2" - - _info "Using Level27 to add a TXT record for $fulldomain" - - if ! _level27_init; then - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "Could not determine the root zone for $fulldomain at Level27." - return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _level27_data="{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\"}" - if ! _level27_rest POST "domains/$_domain_id/records" "$_level27_data"; then - _err "Could not add the TXT record." - return 1 - fi - - if _contains "$response" "\"id\":"; then - _info "TXT record added." - return 0 - fi - - _err "Unexpected response while adding the TXT record." - return 1 -} - -# Usage: dns_level27_rm _acme-challenge.www.example.com "TXT-value" -dns_level27_rm() { - fulldomain="$(_idn "$1")" - txtvalue="$2" - - _info "Using Level27 to remove the TXT record for $fulldomain" - - if ! _level27_init; then - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "Could not determine the root zone for $fulldomain at Level27." - return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - if ! _level27_rest GET "domains/$_domain_id/records?type=TXT"; then - _err "Could not list the existing TXT records." - return 1 - fi - - _record_id="$(_level27_find_record_id "$response" "$txtvalue")" - if [ -z "$_record_id" ]; then - _info "No matching TXT record found; nothing to remove." - return 0 - fi - _debug _record_id "$_record_id" - - if ! _level27_rest DELETE "domains/$_domain_id/records/$_record_id"; then - _err "Could not remove the TXT record." - return 1 - fi - - _info "TXT record removed." - return 0 -} - -#################### Private functions below ################################## - -# Reads and validates the API credentials and endpoint, and stores them for renewals. -_level27_init() { - LEVEL27_API_KEY="${LEVEL27_API_KEY:-$(_readaccountconf_mutable LEVEL27_API_KEY)}" - if [ -z "$LEVEL27_API_KEY" ]; then - LEVEL27_API_KEY="" - _err "You must export the variable LEVEL27_API_KEY before using the Level27 DNS API." - _err "Get an API key from the Level27 control panel (https://app.level27.eu/account/profile/security)." - return 1 - fi - LEVEL27_API_KEY="$(echo "$LEVEL27_API_KEY" | tr -d '"')" - _saveaccountconf_mutable LEVEL27_API_KEY "$LEVEL27_API_KEY" - - LEVEL27_API="${LEVEL27_API:-$(_readaccountconf_mutable LEVEL27_API)}" - if [ -z "$LEVEL27_API" ]; then - LEVEL27_API="$LEVEL27_API_DEFAULT" - fi - _saveaccountconf_mutable LEVEL27_API "$LEVEL27_API" - - # Remove a trailing slash so endpoints can be appended consistently. - LEVEL27_API="$(echo "$LEVEL27_API" | sed 's#/$##')" - return 0 -} - -# Usage: _get_root _acme-challenge.www.example.com -# Splits the full domain into the registered zone and the subdomain part. -# Sets: _domain, _domain_id, _sub_domain -_get_root() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - # not valid - return 1 - fi - - if ! _level27_rest GET "domains?filter=$h"; then - return 1 - fi - - _level27_zones="$(echo "$response" | _normalizeJson)" - if _contains "$_level27_zones" "\"fullname\":\"$h\""; then - _domain_line="$(echo "$_level27_zones" | sed 's/},{/}\n{/g' | grep "\"fullname\":\"$h\"" | _head_n 1)" - _domain_id="$(echo "$_domain_line" | _egrep_o '"id":[0-9]*' | _head_n 1 | cut -d : -f 2)" - 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: _level27_find_record_id "" "" -# Prints the id of the TXT record whose content matches the value, or nothing. -_level27_find_record_id() { - _records="$(echo "$1" | _normalizeJson | sed 's/},{/}\n{/g')" - _wanted="$2" - _record_line="$(echo "$_records" | grep "\"content\":\"$_wanted\"" | _head_n 1)" - if [ -z "$_record_line" ]; then - # Some APIs store TXT content wrapped in quotes. - _record_line="$(echo "$_records" | grep "\"content\":\"\\\\\"$_wanted\\\\\"\"" | _head_n 1)" - fi - if [ -z "$_record_line" ]; then - return 0 - fi - echo "$_record_line" | _egrep_o '"id":[0-9]*' | _head_n 1 | cut -d : -f 2 -} - -# Usage: _level27_rest [data] -# Performs an authenticated API call and stores the body in $response. -_level27_rest() { - m="$1" - ep="$2" - data="$3" - _debug "$ep" - - export _H1="Authorization: $LEVEL27_API_KEY" - export _H2="Content-Type: application/json" - export _H3="Accept: application/json" - - if [ "$m" != "GET" ]; then - _debug2 data "$data" - response="$(_post "$data" "$LEVEL27_API/$ep" "" "$m")" - else - response="$(_get "$LEVEL27_API/$ep")" - fi - - if [ "$?" != "0" ]; then - _err "Error querying the Level27 API endpoint: $ep" - return 1 - fi - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_lexicon.sh b/dnsapi/dns_lexicon.sh index a4b2a801..19702343 100755 --- a/dnsapi/dns_lexicon.sh +++ b/dnsapi/dns_lexicon.sh @@ -1,12 +1,8 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_lexicon_info='Lexicon DNS client -Site: github.com/AnalogJ/lexicon -Docs: github.com/acmesh-official/acme.sh/wiki/How-to-use-lexicon-DNS-API -Options: - PROVIDER Provider -' +# dns api wrapper of lexicon for acme.sh + +# https://github.com/AnalogJ/lexicon lexicon_cmd="lexicon" wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-lexicon-dns-api" diff --git a/dnsapi/dns_limacity.sh b/dnsapi/dns_limacity.sh deleted file mode 100644 index 5734be9e..00000000 --- a/dnsapi/dns_limacity.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_limacity_info='lima-city.de -Site: www.lima-city.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_limacity -Options: - LIMACITY_APIKEY API Key. Note: The API Key must have following roles: dns.admin, domains.reader -Issues: github.com/acmesh-official/acme.sh/issues/4758 -Author: @Laraveluser -' - -######## Public functions ##################### - -LIMACITY_APIKEY="${LIMACITY_APIKEY:-$(_readaccountconf_mutable LIMACITY_APIKEY)}" -AUTH=$(printf "%s" "api:$LIMACITY_APIKEY" | _base64 -w 0) -export _H1="Authorization: Basic $AUTH" -export _H2="Content-Type: application/json" -APIBASE=https://www.lima-city.de/usercp - -#Usage: dns_limacity_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_limacity_add() { - _debug LIMACITY_APIKEY "$LIMACITY_APIKEY" - if [ "$LIMACITY_APIKEY" = "" ]; then - _err "No Credentials given" - return 1 - fi - - # save the dns server and key to the account conf file. - _saveaccountconf_mutable LIMACITY_APIKEY "${LIMACITY_APIKEY}" - - fulldomain=$1 - txtvalue=$2 - if ! _lima_get_domain_id "$fulldomain"; then return 1; fi - - msg=$(_post "{\"nameserver_record\":{\"name\":\"${fulldomain}\",\"type\":\"TXT\",\"content\":\"${txtvalue}\",\"ttl\":60}}" "${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json" "" "POST") - _debug "$msg" - - if [ "$(echo "$msg" | _egrep_o "\"status\":\"ok\"")" = "" ]; then - _err "$msg" - return 1 - fi - - return 0 -} - -#Usage: dns_limacity_rm _acme-challenge.www.domain.com -dns_limacity_rm() { - - fulldomain=$1 - txtvalue=$2 - if ! _lima_get_domain_id "$fulldomain"; then return 1; fi - - for recordId in $(_get "${APIBASE}/domains/${LIMACITY_DOMAINID}/records.json" | _egrep_o "{\"id\":[0-9]*[^}]*,\"name\":\"${fulldomain}\"" | _egrep_o "[0-9]*"); do - _post "" "${APIBASE}/domains/${LIMACITY_DOMAINID}/records/${recordId}" "" "DELETE" - done - - return 0 -} - -#################### Private functions below ################################## - -_lima_get_domain_id() { - domain="$1" - _debug "$domain" - i=2 - p=1 - - domains=$(_get "${APIBASE}/domains.json") - if [ "$(echo "$domains" | _egrep_o "\{.*""domains""")" ]; then - response="$(echo "$domains" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - #not valid - return 1 - fi - - hostedzone="$(echo "$response" | _egrep_o "\{.*""unicode_fqdn""[^,]+""$h"".*\}")" - if [ "$hostedzone" ]; then - LIMACITY_DOMAINID=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) - if [ "$LIMACITY_DOMAINID" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 - fi - return 1 - fi - p=$i - i=$(_math "$i" + 1) - done - fi - return 1 -} diff --git a/dnsapi/dns_linode.sh b/dnsapi/dns_linode.sh new file mode 100755 index 00000000..ead5b164 --- /dev/null +++ b/dnsapi/dns_linode.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +#Author: Philipp Grosswiler + +LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action=" + +######## Public functions ##################### + +#Usage: dns_linode_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_linode_add() { + fulldomain="${1}" + txtvalue="${2}" + + if ! _Linode_API; then + return 1 + fi + + _info "Using Linode" + _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain does not exist." + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue" + + if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _resource_id "$_resource_id" + + if [ -z "$_resource_id" ]; then + _err "Error adding the domain resource." + return 1 + fi + + _info "Domain resource successfully added." + return 0 + fi + + return 1 +} + +#Usage: dns_linode_rm _acme-challenge.www.domain.com +dns_linode_rm() { + fulldomain="${1}" + + if ! _Linode_API; then + return 1 + fi + + _info "Using Linode" + _debug "Calling: dns_linode_rm() '${fulldomain}'" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain does not exist." + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _parameters="&DomainID=$_domain_id" + + if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then + response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" + + resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")" + if [ "$resource" ]; then + _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$_resource_id" ]; then + _debug _resource_id "$_resource_id" + + _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id" + + if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _resource_id "$_resource_id" + + if [ -z "$_resource_id" ]; then + _err "Error deleting the domain resource." + return 1 + fi + + _info "Domain resource successfully deleted." + return 0 + fi + fi + + return 1 + fi + + return 0 + fi + + return 1 +} + +#################### Private functions below ################################## + +_Linode_API() { + if [ -z "$LINODE_API_KEY" ]; then + LINODE_API_KEY="" + + _err "You didn't specify the Linode API key yet." + _err "Please create your key and try again." + + return 1 + fi + + _saveaccountconf LINODE_API_KEY "$LINODE_API_KEY" +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=12345 +_get_root() { + domain=$1 + i=2 + p=1 + + if _rest GET "domain.list"; then + response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")" + if [ "$hostedzone" ]; then + _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + fi + return 1 +} + +#method method action data +_rest() { + mtd="$1" + ep="$2" + data="$3" + + _debug mtd "$mtd" + _debug ep "$ep" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + + if [ "$mtd" != "GET" ]; then + # both POST and DELETE. + _debug data "$data" + response="$(_post "$data" "$LINODE_API_URL$ep" "" "$mtd")" + else + response="$(_get "$LINODE_API_URL$ep$data")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_linode_v4.sh b/dnsapi/dns_linode_v4.sh index 3c6997a0..9504afbf 100755 --- a/dnsapi/dns_linode_v4.sh +++ b/dnsapi/dns_linode_v4.sh @@ -1,12 +1,7 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_linode_v4_info='Linode.com -Site: Linode.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_linode_v4 -Options: - LINODE_V4_API_KEY API Key -Author: Philipp Grosswiler , Aaron W. Swenson -' + +#Original Author: Philipp Grosswiler +#v4 Update Author: Aaron W. Swenson LINODE_V4_API_URL="https://api.linode.com/v4/domains" @@ -76,7 +71,7 @@ dns_linode_v4_rm() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - if _H4="X-Filter: { \"type\": \"TXT\", \"name\": \"$_sub_domain\" }" _rest GET "/$_domain_id/records" && [ -n "$response" ]; then + if _rest GET "/$_domain_id/records" && [ -n "$response" ]; then response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" resource="$(echo "$response" | _egrep_o "\{.*\"name\": *\"$_sub_domain\".*}")" @@ -131,42 +126,34 @@ _Linode_API() { # _domain=domain.com # _domain_id=12345 _get_root() { - full_host_str="$1" - + domain=$1 i=2 p=1 - while true; do - # loop through the received string (e.g. _acme-challenge.sub3.sub2.sub1.domain.tld), - # starting from the lowest subdomain, and check if it's a hosted domain - tst_hosted_domain=$(printf "%s" "$full_host_str" | cut -d . -f "$i"-100) - _debug tst_hosted_domain "$tst_hosted_domain" - if [ -z "$tst_hosted_domain" ]; then - #not valid - _err "Couldn't get domain from string '$full_host_str'." - return 1 - fi - _debug "Querying Linode APIv4 for hosted zone: $tst_hosted_domain" - if _H4="X-Filter: {\"domain\":\"$tst_hosted_domain\"}" _rest GET; then - _debug "Got response from API: $response" - response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" - hostedzone="$(echo "$response" | _egrep_o "\{.*\"domain\": *\"$tst_hosted_domain\".*}")" + if _rest GET; then + response="$(echo "$response" | tr -d "\n" | tr '{' "|" | sed 's/|/&{/g' | tr "|" "\n")" + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "$response" | _egrep_o "\{.*\"domain\": *\"$h\".*}")" if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) - _debug "Found domain hosted on Linode DNS. Zone: $tst_hosted_domain, id: $_domain_id" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$full_host_str" | cut -d . -f 1-"$p") - _domain=$tst_hosted_domain + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h return 0 fi return 1 fi - p=$i i=$(_math "$i" + 1) - fi - done - + done + fi return 1 } diff --git a/dnsapi/dns_loopia.sh b/dnsapi/dns_loopia.sh index 98a4f3ab..399c7867 100644 --- a/dnsapi/dns_loopia.sh +++ b/dnsapi/dns_loopia.sh @@ -1,13 +1,11 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_loopia_info='Loopia.se -Site: Loopia.se -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_loopia -Options: - LOOPIA_Api API URL. E.g. "https://api.loopia./RPCSERV" where the is one of: com, no, rs, se. Default: "se". - LOOPIA_User Username - LOOPIA_Password Password -' + +# +#LOOPIA_User="username" +# +#LOOPIA_Password="password" +# +#LOOPIA_Api="https://api.loopia./RPCSERV" LOOPIA_Api_Default="https://api.loopia.se/RPCSERV" @@ -109,7 +107,7 @@ _loopia_load_config() { fi if _contains "$LOOPIA_Password" "'" || _contains "$LOOPIA_Password" '"'; then - _err "Password contains a quotation mark or double quotation marks and this is not supported by dns_loopia.sh" + _err "Password contains quoute or double quoute and this is not supported by dns_loopia.sh" return 1 fi @@ -180,14 +178,14 @@ _get_root() { response="$(_post "$xml_content" "$LOOPIA_Api" "" "POST")" while true; do - h=$(echo "$domain" | cut -d . -f "$i"-100) + h=$(echo "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 fi if _contains "$response" "$h"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_lua.sh b/dnsapi/dns_lua.sh index 34cce6a1..30c15579 100755 --- a/dnsapi/dns_lua.sh +++ b/dnsapi/dns_lua.sh @@ -1,14 +1,11 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_lua_info='LuaDNS.com -Domains: LuaDNS.net -Site: LuaDNS.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_lua -Options: - LUA_Key API key - LUA_Email Email -Author: -' + +# bug reports to dev@1e.ca + +# +#LUA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#LUA_Email="user@luadns.net" LUA_Api="https://api.luadns.com/v1" @@ -110,7 +107,7 @@ _get_root() { return 1 fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -121,7 +118,7 @@ _get_root() { _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1) _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_maradns.sh b/dnsapi/dns_maradns.sh index 9eefb175..4ff6ca2d 100755 --- a/dnsapi/dns_maradns.sh +++ b/dnsapi/dns_maradns.sh @@ -1,13 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_maradns_info='MaraDNS Server -Site: MaraDNS.samiam.org -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_maradns -Options: - MARA_ZONE_FILE Zone file path. E.g. "/etc/maradns/db.domain.com" - MARA_DUENDE_PID_PATH Duende PID Path. E.g. "/run/maradns/etc_maradns_mararc.pid" -Issues: github.com/acmesh-official/acme.sh/issues/2072 -' #Usage: dns_maradns_add _acme-challenge.www.domain.com "token" dns_maradns_add() { @@ -72,7 +63,7 @@ _reload_maradns() { pidpath="$1" kill -s HUP -- "$(cat "$pidpath")" if [ $? -ne 0 ]; then - _err "Unable to reload MaraDNS, kill returned" + _err "Unable to reload MaraDNS, kill returned $?" return 1 fi } diff --git a/dnsapi/dns_me.sh b/dnsapi/dns_me.sh index 0966c5f1..49007402 100644 --- a/dnsapi/dns_me.sh +++ b/dnsapi/dns_me.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_me_info='DnsMadeEasy.com -Site: DnsMadeEasy.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_me -Options: - ME_Key API Key - ME_Secret API Secret -Author: -' + +# bug reports to dev@1e.ca + +# ME_Key=qmlkdjflmkqdjf +# ME_Secret=qmsdlkqmlksdvnnpae ME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed @@ -53,8 +49,6 @@ dns_me_add() { _info "Added" #todo: check if the record takes effect return 0 - elif printf -- "%s" "$response" | grep -q "already exists"; then - _info "Record already exists, skipping." else _err "Add txt record error." return 1 @@ -109,7 +103,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -122,7 +116,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\""; then _domain_id=$(printf "%s\n" "$response" | sed 's/^{//; s/}$//; s/{.*}//' | sed -r 's/^.*"id":([0-9]+).*$/\1/') if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi @@ -140,7 +134,7 @@ _me_rest() { data="$3" _debug "$ep" - cdate=$(LC_ALL=C date -u +"%a, %d %b %Y %T %Z") + cdate=$(LANG=C date -u +"%a, %d %b %Y %T %Z") hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) export _H1="x-dnsme-apiKey: $ME_Key" diff --git a/dnsapi/dns_mgwm.sh b/dnsapi/dns_mgwm.sh deleted file mode 100644 index 57679127..00000000 --- a/dnsapi/dns_mgwm.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_mgwm_info='mgw-media.de -Site: mgw-media.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mgwm -Options: - MGWM_CUSTOMER Your customer number - MGWM_API_HASH Your API Hash -Issues: github.com/acmesh-official/acme.sh/issues/6669 -' -# Base URL for the mgw-media.de API -MGWM_API_BASE="https://api.mgw-media.de/record" - -######## Public functions ##################### - -# This function is called by acme.sh to add a TXT record. -dns_mgwm_add() { - fulldomain=$1 - txtvalue=$2 - _info "Using mgw-media.de DNS API for domain $fulldomain (add record)" - _debug "fulldomain: $fulldomain" - _debug "txtvalue: $txtvalue" - - # Call the new private function to handle the API request. - # The 'add' action, fulldomain, type 'txt' and txtvalue are passed. - if _mgwm_request "add" "$fulldomain" "txt" "$txtvalue"; then - _info "TXT record for $fulldomain successfully added via mgw-media.de API." - _sleep 10 # Wait briefly for DNS propagation, a common practice in DNS-01 hooks. - return 0 - else - # Error message already logged by _mgwm_request, but a specific one here helps. - _err "mgwm_add: Failed to add TXT record for $fulldomain." - return 1 - fi -} -# This function is called by acme.sh to remove a TXT record after validation. -dns_mgwm_rm() { - fulldomain=$1 - txtvalue=$2 # This txtvalue is now used to identify the specific record to be removed. - _info "Removing TXT record for $fulldomain using mgw-media.de DNS API (remove record)" - _debug "fulldomain: $fulldomain" - _debug "txtvalue: $txtvalue" - - # Call the new private function to handle the API request. - # The 'rm' action, fulldomain, type 'txt' and txtvalue are passed. - if _mgwm_request "rm" "$fulldomain" "txt" "$txtvalue"; then - _info "TXT record for $fulldomain successfully removed via mgw-media.de API." - return 0 - else - # Error message already logged by _mgwm_request, but a specific one here helps. - _err "mgwm_rm: Failed to remove TXT record for $fulldomain." - return 1 - fi -} -#################### Private functions below ################################## - -# _mgwm_request() encapsulates the API call logic, including -# loading credentials, setting the Authorization header, and executing the request. -# Arguments: -# $1: action (e.g., "add", "rm") -# $2: fulldomain -# $3: type (e.g., "txt") -# $4: content (the txtvalue) -_mgwm_request() { - _action="$1" - _fulldomain="$2" - _type="$3" - _content="$4" - - _debug "Calling _mgwm_request for action: $_action, domain: $_fulldomain, type: $_type, content: $_content" - - # Load credentials from environment or acme.sh config - MGWM_CUSTOMER="${MGWM_CUSTOMER:-$(_readaccountconf_mutable MGWM_CUSTOMER)}" - MGWM_API_HASH="${MGWM_API_HASH:-$(_readaccountconf_mutable MGWM_API_HASH)}" - - # Check if credentials are set - if [ -z "$MGWM_CUSTOMER" ] || [ -z "$MGWM_API_HASH" ]; then - _err "You didn't specify one or more of MGWM_CUSTOMER or MGWM_API_HASH." - _err "Please check these environment variables and try again." - return 1 - fi - - # Save credentials for automatic renewal and future calls - _saveaccountconf_mutable MGWM_CUSTOMER "$MGWM_CUSTOMER" - _saveaccountconf_mutable MGWM_API_HASH "$MGWM_API_HASH" - - # Create the Basic Auth Header. acme.sh's _base64 function is used for encoding. - _credentials="$(printf "%s:%s" "$MGWM_CUSTOMER" "$MGWM_API_HASH" | _base64)" - export _H1="Authorization: Basic $_credentials" - _debug "Set Authorization Header: Basic " # Log debug message without sensitive credentials - - # Construct the API URL based on the action and provided parameters. - _request_url="${MGWM_API_BASE}/${_action}/${_fulldomain}/${_type}/${_content}" - _debug "Constructed mgw-media.de API URL for action '$_action': ${_request_url}" - - # Execute the HTTP GET request with the Authorization Header. - # The 5th parameter of _get is where acme.sh expects custom HTTP headers like Authorization. - response="$(_get "$_request_url")" - _debug "mgw-media.de API response for action '$_action': $response" - - # Check the API response for success. The API returns "OK" on success. - if [ "$response" = "OK" ]; then - _info "mgw-media.de API action '$_action' for record '$_fulldomain' successful." - return 0 - else - _err "Failed mgw-media.de API action '$_action' for record '$_fulldomain'. Unexpected API Response: '$response'" - return 1 - fi -} diff --git a/dnsapi/dns_miab.sh b/dnsapi/dns_miab.sh index 0824a4e7..dad69bde 100644 --- a/dnsapi/dns_miab.sh +++ b/dnsapi/dns_miab.sh @@ -1,23 +1,24 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_miab_info='Mail-in-a-Box -Site: MailInaBox.email -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_miab -Options: - MIAB_Username Admin username - MIAB_Password Admin password - MIAB_Server Server hostname. FQDN of your_MIAB Server -Issues: github.com/acmesh-official/acme.sh/issues/2550 -Author: Darven Dissek, William Gertz -' +# Name: dns_miab.sh +# +# Authors: +# Darven Dissek 2018 +# William Gertz 2019 +# +# Thanks to Neil Pang and other developers here for code reused from acme.sh from DNS-01 +# used to communicate with the MailinaBox Custom DNS API +# Report Bugs here: +# https://github.com/billgertz/MIAB_dns_api (for dns_miab.sh) +# https://github.com/acmesh-official/acme.sh (for acme.sh) +# ######## Public functions ##################### #Usage: dns_miab_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_miab_add() { fulldomain=$1 txtvalue=$2 - _info "Using miab challenge add" + _info "Using miab challange add" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" @@ -26,7 +27,7 @@ dns_miab_add() { return 1 fi - #check domain and seperate into domain and host + #check domain and seperate into doamin and host if ! _get_root "$fulldomain"; then _err "Cannot find any part of ${fulldomain} is hosted on ${MIAB_Server}" return 1 @@ -55,7 +56,7 @@ dns_miab_rm() { fulldomain=$1 txtvalue=$2 - _info "Using miab challenge delete" + _info "Using miab challage delete" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" @@ -112,7 +113,7 @@ _get_root() { #cycle through the passed domain seperating out a test domain discarding # the subdomain by marching thorugh the dots while true; do - _test_domain=$(printf "%s" "$_passed_domain" | cut -d . -f "${_i}"-100) + _test_domain=$(printf "%s" "$_passed_domain" | cut -d . -f ${_i}-100) _debug _test_domain "$_test_domain" if [ -z "$_test_domain" ]; then @@ -122,7 +123,7 @@ _get_root() { #report found if the test domain is in the json response and # report the subdomain if _contains "$response" "\"$_test_domain\""; then - _sub_domain=$(printf "%s" "$_passed_domain" | cut -d . -f 1-"${_p}") + _sub_domain=$(printf "%s" "$_passed_domain" | cut -d . -f 1-${_p}) _domain=${_test_domain} return 0 fi diff --git a/dnsapi/dns_mijnhost.sh b/dnsapi/dns_mijnhost.sh deleted file mode 100644 index 9f5e7710..00000000 --- a/dnsapi/dns_mijnhost.sh +++ /dev/null @@ -1,214 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_mijnhost_info='mijn.host -Site: mijn.host -Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mijnhost -Options: - MIJNHOST_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/6177 -Author: @peterv99 -' - -######## Public functions ###################### -MIJNHOST_API="https://mijn.host/api/v2" - -# Add TXT record for domain verification -dns_mijnhost_add() { - fulldomain=$1 - txtvalue=$2 - - MIJNHOST_API_KEY="${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}" - if [ -z "$MIJNHOST_API_KEY" ]; then - MIJNHOST_API_KEY="" - _err "You haven't specified your mijn-host API key yet." - _err "Please add MIJNHOST_API_KEY to the env." - return 1 - fi - - # Save the API key for future use - _saveaccountconf_mutable MIJNHOST_API_KEY "$MIJNHOST_API_KEY" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "Invalid domain" - return 1 - fi - - _debug2 _sub_domain "$_sub_domain" - _debug2 _domain "$_domain" - _debug "Adding DNS record" "${fulldomain}." - - # Construct the API URL - api_url="$MIJNHOST_API/domains/$_domain/dns" - - # Getting previous records - _mijnhost_rest GET "$api_url" "" - - if [ "$_code" != "200" ]; then - _err "Error getting current DNS enties ($_code)" - return 1 - fi - - records=$(echo "$response" | _egrep_o '"records":\[.*\]' | sed 's/"records"://') - - _debug2 "Current records" "$records" - - # Build the payload for the API - data="{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"value\":\"$txtvalue\",\"ttl\":300}" - - _debug2 "Record to add" "$data" - - # Updating the records - updated_records=$(echo "$records" | sed -E "s/\]( *$)/,$data\]/") - - _debug2 "Updated records" "$updated_records" - - # data - data="{\"records\": $updated_records}" - - _mijnhost_rest PUT "$api_url" "$data" - - if [ "$_code" = "200" ]; then - _info "DNS record succesfully added." - return 0 - else - _err "Error adding DNS record ($_code)." - return 1 - fi -} - -# Remove TXT record after verification -dns_mijnhost_rm() { - fulldomain=$1 - txtvalue=$2 - - MIJNHOST_API_KEY="${MIJNHOST_API_KEY:-$(_readaccountconf_mutable MIJNHOST_API_KEY)}" - if [ -z "$MIJNHOST_API_KEY" ]; then - MIJNHOST_API_KEY="" - _err "You haven't specified your mijn-host API key yet." - _err "Please add MIJNHOST_API_KEY to the env." - return 1 - fi - - _debug "Detecting root zone for" "${fulldomain}." - if ! _get_root "$fulldomain"; then - _err "Invalid domain" - return 1 - fi - - _debug "Removing DNS record for TXT value" "${txtvalue}." - - # Construct the API URL - api_url="$MIJNHOST_API/domains/$_domain/dns" - - # Get current records - _mijnhost_rest GET "$api_url" "" - - if [ "$_code" != "200" ]; then - _err "Error getting current DNS enties ($_code)" - return 1 - fi - - _debug2 "Get current records response:" "$response" - - records=$(echo "$response" | _egrep_o '"records":\[.*\]' | sed 's/"records"://') - - _debug2 "Current records:" "$records" - - updated_records=$(echo "$records" | sed -E "s/\{[^}]*\"value\":\"$txtvalue\"[^}]*\},?//g" | sed 's/,]/]/g') - - _debug2 "Updated records:" "$updated_records" - - # Build the new payload - data="{\"records\": $updated_records}" - - # Use the _put method to update the records - _mijnhost_rest PUT "$api_url" "$data" - - if [ "$_code" = "200" ]; then - _info "DNS record removed successfully." - return 0 - else - _err "Error removing DNS record ($_code)." - return 1 - fi -} - -# Helper function to detect the root zone -_get_root() { - domain=$1 - - # Get current records - _debug "Getting current domains" - _mijnhost_rest GET "$MIJNHOST_API/domains" "" - - if [ "$_code" != "200" ]; then - _err "error getting current domains ($_code)" - return 1 - fi - - # Extract root domains from response - rootDomains=$(echo "$response" | _egrep_o '"domain":"[^"]*"' | sed -E 's/"domain":"([^"]*)"/\1/') - _debug "Root domains:" "$rootDomains" - - for rootDomain in $rootDomains; do - if _contains "$domain" "$rootDomain"; then - _domain="$rootDomain" - _sub_domain=$(echo "$domain" | sed "s/.$rootDomain//g") - _debug "Found root domain" "$_domain" "and subdomain" "$_sub_domain" "for" "$domain" - return 0 - fi - done - return 1 -} - -# Helper function for rest calls -_mijnhost_rest() { - m=$1 - ep="$2" - data="$3" - - MAX_REQUEST_RETRY_TIMES=15 - _request_retry_times=0 - _retry_sleep=5 #Initial sleep time in seconds. - - while [ "${_request_retry_times}" -lt "$MAX_REQUEST_RETRY_TIMES" ]; do - _debug2 _request_retry_times "$_request_retry_times" - export _H1="API-Key: $MIJNHOST_API_KEY" - export _H2="Content-Type: application/json" - # clear headers from previous request to avoid getting wrong http code on timeouts - : >"$HTTP_HEADER" - _debug "$ep" - if [ "$m" != "GET" ]; then - _debug2 "data $data" - response="$(_post "$data" "$ep" "" "$m")" - else - response="$(_get "$ep")" - fi - _ret="$?" - _debug2 "response $response" - _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" - _debug "http response code $_code" - if [ "$_code" = "401" ]; then - # we have an invalid API token, maybe it is expired? - _err "Access denied. Invalid API token." - return 1 - fi - - if [ "$_ret" != "0" ] || [ -z "$_code" ] || [ "$_code" = "400" ] || _contains "$response" "DNS records not managed by mijn.host"; then #Sometimes API errors out - _request_retry_times="$(_math "$_request_retry_times" + 1)" - _info "REST call error $_code retrying $ep in ${_retry_sleep}s" - _sleep "$_retry_sleep" - _retry_sleep="$(_math "$_retry_sleep" \* 2)" - continue - fi - break - done - if [ "$_request_retry_times" = "$MAX_REQUEST_RETRY_TIMES" ]; then - _err "Error mijn.host API call was retried $MAX_REQUEST_RETRY_TIMES times." - _err "Calling $ep failed." - return 1 - fi - response="$(echo "$response" | _normalizeJson)" - return 0 -} diff --git a/dnsapi/dns_misaka.sh b/dnsapi/dns_misaka.sh index 50ed4360..36ba5cfd 100755 --- a/dnsapi/dns_misaka.sh +++ b/dnsapi/dns_misaka.sh @@ -1,12 +1,11 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_misaka_info='Misaka.io -Site: Misaka.io -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_misaka -Options: - Misaka_Key API Key -Author: -' + +# bug reports to support+acmesh@misaka.io +# based on dns_nsone.sh by dev@1e.ca + +# +#Misaka_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# Misaka_Api="https://dnsapi.misaka.io/dns" @@ -116,7 +115,7 @@ _get_root() { return 1 fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -124,7 +123,7 @@ _get_root() { fi if _contains "$response" "\"name\":\"$h\""; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_muumuu.sh b/dnsapi/dns_muumuu.sh deleted file mode 100755 index 8ef0b8c8..00000000 --- a/dnsapi/dns_muumuu.sh +++ /dev/null @@ -1,167 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_muumuu_info='muumuu-domain.com -Site: muumuu-domain.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_muumuu -Options: - MUUMUU_PAT Personal Access Token (scopes: domains:read, dns:read, dns:write) -Issues: github.com/acmesh-official/acme.sh/issues/7011 -' - -MUUMUU_API="https://muumuu-domain.com/api/v2" - -######## Public functions ##################### - -dns_muumuu_add() { - fulldomain="$(echo "$1" | _lower_case)" - txtvalue="$2" - - _info "Using muumuu-domain.com DNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - MUUMUU_PAT="${MUUMUU_PAT:-$(_readaccountconf_mutable MUUMUU_PAT)}" - if [ -z "$MUUMUU_PAT" ]; then - _err "MUUMUU_PAT is not set." - _err "Please create a Personal Access Token at https://muumuu-domain.com" - _err "with scopes: domains:read, dns:read, dns:write" - return 1 - fi - _saveaccountconf_mutable MUUMUU_PAT "$MUUMUU_PAT" - - if ! _muumuu_get_root "$fulldomain"; then - _err "Unable to find the root domain for $fulldomain" - return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _info "Adding TXT record for ${fulldomain}" - body="{\"fqdn\":\"${fulldomain}.\",\"type\":\"TXT\",\"value\":\"${txtvalue}\",\"ttl\":3600}" - if _muumuu_rest POST "/me/domains/${_domain_id}/dns-records" "$body"; then - if [ "$_muumuu_code" = "201" ]; then - _info "TXT record added successfully" - return 0 - fi - fi - - _err "Failed to add TXT record (HTTP ${_muumuu_code})" - return 1 -} - -dns_muumuu_rm() { - fulldomain="$(echo "$1" | _lower_case)" - txtvalue="$2" - - _info "Using muumuu-domain.com DNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - MUUMUU_PAT="${MUUMUU_PAT:-$(_readaccountconf_mutable MUUMUU_PAT)}" - if [ -z "$MUUMUU_PAT" ]; then - _err "MUUMUU_PAT is not set." - return 1 - fi - - if ! _muumuu_get_root "$fulldomain"; then - _err "Unable to find the root domain for $fulldomain" - return 1 - fi - _debug _domain_id "$_domain_id" - - _info "Looking up TXT record for ${fulldomain}" - if ! _muumuu_rest GET "/me/domains/${_domain_id}/dns-records?type=TXT&fqdn=${fulldomain}."; then - _err "Failed to list TXT records" - return 1 - fi - - record_id=$(echo "$response" | _egrep_o "\"id\":[0-9]+[^}]*\"value\":\"${txtvalue}\"" | _egrep_o "\"id\":[0-9]+" | _head_n 1 | cut -d: -f2) - if [ -z "$record_id" ]; then - _info "TXT record not found, nothing to remove" - return 0 - fi - _debug record_id "$record_id" - - if _muumuu_rest DELETE "/me/domains/${_domain_id}/dns-records/${record_id}"; then - if [ "$_muumuu_code" = "204" ]; then - _info "TXT record deleted successfully" - return 0 - fi - fi - - _err "Failed to delete TXT record (HTTP ${_muumuu_code})" - return 1 -} - -#################### Private functions below ################################## - -# _acme-challenge.www.example.com -# sets: -# _domain_id MU00000001 -# _sub_domain _acme-challenge.www -# _domain example.com -_muumuu_get_root() { - domain="$1" - i=1 - p=0 - h="" - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - return 1 - fi - if ! _muumuu_rest GET "/me/domains?fqdn=${h}&page-size=1"; then - return 1 - fi - if [ "$_muumuu_code" = "401" ] || [ "$_muumuu_code" = "403" ]; then - _err "Authentication failed (HTTP ${_muumuu_code}). Check MUUMUU_PAT." - return 1 - fi - if _contains "$response" "\"fqdn\":\"${h}\""; then - _domain_id=$(echo "$response" | _egrep_o "\"id\":\"MU[0-9]+\"" | _head_n 1 | cut -d: -f2 | tr -d '"') - _domain="$h" - if [ "$p" = "0" ]; then - _sub_domain="" - else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - fi - return 0 - fi - p="$i" - i=$(_math "$i" + 1) - done -} - -_muumuu_rest() { - _muumuu_method="$1" - _muumuu_path="$2" - _muumuu_data="$3" - _muumuu_url="${MUUMUU_API}${_muumuu_path}" - - export _H1="Authorization: Bearer ${MUUMUU_PAT}" - export _H2="Content-Type: application/json" - export _H3="Accept: application/json" - export _H4="" - export _H5="" - - _secure_debug2 data "$_muumuu_data" - - if [ "$_muumuu_method" = "GET" ]; then - response="$(_get "$_muumuu_url")" - else - response="$(_post "$_muumuu_data" "$_muumuu_url" "" "$_muumuu_method")" - fi - _muumuu_ret="$?" - _muumuu_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" - _debug "HTTP code: ${_muumuu_code}" - _secure_debug2 response "$response" - - if [ "$_muumuu_ret" != "0" ]; then - _err "Error accessing ${_muumuu_url}" - return 1 - fi - - response="$(printf "%s" "$response" | _normalizeJson)" - return 0 -} diff --git a/dnsapi/dns_myapi.sh b/dnsapi/dns_myapi.sh index 101854d5..7f3c5a86 100755 --- a/dnsapi/dns_myapi.sh +++ b/dnsapi/dns_myapi.sh @@ -1,23 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_myapi_info='Custom API Example - A sample custom DNS API script description. -Domains: example.com example.net -Site: github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_myapi -Options: - MYAPI_Token API Token. Get API Token from https://example.com/api/ - MYAPI_Variable2 Option 2. Default "default value". - MYAPI_Variable2 Option 3. Optional. -Issues: github.com/acmesh-official/acme.sh -Author: Neil Pang -' +#Here is a sample custom api script. #This file name is "dns_myapi.sh" #So, here must be a method dns_myapi_add() #Which will be called by acme.sh to add the txt record to your api system. #returns 0 means success, otherwise error. - +# +#Author: Neilpang +#Report Bugs here: https://github.com/acmesh-official/acme.sh +# ######## Public functions ##################### # Please Read this guide first: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Dev-Guide diff --git a/dnsapi/dns_mydevil.sh b/dnsapi/dns_mydevil.sh index e9b3d3c8..953290af 100755 --- a/dnsapi/dns_mydevil.sh +++ b/dnsapi/dns_mydevil.sh @@ -1,16 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_mydevil_info='MyDevil.net - MyDevil.net already supports automatic Lets Encrypt certificates, - except for wildcard domains. - This script depends on devil command that MyDevil.net provides, - which means that it works only on server side. -Site: MyDevil.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydevil -Issues: github.com/acmesh-official/acme.sh/issues/2079 -Author: Marcin Konicki -' +# MyDevil.net API (2019-02-03) +# +# MyDevil.net already supports automatic Let's Encrypt certificates, +# except for wildcard domains. +# +# This script depends on `devil` command that MyDevil.net provides, +# which means that it works only on server side. +# +# Author: Marcin Konicki +# ######## Public functions ##################### #Usage: dns_mydevil_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" diff --git a/dnsapi/dns_mydnsjp.sh b/dnsapi/dns_mydnsjp.sh index 4dfffaaa..13866f70 100755 --- a/dnsapi/dns_mydnsjp.sh +++ b/dnsapi/dns_mydnsjp.sh @@ -1,14 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_mydnsjp_info='MyDNS.JP -Site: MyDNS.JP -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_mydnsjp -Options: - MYDNSJP_MasterID Master ID - MYDNSJP_Password Password -Author: @tkmsst -' +#Here is a api script for MyDNS.JP. +#This file name is "dns_mydnsjp.sh" +#So, here must be a method dns_mydnsjp_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: epgdatacapbon +#Report Bugs here: https://github.com/epgdatacapbon/acme.sh +# ######## Public functions ##################### # Export MyDNS.JP MasterID and Password in following variables... @@ -126,7 +126,7 @@ _get_root() { fi while true; do - _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) + _domain=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) if [ -z "$_domain" ]; then # not valid @@ -134,7 +134,7 @@ _get_root() { fi if [ "$_domain" = "$_root_domain" ]; then - _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-$p) return 0 fi diff --git a/dnsapi/dns_mythic_beasts.sh b/dnsapi/dns_mythic_beasts.sh index a49ab8ab..294ae84c 100755 --- a/dnsapi/dns_mythic_beasts.sh +++ b/dnsapi/dns_mythic_beasts.sh @@ -1,13 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_mythic_beasts_info='Mythic-Beasts.com -Site: Mythic-Beasts.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_mythic_beasts -Options: - MB_AK API Key - MB_AS API Secret -Issues: github.com/acmesh-official/acme.sh/issues/3848 -' # Mythic Beasts is a long-standing UK service provider using standards-based OAuth2 authentication # To test: ./acme.sh --dns dns_mythic_beasts --test --debug 1 --output-insecure --issue --domain domain.com # Cannot retest once cert is issued @@ -107,7 +98,7 @@ _get_root() { _debug "Detect the root zone" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then _err "Domain exhausted" return 1 @@ -118,7 +109,7 @@ _get_root() { _mb_rest GET "$h/records" ret="$?" if [ "$ret" -eq 0 ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" @@ -186,7 +177,7 @@ _oauth2() { _oauth2_std() { # HTTP Basic Authentication _H1="Authorization: Basic $(echo "$MB_AK:$MB_AS" | _base64)" - _H2="Accept: application/json" + _H2="Accepts: application/json" export _H1 _H2 body="grant_type=client_credentials" @@ -210,7 +201,7 @@ _oauth2_std() { } _oauth2_github() { - _H1="Accept: application/json" + _H1="Accepts: application/json" export _H1 body="{\"login\":{\"handle\":\"$MB_AK\",\"pass\":\"$MB_AS\",\"floating\":1}}" @@ -241,7 +232,7 @@ _mb_rest() { fi _H1="Authorization: Bearer $MB_TK" - _H2="Accept: application/json" + _H2="Accepts: application/json" export _H1 _H2 if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ] || [ "$m" = "DELETE" ]; then # body url [needbase64] [POST|PUT|DELETE] [ContentType] diff --git a/dnsapi/dns_namecheap.sh b/dnsapi/dns_namecheap.sh index 7035640b..a5f667a9 100755 --- a/dnsapi/dns_namecheap.sh +++ b/dnsapi/dns_namecheap.sh @@ -1,17 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_namecheap_info='NameCheap.com -Site: NameCheap.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecheap -Options: - NAMECHEAP_API_KEY API Key - NAMECHEAP_USERNAME Username - NAMECHEAP_SOURCEIP Source IP -Issues: github.com/acmesh-official/acme.sh/issues/2107 -' # Namecheap API # https://www.namecheap.com/support/api/intro.aspx +# +# Requires Namecheap API key set in +#NAMECHEAP_API_KEY, +#NAMECHEAP_USERNAME, +#NAMECHEAP_SOURCEIP # Due to Namecheap's API limitation all the records of your domain will be read and re applied, make sure to have a backup of your records you could apply if any issue would arise. ######## Public functions ##################### @@ -104,15 +99,12 @@ _get_root_by_getList() { return 1 fi - _namecheap_domain_list=$(echo "$response" | _egrep_o ']*') - _debug2 domain_list "$_namecheap_domain_list" - i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -123,10 +115,10 @@ _get_root_by_getList() { return 1 fi - if ! _namecheap_is_our_dns "$h"; then + if ! _contains "$response" "$h"; then _debug "$h not found" else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi @@ -136,41 +128,18 @@ _get_root_by_getList() { return 1 } -#Usage: _namecheap_is_our_dns -#Succeeds only when domains.getList listed exactly AND that entry is -#served by Namecheap's own DNS. A domain parked on Namecheap's webhosting DNS -#is listed with IsOurDNS="false", and every dns.getHosts/setHosts call against -#it is refused with error 2030288 "not using proper DNS servers". Accepting -#such a domain as the root zone hides a subdomain that IS delegated to -#Namecheap DNS and that the getHosts probe below would have found. -#https://github.com/acmesh-official/acme.sh/issues/7178 -_namecheap_is_our_dns() { - _namecheap_entry=$(echo "$_namecheap_domain_list" | grep -F " Name=\"$1\"" | _head_n 1) - if [ -z "$_namecheap_entry" ]; then - return 1 - fi - - _namecheap_ourdns=$(echo "$_namecheap_entry" | _egrep_o ' IsOurDNS="[^"]*' | cut -d '"' -f 2) - _debug2 "$1 IsOurDNS" "$_namecheap_ourdns" - - if [ "$_namecheap_ourdns" = "true" ]; then - return 0 - fi - return 1 -} - _get_root_by_getHosts() { i=100 p=99 - while [ "$p" -ne 0 ]; do + while [ $p -ne 0 ]; do - h=$(printf "%s" "$1" | cut -d . -f "$i"-100) + h=$(printf "%s" "$1" | cut -d . -f $i-100) if [ -n "$h" ]; then if _contains "$h" "\\."; then _debug h "$h" if _namecheap_set_tld_sld "$h"; then - _sub_domain=$(printf "%s" "$1" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$1" | cut -d . -f 1-$p) _domain="$h" return 0 else @@ -290,16 +259,8 @@ _set_namecheap_TXT() { _debug hosts "$hosts" if [ -z "$hosts" ]; then - # An empty host list is only acceptable when the API positively confirms - # a successful getHosts reply: setHosts below REPLACES all records, so - # proceeding on a malformed/unparsed response would wipe the whole zone. - # https://github.com/acmesh-official/acme.sh/issues/6963 - if _contains "$response" "Status=\"OK\"" && _contains "$response" "DomainDNSGetHostsResult"; then - _debug "No existing host records, adding the TXT record as the first one" - else - _err "Hosts not found" - return 1 - fi + _err "Hosts not found" + return 1 fi _namecheap_reset_hostList @@ -412,7 +373,7 @@ _namecheap_set_tld_sld() { while true; do - _tld=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _tld=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug tld "$_tld" if [ -z "$_tld" ]; then diff --git a/dnsapi/dns_namecom.sh b/dnsapi/dns_namecom.sh index bd7da0c2..0d5dd2c4 100755 --- a/dnsapi/dns_namecom.sh +++ b/dnsapi/dns_namecom.sh @@ -1,21 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_namecom_info='Name.com -Site: Name.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namecom -Options: - Namecom_Username Username - Namecom_Token API Token -Author: @RaidenII -' +#Author: RaidenII +#Created 06/28/2017 +#Updated 03/01/2018, rewrote to support name.com API v4 +#Utilize name.com API to finish dns-01 verifications. ######## Public functions ##################### Namecom_API="https://api.name.com/v4" #Usage: dns_namecom_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_namecom_add() { - fulldomain=$(_idn "$1") + fulldomain=$1 txtvalue=$2 Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}" @@ -68,7 +63,7 @@ dns_namecom_add() { #Usage: fulldomain txtvalue #Remove the txt record after validation. dns_namecom_rm() { - fulldomain=$(_idn "$1") + fulldomain=$1 txtvalue=$2 Namecom_Username="${Namecom_Username:-$(_readaccountconf_mutable Namecom_Username)}" @@ -153,20 +148,21 @@ _namecom_get_root() { i=2 p=1 - # Probe each candidate with GetDomain (GET /v4/domains/{domainName}) instead - # of listing all domains: the list is paginated at 1000 domains per page, so - # larger accounts never found their domain on the first page. + if ! _namecom_rest GET "domains"; then + return 1 + fi + # Need to exclude the last field (tld) numfields=$(echo "$domain" | _egrep_o "\." | wc -l) - while [ "$i" -le "$numfields" ]; do - host=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + while [ $i -le "$numfields" ]; do + host=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug host "$host" if [ -z "$host" ]; then return 1 fi - if _namecom_rest GET "domains/$host" && _contains "$response" "\"domainName\":\"$host\""; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + if _contains "$response" "$host"; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$host" return 0 fi diff --git a/dnsapi/dns_namesilo.sh b/dnsapi/dns_namesilo.sh index df5871cf..f961d0bd 100755 --- a/dnsapi/dns_namesilo.sh +++ b/dnsapi/dns_namesilo.sh @@ -1,14 +1,8 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_namesilo_info='NameSilo.com -Site: NameSilo.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_namesilo -Options: - Namesilo_Key API Key -Author: @meowthink -' -#Utilize API to finish dns-01 verifications. +#Author: meowthink +#Created 01/14/2017 +#Utilize namesilo.com API to finish dns-01 verifications. Namesilo_API="https://www.namesilo.com/api" @@ -65,7 +59,7 @@ dns_namesilo_rm() { if _namesilo_rest GET "dnsListRecords?version=1&type=xml&key=$Namesilo_Key&domain=$_domain"; then retcode=$(printf "%s\n" "$response" | _egrep_o "300") if [ "$retcode" ]; then - _record_id=$(echo "$response" | _egrep_o "([^<]*)TXT$_sub_domain$txtvalue" | _egrep_o "([^<]*)" | sed -r "s/([^<]*)<\/record_id>/\1/" | tail -n 1) + _record_id=$(echo "$response" | _egrep_o "([^<]*)TXT$fulldomain" | _egrep_o "([^<]*)" | sed -r "s/([^<]*)<\/record_id>/\1/" | tail -n 1) _debug _record_id "$_record_id" if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." @@ -109,15 +103,15 @@ _get_root() { # Need to exclude the last field (tld) numfields=$(echo "$domain" | _egrep_o "\." | wc -l) - while [ "$i" -le "$numfields" ]; do - host=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + while [ $i -le "$numfields" ]; do + host=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug host "$host" if [ -z "$host" ]; then return 1 fi if _contains "$response" ">$host"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$host" return 0 fi diff --git a/dnsapi/dns_nanelo.sh b/dnsapi/dns_nanelo.sh deleted file mode 100644 index 0c42989b..00000000 --- a/dnsapi/dns_nanelo.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nanelo_info='Nanelo.com -Site: Nanelo.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nanelo -Options: - NANELO_TOKEN API Token -Issues: github.com/acmesh-official/acme.sh/issues/4519 -' - -NANELO_API="https://api.nanelo.com/v1/" - -######## Public functions ##################### - -# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_nanelo_add() { - fulldomain=$1 - txtvalue=$2 - - NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}" - if [ -z "$NANELO_TOKEN" ]; then - NANELO_TOKEN="" - _err "You didn't configure a Nanelo API Key yet." - _err "Please set NANELO_TOKEN and try again." - _err "Login to Nanelo.com and go to Settings > API Keys to get a Key" - return 1 - fi - _saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _info "Adding TXT record to ${fulldomain}" - response="$(_post "" "$NANELO_API$NANELO_TOKEN/dns/addrecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}" "" "" "")" - if _contains "${response}" 'success'; then - return 0 - fi - _err "Could not create resource record, please check the logs" - _err "${response}" - return 1 -} - -dns_nanelo_rm() { - fulldomain=$1 - txtvalue=$2 - - NANELO_TOKEN="${NANELO_TOKEN:-$(_readaccountconf_mutable NANELO_TOKEN)}" - if [ -z "$NANELO_TOKEN" ]; then - NANELO_TOKEN="" - _err "You didn't configure a Nanelo API Key yet." - _err "Please set NANELO_TOKEN and try again." - _err "Login to Nanelo.com and go to Settings > API Keys to get a Key" - return 1 - fi - _saveaccountconf_mutable NANELO_TOKEN "$NANELO_TOKEN" - - _debug "First, let's detect the root zone:" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _info "Deleting resource record $fulldomain" - response="$(_post "" "$NANELO_API$NANELO_TOKEN/dns/deleterecord?domain=${_domain}&type=TXT&ttl=60&name=${_sub_domain}&value=${txtvalue}" "" "" "")" - if _contains "${response}" 'success'; then - return 0 - fi - _err "Could not delete resource record, please check the logs" - _err "${response}" - return 1 -} - -#################### Private functions below ################################## -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com - -_get_root() { - fulldomain=$1 - - # Fetch all zones from Nanelo - response="$(_get "$NANELO_API$NANELO_TOKEN/dns/getzones")" || return 1 - - # Extract "zones" array into space-separated list - zones=$(echo "$response" | - tr -d ' \n' | - sed -n 's/.*"zones":\[\([^]]*\)\].*/\1/p' | - tr -d '"' | - tr , ' ') - _debug zones "$zones" - - bestzone="" - for z in $zones; do - case "$fulldomain" in - *."$z" | "$z") - if [ ${#z} -gt ${#bestzone} ]; then - bestzone=$z - fi - ;; - esac - done - - if [ -z "$bestzone" ]; then - _err "No matching zone found for $fulldomain" - return 1 - fi - - _domain="$bestzone" - _sub_domain=$(printf "%s" "$fulldomain" | sed "s/\\.$_domain\$//") - - return 0 -} diff --git a/dnsapi/dns_nederhost.sh b/dnsapi/dns_nederhost.sh index b16c36ec..abaae42b 100755 --- a/dnsapi/dns_nederhost.sh +++ b/dnsapi/dns_nederhost.sh @@ -1,12 +1,6 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nederhost_info='NederHost.nl -Site: NederHost.nl -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nederhost -Options: - NederHost_Key API Key -Issues: github.com/acmesh-official/acme.sh/issues/2089 -' + +#NederHost_Key="sdfgikogfdfghjklkjhgfcdcfghj" NederHost_Api="https://api.nederhost.nl/dns/v1" @@ -88,8 +82,8 @@ _get_root() { i=2 p=1 while true; do - _domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _domain=$(printf "%s" "$domain" | cut -d . -f $i-100) + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _debug _domain "$_domain" if [ -z "$_domain" ]; then #not valid diff --git a/dnsapi/dns_neodigit.sh b/dnsapi/dns_neodigit.sh index a31f8c9b..64ea8786 100644 --- a/dnsapi/dns_neodigit.sh +++ b/dnsapi/dns_neodigit.sh @@ -1,13 +1,13 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_neodigit_info='Neodigit.net -Site: Neodigit.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_neodigit -Options: - NEODIGIT_API_TOKEN API Token -Author: Adrian Almenar -' +# +# NEODIGIT_API_TOKEN="jasdfhklsjadhflnhsausdfas" + +# This is Neodigit.net api wrapper for acme.sh +# +# Author: Adrian Almenar +# Report Bugs here: https://github.com/tecnocratica/acme.sh +# NEODIGIT_API_URL="https://api.neodigit.net/v1" # ######## Public functions ##################### @@ -126,7 +126,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -142,7 +142,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\"" >/dev/null; then _domain_id=$(echo "$response" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d: -f2 | cut -d, -f1) if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_netcup.sh b/dnsapi/dns_netcup.sh index 3b291854..776fa02d 100644 --- a/dnsapi/dns_netcup.sh +++ b/dnsapi/dns_netcup.sh @@ -1,15 +1,5 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_netcup_info='netcup.eu -Domains: netcup.de netcup.net -Site: netcup.eu/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_netcup -Options: - NC_Apikey API Key - NC_Apipw API Password - NC_CID Customer Number -Author: linux-insideDE -' +#developed by linux-insideDE NC_Apikey="${NC_Apikey:-$(_readaccountconf_mutable NC_Apikey)}" NC_Apipw="${NC_Apipw:-$(_readaccountconf_mutable NC_Apipw)}" @@ -19,7 +9,7 @@ client="" dns_netcup_add() { _debug NC_Apikey "$NC_Apikey" - _login + login if [ "$NC_Apikey" = "" ] || [ "$NC_Apipw" = "" ] || [ "$NC_CID" = "" ]; then _err "No Credentials given" return 1 @@ -33,11 +23,9 @@ dns_netcup_add() { exit=$(echo "$fulldomain" | tr -dc '.' | wc -c) exit=$(_math "$exit" + 1) i=$exit - _nc_last=$(_nc_lastlevel "$i") - _nc_found="" while - [ "$exit" -ge "$_nc_last" ] + [ "$exit" -gt 0 ] do tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit") if [ "$(_math "$i" - "$exit")" -eq 0 ]; then @@ -53,23 +41,17 @@ dns_netcup_add() { _err "$msg" return 1 else - _nc_found=1 break fi fi fi exit=$(_math "$exit" - 1) done - if [ -z "$_nc_found" ]; then - _err "$msg" - _nc_nozone "$fulldomain" - return 1 - fi logout } dns_netcup_rm() { - _login + login fulldomain=$1 txtvalue=$2 @@ -78,11 +60,9 @@ dns_netcup_rm() { exit=$(_math "$exit" + 1) i=$exit rec="" - _nc_last=$(_nc_lastlevel "$i") - _nc_found="" while - [ "$exit" -ge "$_nc_last" ] + [ "$exit" -gt 0 ] do tmp=$(echo "$fulldomain" | cut -d'.' -f"$exit") if [ "$(_math "$i" - "$exit")" -eq 0 ]; then @@ -99,18 +79,12 @@ dns_netcup_rm() { _err "$msg" return 1 else - _nc_found=1 break fi fi fi exit=$(_math "$exit" - 1) done - if [ -z "$_nc_found" ]; then - _err "$msg" - _nc_nozone "$fulldomain" - return 1 - fi ida=0000 idv=0001 @@ -141,28 +115,7 @@ dns_netcup_rm() { logout } -# The zone is looked up by walking the challenge name from the right, one -# label at a time. The leftmost label is the challenge prefix, so the full -# name itself can never be a zone: asking netcup for it only returns 4013 -# "Validation Error", which would then mask the real 5028 "zone could not be -# found". Stop one label short, unless the name is too short to have a -# challenge prefix at all (manual invocation). -# levels -_nc_lastlevel() { - if [ "$1" -ge 3 ]; then - echo 2 - else - echo 1 - fi -} - -# fulldomain -_nc_nozone() { - _err "No DNS zone for $1 was found at netcup." - _err "Check that the domain belongs to the account of the configured NC_CID and that its DNS is hosted at netcup." -} - -_login() { +login() { tmp=$(_post "{\"action\": \"login\", \"param\": {\"apikey\": \"$NC_Apikey\", \"apipassword\": \"$NC_Apipw\", \"customernumber\": \"$NC_CID\"}}" "$end" "" "POST") sid=$(echo "$tmp" | tr '{}' '\n' | grep apisessionid | cut -d '"' -f 4) _debug "$tmp" diff --git a/dnsapi/dns_netlify.sh b/dnsapi/dns_netlify.sh index 322f10ad..0e5dc327 100644 --- a/dnsapi/dns_netlify.sh +++ b/dnsapi/dns_netlify.sh @@ -1,12 +1,6 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_netlify_info='Netlify.com -Site: Netlify.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_netlify -Options: - NETLIFY_ACCESS_TOKEN API Token -Issues: github.com/acmesh-official/acme.sh/issues/3088 -' + +#NETLIFY_ACCESS_TOKEN="xxxx" NETLIFY_HOST="api.netlify.com/api/v1/" NETLIFY_URL="https://$NETLIFY_HOST" @@ -55,6 +49,8 @@ dns_netlify_add() { return 1 fi + _err "Not fully implemented!" + return 1 } #Usage: dns_myapi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -93,6 +89,7 @@ dns_netlify_rm() { _err "error removing validation value ($_code)" return 1 fi + return 0 fi return 1 } @@ -108,7 +105,7 @@ _get_root() { _netlify_rest GET "dns_zones" "" "$accesstoken" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug2 "Checking domain: $h" if [ -z "$h" ]; then #not valid @@ -123,7 +120,7 @@ _get_root() { #create the record at the domain apex (@) if only the domain name was provided as --domain-alias _sub_domain="@" else - _sub_domain=$(echo "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(echo "$domain" | cut -d . -f 1-$p) fi _domain=$h return 0 diff --git a/dnsapi/dns_nexdns.sh b/dnsapi/dns_nexdns.sh deleted file mode 100755 index e4447c0e..00000000 --- a/dnsapi/dns_nexdns.sh +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nexdns_info='NexDNS -Site: nexdns.tech -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nexdns -Options: - NEXDNS_Token API token. Can be created at https://nexdns.tech/settings/api-keys - NEXDNS_Api API base url. Default "https://api.nexdns.tech/v1". Optional. -Issues: github.com/acmesh-official/acme.sh/issues/7179 -Author: NexDNS -' - -NEXDNS_Api_Default="https://api.nexdns.tech/v1" - -######## Public functions ##################### - -#Usage: dns_nexdns_add _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_nexdns_add() { - fulldomain=$1 - txtvalue=$2 - - if ! _nexdns_init; then - return 1 - fi - - _saveaccountconf_mutable NEXDNS_Token "$NEXDNS_Token" - if [ "$NEXDNS_Api" != "$NEXDNS_Api_Default" ]; then - _saveaccountconf_mutable NEXDNS_Api "$NEXDNS_Api" - else - _clearaccountconf_mutable NEXDNS_Api - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "Cannot find the zone of $fulldomain in this NexDNS account." - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - _debug _domain_id "$_domain_id" - - _info "Adding the TXT record for $fulldomain" - if ! _nexdns_rest POST "zones/$_domain_id/records" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"ttl\":120}"; then - return 1 - fi - - _info "The TXT record has been added." - return 0 -} - -#Usage: dns_nexdns_rm _acme-challenge.www.example.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_nexdns_rm() { - fulldomain=$1 - txtvalue=$2 - - if ! _nexdns_init; then - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "Cannot find the zone of $fulldomain in this NexDNS account." - return 1 - fi - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - _debug _domain_id "$_domain_id" - - _info "Removing the TXT record for $fulldomain" - if ! _nexdns_rest GET "zones/$_domain_id/records?type=TXT&name=$_sub_domain"; then - return 1 - fi - - #All the challenge records share one name and one type, so the value is the - #only thing that tells them apart. A certificate covering example.com and - #*.example.com puts two of them at the same name at the same time. - _record_id="$(echo "$response" | tr '{' "\n" | grep -- "$txtvalue" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)" - _debug _record_id "$_record_id" - - if [ -z "$_record_id" ]; then - _info "The TXT record is already gone, nothing to remove." - return 0 - fi - - if ! _nexdns_rest DELETE "zones/$_domain_id/records/$_record_id"; then - return 1 - fi - - _info "The TXT record has been removed." - return 0 -} - -#################### Private functions below ################################## - -#Reads the token and the api url, and applies the default url. -_nexdns_init() { - NEXDNS_Token="${NEXDNS_Token:-$(_readaccountconf_mutable NEXDNS_Token)}" - NEXDNS_Api="${NEXDNS_Api:-$(_readaccountconf_mutable NEXDNS_Api)}" - - if [ -z "$NEXDNS_Token" ]; then - _err "You have not set NEXDNS_Token yet." - _err "Create one at https://nexdns.tech/settings/api-keys, on a plan that includes API access, then:" - _err "export NEXDNS_Token=\"your-api-token\"" - return 1 - fi - - if [ -z "$NEXDNS_Api" ]; then - NEXDNS_Api="$NEXDNS_Api_Default" - fi - #A trailing slash would make every request path begin with a double slash. - NEXDNS_Api="$(echo "$NEXDNS_Api" | sed 's|/*$||')" - _debug NEXDNS_Api "$NEXDNS_Api" - - return 0 -} - -#_acme-challenge.www.example.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=example.com -# _domain_id=Zm9vYmFy -_get_root() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - #not valid - return 1 - fi - - if ! _nexdns_rest GET "zones?search=$h&per_page=100"; then - return 1 - fi - - #search matches on a substring, so the page can also hold zones that merely - #contain h. Take the id of the one whose name is exactly h. - _domain_id="$(echo "$response" | tr '{' "\n" | grep "\"name\":\"$h\"" | _egrep_o '"id":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)" - if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 - fi - - p=$i - i=$(_math "$i" + 1) - done -} - -#Usage: _nexdns_rest GET|POST|DELETE path [body] [attempt] -_nexdns_rest() { - m=$1 - ep=$2 - data=$3 - attempt=${4:-1} - _debug "$ep" - - export _H1="Authorization: Bearer $NEXDNS_Token" - export _H2="Content-Type: application/json" - export _H3="Accept: application/json" - - if [ "$m" = "GET" ]; then - response="$(_get "$NEXDNS_Api/$ep")" - else - _debug2 data "$data" - response="$(_post "$data" "$NEXDNS_Api/$ep" "" "$m" "application/json")" - fi - - if [ "$?" != "0" ]; then - _err "error $ep" - return 1 - fi - - #A single certificate costs a handful of requests, but a renewal sweep over - #many of them meets the account's per-minute budget, and that run is - #unattended. Retry-After is treated as a floor: an api may report the time one - #token needs at an average rate and name a second when nothing frees for a - #minute, so the wait grows on its own across attempts. - if [ "$(grep "^HTTP" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" = "429" ]; then - if [ "$attempt" -ge 4 ]; then - _err "$m $ep failed: rate limited, and the wait budget is spent" - return 1 - fi - - _retry_after="$(grep -i "^Retry-After" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d : -f 2 | tr -d " \r\n")" - _backoff="$(_math "$attempt" \* 15)" - #The header may also carry an http date. Anything but a plain count of - #seconds falls through to the backoff rather than being parsed: guessing - #wrong about a date is worse than waiting a known interval, and comparing a - #date numerically would abort the hook outright. - case "$_retry_after" in - "" | *[!0-9]*) _retry_after="$_backoff" ;; - *) - if [ "$_retry_after" -lt "$_backoff" ]; then - _retry_after="$_backoff" - fi - ;; - esac - - #A wait longer than this is a refusal rather than a schedule, and sleeping - #it out would hold the hook for the length of the window. Hand the run back - #instead, so the next cron pass picks it up. - if [ "$_retry_after" -gt 120 ]; then - _err "$m $ep failed: rate limited for ${_retry_after}s, longer than this hook will wait" - return 1 - fi - - _info "Rate limited by the NexDNS API; retrying in $_retry_after seconds." - _sleep "$_retry_after" - - _nexdns_rest "$m" "$ep" "$data" "$(_math "$attempt" + 1)" - return $? - fi - - #Whitespace between a key and its value would defeat every match made on the - #body, here and in the callers. - response="$(echo "$response" | _normalizeJson)" - _debug2 response "$response" - - #The status line decides success, not the body: a delete answers 204 with no - #body at all, and a record whose own content contains "error": would otherwise - #turn a stored value into a reported failure. The body is read only for the - #message once the status says the request was rejected. - _code="$(grep "^HTTP" "$HTTP_HEADER" 2>/dev/null | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" - _debug2 _code "$_code" - case "$_code" in - "" | 2*) - return 0 - ;; - esac - - #A rejected request carries {"error":{"code":..,"message":..}}, so say what the - #api says went wrong. - _message="$(echo "$response" | _egrep_o '"message":"[^"]*"' | _head_n 1 | cut -d '"' -f 4)" - if [ -z "$_message" ]; then - _message="status $_code" - fi - _err "$m $ep failed: $_message" - - return 1 -} diff --git a/dnsapi/dns_nic.sh b/dnsapi/dns_nic.sh index 5f3e7d5d..56170f87 100644 --- a/dnsapi/dns_nic.sh +++ b/dnsapi/dns_nic.sh @@ -1,15 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nic_info='nic.ru -Site: nic.ru -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nic -Options: - NIC_ClientID Client ID - NIC_ClientSecret Client Secret - NIC_Username Username - NIC_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/2547 -' + +# +#NIC_ClientID='0dc0xxxxxxxxxxxxxxxxxxxxxxxxce88' +#NIC_ClientSecret='3LTtxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxnuW8' +#NIC_Username="000000/NIC-D" +#NIC_Password="xxxxxxx" NIC_Api="https://api.nic.ru" @@ -169,7 +164,7 @@ _get_root() { fi if _contains "$_all_domains" "^$h$"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h _service=$(printf "%s" "$response" | grep -m 1 "idn-name=\"$_domain\"" | sed -r "s/.*service=\"(.*)\".*$/\1/") return 0 diff --git a/dnsapi/dns_njalla.sh b/dnsapi/dns_njalla.sh index 6ce51380..e9243288 100644 --- a/dnsapi/dns_njalla.sh +++ b/dnsapi/dns_njalla.sh @@ -1,12 +1,7 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_njalla_info='Njalla -Site: Njal.la -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_njalla -Options: - NJALLA_Token API Token -Issues: github.com/acmesh-official/acme.sh/issues/2913 -' + +# +#NJALLA_Token="sdfsdfsdfljlbjkljlkjsdfoiwje" NJALLA_Api="https://njal.la/api/1/" @@ -98,7 +93,7 @@ dns_njalla_rm() { echo "$records" | while read -r record; do record_name=$(echo "$record" | _egrep_o "\"name\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") record_content=$(echo "$record" | _egrep_o "\"content\":\s?\"[^\"]*\"" | cut -d : -f 2 | tr -d " " | tr -d \") - record_id=$(echo "$record" | _egrep_o "\"id\":\s?\"?[^\",}]*" | cut -d : -f 2 | tr -d " " | tr -d \") + record_id=$(echo "$record" | _egrep_o "\"id\":\s?[0-9]+" | cut -d : -f 2 | tr -d " " | tr -d \") if [ "$_sub_domain" = "$record_name" ]; then if [ "$txtvalue" = "$record_content" ]; then _debug "record_id" "$record_id" @@ -126,7 +121,7 @@ _get_root() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -140,7 +135,7 @@ _get_root() { if _contains "$response" "\"$h\""; then _domain_returned=$(echo "$response" | _egrep_o "\{\"name\": *\"[^\"]*\"" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ") if [ "$_domain_returned" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_nm.sh b/dnsapi/dns_nm.sh index 1f818a29..4dfcc777 100644 --- a/dnsapi/dns_nm.sh +++ b/dnsapi/dns_nm.sh @@ -1,13 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nm_info='NameMaster.de -Site: NameMaster.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_nm -Options: - NM_user API Username - NM_sha256 API Password as SHA256 hash -Author: Thilo Gass -' + +######################################################################## +# https://namemaster.de hook script for acme.sh +# +# Environment variables: +# +# - $NM_user (your namemaster.de API username) +# - $NM_sha256 (your namemaster.de API password_as_sha256hash) +# +# Author: Thilo Gass +# Git repo: https://github.com/ThiloGa/acme.sh #-- dns_nm_add() - Add TXT record -------------------------------------- # Usage: dns_nm_add _acme-challenge.subdomain.domain.com "XyZ123..." diff --git a/dnsapi/dns_nsd.sh b/dnsapi/dns_nsd.sh index 3ddaa98c..0d29a485 100644 --- a/dnsapi/dns_nsd.sh +++ b/dnsapi/dns_nsd.sh @@ -1,13 +1,7 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nsd_info='NLnetLabs NSD Server -Site: github.com/NLnetLabs/nsd -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#nsd -Options: - Nsd_ZoneFile Zone File path. E.g. "/etc/nsd/zones/example.com.zone" - Nsd_Command Command. E.g. "sudo nsd-control reload" -Issues: github.com/acmesh-official/acme.sh/issues/2245 -' + +#Nsd_ZoneFile="/etc/nsd/zones/example.com.zone" +#Nsd_Command="sudo nsd-control reload" # args: fulldomain txtvalue dns_nsd_add() { diff --git a/dnsapi/dns_nsone.sh b/dnsapi/dns_nsone.sh index e1bfa531..9a998341 100644 --- a/dnsapi/dns_nsone.sh +++ b/dnsapi/dns_nsone.sh @@ -1,13 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nsone_info='ns1.com -Domains: ns1.net -Site: ns1.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nsone -Options: - NS1_Key API Key -Author: -' + +# bug reports to dev@1e.ca + +# +#NS1_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# NS1_Api="https://api.nsone.net/v1" @@ -119,7 +116,7 @@ _get_root() { return 1 fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -127,7 +124,7 @@ _get_root() { fi if _contains "$response" "\"zone\":\"$h\""; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh index 8d7fe2c0..cd4b7140 100755 --- a/dnsapi/dns_nsupdate.sh +++ b/dnsapi/dns_nsupdate.sh @@ -1,14 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_nsupdate_info='nsupdate RFC 2136 DynDNS client -Site: bind9.readthedocs.io/en/v9.18.19/manpages.html#nsupdate-dynamic-dns-update-utility -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_nsupdate -Options: - NSUPDATE_SERVER Server hostname. Default: "localhost". - NSUPDATE_SERVER_PORT Server port. Default: "53". - NSUPDATE_KEY File path to TSIG key. Default: "". Optional. - NSUPDATE_ZONE Domain zone to update. Optional. -' ######## Public functions ##################### @@ -20,63 +10,35 @@ dns_nsupdate_add() { NSUPDATE_SERVER_PORT="${NSUPDATE_SERVER_PORT:-$(_readaccountconf_mutable NSUPDATE_SERVER_PORT)}" NSUPDATE_KEY="${NSUPDATE_KEY:-$(_readaccountconf_mutable NSUPDATE_KEY)}" NSUPDATE_ZONE="${NSUPDATE_ZONE:-$(_readaccountconf_mutable NSUPDATE_ZONE)}" - NSUPDATE_OPT="${NSUPDATE_OPT:-$(_readaccountconf_mutable NSUPDATE_OPT)}" + + _checkKeyFile || return 1 # save the dns server and key to the account conf file. _saveaccountconf_mutable NSUPDATE_SERVER "${NSUPDATE_SERVER}" _saveaccountconf_mutable NSUPDATE_SERVER_PORT "${NSUPDATE_SERVER_PORT}" _saveaccountconf_mutable NSUPDATE_KEY "${NSUPDATE_KEY}" _saveaccountconf_mutable NSUPDATE_ZONE "${NSUPDATE_ZONE}" - _saveaccountconf_mutable NSUPDATE_OPT "${NSUPDATE_OPT}" [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost" [ -n "${NSUPDATE_SERVER_PORT}" ] || NSUPDATE_SERVER_PORT=53 - [ -n "${NSUPDATE_KEY}" ] || NSUPDATE_KEY="" - [ -n "${NSUPDATE_OPT}" ] || NSUPDATE_OPT="" - - NSUPDATE_SERVER_LIST=$(printf "%s" "$NSUPDATE_SERVER" | tr ',' ' ') _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_1" ] && nsdebug="-d" [ -n "$DEBUG" ] && [ "$DEBUG" -ge "$DEBUG_LEVEL_2" ] && nsdebug="-D" - - for NS_SERVER in $NSUPDATE_SERVER_LIST; do - _info "Updating DNS server: $NS_SERVER" - - if [ -z "${NSUPDATE_ZONE}" ]; then - #shellcheck disable=SC2086 - if [ -z "${NSUPDATE_KEY}" ]; then - nsupdate $nsdebug $NSUPDATE_OPT < # - https://portal.nexcess.net/api-token # - https://core.thermo.io/api-token # - https://my.futurehosting.com/api-token +# +# Author: Frank Laszlo NW_API_VERSION="0" @@ -154,7 +157,7 @@ _get_root() { _debug response "${response}" while true; do - h=$(printf "%s" "${domain}" | cut -d . -f "$i"-100) + h=$(printf "%s" "${domain}" | cut -d . -f $i-100) _debug h "${h}" if [ -z "${h}" ]; then #not valid @@ -165,7 +168,7 @@ _get_root() { if [ "${hostedzone}" ]; then _zone_id=$(printf "%s\n" "${hostedzone}" | _egrep_o "\"zone_id\": *[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "${_zone_id}" ]; then - _sub_domain=$(printf "%s" "${domain}" | cut -d . -f 1-"${p}") + _sub_domain=$(printf "%s" "${domain}" | cut -d . -f 1-${p}) _domain="${h}" return 0 fi diff --git a/dnsapi/dns_oci.sh b/dnsapi/dns_oci.sh index e1aa3dd9..3b81143f 100644 --- a/dnsapi/dns_oci.sh +++ b/dnsapi/dns_oci.sh @@ -1,19 +1,6 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_oci_info='Oracle Cloud Infrastructure (OCI) - If OCI CLI configuration file ~/.oci/config has a DEFAULT profile then it will be used. -Site: Cloud.Oracle.com -Docs: github.com/acmesh-official/acme.sh/wiki/How-to-use-Oracle-Cloud-Infrastructure-DNS -Options: - OCI_CLI_TENANCY OCID of tenancy that contains the target DNS zone. Optional. - OCI_CLI_USER OCID of user with permission to add/remove records from zones. Optional. - OCI_CLI_REGION Should point to the tenancy home region. Optional. - OCI_CLI_KEY_FILE Path to private API signing key file in PEM format. Optional. - OCI_CLI_KEY The private API signing key in PEM format. Optional. -Issues: github.com/acmesh-official/acme.sh/issues/3540 -Author: Avi Miller -' - +# +# Acme.sh DNS API plugin for Oracle Cloud Infrastructure # Copyright (c) 2021, Oracle and/or its affiliates # # The plugin will automatically use the default profile from an OCI SDK and CLI @@ -115,15 +102,12 @@ _oci_config() { _clearaccountconf_mutable OCI_CLI_PROFILE fi - if [ -z "$OCI_CLI_TENANCY" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then - _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE" - OCI_CLI_TENANCY=$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE") - fi - if [ -z "$OCI_CLI_TENANCY" ]; then - OCI_CLI_TENANCY=$(_readaccountconf_mutable OCI_CLI_TENANCY) - fi + OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readaccountconf_mutable OCI_CLI_TENANCY)}" if [ "$OCI_CLI_TENANCY" ]; then _saveaccountconf_mutable OCI_CLI_TENANCY "$OCI_CLI_TENANCY" + elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then + _debug "Reading OCI_CLI_TENANCY value from: $OCI_CLI_CONFIG_FILE" + OCI_CLI_TENANCY="${OCI_CLI_TENANCY:-$(_readini "$OCI_CLI_CONFIG_FILE" tenancy "$OCI_CLI_PROFILE")}" fi if [ -z "$OCI_CLI_TENANCY" ]; then @@ -131,47 +115,41 @@ _oci_config() { return 1 fi - if [ -z "$OCI_CLI_USER" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then - _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE" - OCI_CLI_USER=$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE") - fi - if [ -z "$OCI_CLI_USER" ]; then - OCI_CLI_USER=$(_readaccountconf_mutable OCI_CLI_USER) - fi + OCI_CLI_USER="${OCI_CLI_USER:-$(_readaccountconf_mutable OCI_CLI_USER)}" if [ "$OCI_CLI_USER" ]; then _saveaccountconf_mutable OCI_CLI_USER "$OCI_CLI_USER" + elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then + _debug "Reading OCI_CLI_USER value from: $OCI_CLI_CONFIG_FILE" + OCI_CLI_USER="${OCI_CLI_USER:-$(_readini "$OCI_CLI_CONFIG_FILE" user "$OCI_CLI_PROFILE")}" fi if [ -z "$OCI_CLI_USER" ]; then _err "Error: unable to read OCI_CLI_USER from config file or environment variable." return 1 fi - if [ -z "$OCI_CLI_REGION" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then - _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE" - OCI_CLI_REGION=$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE") - fi - if [ -z "$OCI_CLI_REGION" ]; then - OCI_CLI_REGION=$(_readaccountconf_mutable OCI_CLI_REGION) - fi + OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readaccountconf_mutable OCI_CLI_REGION)}" if [ "$OCI_CLI_REGION" ]; then _saveaccountconf_mutable OCI_CLI_REGION "$OCI_CLI_REGION" + elif [ -f "$OCI_CLI_CONFIG_FILE" ]; then + _debug "Reading OCI_CLI_REGION value from: $OCI_CLI_CONFIG_FILE" + OCI_CLI_REGION="${OCI_CLI_REGION:-$(_readini "$OCI_CLI_CONFIG_FILE" region "$OCI_CLI_PROFILE")}" fi if [ -z "$OCI_CLI_REGION" ]; then _err "Error: unable to read OCI_CLI_REGION from config file or environment variable." return 1 fi - if [ -z "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_CONFIG_FILE" ]; then - OCI_CLI_KEY_FILE=$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE") - fi - if [ "$OCI_CLI_KEY" ]; then - _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY" - elif [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then - _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE" - OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE") - _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY" + OCI_CLI_KEY="${OCI_CLI_KEY:-$(_readaccountconf_mutable OCI_CLI_KEY)}" + if [ -z "$OCI_CLI_KEY" ]; then + _clearaccountconf_mutable OCI_CLI_KEY + OCI_CLI_KEY_FILE="${OCI_CLI_KEY_FILE:-$(_readini "$OCI_CLI_CONFIG_FILE" key_file "$OCI_CLI_PROFILE")}" + if [ "$OCI_CLI_KEY_FILE" ] && [ -f "$OCI_CLI_KEY_FILE" ]; then + _debug "Reading OCI_CLI_KEY value from: $OCI_CLI_KEY_FILE" + OCI_CLI_KEY=$(_base64 <"$OCI_CLI_KEY_FILE") + _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY" + fi else - OCI_CLI_KEY=$(_readaccountconf_mutable OCI_CLI_KEY) + _saveaccountconf_mutable OCI_CLI_KEY "$OCI_CLI_KEY" fi if [ -z "$OCI_CLI_KEY_FILE" ] && [ -z "$OCI_CLI_KEY" ]; then @@ -199,7 +177,7 @@ _get_zone() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then # not valid @@ -208,7 +186,7 @@ _get_zone() { _domain_id=$(_signed_request "GET" "/20180115/zones/$h" "" "id") if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h _debug _domain_id "$_domain_id" diff --git a/dnsapi/dns_omglol.sh b/dnsapi/dns_omglol.sh deleted file mode 100644 index fd38d046..00000000 --- a/dnsapi/dns_omglol.sh +++ /dev/null @@ -1,422 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_omglol_info='omg.lol -Site: omg.lol -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_omglol -Options: - OMG_ApiKey - API Key. This is accessible from the bottom of the account page at https://home.omg.lol/account - OMG_Address - Address. This is your omg.lol address, without the preceding @ - you can see your list on your dashboard at https://home.omg.lol/dashboard -Issues: github.com/acmesh-official/acme.sh/issues/5299 -Author: @Kholin -' - -# See API Docs https://api.omg.lol/ - -######## Public functions ##################### - -#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_omglol_add() { - fulldomain=$1 - txtvalue=$2 - OMG_ApiKey="${OMG_ApiKey:-$(_readaccountconf_mutable OMG_ApiKey)}" - OMG_Address="${OMG_Address:-$(_readaccountconf_mutable OMG_Address)}" - - # As omg.lol includes a leading @ for their addresses, pre-strip this before save - OMG_Address="$(echo "$OMG_Address" | tr -d '@')" - - _saveaccountconf_mutable OMG_ApiKey "$OMG_ApiKey" - _saveaccountconf_mutable OMG_Address "$OMG_Address" - - _info "Using omg.lol." - _debug "Function" "dns_omglol_add()" - _debug "Full Domain Name" "$fulldomain" - _debug "txt Record Value" "$txtvalue" - _secure_debug "omg.lol API key" "$OMG_ApiKey" - _debug "omg.lol Address" "$OMG_Address" - - omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" - if [ 1 = $? ]; then - return 1 - fi - - dnsName=$(_getDnsRecordName "$fulldomain" "$OMG_Address") - authHeader="$(_createAuthHeader "$OMG_ApiKey")" - - _debug2 "dns_omglol_add(): Address" "$dnsName" - - omg_add "$OMG_Address" "$authHeader" "$dnsName" "$txtvalue" - -} - -#Usage: fulldomain txtvalue -#Remove the txt record after validation. -dns_omglol_rm() { - fulldomain=$1 - txtvalue=$2 - OMG_ApiKey="${OMG_ApiKey:-$(_readaccountconf_mutable OMG_ApiKey)}" - OMG_Address="${OMG_Address:-$(_readaccountconf_mutable OMG_Address)}" - - # As omg.lol includes a leading @ for their addresses, strip this in case provided - OMG_Address="$(echo "$OMG_Address" | tr -d '@')" - - _info "Using omg.lol" - _debug "Function" "dns_omglol_rm()" - _debug "Full Domain Name" "$fulldomain" - _debug "txt Record Value" "$txtvalue" - _secure_debug "omg.lol API key" "$OMG_ApiKey" - _debug "omg.lol Address" "$OMG_Address" - - omg_validate "$OMG_ApiKey" "$OMG_Address" "$fulldomain" - if [ 1 = $? ]; then - return 1 - fi - - dnsName=$(_getDnsRecordName "$fulldomain" "$OMG_Address") - authHeader="$(_createAuthHeader "$OMG_ApiKey")" - - omg_delete "$OMG_Address" "$authHeader" "$dnsName" "$txtvalue" -} - -#################### Private functions below ################################## -# Check that the minimum requirements are present. Close ungracefully if not -omg_validate() { - omg_apikey=$1 - omg_address=$2 - fulldomain=$3 - - _debug2 "Function" "dns_validate()" - _secure_debug2 "omg.lol API key" "$omg_apikey" - _debug2 "omg.lol Address" "$omg_address" - _debug2 "Full Domain Name" "$fulldomain" - - if [ "" = "$omg_address" ]; then - _err "omg.lol base address not provided. Exiting" - return 1 - fi - - if [ "" = "$omg_apikey" ]; then - _err "omg.lol API key not provided. Exiting" - return 1 - fi - - _endswith "$fulldomain" "omg.lol" - if [ 1 = $? ]; then - _err "Domain name requested is not under omg.lol" - return 1 - fi - - _endswith "$fulldomain" "$omg_address.omg.lol" - if [ 1 = $? ]; then - _err "Domain name is not a subdomain of provided omg.lol address $omg_address" - return 1 - fi - - omg_testconnect "$omg_apikey" "$omg_address" - if [ 1 = $? ]; then - _err "Authentication to omg.lol for address $omg_address using provided API key failed" - return 1 - fi - - _debug "Required environment parameters are all present and validated" -} - -# Validate that the address and API key are both correct and associated to each other -omg_testconnect() { - omg_apikey=$1 - omg_address=$2 - - _debug2 "Function" "omg_testconnect" - _secure_debug2 "omg.lol API key" "$omg_apikey" - _debug2 "omg.lol Address" "$omg_address" - - authheader="$(_createAuthHeader "$omg_apikey")" - export _H1="$authheader" - endpoint="https://api.omg.lol/address/$omg_address/info" - _debug2 "Endpoint for validation" "$endpoint" - - response=$(_get "$endpoint" "" 30) - - _jsonResponseCheck "$response" "status_code" 200 - if [ 1 = $? ]; then - _debug2 "Failed to query omg.lol for $omg_address with provided API key" - _secure_debug2 "API Key" "omg_apikey" - _secure_debug3 "Raw response" "$response" - return 1 - fi -} - -# Add (or modify) an entry for a new ACME query -omg_add() { - address=$1 - authHeader=$2 - dnsName=$3 - txtvalue=$4 - - _info "Creating DNS entry for $dnsName" - _debug2 "omg_add()" - _debug2 "omg.lol Address: " "$address" - _secure_debug2 "omg.lol authorization header: " "$authHeader" - _debug2 "Full Domain name:" "$dnsName.$address.omg.lol" - _debug2 "TXT value to set:" "$txtvalue" - - export _H1="$authHeader" - - endpoint="https://api.omg.lol/address/$address/dns" - _debug2 "Endpoint" "$endpoint" - - payload='{"type": "TXT", "name":"'"$dnsName"'", "data":"'"$txtvalue"'", "ttl":30}' - _debug2 "Payload" "$payload" - - response=$(_post "$payload" "$endpoint" "" "POST" "application/json") - - omg_validate_add "$response" "$dnsName.$address" "$txtvalue" -} - -omg_validate_add() { - response=$1 - name=$2 - content=$3 - - _debug "Validating DNS record addition" - _debug2 "omg_validate_add()" - _debug2 "Response" "$response" - _debug2 "DNS Name" "$name" - _debug2 "DNS value" "$content" - - _jsonResponseCheck "$response" "success" "true" - if [ "1" = "$?" ]; then - _err "Response did not report success" - return 1 - fi - - _jsonResponseCheck "$response" "message" "Your DNS record was created successfully." - if [ "1" = "$?" ]; then - _err "Response message did not indicate DNS record was successfully created" - return 1 - fi - - _jsonResponseCheck "$response" "name" "$name" - if [ "1" = "$?" ]; then - _err "Response DNS Name did not match the response received" - return 1 - fi - - _jsonResponseCheck "$response" "content" "$content" - if [ "1" = "$?" ]; then - _err "Response DNS Name did not match the response received" - return 1 - fi - - _info "Record Created successfully" - return 0 -} - -omg_getRecords() { - address=$1 - authHeader=$2 - dnsName=$3 - txtValue=$4 - - _debug2 "omg_getRecords()" - _debug2 "omg.lol Address: " "$address" - _secure_debug2 "omg.lol Auth Header: " "$authHeader" - _debug2 "omg.lol DNS name:" "$dnsName" - _debug2 "txt Value" "$txtValue" - - export _H1="$authHeader" - - endpoint="https://api.omg.lol/address/$address/dns" - _debug2 "Endpoint" "$endpoint" - - payload=$(_get "$endpoint") - - _debug2 "Received Payload:" "$payload" - - # Reformat the JSON to be more parseable - recordID=$(echo "$payload" | _stripWhitespace) - recordID=$(echo "$recordID" | _exposeJsonArray) - - # Now find the one with the right value, and caputre its ID - recordID=$(echo "$recordID" | grep -- "$txtValue" | grep -i -- "$dnsName.$address") - _getJsonElement "$recordID" "id" -} - -omg_delete() { - address=$1 - authHeader=$2 - dnsName=$3 - txtValue=$4 - - _info "Deleting DNS entry for $dnsName with value $txtValue" - _debug2 "omg_delete()" - _debug2 "omg.lol Address: " "$address" - _secure_debug2 "omg.lol Auth Header: " "$authHeader" - _debug2 "Full Domain name:" "$dnsName.$address.omg.lol" - _debug2 "txt Value" "$txtValue" - - record=$(omg_getRecords "$address" "$authHeader" "$dnsName" "$txtvalue") - if [ "" = "$record" ]; then - _err "DNS record $address not found!" - return 1 - fi - - endpoint="https://api.omg.lol/address/$address/dns/$record" - _debug2 "Endpoint" "$endpoint" - - export _H1="$authHeader" - output=$(_post "" "$endpoint" "" "DELETE") - - _debug2 "Response" "$output" - - omg_validate_delete "$output" -} - -# Validate the response on request to delete. -# Confirm status is success and message indicates deletion was successful. -# Input: Response - HTTP response received from delete request -omg_validate_delete() { - response=$1 - - _info "Validating DNS record deletion" - _debug2 "omg_validate_delete()" - _debug2 "Response" "$response" - - _jsonResponseCheck "$output" "success" "true" - if [ "1" = "$?" ]; then - _err "Response did not report success" - return 1 - fi - - _jsonResponseCheck "$output" "message" "OK, your DNS record has been deleted." - if [ "1" = "$?" ]; then - _err "Response message did not indicate DNS record was successfully deleted" - return 1 - fi - - _info "Record deleted successfully" - return 0 -} - -########## Utility Functions ##################################### -# All utility functions only log at debug3 -_jsonResponseCheck() { - response=$1 - field=$2 - correct=$3 - - correct=$(echo "$correct" | _lower_case) - - _debug3 "jsonResponseCheck()" - _debug3 "Response to parse" "$response" - _debug3 "Field to get response from" "$field" - _debug3 "What is the correct response" "$correct" - - responseValue=$(_jsonGetLastResponse "$response" "$field") - - if [ "$responseValue" != "$correct" ]; then - _debug3 "Expected: $correct" - _debug3 "Actual: $responseValue" - return 1 - else - _debug3 "Matched: $responseValue" - fi - return 0 -} - -_jsonGetLastResponse() { - response=$1 - field=$2 - - _debug3 "jsonGetLastResponse()" - _debug3 "Response provided" "$response" - _debug3 "Field to get responses for" "$field" - - responseValue=$(echo "$response" | grep -- "\"$field\"" | cut -f2 -d":") - - _debug3 "Response lines found:" "$responseValue" - - responseValue=$(echo "$responseValue" | sed 's/^ //g' | sed 's/^"//g' | sed 's/\\"//g') - responseValue=$(echo "$responseValue" | sed 's/,$//g' | sed 's/"$//g') - responseValue=$(echo "$responseValue" | _lower_case) - - _debug3 "Responses found" "$responseValue" - _debug3 "Response Selected" "$(echo "$responseValue" | tail -1)" - - echo "$responseValue" | tail -1 -} - -_stripWhitespace() { - tr -d '\n' | tr -d '\r' | tr -d '\t' | sed -r 's/ +/ /g' | sed 's/\\"//g' -} - -_exposeJsonArray() { - sed -r 's/.*\[//g' | tr '}' '|' | tr '{' '|' | sed 's/|, |/|/g' | tr '|' '\n' -} - -_getJsonElement() { - content=$1 - field=$2 - - _debug3 "_getJsonElement()" - _debug3 "Input JSON element" "$content" - _debug3 "JSON element to isolate" "$field" - - # With a single JSON entry to parse, convert commas to newlines puts each element on - # its own line - which then allows us to just grep teh name, remove the key, and - # isolate the value - output=$(echo "$content" | tr ',' '\n' | grep -- "\"$field\":" | sed 's/.*: //g') - - _debug3 "String before unquoting: $output" - - _unquoteString "$output" -} - -_createAuthHeader() { - apikey=$1 - - _debug3 "_createAuthHeader()" - _secure_debug3 "Provided API Key" "$apikey" - - authheader="Authorization: Bearer $apikey" - _secure_debug3 "Authorization Header" "$authheader" - echo "$authheader" -} - -_getDnsRecordName() { - fqdn=$1 - address=$2 - - _debug3 "_getDnsRecordName()" - _debug3 "FQDN" "$fqdn" - _debug3 "omg.lol Address" "$address" - - echo "$fqdn" | sed 's/\.omg\.lol//g' | sed 's/\.'"$address"'$//g' -} - -_unquoteString() { - output=$1 - quotes=0 - - _debug3 "_unquoteString()" - _debug3 "Possibly quoted string" "$output" - - _startswith "$output" "\"" - if [ $? ]; then - quotes=$((quotes + 1)) - fi - - _endswith "$output" "\"" - if [ $? ]; then - quotes=$((quotes + 1)) - fi - - _debug3 "Original String: $output" - _debug3 "Quotes found: $quotes" - - if [ $((quotes)) -gt 1 ]; then - output=$(echo "$output" | sed 's/^"//g' | sed 's/"$//g') - _debug3 "Quotes removed: $output" - fi - - echo "$output" -} diff --git a/dnsapi/dns_one.sh b/dnsapi/dns_one.sh index d258ecc1..1565b767 100644 --- a/dnsapi/dns_one.sh +++ b/dnsapi/dns_one.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_one_info='one.com -Site: one.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_one -Options: - ONECOM_User Username - ONECOM_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/2103 -' +# one.com ui wrapper for acme.sh + +# +# export ONECOM_User="username" +# export ONECOM_Password="password" dns_one_add() { fulldomain=$1 @@ -94,7 +90,7 @@ _get_root() { i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid @@ -104,7 +100,7 @@ _get_root() { response="$(_get "https://www.one.com/admin/api/domains/$h/dns/custom_records")" if ! _contains "$response" "CRMRST_000302"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_online.sh b/dnsapi/dns_online.sh index 7ec27d71..9158c268 100755 --- a/dnsapi/dns_online.sh +++ b/dnsapi/dns_online.sh @@ -1,16 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_online_info='online.net -Domains: scaleway.com -Site: online.net -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_online -Options: - ONLINE_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/2093 -' # Online API # https://console.online.net/en/api/ +# +# Requires Online API key set in ONLINE_API_KEY ######## Public functions ##################### @@ -124,7 +117,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -133,7 +126,7 @@ _get_root() { _online_rest GET "domain/$h/version/active" if ! _contains "$response" "Domain not found" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" _real_dns_version=$(echo "$response" | _egrep_o '"uuid_ref":.*' | cut -d ':' -f 2 | cut -d '"' -f 2) return 0 diff --git a/dnsapi/dns_openprovider.sh b/dnsapi/dns_openprovider.sh index 2dec9934..0a9e5ade 100755 --- a/dnsapi/dns_openprovider.sh +++ b/dnsapi/dns_openprovider.sh @@ -1,15 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_openprovider_info='OpenProvider.eu -Site: OpenProvider.eu -Domains: OpenProvider.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_openprovider -Options: - OPENPROVIDER_USER Username - OPENPROVIDER_PASSWORDHASH Password hash -Issues: github.com/acmesh-official/acme.sh/issues/2104 -Author: Sylvia van Os -' + +# This is the OpenProvider API wrapper for acme.sh +# +# Author: Sylvia van Os +# Report Bugs here: https://github.com/acmesh-official/acme.sh/issues/2104 +# +# export OPENPROVIDER_USER="username" +# export OPENPROVIDER_PASSWORDHASH="hashed_password" +# +# Usage: +# acme.sh --issue --dns dns_openprovider -d example.com OPENPROVIDER_API="https://api.openprovider.eu/" #OPENPROVIDER_API="https://api.cte.openprovider.eu/" # Test API @@ -69,7 +69,7 @@ dns_openprovider_add() { new_item="$(echo "$item" | sed -n 's/.*.*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/\2<\/name>\3\4\5\6<\/item>/p')" fi - if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then + if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then _debug "not an allowed record type, skipping" "$new_item" continue fi @@ -153,7 +153,7 @@ dns_openprovider_rm() { new_item="$(echo "$item" | sed -n 's/.*.*\(\(.*\)'"$_domain_name"'\.'"$_domain_extension"'<\/name>.*\(.*<\/type>\).*\(.*<\/value>\).*\(.*<\/prio>\).*\(.*<\/ttl>\)\).*<\/item>.*/\2<\/name>\3\4\5\6<\/item>/p')" fi - if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA)<\/type>.*")" ]; then + if [ -z "$(echo "$new_item" | _egrep_o ".*(A|AAAA|CNAME|MX|SPF|SRV|TXT|TLSA|SSHFP|CAA|NS)<\/type>.*")" ]; then _debug "not an allowed record type, skipping" "$new_item" continue fi @@ -187,7 +187,7 @@ _get_root() { results_retrieved=0 while true; do - h=$(echo "$domain" | cut -d . -f "$i"-100) + h=$(echo "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid diff --git a/dnsapi/dns_openprovider_rest.sh b/dnsapi/dns_openprovider_rest.sh deleted file mode 100644 index 210dc6fc..00000000 --- a/dnsapi/dns_openprovider_rest.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_openprovider_rest_info='OpenProvider (REST) -Domains: OpenProvider.com -Site: OpenProvider.eu -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openprovider_rest -Options: - OPENPROVIDER_REST_USERNAME Openprovider Account Username - OPENPROVIDER_REST_PASSWORD Openprovider Account Password -Issues: github.com/acmesh-official/acme.sh/issues/6122 -Author: Lambiek12 -' - -OPENPROVIDER_API_URL="https://api.openprovider.eu/v1beta" - -######## Public functions ##################### - -# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -# Used to add txt record -dns_openprovider_rest_add() { - fulldomain=$1 - txtvalue=$2 - - _openprovider_prepare_credentials || return 1 - - _debug "Try fetch OpenProvider DNS zone details" - if ! _get_dns_zone "$fulldomain"; then - _err "DNS zone not found within configured OpenProvider account." - return 1 - fi - - if [ -n "$_domain_id" ]; then - addzonerecordrequestparameters="dns/zones/$_domain_name" - addzonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"add\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"$txtvalue\"}]}}" - - if _openprovider_rest PUT "$addzonerecordrequestparameters" "$addzonerecordrequestbody"; then - if _contains "$response" "\"success\":true"; then - return 0 - elif _contains "$response" "\"Duplicate record\""; then - _debug "Record already existed" - return 0 - else - _err "Adding TXT record failed due to errors." - return 1 - fi - fi - fi - - _err "Adding TXT record failed due to errors." - return 1 -} - -# Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -# Used to remove the txt record after validation -dns_openprovider_rest_rm() { - fulldomain=$1 - txtvalue=$2 - - _openprovider_prepare_credentials || return 1 - - _debug "Try fetch OpenProvider DNS zone details" - if ! _get_dns_zone "$fulldomain"; then - _err "DNS zone not found within configured OpenProvider account." - return 1 - fi - - if [ -n "$_domain_id" ]; then - removezonerecordrequestparameters="dns/zones/$_domain_name" - removezonerecordrequestbody="{\"id\":$_domain_id,\"name\":\"$_domain_name\",\"records\":{\"remove\":[{\"name\":\"$_sub_domain\",\"ttl\":900,\"type\":\"TXT\",\"value\":\"\\\"$txtvalue\\\"\"}]}}" - - if _openprovider_rest PUT "$removezonerecordrequestparameters" "$removezonerecordrequestbody"; then - if _contains "$response" "\"success\":true"; then - return 0 - else - _err "Removing TXT record failed due to errors." - return 1 - fi - fi - fi - - _err "Removing TXT record failed due to errors." - return 1 -} - -#################### OpenProvider API common functions #################### -_openprovider_prepare_credentials() { - OPENPROVIDER_REST_USERNAME="${OPENPROVIDER_REST_USERNAME:-$(_readaccountconf_mutable OPENPROVIDER_REST_USERNAME)}" - OPENPROVIDER_REST_PASSWORD="${OPENPROVIDER_REST_PASSWORD:-$(_readaccountconf_mutable OPENPROVIDER_REST_PASSWORD)}" - - if [ -z "$OPENPROVIDER_REST_USERNAME" ] || [ -z "$OPENPROVIDER_REST_PASSWORD" ]; then - OPENPROVIDER_REST_USERNAME="" - OPENPROVIDER_REST_PASSWORD="" - _err "You didn't specify the Openprovider username or password yet." - return 1 - fi - - #save the credentials to the account conf file. - _saveaccountconf_mutable OPENPROVIDER_REST_USERNAME "$OPENPROVIDER_REST_USERNAME" - _saveaccountconf_mutable OPENPROVIDER_REST_PASSWORD "$OPENPROVIDER_REST_PASSWORD" -} - -_openprovider_rest() { - httpmethod=$1 - queryparameters=$2 - requestbody=$3 - - _openprovider_rest_login - if [ -z "$openproviderauthtoken" ]; then - _err "Unable to fetch authentication token from Openprovider API." - return 1 - fi - - export _H1="Content-Type: application/json" - export _H2="Accept: application/json" - export _H3="Authorization: Bearer $openproviderauthtoken" - - if [ "$httpmethod" != "GET" ]; then - response="$(_post "$requestbody" "$OPENPROVIDER_API_URL/$queryparameters" "" "$httpmethod")" - else - response="$(_get "$OPENPROVIDER_API_URL/$queryparameters")" - fi - - if [ "$?" != "0" ]; then - _err "No valid parameters supplied for Openprovider API: Error $queryparameters" - return 1 - fi - - _debug2 response "$response" - - return 0 -} - -_openprovider_rest_login() { - export _H1="Content-Type: application/json" - export _H2="Accept: application/json" - - loginrequesturl="$OPENPROVIDER_API_URL/auth/login" - loginrequestbody="{\"ip\":\"0.0.0.0\",\"password\":\"$OPENPROVIDER_REST_PASSWORD\",\"username\":\"$OPENPROVIDER_REST_USERNAME\"}" - loginresponse="$(_post "$loginrequestbody" "$loginrequesturl" "" "POST")" - - openproviderauthtoken="$(printf "%s\n" "$loginresponse" | _egrep_o '"token" *: *"[^"]*' | _head_n 1 | sed 's#^"token" *: *"##')" - - export openproviderauthtoken -} - -#################### Private functions ################################## - -# Usage: _get_dns_zone _acme-challenge.www.domain.com -# Returns: -# _domain_id=123456789 -# _domain_name=domain.com -# _sub_domain=_acme-challenge.www -_get_dns_zone() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - # Empty value not allowed - return 1 - fi - - if ! _openprovider_rest GET "dns/zones/$h" ""; then - return 1 - fi - - if _contains "$response" "\"name\":\"$h\""; then - _domain_id="$(printf "%s\n" "$response" | _egrep_o '"id" *: *[^,]*' | _head_n 1 | sed 's#^"id" *: *##')" - _debug _domain_id "$_domain_id" - - _domain_name="$h" - _debug _domain_name "$_domain_name" - - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _debug _sub_domain "$_sub_domain" - return 0 - fi - - p=$i - i=$(_math "$i" + 1) - done - - return 1 -} diff --git a/dnsapi/dns_openstack.sh b/dnsapi/dns_openstack.sh index fa38bc0b..38619e6f 100755 --- a/dnsapi/dns_openstack.sh +++ b/dnsapi/dns_openstack.sh @@ -1,21 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_openstack_info='OpenStack Designate API - Depends on OpenStackClient and python-desginateclient. - You will require Keystone V3 credentials loaded into your environment, - which could be either password or v3 application credential type. -Site: docs.openstack.org/api-ref/dns/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_openstack -Options: - OS_AUTH_URL Auth URL. E.g. "https://keystone.example.com:5000/" - OS_USERNAME Username - OS_PASSWORD Password - OS_PROJECT_NAME Project name - OS_PROJECT_DOMAIN_NAME Project domain name. E.g. "Default" - OS_USER_DOMAIN_NAME User domain name. E.g. "Default" -Issues: github.com/acmesh-official/acme.sh/issues/3054 -Author: Andy Botting -' + +# OpenStack Designate API plugin +# +# This requires you to have OpenStackClient and python-desginateclient +# installed. +# +# You will require Keystone V3 credentials loaded into your environment, which +# could be either password or v3applicationcredential type. +# +# Author: Andy Botting ######## Public functions ##################### @@ -64,16 +57,16 @@ _dns_openstack_create_recordset() { if [ -z "$_recordset_id" ]; then _info "Creating a new recordset" - if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record="$txtvalue" "$_zone_id" "$fulldomain."); then + if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record "$txtvalue" "$_zone_id" "$fulldomain."); then _err "No recordset ID found after create" return 1 fi else _info "Updating existing recordset" - # Build new list of --record= args for update - _record_args="--record=$txtvalue" + # Build new list of --record args for update + _record_args="--record $txtvalue" for _rec in $_records; do - _record_args="$_record_args --record=$_rec" + _record_args="$_record_args --record $_rec" done # shellcheck disable=SC2086 if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain."); then @@ -114,13 +107,13 @@ _dns_openstack_delete_recordset() { fi else _info "Found existing records, updating recordset" - # Build new list of --record= args for update + # Build new list of --record args for update _record_args="" for _rec in $_records; do if [ "$_rec" = "$txtvalue" ]; then continue fi - _record_args="$_record_args --record=$_rec" + _record_args="$_record_args --record $_rec" done # shellcheck disable=SC2086 if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then diff --git a/dnsapi/dns_opnsense.sh b/dnsapi/dns_opnsense.sh index a11cfae5..c2806a1b 100755 --- a/dnsapi/dns_opnsense.sh +++ b/dnsapi/dns_opnsense.sh @@ -1,16 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_opnsense_info='OPNsense Server -Site: docs.opnsense.org/development/api.html -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_opnsense -Options: - OPNs_Host Server Hostname. E.g. "opnsense.example.com" - OPNs_Port Port. Default: "443". - OPNs_Key API Key - OPNs_Token API Token - OPNs_Api_Insecure Insecure TLS. 0: check for cert validity, 1: always accept -Issues: github.com/acmesh-official/acme.sh/issues/2480 -' + +#OPNsense Bind API +#https://docs.opnsense.org/development/api.html +# +#OPNs_Host="opnsense.example.com" +#OPNs_Port="443" +# optional, defaults to 443 if unset +#OPNs_Key="qocfU9RSbt8vTIBcnW8bPqCrpfAHMDvj5OzadE7Str+rbjyCyk7u6yMrSCHtBXabgDDXx/dY0POUp7ZA" +#OPNs_Token="pZEQ+3ce8dDlfBBdg3N8EpqpF5I1MhFqdxX06le6Gl8YzyQvYCfCzNaFX9O9+IOSyAs7X71fwdRiZ+Lv" +#OPNs_Api_Insecure=0 +# optional, defaults to 0 if unset +# Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" @@ -110,16 +110,15 @@ rm_record() { if _existingchallenge "$_domain" "$_host" "$new_challenge"; then # Delete if _opns_rest "POST" "/record/delRecord/${_uuid}" "\{\}"; then - if echo "$response" | _egrep_o "\"result\":\"deleted\"" >/dev/null; then - _debug "Record deleted" + if echo "$_return_str" | _egrep_o "\"result\":\"deleted\"" >/dev/null; then _opns_rest "POST" "/service/reconfigure" "{}" - _debug "Service reconfigured" + _debug "Record deleted" else _err "Error deleting record $_host from domain $fulldomain" return 1 fi else - _err "Error requesting deletion of record $_host from domain $fulldomain" + _err "Error deleting record $_host from domain $fulldomain" return 1 fi else @@ -138,32 +137,29 @@ _get_root() { domain=$1 i=2 p=1 - if _opns_rest "GET" "/domain/searchPrimaryDomain"; then + if _opns_rest "GET" "/domain/searchMasterDomain"; then _domain_response="$response" else return 1 fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 fi _debug h "$h" - lines=$(echo "$_domain_response" | sed 's/{/\n/g') - for line in $lines; do - id=$(echo "$line" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"1\",\"type\":\"primary\",.*\"domainname\":\"${h}\"" | cut -d ':' -f 2 | cut -d '"' -f 2) - if [ -n "$id" ]; then - _debug id "$id" - _host=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain="${h}" - _domainid="${id}" - return 0 - fi - done + 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) + _domain="${h}" + _domainid="${id}" + return 0 + fi p=$i - i=$(_math "$i" + 1) + i=$(_math $i + 1) done _debug "$domain not found" @@ -210,13 +206,13 @@ _existingchallenge() { return 1 fi _uuid="" - _uuid=$(echo "$_record_response" | _egrep_o "\"uuid\":\"[a-z0-9\-]*\",\"enabled\":\"[01]\",\"domain\":\"[a-z0-9\-]*\",\"%domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"" | cut -d ':' -f 2 | cut -d '"' -f 2) + _uuid=$(echo "$_record_response" | _egrep_o "\"uuid\":\"[^\"]*\",\"enabled\":\"[01]\",\"domain\":\"$1\",\"name\":\"$2\",\"type\":\"TXT\",\"value\":\"$3\"" | cut -d ':' -f 2 | cut -d '"' -f 2) if [ -n "$_uuid" ]; then _debug uuid "$_uuid" return 0 fi - _debug "${2}.${1} record not found" + _debug "${2}.$1{1} record not found" return 1 } diff --git a/dnsapi/dns_opusdns.sh b/dnsapi/dns_opusdns.sh deleted file mode 100755 index 37177696..00000000 --- a/dnsapi/dns_opusdns.sh +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env sh - -# shellcheck disable=SC2034 -dns_opusdns_info='OpusDNS.com -Site: OpusDNS.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_opusdns -Options: - OPUSDNS_API_Key API Key. Can be created at https://dashboard.opusdns.com/settings/api-keys - OPUSDNS_API_Endpoint API Endpoint URL. Default "https://api.opusdns.com". Optional. - OPUSDNS_TTL TTL for DNS challenge records in seconds. Default "60". Optional. -Issues: github.com/acmesh-official/acme.sh/issues/XXXX -Author: OpusDNS Team -' - -OPUSDNS_API_Endpoint_Default="https://api.opusdns.com" -OPUSDNS_TTL_Default=60 - -######## Public functions ########### - -# Add DNS TXT record -dns_opusdns_add() { - fulldomain=$1 - txtvalue=$2 - - _info "Using OpusDNS DNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _opusdns_init; then - return 1 - fi - - if ! _get_zone "$fulldomain"; then - return 1 - fi - - _info "Zone: $_zone, Record: $_record_name" - - if ! _opusdns_api PATCH "/v1/dns/$_zone/records" "{\"ops\":[{\"op\":\"upsert\",\"record\":{\"name\":\"$_record_name\",\"type\":\"TXT\",\"ttl\":$OPUSDNS_TTL,\"rdata\":\"\\\"$txtvalue\\\"\"}}]}"; then - _err "Failed to add TXT record" - return 1 - fi - - _info "TXT record added successfully" - return 0 -} - -# Remove DNS TXT record -dns_opusdns_rm() { - fulldomain=$1 - txtvalue=$2 - - _info "Removing OpusDNS DNS record" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _opusdns_init; then - return 1 - fi - - if ! _get_zone "$fulldomain"; then - _err "Zone not found, cleanup skipped" - return 0 - fi - - _info "Zone: $_zone, Record: $_record_name" - - if ! _opusdns_api PATCH "/v1/dns/$_zone/records" "{\"ops\":[{\"op\":\"remove\",\"record\":{\"name\":\"$_record_name\",\"type\":\"TXT\",\"ttl\":$OPUSDNS_TTL,\"rdata\":\"\\\"$txtvalue\\\"\"}}]}"; then - _err "Warning: Failed to remove TXT record" - return 0 - fi - - _info "TXT record removed successfully" - return 0 -} - -######## Private functions ########### - -# Initialize and validate configuration -_opusdns_init() { - OPUSDNS_API_Key="${OPUSDNS_API_Key:-$(_readaccountconf_mutable OPUSDNS_API_Key)}" - OPUSDNS_API_Endpoint="${OPUSDNS_API_Endpoint:-$(_readaccountconf_mutable OPUSDNS_API_Endpoint)}" - OPUSDNS_TTL="${OPUSDNS_TTL:-$(_readaccountconf_mutable OPUSDNS_TTL)}" - - if [ -z "$OPUSDNS_API_Key" ]; then - _err "OPUSDNS_API_Key not set" - return 1 - fi - - [ -z "$OPUSDNS_API_Endpoint" ] && OPUSDNS_API_Endpoint="$OPUSDNS_API_Endpoint_Default" - [ -z "$OPUSDNS_TTL" ] && OPUSDNS_TTL="$OPUSDNS_TTL_Default" - - _saveaccountconf_mutable OPUSDNS_API_Key "$OPUSDNS_API_Key" - _saveaccountconf_mutable OPUSDNS_API_Endpoint "$OPUSDNS_API_Endpoint" - _saveaccountconf_mutable OPUSDNS_TTL "$OPUSDNS_TTL" - - _debug "Endpoint: $OPUSDNS_API_Endpoint" - return 0 -} - -# Make API request -# Usage: _opusdns_api METHOD PATH [DATA] -_opusdns_api() { - method=$1 - path=$2 - data=$3 - - export _H1="X-Api-Key: $OPUSDNS_API_Key" - export _H2="Content-Type: application/json" - - url="$OPUSDNS_API_Endpoint$path" - _debug2 "API: $method $url" - [ -n "$data" ] && _debug2 "Data: $data" - - if [ -n "$data" ]; then - response=$(_post "$data" "$url" "" "$method") - else - response=$(_get "$url") - fi - - if [ $? -ne 0 ]; then - _err "API request failed" - _debug "Response: $response" - return 1 - fi - - _debug2 "Response: $response" - return 0 -} - -# Detect zone from FQDN -# Sets: _zone, _record_name -_get_zone() { - domain=$(echo "$1" | sed 's/\.$//') - _debug "Finding zone for: $domain" - - i=1 - p=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - - if [ -z "$h" ]; then - _err "No valid zone found for: $domain" - return 1 - fi - - _debug "Trying: $h" - if _opusdns_api GET "/v1/dns/$h" && _contains "$response" '"dnssec_status"'; then - _zone="$h" - _record_name=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - [ -z "$_record_name" ] && _record_name="@" - return 0 - fi - - p="$i" - i=$(_math "$i" + 1) - done -} diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh index df2b184d..5e35011b 100755 --- a/dnsapi/dns_ovh.sh +++ b/dnsapi/dns_ovh.sh @@ -1,24 +1,19 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ovh_info='OVH.com -Domains: kimsufi.com soyoustart.com -Site: OVH.com -Docs: github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api -Options: - OVH_END_POINT Endpoint. "ovh-eu", "ovh-us", "ovh-ca", "kimsufi-eu", "kimsufi-ca", "soyoustart-eu", "soyoustart-ca" or raw URL. Default: "ovh-eu". - OVH_AK Application Key - OVH_AS Application Secret - OVH_CK Consumer Key -' + +#Application Key +#OVH_AK="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#Application Secret +#OVH_AS="sdfsafsdfsdfdsfsdfsa" +# +#Consumer Key +#OVH_CK="sdfsdfsdfsdfsdfdsf" #OVH_END_POINT=ovh-eu #'ovh-eu' OVH_EU='https://eu.api.ovh.com/1.0' -#'ovh-us' -OVH_US='https://api.us.ovhcloud.com/1.0' - #'ovh-ca': OVH_CA='https://ca.api.ovh.com/1.0' @@ -34,6 +29,9 @@ SYS_EU='https://eu.api.soyoustart.com/1.0' #'soyoustart-ca' SYS_CA='https://ca.api.soyoustart.com/1.0' +#'runabove-ca' +RAV_CA='https://api.runabove.com/1.0' + wiki="https://github.com/acmesh-official/acme.sh/wiki/How-to-use-OVH-domain-api" ovh_success="https://github.com/acmesh-official/acme.sh/wiki/OVH-Success" @@ -47,10 +45,6 @@ _ovh_get_api() { printf "%s" $OVH_EU return ;; - ovh-us | ovhus) - printf "%s" $OVH_US - return - ;; ovh-ca | ovhca) printf "%s" $OVH_CA return @@ -71,15 +65,14 @@ _ovh_get_api() { printf "%s" $SYS_CA return ;; - # raw API url starts with https:// - https*) - printf "%s" "$1" + runabove-ca | runaboveca) + printf "%s" $RAV_CA return ;; *) - _err "Unknown endpoint : $1" + _err "Unknown parameter : $1" return 1 ;; esac @@ -113,7 +106,7 @@ _initAuth() { _saveaccountconf_mutable OVH_END_POINT "$OVH_END_POINT" fi - OVH_API="$(_ovh_get_api "$OVH_END_POINT")" + OVH_API="$(_ovh_get_api $OVH_END_POINT)" _debug OVH_API "$OVH_API" OVH_CK="${OVH_CK:-$(_readaccountconf_mutable OVH_CK)}" @@ -201,7 +194,7 @@ dns_ovh_rm() { if ! _ovh_rest GET "domain/zone/$_domain/record/$rid"; then return 1 fi - if _contains "$response" "$txtvalue"; then + if _contains "$response" "\"target\":\"$txtvalue\""; then _debug "Found txt id:$rid" if ! _ovh_rest DELETE "domain/zone/$_domain/record/$rid"; then return 1 @@ -224,7 +217,7 @@ _ovh_authentication() { _H3="" _H4="" - _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "GET","path": "/domain/zone/*/record/*"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}' + _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"},{"method": "DELETE","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}' response="$(_post "$_ovhdata" "$OVH_API/auth/credential")" _debug3 response "$response" @@ -260,7 +253,7 @@ _get_root() { i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -273,7 +266,7 @@ _get_root() { if ! _contains "$response" "This service does not exist" >/dev/null && ! _contains "$response" "This call has not been granted" >/dev/null && ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_pdns.sh b/dnsapi/dns_pdns.sh index 72a58af0..6aa2e953 100755 --- a/dnsapi/dns_pdns.sh +++ b/dnsapi/dns_pdns.sh @@ -1,14 +1,12 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_pdns_info='PowerDNS Server API -Site: PowerDNS.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_pdns -Options: - PDNS_Url API URL. E.g. "http://ns.example.com:8081" - PDNS_ServerId Server ID. E.g. "localhost" - PDNS_Token API Token - PDNS_Ttl Domain TTL. Default: "60". -' + +#PowerDNS Embedded API +#https://doc.powerdns.com/md/httpapi/api_spec/ +# +#PDNS_Url="http://ns.example.com:8081" +#PDNS_ServerId="localhost" +#PDNS_Token="0123456789ABCDEF" +#PDNS_Ttl=60 DEFAULT_PDNS_TTL=60 @@ -20,11 +18,6 @@ dns_pdns_add() { fulldomain=$1 txtvalue=$2 - PDNS_Url="${PDNS_Url:-$(_readaccountconf_mutable PDNS_Url)}" - PDNS_ServerId="${PDNS_ServerId:-$(_readaccountconf_mutable PDNS_ServerId)}" - PDNS_Token="${PDNS_Token:-$(_readaccountconf_mutable PDNS_Token)}" - PDNS_Ttl="${PDNS_Ttl:-$(_readaccountconf_mutable PDNS_Ttl)}" - if [ -z "$PDNS_Url" ]; then PDNS_Url="" _err "You don't specify PowerDNS address." @@ -50,16 +43,13 @@ dns_pdns_add() { PDNS_Ttl="$DEFAULT_PDNS_TTL" fi - # Ensure PDNS_Url has no trailing slash ('/') - PDNS_Url="${PDNS_Url%/}" - #save the api addr and key to the account conf file. - _saveaccountconf_mutable PDNS_Url "$PDNS_Url" - _saveaccountconf_mutable PDNS_ServerId "$PDNS_ServerId" - _saveaccountconf_mutable PDNS_Token "$PDNS_Token" + _saveaccountconf PDNS_Url "$PDNS_Url" + _saveaccountconf PDNS_ServerId "$PDNS_ServerId" + _saveaccountconf PDNS_Token "$PDNS_Token" if [ "$PDNS_Ttl" != "$DEFAULT_PDNS_TTL" ]; then - _saveaccountconf_mutable PDNS_Ttl "$PDNS_Ttl" + _saveaccountconf PDNS_Ttl "$PDNS_Ttl" fi _debug "Detect root zone" @@ -81,11 +71,6 @@ dns_pdns_rm() { fulldomain=$1 txtvalue=$2 - PDNS_Url="${PDNS_Url:-$(_readaccountconf_mutable PDNS_Url)}" - PDNS_ServerId="${PDNS_ServerId:-$(_readaccountconf_mutable PDNS_ServerId)}" - PDNS_Token="${PDNS_Token:-$(_readaccountconf_mutable PDNS_Token)}" - PDNS_Ttl="${PDNS_Ttl:-$(_readaccountconf_mutable PDNS_Ttl)}" - if [ -z "$PDNS_Ttl" ]; then PDNS_Ttl="$DEFAULT_PDNS_TTL" fi @@ -189,29 +174,25 @@ _get_root() { domain=$1 i=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then + _zones_response=$(echo "$response" | _normalizeJson) + fi - # Probe each candidate zone with the server-side name filter instead of - # listing every zone: with large installations (100k zones) the - # unfiltered list takes minutes. Servers that ignore the parameter - # return the full list, which the check below still handles. - # https://doc.powerdns.com/authoritative/http-api/zone.html - if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones?zone=$h."; then - _zones_response=$(echo "$response" | _normalizeJson) - if _contains "$_zones_response" "\"name\":\"$h.\""; then - _domain="$h." - if [ -z "$h" ]; then - _domain="=2E" - fi - return 0 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + + if _contains "$_zones_response" "\"name\":\"$h.\""; then + _domain="$h." + if [ -z "$h" ]; then + _domain="=2E" fi + return 0 fi if [ -z "$h" ]; then return 1 fi - i=$(_math "$i" + 1) + i=$(_math $i + 1) done _debug "$domain not found" diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index 176f329d..f5986827 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -1,17 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_pleskxml_info='Plesk Server API -Site: Plesk.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_pleskxml -Options: - pleskxml_uri Plesk server API URL. E.g. "https://your-plesk-server.net:8443/enterprise/control/agent.php" - pleskxml_user Username - pleskxml_pass Password -Issues: github.com/acmesh-official/acme.sh/issues/2577 -Author: @Stilez, @romanlum -' -## Plesk XML API described at: +## Name: dns_pleskxml.sh +## Created by Stilez. +## Also uses some code from PR#1832 by @romanlum (https://github.com/acmesh-official/acme.sh/pull/1832/files) + +## This DNS-01 method uses the Plesk XML API described at: ## https://docs.plesk.com/en-US/12.5/api-rpc/about-xml-api.28709 ## and more specifically: https://docs.plesk.com/en-US/12.5/api-rpc/reference.28784 @@ -23,6 +16,21 @@ Author: @Stilez, @romanlum ## For ACME v2 purposes, new TXT records are appended when added, and removing one TXT record will not affect any other TXT records. ## The user credentials (username+password) and URL/URI for the Plesk XML API must be set by the user +## before this module is called (case sensitive): +## +## ``` +## export pleskxml_uri="https://address-of-my-plesk-server.net:8443/enterprise/control/agent.php" +## (or probably something similar) +## export pleskxml_user="my plesk username" +## export pleskxml_pass="my plesk password" +## ``` + +## Ok, let's issue a cert now: +## ``` +## acme.sh --issue --dns dns_pleskxml -d example.com -d www.example.com +## ``` +## +## The `pleskxml_uri`, `pleskxml_user` and `pleskxml_pass` will be saved in `~/.acme.sh/account.conf` and reused when needed. #################### INTERNAL VARIABLES + NEWLINE + API TEMPLATES ################################## @@ -33,15 +41,11 @@ pleskxml_init_checks_done=0 NEWLINE='\ ' -pleskxml_tplt_get_domains="" +pleskxml_tplt_get_domains="" # Get a list of domains that PLESK can manage, so we can check root domain + host for acme.sh # Also used to test credentials and URI. # No params. -pleskxml_tplt_get_additional_domains="" -# Get a list of additional domains that PLESK can manage, so we can check root domain + host for acme.sh -# No params. - pleskxml_tplt_get_dns_records="%s" # Get all DNS records for a Plesk domain ID. # PARAM = Plesk domain id to query @@ -141,25 +145,22 @@ dns_pleskxml_rm() { )" if [ -z "$reclist" ]; then - _err "No TXT records found for root domain $fulldomain (Plesk domain ID ${root_domain_id}). Exiting." + _err "No TXT records found for root domain ${root_domain_name} (Plesk domain ID ${root_domain_id}). Exiting." return 1 fi - _debug "Got list of DNS TXT records for root Plesk domain ID ${root_domain_id} of root domain $fulldomain:" + _debug "Got list of DNS TXT records for root domain '$root_domain_name':" _debug "$reclist" - # Extracting the id of the TXT record for the full domain (NOT case-sensitive) and corresponding value recid="$( _value "$reclist" | - grep -Fi "${fulldomain}." | - grep -F "${txtvalue}" | + grep "${fulldomain}." | + grep "${txtvalue}" | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/' )" - _debug "Got id from line: $recid" - if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then - _err "DNS records for root domain '${fulldomain}.' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" + _err "DNS records for root domain '${root_domain_name}' (Plesk ID ${root_domain_id}) + host '${sub_domain_name}' do not contain the TXT record '${txtvalue}'" _err "Cannot delete TXT record. Exiting." return 1 fi @@ -250,12 +251,9 @@ _call_api() { # Detect any that isn't "ok". None of the used calls should fail if the API is working correctly. # Also detect if there simply aren't any status lines (null result?) and report that, as well. - # Remove structure from result string, since it might contain values that are related to the status of the domain and not to the API request - statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | sed '//,/<\/data>/d' | grep -c '^ *[^<]* *$')" - statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | sed '//,/<\/data>/d' | grep -c '^ *ok *$')" - _debug "statuslines_count_total=$statuslines_count_total." - _debug "statuslines_count_okay=$statuslines_count_okay." + statuslines_count_total="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *[^<]* *$')" + statuslines_count_okay="$(echo "$pleskxml_prettyprint_result" | grep -c '^ *ok *$')" if [ -z "$statuslines_count_total" ]; then @@ -371,44 +369,16 @@ _pleskxml_get_root_domain() { return 1 fi - # Generate a crude list of domains known to this Plesk account based on subscriptions. + # Generate a crude list of domains known to this Plesk account. # We convert tags to so it'll flag on a hit with either or fields, # for non-Western character sets. # Output will be one line per known domain, containing 2 tages and a single tag # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. - output="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" - debug_output="$(printf "%s" "$output" | sed -n 's:.*\(.*\).*:\1:p')" + output="$(_api_response_split "$pleskxml_prettyprint_result" 'domain' 'domain' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" - _debug 'Domains managed by Plesk server are:' - _debug "$debug_output" - - _debug "Querying Plesk server for list of additional managed domains..." - - _call_api "$pleskxml_tplt_get_additional_domains" - if [ "$pleskxml_retcode" -ne 0 ]; then - return 1 - fi - - # Generate a crude list of additional domains known to this Plesk account based on sites. - # We convert tags to so it'll flag on a hit with either or fields, - # for non-Western character sets. - # Output will be one line per known domain, containing 2 tages and a single tag - # We don't actually need to check for type, name, *and* id, but it guarantees only usable lines are returned. - - output_additional="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | sed 's///g;s/<\/ascii-name>/<\/name>/g' | grep '' | grep '')" - debug_additional="$(printf "%s" "$output_additional" | sed -n 's:.*\(.*\).*:\1:p')" - - _debug 'Additional domains managed by Plesk server are:' - _debug "$debug_additional" - - # Concate the two outputs together. - - output="$(printf "%s" "$output $NEWLINE $output_additional")" - debug_output="$(printf "%s" "$output" | sed -n 's:.*\(.*\).*:\1:p')" - - _debug 'Domains (including additional) managed by Plesk server are:' - _debug "$debug_output" + _debug 'Domains managed by Plesk server are (ignore the hacked output):' + _debug "$output" # loop and test if domain, or any parent domain, is managed by Plesk # Loop until we don't have any '.' in the string we're testing as a candidate Plesk-managed domain @@ -419,7 +389,7 @@ _pleskxml_get_root_domain() { _debug "Checking if '$root_domain_name' is managed by the Plesk server..." - root_domain_id="$(_value "$output" | grep -F "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" + root_domain_id="$(_value "$output" | grep "$root_domain_name" | _head_n 1 | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/')" if [ -n "$root_domain_id" ]; then # Found a match diff --git a/dnsapi/dns_pointhq.sh b/dnsapi/dns_pointhq.sh index 0abc087b..62313109 100644 --- a/dnsapi/dns_pointhq.sh +++ b/dnsapi/dns_pointhq.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_pointhq_info='pointhq.com PointDNS -Site: pointhq.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_pointhq -Options: - PointHQ_Key API Key - PointHQ_Email Email -Issues: github.com/acmesh-official/acme.sh/issues/2060 -' + +# +#PointHQ_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#PointHQ_Email="xxxx@sss.com" PointHQ_Api="https://api.pointhq.com" @@ -118,7 +114,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -130,7 +126,7 @@ _get_root() { fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_porkbun.sh b/dnsapi/dns_porkbun.sh index 1681ca9a..ad4455b6 100644 --- a/dnsapi/dns_porkbun.sh +++ b/dnsapi/dns_porkbun.sh @@ -1,15 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_porkbun_info='Porkbun.com -Site: Porkbun.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_porkbun -Options: - PORKBUN_API_KEY API Key - PORKBUN_SECRET_API_KEY API Secret -Issues: github.com/acmesh-official/acme.sh/issues/3450 -' -PORKBUN_Api="https://api.porkbun.com/api/json/v3" +# +#PORKBUN_API_KEY="pk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" +#PORKBUN_SECRET_API_KEY="sk1_0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + +PORKBUN_Api="https://porkbun.com/api/json/v3" ######## Public functions ##################### @@ -93,7 +88,7 @@ dns_porkbun_rm() { _err "Delete record error." return 1 fi - echo "$response" | tr -d " " | grep '"status":"SUCCESS"' >/dev/null + echo "$response" | tr -d " " | grep '\"status\":"SUCCESS"' >/dev/null fi } @@ -107,7 +102,7 @@ _get_root() { domain=$1 i=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then return 1 @@ -139,7 +134,7 @@ _porkbun_rest() { api_key_trimmed=$(echo "$PORKBUN_API_KEY" | tr -d '"') secret_api_key_trimmed=$(echo "$PORKBUN_SECRET_API_KEY" | tr -d '"') - test -z "$data" && data="{" || data="$(echo "$data" | cut -d'}' -f1)," + test -z "$data" && data="{" || data="$(echo $data | cut -d'}' -f1)," data="$data\"apikey\":\"$api_key_trimmed\",\"secretapikey\":\"$secret_api_key_trimmed\"}" export _H1="Content-Type: application/json" diff --git a/dnsapi/dns_poweradmin.sh b/dnsapi/dns_poweradmin.sh deleted file mode 100644 index a4c81835..00000000 --- a/dnsapi/dns_poweradmin.sh +++ /dev/null @@ -1,238 +0,0 @@ -#!/usr/bin/env sh - -# shellcheck disable=SC2034 - -# Credits to the authors of dnsapi/dns_pdns.sh as this reuses much of that code. - -dns_poweradmin_info='Poweradmin API -Site: https://www.poweradmin.org/ -Docs: https://github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_poweradmin -Options: -POWERADMIN_URL API URL (with scheme). E.g. "https://poweradmin.example.com" or "http://192.168.0.10:8080" -POWERADMIN_API_KEY API Token "pwa_xxxx" -POWERADMIN_API_VERSION Optionally override Poweradmin API version. -Issues: https://github.com/acmesh-official/acme.sh/issues/6912 -Author: Jakob Næss -' - -######## Public functions #################### - -# Usage: dns_poweradmin_add _acme-challenge.www.domain.com "123456789ABCDEF" -# fulldomain -# txtvalue -dns_poweradmin_add() { - fulldomain=$1 - txtvalue=$2 - - POWERADMIN_URL="${POWERADMIN_URL:-$(_readaccountconf_mutable POWERADMIN_URL)}" - POWERADMIN_API_KEY="${POWERADMIN_API_KEY:-$(_readaccountconf_mutable POWERADMIN_API_KEY)}" - POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-$(_readaccountconf_mutable POWERADMIN_API_VERSION)}" - POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-2}" - - if [ -z "$POWERADMIN_URL" ]; then - POWERADMIN_URL="" - _err "You didn't specify Poweradmin URL." - _err "Please set POWERADMIN_URL and try again." - return 1 - fi - - if [ -z "$POWERADMIN_API_KEY" ]; then - POWERADMIN_API_KEY="" - _err "You didn't specify Poweradmin token." - _err "Please set POWERADMIN_API_KEY and try again." - return 1 - fi - - # Save the api addr, key, and version to the account conf file. - _saveaccountconf_mutable POWERADMIN_URL "$POWERADMIN_URL" - _saveaccountconf_mutable POWERADMIN_API_KEY "$POWERADMIN_API_KEY" - _saveaccountconf_mutable POWERADMIN_API_VERSION "$POWERADMIN_API_VERSION" - - _debug "Detect root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - - _debug _domain "$_domain" - _debug _zone_id "$_zone_id" - - if ! _set_record "$fulldomain" "$txtvalue"; then - return 1 - fi - - return 0 -} - -# Usage: dns_poweradmin_rm _acme-challenge.www.domain.com "123456789ABCDEF" -# fulldomain -# txtvalue -dns_poweradmin_rm() { - fulldomain=$1 - txtvalue=$2 - - POWERADMIN_URL="${POWERADMIN_URL:-$(_readaccountconf_mutable POWERADMIN_URL)}" - POWERADMIN_API_KEY="${POWERADMIN_API_KEY:-$(_readaccountconf_mutable POWERADMIN_API_KEY)}" - POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-$(_readaccountconf_mutable POWERADMIN_API_VERSION)}" - POWERADMIN_API_VERSION="${POWERADMIN_API_VERSION:-2}" - - _debug "Detect root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - - _debug _domain "$_domain" - _debug _zone_id "$_zone_id" - - if ! _rm_record "$fulldomain" "$txtvalue"; then - return 1 - fi - - return 0 -} - -######## Private functions below ##################### - -_set_record() { - _info "Adding TXT record" - full=$1 - new_challenge=$2 - - data='{"name":"'$full'","type":"TXT","content":"'$new_challenge'","ttl":60}' - - if ! _poweradmin_rest "POST" "/api/v${POWERADMIN_API_VERSION}/zones/$_zone_id/records" "$data" "application/json"; then - _err "Failed to add TXT record" - return 1 - fi - - return 0 -} - -_rm_record() { - _info "Remove TXT record" - full=$1 - txtvalue=$2 - - if ! _poweradmin_rest "GET" "/api/v${POWERADMIN_API_VERSION}/zones/$_zone_id/records"; then - _err "Failed to retrieve records" - return 1 - fi - - # The API returns: {"success":true,"data":[{"id":..., "name":"...", "type":"TXT", "content":"...", ...}]} - _txt_record_obj=$( - printf '%s\n' "$response" | - sed 's/^.*"data":\[//; s/\],"message":.*$//' | - awk '{ gsub(/},{/, "}\n{"); print }' | - grep -F "\"name\":\"$full\"" | - grep -F "\"type\":\"TXT\"" | - grep -F "\"content\":\"$txtvalue\"" | - _head_n 1 - ) - - if [ -z "$_txt_record_obj" ]; then - _info "TXT record not found for $full with content $txtvalue" - return 0 - fi - - record_id=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"id":\([0-9][0-9]*\).*/\1/p' | _head_n 1) - record_type=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"type":"\([^"]*\)".*/\1/p' | _head_n 1) - record_name=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"name":"\([^"]*\)".*/\1/p' | _head_n 1) - record_content=$(printf '%s\n' "$_txt_record_obj" | sed -n 's/.*"content":"\([^"]*\)".*/\1/p' | _head_n 1) - - _debug2 "_txt_record_obj=$_txt_record_obj" - _debug2 "record id: $record_id" - _debug2 "record type: $record_type" - _debug2 "record name: $record_name" - _debug2 "record content: $record_content" - - if [ "$record_type" != "TXT" ]; then - _err "Refusing to delete non-TXT record id=$record_id type=$record_type name=$full" - return 1 - fi - - if ! _poweradmin_rest "DELETE" "/api/v${POWERADMIN_API_VERSION}/zones/$_zone_id/records/$record_id"; then - _err "Failed to delete TXT record" - return 1 - fi - - _info "Record deleted successfully" - return 0 -} - -# _acme-challenge.www.domain.com -# returns -# _domain=domain.com -# _zone_id=220 -_get_root() { - domain=$1 - i=1 - - if ! _poweradmin_rest "GET" "/api/v${POWERADMIN_API_VERSION}/zones"; then - _err "Failed to retrieve zones" - return 1 - fi - - _zones_response="$response" - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - - if [ -z "$h" ]; then - _debug "Root domain not found for $domain" - return 1 - fi - - zone_obj=$( - printf '%s' "$_zones_response" | - sed 's/},{/}\n{/g' | - grep -F "\"name\":\"$h\"" | - _head_n 1 - ) - - if [ -n "$zone_obj" ]; then - _zone_id=$(printf '%s' "$zone_obj" | _egrep_o '"id":[0-9][0-9]*' | _head_n 1 | cut -d: -f2) - _domain="$h" - _debug "Found zone: $_domain with id: $_zone_id" - return 0 - fi - - i=$(_math "$i" + 1) - done -} - -_poweradmin_rest() { - method=$1 - ep=$2 - data=$3 - ct=$4 - - export _H1="X-API-Key: $POWERADMIN_API_KEY" - - if [ "$method" = "GET" ]; then - response="$(_get "$POWERADMIN_URL$ep")" - else - _debug "API call: $method $ep" - _debug "Content-Type: $ct" - _debug "Payload: $data" - response="$(_post "$data" "$POWERADMIN_URL$ep" "" "$method" "$ct")" - fi - - # Clear _H1 variable - unset -v _H1 - - if [ "$?" != "0" ]; then - _err "API error on $method $ep" - _debug "Response: $response" - return 1 - fi - - if printf '%s' "$response" | grep -q '"success"[ ]*:[ ]*false'; then - _err "API reported failure on $method $ep" - _debug "Response: $response" - return 1 - fi - - _debug2 "API Response: $response" - return 0 -} diff --git a/dnsapi/dns_qc.sh b/dnsapi/dns_qc.sh deleted file mode 100755 index 78756a35..00000000 --- a/dnsapi/dns_qc.sh +++ /dev/null @@ -1,216 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_qc_info='QUIC.cloud -Site: quic.cloud -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_qc -Options: - QC_API_KEY QC API Key - QC_API_EMAIL Your account email -' - -QC_Api="https://api.quic.cloud/v2" - -######## Public functions ##################### - -#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_qc_add() { - fulldomain=$1 - txtvalue=$2 - - _debug "Enter dns_qc_add fulldomain: $fulldomain, txtvalue: $txtvalue" - QC_API_KEY="${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}" - QC_API_EMAIL="${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}" - - if [ "$QC_API_KEY" ]; then - _saveaccountconf_mutable QC_API_KEY "$QC_API_KEY" - else - _err "You didn't specify a QUIC.cloud api key as QC_API_KEY." - _err "You can get yours from here https://my.quic.cloud/up/api." - return 1 - fi - - if ! _contains "$QC_API_EMAIL" "@"; then - _err "It seems that the QC_API_EMAIL=$QC_API_EMAIL is not a valid email address." - _err "Please check and retry." - return 1 - fi - #save the api key and email to the account conf file. - _saveaccountconf_mutable QC_API_EMAIL "$QC_API_EMAIL" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain during add" - return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting txt records" - _qc_rest GET "zones/${_domain_id}/records" - - if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then - _err "Error failed response from QC GET: $response" - return 1 - fi - - # For wildcard cert, the main root domain and the wildcard domain have the same txt subdomain name, so - # we can not use updating anymore. - # count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) - # _debug count "$count" - # if [ "$count" = "0" ]; then - _info "Adding txt record" - if _qc_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":1800}"; then - if _contains "$response" "$txtvalue"; then - _info "Added txt record, OK" - return 0 - elif _contains "$response" "Same record already exists"; then - _info "txt record already exists, OK" - return 0 - else - _err "Add txt record error: $response" - return 1 - fi - fi - _err "Add txt record error: POST failed: $response" - return 1 - -} - -#fulldomain txtvalue -dns_qc_rm() { - fulldomain=$1 - txtvalue=$2 - - _debug "Enter dns_qc_rm fulldomain: $fulldomain, txtvalue: $txtvalue" - QC_API_KEY="${QC_API_KEY:-$(_readaccountconf_mutable QC_API_KEY)}" - QC_API_EMAIL="${QC_API_EMAIL:-$(_readaccountconf_mutable QC_API_EMAIL)}" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain during rm" - return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting txt records" - _qc_rest GET "zones/${_domain_id}/records" - - if ! echo "$response" | tr -d " " | grep \"success\":true >/dev/null; then - _err "Error rm GET response: $response" - return 1 - fi - - _debug "Pre-jq response:" "$response" - # Do not use jq or subsequent code - #response=$(echo "$response" | jq ".result[] | select(.id) | select(.content == \"$txtvalue\") | select(.type == \"TXT\")") - #_debug "get txt response" "$response" - #if [ "${response}" = "" ]; then - # _info "Don't need to remove txt records." - # return 0 - #fi - #record_id=$(echo "$response" | grep \"id\" | awk -F ' ' '{print $2}' | sed 's/,$//') - #_debug "txt record_id" "$record_id" - #Instead of jq - array=$(echo "$response" | grep -o '\[[^]]*\]' | sed 's/^\[\(.*\)\]$/\1/') - if [ -z "$array" ]; then - _err "Expected array in QC response: $response" - return 1 - fi - # Temporary file to hold matched content (one per line) - tmpfile=$(_mktemp) - echo "$array" | grep -o '{[^}]*}' | sed 's/^{//;s/}$//' >"$tmpfile" - record_id="" - - while IFS= read -r obj || [ -n "$obj" ]; do - if echo "$obj" | grep -q '"TXT"' && echo "$obj" | grep -q '"id"' && echo "$obj" | grep -q "$txtvalue"; then - _debug "response includes" "$obj" - record_id=$(echo "$obj" | sed 's/^\"id\":\([0-9]\+\).*/\1/') - break - fi - done <"$tmpfile" - - rm "$tmpfile" - - if [ -z "$record_id" ]; then - _info "TXT record, or $txtvalue not found, nothing to remove" - return 0 - fi - - #End of jq replacement - if ! _qc_rest DELETE "zones/$_domain_id/records/$record_id"; then - _info "Delete txt record error." - return 1 - fi - - _info "TXT Record ID: $record_id successfully deleted" - return 0 - -} - -#################### Private functions below ################################## -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -# _domain_id=sdjkglgdfewsdfg -_get_root() { - domain=$1 - i=1 - p=1 - - h=$(printf "%s" "$domain" | cut -d . -f2-) - _debug h "$h" - if [ -z "$h" ]; then - _err "$h ($domain) is an invalid domain" - return 1 - fi - - if ! _qc_rest GET "zones"; then - _err "qc_rest failed" - return 1 - fi - - if _contains "$response" "\"name\":\"$h\"" || _contains "$response" "\"name\":\"$h.\""; then - _domain_id=$h - if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 - fi - _err "Empty domain_id $h" - return 1 - fi - _err "Missing domain_id $h" - return 1 -} - -_qc_rest() { - m=$1 - ep="$2" - data="$3" - _debug "$ep" - - email_trimmed=$(echo "$QC_API_EMAIL" | tr -d '"') - token_trimmed=$(echo "$QC_API_KEY" | tr -d '"') - - export _H1="Content-Type: application/json" - export _H2="X-Auth-Email: $email_trimmed" - export _H3="X-Auth-Key: $token_trimmed" - - if [ "$m" != "GET" ]; then - _debug data "$data" - response="$(_post "$data" "$QC_Api/$ep" "" "$m")" - else - response="$(_get "$QC_Api/$ep")" - fi - - if [ "$?" != "0" ]; then - _err "error $ep" - return 1 - fi - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_rackcorp.sh b/dnsapi/dns_rackcorp.sh index b8fc73ab..6aabfddc 100644 --- a/dnsapi/dns_rackcorp.sh +++ b/dnsapi/dns_rackcorp.sh @@ -1,14 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_rackcorp_info='RackCorp.com -Site: RackCorp.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rackcorp -Options: - RACKCORP_APIUUID API UUID. See Portal: ADMINISTRATION -> API - RACKCORP_APISECRET API Secret -Issues: github.com/acmesh-official/acme.sh/issues/3351 -Author: Stephen Dendtler -' + +# Provider: RackCorp (www.rackcorp.com) +# Author: Stephen Dendtler (sdendtler@rackcorp.com) +# Report Bugs here: https://github.com/senjoo/acme.sh +# Alternate email contact: support@rackcorp.com +# +# You'll need an API key (Portal: ADMINISTRATION -> API) +# Set the environment variables as below: +# +# export RACKCORP_APIUUID="UUIDHERE" +# export RACKCORP_APISECRET="SECRETHERE" +# RACKCORP_API_ENDPOINT="https://api.rackcorp.net/api/rest/v2.4/json.php" @@ -83,7 +85,7 @@ _get_root() { return 1 fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug searchhost "$h" if [ -z "$h" ]; then _err "Could not find domain for record $domain in RackCorp using the provided credentials" @@ -95,7 +97,7 @@ _get_root() { if _contains "$response" "\"matches\":1"; then if _contains "$response" "\"name\":\"$h\""; then - _lookup=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _lookup=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_rackspace.sh b/dnsapi/dns_rackspace.sh index 05ec14a6..b50d9168 100644 --- a/dnsapi/dns_rackspace.sh +++ b/dnsapi/dns_rackspace.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_rackspace_info='RackSpace.com -Site: RackSpace.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_rackspace -Options: - RACKSPACE_Apikey API Key - RACKSPACE_Username Username -Issues: github.com/acmesh-official/acme.sh/issues/2091 -' +# +# +#RACKSPACE_Username="" +# +#RACKSPACE_Apikey="" RACKSPACE_Endpoint="https://dns.api.rackspacecloud.com/v1.0" @@ -72,7 +68,7 @@ _get_root_zone() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -88,7 +84,7 @@ _get_root_zone() { _domain_id=$(echo "$response" | sed -n "s/^.*\"id\":\"\([^,]*\)\",\"accountId\":\"[0-9]*\",\"name\":\"$h\",.*/\1/p") _debug2 domain_id "$_domain_id" if [ -n "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_rage4.sh b/dnsapi/dns_rage4.sh index b9abff17..4af4541d 100755 --- a/dnsapi/dns_rage4.sh +++ b/dnsapi/dns_rage4.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_rage4_info='rage4.com -Site: rage4.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_rage4 -Options: - RAGE4_TOKEN API Key - RAGE4_USERNAME Username -Issues: github.com/acmesh-official/acme.sh/issues/4306 -' + +# +#RAGE4_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#RAGE4_USERNAME="xxxx@sss.com" RAGE4_Api="https://rage4.com/rapi/" @@ -42,14 +38,6 @@ dns_rage4_add() { _debug _domain_id "$_domain_id" _rage4_rest "createrecord/?id=$_domain_id&name=$fulldomain&content=$unquotedtxtvalue&type=TXT&active=true&ttl=1" - - # Response after adding a TXT record should be something like this: - # {"status":true,"id":28160443,"error":null} - if ! _contains "$response" '"error":null' >/dev/null; then - _err "Error while adding TXT record: '$response'" - return 1 - fi - return 0 } @@ -71,12 +59,7 @@ dns_rage4_rm() { _debug "Getting txt records" _rage4_rest "getrecords/?id=${_domain_id}" - _record_id=$(echo "$response" | tr '{' '\n' | grep '"TXT"' | grep "\"$txtvalue" | sed -n 's/.*"id":\([0-9][0-9]*\),.*/\1/p') - if [ -z "$_record_id" ]; then - _err "error retrieving the record_id of the new TXT record in order to delete it, got: '$_record_id'." - return 1 - fi - + _record_id=$(echo "$response" | sed -rn 's/.*"id":([[:digit:]]+)[^\}]*'"$txtvalue"'.*/\1/p') _rage4_rest "deleterecord/?id=${_record_id}" return 0 } @@ -118,7 +101,8 @@ _rage4_rest() { token_trimmed=$(echo "$RAGE4_TOKEN" | tr -d '"') auth=$(printf '%s:%s' "$username_trimmed" "$token_trimmed" | _base64) - export _H1="Authorization: Basic $auth" + export _H1="Content-Type: application/json" + export _H2="Authorization: Basic $auth" response="$(_get "$RAGE4_Api$ep")" diff --git a/dnsapi/dns_rcode0.sh b/dnsapi/dns_rcode0.sh index 4ffdf572..d3f7f219 100755 --- a/dnsapi/dns_rcode0.sh +++ b/dnsapi/dns_rcode0.sh @@ -1,20 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_rcode0_info='Rcode0 rcodezero.at -Site: rcodezero.at -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rcode0 -Options: - RCODE0_URL API URL. E.g. "https://my.rcodezero.at" - RCODE0_API_TOKEN API Token - RCODE0_TTL TTL. Default: "60". -Issues: github.com/acmesh-official/acme.sh/issues/2490 -' #Rcode0 API Integration #https://my.rcodezero.at/api-doc # # log into https://my.rcodezero.at/enableapi and get your ACME API Token (the ACME API token has limited # access to the REST calls needed for acme.sh only) +# +#RCODE0_URL="https://my.rcodezero.at" +#RCODE0_API_TOKEN="0123456789ABCDEF" +#RCODE0_TTL=60 DEFAULT_RCODE0_URL="https://my.rcodezero.at" DEFAULT_RCODE0_TTL=60 @@ -171,7 +165,7 @@ _get_root() { i=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug "try to find: $h" if _rcode0_rest "GET" "/api/v1/acme/zones/$h"; then @@ -189,7 +183,7 @@ _get_root() { if [ -z "$h" ]; then return 1 fi - i=$(_math "$i" + 1) + i=$(_math $i + 1) done _debug "no matching domain for $domain found" diff --git a/dnsapi/dns_regru.sh b/dnsapi/dns_regru.sh index edf8b464..8ff380f0 100644 --- a/dnsapi/dns_regru.sh +++ b/dnsapi/dns_regru.sh @@ -1,13 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_regru_info='reg.ru -Site: reg.ru -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_regru -Options: - REGRU_API_Username Username - REGRU_API_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/2336 -' + +# +# REGRU_API_Username="test" +# +# REGRU_API_Password="test" +# REGRU_API_URL="https://api.reg.ru/api/regru2" @@ -96,8 +93,8 @@ _get_root() { for ITEM in ${domains_list}; do IDN_ITEM=${ITEM} - case ".${domain}" in - *.${IDN_ITEM}*) + case "${domain}" in + *${IDN_ITEM}*) _domain="$(_idn "${ITEM}")" _debug _domain "${_domain}" return 0 diff --git a/dnsapi/dns_rltx.sh b/dnsapi/dns_rltx.sh deleted file mode 100644 index 065ac177..00000000 --- a/dnsapi/dns_rltx.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_rltx_info='Realtox Media Cloudpanel DNS API -Site: realtoxmedia.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_rltx -Options: - RLTX_Key API Key - RLTX_OrganizationID Organization ID -' - -######## Public functions ##################### - -#Usage: dns_rltx_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_rltx_add() { - fulldomain=$1 - txtvalue=$2 - - _info "Using Realtox Media Cloudpanel DNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _rltx_init; then - return 1 - fi - - if ! _get_root "$fulldomain"; then - _err "Could not find matching DNS zone for $fulldomain" - return 1 - fi - - _debug _domain_id "$_domain_id" - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - - data="{\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"ttl\":120}" - if ! _rltx_rest POST "domains/$_domain_id/dns/acme-txt" "$data"; then - _err "Add TXT record request failed" - return 1 - fi - if _contains "$response" '"status":"added"'; then - _info "Added TXT record, OK" - return 0 - fi - _err "Add TXT record failed: $response" - return 1 -} - -#Usage: fulldomain txtvalue -#Remove the txt record after validation. -dns_rltx_rm() { - fulldomain=$1 - txtvalue=$2 - - _info "Using Realtox Media Cloudpanel DNS API" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - if ! _rltx_init; then - return 1 - fi - - if ! _get_root "$fulldomain"; then - _err "Could not find matching DNS zone for $fulldomain" - return 1 - fi - - _debug _domain_id "$_domain_id" - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - - data="{\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"ttl\":120}" - if ! _rltx_rest DELETE "domains/$_domain_id/dns/acme-txt" "$data"; then - _err "Remove TXT record request failed" - return 1 - fi - if _contains "$response" '"status":"removed"'; then - _info "Removed TXT record, OK" - return 0 - fi - _err "Remove TXT record failed: $response" - return 1 -} - -#################### Private functions below ################################## - -_rltx_init() { - RLTX_Key="${RLTX_Key:-$(_readaccountconf_mutable RLTX_Key)}" - RLTX_OrganizationID="${RLTX_OrganizationID:-$(_readaccountconf_mutable RLTX_OrganizationID)}" - - if [ -z "$RLTX_Key" ] || [ -z "$RLTX_OrganizationID" ]; then - RLTX_Key="" - RLTX_OrganizationID="" - _err "Please specify RLTX_Key and RLTX_OrganizationID." - _err "You can export them and retry: export RLTX_Key=... RLTX_OrganizationID=..." - return 1 - fi - - _saveaccountconf_mutable RLTX_Key "$RLTX_Key" - _saveaccountconf_mutable RLTX_OrganizationID "$RLTX_OrganizationID" -} - -_get_root() { - domain=$1 - fqdn_encoded="$(printf "%s" "$domain" | _url_encode)" - if ! _rltx_rest GET "domains/dns/acme-zone?fqdn=$fqdn_encoded"; then - return 1 - fi - if ! _contains "$response" '"domain_id":"'; then - return 1 - fi - - _domain_id="$(printf "%s" "$response" | _egrep_o '"domain_id":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)" - _domain="$(printf "%s" "$response" | _egrep_o '"zone":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)" - _sub_domain="$(printf "%s" "$response" | _egrep_o '"record_name":"[^"]*"' | cut -d : -f 2 | tr -d '"' | _head_n 1)" - - if [ -z "$_domain_id" ] || [ -z "$_domain" ] || [ -z "$_sub_domain" ]; then - return 1 - fi - return 0 -} - -_rltx_rest() { - m=$1 - ep="$2" - data="$3" - _debug "$ep" - - export _H1="X-API-Key: $RLTX_Key" - export _H2="X-Organization-ID: $RLTX_OrganizationID" - export _H3="Content-Type: application/json" - - if [ "$m" = "GET" ]; then - response="$(_get "https://api.ccp.realtoxmedia.de/api/$ep")" - else - _debug2 data "$data" - response="$(_post "$data" "https://api.ccp.realtoxmedia.de/api/$ep" "" "$m")" - fi - - if [ "$?" != "0" ]; then - _err "Realtox Media Cloudpanel API request failed: $ep" - return 1 - fi - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_scaleway.sh b/dnsapi/dns_scaleway.sh index 4cbf68d2..a0a0f318 100755 --- a/dnsapi/dns_scaleway.sh +++ b/dnsapi/dns_scaleway.sh @@ -1,15 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_scaleway_info='ScaleWay.com -Site: ScaleWay.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_scaleway -Options: - SCALEWAY_API_TOKEN API Token -Issues: github.com/acmesh-official/acme.sh/issues/3295 -' # Scaleway API # https://developers.scaleway.com/en/products/domain/dns/api/ +# +# Requires Scaleway API token set in SCALEWAY_API_TOKEN ######## Public functions ##################### @@ -41,7 +35,9 @@ dns_scaleway_add() { _err error "$response" return 1 fi + _info "Record added." + return 0 } dns_scaleway_rm() { @@ -69,7 +65,9 @@ dns_scaleway_rm() { _err error "$response" return 1 fi + _info "Record deleted." + return 0 } #################### Private functions below ################################## @@ -100,7 +98,7 @@ _get_root() { i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -109,7 +107,7 @@ _get_root() { _scaleway_rest GET "dns-zones/$h/records" if ! _contains "$response" "subdomain not found" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_schlundtech.sh b/dnsapi/dns_schlundtech.sh index 21930110..399c50e0 100644 --- a/dnsapi/dns_schlundtech.sh +++ b/dnsapi/dns_schlundtech.sh @@ -1,14 +1,16 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_schlundtech_info='SchlundTech.de -Site: SchlundTech.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_schlundtech -Options: - SCHLUNDTECH_USER Username - SCHLUNDTECH_PASSWORD Password -Issues: github.com/acmesh-official/acme.sh/issues/2246 -Author: @mod242 -' +# -*- mode: sh; tab-width: 2; indent-tabs-mode: s; coding: utf-8 -*- + +# Schlundtech DNS API +# Author: mod242 +# Created: 2019-40-29 +# Completly based on the autoDNS xml api wrapper by auerswald@gmail.com +# +# export SCHLUNDTECH_USER="username" +# export SCHLUNDTECH_PASSWORD="password" +# +# Usage: +# acme.sh --issue --dns dns_schlundtech -d example.com SCHLUNDTECH_API="https://gateway.schlundtech.de" @@ -106,7 +108,7 @@ _get_autodns_zone() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then @@ -124,7 +126,7 @@ _get_autodns_zone() { if _contains "$autodns_response" "1" >/dev/null; then _zone="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" _system_ns="$(echo "$autodns_response" | _egrep_o '[^<]*' | cut -d '>' -f 2 | cut -d '<' -f 1)" - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) return 0 fi diff --git a/dnsapi/dns_selectel.sh b/dnsapi/dns_selectel.sh index 8ba9a4fb..1b09882d 100644 --- a/dnsapi/dns_selectel.sh +++ b/dnsapi/dns_selectel.sh @@ -1,25 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_selectel_info='Selectel.com -Domains: Selectel.ru -Site: Selectel.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_selectel -Options: For old API version v1 (deprecated) - SL_Ver API version. Use "v1". - SL_Key API Key -OptionsAlt: For the current API version v2 - SL_Ver API version. Use "v2". - SL_Login_ID Account ID - SL_Project_Name Project name - SL_Login_Name Service user name - SL_Pswd Service user password - SL_Expire Token lifetime. In minutes (0-1440). Default "1400" -Issues: github.com/acmesh-official/acme.sh/issues/5126 -' -SL_Api="https://api.selectel.ru/domains" -auth_uri="https://cloud.api.selcloud.ru/identity/v3/auth/tokens" -_sl_sep='#' +# +#SL_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# + +SL_Api="https://api.selectel.ru/domains/v1" ######## Public functions ##################### @@ -28,14 +13,17 @@ dns_selectel_add() { fulldomain=$1 txtvalue=$2 - if ! _sl_init_vars; then + SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" + + if [ -z "$SL_Key" ]; then + SL_Key="" + _err "You don't specify selectel.ru api key yet." + _err "Please create you key and try again." return 1 fi - _debug2 SL_Ver "$SL_Ver" - _debug2 SL_Expire "$SL_Expire" - _debug2 SL_Login_Name "$SL_Login_Name" - _debug2 SL_Login_ID "$SL_Login_ID" - _debug2 SL_Project_Name "$SL_Project_Name" + + #save the api key to the account conf file. + _saveaccountconf_mutable SL_Key "$SL_Key" _debug "First detect the root zone" if ! _get_root "$fulldomain"; then @@ -47,63 +35,11 @@ dns_selectel_add() { _debug _domain "$_domain" _info "Adding record" - if [ "$SL_Ver" = "v2" ]; then - _ext_srv1="/zones/" - _ext_srv2="/rrset/" - _text_tmp=$(echo "$txtvalue" | sed -En "s/[\"]*([^\"]*)/\1/p") - _text_tmp='\"'$_text_tmp'\"' - _data="{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"${fulldomain}.\", \"records\": [{\"content\":\"$_text_tmp\"}]}" - elif [ "$SL_Ver" = "v1" ]; then - _ext_srv1="/" - _ext_srv2="/records/" - _data="{\"type\":\"TXT\",\"ttl\":60,\"name\":\"$fulldomain\",\"content\":\"$txtvalue\"}" - else - _err "Error. Unsupported version API $SL_Ver" - return 1 - fi - _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}" - _debug _ext_uri "$_ext_uri" - _debug _data "$_data" - - if _sl_rest POST "$_ext_uri" "$_data"; then - if _contains "$response" "$txtvalue"; then + if _sl_rest POST "/$_domain_id/records/" "{\"type\": \"TXT\", \"ttl\": 60, \"name\": \"$fulldomain\", \"content\": \"$txtvalue\"}"; then + if _contains "$response" "$txtvalue" || _contains "$response" "record_already_exists"; then _info "Added, OK" return 0 fi - if _contains "$response" "already_exists"; then - # record TXT with $fulldomain already exists - if [ "$SL_Ver" = "v2" ]; then - # It is necessary to add one more content to the comments - # read all records rrset - _debug "Getting txt records" - _sl_rest GET "${_ext_uri}" - # There is already a $txtvalue value, no need to add it - if _contains "$response" "$txtvalue"; then - _info "Added, OK" - _info "Txt record ${fulldomain} with value ${txtvalue} already exists" - return 0 - fi - # group \1 - full record rrset; group \2 - records attribute value, exactly {"content":"\"value1\""},{"content":"\"value2\""}",... - _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\1/p")" - _record_array="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*${fulldomain}[^}]*records[^}]*\[(\{[^]]*\})\][^}]*}).*/\2/p")" - # record id - _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")" - # preparing _data - _tmp_str="${_record_array},{\"content\":\"${_text_tmp}\"}" - _data="{\"ttl\": 60, \"records\": [${_tmp_str}]}" - _debug2 _record_seg "$_record_seg" - _debug2 _record_array "$_record_array" - _debug2 _record_array "$_record_id" - _debug "New data for record" "$_data" - if _sl_rest PATCH "${_ext_uri}${_record_id}" "$_data"; then - _info "Added, OK" - return 0 - fi - elif [ "$SL_Ver" = "v1" ]; then - _info "Added, OK" - return 0 - fi - fi fi _err "Add txt record error." return 1 @@ -114,15 +50,15 @@ dns_selectel_rm() { fulldomain=$1 txtvalue=$2 - if ! _sl_init_vars "nosave"; then + SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" + + if [ -z "$SL_Key" ]; then + SL_Key="" + _err "You don't specify slectel api key yet." + _err "Please create you key and try again." return 1 fi - _debug2 SL_Ver "$SL_Ver" - _debug2 SL_Expire "$SL_Expire" - _debug2 SL_Login_Name "$SL_Login_Name" - _debug2 SL_Login_ID "$SL_Login_ID" - _debug2 SL_Project_Name "$SL_Project_Name" - # + _debug "First detect the root zone" if ! _get_root "$fulldomain"; then _err "invalid domain" @@ -131,195 +67,91 @@ dns_selectel_rm() { _debug _domain_id "$_domain_id" _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - # - if [ "$SL_Ver" = "v2" ]; then - _ext_srv1="/zones/" - _ext_srv2="/rrset/" - elif [ "$SL_Ver" = "v1" ]; then - _ext_srv1="/" - _ext_srv2="/records/" - else - _err "Error. Unsupported version API $SL_Ver" - return 1 - fi - # + _debug "Getting txt records" - _ext_uri="${_ext_srv1}$_domain_id${_ext_srv2}" - _debug _ext_uri "$_ext_uri" - _sl_rest GET "${_ext_uri}" - # + _sl_rest GET "/${_domain_id}/records/" + if ! _contains "$response" "$txtvalue"; then _err "Txt record not found" return 1 fi - # - if [ "$SL_Ver" = "v2" ]; then - _record_seg="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\1/gp")" - _record_arr="$(echo "$response" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/p")" - elif [ "$SL_Ver" = "v1" ]; then - _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")" - else - _err "Error. Unsupported version API $SL_Ver" - return 1 - fi + + _record_seg="$(echo "$response" | _egrep_o "[^{]*\"content\" *: *\"$txtvalue\"[^}]*}")" _debug2 "_record_seg" "$_record_seg" if [ -z "$_record_seg" ]; then _err "can not find _record_seg" return 1 fi - # record id - # the following lines change the algorithm for deleting records with the value $txtvalue - # if you use the 1st line, then all such records are deleted at once - # if you use the 2nd line, then only the first entry from them is deleted - #_record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"")" - _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2 | tr -d "\"" | sed '1!d')" + + _record_id="$(echo "$_record_seg" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\"" | cut -d : -f 2)" + _debug2 "_record_id" "$_record_id" if [ -z "$_record_id" ]; then _err "can not find _record_id" return 1 fi - _debug2 "_record_id" "$_record_id" - # delete all record type TXT with text $txtvalue - if [ "$SL_Ver" = "v2" ]; then - # actual - _new_arr="$(echo "$_record_seg" | sed -En "s/.*(\{\"id\"[^}]*records[^[]*(\[(\{[^]]*${txtvalue}[^]]*)\])[^}]*}).*/\3/gp" | sed -En "s/(\},\{)/}\n{/gp" | sed "/${txtvalue}/d" | sed ":a;N;s/\n/,/;ta")" - # uri record for DEL or PATCH - _del_uri="${_ext_uri}${_record_id}" - _debug _del_uri "$_del_uri" - if [ -z "$_new_arr" ]; then - # remove record - if ! _sl_rest DELETE "${_del_uri}"; then - _err "Delete record error: ${_del_uri}." - else - info "Delete record success: ${_del_uri}." - fi - else - # update a record by removing one element in content - _data="{\"ttl\": 60, \"records\": [${_new_arr}]}" - _debug2 _data "$_data" - # REST API PATCH call - if _sl_rest PATCH "${_del_uri}" "$_data"; then - _info "Patched, OK: ${_del_uri}" - else - _err "Patched record error: ${_del_uri}." - fi - fi - else - # legacy - for _one_id in $_record_id; do - _del_uri="${_ext_uri}${_one_id}" - _debug _del_uri "$_del_uri" - if ! _sl_rest DELETE "${_del_uri}"; then - _err "Delete record error: ${_del_uri}." - else - info "Delete record success: ${_del_uri}." - fi - done + + if ! _sl_rest DELETE "/$_domain_id/records/$_record_id"; then + _err "Delete record error." + return 1 fi return 0 } #################### Private functions below ################################## - +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg _get_root() { domain=$1 - if [ "$SL_Ver" = 'v1' ]; then - # version API 1 - if ! _sl_rest GET "/"; then - return 1 - fi - i=2 - p=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - return 1 - fi - if _contains "$response" "\"name\" *: *\"$h\","; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - _debug "Getting domain id for $h" - if ! _sl_rest GET "/$h"; then - _err "Error read records of all domains $SL_Ver" - return 1 - fi - _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)" - return 0 - fi - p=$i - i=$(_math "$i" + 1) - done - _err "Error read records of all domains $SL_Ver" - return 1 - elif [ "$SL_Ver" = "v2" ]; then - # version API 2 - _ext_uri='/zones/' - domain="${domain}." - _debug "domain:: " "$domain" - # read records of all domains - if ! _sl_rest GET "$_ext_uri"; then - _err "Error read records of all domains $SL_Ver" - return 1 - fi - i=1 - p=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - if [ -z "$h" ]; then - _err "The domain was not found among the registered ones" - return 1 - fi - _domain_record=$(echo "$response" | sed -En "s/.*(\{[^}]*id[^}]*\"name\" *: *\"$h\"[^}]*}).*/\1/p") - _debug "_domain_record:: " "$_domain_record" - if [ -n "$_domain_record" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - _debug "Getting domain id for $h" - _domain_id=$(echo "$_domain_record" | sed -En "s/\{[^}]*\"id\" *: *\"([^\"]*)\"[^}]*\}/\1/p") - return 0 - fi - p=$i - i=$(_math "$i" + 1) - done - _err "Error read records of all domains $SL_Ver" - return 1 - else - _err "Error. Unsupported version API $SL_Ver" + if ! _sl_rest GET "/"; then return 1 fi + + i=2 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if _contains "$response" "\"name\" *: *\"$h\","; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + _debug "Getting domain id for $h" + if ! _sl_rest GET "/$h"; then + return 1 + fi + _domain_id="$(echo "$response" | tr "," "\n" | tr "}" "\n" | tr -d " " | grep "\"id\":" | cut -d : -f 2)" + return 0 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 } -################################################################# -# use: method add_url body _sl_rest() { m=$1 ep="$2" data="$3" + _debug "$ep" - _token=$(_get_auth_token) - if [ -z "$_token" ]; then - _err "BAD key or token $ep" - return 1 - fi - if [ "$SL_Ver" = v2 ]; then - _h1_name="X-Auth-Token" - else - _h1_name='X-Token' - fi - export _H1="${_h1_name}: ${_token}" + export _H1="X-Token: $SL_Key" export _H2="Content-Type: application/json" - _debug2 "Full URI: " "$SL_Api/${SL_Ver}${ep}" - _debug2 "_H1:" "$_H1" - _debug2 "_H2:" "$_H2" + if [ "$m" != "GET" ]; then _debug data "$data" - response="$(_post "$data" "$SL_Api/${SL_Ver}${ep}" "" "$m")" + response="$(_post "$data" "$SL_Api/$ep" "" "$m")" else - response="$(_get "$SL_Api/${SL_Ver}${ep}")" + response="$(_get "$SL_Api/$ep")" fi - # shellcheck disable=SC2181 + if [ "$?" != "0" ]; then _err "error $ep" return 1 @@ -327,152 +159,3 @@ _sl_rest() { _debug2 response "$response" return 0 } - -_get_auth_token() { - if [ "$SL_Ver" = 'v1' ]; then - # token for v1 - _debug "Token v1" - _token_keystone=$SL_Key - elif [ "$SL_Ver" = 'v2' ]; then - # token for v2. Get a token for calling the API - _debug "Keystone Token v2" - token_v2=$(_readaccountconf_mutable SL_Token_V2) - if [ -n "$token_v2" ]; then - # The structure with the token was considered. Let's check its validity - # field 1 - SL_Login_Name - # field 2 - token keystone - # field 3 - SL_Login_ID - # field 4 - SL_Project_Name - # field 5 - Receipt time - # separator - '$_sl_sep' - _login_name=$(_getfield "$token_v2" 1 "$_sl_sep") - _token_keystone=$(_getfield "$token_v2" 2 "$_sl_sep") - _project_name=$(_getfield "$token_v2" 4 "$_sl_sep") - _receipt_time=$(_getfield "$token_v2" 5 "$_sl_sep") - _login_id=$(_getfield "$token_v2" 3 "$_sl_sep") - _debug2 _login_name "$_login_name" - _debug2 _login_id "$_login_id" - _debug2 _project_name "$_project_name" - # check the validity of the token for the user and the project and its lifetime - _dt_diff_minute=$((($(date +%s) - _receipt_time) / 60)) - _debug2 _dt_diff_minute "$_dt_diff_minute" - [ "$_dt_diff_minute" -gt "$SL_Expire" ] && unset _token_keystone - if [ "$_project_name" != "$SL_Project_Name" ] || [ "$_login_name" != "$SL_Login_Name" ] || [ "$_login_id" != "$SL_Login_ID" ]; then - unset _token_keystone - fi - _debug "Get exists token" - fi - if [ -z "$_token_keystone" ]; then - # the previous token is incorrect or was not received, get a new one - _debug "Update (get new) token" - _data_auth="{\"auth\":{\"identity\":{\"methods\":[\"password\"],\"password\":{\"user\":{\"name\":\"${SL_Login_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"},\"password\":\"${SL_Pswd}\"}}},\"scope\":{\"project\":{\"name\":\"${SL_Project_Name}\",\"domain\":{\"name\":\"${SL_Login_ID}\"}}}}}" - export _H1="Content-Type: application/json" - _result=$(_post "$_data_auth" "$auth_uri") - _token_keystone=$(grep 'x-subject-token' "$HTTP_HEADER" | cut -d ':' -f 2- | tr -d ' \t\r') - _dt_curr=$(date +%s) - SL_Token_V2="${SL_Login_Name}${_sl_sep}${_token_keystone}${_sl_sep}${SL_Login_ID}${_sl_sep}${SL_Project_Name}${_sl_sep}${_dt_curr}" - _saveaccountconf_mutable SL_Token_V2 "$SL_Token_V2" - fi - else - # token set empty for unsupported version API - _token_keystone="" - fi - printf -- "%s" "$_token_keystone" -} - -################################################################# -# use: [non_save] -_sl_init_vars() { - _non_save="${1}" - _debug2 _non_save "$_non_save" - - _debug "First init variables" - # version API - SL_Ver="${SL_Ver:-$(_readaccountconf_mutable SL_Ver)}" - if [ -z "$SL_Ver" ]; then - SL_Ver="v1" - fi - if ! [ "$SL_Ver" = "v1" ] && ! [ "$SL_Ver" = "v2" ]; then - _err "You don't specify selectel.ru API version." - _err "Please define specify API version." - fi - _debug2 SL_Ver "$SL_Ver" - if [ "$SL_Ver" = "v1" ]; then - # token - SL_Key="${SL_Key:-$(_readaccountconf_mutable SL_Key)}" - - if [ -z "$SL_Key" ]; then - SL_Key="" - _err "You don't specify selectel.ru api key yet." - _err "Please create you key and try again." - return 1 - fi - #save the api key to the account conf file. - if [ -z "$_non_save" ]; then - _saveaccountconf_mutable SL_Key "$SL_Key" - fi - elif [ "$SL_Ver" = "v2" ]; then - # time expire token - SL_Expire="${SL_Expire:-$(_readaccountconf_mutable SL_Expire)}" - if [ -z "$SL_Expire" ]; then - SL_Expire=1400 # 23h 20 min - fi - if [ -z "$_non_save" ]; then - _saveaccountconf_mutable SL_Expire "$SL_Expire" - fi - # login service user - SL_Login_Name="${SL_Login_Name:-$(_readaccountconf_mutable SL_Login_Name)}" - if [ -z "$SL_Login_Name" ]; then - SL_Login_Name='' - _err "You did not specify the selectel.ru API service user name." - _err "Please provide a service user name and try again." - return 1 - fi - if [ -z "$_non_save" ]; then - _saveaccountconf_mutable SL_Login_Name "$SL_Login_Name" - fi - # user ID - SL_Login_ID="${SL_Login_ID:-$(_readaccountconf_mutable SL_Login_ID)}" - if [ -z "$SL_Login_ID" ]; then - SL_Login_ID='' - _err "You did not specify the selectel.ru API user ID." - _err "Please provide a user ID and try again." - return 1 - fi - if [ -z "$_non_save" ]; then - _saveaccountconf_mutable SL_Login_ID "$SL_Login_ID" - fi - # project name - SL_Project_Name="${SL_Project_Name:-$(_readaccountconf_mutable SL_Project_Name)}" - if [ -z "$SL_Project_Name" ]; then - SL_Project_Name='' - _err "You did not specify the project name." - _err "Please provide a project name and try again." - return 1 - fi - if [ -z "$_non_save" ]; then - _saveaccountconf_mutable SL_Project_Name "$SL_Project_Name" - fi - # service user password - SL_Pswd="${SL_Pswd:-$(_readaccountconf_mutable SL_Pswd)}" - if [ -z "$SL_Pswd" ]; then - SL_Pswd='' - _err "You did not specify the service user password." - _err "Please provide a service user password and try again." - return 1 - fi - if [ -z "$_non_save" ]; then - _saveaccountconf_mutable SL_Pswd "$SL_Pswd" "12345678" - fi - else - SL_Ver="" - _err "You also specified the wrong version of the selectel.ru API." - _err "Please provide the correct API version and try again." - return 1 - fi - if [ -z "$_non_save" ]; then - _saveaccountconf_mutable SL_Ver "$SL_Ver" - fi - - return 0 -} diff --git a/dnsapi/dns_selfhost.sh b/dnsapi/dns_selfhost.sh index 25130146..a6ef1f94 100644 --- a/dnsapi/dns_selfhost.sh +++ b/dnsapi/dns_selfhost.sh @@ -1,16 +1,8 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_selfhost_info='SelfHost.de -Site: SelfHost.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_selfhost -Options: - SELFHOSTDNS_USERNAME Username - SELFHOSTDNS_PASSWORD Password - SELFHOSTDNS_MAP Subdomain name - SELFHOSTDNS_UPDATE_URL API url. Optional. Default "https://account.selfhost.de/cgi-bin/api.pl" -Issues: github.com/acmesh-official/acme.sh/issues/4291 -Author: Marvin Edeler -' +# +# Author: Marvin Edeler +# Report Bugs here: https://github.com/Marvo2011/acme.sh/issues/1 +# Last Edit: 17.02.2022 dns_selfhost_add() { fulldomain=$1 @@ -19,11 +11,9 @@ dns_selfhost_add() { _debug fulldomain "$fulldomain" _debug txtvalue "$txt" - DEFAULT_SELFHOSTDNS_UPDATE_URL="https://account.selfhost.de/cgi-bin/api.pl" + SELFHOSTDNS_UPDATE_URL="https://selfhost.de/cgi-bin/api.pl" # Get values, but don't save until we successfully validated - SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$(_readaccountconf_mutable SELFHOSTDNS_UPDATE_URL)}" - SELFHOSTDNS_UPDATE_URL="${SELFHOSTDNS_UPDATE_URL:-$DEFAULT_SELFHOSTDNS_UPDATE_URL}" SELFHOSTDNS_USERNAME="${SELFHOSTDNS_USERNAME:-$(_readaccountconf_mutable SELFHOSTDNS_USERNAME)}" SELFHOSTDNS_PASSWORD="${SELFHOSTDNS_PASSWORD:-$(_readaccountconf_mutable SELFHOSTDNS_PASSWORD)}" # These values are domain dependent, so read them from there @@ -42,10 +32,7 @@ dns_selfhost_add() { # only match full domains (at the beginning of the string or with a leading whitespace), # e.g. don't match mytest.example.com or sub.test.example.com for test.example.com # if the domain is defined multiple times only the last occurance will be matched - # prepend a space to each line so "start of line" and "after whitespace" - # can both be matched as "after a space/tab" (portable BRE, no ERE (^|..)) - _selfhost_tab="$(printf '\t')" - mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*:\{0,1\}[0-9]*\).*/\1/p") + mapEntry=$(echo "$SELFHOSTDNS_MAP" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain)(:[[:digit:]]+)([:]?[[:digit:]]*)(.*)/\2\3\4/p") _debug2 mapEntry "$mapEntry" if test -z "$mapEntry"; then _err "SELFHOSTDNS_MAP must contain the fulldomain incl. prefix and at least one RID" @@ -57,7 +44,7 @@ dns_selfhost_add() { rid2=$(echo "$mapEntry" | cut -d: -f3) # read last used rid domain - lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed 's/^/ /' | sed -n "s/.*[ $_selfhost_tab]\($fulldomain:[0-9][0-9]*\).*/\1/p") + lastUsedRidForDomainEntry=$(echo "$SELFHOSTDNS_MAP_LAST_USED_INTERNAL" | sed -n -E "s/(^|^.*[[:space:]])($fulldomain:[[:digit:]]+)(.*)/\2/p") _debug2 lastUsedRidForDomainEntry "$lastUsedRidForDomainEntry" lastUsedRidForDomain=$(echo "$lastUsedRidForDomainEntry" | cut -d: -f2) @@ -90,11 +77,6 @@ dns_selfhost_add() { fi fi - # Save api url if different from default - if [ "$DEFAULT_SELFHOSTDNS_UPDATE_URL" != "$SELFHOSTDNS_UPDATE_URL" ]; then - _saveaccountconf_mutable SELFHOSTDNS_UPDATE_URL "$SELFHOSTDNS_UPDATE_URL" - fi - # Now that we know the values are good, save them _saveaccountconf_mutable SELFHOSTDNS_USERNAME "$SELFHOSTDNS_USERNAME" _saveaccountconf_mutable SELFHOSTDNS_PASSWORD "$SELFHOSTDNS_PASSWORD" diff --git a/dnsapi/dns_servercow.sh b/dnsapi/dns_servercow.sh index d6994681..52137905 100755 --- a/dnsapi/dns_servercow.sh +++ b/dnsapi/dns_servercow.sh @@ -1,14 +1,19 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_servercow_info='ServerCow.de -Site: ServerCow.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_servercow -Options: - SERVERCOW_API_Username Username - SERVERCOW_API_Password Password -Issues: github.com/jhartlep/servercow-dns-api/issues -Author: Jens Hartlep -' + +########## +# Custom servercow.de DNS API v1 for use with [acme.sh](https://github.com/acmesh-official/acme.sh) +# +# Usage: +# export SERVERCOW_API_Username=username +# export SERVERCOW_API_Password=password +# acme.sh --issue -d example.com --dns dns_servercow +# +# Issues: +# Any issues / questions / suggestions can be posted here: +# https://github.com/jhartlep/servercow-dns-api/issues +# +# Author: Jens Hartlep +########## SERVERCOW_API="https://api.servercow.de/dns/v1/domains" @@ -81,6 +86,7 @@ dns_servercow_add() { return 1 fi + return 1 } # Usage fulldomain txtvalue @@ -136,7 +142,7 @@ _get_root() { p=1 while true; do - _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-100) + _domain=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) _debug _domain "$_domain" if [ -z "$_domain" ]; then @@ -149,7 +155,7 @@ _get_root() { fi if ! _contains "$response" '"error":"no such domain in user context"' >/dev/null; then - _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-$p) if [ -z "$_sub_domain" ]; then # not valid return 1 diff --git a/dnsapi/dns_simply.sh b/dnsapi/dns_simply.sh index 74e891ad..6a8d0e18 100644 --- a/dnsapi/dns_simply.sh +++ b/dnsapi/dns_simply.sh @@ -1,14 +1,15 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_simply_info='Simply.com -Site: Simply.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_simply -Options: - SIMPLY_AccountName Account name - SIMPLY_ApiKey API Key -' -SIMPLY_Api="https://api.simply.com/2" +# API-integration for Simply.com (https://www.simply.com) + +#SIMPLY_AccountName="accountname" +#SIMPLY_ApiKey="apikey" +# +#SIMPLY_Api="https://api.simply.com/2/" +SIMPLY_Api_Default="https://api.simply.com/2" + +#This is used for determining success of REST call +SIMPLY_SUCCESS_CODE='"status":200' ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -68,16 +69,7 @@ dns_simply_rm() { return 1 fi - case "$_simply_http_code" in - 2*) ;; - *) - _err "Failed to fetch DNS records (HTTP $_simply_http_code)" - _err "$response" - return 1 - ;; - esac - - records=$(echo "$response" | tr '{' "\n" | grep -E 'record_id|type|data|name' | sed 's/\"record_id/;\"record_id/' | tr "\n" ' ' | tr -d ' ' | tr ';' ' ') + records=$(echo "$response" | tr '{' "\n" | grep 'record_id\|type\|data\|\name' | sed 's/\"record_id/;\"record_id/' | tr "\n" ' ' | tr -d ' ' | tr ';' ' ') nr_of_deleted_records=0 _info "Fetching txt record" @@ -100,7 +92,7 @@ dns_simply_rm() { if [ "$record_id" -gt 0 ]; then - if ! _simply_delete_record "$_domain" "$record_id"; then + if ! _simply_delete_record "$_domain" "$_sub_domain" "$record_id"; then _err "Record with id $record_id could not be deleted" return 1 fi @@ -127,9 +119,14 @@ dns_simply_rm() { #################### Private functions below ################################## _simply_load_config() { + SIMPLY_Api="${SIMPLY_Api:-$(_readaccountconf_mutable SIMPLY_Api)}" SIMPLY_AccountName="${SIMPLY_AccountName:-$(_readaccountconf_mutable SIMPLY_AccountName)}" SIMPLY_ApiKey="${SIMPLY_ApiKey:-$(_readaccountconf_mutable SIMPLY_ApiKey)}" + if [ -z "$SIMPLY_Api" ]; then + SIMPLY_Api="$SIMPLY_Api_Default" + fi + if [ -z "$SIMPLY_AccountName" ] || [ -z "$SIMPLY_ApiKey" ]; then SIMPLY_AccountName="" SIMPLY_ApiKey="" @@ -144,6 +141,9 @@ _simply_load_config() { } _simply_save_config() { + if [ "$SIMPLY_Api" != "$SIMPLY_Api_Default" ]; then + _saveaccountconf_mutable SIMPLY_Api "$SIMPLY_Api" + fi _saveaccountconf_mutable SIMPLY_AccountName "$SIMPLY_AccountName" _saveaccountconf_mutable SIMPLY_ApiKey "$SIMPLY_ApiKey" } @@ -160,39 +160,26 @@ _simply_get_all_records() { _get_root() { domain=$1 - - if ! _simply_rest GET "my/products/"; then - return 1 - fi - - case "$_simply_http_code" in - 2*) ;; - *) - _err "Failed to fetch product list (HTTP $_simply_http_code)" - _err "$response" - return 1 - ;; - esac - i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then + #not valid return 1 fi - _domain=$(printf "%s" "$response" | tr '}' '\n' | - grep -F -e "\"object\":\"$h\"" -e "\"name\":\"$h\"" -e "\"name_idn\":\"$h\"" | - sed -n 's/.*"object":"\([^"]*\)".*/\1/p' | - _head_n 1) - - if [ -n "$_domain" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - return 0 + if ! _simply_rest GET "my/products/$h/dns/"; then + return 1 fi - _debug "No Simply.com product found for $h" + if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then + _debug "$h not found" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain="$h" + return 0 + fi p="$i" i=$(_math "$i" + 1) done @@ -204,44 +191,39 @@ _simply_add_record() { sub_domain=$2 txtval=$3 - data="{\"name\": \"$sub_domain\", \"type\":\"TXT\", \"data\": \"$txtval\", \"priority\":0, \"ttl\": 120}" + data="{\"name\": \"$sub_domain\", \"type\":\"TXT\", \"data\": \"$txtval\", \"priority\":0, \"ttl\": 3600}" if ! _simply_rest POST "my/products/$domain/dns/records/" "$data"; then - _err "Adding record not successful!" + _err "Adding record not successfull!" return 1 fi - case "$_simply_http_code" in - 2*) ;; - *) - _err "Call to API not successful (HTTP $_simply_http_code), see below message for more details" + if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then + _err "Call to API not sucessfull, see below message for more details" _err "$response" return 1 - ;; - esac + fi return 0 } _simply_delete_record() { domain=$1 - record_id=$2 + sub_domain=$2 + record_id=$3 _debug record_id "Delete record with id $record_id" if ! _simply_rest DELETE "my/products/$domain/dns/records/$record_id/"; then - _err "Deleting record not successful!" + _err "Deleting record not successfull!" return 1 fi - case "$_simply_http_code" in - 2*) ;; - *) - _err "Call to API not successful (HTTP $_simply_http_code), see below message for more details" + if ! _contains "$response" "$SIMPLY_SUCCESS_CODE"; then + _err "Call to API not sucessfull, see below message for more details" _err "$response" return 1 - ;; - esac + fi return 0 } @@ -263,24 +245,17 @@ _simply_rest() { export _H2="Content-Type: application/json" - : >"$HTTP_HEADER" - if [ "$m" != "GET" ]; then response="$(_post "$data" "$SIMPLY_Api/$ep" "" "$m")" else response="$(_get "$SIMPLY_Api/$ep")" fi - _ret="$?" - unset _H1 _H2 - - if [ "$_ret" != "0" ]; then + if [ "$?" != "0" ]; then _err "error $ep" return 1 fi - _simply_http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d' ' -f2 | tr -d '\r\n')" - response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" diff --git a/dnsapi/dns_sitehost.sh b/dnsapi/dns_sitehost.sh deleted file mode 100755 index 94a0ee93..00000000 --- a/dnsapi/dns_sitehost.sh +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_sitehost_info='SiteHost -Site: sitehost.nz -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sitehost -Options: - SITEHOST_API_KEY API Key - SITEHOST_CLIENT_ID Client ID. The numeric client ID for your SiteHost account. -Issues: github.com/acmesh-official/acme.sh/issues/6892 -Author: Jordan Russell -' - -SITEHOST_API="https://api.sitehost.nz/1.5" - -######## Public functions ##################### - -# Usage: dns_sitehost_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_sitehost_add() { - fulldomain=$1 - txtvalue=$2 - - if ! _sitehost_load_creds; then - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - # SiteHost expects the full record name as the name parameter - _info "Adding TXT record for ${fulldomain}" - if _sitehost_rest POST "dns/add_record.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&domain=$(printf '%s' "${_domain}" | _url_encode)&type=TXT&name=$(printf '%s' "${fulldomain}" | _url_encode)&content=$(printf '%s' "${txtvalue}" | _url_encode)"; then - if _contains "$response" '"status":true'; then - _info "TXT record added successfully." - return 0 - fi - fi - - _err "Could not add TXT record for ${fulldomain}" - _err "$response" - return 1 -} - -# Usage: dns_sitehost_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -# Remove the txt record after validation. -dns_sitehost_rm() { - fulldomain=$1 - txtvalue=$2 - - if ! _sitehost_load_creds; then - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - return 1 - fi - - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _debug "Getting TXT records for ${_domain}" - if ! _sitehost_rest GET "dns/list_records.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&domain=$(printf '%s' "${_domain}" | _url_encode)"; then - _err "Could not list DNS records" - _err "$response" - return 1 - fi - - if ! _contains "$response" '"status":true'; then - _err "Error listing DNS records" - _err "$response" - return 1 - fi - - # Extract record ID matching our fulldomain, type TXT, and txtvalue - # Response format: {"return":[{"id":"123","name":"...","type":"TXT","content":"..."},...]} - # SiteHost returns flat single-line JSON objects in the records array - # Escape regex metacharacters in values before grep matching - _fulldomain_grep="$(printf "%s" "$fulldomain" | sed 's/[][\\.^$*]/\\&/g')" - _txtvalue_grep="$(printf "%s" "$txtvalue" | sed 's/[][\\.^$*]/\\&/g')" - # Use field-specific matching to avoid false positives from substring matches - _record_id="$(echo "$response" | _egrep_o '\{[^}]*\}' | grep '"name" *: *"'"${_fulldomain_grep}"'"' | grep '"type" *: *"TXT"' | grep '"content" *: *"'"${_txtvalue_grep}"'"' | _head_n 1 | _egrep_o '"id" *: *"?[0-9]+"?' | _egrep_o '[0-9]+')" - - if [ -z "$_record_id" ]; then - _info "TXT record not found, nothing to remove." - return 0 - fi - - _debug _record_id "$_record_id" - - _info "Deleting TXT record ${_record_id} for ${fulldomain}" - if _sitehost_rest POST "dns/delete_record.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&domain=$(printf '%s' "${_domain}" | _url_encode)&record_id=$(printf '%s' "${_record_id}" | _url_encode)"; then - if _contains "$response" '"status":true'; then - _info "TXT record deleted successfully." - return 0 - fi - fi - - _err "Could not delete TXT record for ${fulldomain}" - _err "$response" - return 1 -} - -#################### Private functions below ################################## - -_sitehost_load_creds() { - SITEHOST_API_KEY="${SITEHOST_API_KEY:-$(_readaccountconf_mutable SITEHOST_API_KEY)}" - SITEHOST_CLIENT_ID="${SITEHOST_CLIENT_ID:-$(_readaccountconf_mutable SITEHOST_CLIENT_ID)}" - - if [ -z "$SITEHOST_API_KEY" ] || [ -z "$SITEHOST_CLIENT_ID" ]; then - SITEHOST_API_KEY="" - SITEHOST_CLIENT_ID="" - _err "You didn't specify SITEHOST_API_KEY and/or SITEHOST_CLIENT_ID." - _err "Please export them and try again." - return 1 - fi - - _saveaccountconf_mutable SITEHOST_API_KEY "$SITEHOST_API_KEY" - _saveaccountconf_mutable SITEHOST_CLIENT_ID "$SITEHOST_CLIENT_ID" - return 0 -} - -#_acme-challenge.www.domain.com -#returns -# _sub_domain=_acme-challenge.www -# _domain=domain.com -_get_root() { - domain=$1 - - _debug "Getting domain list" - - # Fetch ALL pages of domains first so we can match the most specific zone - # (a more specific zone on a later page must take precedence over a broader match) - _all_domains="" - _page=1 - - while true; do - if ! _sitehost_rest GET "dns/list_domains.json" "client_id=$(printf '%s' "${SITEHOST_CLIENT_ID}" | _url_encode)&filters%5Bpage_number%5D=${_page}"; then - _err "Could not list domains" - return 1 - fi - - if ! _contains "$response" '"status":true'; then - _err "Error listing domains" - _err "$response" - return 1 - fi - - _all_domains="${_all_domains} ${response}" - - _total_pages=$(echo "$response" | _egrep_o '"total_pages" *: *[0-9]+' | _egrep_o '[0-9]+') - if [ -z "$_total_pages" ] || [ "$_page" -ge "$_total_pages" ]; then - break - fi - - _page=$(_math "$_page" + 1) - done - - # Try each subdomain level, most specific first - _i=1 - _p=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "${_i}"-100) - _debug h "$h" - if [ -z "$h" ]; then - return 1 - fi - - if echo "$_all_domains" | grep -F "\"${h}\"" >/dev/null 2>&1; then - if [ "$_i" = "1" ]; then - # DNS alias mode - fulldomain is the zone itself - _sub_domain="" - else - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"${_p}") - fi - _domain="${h}" - return 0 - fi - - _p="${_i}" - _i=$(_math "$_i" + 1) - done - - return 1 -} - -# Usage: _sitehost_rest method endpoint data -_sitehost_rest() { - m="$1" - ep="$2" - data="$3" - url="${SITEHOST_API}/${ep}" - - _debug url "$url" - - _apikey="$(printf "%s" "${SITEHOST_API_KEY}" | _url_encode)" - - if [ "$m" = "GET" ]; then - response="$(_get "${url}?apikey=${_apikey}&${data}")" - else - _debug2 data "$data" - response="$(_post "apikey=${_apikey}&${data}" "$url")" - fi - - if [ "$?" != "0" ]; then - _err "error ${ep}" - return 1 - fi - - response="$(printf '%s' "$response" | tr -d '\r')" - - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_sotoon.sh b/dnsapi/dns_sotoon.sh deleted file mode 100644 index b94a220f..00000000 --- a/dnsapi/dns_sotoon.sh +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_sotoon_info='Sotoon.ir -Site: Sotoon.ir -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_sotoon -Options: - Sotoon_Token API Token - Sotoon_WorkspaceUUID Workspace UUID -Issues: github.com/acmesh-official/acme.sh/issues/6656 -Author: Erfan Gholizade -' - -SOTOON_API_URL="https://api.sotoon.ir/delivery/v2.1/global" - -######## Public functions ##################### - -#Adding the txt record for validation. -#Usage: dns_sotoon_add fulldomain TXT_record -#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_sotoon_add() { - fulldomain=$1 - txtvalue=$2 - _info_sotoon "Using Sotoon" - - Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" - Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" - - if [ -z "$Sotoon_Token" ]; then - _err_sotoon "You didn't specify \"Sotoon_Token\" token yet." - _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/tokens" - return 1 - fi - if [ -z "$Sotoon_WorkspaceUUID" ]; then - _err_sotoon "You didn't specify \"Sotoon_WorkspaceUUID\" Workspace UUID yet." - _err_sotoon "You can get yours from here https://ocean.sotoon.ir/profile/workspaces" - return 1 - fi - - #save the info to the account conf file. - _saveaccountconf_mutable Sotoon_Token "$Sotoon_Token" - _saveaccountconf_mutable Sotoon_WorkspaceUUID "$Sotoon_WorkspaceUUID" - - _debug_sotoon "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err_sotoon "invalid domain" - return 1 - fi - - _info_sotoon "Adding record" - - _debug_sotoon _domain_id "$_domain_id" - _debug_sotoon _sub_domain "$_sub_domain" - _debug_sotoon _domain "$_domain" - - # First, GET the current domain zone to check for existing TXT records - # This is needed for wildcard certs which require multiple TXT values - _info_sotoon "Checking for existing TXT records" - if ! _sotoon_rest GET "$_domain_id"; then - _err_sotoon "Failed to get domain zone" - return 1 - fi - - # Check if there are existing TXT records for this subdomain - _existing_txt="" - if _contains "$response" "\"$_sub_domain\""; then - _debug_sotoon "Found existing records for $_sub_domain" - # Extract existing TXT values from the response - # The format is: "_acme-challenge":[{"TXT":"value1","type":"TXT","ttl":10},{"TXT":"value2",...}] - _existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") - _debug_sotoon "Existing TXT records: $_existing_txt" - fi - - # Build the new record entry - _new_record="{\"TXT\":\"$txtvalue\",\"type\":\"TXT\",\"ttl\":120}" - - # If there are existing records, append to them; otherwise create new array - if [ -n "$_existing_txt" ] && [ "$_existing_txt" != "[]" ] && [ "$_existing_txt" != "null" ]; then - # Check if this exact TXT value already exists (avoid duplicates) - if _contains "$_existing_txt" "\"$txtvalue\""; then - _info_sotoon "TXT record already exists, skipping" - return 0 - fi - # Remove the closing bracket and append new record - _combined_records="$(echo "$_existing_txt" | sed 's/]$//'),$_new_record]" - _debug_sotoon "Combined records: $_combined_records" - else - # No existing records, create new array - _combined_records="[$_new_record]" - fi - - # Prepare the DNS record data in Kubernetes CRD format - _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_combined_records}}}" - - _debug_sotoon "DNS record payload: $_dns_record" - - # Use PATCH to update/add the record to the domain zone - _info_sotoon "Updating domain zone $_domain_id with TXT record" - if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then - if _contains "$response" "$txtvalue" || _contains "$response" "\"$_sub_domain\""; then - _info_sotoon "Added, OK" - return 0 - else - _debug_sotoon "Response: $response" - _err_sotoon "Add txt record error." - return 1 - fi - fi - - _err_sotoon "Add txt record error." - return 1 -} - -#Remove the txt record after validation. -#Usage: dns_sotoon_rm fulldomain TXT_record -#Usage: dns_sotoon_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_sotoon_rm() { - fulldomain=$1 - txtvalue=$2 - _info_sotoon "Using Sotoon" - _debug_sotoon fulldomain "$fulldomain" - _debug_sotoon txtvalue "$txtvalue" - - Sotoon_Token="${Sotoon_Token:-$(_readaccountconf_mutable Sotoon_Token)}" - Sotoon_WorkspaceUUID="${Sotoon_WorkspaceUUID:-$(_readaccountconf_mutable Sotoon_WorkspaceUUID)}" - - _debug_sotoon "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err_sotoon "invalid domain" - return 1 - fi - _debug_sotoon _domain_id "$_domain_id" - _debug_sotoon _sub_domain "$_sub_domain" - _debug_sotoon _domain "$_domain" - - _info_sotoon "Removing TXT record" - - # First, GET the current domain zone to check for existing TXT records - if ! _sotoon_rest GET "$_domain_id"; then - _err_sotoon "Failed to get domain zone" - return 1 - fi - - # Check if there are existing TXT records for this subdomain - _existing_txt="" - if _contains "$response" "\"$_sub_domain\""; then - _debug_sotoon "Found existing records for $_sub_domain" - _existing_txt=$(echo "$response" | _egrep_o "\"$_sub_domain\":\[[^]]*\]" | sed "s/\"$_sub_domain\"://") - _debug_sotoon "Existing TXT records: $_existing_txt" - fi - - # If no existing records, nothing to remove - if [ -z "$_existing_txt" ] || [ "$_existing_txt" = "[]" ] || [ "$_existing_txt" = "null" ]; then - _info_sotoon "No TXT records found, nothing to remove" - return 0 - fi - - # Remove the specific TXT value from the array - # This handles the case where there are multiple TXT values (wildcard certs) - _remaining_records=$(echo "$_existing_txt" | sed "s/{\"TXT\":\"$txtvalue\"[^}]*},*//g" | sed 's/,]/]/g' | sed 's/\[,/[/g') - _debug_sotoon "Remaining records after removal: $_remaining_records" - - # If no records remain, set to null to remove the subdomain entirely - if [ "$_remaining_records" = "[]" ] || [ -z "$_remaining_records" ]; then - _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":null}}}" - else - _dns_record="{\"spec\":{\"records\":{\"$_sub_domain\":$_remaining_records}}}" - fi - - _debug_sotoon "Remove record payload: $_dns_record" - - # Use PATCH to remove the record from the domain zone - if _sotoon_rest PATCH "$_domain_id" "$_dns_record"; then - _info_sotoon "Record removed, OK" - return 0 - else - _debug_sotoon "Response: $response" - _err_sotoon "Error removing record" - return 1 - fi -} - -#################### Private functions below ################################## - -_get_root() { - domain=$1 - i=1 - p=1 - - _debug_sotoon "Getting root domain for: $domain" - _debug_sotoon "Sotoon WorkspaceUUID: $Sotoon_WorkspaceUUID" - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug_sotoon "Checking domain part: $h" - - if [ -z "$h" ]; then - #not valid - _err_sotoon "Could not find valid domain" - return 1 - fi - - _debug_sotoon "Fetching domain zones from Sotoon API" - if ! _sotoon_rest GET ""; then - _err_sotoon "Failed to get domain zones from Sotoon API" - _err_sotoon "Please check your Sotoon_Token, Sotoon_WorkspaceUUID" - return 1 - fi - - _debug2_sotoon "API Response: $response" - - # Check if the response contains our domain - # Sotoon API uses Kubernetes CRD format with spec.origin for domain matching - if _contains "$response" "\"origin\":\"$h\""; then - _debug_sotoon "Found domain by origin: $h" - - # In Kubernetes CRD format, the metadata.name is the resource identifier - # The name can be either: - # 1. Same as origin - # 2. Origin with dots replaced by hyphens - # We check both patterns in the response to determine which one exists - - # Convert origin to hyphenated version for checking - _h_hyphenated=$(echo "$h" | tr '.' '-') - - # Check if the hyphenated name exists in the response - if _contains "$response" "\"name\":\"$_h_hyphenated\""; then - _domain_id="$_h_hyphenated" - _debug_sotoon "Found domain ID (hyphenated): $_domain_id" - # Check if the origin itself is used as name - elif _contains "$response" "\"name\":\"$h\""; then - _domain_id="$h" - _debug_sotoon "Found domain ID (same as origin): $_domain_id" - else - # Fallback: use the hyphenated version (more common) - _domain_id="$_h_hyphenated" - _debug_sotoon "Using hyphenated domain ID as fallback: $_domain_id" - fi - - if [ -n "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - _debug_sotoon "Domain ID (metadata.name): $_domain_id" - _debug_sotoon "Sub domain: $_sub_domain" - _debug_sotoon "Domain (origin): $_domain" - return 0 - fi - _err_sotoon "Found domain $h but could not extract domain ID" - return 1 - fi - p=$i - i=$(_math "$i" + 1) - done - return 1 -} - -_sotoon_rest() { - mtd="$1" - resource_id="$2" - data="$3" - - token_trimmed=$(echo "$Sotoon_Token" | tr -d '"') - - # Construct the API endpoint - _api_path="$SOTOON_API_URL/workspaces/$Sotoon_WorkspaceUUID/domainzones" - - if [ -n "$resource_id" ]; then - _api_path="$_api_path/$resource_id" - fi - - _debug_sotoon "API Path: $_api_path" - _debug_sotoon "Method: $mtd" - - # Set authorization header - Sotoon API uses Bearer token - export _H1="Authorization: Bearer $token_trimmed" - - if [ "$mtd" = "GET" ]; then - # GET request - _debug_sotoon "GET" "$_api_path" - response="$(_get "$_api_path")" - elif [ "$mtd" = "PATCH" ]; then - # PATCH Request - export _H2="Content-Type: application/merge-patch+json" - _debug_sotoon data "$data" - response="$(_post "$data" "$_api_path" "" "$mtd")" - else - _err_sotoon "Unknown method: $mtd" - return 1 - fi - - _debug2_sotoon response "$response" - return 0 -} - -#Wrappers for logging -_info_sotoon() { - _info "[Sotoon]" "$@" -} - -_err_sotoon() { - _err "[Sotoon]" "$@" -} - -_debug_sotoon() { - _debug "[Sotoon]" "$@" -} - -_debug2_sotoon() { - _debug2 "[Sotoon]" "$@" -} diff --git a/dnsapi/dns_spaceship.sh b/dnsapi/dns_spaceship.sh deleted file mode 100644 index 8fff4037..00000000 --- a/dnsapi/dns_spaceship.sh +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_spaceship_info='Spaceship.com -Site: Spaceship.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_spaceship -Options: - SPACESHIP_API_KEY API Key - SPACESHIP_API_SECRET API Secret - SPACESHIP_ROOT_DOMAIN Root domain. Manually specify the root domain if auto-detection fails. Optional. -Issues: github.com/acmesh-official/acme.sh/issues/6304 -Author: Meow <@Meo597> -' - -# Spaceship API -# https://docs.spaceship.dev/ - -######## Public functions ##################### - -SPACESHIP_API_BASE="https://spaceship.dev/api/v1" - -# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -# Used to add txt record -dns_spaceship_add() { - fulldomain="$1" - txtvalue="$2" - - _info "Adding TXT record for $fulldomain with value $txtvalue" - - # Initialize API credentials and headers - if ! _spaceship_init; then - return 1 - fi - - # Detect root zone - if ! _get_root "$fulldomain"; then - return 1 - fi - - # Extract subdomain part relative to root domain - subdomain=$(echo "$fulldomain" | sed "s/\.$_domain$//") - if [ "$subdomain" = "$fulldomain" ]; then - _err "Failed to extract subdomain from $fulldomain relative to root domain $_domain" - return 1 - fi - _debug "Extracted subdomain: $subdomain for root domain: $_domain" - - # Escape txtvalue to prevent JSON injection (e.g., quotes in txtvalue) - escaped_txtvalue=$(echo "$txtvalue" | sed 's/"/\\"/g') - - # Prepare payload and URL for adding TXT record - # Note: 'name' in payload uses subdomain (e.g., _acme-challenge.sub) as required by Spaceship API - payload="{\"force\": true, \"items\": [{\"type\": \"TXT\", \"name\": \"$subdomain\", \"value\": \"$escaped_txtvalue\", \"ttl\": 600}]}" - url="$SPACESHIP_API_BASE/dns/records/$_domain" - - # Send API request - if _spaceship_api_request "PUT" "$url" "$payload"; then - _info "Successfully added TXT record for $fulldomain" - return 0 - else - _err "Failed to add TXT record. If the domain $_domain is incorrect, set SPACESHIP_ROOT_DOMAIN to the correct root domain." - return 1 - fi -} - -# Usage: fulldomain txtvalue -# Used to remove the txt record after validation -dns_spaceship_rm() { - fulldomain="$1" - txtvalue="$2" - - _info "Removing TXT record for $fulldomain with value $txtvalue" - - # Initialize API credentials and headers - if ! _spaceship_init; then - return 1 - fi - - # Detect root zone - if ! _get_root "$fulldomain"; then - return 1 - fi - - # Extract subdomain part relative to root domain - subdomain=$(echo "$fulldomain" | sed "s/\.$_domain$//") - if [ "$subdomain" = "$fulldomain" ]; then - _err "Failed to extract subdomain from $fulldomain relative to root domain $_domain" - return 1 - fi - _debug "Extracted subdomain: $subdomain for root domain: $_domain" - - # Escape txtvalue to prevent JSON injection - escaped_txtvalue=$(echo "$txtvalue" | sed 's/"/\\"/g') - - # Prepare payload and URL for deleting TXT record - # Note: 'name' in payload uses subdomain (e.g., _acme-challenge.sub) as required by Spaceship API - payload="[{\"type\": \"TXT\", \"name\": \"$subdomain\", \"value\": \"$escaped_txtvalue\"}]" - url="$SPACESHIP_API_BASE/dns/records/$_domain" - - # Send API request - if _spaceship_api_request "DELETE" "$url" "$payload"; then - _info "Successfully deleted TXT record for $fulldomain" - return 0 - else - _err "Failed to delete TXT record. If the domain $_domain is incorrect, set SPACESHIP_ROOT_DOMAIN to the correct root domain." - return 1 - fi -} - -#################### Private functions below ################################## - -_spaceship_init() { - SPACESHIP_API_KEY="${SPACESHIP_API_KEY:-$(_readaccountconf_mutable SPACESHIP_API_KEY)}" - SPACESHIP_API_SECRET="${SPACESHIP_API_SECRET:-$(_readaccountconf_mutable SPACESHIP_API_SECRET)}" - - if [ -z "$SPACESHIP_API_KEY" ] || [ -z "$SPACESHIP_API_SECRET" ]; then - _err "Spaceship API credentials are not set. Please set SPACESHIP_API_KEY and SPACESHIP_API_SECRET." - _err "Ensure \"$LE_CONFIG_HOME\" directory has restricted permissions (chmod 700 \"$LE_CONFIG_HOME\") to protect credentials." - return 1 - fi - - # Save credentials to account config for future renewals - _saveaccountconf_mutable SPACESHIP_API_KEY "$SPACESHIP_API_KEY" - _saveaccountconf_mutable SPACESHIP_API_SECRET "$SPACESHIP_API_SECRET" - - # Set common headers for API requests - export _H1="X-API-Key: $SPACESHIP_API_KEY" - export _H2="X-API-Secret: $SPACESHIP_API_SECRET" - export _H3="Content-Type: application/json" - return 0 -} - -_get_root() { - domain="$1" - - # Check manual override - SPACESHIP_ROOT_DOMAIN="${SPACESHIP_ROOT_DOMAIN:-$(_readdomainconf SPACESHIP_ROOT_DOMAIN)}" - if [ -n "$SPACESHIP_ROOT_DOMAIN" ]; then - _domain="$SPACESHIP_ROOT_DOMAIN" - _debug "Using manually specified or saved root domain: $_domain" - _savedomainconf SPACESHIP_ROOT_DOMAIN "$SPACESHIP_ROOT_DOMAIN" - return 0 - fi - - _debug "Detecting root zone for '$domain'" - - i=1 - p=1 - while true; do - _cutdomain=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - - _debug "Attempt i=$i: Checking if '$_cutdomain' is root zone (cut ret=$?)" - - if [ -z "$_cutdomain" ]; then - _debug "Cut resulted in empty string, root zone not found." - break - fi - - # Call the API to check if this _cutdomain is a manageable zone - if _spaceship_api_request "GET" "$SPACESHIP_API_BASE/dns/records/$_cutdomain?take=1&skip=0"; then - # API call succeeded (HTTP 200 OK for GET /dns/records) - _domain="$_cutdomain" - _debug "Root zone found: '$_domain'" - - # Save the detected root domain - _savedomainconf SPACESHIP_ROOT_DOMAIN "$_domain" - _info "Root domain '$_domain' saved to configuration for future use." - - return 0 - fi - - _debug "API check failed for '$_cutdomain'. Continuing search." - - p=$i - i=$((i + 1)) - done - - _err "Could not detect root zone for '$domain'. Please set SPACESHIP_ROOT_DOMAIN manually." - return 1 -} - -_spaceship_api_request() { - method="$1" - url="$2" - payload="$3" - - _debug2 "Sending $method request to $url with payload $payload" - if [ "$method" = "GET" ]; then - response="$(_get "$url")" - else - response="$(_post "$payload" "$url" "" "$method")" - fi - - if [ "$?" != "0" ]; then - _err "API request failed. Response: $response" - return 1 - fi - - _debug2 "API response body: $response" - - if [ "$method" = "GET" ]; then - if _contains "$(_head_n 1 <"$HTTP_HEADER")" '200'; then - return 0 - fi - else - if _contains "$(_head_n 1 <"$HTTP_HEADER")" '204'; then - return 0 - fi - fi - - _debug2 "API response header: $HTTP_HEADER" - return 1 -} diff --git a/dnsapi/dns_subreg.sh b/dnsapi/dns_subreg.sh deleted file mode 100644 index 5e7e7ced..00000000 --- a/dnsapi/dns_subreg.sh +++ /dev/null @@ -1,220 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_subreg_info='Subreg.cz -Site: subreg.cz -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_subreg -Options: - SUBREG_API_USERNAME API username - SUBREG_API_PASSWORD API password -Issues: github.com/acmesh-official/acme.sh/issues/6835 -Author: Tomas Pavlic -' - -# Subreg SOAP API -# https://subreg.cz/manual/ - -SUBREG_API_URL="https://soap.subreg.cz/cmd.php" - -######## Public functions ##################### - -# Usage: dns_subreg_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_subreg_add() { - fulldomain=$1 - txtvalue=$2 - - SUBREG_API_USERNAME="${SUBREG_API_USERNAME:-$(_readaccountconf_mutable SUBREG_API_USERNAME)}" - SUBREG_API_PASSWORD="${SUBREG_API_PASSWORD:-$(_readaccountconf_mutable SUBREG_API_PASSWORD)}" - if [ -z "$SUBREG_API_USERNAME" ] || [ -z "$SUBREG_API_PASSWORD" ]; then - _err "SUBREG_API_USERNAME and SUBREG_API_PASSWORD are not set." - return 1 - fi - - _saveaccountconf_mutable SUBREG_API_USERNAME "$SUBREG_API_USERNAME" - _saveaccountconf_mutable SUBREG_API_PASSWORD "$SUBREG_API_PASSWORD" - - if ! _subreg_login; then - return 1 - fi - - if ! _get_root "$fulldomain"; then - _err "Cannot determine root domain for: $fulldomain" - return 1 - fi - - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _subreg_soap "Add_DNS_Record" "$_domain$_sub_domainTXT$txtvalue0120" - if _subreg_ok; then - _record_id="$(_subreg_map_get record_id)" - - if [ -z "$_record_id" ]; then - _err "Subreg API did not return a record_id for TXT record on $fulldomain" - _err "$response" - return 1 - fi - - _savedomainconf "$(_subreg_record_id_key "$txtvalue")" "$_record_id" - return 0 - fi - _err "Failed to add TXT record." - _err "$response" - return 1 -} - -# Usage: dns_subreg_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_subreg_rm() { - fulldomain=$1 - txtvalue=$2 - - SUBREG_API_USERNAME="${SUBREG_API_USERNAME:-$(_readaccountconf_mutable SUBREG_API_USERNAME)}" - SUBREG_API_PASSWORD="${SUBREG_API_PASSWORD:-$(_readaccountconf_mutable SUBREG_API_PASSWORD)}" - if [ -z "$SUBREG_API_USERNAME" ] || [ -z "$SUBREG_API_PASSWORD" ]; then - _err "SUBREG_API_USERNAME and SUBREG_API_PASSWORD are not set." - return 1 - fi - - if ! _subreg_login; then - return 1 - fi - - if ! _get_root "$fulldomain"; then - _err "Cannot determine root domain for: $fulldomain" - return 1 - fi - - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _record_id="$(_readdomainconf "$(_subreg_record_id_key "$txtvalue")")" - if [ -z "$_record_id" ]; then - _err "Could not find saved record ID for $fulldomain" - return 1 - fi - - _debug "Deleting record ID: $_record_id" - _subreg_soap "Delete_DNS_Record" "$_domain$_record_id" - if _subreg_ok; then - - _cleardomainconf "$(_subreg_record_id_key "$txtvalue")" - return 0 - fi - - _err "Failed to delete TXT record." - _err "$response" - return 1 -} - -#################### Private functions ##################### - -# Build a domain-conf key for storing the record ID of a given TXT value. -# Base64url chars include '-' which is invalid in shell variable names, so replace with '_'. -_subreg_record_id_key() { - printf 'SUBREG_RECORD_ID_%s' "$(printf '%s' "$1" | tr '-' '_')" -} - -# Check if the current $response contains a successful status in the ns2:Map format: -# statusok -_subreg_ok() { - [ "$(_subreg_map_get status)" = "ok" ] -} - -# Extract the value for a given key from the ns2:Map response. -# Usage: _subreg_map_get keyname -# Reads from $response -_subreg_map_get() { - _key="$1" - echo "$response" | tr -d '\n\r' | _egrep_o ">${_key}]*>[^<]*" | sed 's/.*]*>//;s/<\/value>//' -} - -# Login and store session token in _subreg_ssid -_subreg_login() { - _debug "Logging in to Subreg API as $SUBREG_API_USERNAME" - _subreg_soap_noauth "Login" "$SUBREG_API_USERNAME$SUBREG_API_PASSWORD" - if ! _subreg_ok; then - _err "Subreg login failed." - _err "$response" - return 1 - fi - _subreg_ssid="$(_subreg_map_get ssid)" - if [ -z "$_subreg_ssid" ]; then - _err "Subreg login: could not extract session token (ssid)." - return 1 - fi - _debug "Subreg login: session token (ssid) obtained" - return 0 -} - -# _get_root _acme-challenge.www.domain.com -# returns _sub_domain and _domain -_get_root() { - domain=$1 - i=1 - p=1 - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - _err "Unable to retrieve DNS zone matching domain: $domain" - return 1 - fi - - _subreg_soap "Get_DNS_Zone" "$h" - - if _subreg_ok; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain="$h" - return 0 - fi - - p=$i - i=$(_math "$i" + 1) - done -} - -# Send a SOAP request without authentication (used for Login) -# _subreg_soap_noauth command inner_xml -_subreg_build_soap() { - _cmd="$1" - _data_inner="$2" - - _soap_body=" - - - - - ${_data_inner} - - - -" - - export _H1="Content-Type: text/xml" - export _H2="SOAPAction: http://soap.subreg.cz/soap#${_cmd}" - response="$(_post "$_soap_body" "$SUBREG_API_URL" "" "POST" "text/xml")" -} - -# Send an authenticated SOAP request (requires _subreg_ssid to be set) -# _subreg_soap command inner_xml -_subreg_soap_noauth() { - _cmd="$1" - _inner="$2" - - _subreg_build_soap "$_cmd" "$_inner" -} - -# Send an authenticated SOAP request (requires _subreg_ssid to be set) -# _subreg_soap command inner_xml -_subreg_soap() { - _cmd="$1" - _inner="$2" - _inner_with_ssid="${_subreg_ssid}${_inner}" - - _subreg_build_soap "$_cmd" "$_inner_with_ssid" -} diff --git a/dnsapi/dns_technitium.sh b/dnsapi/dns_technitium.sh deleted file mode 100755 index fbe44606..00000000 --- a/dnsapi/dns_technitium.sh +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_technitium_info='Technitium DNS Server -Site: Technitium.com/dns/ -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_technitium -Options: - Technitium_Server Server Address - Technitium_Token API Token - Technitium_Expiry_Ttl Number of seconds before DNS server auto-deletes the acme record -Issues: github.com/acmesh-official/acme.sh/issues/6116 -Author: Henning Reich -' - -dns_technitium_add() { - _info "add txt Record using Technitium" - _Technitium_account - fulldomain=$1 - txtvalue=$2 - expiryTtl=${Technitium_Expirty_Ttl:-$(_readaccountconf_mutable Technitium_Expiry_Ttl)} - expiryTtl=${expiryTtl:-0} - - response="$(_get "$Technitium_Server/api/zones/records/add?token=$Technitium_Token&domain=$fulldomain&type=TXT&text=${txtvalue}&expiryTtl=$expiryTtl")" - if _contains "$response" '"status":"ok"'; then - return 0 - fi - _err "Could not add txt record." - return 1 -} - -dns_technitium_rm() { - _info "remove txt record using Technitium" - _Technitium_account - fulldomain=$1 - txtvalue=$2 - expiryTtl=${Technitium_Expirty_Ttl:-$(_readaccountconf_mutable Technitium_Expiry_Ttl)} - expiryTtl=${expiryTtl:-0} - - if [ "$expiryTtl" -ne 0 ]; then - _info "DNS record is configured to be auto-removed after $expiryTtl seconds. Remove operation is bypassed." - return 0 - fi - - response="$(_get "$Technitium_Server/api/zones/records/delete?token=$Technitium_Token&domain=$fulldomain&type=TXT&text=${txtvalue}")" - if _contains "$response" '"status":"ok"'; then - return 0 - fi - _err "Could not remove txt record" - return 1 -} - -#################### Private functions below ################################## - -_Technitium_account() { - Technitium_Server="${Technitium_Server:-$(_readaccountconf_mutable Technitium_Server)}" - Technitium_Token="${Technitium_Token:-$(_readaccountconf_mutable Technitium_Token)}" - if [ -z "$Technitium_Server" ] || [ -z "$Technitium_Token" ]; then - Technitium_Server="" - Technitium_Token="" - _err "You don't specify Technitium Server and Token yet." - _err "Please create your Token and add server address and try again." - return 1 - fi - - #save the credentials to the account conf file. - _saveaccountconf_mutable Technitium_Server "$Technitium_Server" - _saveaccountconf_mutable Technitium_Token "$Technitium_Token" -} diff --git a/dnsapi/dns_tele3.sh b/dnsapi/dns_tele3.sh index 3a3ccf8c..76c90913 100644 --- a/dnsapi/dns_tele3.sh +++ b/dnsapi/dns_tele3.sh @@ -1,13 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_tele3_info='tele3.cz -Site: tele3.cz -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#tele3 -Options: - TELE3_Key API Key - TELE3_Secret API Secret -Author: Roman Blizik <@par-pa> -' +# +# tele3.cz DNS API +# +# Author: Roman Blizik +# Report Bugs here: https://github.com/par-pa/acme.sh +# +# -- +# export TELE3_Key="MS2I4uPPaI..." +# export TELE3_Secret="kjhOIHGJKHg" +# -- TELE3_API="https://www.tele3.cz/acme/" diff --git a/dnsapi/dns_tencent.sh b/dnsapi/dns_tencent.sh deleted file mode 100644 index b148adc3..00000000 --- a/dnsapi/dns_tencent.sh +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_tencent_info='Tencent.com -Site: cloud.Tencent.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_tencent -Options: - Tencent_SecretId Secret ID - Tencent_SecretKey Secret Key -Issues: github.com/acmesh-official/acme.sh/issues/4781 -' -Tencent_API="https://dnspod.tencentcloudapi.com" - -#Usage: dns_tencent_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_tencent_add() { - fulldomain=$1 - txtvalue=$2 - - Tencent_SecretId="${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}" - Tencent_SecretKey="${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}" - if [ -z "$Tencent_SecretId" ] || [ -z "$Tencent_SecretKey" ]; then - Tencent_SecretId="" - Tencent_SecretKey="" - _err "You don't specify tencent api SecretId and SecretKey yet." - return 1 - fi - - #save the api SecretId and SecretKey to the account conf file. - _saveaccountconf_mutable Tencent_SecretId "$Tencent_SecretId" - _saveaccountconf_mutable Tencent_SecretKey "$Tencent_SecretKey" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - return 1 - fi - - _debug "Add record" - _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _tencent_rest "CreateRecord" -} - -dns_tencent_rm() { - fulldomain=$1 - txtvalue=$2 - Tencent_SecretId="${Tencent_SecretId:-$(_readaccountconf_mutable Tencent_SecretId)}" - Tencent_SecretKey="${Tencent_SecretKey:-$(_readaccountconf_mutable Tencent_SecretKey)}" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - return 1 - fi - - _debug "Get record list" - attempt=1 - max_attempts=5 - while [ -z "$record_id" ] && [ "$attempt" -le $max_attempts ]; do - _check_exist_query "$_domain" "$_sub_domain" "$txtvalue" && _tencent_rest "DescribeRecordFilterList" - record_id="$(echo "$response" | _egrep_o "\"RecordId\":\s*[0-9]+" | _egrep_o "[0-9]+")" - _debug2 record_id "$record_id" - if [ -z "$record_id" ]; then - _debug "Due to TencentCloud API synchronization delay, record not found, waiting 10 seconds and retrying" - _sleep 10 - attempt=$(_math "$attempt + 1") - fi - done - - record_id="$(echo "$response" | _egrep_o "\"RecordId\":\s*[0-9]+" | _egrep_o "[0-9]+")" - _debug2 record_id "$record_id" - - if [ -z "$record_id" ]; then - _debug "record not found after $max_attempts attempts, skip" - else - _debug "Delete record" - _delete_record_query "$record_id" && _tencent_rest "DeleteRecord" - fi -} - -#################### Private functions below ################################## - -_get_root() { - domain=$1 - i=1 - p=1 - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - if [ -z "$h" ]; then - #not valid - return 1 - fi - - _describe_records_query "$h" "@" - if ! _tencent_rest "DescribeRecordList" "ignore"; then - return 1 - fi - - if _contains "$response" "\"TotalCount\":"; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _debug _sub_domain "$_sub_domain" - _domain="$h" - _debug _domain "$_domain" - return 0 - fi - p="$i" - i=$(_math "$i" + 1) - done - return 1 -} - -_tencent_rest() { - action=$1 - service="dnspod" - payload="${query}" - timestamp=$(date -u +%s) - - token=$(tencent_signature_v3 $service "$action" "$payload" "$timestamp") - version="2021-03-23" - - if ! response="$(tencent_api_request $service $version "$action" "$payload" "$timestamp")"; then - _err "Error <$1>" - return 1 - fi - - _debug2 response "$response" - if [ -z "$2" ]; then - message="$(echo "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" - if [ "$message" ]; then - _err "$message" - return 1 - fi - fi -} - -_add_record_query() { - query="{\"Domain\":\"$1\",\"SubDomain\":\"$2\",\"RecordType\":\"TXT\",\"RecordLineId\":\"0\",\"RecordLine\":\"0\",\"Value\":\"$3\",\"TTL\":600}" -} - -_describe_records_query() { - query="{\"Domain\":\"$1\",\"Limit\":3000}" -} - -_delete_record_query() { - query="{\"Domain\":\"$_domain\",\"RecordId\":$1}" -} - -_check_exist_query() { - _domain="$1" - _subdomain="$2" - _value="$3" - query="{\"Domain\":\"$_domain\",\"SubDomain\":\"$_subdomain\",\"RecordValue\":\"$_value\"}" -} - -# shell client for tencent cloud api v3 | @author: rehiy - -tencent_sha256() { - printf %b "$@" | _digest sha256 hex -} - -tencent_hmac_sha256() { - k=$1 - shift - hex_key=$(printf %b "$k" | _hex_dump | tr -d ' ') - printf %b "$@" | _hmac sha256 "$hex_key" hex -} - -tencent_hmac_sha256_hexkey() { - k=$1 - shift - printf %b "$@" | _hmac sha256 "$k" hex -} - -tencent_signature_v3() { - service=$1 - action=$(echo "$2" | _lower_case) - payload=${3:-'{}'} - timestamp=${4:-$(date +%s)} - - domain="$service.tencentcloudapi.com" - secretId=${Tencent_SecretId:-'tencent-cloud-secret-id'} - secretKey=${Tencent_SecretKey:-'tencent-cloud-secret-key'} - - algorithm='TC3-HMAC-SHA256' - date=$(date -u -d "@$timestamp" +%Y-%m-%d 2>/dev/null) - [ -z "$date" ] && date=$(date -u -r "$timestamp" +%Y-%m-%d) - - canonicalUri='/' - canonicalQuery='' - canonicalHeaders="content-type:application/json\nhost:$domain\nx-tc-action:$action\n" - - signedHeaders='content-type;host;x-tc-action' - canonicalRequest="POST\n$canonicalUri\n$canonicalQuery\n$canonicalHeaders\n$signedHeaders\n$(tencent_sha256 "$payload")" - - credentialScope="$date/$service/tc3_request" - stringToSign="$algorithm\n$timestamp\n$credentialScope\n$(tencent_sha256 "$canonicalRequest")" - - secretDate=$(tencent_hmac_sha256 "TC3$secretKey" "$date") - secretService=$(tencent_hmac_sha256_hexkey "$secretDate" "$service") - secretSigning=$(tencent_hmac_sha256_hexkey "$secretService" 'tc3_request') - signature=$(tencent_hmac_sha256_hexkey "$secretSigning" "$stringToSign") - - echo "$algorithm Credential=$secretId/$credentialScope, SignedHeaders=$signedHeaders, Signature=$signature" -} - -tencent_api_request() { - service=$1 - version=$2 - action=$3 - payload=${4:-'{}'} - timestamp=${5:-$(date +%s)} - - token=$(tencent_signature_v3 "$service" "$action" "$payload" "$timestamp") - - _H1="Content-Type: application/json" - _H2="Authorization: $token" - _H3="X-TC-Version: $version" - _H4="X-TC-Timestamp: $timestamp" - _H5="X-TC-Action: $action" - - _post "$payload" "$Tencent_API" "" "POST" "application/json" -} diff --git a/dnsapi/dns_timeweb.sh b/dnsapi/dns_timeweb.sh deleted file mode 100644 index 7040ac9a..00000000 --- a/dnsapi/dns_timeweb.sh +++ /dev/null @@ -1,403 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_timeweb_info='Timeweb.Cloud -Site: Timeweb.Cloud -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_timeweb -Options: - TW_Token API JWT token. Get it from the control panel at https://timeweb.cloud/my/api-keys -Issues: github.com/acmesh-official/acme.sh/issues/5140 -Author: Nikolay Pronchev <@nikolaypronchev> -' - -TW_Api="https://api.timeweb.cloud/api/v1" - -################ Public functions ################ - -# Adds an ACME DNS-01 challenge DNS TXT record via the Timeweb Cloud API. -# -# Param1: The ACME DNS-01 challenge FQDN. -# Param2: The value of the ACME DNS-01 challenge TXT record. -# -# Example: dns_timeweb_add "_acme-challenge.sub.domain.com" "D-52Wm...4uYM" -dns_timeweb_add() { - _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_add\" started." - - _timeweb_set_acme_fqdn "$1" || return 1 - _timeweb_set_acme_txt "$2" || return 1 - _timeweb_check_token || return 1 - _timeweb_split_acme_fqdn || return 1 - _timeweb_dns_txt_add || return 1 - - _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_add\" finished." -} - -# Removes a DNS TXT record via the Timeweb Cloud API. -# -# Param1: The ACME DNS-01 challenge FQDN. -# Param2: The value of the ACME DNS-01 challenge TXT record. -# -# Example: dns_timeweb_rm "_acme-challenge.sub.domain.com" "D-52Wm...4uYM" -dns_timeweb_rm() { - _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_rm\" started." - - _timeweb_set_acme_fqdn "$1" || return 1 - _timeweb_set_acme_txt "$2" || return 1 - _timeweb_check_token || return 1 - _timeweb_split_acme_fqdn || return 1 - _timeweb_get_dns_txt || return 1 - _timeweb_dns_txt_remove || return 1 - - _debug "$(__green "Timeweb DNS API"): \"dns_timeweb_rm\" finished." -} - -################ Private functions ################ - -# Checks and sets the ACME DNS-01 challenge FQDN. -# -# Param1: The ACME DNS-01 challenge FQDN. -# -# Example: _timeweb_set_acme_fqdn "_acme-challenge.sub.domain.com" -# -# Sets the "Acme_Fqdn" variable (_acme-challenge.sub.domain.com) -_timeweb_set_acme_fqdn() { - Acme_Fqdn=$1 - _debug "Setting ACME DNS-01 challenge FQDN \"$Acme_Fqdn\"." - [ -z "$Acme_Fqdn" ] && { - _err "ACME DNS-01 challenge FQDN is empty." - return 1 - } - return 0 -} - -# Checks and sets the value of the ACME DNS-01 challenge TXT record. -# -# Param1: Value of the ACME DNS-01 challenge TXT record. -# -# Example: _timeweb_set_acme_txt "D-52Wm...4uYM" -# -# Sets the "Acme_Txt" variable to the provided value (D-52Wm...4uYM) -_timeweb_set_acme_txt() { - Acme_Txt=$1 - _debug "Setting the value of the ACME DNS-01 challenge TXT record to \"$Acme_Txt\"." - [ -z "$Acme_Txt" ] && { - _err "ACME DNS-01 challenge TXT record value is empty." - return 1 - } - return 0 -} - -# Checks if the Timeweb Cloud API JWT token is present (refer to the script description). -# Adds or updates the token in the acme.sh account configuration. -_timeweb_check_token() { - _debug "Checking for the presence of the Timeweb Cloud API JWT token." - - TW_Token="${TW_Token:-$(_readaccountconf_mutable TW_Token)}" - - [ -z "$TW_Token" ] && { - _err "Timeweb Cloud API JWT token was not found." - return 1 - } - - _saveaccountconf_mutable TW_Token "$TW_Token" -} - -# Divides the ACME DNS-01 challenge FQDN into its main domain and subdomain components. -_timeweb_split_acme_fqdn() { - _debug "Trying to divide \"$Acme_Fqdn\" into its main domain and subdomain components." - - TW_Page_Limit=100 - TW_Page_Offset=0 - TW_Domains_Returned="" - - while [ -z "$TW_Domains_Returned" ] || [ "$TW_Domains_Returned" -ge "$TW_Page_Limit" ]; do - - _timeweb_list_domains "$TW_Page_Limit" "$TW_Page_Offset" || return 1 - - # Remove the 'subdomains' subarray to prevent confusion with FQDNs. - - TW_Domains=$( - echo "$TW_Domains" | - sed 's/"subdomains":\[[^]]*]//g' - ) - - [ -z "$TW_Domains" ] && { - _err "Failed to parse the list of domains." - return 1 - } - - while - TW_Domain=$( - echo "$TW_Domains" | - sed -n 's/.*{[^{]*"fqdn":"\([^"]*\)"[^}]*}.*/\1/p' - ) - - [ -n "$TW_Domain" ] && { - _timeweb_is_main_domain "$TW_Domain" && return 0 - - TW_Domains=$( - echo "$TW_Domains" | - sed 's/{\([^{]*"fqdn":"'"$TW_Domain"'"[^}]*\)}//' - ) - continue - } - do :; done - - TW_Page_Offset=$(_math "$TW_Page_Offset" + "$TW_Page_Limit") - done - - _err "Failed to divide \"$Acme_Fqdn\" into its main domain and subdomain components." - return 1 -} - -# Searches for a previously added DNS TXT record. -# -# Sets the "TW_Dns_Txt_Id" variable. -_timeweb_get_dns_txt() { - _debug "Trying to locate a DNS TXT record with the value \"$Acme_Txt\"." - - TW_Page_Limit=100 - TW_Page_Offset=0 - TW_Dns_Records_Returned="" - - while [ -z "$TW_Dns_Records_Returned" ] || [ "$TW_Dns_Records_Returned" -ge "$TW_Page_Limit" ]; do - - _timeweb_list_dns_records "$TW_Page_Limit" "$TW_Page_Offset" || return 1 - - while - Dns_Record=$( - echo "$TW_Dns_Records" | - sed -n 's/.*{\([^{]*{[^{]*'"$Acme_Txt"'[^}]*}[^}]*\)}.*/\1/p' - ) - - [ -n "$Dns_Record" ] && { - _timeweb_is_added_txt "$Dns_Record" && return 0 - - TW_Dns_Records=$( - echo "$TW_Dns_Records" | - sed 's/{\([^{]*{[^{]*'"$Acme_Txt"'[^}]*}[^}]*\)}//' - ) - continue - } - do :; done - - TW_Page_Offset=$(_math "$TW_Page_Offset" + "$TW_Page_Limit") - done - - _err "DNS TXT record was not found." - return 1 -} - -# Lists domains via the Timeweb Cloud API. -# -# Param 1: Limit for listed domains. -# Param 2: Offset for domains list. -# -# Sets the "TW_Domains" variable. -# Sets the "TW_Domains_Returned" variable. -_timeweb_list_domains() { - _debug "Listing domains via Timeweb Cloud API. Limit: $1, offset: $2." - - export _H1="Authorization: Bearer $TW_Token" - - if ! TW_Domains=$(_get "$TW_Api/domains?limit=$1&offset=$2"); then - _err "The request to the Timeweb Cloud API failed." - return 1 - fi - - [ -z "$TW_Domains" ] && { - _err "Empty response from the Timeweb Cloud API." - return 1 - } - - TW_Domains_Returned=$( - echo "$TW_Domains" | - sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/' - ) - - [ -z "$TW_Domains_Returned" ] && { - _err "Failed to extract the total count of domains." - return 1 - } - - [ "$TW_Domains_Returned" -eq "0" ] && { - _err "Domains are missing." - return 1 - } - - _debug "Domains returned by Timeweb Cloud API: $TW_Domains_Returned." -} - -# Lists domain DNS records via the Timeweb Cloud API. -# -# Param 1: Limit for listed DNS records. -# Param 2: Offset for DNS records list. -# -# Sets the "TW_Dns_Records" variable. -# Sets the "TW_Dns_Records_Returned" variable. -_timeweb_list_dns_records() { - _debug "Listing domain DNS records via the Timeweb Cloud API. Limit: $1, offset: $2." - - export _H1="Authorization: Bearer $TW_Token" - - if ! TW_Dns_Records=$(_get "$TW_Api/domains/$TW_Main_Domain/dns-records?limit=$1&offset=$2"); then - _err "The request to the Timeweb Cloud API failed." - return 1 - fi - - [ -z "$TW_Dns_Records" ] && { - _err "Empty response from the Timeweb Cloud API." - return 1 - } - - TW_Dns_Records_Returned=$( - echo "$TW_Dns_Records" | - sed 's/.*"meta":{"total":\([0-9]*\)[^0-9].*/\1/' - ) - - [ -z "$TW_Dns_Records_Returned" ] && { - _err "Failed to extract the total count of DNS records." - return 1 - } - - [ "$TW_Dns_Records_Returned" -eq "0" ] && { - _err "DNS records are missing." - return 1 - } - - _debug "DNS records returned by Timeweb Cloud API: $TW_Dns_Records_Returned." -} - -# Verifies whether the domain is the primary domain for the ACME DNS-01 challenge FQDN. -# The requirement is that the provided domain is the top-level domain -# for the ACME DNS-01 challenge FQDN. -# -# Param 1: Domain object returned by Timeweb Cloud API. -# -# Sets the "TW_Main_Domain" variable (e.g. "_acme-challenge.s1.domain.co.uk" → "domain.co.uk"). -# Sets the "TW_Subdomains" variable (e.g. "_acme-challenge.s1.domain.co.uk" → "_acme-challenge.s1"). -_timeweb_is_main_domain() { - _debug "Checking if \"$1\" is the main domain of the ACME DNS-01 challenge FQDN." - - [ -z "$1" ] && { - _debug "Failed to extract FQDN. Skipping domain." - return 1 - } - - ! echo ".$Acme_Fqdn" | grep -qi "\.$1$" && { - _debug "Domain does not match the ACME DNS-01 challenge FQDN. Skipping domain." - return 1 - } - - TW_Main_Domain=$1 - TW_Subdomains=$( - echo "$Acme_Fqdn" | - sed "s/\.*.\{${#1}\}$//" - ) - - _debug "Matched domain. ACME DNS-01 challenge FQDN split as [$TW_Subdomains].[$TW_Main_Domain]." - return 0 -} - -# Verifies whether a DNS record was previously added based on the following criteria: -# - The value matches the ACME DNS-01 challenge TXT record value; -# - The record type is TXT; -# - The subdomain matches the ACME DNS-01 challenge FQDN. -# -# Param 1: DNS record object returned by Timeweb Cloud API. -# -# Sets the "TW_Dns_Txt_Id" variable. -_timeweb_is_added_txt() { - _debug "Checking if \"$1\" is a previously added DNS TXT record." - - echo "$1" | grep -qv '"type":"TXT"' && { - _debug "Not a TXT record. Skipping the record." - return 1 - } - - if [ -n "$TW_Subdomains" ]; then - echo "$1" | grep -qvi "\"subdomain\":\"$TW_Subdomains\"" && { - _debug "Subdomains do not match. Skipping the record." - return 1 - } - else - echo "$1" | grep -q '"subdomain\":"..*"' && { - _debug "Subdomains do not match. Skipping the record." - return 1 - } - fi - - TW_Dns_Txt_Id=$( - echo "$1" | - sed 's/.*"id":\([0-9]*\)[^0-9].*/\1/' - ) - - [ -z "$TW_Dns_Txt_Id" ] && { - _debug "Failed to extract the DNS record ID. Skipping the record." - return 1 - } - - _debug "Matching DNS TXT record ID is \"$TW_Dns_Txt_Id\"." - return 0 -} - -# Adds a DNS TXT record via the Timeweb Cloud API. -_timeweb_dns_txt_add() { - _debug "Adding a new DNS TXT record via the Timeweb Cloud API." - - export _H1="Authorization: Bearer $TW_Token" - export _H2="Content-Type: application/json" - - if ! TW_Response=$( - _post "{ - \"subdomain\":\"$TW_Subdomains\", - \"type\":\"TXT\", - \"value\":\"$Acme_Txt\" - }" \ - "$TW_Api/domains/$TW_Main_Domain/dns-records" - ); then - _err "The request to the Timeweb Cloud API failed." - return 1 - fi - - [ -z "$TW_Response" ] && { - _err "An unexpected empty response was received from the Timeweb Cloud API." - return 1 - } - - TW_Dns_Txt_Id=$( - echo "$TW_Response" | - sed 's/.*"id":\([0-9]*\)[^0-9].*/\1/' - ) - - [ -z "$TW_Dns_Txt_Id" ] && { - _err "Failed to extract the DNS TXT Record ID." - return 1 - } - - _debug "DNS TXT record has been added. ID: \"$TW_Dns_Txt_Id\"." -} - -# Removes a DNS record via the Timeweb Cloud API. -_timeweb_dns_txt_remove() { - _debug "Removing DNS record via the Timeweb Cloud API." - - export _H1="Authorization: Bearer $TW_Token" - - if ! TW_Response=$( - _post \ - "" \ - "$TW_Api/domains/$TW_Main_Domain/dns-records/$TW_Dns_Txt_Id" \ - "" \ - "DELETE" - ); then - _err "The request to the Timeweb Cloud API failed." - return 1 - fi - - [ -n "$TW_Response" ] && { - _err "Received an unexpected response body from the Timeweb Cloud API." - return 1 - } - - _debug "DNS TXT record with ID \"$TW_Dns_Txt_Id\" has been removed." -} diff --git a/dnsapi/dns_transip.sh b/dnsapi/dns_transip.sh index b3c5ed70..64a256ec 100644 --- a/dnsapi/dns_transip.sh +++ b/dnsapi/dns_transip.sh @@ -1,14 +1,4 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_transip_info='TransIP.nl -Site: TransIP.nl -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_transip -Options: - TRANSIP_Username Username - TRANSIP_Key_File Private key file path -Issues: github.com/acmesh-official/acme.sh/issues/2949 -' - TRANSIP_Api_Url="https://api.transip.nl/v6" TRANSIP_Token_Read_Only="false" TRANSIP_Token_Expiration="30 minutes" @@ -24,7 +14,7 @@ dns_transip_add() { _debug txtvalue="$txtvalue" _transip_setup "$fulldomain" || return 1 _info "Creating TXT record." - if ! _transip_rest POST "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":60}}"; then + if ! _transip_rest POST "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then _err "Could not add TXT record." return 1 fi @@ -38,7 +28,7 @@ dns_transip_rm() { _debug txtvalue="$txtvalue" _transip_setup "$fulldomain" || return 1 _info "Removing TXT record." - if ! _transip_rest DELETE "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":60}}"; then + if ! _transip_rest DELETE "domains/$_domain/dns" "{\"dnsEntry\":{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"content\":\"$txtvalue\",\"expire\":300}}"; then _err "Could not remove TXT record $_sub_domain for $domain" return 1 fi @@ -55,14 +45,14 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 fi - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" if _transip_rest GET "domains/$h/dns" && _contains "$response" "dnsEntries"; then diff --git a/dnsapi/dns_udr.sh b/dnsapi/dns_udr.sh index dbc959d6..caada826 100644 --- a/dnsapi/dns_udr.sh +++ b/dnsapi/dns_udr.sh @@ -1,14 +1,14 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_udr_info='united-domains Reselling -Site: ud-reselling.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_udr -Options: - UDR_USER Username - UDR_PASS Password -Issues: github.com/acmesh-official/acme.sh/issues/3923 -Author: Andreas Scherer <@andischerer> -' + +# united-domains Reselling (https://www.ud-reselling.com/) DNS API +# Author: Andreas Scherer (https://github.com/andischerer) +# Created: 2021-02-01 +# +# Set the environment variables as below: +# +# export UDR_USER="your_username_goes_here" +# export UDR_PASS="some_password_goes_here" +# UDR_API="https://api.domainreselling.de/api/call.cgi" UDR_TTL="30" @@ -115,7 +115,7 @@ _get_root() { fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then @@ -145,8 +145,8 @@ _udr_rest() { _debug data "${data}" response="$(_post "${data}" "${UDR_API}?s_login=${UDR_USER}&s_pw=${UDR_PASS}" "" "POST")" - _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | tr -d ' \t\r') - _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | tr -d '\r' | sed -e 's/^[ ]*//' -e 's/[ ]*$//') + _code=$(echo "$response" | _egrep_o "code = ([0-9]+)" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') + _description=$(echo "$response" | _egrep_o "description = .*" | _head_n 1 | cut -d = -f 2 | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//') _debug response_code "$_code" _debug response_description "$_description" diff --git a/dnsapi/dns_ultra.sh b/dnsapi/dns_ultra.sh index e8da431c..0f26bd97 100644 --- a/dnsapi/dns_ultra.sh +++ b/dnsapi/dns_ultra.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_ultra_info='UltraDNS.com -Site: UltraDNS.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_ultra -Options: - ULTRA_USR Username - ULTRA_PWD Password -Issues: github.com/acmesh-official/acme.sh/issues/2118 -' + +# +# ULTRA_USR="your_user_goes_here" +# +# ULTRA_PWD="some_password_goes_here" ULTRA_API="https://api.ultradns.com/v3/" ULTRA_AUTH_API="https://api.ultradns.com/v2/" @@ -115,7 +111,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" _debug response "$response" if [ -z "$h" ]; then @@ -128,7 +124,7 @@ _get_root() { if _contains "${response}" "${h}." >/dev/null; then _domain_id=$(echo "$response" | _egrep_o "${h}" | head -1) if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="${h}" _debug sub_domain "${_sub_domain}" _debug domain "${_domain}" diff --git a/dnsapi/dns_unoeuro.sh b/dnsapi/dns_unoeuro.sh index ff70c8b6..13ba8a00 100644 --- a/dnsapi/dns_unoeuro.sh +++ b/dnsapi/dns_unoeuro.sh @@ -1,13 +1,9 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_unoeuro_info='unoeuro.com - Deprecated. The unoeuro.com is now simply.com -Site: unoeuro.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_unoeuro -Options: - UNO_Key API Key - UNO_User Username -' + +# +#UNO_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# +#UNO_User="UExxxxxx" Uno_Api="https://api.simply.com/1" @@ -133,7 +129,7 @@ _get_root() { i=2 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -147,7 +143,7 @@ _get_root() { if _contains "$response" "\"status\": 200"; then _domain_id=$h if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_variomedia.sh b/dnsapi/dns_variomedia.sh index 4620b854..a35b8f0f 100644 --- a/dnsapi/dns_variomedia.sh +++ b/dnsapi/dns_variomedia.sh @@ -1,12 +1,7 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_variomedia_info='variomedia.de -Site: variomedia.de -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_variomedia -Options: - VARIOMEDIA_API_TOKEN API Token -Issues: github.com/acmesh-official/acme.sh/issues/2564 -' + +# +#VARIOMEDIA_API_TOKEN=000011112222333344445555666677778888 VARIOMEDIA_API="https://api.variomedia.de" @@ -74,7 +69,7 @@ dns_variomedia_rm() { return 1 fi - _record_id="$(echo "$response" | sed -E 's/,"tags":\[[^]]*\]//g' | cut -d '[' -f3 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep -i "$_sub_domain" | grep -- "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')" + _record_id="$(echo "$response" | cut -d '[' -f2 | cut -d']' -f1 | sed 's/},[ \t]*{/\},§\{/g' | tr § '\n' | grep "$_sub_domain" | grep "$txtvalue" | sed 's/^{//;s/}[,]?$//' | tr , '\n' | tr -d '\"' | grep ^id | cut -d : -f2 | tr -d ' ')" _debug _record_id "$_record_id" if [ "$_record_id" ]; then _info "Successfully retrieved the record id for ACME challenge." @@ -98,11 +93,11 @@ dns_variomedia_rm() { # _sub_domain=_acme-challenge.www # _domain=domain.com _get_root() { - domain=$1 + fulldomain=$1 i=1 - p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$fulldomain" | cut -d . -f $i-100) + _debug h "$h" if [ -z "$h" ]; then return 1 fi @@ -111,14 +106,17 @@ _get_root() { return 1 fi - if _contains "$response" "\"id\":\"$h\""; then - _sub_domain=$(printf "%s" "$domain" | cut -d '.' -f 1-"$p") - _domain="$h" - return 0 + if _startswith "$response" "\{\"data\":"; then + if _contains "$response" "\"id\":\"$h\""; then + _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")" + _domain=$h + return 0 + fi fi - p=$i i=$(_math "$i" + 1) done + + _debug "root domain not found" return 1 } diff --git a/dnsapi/dns_veesp.sh b/dnsapi/dns_veesp.sh index 1afeeb30..b8a41d00 100644 --- a/dnsapi/dns_veesp.sh +++ b/dnsapi/dns_veesp.sh @@ -1,14 +1,10 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_veesp_info='veesp.com -Site: veesp.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_veesp -Options: - VEESP_User Username - VEESP_Password Password -Issues: github.com/acmesh-official/acme.sh/issues/3712 -Author: -' + +# bug reports to stepan@plyask.in + +# +# export VEESP_User="username" +# export VEESP_Password="password" VEESP_Api="https://secure.veesp.com/api" @@ -112,7 +108,7 @@ _get_root() { return 1 fi while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -125,7 +121,7 @@ _get_root() { _service_id=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$h\",\"service_id\":[^}]*" | cut -d : -f 3 | cut -d '"' -f 2) _debug _service_id "$_service_id" if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 fi diff --git a/dnsapi/dns_vercel.sh b/dnsapi/dns_vercel.sh index 469f7670..7bf6b0e5 100644 --- a/dnsapi/dns_vercel.sh +++ b/dnsapi/dns_vercel.sh @@ -1,14 +1,11 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_vercel_info='Vercel.com -Site: Vercel.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vercel -Options: - VERCEL_TOKEN API Token -' +# Vercel DNS API +# # This is your API token which can be acquired on the account page. # https://vercel.com/account/tokens +# +# VERCEL_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" VERCEL_API="https://api.vercel.com" @@ -94,7 +91,7 @@ _get_root() { i=1 p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) if [ -z "$h" ]; then #not valid return 1 @@ -105,7 +102,7 @@ _get_root() { fi if _contains "$response" "\"name\":\"$h\"" >/dev/null; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_virakcloud.sh b/dnsapi/dns_virakcloud.sh deleted file mode 100755 index 7ae665d2..00000000 --- a/dnsapi/dns_virakcloud.sh +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_virakcloud_info='VirakCloud DNS API -Site: VirakCloud.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_virakcloud -Options: - VIRAKCLOUD_API_TOKEN VirakCloud API Bearer Token -' - -VIRAKCLOUD_API_URL="https://public-api.virakcloud.com/dns" - -######## Public functions ##################### - -#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -#Used to add txt record -dns_virakcloud_add() { - fulldomain=$1 - txtvalue=$2 - - VIRAKCLOUD_API_TOKEN="${VIRAKCLOUD_API_TOKEN:-$(_readaccountconf_mutable VIRAKCLOUD_API_TOKEN)}" - - if [ -z "$VIRAKCLOUD_API_TOKEN" ]; then - _err "You haven't configured your VirakCloud API token yet." - _err "Please set VIRAKCLOUD_API_TOKEN environment variable or run:" - _err " export VIRAKCLOUD_API_TOKEN=\"your-api-token\"" - return 1 - fi - - _saveaccountconf_mutable VIRAKCLOUD_API_TOKEN "$VIRAKCLOUD_API_TOKEN" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" - if [ "$http_code" = "401" ]; then - return 1 - fi - _err "Invalid domain" - return 1 - fi - - _debug _domain "$_domain" - _debug fulldomain "$fulldomain" - - _info "Adding TXT record" - - if _virakcloud_rest POST "domains/${_domain}/records" "{\"record\":\"${fulldomain}\",\"type\":\"TXT\",\"ttl\":3600,\"content\":\"${txtvalue}\"}"; then - if echo "$response" | grep -q "success" || echo "$response" | grep -q "\"data\""; then - _info "Added, OK" - return 0 - elif echo "$response" | grep -q "already exists" || echo "$response" | grep -q "duplicate"; then - _info "Record already exists, OK" - return 0 - else - _err "Add TXT record error." - _err "Response: $response" - return 1 - fi - fi - - _err "Add TXT record error." - return 1 -} - -#Usage: fulldomain txtvalue -#Used to remove the txt record after validation -dns_virakcloud_rm() { - fulldomain=$1 - txtvalue=$2 - - VIRAKCLOUD_API_TOKEN="${VIRAKCLOUD_API_TOKEN:-$(_readaccountconf_mutable VIRAKCLOUD_API_TOKEN)}" - - if [ -z "$VIRAKCLOUD_API_TOKEN" ]; then - _err "You haven't configured your VirakCloud API token yet." - return 1 - fi - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" - if [ "$http_code" = "401" ]; then - return 1 - fi - _err "Invalid domain" - return 1 - fi - - _debug _domain "$_domain" - _debug fulldomain "$fulldomain" - _debug txtvalue "$txtvalue" - - _info "Removing TXT record" - - _debug "Getting list of records to find content ID" - if ! _virakcloud_rest GET "domains/${_domain}/records" ""; then - return 1 - fi - - _debug2 "Records response" "$response" - - contentid="" - # Extract innermost objects (content objects) which look like {"id":"...","content_raw":"..."} - # We filter for the one containing txtvalue - - target_obj=$(echo "$response" | grep -o '{[^}]*}' | grep "$txtvalue" | _head_n 1) - - if [ -n "$target_obj" ]; then - contentid=$(echo "$target_obj" | _egrep_o '"id":"[^"]*"' | cut -d '"' -f 4) - fi - - if [ -z "$contentid" ]; then - _debug "Could not find matching record ID in response" - _info "Record not found, may have been already removed" - return 0 - fi - - _debug contentid "$contentid" - - if _virakcloud_rest DELETE "domains/${_domain}/records/${fulldomain}/TXT/${contentid}" ""; then - if echo "$response" | grep -q "success" || [ -z "$response" ]; then - _info "Removed, OK" - return 0 - elif echo "$response" | grep -q "not found" || echo "$response" | grep -q "404"; then - _info "Record not found, OK" - return 0 - else - _err "Remove TXT record error." - _err "Response: $response" - return 1 - fi - fi - - _err "Remove TXT record error." - return 1 -} - -#################### Private functions below ################################## - -#_acme-challenge.www.domain.com -#returns -# _domain=domain.com -_get_root() { - domain=$1 - i=1 - p=1 - - # Optimization: skip _acme-challenge subdomain to avoid 422 errors - if echo "$domain" | grep -q "^_acme-challenge."; then - i=2 - fi - - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug h "$h" - - if [ -z "$h" ]; then - return 1 - fi - - if ! _virakcloud_rest GET "domains/$h" ""; then - http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" - if [ "$http_code" = "401" ]; then - return 1 - fi - p=$i - i=$(_math "$i" + 1) - continue - fi - - if echo "$response" | grep -q "\"name\""; then - _domain="$h" - return 0 - fi - - p=$i - i=$(_math "$i" + 1) - done - - return 1 -} - -_virakcloud_rest() { - m=$1 - ep="$2" - data="$3" - - _debug "$ep" - - export _H1="Content-Type: application/json" - export _H2="Authorization: Bearer $VIRAKCLOUD_API_TOKEN" - - if [ "$m" != "GET" ]; then - _debug data "$data" - response="$(_post "$data" "$VIRAKCLOUD_API_URL/$ep" "" "$m")" - else - response="$(_get "$VIRAKCLOUD_API_URL/$ep")" - fi - - _ret="$?" - - if [ "$_ret" != "0" ]; then - _err "error on $m $ep" - return 1 - fi - - http_code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\r\n")" - _debug "http response code" "$http_code" - - if [ "$http_code" = "401" ]; then - _err "VirakCloud API returned 401 Unauthorized." - _err "Your VIRAKCLOUD_API_TOKEN is invalid or expired." - _err "Please check your API token and try again." - return 1 - fi - - if [ "$http_code" = "403" ]; then - _err "VirakCloud API returned 403 Forbidden." - _err "Your API token does not have permission to access this resource." - return 1 - fi - - if [ -n "$http_code" ] && [ "$http_code" -ge 400 ]; then - _err "VirakCloud API error. HTTP code: $http_code" - _err "Response: $response" - return 1 - fi - - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_volcengine.sh b/dnsapi/dns_volcengine.sh deleted file mode 100755 index 2cc805d5..00000000 --- a/dnsapi/dns_volcengine.sh +++ /dev/null @@ -1,297 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_volcengine_info='Volcano Engine DNS API -Site: https://www.volcengine.com/docs/6758/155086 -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_volcengine -Options: - Volcengine_ACCESS_KEY_ID API Key ID - Volcengine_SECRET_ACCESS_KEY API Secret - Volcengine_SESSION_TOKEN Session Token. Optional, only needed when using temporary STS credentials. -Issues: github.com/acmesh-official/acme.sh/issues/7064 -' - -Volcengine_HOST="dns.volcengineapi.com" -Volcengine_URL="https://$Volcengine_HOST" - -######## Public functions ##################### - -#fulldomain txtvalue -dns_volcengine_add() { - fulldomain=$1 - txtvalue=$2 - _record_id="" - - Volcengine_ACCESS_KEY_ID="${Volcengine_ACCESS_KEY_ID:-$(_readaccountconf_mutable Volcengine_ACCESS_KEY_ID)}" - Volcengine_SECRET_ACCESS_KEY="${Volcengine_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable Volcengine_SECRET_ACCESS_KEY)}" - - if [ -z "$Volcengine_ACCESS_KEY_ID" ] || [ -z "$Volcengine_SECRET_ACCESS_KEY" ]; then - Volcengine_ACCESS_KEY_ID="" - Volcengine_SECRET_ACCESS_KEY="" - _err "You haven't specified the volcengine dns api key id and api key secret yet." - return 1 - fi - - #save the api key and email to the account conf file. - _saveaccountconf_mutable Volcengine_ACCESS_KEY_ID "$Volcengine_ACCESS_KEY_ID" - _saveaccountconf_mutable Volcengine_SECRET_ACCESS_KEY "$Volcengine_SECRET_ACCESS_KEY" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - _sleep 1 - return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - # _info "Getting existing records for $fulldomain" - if ! volcengine_rest POST "" "Action=ListRecords&Version=2018-08-01" "{\"ZID\":$_domain_id,\"Host\":\"$_sub_domain\",\"Type\":\"TXT\",\"Value\":\"$txtvalue\",\"SearchMode\":\"exact\"}"; then - _sleep 1 - return 1 - fi - - # ListRecords already filtered by ZID + Host + Value + SearchMode:exact, - # so any returned record is our target. Don't match on FQDN: Volcengine - # lowercases the Host/FQDN in the response, which would break a - # case-sensitive string compare against $fulldomain. - _record_id="$(echo "$response" | _egrep_o "\"RecordID\":\"[0-9]+\"," | cut -d: -f2 | cut -d, -f1 | tr -d '"')" - _debug "_record_id" "$_record_id" - - if [ "$_record_id" ] && _contains "$response" "$txtvalue"; then - _info "The TXT record already exists. Skipping." - _sleep 1 - return 0 - fi - - _debug "Adding records" - - if volcengine_rest POST "" "Action=CreateRecord&Version=2018-08-01" "{\"ZID\":$_domain_id,\"Host\":\"$_sub_domain\",\"Type\":\"TXT\",\"Value\":\"$txtvalue\",\"Remark\":\"acme.sh\"}"; then - _info "TXT record updated successfully." - _sleep 1 - return 0 - fi - - _sleep 1 - return 1 -} - -#fulldomain txtvalue -dns_volcengine_rm() { - fulldomain=$1 - txtvalue=$2 - _record_id="" - - Volcengine_ACCESS_KEY_ID="${Volcengine_ACCESS_KEY_ID:-$(_readaccountconf_mutable Volcengine_ACCESS_KEY_ID)}" - Volcengine_SECRET_ACCESS_KEY="${Volcengine_SECRET_ACCESS_KEY:-$(_readaccountconf_mutable Volcengine_SECRET_ACCESS_KEY)}" - - _debug "First detect the root zone" - if ! _get_root "$fulldomain"; then - _err "invalid domain" - _sleep 1 - return 1 - fi - _debug _domain_id "$_domain_id" - _debug _sub_domain "$_sub_domain" - _debug _domain "$_domain" - - _info "Getting existing records for $fulldomain" - - if ! volcengine_rest POST "" "Action=ListRecords&Version=2018-08-01" "{\"ZID\":$_domain_id,\"Host\":\"$_sub_domain\",\"Type\":\"TXT\",\"Value\":\"$txtvalue\",\"SearchMode\":\"exact\"}"; then - _sleep 1 - return 1 - fi - - # ListRecords already filtered by ZID + Host + Value + SearchMode:exact, - # so any returned record is our target. Don't match on FQDN: Volcengine - # lowercases the Host/FQDN in the response, which would break a - # case-sensitive string compare against $fulldomain. - _record_id="$(echo "$response" | _egrep_o "\"RecordID\":\"[0-9]+\"," | cut -d: -f2 | cut -d, -f1 | tr -d '"')" - _debug "_record_id" "$_record_id" - - if [ -z "$_record_id" ]; then - _debug "no records exist, skip" - _sleep 1 - return 0 - fi - - if volcengine_rest POST "" "Action=DeleteRecord&Version=2018-08-01" "{\"RecordID\":\"$_record_id\"}"; then - _info "TXT record deleted successfully." - _sleep 1 - return 0 - fi - _sleep 1 - return 1 -} - -#################### Private functions below ################################## - -_get_root() { - domain=$1 - i=1 - p=1 - - # iterate over names (a.b.c.d -> b.c.d -> c.d -> d) - while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) - _debug "Checking domain: $h" - if [ -z "$h" ]; then - _err "invalid domain" - return 1 - fi - - # iterate over paginated result for list_hosted_zones - if ! volcengine_rest POST "" "Action=ListZones&Version=2018-08-01" "{\"Key\":\"$h\",\"SearchMode\":\"exact\"}"; then - return 1 - fi - if _contains "$response" "\"ZoneName\":\"$h\""; then - _domain_id=$(printf "%s" "$response" | _egrep_o "\"ZID\":[0-9]+," | cut -d: -f2 | cut -d, -f1) - if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") - _domain=$h - return 0 - fi - _err "Can't find domain with id: $h" - return 1 - fi - p=$i - i=$(_math "$i" + 1) - done - return 1 -} - -#method uri qstr data -volcengine_rest() { - mtd="$1" - ep="$2" - qsr="$3" - data="$4" - - _debug mtd "$mtd" - _debug ep "$ep" - _debug qsr "$qsr" - _debug data "$data" - - # clear any header state left over from a previous request so that - # conditionally-set headers (e.g. x-content-sha256, x-security-token) - # can't leak into the next request - _H1="" - _H2="" - _H3="" - _H4="" - _H5="" - - CanonicalURI="/$ep" - _debug2 CanonicalURI "$CanonicalURI" - - CanonicalQueryString="$qsr" - _debug2 CanonicalQueryString "$CanonicalQueryString" - - RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")" - _debug2 RequestDate "$RequestDate" - - Hash="sha256" - - _H1="X-Date: $RequestDate" - _debug2 _H1 "$_H1" - - volcengine_host="$Volcengine_HOST" - CanonicalHeaders="host:$volcengine_host\n" - SignedHeaders="host" - - if [ -n "$data" ]; then - XContentSha256="$(printf "%s" "$data" | _digest "$Hash" hex)" - _H4="x-content-sha256: $XContentSha256" - _debug2 _H4 "$_H4" - - CanonicalHeaders="${CanonicalHeaders}x-content-sha256:$XContentSha256\n" - SignedHeaders="${SignedHeaders};x-content-sha256" - fi - - CanonicalHeaders="${CanonicalHeaders}x-date:$RequestDate\n" - SignedHeaders="${SignedHeaders};x-date" - - if [ -n "$Volcengine_SESSION_TOKEN" ]; then - _H3="x-security-token: $Volcengine_SESSION_TOKEN" - CanonicalHeaders="${CanonicalHeaders}x-security-token:$Volcengine_SESSION_TOKEN\n" - SignedHeaders="${SignedHeaders};x-security-token" - fi - - _debug2 CanonicalHeaders "$CanonicalHeaders" - _debug2 SignedHeaders "$SignedHeaders" - - RequestPayload="$data" - _debug2 RequestPayload "$RequestPayload" - - CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)" - _debug2 CanonicalRequest "$CanonicalRequest" - - HashedCanonicalRequest="$(printf '%b' "$CanonicalRequest" | _digest "$Hash" hex)" - _debug2 HashedCanonicalRequest "$HashedCanonicalRequest" - - Algorithm="HMAC-SHA256" - _debug2 Algorithm "$Algorithm" - - RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)" - _debug2 RequestDateOnly "$RequestDateOnly" - - Region="cn-beijing" - Service="dns" - - CredentialScope="$RequestDateOnly/$Region/$Service/request" - _debug2 CredentialScope "$CredentialScope" - - StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest" - - _debug2 StringToSign "$StringToSign" - - kSecret="$Volcengine_SECRET_ACCESS_KEY" - - _secure_debug2 kSecret "$kSecret" - - kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")" - _secure_debug2 kSecretH "$kSecretH" - - kDateH="$(printf "%s" "$RequestDateOnly" | _hmac "$Hash" "$kSecretH" hex)" - _debug2 kDateH "$kDateH" - - kRegionH="$(printf "%s" "$Region" | _hmac "$Hash" "$kDateH" hex)" - _debug2 kRegionH "$kRegionH" - - kServiceH="$(printf "%s" "$Service" | _hmac "$Hash" "$kRegionH" hex)" - _debug2 kServiceH "$kServiceH" - - kSigningH="$(printf "%s" "request" | _hmac "$Hash" "$kServiceH" hex)" - _debug2 kSigningH "$kSigningH" - - signature="$(printf '%b' "$StringToSign" | _hmac "$Hash" "$kSigningH" hex)" - _debug2 signature "$signature" - - Authorization="$Algorithm Credential=$Volcengine_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature" - _debug2 Authorization "$Authorization" - - _H2="Authorization: $Authorization" - _debug2 _H2 "$_H2" - - url="$Volcengine_URL/$ep" - if [ "$qsr" ]; then - url="$Volcengine_URL/$ep?$qsr" - fi - - if [ "$mtd" = "GET" ]; then - response="$(_get "$url")" - else - response="$(_post "$data" "$url" "" "POST" "application/json")" - fi - - _ret="$?" - _debug response "$response" - if [ "$_ret" = "0" ]; then - if _contains "$response" "\"Error\":{"; then - _err "Response error:$response" - return 1 - fi - fi - - return "$_ret" -} diff --git a/dnsapi/dns_vscale.sh b/dnsapi/dns_vscale.sh index faf3105d..d717d6e2 100755 --- a/dnsapi/dns_vscale.sh +++ b/dnsapi/dns_vscale.sh @@ -1,13 +1,11 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_vscale_info='vscale.io -Site: vscale.io -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_vscale -Options: - VSCALE_API_KEY API Key -Author: Alex Loban <@LAV45> -' +#This is the vscale.io api wrapper for acme.sh +# +#Author: Alex Loban +#Report Bugs here: https://github.com/LAV45/acme.sh + +#VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" VSCALE_API_URL="https://api.vscale.io/v1" ######## Public functions ##################### @@ -97,7 +95,7 @@ _get_root() { if _vscale_rest GET "domains/"; then response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -108,7 +106,7 @@ _get_root() { if [ "$hostedzone" ]; then _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_vultr.sh b/dnsapi/dns_vultr.sh index 4002e5de..58f14be1 100644 --- a/dnsapi/dns_vultr.sh +++ b/dnsapi/dns_vultr.sh @@ -1,12 +1,7 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_vultr_info='vultr.com -Site: vultr.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_vultr -Options: - VULTR_API_KEY API Key -Issues: github.com/acmesh-official/acme.sh/issues/2374 -' + +# +#VULTR_API_KEY=000011112222333344445555666677778888 VULTR_Api="https://api.vultr.com/v2" @@ -83,7 +78,7 @@ dns_vultr_rm() { return 1 fi - _record_id="$(echo "$response" | tr '{}' '\n' | grep '"TXT"' | grep -- "$txtvalue" | tr ',' '\n' | grep -i 'id' | cut -d : -f 2 | tr -d '"')" + _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." @@ -111,7 +106,7 @@ _get_root() { domain=$1 i=1 while true; do - _domain=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + _domain=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$_domain" if [ -z "$_domain" ]; then return 1 @@ -121,7 +116,7 @@ _get_root() { return 1 fi - if printf "%s\n" "$response" | grep -E '^\{.*\}' >/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 diff --git a/dnsapi/dns_websupport.sh b/dnsapi/dns_websupport.sh index 2374afc3..e824c9c0 100644 --- a/dnsapi/dns_websupport.sh +++ b/dnsapi/dns_websupport.sh @@ -1,16 +1,18 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_websupport_info='Websupport.sk -Site: Websupport.sk -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_websupport -Options: - WS_ApiKey API Key. Called "Identifier" in the WS Admin - WS_ApiSecret API Secret. Called "Secret key" in the WS Admin -Issues: github.com/acmesh-official/acme.sh/issues/3486 -Author: trgo.sk <@trgosk>, @akulumbeg -' + +# Acme.sh DNS API wrapper for websupport.sk +# +# Original author: trgo.sk (https://github.com/trgosk) +# Tweaks by: akulumbeg (https://github.com/akulumbeg) +# Report Bugs here: https://github.com/akulumbeg/acme.sh # Requirements: API Key and Secret from https://admin.websupport.sk/en/auth/apiKey +# +# WS_ApiKey="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +# (called "Identifier" in the WS Admin) +# +# WS_ApiSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" +# (called "Secret key" in the WS Admin) WS_Api="https://rest.websupport.sk" @@ -121,7 +123,7 @@ _get_root() { p=1 while true; do - h=$(printf "%s" "$domain" | cut -d . -f "$i"-100) + h=$(printf "%s" "$domain" | cut -d . -f $i-100) _debug h "$h" if [ -z "$h" ]; then #not valid @@ -135,7 +137,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\""; then _domain_id=$(echo "$response" | _egrep_o "\[.\"id\": *[^,]*" | _head_n 1 | cut -d : -f 2 | tr -d \" | tr -d " ") if [ "$_domain_id" ]; then - _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-"$p") + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h return 0 fi diff --git a/dnsapi/dns_wedos.sh b/dnsapi/dns_wedos.sh deleted file mode 100644 index d1f353e2..00000000 --- a/dnsapi/dns_wedos.sh +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_wedos_info='WEDOS.com -Site: wedos.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_wedos -Options: - WEDOS_Username WAPI login (account email) - WEDOS_Wapipass WAPI password -Issues: github.com/acmesh-official/acme.sh/issues/7071 -Author: Jan Forman -' - -WEDOS_Api="https://api.wedos.com/wapi/json" - -######## Public functions ##################### - -#Usage: dns_wedos_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_wedos_add() { - fulldomain=$(echo "$1" | _lower_case) - txtvalue=$2 - - if ! _wedos_init; then - return 1 - fi - - _debug "Detecting root zone for $fulldomain" - if ! _get_root "$fulldomain"; then - _err "Cannot determine root zone for: $fulldomain" - return 1 - fi - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - - _info "Adding TXT record: $_sub_domain.$_domain" - if ! _wedos_request "dns-row-add" "{\"domain\":\"$_domain\",\"name\":\"$_sub_domain\",\"ttl\":\"300\",\"type\":\"TXT\",\"rdata\":\"$txtvalue\"}"; then - _err "Failed to add TXT record" - return 1 - fi - - _info "Committing DNS changes for $_domain" - if ! _wedos_request "dns-domain-commit" "{\"name\":\"$_domain\"}"; then - _err "Failed to commit DNS changes" - return 1 - fi - - return 0 -} - -#Usage: dns_wedos_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_wedos_rm() { - fulldomain=$(echo "$1" | _lower_case) - txtvalue=$2 - - if ! _wedos_init; then - return 1 - fi - - _debug "Detecting root zone for $fulldomain" - if ! _get_root "$fulldomain"; then - _err "Cannot determine root zone for: $fulldomain" - return 1 - fi - _debug _domain "$_domain" - _debug _sub_domain "$_sub_domain" - - # _get_root leaves the dns-rows-list response for $_domain in $response - _debug "Looking up row IDs for TXT value: $txtvalue" - _row_ids=$(echo "$response" | tr '{' '\n' | grep -F -- "\"rdata\":\"$txtvalue\"" | grep -F -- "\"name\":\"$_sub_domain\"" | _egrep_o '"ID": *"[0-9]*"' | tr -dc '0-9\n') - _debug _row_ids "$_row_ids" - - if [ -z "$_row_ids" ]; then - _info "TXT record not found, nothing to remove" - return 0 - fi - - for _row_id in $_row_ids; do - _info "Removing TXT record ID $_row_id from $_domain" - if ! _wedos_request "dns-row-delete" "{\"domain\":\"$_domain\",\"row_id\":\"$_row_id\"}"; then - _err "Failed to delete TXT record" - return 1 - fi - done - - _info "Committing DNS changes for $_domain" - if ! _wedos_request "dns-domain-commit" "{\"name\":\"$_domain\"}"; then - _err "Failed to commit DNS changes" - return 1 - fi - - return 0 -} - -#################### Private functions below ################################## - -_wedos_init() { - WEDOS_Username="${WEDOS_Username:-$(_readaccountconf_mutable WEDOS_Username)}" - WEDOS_Wapipass="${WEDOS_Wapipass:-$(_readaccountconf_mutable WEDOS_Wapipass)}" - - if [ -z "$WEDOS_Username" ] || [ -z "$WEDOS_Wapipass" ]; then - WEDOS_Username="" - WEDOS_Wapipass="" - _err "You didn't specify the WEDOS WAPI credentials yet." - _err "Please export WEDOS_Username and WEDOS_Wapipass and try again." - return 1 - fi - - _saveaccountconf_mutable WEDOS_Username "$WEDOS_Username" - _saveaccountconf_mutable WEDOS_Wapipass "$WEDOS_Wapipass" - return 0 -} - -# WAPI auth token: sha1(login + sha1(password) + hour), where the hour is -# the current hour on the WEDOS servers (Europe/Prague timezone). -# The POSIX TZ string is used so no tzdata is required on the client. -_wedos_auth() { - if [ "$_wedos_utc" ]; then - # fallback: WAPI accepts 1 hour of skew, UTC+1 fits both CET and CEST - _wedos_hour=$(date -u +%H) - _wedos_hour=$(printf '%02d' "$(((${_wedos_hour#0} + 1) % 24))") - else - _wedos_hour=$(TZ='CET-1CEST,M3.5.0,M10.5.0/3' date +%H) - fi - _wedos_phash=$(printf '%s' "$WEDOS_Wapipass" | _digest sha1 hex) - printf '%s' "${WEDOS_Username}${_wedos_phash}${_wedos_hour}" | _digest sha1 hex -} - -#Usage: _wedos_request -#Returns 0 and sets $response on WAPI code 1000, returns 1 otherwise. -_wedos_request() { - _wedos_cmd="$1" - _wedos_data="$2" - - _wedos_token=$(_wedos_auth) - _secure_debug _wedos_token "$_wedos_token" - - _wedos_json="{\"request\":{\"user\":\"$WEDOS_Username\",\"auth\":\"$_wedos_token\",\"command\":\"$_wedos_cmd\",\"data\":$_wedos_data}}" - _debug2 "WAPI command: $_wedos_cmd" - _debug2 "WAPI data: $_wedos_data" - - # _post sends the global _H1.._H5 headers with every request; clear them so - # headers from earlier API calls are not leaked to the WAPI endpoint. - export _H1="" - export _H2="" - export _H3="" - export _H4="" - export _H5="" - - _wedos_body="request=$(printf '%s' "$_wedos_json" | _url_encode)" - response=$(_post "$_wedos_body" "$WEDOS_Api" "" "POST" "application/x-www-form-urlencoded") - if [ "$?" != "0" ]; then - _err "WAPI request failed for command '$_wedos_cmd'" - return 1 - fi - _debug2 "WAPI response: $response" - - _wedos_code=$(echo "$response" | _egrep_o '"code": *[0-9]*' | _head_n 1 | tr -dc '0-9') - _debug2 "WAPI result code: $_wedos_code" - if [ "$_wedos_code" = "1000" ]; then - return 0 - fi - - # some systems ignore the TZ variable (Haiku), sending a wrong auth hour; - # retry once with the UTC fallback in _wedos_auth - if [ "$_wedos_code" = "2050" ] && [ -z "$_wedos_utc" ]; then - _wedos_utc=1 - _wedos_request "$_wedos_cmd" "$_wedos_data" - return $? - fi - - # 2050 = bad credentials, 2051 = IP not whitelisted, 2052 = IP blocked - if [ "$_wedos_code" = "2050" ] || [ "$_wedos_code" = "2051" ] || [ "$_wedos_code" = "2052" ]; then - _wedos_result=$(echo "$response" | _egrep_o '"result": *"[^"]*"' | _head_n 1 | cut -d '"' -f 4) - _err "WAPI authentication error $_wedos_code: $_wedos_result" - _err "Check WEDOS_Username, WEDOS_Wapipass and the WAPI IP whitelist." - _wedos_autherr=1 - return 1 - fi - - _debug "WAPI error for command '$_wedos_cmd': $response" - return 1 -} - -# Determine the registered domain (_domain) and subdomain prefix (_sub_domain) -# by walking up the labels and calling dns-rows-list until WAPI accepts one. -# _acme-challenge.www.example.co.uk -# -> _sub_domain=_acme-challenge.www _domain=example.co.uk -# The full domain itself is tried first, so a zone apex (e.g. DNS alias mode -# pointing at the registered domain) resolves to an empty _sub_domain. -_get_root() { - _gr_full="$1" - _gr_i=1 - _wedos_autherr="" - while true; do - _gr_candidate=$(printf '%s' "$_gr_full" | cut -d . -f "${_gr_i}"-100) - _debug2 "Checking zone candidate: $_gr_candidate" - if [ -z "$_gr_candidate" ]; then - return 1 - fi - - if _wedos_request "dns-rows-list" "{\"domain\":\"$_gr_candidate\"}"; then - _domain="$_gr_candidate" - if [ "$_gr_i" = "1" ]; then - _sub_domain="" - else - _sub_domain=$(printf '%s' "$_gr_full" | cut -d . -f 1-"$((_gr_i - 1))") - fi - return 0 - fi - - # auth error hits every candidate, stop the walk - if [ "$_wedos_autherr" ]; then - return 1 - fi - - _gr_i=$((_gr_i + 1)) - done -} diff --git a/dnsapi/dns_west_cn.sh b/dnsapi/dns_west_cn.sh deleted file mode 100644 index b873bfc0..00000000 --- a/dnsapi/dns_west_cn.sh +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_west_cn_info='West.cn -Site: West.cn -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi2#dns_west_cn -Options: - WEST_Username API username - WEST_Key API Key. Set at https://www.west.cn/manager/API/APIconfig.asp -Issues: github.com/acmesh-official/acme.sh/issues/4894 -' - -REST_API="https://api.west.cn/API/v2" - -######## Public functions ##################### - -#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" -dns_west_cn_add() { - fulldomain=$1 - txtvalue=$2 - - WEST_Username="${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}" - WEST_Key="${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}" - if [ -z "$WEST_Username" ] || [ -z "$WEST_Key" ]; then - WEST_Username="" - WEST_Key="" - _err "You don't specify west api key and username yet." - _err "Please set you key and try again." - return 1 - fi - - #save the api key and email to the account conf file. - _saveaccountconf_mutable WEST_Username "$WEST_Username" - _saveaccountconf_mutable WEST_Key "$WEST_Key" - - add_record "$fulldomain" "$txtvalue" -} - -#Usage: rm _acme-challenge.www.domain.com -dns_west_cn_rm() { - fulldomain=$1 - txtvalue=$2 - - WEST_Username="${WEST_Username:-$(_readaccountconf_mutable WEST_Username)}" - WEST_Key="${WEST_Key:-$(_readaccountconf_mutable WEST_Key)}" - - if ! _rest POST "domain/dns/" "act=dnsrec.list&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT"; then - _err "dnsrec.list error." - return 1 - fi - - if _contains "$response" 'no records'; then - _info "Don't need to remove." - return 0 - fi - - record_id=$(echo "$response" | tr "{" "\n" | grep -- "$txtvalue" | grep '^"record_id"' | cut -d : -f 2 | cut -d ',' -f 1) - _debug record_id "$record_id" - if [ -z "$record_id" ]; then - _err "Can not get record id." - return 1 - fi - - if ! _rest POST "domain/dns/" "act=dnsrec.remove&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_id=$record_id"; then - _err "dnsrec.remove error." - return 1 - fi - - _contains "$response" "success" -} - -#add the txt record. -#usage: add fulldomain txtvalue -add_record() { - fulldomain=$1 - txtvalue=$2 - - _info "Adding record" - - if ! _rest POST "domain/dns/" "act=dnsrec.add&username=$WEST_Username&apikey=$WEST_Key&domain=$fulldomain&hostname=$fulldomain&record_type=TXT&record_value=$txtvalue"; then - return 1 - fi - - _contains "$response" "success" -} - -#Usage: method URI data -_rest() { - m="$1" - ep="$2" - data="$3" - _debug "$ep" - url="$REST_API/$ep" - - _debug url "$url" - - if [ "$m" = "GET" ]; then - response="$(_get "$url" | tr -d '\r')" - else - _debug2 data "$data" - response="$(_post "$data" "$url" | tr -d '\r')" - fi - - if [ "$?" != "0" ]; then - _err "error $ep" - return 1 - fi - _debug2 response "$response" - return 0 -} diff --git a/dnsapi/dns_world4you.sh b/dnsapi/dns_world4you.sh index 0a1cda6b..dfda4efd 100644 --- a/dnsapi/dns_world4you.sh +++ b/dnsapi/dns_world4you.sh @@ -1,14 +1,7 @@ #!/usr/bin/env sh -# shellcheck disable=SC2034 -dns_world4you_info='World4You.com -Site: World4You.com -Docs: github.com/acmesh-official/acme.sh/wiki/dnsapi#dns_world4you -Options: - WORLD4YOU_USERNAME Username - WORLD4YOU_PASSWORD Password -Issues: github.com/acmesh-official/acme.sh/issues/3269 -Author: Lorenz Stechauner <@NerLOR> -' + +# World4You - www.world4you.com +# Lorenz Stechauner, 2020 - https://www.github.com/NerLOR WORLD4YOU_API="https://my.world4you.com/en" PAKETNR='' @@ -61,7 +54,7 @@ dns_world4you_add() { if _contains "$res" "successfully"; then return 0 else - msg=$(_w4y_alert_msg "$res") + msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to add record: Unknown error" echo "$ret" >'error-01.html' @@ -110,12 +103,12 @@ dns_world4you_rm() { return 3 fi - recordid=$(echo "$form" | grep 'data-records="' | sed 's/.*"\([^"]*\)".*/\1/;s/"/"/g;s/},{/}\n{/g' | grep '"type":"TXT"' | grep "\"name\":\"$fqdn\"" | grep "\"value\":\"$value\"" | sed 's/^.*"id":"\([^"]*\)".*$/\1/') + recordid=$(printf "TXT:%s.:\"%s\"" "$fqdn" "$value" | _base64) _debug recordid "$recordid" _resethttp export ACME_HTTP_NO_REDIRECTS=1 - body="DeleteDnsRecordForm[id]=$recordid&DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[_token]=$form_token" + body="DeleteDnsRecordForm[recordId]=$recordid&DeleteDnsRecordForm[uniqueFormIdDP]=$formiddp&DeleteDnsRecordForm[_token]=$form_token" _info "Removing record..." ret=$(_post "$body" "$WORLD4YOU_API/$paketnr/dns/record/delete" '' POST 'application/x-www-form-urlencoded') _resethttp @@ -125,7 +118,7 @@ dns_world4you_rm() { if _contains "$res" "successfully"; then return 0 else - msg=$(_w4y_alert_msg "$res") + msg=$(echo "$res" | grep -A 15 'data-type="danger"' | grep "]*>[^<]" | sed 's/<[^>]*>//g' | sed 's/^\s*//g') if [ "$msg" = '' ]; then _err "Unable to remove record: Unknown error" echo "$ret" >'error-01.html' @@ -145,17 +138,6 @@ dns_world4you_rm() { ################ Private functions ################ -# Usage: _w4y_alert_msg -# Extracts the error text out of the alert box of a DNS page. -# "grep -A" is not portable (Solaris /usr/bin/grep: "illegal option -- A"), -# so select from the alert to EOF and keep the same number of lines. -# "\s" is a GNU sed extension, use an explicit space/tab bracket instead. -_w4y_alert_msg() { - _w4y_tab=$(printf '\t') - echo "$1" | sed -n '/alert-notification/,$p' | _head_n 21 | - grep 'class="weak-title">[^<]' | sed "s/<[^>]*>//g;s/^[ $_w4y_tab]*//" -} - # Usage: _login _login() { WORLD4YOU_USERNAME="${WORLD4YOU_USERNAME:-$(_readaccountconf_mutable WORLD4YOU_USERNAME)}" @@ -213,8 +195,7 @@ _get_paketnr() { fqdn="$1" form="$2" - domains=$(echo "$form" | grep 'paketListData' | grep -o '"fqdn":"[^"]*"' | sed 's/.*:"\(.*\)"/\1/') - _debug domains "$domains" + domains=$(echo "$form" | grep '