diff --git a/.github/workflows/DNS.yml b/.github/workflows/DNS.yml new file mode 100644 index 00000000..b7153506 --- /dev/null +++ b/.github/workflows/DNS.yml @@ -0,0 +1,211 @@ +name: DNS +on: + push: + paths: + - 'dnsapi/*.sh' + - '.github/workflows/DNS.yml' + pull_request: + branches: + - 'dev' + paths: + - 'dnsapi/*.sh' + - '.github/workflows/DNS.yml' + + +jobs: + CheckToken: + runs-on: ubuntu-latest + outputs: + hasToken: ${{ steps.step_one.outputs.hasToken }} + steps: + - name: Set the value + id: step_one + run: | + if [ "${{secrets.TokenName1}}" ] ; then + echo "::set-output name=hasToken::true" + else + echo "::set-output name=hasToken::false" + fi + - name: Check the value + run: echo ${{ steps.step_one.outputs.hasToken }} + + Fail: + runs-on: ubuntu-latest + needs: CheckToken + if: "contains(needs.CheckToken.outputs.hasToken, 'false')" + steps: + - name: "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test" + run: | + echo "Read this: https://github.com/acmesh-official/acme.sh/wiki/DNS-API-Test" + if [ "${{github.actor}}" != "Neilpang" ]; then + false + fi + + Docker: + runs-on: ubuntu-latest + needs: CheckToken + if: "contains(needs.CheckToken.outputs.hasToken, 'true')" + env: + TEST_DNS : ${{ secrets.TEST_DNS }} + TestingDomain: ${{ secrets.TestingDomain }} + TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} + TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} + CASE: le_test_dnsapi + TEST_LOCAL: 1 + DEBUG: 1 + steps: + - uses: actions/checkout@v2 + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Set env file + run: | + cd ../acmetest + if [ "${{ secrets.TokenName1}}" ] ; then + echo "${{ secrets.TokenName1}}=${{ secrets.TokenValue1}}" >> env.list + fi + if [ "${{ secrets.TokenName2}}" ] ; then + echo "${{ secrets.TokenName2}}=${{ secrets.TokenValue2}}" >> env.list + fi + if [ "${{ secrets.TokenName3}}" ] ; then + echo "${{ secrets.TokenName3}}=${{ secrets.TokenValue3}}" >> env.list + fi + if [ "${{ secrets.TokenName4}}" ] ; then + echo "${{ secrets.TokenName4}}=${{ secrets.TokenValue4}}" >> env.list + fi + if [ "${{ secrets.TokenName5}}" ] ; then + echo "${{ secrets.TokenName5}}=${{ secrets.TokenValue5}}" >> env.list + fi + echo "TEST_DNS_NO_WILDCARD" >> env.list + echo "TEST_DNS_SLEEP" >> env.list + - name: Run acmetest + run: cd ../acmetest && ./rundocker.sh testall + + MacOS: + runs-on: macos-latest + needs: Docker + env: + TEST_DNS : ${{ secrets.TEST_DNS }} + TestingDomain: ${{ secrets.TestingDomain }} + TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} + TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} + CASE: le_test_dnsapi + TEST_LOCAL: 1 + DEBUG: 1 + steps: + - uses: actions/checkout@v2 + - name: Install tools + run: brew install socat + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Run acmetest + run: | + if [ "${{ secrets.TokenName1}}" ] ; then + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} + fi + if [ "${{ secrets.TokenName2}}" ] ; then + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} + fi + if [ "${{ secrets.TokenName3}}" ] ; then + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} + fi + if [ "${{ secrets.TokenName4}}" ] ; then + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} + fi + if [ "${{ secrets.TokenName5}}" ] ; then + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} + fi + cd ../acmetest + ./letest.sh + + Windows: + runs-on: windows-latest + needs: MacOS + env: + TEST_DNS : ${{ secrets.TEST_DNS }} + TestingDomain: ${{ secrets.TestingDomain }} + TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} + TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} + CASE: le_test_dnsapi + TEST_LOCAL: 1 + DEBUG: 1 + steps: + - name: Set git to use LF + run: | + git config --global core.autocrlf false + - uses: actions/checkout@v2 + - name: Install cygwin base packages with chocolatey + run: | + choco config get cacheLocation + choco install --no-progress cygwin + shell: cmd + - name: Install cygwin additional packages + run: | + C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git + shell: cmd + - name: Set ENV + run: | + echo '::set-env name=PATH::C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Run acmetest + shell: bash + run: | + if [ "${{ secrets.TokenName1}}" ] ; then + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} + fi + if [ "${{ secrets.TokenName2}}" ] ; then + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} + fi + if [ "${{ secrets.TokenName3}}" ] ; then + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} + fi + if [ "${{ secrets.TokenName4}}" ] ; then + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} + fi + if [ "${{ secrets.TokenName5}}" ] ; then + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} + fi + cd ../acmetest + ./letest.sh + + FreeBSD: + runs-on: macos-latest + needs: Windows + env: + TEST_DNS : ${{ secrets.TEST_DNS }} + TestingDomain: ${{ secrets.TestingDomain }} + TEST_DNS_NO_WILDCARD: ${{ secrets.TEST_DNS_NO_WILDCARD }} + TEST_DNS_SLEEP: ${{ secrets.TEST_DNS_SLEEP }} + CASE: le_test_dnsapi + TEST_LOCAL: 1 + DEBUG: 1 + steps: + - uses: actions/checkout@v2 + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/freebsd-vm@v0.0.7 + with: + envs: 'TEST_DNS TestingDomain TEST_DNS_NO_WILDCARD TEST_DNS_SLEEP CASE TEST_LOCAL DEBUG ${{ secrets.TokenName1}} ${{ secrets.TokenName2}} ${{ secrets.TokenName3}} ${{ secrets.TokenName4}} ${{ secrets.TokenName5}}' + prepare: pkg install -y socat curl + usesh: true + run: | + if [ "${{ secrets.TokenName1}}" ] ; then + export ${{ secrets.TokenName1}}=${{ secrets.TokenValue1}} + fi + if [ "${{ secrets.TokenName2}}" ] ; then + export ${{ secrets.TokenName2}}=${{ secrets.TokenValue2}} + fi + if [ "${{ secrets.TokenName3}}" ] ; then + export ${{ secrets.TokenName3}}=${{ secrets.TokenValue3}} + fi + if [ "${{ secrets.TokenName4}}" ] ; then + export ${{ secrets.TokenName4}}=${{ secrets.TokenValue4}} + fi + if [ "${{ secrets.TokenName5}}" ] ; then + export ${{ secrets.TokenName5}}=${{ secrets.TokenValue5}} + fi + cd ../acmetest + ./letest.sh + + + diff --git a/.github/workflows/LetsEncrypt.yml b/.github/workflows/LetsEncrypt.yml new file mode 100644 index 00000000..5f0bba72 --- /dev/null +++ b/.github/workflows/LetsEncrypt.yml @@ -0,0 +1,116 @@ +name: LetsEncrypt +on: + push: + branches: + - '*' + paths: + - '**.sh' + - '**.yml' + pull_request: + branches: + - dev + paths: + - '**.sh' + - '**.yml' + + +jobs: + CheckToken: + runs-on: ubuntu-latest + outputs: + hasToken: ${{ steps.step_one.outputs.hasToken }} + env: + NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }} + steps: + - name: Set the value + id: step_one + run: | + if [ "$NGROK_TOKEN" ] ; then + echo "::set-output name=hasToken::true" + else + echo "::set-output name=hasToken::false" + fi + - name: Check the value + run: echo ${{ steps.step_one.outputs.hasToken }} + + Ubuntu: + runs-on: ubuntu-latest + needs: CheckToken + if: "contains(needs.CheckToken.outputs.hasToken, 'true')" + env: + NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }} + TEST_LOCAL: 1 + steps: + - uses: actions/checkout@v2 + - name: Install tools + run: sudo apt-get install -y socat + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Run acmetest + run: cd ../acmetest && sudo --preserve-env ./letest.sh + + MacOS: + runs-on: macos-latest + needs: Ubuntu + env: + NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }} + TEST_LOCAL: 1 + steps: + - uses: actions/checkout@v2 + - name: Install tools + run: brew install socat + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Run acmetest + run: cd ../acmetest && sudo --preserve-env ./letest.sh + + Windows: + runs-on: windows-latest + needs: MacOS + env: + NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }} + TEST_LOCAL: 1 + #The 80 port is used by Windows server, we have to use a custom port, ngrok will also use this port. + Le_HTTPPort: 8888 + steps: + - name: Set git to use LF + run: | + git config --global core.autocrlf false + - uses: actions/checkout@v2 + - name: Install cygwin base packages with chocolatey + run: | + choco config get cacheLocation + choco install --no-progress cygwin + shell: cmd + - name: Install cygwin additional packages + run: | + C:\tools\cygwin\cygwinsetup.exe -qgnNdO -R C:/tools/cygwin -s http://mirrors.kernel.org/sourceware/cygwin/ -P socat,curl,cron,unzip,git + shell: cmd + - name: Set ENV + run: | + echo '::set-env name=PATH::C:\tools\cygwin\bin;C:\tools\cygwin\usr\bin' + - name: Clone acmetest + shell: cmd + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Run acmetest + shell: cmd + run: cd ../acmetest && bash.exe -c ./letest.sh + + FreeBSD: + runs-on: macos-latest + needs: Windows + env: + NGROK_TOKEN : ${{ secrets.NGROK_TOKEN }} + TEST_LOCAL: 1 + steps: + - uses: actions/checkout@v2 + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - uses: vmactions/freebsd-vm@v0.0.7 + with: + envs: 'NGROK_TOKEN TEST_LOCAL' + prepare: pkg install -y socat curl + usesh: true + run: | + cd ../acmetest && ./letest.sh + diff --git a/.github/workflows/PebbleStrict.yml b/.github/workflows/PebbleStrict.yml new file mode 100644 index 00000000..976e5373 --- /dev/null +++ b/.github/workflows/PebbleStrict.yml @@ -0,0 +1,39 @@ +name: PebbleStrict +on: + push: + branches: + - '*' + paths: + - '**.sh' + - '**.yml' + pull_request: + branches: + - dev + paths: + - '**.sh' + - '**.yml' + +jobs: + PebbleStrict: + runs-on: ubuntu-latest + env: + TestingDomain: example.com + TestingAltDomains: www.example.com + ACME_DIRECTORY: https://localhost:14000/dir + HTTPS_INSECURE: 1 + Le_HTTPPort: 5002 + TEST_LOCAL: 1 + TEST_CA: "Pebble Intermediate CA" + + steps: + - uses: actions/checkout@v2 + - name: Install tools + run: sudo apt-get install -y socat + - name: Run Pebble + run: cd .. && curl https://raw.githubusercontent.com/letsencrypt/pebble/master/docker-compose.yml >docker-compose.yml && docker-compose up -d + - name: Set up Pebble + run: curl --request POST --data '{"ip":"10.30.50.1"}' http://localhost:8055/set-default-ipv4 + - name: Clone acmetest + run: cd .. && git clone https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ + - name: Run acmetest + run: cd ../acmetest && ./letest.sh \ No newline at end of file diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml index f1c0025d..89915af7 100644 --- a/.github/workflows/dockerhub.yml +++ b/.github/workflows/dockerhub.yml @@ -2,15 +2,65 @@ name: Build DockerHub on: push: - branches: [ master, dev ] + branches: + - '*' + tags: + - '*' jobs: + CheckToken: + runs-on: ubuntu-latest + outputs: + hasToken: ${{ steps.step_one.outputs.hasToken }} + env: + DOCKER_PASSWORD : ${{ secrets.DOCKER_PASSWORD }} + steps: + - name: Set the value + id: step_one + run: | + if [ "$DOCKER_PASSWORD" ] ; then + echo "::set-output name=hasToken::true" + else + echo "::set-output name=hasToken::false" + fi + - name: Check the value + run: echo ${{ steps.step_one.outputs.hasToken }} + build: runs-on: ubuntu-latest + needs: CheckToken + if: "contains(needs.CheckToken.outputs.hasToken, 'true')" steps: - - name: trigger - run: curl -X POST https://hub.docker.com/api/build/v1/source/1813a660-2ee5-4583-a238-dd54e9a6ebac/trigger/c8cd9f1f-f269-45bc-9750-a08327257f62/call/ - - + - name: checkout code + uses: actions/checkout@v2 + - name: install buildx + id: buildx + uses: crazy-max/ghaction-docker-buildx@v3 + with: + buildx-version: latest + qemu-version: latest + - name: login to docker hub + run: | + echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + - name: build and push the image + run: | + DOCKER_IMAGE=neilpang/acme.sh + + if [[ $GITHUB_REF == refs/tags/* ]]; then + DOCKER_IMAGE_TAG=${GITHUB_REF#refs/tags/} + fi + + if [[ $GITHUB_REF == refs/heads/* ]]; then + DOCKER_IMAGE_TAG=${GITHUB_REF#refs/heads/} + if [[ $DOCKER_IMAGE_TAG == master ]]; then + DOCKER_IMAGE_TAG=latest + AUTO_UPGRADE=1 + fi + fi + docker buildx build \ + --tag ${DOCKER_IMAGE}:${DOCKER_IMAGE_TAG} \ + --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 . diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 00000000..b22a2fd8 --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,33 @@ +name: Shellcheck +on: + push: + branches: + - '*' + paths: + - '**.sh' + - '**.yml' + pull_request: + branches: + - dev + paths: + - '**.sh' + - '**.yml' + +jobs: + ShellCheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Shellcheck + run: sudo apt-get install -y shellcheck + - name: DoShellcheck + run: shellcheck -V && shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" + + shfmt: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install shfmt + run: curl -sSL https://github.com/mvdan/sh/releases/download/v3.1.2/shfmt_v3.1.2_linux_amd64 -o ~/shfmt && chmod +x ~/shfmt + - name: shfmt + run: ~/shfmt -l -w -i 2 . ; git diff --exit-code && echo "shfmt OK" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 91da2731..00000000 --- a/.travis.yml +++ /dev/null @@ -1,37 +0,0 @@ -language: shell -dist: trusty - -os: - - linux - - osx - -services: - - docker - -env: - global: - - SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64 - - -install: - - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then - brew update && brew install socat; - export PATH="/usr/local/opt/openssl@1.1/bin:$PATH" ; - fi - -script: - - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)" - - command -V openssl && openssl version - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt && chmod +x ~/shfmt && ~/shfmt -l -w -i 2 . ; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then git diff --exit-code && echo "shfmt OK" ; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi - - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -e SC2181 **/*.sh && echo "shellcheck OK" ; fi - - cd .. - - git clone --depth 1 https://github.com/acmesh-official/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest - - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./rundocker.sh testplat ubuntu:latest ; fi - - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi - -matrix: - fast_finish: true - - diff --git a/Dockerfile b/Dockerfile index f00d03bd..6b382242 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ RUN apk update -f \ coreutils \ bind-tools \ curl \ + sed \ socat \ tzdata \ oath-toolkit-oathtool \ @@ -15,7 +16,9 @@ RUN apk update -f \ ENV LE_CONFIG_HOME /acme.sh -ENV AUTO_UPGRADE 1 +ARG AUTO_UPGRADE=1 + +ENV AUTO_UPGRADE $AUTO_UPGRADE #Install ADD ./ /install_acme.sh/ @@ -52,6 +55,7 @@ RUN for verb in help \ deactivate \ deactivate-account \ set-notify \ + set-default-ca \ ; do \ printf -- "%b" "#!/usr/bin/env sh\n/root/.acme.sh/acme.sh --${verb} --config-home /acme.sh \"\$@\"" >/usr/local/bin/--${verb} && chmod +x /usr/local/bin/--${verb} \ ; done diff --git a/README.md b/README.md index 67402f29..7215785c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,24 @@ -# An ACME Shell script: acme.sh [![Build Status](https://travis-ci.org/acmesh-official/acme.sh.svg?branch=master)](https://travis-ci.org/acmesh-official/acme.sh) +# An ACME Shell script: acme.sh + +![LetsEncrypt](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg) +![Shellcheck](https://github.com/acmesh-official/acme.sh/workflows/Shellcheck/badge.svg) +![PebbleStrict](https://github.com/acmesh-official/acme.sh/workflows/PebbleStrict/badge.svg) +![DockerHub](https://github.com/acmesh-official/acme.sh/workflows/Build%20DockerHub/badge.svg) + + + +[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Docker stars](https://img.shields.io/docker/stars/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") +[![Docker pulls](https://img.shields.io/docker/pulls/neilpang/acme.sh.svg)](https://hub.docker.com/r/neilpang/acme.sh "Click to view the image on Docker Hub") + + - [![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) - An ACME protocol client written purely in Shell (Unix shell) language. - Full ACME protocol implementation. - Support ACME v1 and ACME v2 - Support ACME v2 wildcard certs - Simple, powerful and very easy to use. You only need 3 minutes to learn it. - Bash, dash and sh compatible. -- Simplest shell script for Let's Encrypt free certificate client. - Purely written in Shell with no dependencies on python or the official Let's Encrypt client. - Just one script to issue, renew and install your certificates automatically. - DOES NOT require `root/sudoer` access. @@ -46,26 +57,26 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) | NO | Status| Platform| |----|-------|---------| -|1|[![](https://acmesh-official.github.io/acmetest/status/ubuntu-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Ubuntu -|2|[![](https://acmesh-official.github.io/acmetest/status/debian-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Debian -|3|[![](https://acmesh-official.github.io/acmetest/status/centos-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|CentOS -|4|[![](https://acmesh-official.github.io/acmetest/status/windows-cygwin.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included) -|5|[![](https://acmesh-official.github.io/acmetest/status/freebsd.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|FreeBSD -|6|[![](https://acmesh-official.github.io/acmetest/status/pfsense.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|pfsense -|7|[![](https://acmesh-official.github.io/acmetest/status/opensuse-leap-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|openSUSE -|8|[![](https://acmesh-official.github.io/acmetest/status/alpine-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Alpine Linux (with curl) -|9|[![](https://acmesh-official.github.io/acmetest/status/archlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Archlinux -|10|[![](https://acmesh-official.github.io/acmetest/status/fedora-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|fedora -|11|[![](https://acmesh-official.github.io/acmetest/status/kalilinux-kali.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Kali Linux -|12|[![](https://acmesh-official.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Oracle Linux -|13|[![](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) -|14|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 -|15|[![](https://acmesh-official.github.io/acmetest/status/openbsd.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|OpenBSD -|16|[![](https://acmesh-official.github.io/acmetest/status/mageia.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Mageia -|17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) -|18|[![](https://acmesh-official.github.io/acmetest/status/solaris.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|SunOS/Solaris -|19|[![](https://acmesh-official.github.io/acmetest/status/gentoo-stage3-amd64.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Gentoo Linux -|20|[![Build Status](https://travis-ci.org/acmesh-official/acme.sh.svg?branch=master)](https://travis-ci.org/acmesh-official/acme.sh)|Mac OSX +|1|[![MacOS](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Mac OSX +|2|[![Windows](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|Windows (cygwin with curl, openssl and crontab included) +|3|[![FreeBSD](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)|FreeBSD +|4|[![Ubuntu](https://github.com/acmesh-official/acme.sh/workflows/LetsEncrypt/badge.svg)](https://github.com/acmesh-official/acme.sh/actions?query=workflow%3ALetsEncrypt)| Ubuntu +|5|[![](https://acmesh-official.github.io/acmetest/status/pfsense.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|pfsense +|6|[![](https://acmesh-official.github.io/acmetest/status/openbsd.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|OpenBSD +|7|[![](https://acmesh-official.github.io/acmetest/status/solaris.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|SunOS/Solaris +|8|[![](https://acmesh-official.github.io/acmetest/status/debian-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)| Debian +|9|[![](https://acmesh-official.github.io/acmetest/status/centos-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|CentOS +|10|[![](https://acmesh-official.github.io/acmetest/status/opensuse-leap-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|openSUSE +|11|[![](https://acmesh-official.github.io/acmetest/status/alpine-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Alpine Linux (with curl) +|12|[![](https://acmesh-official.github.io/acmetest/status/archlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Archlinux +|13|[![](https://acmesh-official.github.io/acmetest/status/fedora-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|fedora +|14|[![](https://acmesh-official.github.io/acmetest/status/kalilinux-kali.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Kali Linux +|15|[![](https://acmesh-official.github.io/acmetest/status/oraclelinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Oracle Linux +|16|[![](https://acmesh-official.github.io/acmetest/status/proxmox.svg)](https://github.com/acmesh-official/letest#here-are-the-latest-status)| Proxmox: See Proxmox VE Wiki. Version [4.x, 5.0, 5.1](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x,_5.0_and_5.1)#Let.27s_Encrypt_using_acme.sh), version [5.2 and up](https://pve.proxmox.com/wiki/Certificate_Management) +|17|-----| Cloud Linux https://github.com/acmesh-official/acme.sh/issues/111 +|18|[![](https://acmesh-official.github.io/acmetest/status/mageia.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Mageia +|19|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/acmesh-official/acme.sh/wiki/How-to-run-on-OpenWRT) +|20|[![](https://acmesh-official.github.io/acmetest/status/gentoo-stage3-amd64.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|Gentoo Linux |21|[![](https://acmesh-official.github.io/acmetest/status/clearlinux-latest.svg)](https://github.com/acmesh-official/acmetest#here-are-the-latest-status)|ClearLinux For all build statuses, check our [weekly build project](https://github.com/acmesh-official/acmetest): @@ -75,6 +86,7 @@ https://github.com/acmesh-official/acmetest # Supported CA - Letsencrypt.org CA(default) +- [ZeroSSL.com CA](https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA) - [BuyPass.com CA](https://github.com/acmesh-official/acme.sh/wiki/BuyPass.com-CA) - [Pebble strict Mode](https://github.com/letsencrypt/pebble) @@ -300,7 +312,7 @@ https://github.com/acmesh-official/acme.sh/wiki/dnsapi See: https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode first. -If your dns provider doesn't support any api access, you can add the txt record by your hand. +If your dns provider doesn't support any api access, you can add the txt record by hand. ```bash acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com diff --git a/acme.sh b/acme.sh index 22eaaa86..3be3849d 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=2.8.7 +VER=2.8.8 PROJECT_NAME="acme.sh" @@ -23,23 +23,35 @@ _SUB_FOLDERS="$_SUB_FOLDER_DNSAPI $_SUB_FOLDER_DEPLOY $_SUB_FOLDER_NOTIFY" LETSENCRYPT_CA_V1="https://acme-v01.api.letsencrypt.org/directory" LETSENCRYPT_STAGING_CA_V1="https://acme-staging.api.letsencrypt.org/directory" -LETSENCRYPT_CA_V2="https://acme-v02.api.letsencrypt.org/directory" -LETSENCRYPT_STAGING_CA_V2="https://acme-staging-v02.api.letsencrypt.org/directory" +CA_LETSENCRYPT_V2="https://acme-v02.api.letsencrypt.org/directory" +CA_LETSENCRYPT_V2_TEST="https://acme-staging-v02.api.letsencrypt.org/directory" -DEFAULT_CA=$LETSENCRYPT_CA_V2 -DEFAULT_STAGING_CA=$LETSENCRYPT_STAGING_CA_V2 +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="http://api.zerossl.com/acme/eab-credentials-email" + +DEFAULT_CA=$CA_LETSENCRYPT_V2 +DEFAULT_STAGING_CA=$CA_LETSENCRYPT_V2_TEST + +CA_NAMES=" +LetsEncrypt.org,letsencrypt +LetsEncrypt.org_test,letsencrypt_test,letsencrypttest +BuyPass.com,buypass +BuyPass.com_test,buypass_test,buypasstest +ZeroSSL.com,zerossl +" + +CA_SERVERS="$CA_LETSENCRYPT_V2,$CA_LETSENCRYPT_V2_TEST,$CA_BUYPASS,$CA_BUYPASS_TEST,$CA_ZEROSSL" DEFAULT_USER_AGENT="$PROJECT_NAME/$VER ($PROJECT)" -DEFAULT_ACCOUNT_EMAIL="" DEFAULT_ACCOUNT_KEY_LENGTH=2048 DEFAULT_DOMAIN_KEY_LENGTH=2048 DEFAULT_OPENSSL_BIN="openssl" -_OLD_CA_HOST="https://acme-v01.api.letsencrypt.org" -_OLD_STAGE_CA_HOST="https://acme-staging.api.letsencrypt.org" - VTYPE_HTTP="http-01" VTYPE_DNS="dns-01" VTYPE_ALPN="tls-alpn-01" @@ -134,12 +146,20 @@ _DNS_ALIAS_WIKI="https://github.com/acmesh-official/acme.sh/wiki/DNS-alias-mode" _DNS_MANUAL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dns-manual-mode" +_DNS_API_WIKI="https://github.com/acmesh-official/acme.sh/wiki/dnsapi" + _NOTIFY_WIKI="https://github.com/acmesh-official/acme.sh/wiki/notify" _SUDO_WIKI="https://github.com/acmesh-official/acme.sh/wiki/sudo" _REVOKE_WIKI="https://github.com/acmesh-official/acme.sh/wiki/revokecert" +_ZEROSSL_WIKI="https://github.com/acmesh-official/acme.sh/wiki/ZeroSSL.com-CA" + +_SERVER_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Server" + +_PREFERRED_CHAIN_WIKI="https://github.com/acmesh-official/acme.sh/wiki/Preferred-Chain" + _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" @@ -504,27 +524,27 @@ _math() { _h_char_2_dec() { _ch=$1 case "${_ch}" in - a | A) - printf "10" - ;; - b | B) - printf "11" - ;; - c | C) - printf "12" - ;; - d | D) - printf "13" - ;; - e | E) - printf "14" - ;; - f | F) - printf "15" - ;; - *) - printf "%s" "$_ch" - ;; + a | A) + printf "10" + ;; + b | B) + printf "11" + ;; + c | C) + printf "12" + ;; + d | D) + printf "13" + ;; + e | E) + printf "14" + ;; + f | F) + printf "15" + ;; + *) + printf "%s" "$_ch" + ;; esac } @@ -632,211 +652,211 @@ _url_encode() { for _hex_code in $_hex_str; do #upper case case "${_hex_code}" in - "41") - printf "%s" "A" - ;; - "42") - printf "%s" "B" - ;; - "43") - printf "%s" "C" - ;; - "44") - printf "%s" "D" - ;; - "45") - printf "%s" "E" - ;; - "46") - printf "%s" "F" - ;; - "47") - printf "%s" "G" - ;; - "48") - printf "%s" "H" - ;; - "49") - printf "%s" "I" - ;; - "4a") - printf "%s" "J" - ;; - "4b") - printf "%s" "K" - ;; - "4c") - printf "%s" "L" - ;; - "4d") - printf "%s" "M" - ;; - "4e") - printf "%s" "N" - ;; - "4f") - printf "%s" "O" - ;; - "50") - printf "%s" "P" - ;; - "51") - printf "%s" "Q" - ;; - "52") - printf "%s" "R" - ;; - "53") - printf "%s" "S" - ;; - "54") - printf "%s" "T" - ;; - "55") - printf "%s" "U" - ;; - "56") - printf "%s" "V" - ;; - "57") - printf "%s" "W" - ;; - "58") - printf "%s" "X" - ;; - "59") - printf "%s" "Y" - ;; - "5a") - printf "%s" "Z" - ;; + "41") + printf "%s" "A" + ;; + "42") + printf "%s" "B" + ;; + "43") + printf "%s" "C" + ;; + "44") + printf "%s" "D" + ;; + "45") + printf "%s" "E" + ;; + "46") + printf "%s" "F" + ;; + "47") + printf "%s" "G" + ;; + "48") + printf "%s" "H" + ;; + "49") + printf "%s" "I" + ;; + "4a") + printf "%s" "J" + ;; + "4b") + printf "%s" "K" + ;; + "4c") + printf "%s" "L" + ;; + "4d") + printf "%s" "M" + ;; + "4e") + printf "%s" "N" + ;; + "4f") + printf "%s" "O" + ;; + "50") + printf "%s" "P" + ;; + "51") + printf "%s" "Q" + ;; + "52") + printf "%s" "R" + ;; + "53") + printf "%s" "S" + ;; + "54") + printf "%s" "T" + ;; + "55") + printf "%s" "U" + ;; + "56") + printf "%s" "V" + ;; + "57") + printf "%s" "W" + ;; + "58") + printf "%s" "X" + ;; + "59") + printf "%s" "Y" + ;; + "5a") + printf "%s" "Z" + ;; #lower case - "61") - printf "%s" "a" - ;; - "62") - printf "%s" "b" - ;; - "63") - printf "%s" "c" - ;; - "64") - printf "%s" "d" - ;; - "65") - printf "%s" "e" - ;; - "66") - printf "%s" "f" - ;; - "67") - printf "%s" "g" - ;; - "68") - printf "%s" "h" - ;; - "69") - printf "%s" "i" - ;; - "6a") - printf "%s" "j" - ;; - "6b") - printf "%s" "k" - ;; - "6c") - printf "%s" "l" - ;; - "6d") - printf "%s" "m" - ;; - "6e") - printf "%s" "n" - ;; - "6f") - printf "%s" "o" - ;; - "70") - printf "%s" "p" - ;; - "71") - printf "%s" "q" - ;; - "72") - printf "%s" "r" - ;; - "73") - printf "%s" "s" - ;; - "74") - printf "%s" "t" - ;; - "75") - printf "%s" "u" - ;; - "76") - printf "%s" "v" - ;; - "77") - printf "%s" "w" - ;; - "78") - printf "%s" "x" - ;; - "79") - printf "%s" "y" - ;; - "7a") - printf "%s" "z" - ;; + "61") + printf "%s" "a" + ;; + "62") + printf "%s" "b" + ;; + "63") + printf "%s" "c" + ;; + "64") + printf "%s" "d" + ;; + "65") + printf "%s" "e" + ;; + "66") + printf "%s" "f" + ;; + "67") + printf "%s" "g" + ;; + "68") + printf "%s" "h" + ;; + "69") + printf "%s" "i" + ;; + "6a") + printf "%s" "j" + ;; + "6b") + printf "%s" "k" + ;; + "6c") + printf "%s" "l" + ;; + "6d") + printf "%s" "m" + ;; + "6e") + printf "%s" "n" + ;; + "6f") + printf "%s" "o" + ;; + "70") + printf "%s" "p" + ;; + "71") + printf "%s" "q" + ;; + "72") + printf "%s" "r" + ;; + "73") + printf "%s" "s" + ;; + "74") + printf "%s" "t" + ;; + "75") + printf "%s" "u" + ;; + "76") + printf "%s" "v" + ;; + "77") + printf "%s" "w" + ;; + "78") + printf "%s" "x" + ;; + "79") + printf "%s" "y" + ;; + "7a") + printf "%s" "z" + ;; #numbers - "30") - printf "%s" "0" - ;; - "31") - printf "%s" "1" - ;; - "32") - printf "%s" "2" - ;; - "33") - printf "%s" "3" - ;; - "34") - printf "%s" "4" - ;; - "35") - printf "%s" "5" - ;; - "36") - printf "%s" "6" - ;; - "37") - printf "%s" "7" - ;; - "38") - printf "%s" "8" - ;; - "39") - printf "%s" "9" - ;; - "2d") - printf "%s" "-" - ;; - "5f") - printf "%s" "_" - ;; - "2e") - printf "%s" "." - ;; - "7e") - printf "%s" "~" - ;; - #other hex - *) - printf '%%%s' "$_hex_code" - ;; + "30") + printf "%s" "0" + ;; + "31") + printf "%s" "1" + ;; + "32") + printf "%s" "2" + ;; + "33") + printf "%s" "3" + ;; + "34") + printf "%s" "4" + ;; + "35") + printf "%s" "5" + ;; + "36") + printf "%s" "6" + ;; + "37") + printf "%s" "7" + ;; + "38") + printf "%s" "8" + ;; + "39") + printf "%s" "9" + ;; + "2d") + printf "%s" "-" + ;; + "5f") + printf "%s" "_" + ;; + "2e") + printf "%s" "." + ;; + "7e") + printf "%s" "~" + ;; + #other hex + *) + printf '%%%s' "$_hex_code" + ;; esac done } @@ -936,9 +956,9 @@ _dbase64() { _checkcert() { _cf="$1" if [ "$DEBUG" ]; then - openssl x509 -noout -text -in "$_cf" + ${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in "$_cf" else - openssl x509 -noout -text -in "$_cf" >/dev/null 2>&1 + ${ACME_OPENSSL_BIN:-openssl} x509 -noout -text -in "$_cf" >/dev/null 2>&1 fi } @@ -1039,7 +1059,7 @@ _sign() { _ec_s="0${_ec_s}" done fi - _debug3 "_ec_r" "$_ec_r" + _debug3 "_ec_r" "$_ec_r" _debug3 "_ec_s" "$_ec_s" printf "%s" "$_ec_r$_ec_s" | _h2b | _base64 else @@ -1057,11 +1077,11 @@ _isEccKey() { return 1 fi - [ "$_length" != "1024" ] \ - && [ "$_length" != "2048" ] \ - && [ "$_length" != "3072" ] \ - && [ "$_length" != "4096" ] \ - && [ "$_length" != "8192" ] + [ "$_length" != "1024" ] && + [ "$_length" != "2048" ] && + [ "$_length" != "3072" ] && + [ "$_length" != "4096" ] && + [ "$_length" != "8192" ] } # _createkey 2048|ec-256 file @@ -1355,7 +1375,7 @@ toPkcs() { domain="$1" pfxPassword="$2" if [ -z "$domain" ]; then - _usage "Usage: $PROJECT_ENTRY --toPkcs -d domain [--password pfx-password]" + _usage "Usage: $PROJECT_ENTRY --to-pkcs12 --domain [--password ] [--ecc]" return 1 fi @@ -1376,7 +1396,7 @@ toPkcs8() { domain="$1" if [ -z "$domain" ]; then - _usage "Usage: $PROJECT_ENTRY --toPkcs8 -d domain [--ecc]" + _usage "Usage: $PROJECT_ENTRY --to-pkcs8 --domain [--ecc]" return 1 fi @@ -1396,7 +1416,7 @@ toPkcs8() { createAccountKey() { _info "Creating account key" if [ -z "$1" ]; then - _usage "Usage: $PROJECT_ENTRY --createAccountKey --accountkeylength 2048" + _usage "Usage: $PROJECT_ENTRY --create-account-key [--accountkeylength ]" return fi @@ -1439,7 +1459,7 @@ _create_account_key() { createDomainKey() { _info "Creating domain key" if [ -z "$1" ]; then - _usage "Usage: $PROJECT_ENTRY --createDomainKey -d domain.com [ --keylength 2048 ]" + _usage "Usage: $PROJECT_ENTRY --create-domain-key --domain [--keylength ]" return fi @@ -1453,7 +1473,7 @@ createDomainKey() { _initpath "$domain" "$_cdl" - if [ ! -f "$CERT_KEY_PATH" ] || [ ! -s "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$IS_RENEW" ]) || [ "$Le_ForceNewDomainKey" = "1" ]; then + if [ ! -f "$CERT_KEY_PATH" ] || [ ! -s "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$_ACME_IS_RENEW" ]) || [ "$Le_ForceNewDomainKey" = "1" ]; then if _createkey "$_cdl" "$CERT_KEY_PATH"; then _savedomainconf Le_Keylength "$_cdl" _info "The domain key is here: $(__green $CERT_KEY_PATH)" @@ -1463,7 +1483,7 @@ createDomainKey() { return 1 fi else - if [ "$IS_RENEW" ]; then + if [ "$_ACME_IS_RENEW" ]; then _info "Domain key exists, skip" return 0 else @@ -1479,7 +1499,7 @@ createDomainKey() { createCSR() { _info "Creating csr" if [ -z "$1" ]; then - _usage "Usage: $PROJECT_ENTRY --createCSR -d domain1.com [-d domain2.com -d domain3.com ... ]" + _usage "Usage: $PROJECT_ENTRY --create-csr --domain [--domain ...]" return fi @@ -1489,7 +1509,7 @@ createCSR() { _initpath "$domain" "$_isEcc" - if [ -f "$CSR_PATH" ] && [ "$IS_RENEW" ] && [ -z "$FORCE" ]; then + if [ -f "$CSR_PATH" ] && [ "$_ACME_IS_RENEW" ] && [ -z "$FORCE" ]; then _info "CSR exists, skip" return fi @@ -1507,6 +1527,19 @@ _url_replace() { tr '/+' '_-' | tr -d '= ' } +#base64 string +_durl_replace_base64() { + _l=$((${#1} % 4)) + if [ $_l -eq 2 ]; then + _s="$1"'==' + elif [ $_l -eq 3 ]; then + _s="$1"'=' + else + _s="$1" + fi + echo "$_s" | tr '_-' '/+' +} + _time2str() { #BSD if date -u -r "$1" 2>/dev/null; then @@ -1597,22 +1630,22 @@ _calcjwk() { crv_oid="$(${ACME_OPENSSL_BIN:-openssl} ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")" _debug3 crv_oid "$crv_oid" case "${crv_oid}" in - "prime256v1") - crv="P-256" - __ECC_KEY_LEN=256 - ;; - "secp384r1") - crv="P-384" - __ECC_KEY_LEN=384 - ;; - "secp521r1") - crv="P-521" - __ECC_KEY_LEN=512 - ;; - *) - _err "ECC oid : $crv_oid" - return 1 - ;; + "prime256v1") + crv="P-256" + __ECC_KEY_LEN=256 + ;; + "secp384r1") + crv="P-384" + __ECC_KEY_LEN=384 + ;; + "secp521r1") + crv="P-521" + __ECC_KEY_LEN=512 + ;; + *) + _err "ECC oid : $crv_oid" + return 1 + ;; esac _debug3 crv "$crv" fi @@ -2053,7 +2086,7 @@ _send_signed_request() { _debug2 original "$response" if echo "$responseHeaders" | grep -i "Content-Type: *application/json" >/dev/null 2>&1; then - response="$(echo "$response" | _normalizeJson)" + response="$(echo "$response" | _normalizeJson | _json_decode)" fi _debug2 response "$response" @@ -2470,6 +2503,7 @@ _initAPI() { _err "Can not init api." return 1 fi + response=$(echo "$response" | _json_decode) _debug2 "response" "$response" ACME_KEY_CHANGE=$(echo "$response" | _egrep_o 'key-change" *: *"[^"]*"' | cut -d '"' -f 3) @@ -2552,7 +2586,7 @@ _initpath() { . "$ACCOUNT_CONF_PATH" fi - if [ "$ACME_IN_CRON" ]; then + if [ "$_ACME_IN_CRON" ]; then if [ ! "$_USER_PATH_EXPORTED" ]; then _USER_PATH_EXPORTED=1 export PATH="$USER_PATH:$PATH" @@ -2563,17 +2597,18 @@ _initpath() { CA_HOME="$DEFAULT_CA_HOME" fi - if [ "$ACME_VERSION" = "2" ]; then - DEFAULT_CA="$LETSENCRYPT_CA_V2" - DEFAULT_STAGING_CA="$LETSENCRYPT_STAGING_CA_V2" - fi - if [ -z "$ACME_DIRECTORY" ]; then - if [ -z "$STAGE" ]; then - ACME_DIRECTORY="$DEFAULT_CA" - else + if [ "$STAGE" ]; then ACME_DIRECTORY="$DEFAULT_STAGING_CA" - _info "Using stage ACME_DIRECTORY: $ACME_DIRECTORY" + _info "Using ACME_DIRECTORY: $ACME_DIRECTORY" + else + default_acme_server=$(_readaccountconf "DEFAULT_ACME_SERVER") + _debug default_acme_server "$default_acme_server" + if [ "$default_acme_server" ]; then + ACME_DIRECTORY="$default_acme_server" + else + ACME_DIRECTORY="$DEFAULT_CA" + fi fi fi @@ -2853,7 +2888,7 @@ Allow from all if _restoreApache; then _err "The apache config file is restored." else - _err "Sorry, The apache config file can not be restored, please report bug." + _err "Sorry, the apache config file can not be restored, please report bug." fi return 1 fi @@ -3353,7 +3388,7 @@ _on_issue_err() { ) fi - if [ "$IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "$W_DNS"; then + if [ "$_ACME_IS_RENEW" = "1" ] && _hasfield "$Le_Webroot" "$W_DNS"; then _err "$_DNS_MANUAL_ERR" fi @@ -3385,7 +3420,7 @@ _on_issue_success() { fi #run renew hook - if [ "$IS_RENEW" ] && [ "$_chk_renew_hook" ]; then + if [ "$_ACME_IS_RENEW" ] && [ "$_chk_renew_hook" ]; then _info "Run renew hook:'$_chk_renew_hook'" if ! ( export CERT_PATH @@ -3406,10 +3441,13 @@ _on_issue_success() { } +#account_key_length eab-kid eab-hmac-key registeraccount() { - _reg_length="$1" + _account_key_length="$1" + _eab_id="$2" + _eab_hmac_key="$3" _initpath - _regAccount "$_reg_length" + _regAccount "$_account_key_length" "$_eab_id" "$_eab_hmac_key" } __calcAccountKeyHash() { @@ -3420,10 +3458,27 @@ __calc_account_thumbprint() { printf "%s" "$jwk" | tr -d ' ' | _digest "sha256" | _url_replace } +_getAccountEmail() { + if [ "$ACCOUNT_EMAIL" ]; then + echo "$ACCOUNT_EMAIL" + return 0 + fi + if [ -z "$CA_EMAIL" ]; then + CA_EMAIL="$(_readcaconf CA_EMAIL)" + fi + if [ "$CA_EMAIL" ]; then + echo "$CA_EMAIL" + return 0 + fi + _readaccountconf "ACCOUNT_EMAIL" +} + #keylength _regAccount() { _initpath _reg_length="$1" + _eab_id="$2" + _eab_hmac_key="$3" _debug3 _regAccount "$_regAccount" _initAPI @@ -3448,46 +3503,115 @@ _regAccount() { if ! _calcjwk "$ACCOUNT_KEY_PATH"; then return 1 fi - + if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then + _savecaconf CA_EAB_KEY_ID "$_eab_id" + _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" + fi + _eab_id=$(_readcaconf "CA_EAB_KEY_ID") + _eab_hmac_key=$(_readcaconf "CA_EAB_HMAC_KEY") + _secure_debug3 _eab_id "$_eab_id" + _secure_debug3 _eab_hmac_key "$_eab_hmac_key" + _email="$(_getAccountEmail)" + if [ "$_email" ]; then + _savecaconf "CA_EMAIL" "$_email" + fi if [ "$ACME_VERSION" = "2" ]; then - regjson='{"termsOfServiceAgreed": true}' - if [ "$ACCOUNT_EMAIL" ]; then - regjson='{"contact": ["mailto:'$ACCOUNT_EMAIL'"], "termsOfServiceAgreed": true}' + if [ "$ACME_DIRECTORY" = "$CA_ZEROSSL" ]; then + if [ -z "$_eab_id" ] || [ -z "$_eab_hmac_key" ]; then + _info "No EAB credentials found for ZeroSSL, let's get one" + if [ -z "$_email" ]; then + _err "Please provide a email address for ZeroSSL account." + _err "See ZeroSSL usage: $_ZEROSSL_WIKI" + return 1 + fi + _eabresp=$(_post "email=$_email" $_ZERO_EAB_ENDPOINT) + if [ "$?" != "0" ]; then + _debug2 "$_eabresp" + _err "Can not get EAB credentials from ZeroSSL." + return 1 + fi + _eab_id="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_kid"' | cut -d : -f 2 | tr -d '"')" + if [ -z "$_eab_id" ]; then + _err "Can not resolve _eab_id" + return 1 + fi + _eab_hmac_key="$(echo "$_eabresp" | tr ',}' '\n' | grep '"eab_hmac_key"' | cut -d : -f 2 | tr -d '"')" + if [ -z "$_eab_hmac_key" ]; then + _err "Can not resolve _eab_hmac_key" + return 1 + fi + _savecaconf CA_EAB_KEY_ID "$_eab_id" + _savecaconf CA_EAB_HMAC_KEY "$_eab_hmac_key" + fi + fi + if [ "$_eab_id" ] && [ "$_eab_hmac_key" ]; then + eab_protected="{\"alg\":\"HS256\",\"kid\":\"$_eab_id\",\"url\":\"${ACME_NEW_ACCOUNT}\"}" + _debug3 eab_protected "$eab_protected" + + eab_protected64=$(printf "%s" "$eab_protected" | _base64 | _url_replace) + _debug3 eab_protected64 "$eab_protected64" + + eab_payload64=$(printf "%s" "$jwk" | _base64 | _url_replace) + _debug3 eab_payload64 "$eab_payload64" + + eab_sign_t="$eab_protected64.$eab_payload64" + _debug3 eab_sign_t "$eab_sign_t" + + key_hex="$(_durl_replace_base64 "$_eab_hmac_key" | _dbase64 | _hex_dump | tr -d ' ')" + _debug3 key_hex "$key_hex" + + eab_signature=$(printf "%s" "$eab_sign_t" | _hmac sha256 $key_hex | _base64 | _url_replace) + _debug3 eab_signature "$eab_signature" + + externalBinding=",\"externalAccountBinding\":{\"protected\":\"$eab_protected64\", \"payload\":\"$eab_payload64\", \"signature\":\"$eab_signature\"}" + _debug3 externalBinding "$externalBinding" fi + if [ "$_email" ]; then + email_sg="\"contact\": [\"mailto:$_email\"], " + fi + regjson="{$email_sg\"termsOfServiceAgreed\": true$externalBinding}" else _reg_res="$ACME_NEW_ACCOUNT_RES" regjson='{"resource": "'$_reg_res'", "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}' - if [ "$ACCOUNT_EMAIL" ]; then - regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$ACCOUNT_EMAIL'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}' + if [ "$_email" ]; then + regjson='{"resource": "'$_reg_res'", "contact": ["mailto:'$_email'"], "terms-of-service-agreed": true, "agreement": "'$ACME_AGREEMENT'"}' fi fi - _info "Registering account" + _info "Registering account: $ACME_DIRECTORY" if ! _send_signed_request "${ACME_NEW_ACCOUNT}" "$regjson"; then _err "Register account Error: $response" return 1 fi + _eabAlreadyBound="" if [ "$code" = "" ] || [ "$code" = '201' ]; then echo "$response" >"$ACCOUNT_JSON_PATH" _info "Registered" elif [ "$code" = '409' ] || [ "$code" = '200' ]; then _info "Already registered" + elif [ "$code" = '400' ] && _contains "$response" 'The account is not awaiting external account binding'; then + _info "Already register EAB." + _eabAlreadyBound=1 else _err "Register account Error: $response" return 1 fi - _debug2 responseHeaders "$responseHeaders" - _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")" - _debug "_accUri" "$_accUri" - if [ -z "$_accUri" ]; then - _err "Can not find account id url." - _err "$responseHeaders" - return 1 + if [ -z "$_eabAlreadyBound" ]; then + _debug2 responseHeaders "$responseHeaders" + _accUri="$(echo "$responseHeaders" | grep -i "^Location:" | _head_n 1 | cut -d ':' -f 2- | tr -d "\r\n ")" + _debug "_accUri" "$_accUri" + if [ -z "$_accUri" ]; then + _err "Can not find account id url." + _err "$responseHeaders" + return 1 + fi + _savecaconf "ACCOUNT_URL" "$_accUri" + else + ACCOUNT_URL="$(_readcaconf ACCOUNT_URL)" fi - _savecaconf "ACCOUNT_URL" "$_accUri" export ACCOUNT_URL="$_accUri" CA_KEY_HASH="$(__calcAccountKeyHash)" @@ -3536,9 +3660,10 @@ updateaccount() { fi _initAPI + _email="$(_getAccountEmail)" if [ "$ACME_VERSION" = "2" ]; then if [ "$ACCOUNT_EMAIL" ]; then - updjson='{"contact": ["mailto:'$ACCOUNT_EMAIL'"]}' + updjson='{"contact": ["mailto:'$_email'"]}' else updjson='{"contact": []}' fi @@ -3865,10 +3990,34 @@ _check_dns_entries() { } +#file +_get_cert_issuers() { + _cfile="$1" + if _contains "$(${ACME_OPENSSL_BIN:-openssl} help crl2pkcs7 2>&1)" "Usage: crl2pkcs7"; then + ${ACME_OPENSSL_BIN:-openssl} crl2pkcs7 -nocrl -certfile $_cfile | ${ACME_OPENSSL_BIN:-openssl} pkcs7 -print_certs -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 + else + ${ACME_OPENSSL_BIN:-openssl} x509 -in $_cfile -text -noout | grep 'Issuer:' | _egrep_o "CN *=[^,]*" | cut -d = -f 2 + fi +} + +#cert issuer +_match_issuer() { + _cfile="$1" + _missuer="$2" + _fissuers="$(_get_cert_issuers $_cfile)" + _debug2 _fissuers "$_fissuers" + if _contains "$_fissuers" "$_missuer"; then + return 0 + fi + _fissuers="$(echo "$_fissuers" | _lower_case)" + _missuer="$(echo "$_missuer" | _lower_case)" + _contains "$_fissuers" "$_missuer" +} + #webroot, domain domainlist keylength issue() { if [ -z "$2" ]; then - _usage "Usage: $PROJECT_ENTRY --issue -d a.com -w /path/to/webroot/a.com/ " + _usage "Usage: $PROJECT_ENTRY --issue --domain --webroot " return 1 fi if [ -z "$1" ]; then @@ -3897,18 +4046,9 @@ issue() { _renew_hook="${12}" _local_addr="${13}" _challenge_alias="${14}" - #remove these later. - if [ "$_web_roots" = "dns-cf" ]; then - _web_roots="dns_cf" - fi - if [ "$_web_roots" = "dns-dp" ]; then - _web_roots="dns_dp" - fi - if [ "$_web_roots" = "dns-cx" ]; then - _web_roots="dns_cx" - fi + _preferred_chain="${15}" - if [ ! "$IS_RENEW" ]; then + if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" mkdir -p "$DOMAIN_PATH" fi @@ -3959,14 +4099,16 @@ issue() { else _cleardomainconf "Le_ChallengeAlias" fi - - if [ "$ACME_DIRECTORY" != "$DEFAULT_CA" ]; then - Le_API="$ACME_DIRECTORY" - _savedomainconf "Le_API" "$Le_API" + if [ "$_preferred_chain" ]; then + _savedomainconf "Le_Preferred_Chain" "$_preferred_chain" "base64" else - _cleardomainconf Le_API + _cleardomainconf "Le_Preferred_Chain" fi + Le_API="$ACME_DIRECTORY" + _savedomainconf "Le_API" "$Le_API" + + _info "Using CA: $ACME_DIRECTORY" if [ "$_alt_domains" = "$NO_VALUE" ]; then _alt_domains="" fi @@ -4558,7 +4700,8 @@ $_authorizations_map" der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)" if [ "$ACME_VERSION" = "2" ]; then - _info "Lets finalize the order, Le_OrderFinalize: $Le_OrderFinalize" + _info "Lets finalize the order." + _info "Le_OrderFinalize" "$Le_OrderFinalize" if ! _send_signed_request "${Le_OrderFinalize}" "{\"csr\": \"$der\"}"; then _err "Sign failed." _on_issue_err "$_post_hook" @@ -4571,7 +4714,7 @@ $_authorizations_map" return 1 fi if [ -z "$Le_LinkOrder" ]; then - Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n" | cut -d ":" -f 2-)" + Le_LinkOrder="$(echo "$responseHeaders" | grep -i '^Location.*$' | _tail_n 1 | tr -d "\r\n \t" | cut -d ":" -f 2-)" fi _savedomainconf "Le_LinkOrder" "$Le_LinkOrder" @@ -4629,7 +4772,8 @@ $_authorizations_map" _on_issue_err "$_post_hook" return 1 fi - _info "Download cert, Le_LinkCert: $Le_LinkCert" + _info "Downloading cert." + _info "Le_LinkCert" "$Le_LinkCert" if ! _send_signed_request "$Le_LinkCert"; then _err "Sign failed, can not download cert:$Le_LinkCert." _err "$response" @@ -4638,17 +4782,34 @@ $_authorizations_map" fi echo "$response" >"$CERT_PATH" - - if [ "$(grep -- "$BEGIN_CERT" "$CERT_PATH" | wc -l)" -gt "1" ]; then - _debug "Found cert chain" - cat "$CERT_PATH" >"$CERT_FULLCHAIN_PATH" - _end_n="$(grep -n -- "$END_CERT" "$CERT_FULLCHAIN_PATH" | _head_n 1 | cut -d : -f 1)" - _debug _end_n "$_end_n" - sed -n "1,${_end_n}p" "$CERT_FULLCHAIN_PATH" >"$CERT_PATH" - _end_n="$(_math $_end_n + 1)" - sed -n "${_end_n},9999p" "$CERT_FULLCHAIN_PATH" >"$CA_CERT_PATH" + _split_cert_chain "$CERT_PATH" "$CERT_FULLCHAIN_PATH" "$CA_CERT_PATH" + + if [ "$_preferred_chain" ] && [ -f "$CERT_FULLCHAIN_PATH" ]; then + if ! _match_issuer "$CERT_FULLCHAIN_PATH" "$_preferred_chain"; then + rels="$(echo "$responseHeaders" | tr -d ' <>' | grep -i "^link:" | grep -i 'rel="alternate"' | cut -d : -f 2- | cut -d ';' -f 1)" + _debug2 "rels" "$rels" + for rel in $rels; do + _info "Try rel: $rel" + if ! _send_signed_request "$rel"; then + _err "Sign failed, can not download cert:$rel" + _err "$response" + continue + fi + _relcert="$CERT_PATH.alt" + _relfullchain="$CERT_FULLCHAIN_PATH.alt" + _relca="$CA_CERT_PATH.alt" + echo "$response" >"$_relcert" + _split_cert_chain "$_relcert" "$_relfullchain" "$_relca" + if _match_issuer "$_relfullchain" "$_preferred_chain"; then + _info "Matched issuer in: $rel" + cat $_relcert >"$CERT_PATH" + cat $_relfullchain >"$CERT_FULLCHAIN_PATH" + cat $_relca >"$CA_CERT_PATH" + break + fi + done + fi fi - else if ! _send_signed_request "${ACME_NEW_ORDER}" "{\"resource\": \"$ACME_NEW_ORDER_RES\", \"csr\": \"$der\"}" "needbase64"; then _err "Sign failed. $response" @@ -4692,7 +4853,7 @@ $_authorizations_map" _info "Your cert key is in $(__green " $CERT_KEY_PATH ")" fi - if [ ! "$USER_PATH" ] || [ ! "$ACME_IN_CRON" ]; then + if [ ! "$USER_PATH" ] || [ ! "$_ACME_IN_CRON" ]; then USER_PATH="$PATH" _saveaccountconf "USER_PATH" "$USER_PATH" fi @@ -4817,11 +4978,27 @@ $_authorizations_map" fi } +#in_out_cert out_fullchain out_ca +_split_cert_chain() { + _certf="$1" + _fullchainf="$2" + _caf="$3" + if [ "$(grep -- "$BEGIN_CERT" "$_certf" | wc -l)" -gt "1" ]; then + _debug "Found cert chain" + cat "$_certf" >"$_fullchainf" + _end_n="$(grep -n -- "$END_CERT" "$_fullchainf" | _head_n 1 | cut -d : -f 1)" + _debug _end_n "$_end_n" + sed -n "1,${_end_n}p" "$_fullchainf" >"$_certf" + _end_n="$(_math $_end_n + 1)" + sed -n "${_end_n},9999p" "$_fullchainf" >"$_caf" + fi +} + #domain [isEcc] renew() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then - _usage "Usage: $PROJECT_ENTRY --renew -d domain.com [--ecc]" + _usage "Usage: $PROJECT_ENTRY --renew --domain [--ecc]" return 1 fi @@ -4831,7 +5008,7 @@ renew() { _info "$(__green "Renew: '$Le_Domain'")" if [ ! -f "$DOMAIN_CONF" ]; then - _info "'$Le_Domain' is not a issued domain, skip." + _info "'$Le_Domain' is not an issued domain, skip." return $RENEW_SKIP fi @@ -4852,14 +5029,6 @@ renew() { fi if [ "$Le_API" ]; then - if [ "$_OLD_CA_HOST" = "$Le_API" ]; then - export Le_API="$DEFAULT_CA" - _savedomainconf Le_API "$Le_API" - fi - if [ "$_OLD_STAGE_CA_HOST" = "$Le_API" ]; then - export Le_API="$DEFAULT_STAGING_CA" - _savedomainconf Le_API "$Le_API" - fi export ACME_DIRECTORY="$Le_API" #reload ca configs ACCOUNT_KEY_PATH="" @@ -4875,17 +5044,18 @@ renew() { return "$RENEW_SKIP" fi - if [ "$ACME_IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then + if [ "$_ACME_IN_CRON" = "1" ] && [ -z "$Le_CertCreateTime" ]; then _info "Skip invalid cert for: $Le_Domain" return $RENEW_SKIP fi - IS_RENEW="1" + _ACME_IS_RENEW="1" Le_ReloadCmd="$(_readdomainconf Le_ReloadCmd)" Le_PreHook="$(_readdomainconf Le_PreHook)" Le_PostHook="$(_readdomainconf Le_PostHook)" Le_RenewHook="$(_readdomainconf Le_RenewHook)" - 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="$(_readdomainconf Le_Preferred_Chain)" + 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" res="$?" if [ "$res" != "0" ]; then return "$res" @@ -4896,7 +5066,7 @@ renew() { res="$?" fi - IS_RENEW="" + _ACME_IS_RENEW="" return "$res" } @@ -4917,7 +5087,7 @@ renewAll() { for di in "${CERT_HOME}"/*.*/; do _debug di "$di" if ! [ -d "$di" ]; then - _debug "Not directory, skip: $di" + _debug "Not a directory, skip: $di" continue fi d=$(basename "$di") @@ -4936,7 +5106,7 @@ renewAll() { _error_level="$NOTIFY_LEVEL_RENEW" _notify_code=0 fi - if [ "$ACME_IN_CRON" ]; then + if [ "$_ACME_IN_CRON" ]; then if [ $_set_level -ge $NOTIFY_LEVEL_RENEW ]; then if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then _send_notify "Renew $d success" "Good, the cert is renewed." "$NOTIFY_HOOK" 0 @@ -4950,7 +5120,7 @@ renewAll() { _error_level="$NOTIFY_LEVEL_SKIP" _notify_code=$RENEW_SKIP fi - if [ "$ACME_IN_CRON" ]; then + if [ "$_ACME_IN_CRON" ]; then if [ $_set_level -ge $NOTIFY_LEVEL_SKIP ]; then if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then _send_notify "Renew $d skipped" "Good, the cert is skipped." "$NOTIFY_HOOK" "$RENEW_SKIP" @@ -4965,7 +5135,7 @@ renewAll() { _error_level="$NOTIFY_LEVEL_ERROR" _notify_code=1 fi - if [ "$ACME_IN_CRON" ]; then + if [ "$_ACME_IN_CRON" ]; then if [ $_set_level -ge $NOTIFY_LEVEL_ERROR ]; then if [ "$NOTIFY_MODE" = "$NOTIFY_MODE_CERT" ]; then _send_notify "Renew $d error" "There is an error." "$NOTIFY_HOOK" 1 @@ -4986,7 +5156,7 @@ renewAll() { done _debug _error_level "$_error_level" _debug _set_level "$_set_level" - if [ "$ACME_IN_CRON" ] && [ $_error_level -le $_set_level ]; then + if [ "$_ACME_IN_CRON" ] && [ $_error_level -le $_set_level ]; then if [ -z "$NOTIFY_MODE" ] || [ "$NOTIFY_MODE" = "$NOTIFY_MODE_BULK" ]; then _msg_subject="Renew" if [ "$_error_msg" ]; then @@ -5020,7 +5190,7 @@ signcsr() { _csrfile="$1" _csrW="$2" if [ -z "$_csrfile" ] || [ -z "$_csrW" ]; then - _usage "Usage: $PROJECT_ENTRY --signcsr --csr mycsr.csr -w /path/to/webroot/a.com/ " + _usage "Usage: $PROJECT_ENTRY --sign-csr --csr --webroot " return 1 fi @@ -5088,7 +5258,7 @@ showcsr() { _csrfile="$1" _csrd="$2" if [ -z "$_csrfile" ] && [ -z "$_csrd" ]; then - _usage "Usage: $PROJECT_ENTRY --showcsr --csr mycsr.csr" + _usage "Usage: $PROJECT_ENTRY --show-csr --csr " return 1 fi @@ -5119,13 +5289,17 @@ showcsr() { _info "KeyLength=$_csrkeylength" } +#listraw domain list() { _raw="$1" + _domain="$2" _initpath _sep="|" if [ "$_raw" ]; then - printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Created${_sep}Renew" + if [ -z "$_domain" ]; then + printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}CA${_sep}Created${_sep}Renew" + fi for di in "${CERT_HOME}"/*.*/; do d=$(basename "$di") _debug d "$d" @@ -5137,15 +5311,22 @@ list() { DOMAIN_CONF="$di/$d.conf" if [ -f "$DOMAIN_CONF" ]; then . "$DOMAIN_CONF" - printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr" + _ca="$(_getCAShortName "$Le_API")" + if [ -z "$_domain" ]; then + printf "%s\n" "$Le_Domain${_sep}\"$Le_Keylength\"${_sep}$Le_Alt${_sep}$_ca${_sep}$Le_CertCreateTimeStr${_sep}$Le_NextRenewTimeStr" + else + if [ "$_domain" = "$d" ]; then + cat "$DOMAIN_CONF" + fi + fi fi ) done else if _exists column; then - list "raw" | column -t -s "$_sep" + list "raw" "$_domain" | column -t -s "$_sep" else - list "raw" | tr "$_sep" '\t' + list "raw" "$_domain" | tr "$_sep" '\t' fi fi @@ -5194,7 +5375,7 @@ deploy() { _hooks="$2" _isEcc="$3" if [ -z "$_hooks" ]; then - _usage "Usage: $PROJECT_ENTRY --deploy -d domain.com --deploy-hook cpanel [--ecc] " + _usage "Usage: $PROJECT_ENTRY --deploy --domain --deploy-hook [--ecc] " return 1 fi @@ -5215,7 +5396,7 @@ deploy() { installcert() { _main_domain="$1" if [ -z "$_main_domain" ]; then - _usage "Usage: $PROJECT_ENTRY --installcert -d domain.com [--ecc] [--cert-file cert-file-path] [--key-file key-file-path] [--ca-file ca-cert-file-path] [ --reloadCmd reloadCmd] [--fullchain-file fullchain-path]" + _usage "Usage: $PROJECT_ENTRY --install-cert --domain [--ecc] [--cert-file ] [--key-file ] [--ca-file ] [ --reloadcmd ] [--fullchain-file ]" return 1 fi @@ -5273,7 +5454,7 @@ _installcert() { if [ "$_real_cert" ]; then _info "Installing cert to:$_real_cert" - if [ -f "$_real_cert" ] && [ ! "$IS_RENEW" ]; then + if [ -f "$_real_cert" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_cert" "$_backup_path/cert.bak" fi cat "$CERT_PATH" >"$_real_cert" || return 1 @@ -5285,7 +5466,7 @@ _installcert() { echo "" >>"$_real_ca" cat "$CA_CERT_PATH" >>"$_real_ca" || return 1 else - if [ -f "$_real_ca" ] && [ ! "$IS_RENEW" ]; then + if [ -f "$_real_ca" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_ca" "$_backup_path/ca.bak" fi cat "$CA_CERT_PATH" >"$_real_ca" || return 1 @@ -5294,7 +5475,7 @@ _installcert() { if [ "$_real_key" ]; then _info "Installing key to:$_real_key" - if [ -f "$_real_key" ] && [ ! "$IS_RENEW" ]; then + if [ -f "$_real_key" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_key" "$_backup_path/key.bak" fi if [ -f "$_real_key" ]; then @@ -5307,7 +5488,7 @@ _installcert() { if [ "$_real_fullchain" ]; then _info "Installing full chain to:$_real_fullchain" - if [ -f "$_real_fullchain" ] && [ ! "$IS_RENEW" ]; then + if [ -f "$_real_fullchain" ] && [ ! "$_ACME_IS_RENEW" ]; then cp "$_real_fullchain" "$_backup_path/fullchain.bak" fi cat "$CERT_FULLCHAIN_PATH" >"$_real_fullchain" || return 1 @@ -5494,7 +5675,7 @@ uninstallcronjob() { revoke() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then - _usage "Usage: $PROJECT_ENTRY --revoke -d domain.com [--ecc]" + _usage "Usage: $PROJECT_ENTRY --revoke --domain [--ecc]" return 1 fi @@ -5565,7 +5746,7 @@ revoke() { remove() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then - _usage "Usage: $PROJECT_ENTRY --remove -d domain.com [--ecc]" + _usage "Usage: $PROJECT_ENTRY --remove --domain [--ecc]" return 1 fi @@ -5642,7 +5823,7 @@ _deactivate() { _URL_NAME="uri" fi - entries="$(echo "$response" | _egrep_o "{ *\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")" + entries="$(echo "$response" | _egrep_o "[^{]*\"type\":\"[^\"]*\", *\"status\": *\"valid\", *\"$_URL_NAME\"[^}]*")" if [ -z "$entries" ]; then _info "No valid entries found." if [ -z "$thumbprint" ]; then @@ -5725,7 +5906,7 @@ deactivate() { _initAPI _debug _d_domain_list "$_d_domain_list" if [ -z "$(echo $_d_domain_list | cut -d , -f 1)" ]; then - _usage "Usage: $PROJECT_ENTRY --deactivate -d domain.com [-d domain.com]" + _usage "Usage: $PROJECT_ENTRY --deactivate --domain [--domain ...]" return 1 fi for _d_dm in $(echo "$_d_domain_list" | tr ',' ' '); do @@ -5924,7 +6105,7 @@ install() { _debug "Skip install cron job" fi - if [ "$ACME_IN_CRON" != "1" ]; then + if [ "$_ACME_IN_CRON" != "1" ]; then if ! _precheck "$_nocron"; then _err "Pre-check failed, can not install." return 1 @@ -5981,7 +6162,7 @@ install() { _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" - if [ "$ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then + if [ "$_ACME_IN_CRON" != "1" ] && [ -z "$_noprofile" ]; then _installalias "$_c_home" fi @@ -6079,7 +6260,7 @@ _uninstallalias() { } cron() { - export ACME_IN_CRON=1 + export _ACME_IN_CRON=1 _initpath _info "$(__green "===Starting cron===")" if [ "$AUTO_UPGRADE" = "1" ]; then @@ -6100,7 +6281,7 @@ cron() { fi renewAll _ret="$?" - ACME_IN_CRON="" + _ACME_IN_CRON="" _info "$(__green "===End cron===")" exit $_ret } @@ -6185,7 +6366,7 @@ setnotify() { _initpath if [ -z "$_nhook$_nlevel$_nmode" ]; then - _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook mailgun] [--notify-level $NOTIFY_LEVEL_DEFAULT] [--notify-mode $NOTIFY_MODE_DEFAULT]" + _usage "Usage: $PROJECT_ENTRY --set-notify [--notify-hook ] [--notify-level <0|1|2|3>] [--notify-mode <0|1>]" _usage "$_NOTIFY_WIKI" return 1 fi @@ -6224,119 +6405,144 @@ setnotify() { showhelp() { _initpath version - echo "Usage: $PROJECT_ENTRY command ...[parameters].... + echo "Usage: $PROJECT_ENTRY ... [parameters ...] Commands: - --help, -h Show this help message. - --version, -v Show version info. + -h, --help Show this help message. + -v, --version Show version info. --install Install $PROJECT_NAME to your system. --uninstall Uninstall $PROJECT_NAME, and uninstall the cron job. --upgrade Upgrade $PROJECT_NAME to the latest code from $PROJECT. --issue Issue a cert. - --signcsr Issue a cert from an existing csr. --deploy Deploy the cert to your server. - --install-cert Install the issued cert to apache/nginx or any other server. - --renew, -r Renew a cert. + -i, --install-cert Install the issued cert to apache/nginx or any other server. + -r, --renew Renew a cert. --renew-all Renew all the certs. --revoke Revoke a cert. --remove Remove the cert from list of certs known to $PROJECT_NAME. --list List all the certs. - --showcsr Show the content of a csr. - --install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. - --uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically. - --cron Run cron job to renew all the certs. - --toPkcs Export the certificate and key to a pfx file. - --toPkcs8 Convert to pkcs8 format. + --to-pkcs12 Export the certificate and key to a pfx file. + --to-pkcs8 Convert to pkcs8 format. + --sign-csr Issue a cert from an existing csr. + --show-csr Show the content of a csr. + -ccr, --create-csr Create CSR, professional use. + --create-domain-key Create an domain private key, professional use. --update-account Update account info. --register-account Register account key. --deactivate-account Deactivate the account. --create-account-key Create an account private key, professional use. - --create-domain-key Create an domain private key, professional use. - --createCSR, -ccsr Create CSR , professional use. - --deactivate Deactivate the domain authz, professional use. + --install-cronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. + --uninstall-cronjob Uninstall the cron job. The 'uninstall' command can do this automatically. + --cron Run cron job to renew all the certs. --set-notify Set the cron notification hook, level or mode. + --deactivate Deactivate the domain authz, professional use. + --set-default-ca Used with '--server', Set the default CA to use. + See: $_SERVER_WIKI Parameters: - --domain, -d domain.tld Specifies a domain, used to issue, renew or revoke etc. - --challenge-alias domain.tld The challenge domain alias for DNS alias mode: $_DNS_ALIAS_WIKI - --domain-alias domain.tld The domain alias for DNS alias mode: $_DNS_ALIAS_WIKI - --force, -f Used to force to install or force to renew a cert immediately. - --staging, --test Use staging server, just for test. - --debug Output debug info. - --output-insecure Output all the sensitive messages. By default all the credentials/sensitive messages are hidden from the output/debug/log for security. - --webroot, -w /path/to/webroot Specifies the web root folder for web root mode. + -d, --domain Specifies a domain, used to issue, renew or revoke etc. + --challenge-alias The challenge domain alias for DNS alias mode. + See: $_DNS_ALIAS_WIKI + + --domain-alias The domain alias for DNS alias mode. + See: $_DNS_ALIAS_WIKI + + --preferred-chain If the CA offers multiple certificate chains, prefer the chain with an issuer matching this Subject Common Name. + If no match, the default offered chain will be used. (default: empty) + See: $_PREFERRED_CHAIN_WIKI + + -f, --force Force install, force cert renewal or override sudo restrictions. + --staging, --test Use staging server, for testing. + --debug [0|1|2|3] Output debug info. Defaults to 1 if argument is omitted. + --output-insecure Output all the sensitive messages. + By default all the credentials/sensitive messages are hidden from the output/debug/log for security. + -w, --webroot Specifies the web root folder for web root mode. --standalone Use standalone mode. --alpn Use standalone alpn mode. - --stateless Use stateless mode, see: $_STATELESS_WIKI + --stateless Use stateless mode. + See: $_STATELESS_WIKI + --apache Use apache mode. - --dns [dns_cf|dns_dp|dns_cx|/path/to/api/file] Use dns mode or dns api. - --dnssleep 300 The time in seconds to wait for all the txt records to take effect in dns api mode. It's not necessary to use this by default, $PROJECT_NAME polls dns status automatically. + --dns [dns_hook] Use dns manual mode or dns api. Defaults to manual mode when argument is omitted. + See: $_DNS_API_WIKI + + --dnssleep The time in seconds to wait for all the txt records to propagate in dns api mode. + It's not necessary to use this by default, $PROJECT_NAME polls dns status by DOH automatically. + -k, --keylength Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521. + -ak, --accountkeylength Specifies the account key length: 2048, 3072, 4096 + --log [file] Specifies the log file. Defaults to \"$DEFAULT_LOG_FILE\" if argument is omitted. + --log-level <1|2> Specifies the log level, default is 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. - --keylength, -k [2048] Specifies the domain key length: 2048, 3072, 4096, 8192 or ec-256, ec-384, ec-521. - --accountkeylength, -ak [2048] Specifies the account key length: 2048, 3072, 4096 - --log [/path/to/logfile] Specifies the log file. The default is: \"$DEFAULT_LOG_FILE\" if you don't give a file path here. - --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. These parameters are to install the cert to nginx/apache or any other server after issue/renew a cert: - --cert-file After issue/renew, the cert will be copied to this path. - --key-file After issue/renew, the key will be copied to this path. - --ca-file After issue/renew, the intermediate cert will be copied to this path. - --fullchain-file After issue/renew, the fullchain cert will be copied to this path. - - --reloadcmd \"service nginx reload\" After issue/renew, it's used to reload the server. - - --server SERVER ACME Directory Resource URI. (default: $DEFAULT_CA) - --accountconf Specifies a customized account config file. - --home Specifies the home dir for $PROJECT_NAME. - --cert-home Specifies the home dir to save all the certs, only valid for '--install' command. - --config-home Specifies the home dir to save all the configurations. - --useragent Specifies the user agent string. it will be saved for future use too. - --accountemail Specifies the account email, only valid for the '--install' and '--update-account' command. - --accountkey Specifies the account key path, only valid for the '--install' command. - --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. - --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. - --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. - --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses. + --cert-file Path to copy the cert file to after issue/renew.. + --key-file Path to copy the key file to after issue/renew. + --ca-file Path to copy the intermediate cert file to after issue/renew. + --fullchain-file Path to copy the fullchain cert file to after issue/renew. + --reloadcmd Command to execute after issue/renew to reload the server. + + --server ACME Directory Resource URI. (default: $DEFAULT_CA) + See: $_SERVER_WIKI + + --accountconf Specifies a customized account config file. + --home Specifies the home dir for $PROJECT_NAME. + --cert-home Specifies the home dir to save all the certs, only valid for '--install' command. + --config-home Specifies the home dir to save all the configurations. + --useragent Specifies the user agent string. it will be saved for future use too. + -m, --accountemail Specifies the account email, only valid for the '--install' and '--update-account' command. + --accountkey Specifies the account key path, only valid for the '--install' command. + --days Specifies the days to renew the cert when using '--issue' command. The default value is $DEFAULT_RENEW days. + --httpport Specifies the standalone listening port. Only valid if the server is behind a reverse proxy or load balancer. + --tlsport Specifies the standalone tls listening port. Only valid if the server is behind a reverse proxy or load balancer. + --local-address Specifies the standalone/tls server listening address, in case you have multiple ip addresses. --listraw Only used for '--list' command, list the certs in raw format. - --stopRenewOnError, -se Only valid for '--renew-all' command. Stop if one cert has error in renewal. + -se, --stop-renew-on-error Only valid for '--renew-all' command. Stop if one cert has error in renewal. --insecure Do not check the server certificate, in some devices, the api server's certificate may not be trusted. - --ca-bundle Specifies the path to the CA certificate bundle to verify api server's certificate. - --ca-path Specifies directory containing CA certificates in PEM format, used by wget or curl. - --nocron Only valid for '--install' command, which means: do not install the default cron job. In this case, the certs will not be renewed automatically. + --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. + --nocron Only valid for '--install' command, which means: do not install the default cron job. + In this case, the certs will not be renewed automatically. --noprofile Only valid for '--install' command, which means: do not install aliases to user profile. --no-color Do not output color text. --force-color Force output of color text. Useful for non-interactive use with the aha tool for HTML E-Mails. - --ecc Specifies to use the ECC cert. Valid for '--install-cert', '--renew', '--revoke', '--toPkcs' and '--createCSR' - --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. No matter the obtain/renew is success or failed. - --renew-hook Command to be run once for each successfully renewed certificate. - --deploy-hook The hook file to deploy cert - --ocsp-must-staple, --ocsp Generate ocsp must Staple extension. - --always-force-new-domain-key Generate new domain key when 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. + --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 + --ocsp, --ocsp-must-staple Generate OCSP-Must-Staple extension. + --always-force-new-domain-key Generate new domain key on renewal. Otherwise, the domain key is not changed by default. + --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. Defaults to 1 if argument is omitted. --listen-v4 Force standalone/tls server to listen at ipv4. --listen-v6 Force standalone/tls server to listen at ipv6. - --openssl-bin Specifies a custom openssl bin location. + --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 to use dns manual mode: $_DNS_MANUAL_WIKI - --branch, -b Only valid for '--upgrade' command, specifies the branch name to upgrade to. - - --notify-level 0|1|2|3 Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT. - 0: disabled, no notification will be sent. - 1: send notifications only when there is an error. - 2: send notifications when a cert is successfully renewed, or there is an error. - 3: send notifications when a cert is skipped, renewed, or error. - --notify-mode 0|1 Set notification mode. Default value is $NOTIFY_MODE_DEFAULT. - 0: Bulk mode. Send all the domain's notifications in one message(mail). - 1: Cert mode. Send a message for every single cert. - --notify-hook [hookname] Set the notify hook - --revoke-reason [0-10] The reason for '--revoke' command. See: $_REVOKE_WIKI + --yes-I-know-dns-manual-mode-enough-go-ahead-please Force use of dns manual mode. + See: $_DNS_MANUAL_WIKI -" -} + -b, --branch Only valid for '--upgrade' command, specifies the branch name to upgrade to. + --notify-level <0|1|2|3> Set the notification level: Default value is $NOTIFY_LEVEL_DEFAULT. + 0: disabled, no notification will be sent. + 1: send notifications only when there is an error. + 2: send notifications when a cert is successfully renewed, or there is an error. + 3: send notifications when a cert is skipped, renewed, or error. + --notify-mode <0|1> Set notification mode. Default value is $NOTIFY_MODE_DEFAULT. + 0: Bulk mode. Send all the domain's notifications in one message(mail). + 1: Cert mode. Send a message for every single cert. + --notify-hook Set the notify hook + --revoke-reason <0-10> The reason for revocation, can be used in conjunction with the '--revoke' command. + See: $_REVOKE_WIKI + + --password Add a password to exported pfx file. Use with --to-pkcs12. + + +" +} # nocron noprofile _installOnline() { @@ -6416,12 +6622,6 @@ _processAccountConf() { _saveaccountconf "USER_AGENT" "$USER_AGENT" fi - if [ "$_accountemail" ]; then - _saveaccountconf "ACCOUNT_EMAIL" "$_accountemail" - elif [ "$ACCOUNT_EMAIL" ] && [ "$ACCOUNT_EMAIL" != "$DEFAULT_ACCOUNT_EMAIL" ]; then - _saveaccountconf "ACCOUNT_EMAIL" "$ACCOUNT_EMAIL" - fi - if [ "$_openssl_bin" ]; then _saveaccountconf "ACME_OPENSSL_BIN" "$_openssl_bin" elif [ "$ACME_OPENSSL_BIN" ] && [ "$ACME_OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then @@ -6448,10 +6648,10 @@ _checkSudo() { #it's root using sudo, no matter it's using sudo or not, just fine return 0 fi - if [ "$SUDO_COMMAND" = "/bin/su" ] || [ "$SUDO_COMMAND" = "/bin/bash" ]; then + if [ -n "$SUDO_COMMAND" ]; then #it's a normal user doing "sudo su", or `sudo -i` or `sudo -s` - #fine - return 0 + _endswith "$SUDO_COMMAND" /bin/su || grep "^$SUDO_COMMAND\$" /etc/shells >/dev/null 2>&1 + return $? fi #otherwise return 1 @@ -6459,6 +6659,64 @@ _checkSudo() { return 0 } +#server +_selectServer() { + _server="$1" + _server_lower="$(echo "$_server" | _lower_case)" + _sindex=0 + for snames in $CA_NAMES; do + snames="$(echo "$snames" | _lower_case)" + _sindex="$(_math $_sindex + 1)" + _debug2 "_selectServer try snames" "$snames" + for sname in $(echo "$snames" | tr ',' ' '); do + if [ "$_server_lower" = "$sname" ]; then + _debug2 "_selectServer match $sname" + _serverdir="$(_getfield "$CA_SERVERS" $_sindex)" + _debug "Selected server: $_serverdir" + ACME_DIRECTORY="$_serverdir" + export ACME_DIRECTORY + return + fi + done + done + ACME_DIRECTORY="$_server" + export ACME_DIRECTORY +} + +#url +_getCAShortName() { + caurl="$1" + if [ -z "$caurl" ]; then + caurl="$DEFAULT_CA" + fi + caurl_lower="$(echo $caurl | _lower_case)" + _sindex=0 + for surl in $(echo "$CA_SERVERS" | _lower_case | tr , ' '); do + _sindex="$(_math $_sindex + 1)" + if [ "$caurl_lower" = "$surl" ]; then + _nindex=0 + for snames in $CA_NAMES; do + _nindex="$(_math $_nindex + 1)" + if [ $_nindex -ge $_sindex ]; then + _getfield "$snames" 1 + return + fi + done + fi + done + echo "$caurl" +} + +#set default ca to $ACME_DIRECTORY +setdefaultca() { + if [ -z "$ACME_DIRECTORY" ]; then + _err "Please give a --server parameter." + return 1 + fi + _saveaccountconf "DEFAULT_ACME_SERVER" "$ACME_DIRECTORY" + _info "Changed default CA to: $(__green "$ACME_DIRECTORY")" +} + _process() { _CMD="" _domain="" @@ -6510,490 +6768,507 @@ _process() { _notify_level="" _notify_mode="" _revoke_reason="" + _eab_kid="" + _eab_hmac_key="" + _preferred_chain="" while [ ${#} -gt 0 ]; do case "${1}" in - --help | -h) - showhelp - return - ;; - --version | -v) - version - return - ;; - --install) - _CMD="install" - ;; - --uninstall) - _CMD="uninstall" - ;; - --upgrade) - _CMD="upgrade" - ;; - --issue) - _CMD="issue" - ;; - --deploy) - _CMD="deploy" - ;; - --signcsr) - _CMD="signcsr" - ;; - --showcsr) - _CMD="showcsr" - ;; - --installcert | -i | --install-cert) - _CMD="installcert" - ;; - --renew | -r) - _CMD="renew" - ;; - --renewAll | --renewall | --renew-all) - _CMD="renewAll" - ;; - --revoke) - _CMD="revoke" - ;; - --remove) - _CMD="remove" - ;; - --list) - _CMD="list" - ;; - --installcronjob | --install-cronjob) - _CMD="installcronjob" - ;; - --uninstallcronjob | --uninstall-cronjob) - _CMD="uninstallcronjob" - ;; - --cron) - _CMD="cron" - ;; - --toPkcs) - _CMD="toPkcs" - ;; - --toPkcs8) - _CMD="toPkcs8" - ;; - --createAccountKey | --createaccountkey | -cak | --create-account-key) - _CMD="createAccountKey" - ;; - --createDomainKey | --createdomainkey | -cdk | --create-domain-key) - _CMD="createDomainKey" - ;; - --createCSR | --createcsr | -ccr) - _CMD="createCSR" - ;; - --deactivate) - _CMD="deactivate" - ;; - --updateaccount | --update-account) - _CMD="updateaccount" - ;; - --registeraccount | --register-account) - _CMD="registeraccount" - ;; - --deactivate-account) - _CMD="deactivateaccount" - ;; - --set-notify) - _CMD="setnotify" - ;; - --domain | -d) - _dvalue="$2" + --help | -h) + showhelp + return + ;; + --version | -v) + version + return + ;; + --install) + _CMD="install" + ;; + --uninstall) + _CMD="uninstall" + ;; + --upgrade) + _CMD="upgrade" + ;; + --issue) + _CMD="issue" + ;; + --deploy) + _CMD="deploy" + ;; + --sign-csr | --signcsr) + _CMD="signcsr" + ;; + --show-csr | --showcsr) + _CMD="showcsr" + ;; + -i | --install-cert | --installcert) + _CMD="installcert" + ;; + --renew | -r) + _CMD="renew" + ;; + --renew-all | --renewAll | --renewall) + _CMD="renewAll" + ;; + --revoke) + _CMD="revoke" + ;; + --remove) + _CMD="remove" + ;; + --list) + _CMD="list" + ;; + --install-cronjob | --installcronjob) + _CMD="installcronjob" + ;; + --uninstall-cronjob | --uninstallcronjob) + _CMD="uninstallcronjob" + ;; + --cron) + _CMD="cron" + ;; + --to-pkcs12 | --to-pkcs | --toPkcs) + _CMD="toPkcs" + ;; + --to-pkcs8 | --toPkcs8) + _CMD="toPkcs8" + ;; + --create-account-key | --createAccountKey | --createaccountkey | -cak) + _CMD="createAccountKey" + ;; + --create-domain-key | --createDomainKey | --createdomainkey | -cdk) + _CMD="createDomainKey" + ;; + -ccr | --create-csr | --createCSR | --createcsr) + _CMD="createCSR" + ;; + --deactivate) + _CMD="deactivate" + ;; + --update-account | --updateaccount) + _CMD="updateaccount" + ;; + --register-account | --registeraccount) + _CMD="registeraccount" + ;; + --deactivate-account) + _CMD="deactivateaccount" + ;; + --set-notify) + _CMD="setnotify" + ;; + --set-default-ca) + _CMD="setdefaultca" + ;; + -d | --domain) + _dvalue="$2" - if [ "$_dvalue" ]; then - if _startswith "$_dvalue" "-"; then - _err "'$_dvalue' is not a valid domain for parameter '$1'" - return 1 - fi - if _is_idn "$_dvalue" && ! _exists idn; then - _err "It seems that $_dvalue is an IDN( Internationalized Domain Names), please install 'idn' command first." - return 1 - fi + if [ "$_dvalue" ]; then + if _startswith "$_dvalue" "-"; then + _err "'$_dvalue' is not a valid domain for parameter '$1'" + return 1 + fi + if _is_idn "$_dvalue" && ! _exists idn; then + _err "It seems that $_dvalue is an IDN( Internationalized Domain Names), please install 'idn' command first." + return 1 + fi - if _startswith "$_dvalue" "*."; then - _debug "Wildcard domain" - export ACME_VERSION=2 - fi - if [ -z "$_domain" ]; then - _domain="$_dvalue" + if _startswith "$_dvalue" "*."; then + _debug "Wildcard domain" + export ACME_VERSION=2 + fi + if [ -z "$_domain" ]; then + _domain="$_dvalue" + else + if [ "$_altdomains" = "$NO_VALUE" ]; then + _altdomains="$_dvalue" else - if [ "$_altdomains" = "$NO_VALUE" ]; then - _altdomains="$_dvalue" - else - _altdomains="$_altdomains,$_dvalue" - fi + _altdomains="$_altdomains,$_dvalue" fi fi + fi - shift - ;; + shift + ;; - --force | -f) - FORCE="1" - ;; - --staging | --test) - STAGE="1" - ;; - --server) - ACME_DIRECTORY="$2" - _server="$ACME_DIRECTORY" - export ACME_DIRECTORY - shift - ;; - --debug) - if [ -z "$2" ] || _startswith "$2" "-"; then - DEBUG="$DEBUG_LEVEL_DEFAULT" - else - DEBUG="$2" - shift - fi - ;; - --output-insecure) - export OUTPUT_INSECURE=1 - ;; - --webroot | -w) - wvalue="$2" - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - shift - ;; - --challenge-alias) - cvalue="$2" - _challenge_alias="$_challenge_alias$cvalue," - shift - ;; - --domain-alias) - cvalue="$DNS_ALIAS_PREFIX$2" - _challenge_alias="$_challenge_alias$cvalue," + -f | --force) + FORCE="1" + ;; + --staging | --test) + STAGE="1" + ;; + --server) + _server="$2" + _selectServer "$_server" + shift + ;; + --debug) + if [ -z "$2" ] || _startswith "$2" "-"; then + DEBUG="$DEBUG_LEVEL_DEFAULT" + else + DEBUG="$2" shift - ;; - --standalone) - wvalue="$NO_VALUE" - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; - --alpn) - wvalue="$W_ALPN" - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; - --stateless) - wvalue="$MODE_STATELESS" - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; - --local-address) - lvalue="$2" - _local_address="$_local_address$lvalue," + fi + ;; + --output-insecure) + export OUTPUT_INSECURE=1 + ;; + -w | --webroot) + wvalue="$2" + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + shift + ;; + --challenge-alias) + cvalue="$2" + _challenge_alias="$_challenge_alias$cvalue," + shift + ;; + --domain-alias) + cvalue="$DNS_ALIAS_PREFIX$2" + _challenge_alias="$_challenge_alias$cvalue," + shift + ;; + --standalone) + wvalue="$NO_VALUE" + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; + --alpn) + wvalue="$W_ALPN" + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; + --stateless) + wvalue="$MODE_STATELESS" + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; + --local-address) + lvalue="$2" + _local_address="$_local_address$lvalue," + shift + ;; + --apache) + wvalue="apache" + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; + --nginx) + wvalue="$NGINX" + if [ "$2" ] && ! _startswith "$2" "-"; then + wvalue="$NGINX$2" shift - ;; - --apache) - wvalue="apache" - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; - --nginx) - wvalue="$NGINX" - if [ "$2" ] && ! _startswith "$2" "-"; then - wvalue="$NGINX$2" - shift - fi - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; - --dns) - wvalue="$W_DNS" - if [ "$2" ] && ! _startswith "$2" "-"; then - wvalue="$2" - shift - fi - if [ -z "$_webroot" ]; then - _webroot="$wvalue" - else - _webroot="$_webroot,$wvalue" - fi - ;; - --dnssleep) - _dnssleep="$2" - Le_DNSSleep="$_dnssleep" + fi + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; + --dns) + wvalue="$W_DNS" + if [ "$2" ] && ! _startswith "$2" "-"; then + wvalue="$2" shift - ;; + fi + if [ -z "$_webroot" ]; then + _webroot="$wvalue" + else + _webroot="$_webroot,$wvalue" + fi + ;; + --dnssleep) + _dnssleep="$2" + Le_DNSSleep="$_dnssleep" + shift + ;; - --keylength | -k) - _keylength="$2" - shift - ;; - --accountkeylength | -ak) - _accountkeylength="$2" - shift - ;; + --keylength | -k) + _keylength="$2" + shift + ;; + -ak | --accountkeylength) + _accountkeylength="$2" + shift + ;; - --cert-file | --certpath) - _cert_file="$2" - shift - ;; - --key-file | --keypath) - _key_file="$2" - shift - ;; - --ca-file | --capath) - _ca_file="$2" - shift - ;; - --fullchain-file | --fullchainpath) - _fullchain_file="$2" - shift - ;; - --reloadcmd | --reloadCmd) - _reloadcmd="$2" - shift - ;; - --password) - _password="$2" - shift - ;; - --accountconf) - _accountconf="$2" - ACCOUNT_CONF_PATH="$_accountconf" - shift - ;; - --home) - LE_WORKING_DIR="$2" - shift - ;; - --certhome | --cert-home) - _certhome="$2" - CERT_HOME="$_certhome" - shift - ;; - --config-home) - _confighome="$2" - LE_CONFIG_HOME="$_confighome" - shift - ;; - --useragent) - _useragent="$2" - USER_AGENT="$_useragent" - shift - ;; - --accountemail) - _accountemail="$2" - ACCOUNT_EMAIL="$_accountemail" - shift - ;; - --accountkey) - _accountkey="$2" - ACCOUNT_KEY_PATH="$_accountkey" - shift - ;; - --days) - _days="$2" - Le_RenewalDays="$_days" - shift - ;; - --httpport) - _httpport="$2" - Le_HTTPPort="$_httpport" - shift - ;; - --tlsport) - _tlsport="$2" - Le_TLSPort="$_tlsport" - shift - ;; - --listraw) - _listraw="raw" - ;; - --stopRenewOnError | --stoprenewonerror | -se) - _stopRenewOnError="1" - ;; - --insecure) - #_insecure="1" - HTTPS_INSECURE="1" - ;; - --ca-bundle) - _ca_bundle="$(_readlink "$2")" - CA_BUNDLE="$_ca_bundle" - shift - ;; - --ca-path) - _ca_path="$2" - CA_PATH="$_ca_path" - shift - ;; - --nocron) - _nocron="1" - ;; - --noprofile) - _noprofile="1" - ;; - --no-color) - export ACME_NO_COLOR=1 - ;; - --force-color) - export ACME_FORCE_COLOR=1 - ;; - --ecc) - _ecc="isEcc" - ;; - --csr) - _csr="$2" - shift - ;; - --pre-hook) - _pre_hook="$2" - shift - ;; - --post-hook) - _post_hook="$2" - shift - ;; - --renew-hook) - _renew_hook="$2" - shift - ;; - --deploy-hook) - if [ -z "$2" ] || _startswith "$2" "-"; then - _usage "Please specify a value for '--deploy-hook'" - return 1 - fi - _deploy_hook="$_deploy_hook$2," - shift - ;; - --ocsp-must-staple | --ocsp) - Le_OCSP_Staple="1" - ;; - --always-force-new-domain-key) - if [ -z "$2" ] || _startswith "$2" "-"; then - Le_ForceNewDomainKey=1 - else - Le_ForceNewDomainKey="$2" - shift - fi - ;; - --yes-I-know-dns-manual-mode-enough-go-ahead-please) - export FORCE_DNS_MANUAL=1 - ;; - --log | --logfile) - _log="1" - _logfile="$2" - if _startswith "$_logfile" '-'; then - _logfile="" - else - shift - fi - LOG_FILE="$_logfile" - if [ -z "$LOG_LEVEL" ]; then - LOG_LEVEL="$DEFAULT_LOG_LEVEL" - fi - ;; - --log-level) - _log_level="$2" - LOG_LEVEL="$_log_level" - shift - ;; - --syslog) - if ! _startswith "$2" '-'; then - _syslog="$2" - shift - fi - if [ -z "$_syslog" ]; then - _syslog="$SYSLOG_LEVEL_DEFAULT" - fi - ;; - --auto-upgrade) - _auto_upgrade="$2" - if [ -z "$_auto_upgrade" ] || _startswith "$_auto_upgrade" '-'; then - _auto_upgrade="1" - else - shift - fi - AUTO_UPGRADE="$_auto_upgrade" - ;; - --listen-v4) - _listen_v4="1" - Le_Listen_V4="$_listen_v4" - ;; - --listen-v6) - _listen_v6="1" - Le_Listen_V6="$_listen_v6" - ;; - --openssl-bin) - _openssl_bin="$2" - ACME_OPENSSL_BIN="$_openssl_bin" - shift - ;; - --use-wget) - _use_wget="1" - ACME_USE_WGET="1" - ;; - --branch | -b) - export BRANCH="$2" - shift - ;; - --notify-hook) - _nhook="$2" - if _startswith "$_nhook" "-"; then - _err "'$_nhook' is not a hook name for '$1'" - return 1 - fi - if [ "$_notify_hook" ]; then - _notify_hook="$_notify_hook,$_nhook" - else - _notify_hook="$_nhook" - fi + --cert-file | --certpath) + _cert_file="$2" + shift + ;; + --key-file | --keypath) + _key_file="$2" + shift + ;; + --ca-file | --capath) + _ca_file="$2" + shift + ;; + --fullchain-file | --fullchainpath) + _fullchain_file="$2" + shift + ;; + --reloadcmd | --reloadCmd) + _reloadcmd="$2" + shift + ;; + --password) + _password="$2" + shift + ;; + --accountconf) + _accountconf="$2" + ACCOUNT_CONF_PATH="$_accountconf" + shift + ;; + --home) + LE_WORKING_DIR="$2" + shift + ;; + --cert-home | --certhome) + _certhome="$2" + CERT_HOME="$_certhome" + shift + ;; + --config-home) + _confighome="$2" + LE_CONFIG_HOME="$_confighome" + shift + ;; + --useragent) + _useragent="$2" + USER_AGENT="$_useragent" + shift + ;; + -m | --accountemail) + _accountemail="$2" + ACCOUNT_EMAIL="$_accountemail" + shift + ;; + --accountkey) + _accountkey="$2" + ACCOUNT_KEY_PATH="$_accountkey" + shift + ;; + --days) + _days="$2" + Le_RenewalDays="$_days" + shift + ;; + --httpport) + _httpport="$2" + Le_HTTPPort="$_httpport" + shift + ;; + --tlsport) + _tlsport="$2" + Le_TLSPort="$_tlsport" + shift + ;; + --listraw) + _listraw="raw" + ;; + -se | --stop-renew-on-error | --stopRenewOnError | --stoprenewonerror) + _stopRenewOnError="1" + ;; + --insecure) + #_insecure="1" + HTTPS_INSECURE="1" + ;; + --ca-bundle) + _ca_bundle="$(_readlink "$2")" + CA_BUNDLE="$_ca_bundle" + shift + ;; + --ca-path) + _ca_path="$2" + CA_PATH="$_ca_path" + shift + ;; + --nocron) + _nocron="1" + ;; + --noprofile) + _noprofile="1" + ;; + --no-color) + export ACME_NO_COLOR=1 + ;; + --force-color) + export ACME_FORCE_COLOR=1 + ;; + --ecc) + _ecc="isEcc" + ;; + --csr) + _csr="$2" + shift + ;; + --pre-hook) + _pre_hook="$2" + shift + ;; + --post-hook) + _post_hook="$2" + shift + ;; + --renew-hook) + _renew_hook="$2" + shift + ;; + --deploy-hook) + if [ -z "$2" ] || _startswith "$2" "-"; then + _usage "Please specify a value for '--deploy-hook'" + return 1 + fi + _deploy_hook="$_deploy_hook$2," + shift + ;; + --ocsp-must-staple | --ocsp) + Le_OCSP_Staple="1" + ;; + --always-force-new-domain-key) + if [ -z "$2" ] || _startswith "$2" "-"; then + Le_ForceNewDomainKey=1 + else + Le_ForceNewDomainKey="$2" shift - ;; - --notify-level) - _nlevel="$2" - if _startswith "$_nlevel" "-"; then - _err "'$_nlevel' is not a integer for '$1'" - return 1 - fi - _notify_level="$_nlevel" + fi + ;; + --yes-I-know-dns-manual-mode-enough-go-ahead-please) + export FORCE_DNS_MANUAL=1 + ;; + --log | --logfile) + _log="1" + _logfile="$2" + if _startswith "$_logfile" '-'; then + _logfile="" + else shift - ;; - --notify-mode) - _nmode="$2" - if _startswith "$_nmode" "-"; then - _err "'$_nmode' is not a integer for '$1'" - return 1 - fi - _notify_mode="$_nmode" + fi + LOG_FILE="$_logfile" + if [ -z "$LOG_LEVEL" ]; then + LOG_LEVEL="$DEFAULT_LOG_LEVEL" + fi + ;; + --log-level) + _log_level="$2" + LOG_LEVEL="$_log_level" + shift + ;; + --syslog) + if ! _startswith "$2" '-'; then + _syslog="$2" shift - ;; - --revoke-reason) - _revoke_reason="$2" - if _startswith "$_revoke_reason" "-"; then - _err "'$_revoke_reason' is not a integer for '$1'" - return 1 - fi + fi + if [ -z "$_syslog" ]; then + _syslog="$SYSLOG_LEVEL_DEFAULT" + fi + ;; + --auto-upgrade) + _auto_upgrade="$2" + if [ -z "$_auto_upgrade" ] || _startswith "$_auto_upgrade" '-'; then + _auto_upgrade="1" + else shift - ;; - *) - _err "Unknown parameter : $1" + fi + AUTO_UPGRADE="$_auto_upgrade" + ;; + --listen-v4) + _listen_v4="1" + Le_Listen_V4="$_listen_v4" + ;; + --listen-v6) + _listen_v6="1" + Le_Listen_V6="$_listen_v6" + ;; + --openssl-bin) + _openssl_bin="$2" + ACME_OPENSSL_BIN="$_openssl_bin" + shift + ;; + --use-wget) + _use_wget="1" + ACME_USE_WGET="1" + ;; + --branch | -b) + export BRANCH="$2" + shift + ;; + --notify-hook) + _nhook="$2" + if _startswith "$_nhook" "-"; then + _err "'$_nhook' is not a hook name for '$1'" return 1 - ;; + fi + if [ "$_notify_hook" ]; then + _notify_hook="$_notify_hook,$_nhook" + else + _notify_hook="$_nhook" + fi + shift + ;; + --notify-level) + _nlevel="$2" + if _startswith "$_nlevel" "-"; then + _err "'$_nlevel' is not a integer for '$1'" + return 1 + fi + _notify_level="$_nlevel" + shift + ;; + --notify-mode) + _nmode="$2" + if _startswith "$_nmode" "-"; then + _err "'$_nmode' is not a integer for '$1'" + return 1 + fi + _notify_mode="$_nmode" + shift + ;; + --revoke-reason) + _revoke_reason="$2" + if _startswith "$_revoke_reason" "-"; then + _err "'$_revoke_reason' is not a integer for '$1'" + return 1 + fi + shift + ;; + --eab-kid) + _eab_kid="$2" + shift + ;; + --eab-hmac-key) + _eab_hmac_key="$2" + shift + ;; + --preferred-chain) + _preferred_chain="$2" + shift + ;; + *) + _err "Unknown parameter : $1" + return 1 + ;; esac shift 1 @@ -7052,79 +7327,82 @@ _process() { fi _debug "Running cmd: ${_CMD}" case "${_CMD}" in - install) install "$_nocron" "$_confighome" "$_noprofile" ;; - 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" - ;; - 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" - ;; - showcsr) - showcsr "$_csr" "$_domain" - ;; - installcert) - installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc" - ;; - renew) - renew "$_domain" "$_ecc" - ;; - renewAll) - renewAll "$_stopRenewOnError" - ;; - revoke) - revoke "$_domain" "$_ecc" "$_revoke_reason" - ;; - remove) - remove "$_domain" "$_ecc" - ;; - deactivate) - deactivate "$_domain,$_altdomains" - ;; - registeraccount) - registeraccount "$_accountkeylength" - ;; - updateaccount) - updateaccount - ;; - deactivateaccount) - deactivateaccount - ;; - list) - list "$_listraw" - ;; - installcronjob) installcronjob "$_confighome" ;; - uninstallcronjob) uninstallcronjob ;; - cron) cron ;; - toPkcs) - toPkcs "$_domain" "$_password" "$_ecc" - ;; - toPkcs8) - toPkcs8 "$_domain" "$_ecc" - ;; - createAccountKey) - createAccountKey "$_accountkeylength" - ;; - createDomainKey) - createDomainKey "$_domain" "$_keylength" - ;; - createCSR) - createCSR "$_domain" "$_altdomains" "$_ecc" - ;; - setnotify) - setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" - ;; - *) - if [ "$_CMD" ]; then - _err "Invalid command: $_CMD" - fi - showhelp - return 1 - ;; + install) install "$_nocron" "$_confighome" "$_noprofile" ;; + 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" + ;; + 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" + ;; + showcsr) + showcsr "$_csr" "$_domain" + ;; + installcert) + installcert "$_domain" "$_cert_file" "$_key_file" "$_ca_file" "$_reloadcmd" "$_fullchain_file" "$_ecc" + ;; + renew) + renew "$_domain" "$_ecc" + ;; + renewAll) + renewAll "$_stopRenewOnError" + ;; + revoke) + revoke "$_domain" "$_ecc" "$_revoke_reason" + ;; + remove) + remove "$_domain" "$_ecc" + ;; + deactivate) + deactivate "$_domain,$_altdomains" + ;; + registeraccount) + registeraccount "$_accountkeylength" "$_eab_kid" "$_eab_hmac_key" + ;; + updateaccount) + updateaccount + ;; + deactivateaccount) + deactivateaccount + ;; + list) + list "$_listraw" "$_domain" + ;; + installcronjob) installcronjob "$_confighome" ;; + uninstallcronjob) uninstallcronjob ;; + cron) cron ;; + toPkcs) + toPkcs "$_domain" "$_password" "$_ecc" + ;; + toPkcs8) + toPkcs8 "$_domain" "$_ecc" + ;; + createAccountKey) + createAccountKey "$_accountkeylength" + ;; + createDomainKey) + createDomainKey "$_domain" "$_keylength" + ;; + createCSR) + createCSR "$_domain" "$_altdomains" "$_ecc" + ;; + setnotify) + setnotify "$_notify_hook" "$_notify_level" "$_notify_mode" + ;; + setdefaultca) + setdefaultca + ;; + *) + if [ "$_CMD" ]; then + _err "Invalid command: $_CMD" + fi + showhelp + return 1 + ;; esac _ret="$?" if [ "$_ret" != "0" ]; then diff --git a/deploy/exim4.sh b/deploy/exim4.sh index 573f762b..260b8798 100644 --- a/deploy/exim4.sh +++ b/deploy/exim4.sh @@ -69,8 +69,8 @@ exim4_deploy() { cp "$_exim4_conf" "$_backup_conf" _info "Modify exim4 conf: $_exim4_conf" - if _setopt "$_exim4_conf" "tls_certificate" "=" "$_real_fullchain" \ - && _setopt "$_exim4_conf" "tls_privatekey" "=" "$_real_key"; then + if _setopt "$_exim4_conf" "tls_certificate" "=" "$_real_fullchain" && + _setopt "$_exim4_conf" "tls_privatekey" "=" "$_real_key"; then _info "Set config success!" else _err "Config exim4 server error, please report bug to us." diff --git a/deploy/openstack.sh b/deploy/openstack.sh new file mode 100644 index 00000000..f2058853 --- /dev/null +++ b/deploy/openstack.sh @@ -0,0 +1,262 @@ +#!/usr/bin/env sh + +# OpenStack Barbican deploy hook +# +# This requires you to have OpenStackClient and python-barbicanclient +# installed. +# +# You will require Keystone V3 credentials loaded into your environment, which +# could be either password or v3applicationcredential type. +# +# Author: Andy Botting + +openstack_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + if ! _exists openstack; then + _err "OpenStack client not found" + return 1 + fi + + _openstack_credentials || return $? + + _info "Generate import pkcs12" + _import_pkcs12="$(_mktemp)" + if ! _openstack_to_pkcs "$_import_pkcs12" "$_ckey" "$_ccert" "$_cca"; then + _err "Error creating pkcs12 certificate" + return 1 + fi + _debug _import_pkcs12 "$_import_pkcs12" + _base64_pkcs12=$(_base64 "multiline" <"$_import_pkcs12") + + secretHrefs=$(_openstack_get_secrets) + _debug secretHrefs "$secretHrefs" + _openstack_store_secret || return $? + + if [ -n "$secretHrefs" ]; then + _info "Cleaning up existing secret" + _openstack_delete_secrets || return $? + fi + + _info "Certificate successfully deployed" + return 0 +} + +_openstack_store_secret() { + if ! openstack secret store --name "$_cdomain." -t 'application/octet-stream' -e base64 --payload "$_base64_pkcs12"; then + _err "Failed to create OpenStack secret" + return 1 + fi + return +} + +_openstack_delete_secrets() { + echo "$secretHrefs" | while read -r secretHref; do + _info "Deleting old secret $secretHref" + if ! openstack secret delete "$secretHref"; then + _err "Failed to delete OpenStack secret" + return 1 + fi + done + return +} + +_openstack_get_secrets() { + if ! secretHrefs=$(openstack secret list -f value --name "$_cdomain." | cut -d' ' -f1); then + _err "Failed to list secrets" + return 1 + fi + echo "$secretHrefs" +} + +_openstack_to_pkcs() { + # The existing _toPkcs command can't allow an empty password, due to sh + # -z test, so copied here and forcing the empty password. + _cpfx="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + + ${ACME_OPENSSL_BIN:-openssl} pkcs12 -export -out "$_cpfx" -inkey "$_ckey" -in "$_ccert" -certfile "$_cca" -password "pass:" +} + +_openstack_credentials() { + _debug "Check OpenStack credentials" + + # If we have OS_AUTH_URL already set in the environment, then assume we want + # to use those, otherwise use stored credentials + if [ -n "$OS_AUTH_URL" ]; then + _debug "OS_AUTH_URL env var found, using environment" + else + _debug "OS_AUTH_URL not found, loading stored credentials" + OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}" + OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}" + OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}" + OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}" + OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}" + OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}" + OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}" + OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}" + OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}" + OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}" + OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}" + OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}" + OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}" + fi + + # Check each var and either save or clear it depending on whether its set. + # The helps us clear out old vars in the case where a user may want + # to switch between password and app creds + _debug "OS_AUTH_URL" "$OS_AUTH_URL" + if [ -n "$OS_AUTH_URL" ]; then + export OS_AUTH_URL + _saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL" + else + unset OS_AUTH_URL + _clearaccountconf SAVED_OS_AUTH_URL + fi + + _debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION" + if [ -n "$OS_IDENTITY_API_VERSION" ]; then + export OS_IDENTITY_API_VERSION + _saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION" + else + unset OS_IDENTITY_API_VERSION + _clearaccountconf SAVED_OS_IDENTITY_API_VERSION + fi + + _debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE" + if [ -n "$OS_AUTH_TYPE" ]; then + export OS_AUTH_TYPE + _saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE" + else + unset OS_AUTH_TYPE + _clearaccountconf SAVED_OS_AUTH_TYPE + fi + + _debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID" + if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then + export OS_APPLICATION_CREDENTIAL_ID + _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID" + else + unset OS_APPLICATION_CREDENTIAL_ID + _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID + fi + + _secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET" + if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then + export OS_APPLICATION_CREDENTIAL_SECRET + _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET" + else + unset OS_APPLICATION_CREDENTIAL_SECRET + _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET + fi + + _debug "OS_USERNAME" "$OS_USERNAME" + if [ -n "$OS_USERNAME" ]; then + export OS_USERNAME + _saveaccountconf_mutable OS_USERNAME "$OS_USERNAME" + else + unset OS_USERNAME + _clearaccountconf SAVED_OS_USERNAME + fi + + _secure_debug "OS_PASSWORD" "$OS_PASSWORD" + if [ -n "$OS_PASSWORD" ]; then + export OS_PASSWORD + _saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD" + else + unset OS_PASSWORD + _clearaccountconf SAVED_OS_PASSWORD + fi + + _debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME" + if [ -n "$OS_PROJECT_NAME" ]; then + export OS_PROJECT_NAME + _saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME" + else + unset OS_PROJECT_NAME + _clearaccountconf SAVED_OS_PROJECT_NAME + fi + + _debug "OS_PROJECT_ID" "$OS_PROJECT_ID" + if [ -n "$OS_PROJECT_ID" ]; then + export OS_PROJECT_ID + _saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID" + else + unset OS_PROJECT_ID + _clearaccountconf SAVED_OS_PROJECT_ID + fi + + _debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME" + if [ -n "$OS_USER_DOMAIN_NAME" ]; then + export OS_USER_DOMAIN_NAME + _saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME" + else + unset OS_USER_DOMAIN_NAME + _clearaccountconf SAVED_OS_USER_DOMAIN_NAME + fi + + _debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID" + if [ -n "$OS_USER_DOMAIN_ID" ]; then + export OS_USER_DOMAIN_ID + _saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID" + else + unset OS_USER_DOMAIN_ID + _clearaccountconf SAVED_OS_USER_DOMAIN_ID + fi + + _debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME" + if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then + export OS_PROJECT_DOMAIN_NAME + _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME" + else + unset OS_PROJECT_DOMAIN_NAME + _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME + fi + + _debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID" + if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then + export OS_PROJECT_DOMAIN_ID + _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID" + else + unset OS_PROJECT_DOMAIN_ID + _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID + fi + + if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then + # Application Credential auth + if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then + _err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID" + _err "and OS_APPLICATION_CREDENTIAL_SECRET must be set." + _err "Please check your credentials and try again." + return 1 + fi + else + # Password auth + if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then + _err "OpenStack username or password not found." + _err "Please check your credentials and try again." + return 1 + fi + + if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then + _err "When using password authentication, OS_PROJECT_NAME or" + _err "OS_PROJECT_ID must be set." + _err "Please check your credentials and try again." + return 1 + fi + fi + + return 0 +} diff --git a/deploy/ssh.sh b/deploy/ssh.sh index 06d4b2b4..18de4aa6 100644 --- a/deploy/ssh.sh +++ b/deploy/ssh.sh @@ -195,8 +195,8 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d fi if [ -n "$Le_Deploy_ssh_cafile" ]; then _pipe=">" - if [ "$Le_Deploy_ssh_cafile" = "$Le_Deploy_ssh_keyfile" ] \ - || [ "$Le_Deploy_ssh_cafile" = "$Le_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 [ "$Le_Deploy_ssh_backup" = "yes" ]; then @@ -222,9 +222,9 @@ then rm -rf \"\$fn\"; echo \"Backup \$fn deleted as older than 180 days\"; fi; d fi if [ -n "$Le_Deploy_ssh_fullchain" ]; then _pipe=">" - 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 [ "$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 [ "$Le_Deploy_ssh_backup" = "yes" ]; then diff --git a/deploy/synology_dsm.sh b/deploy/synology_dsm.sh index c57d50bc..2ec0ceb3 100644 --- a/deploy/synology_dsm.sh +++ b/deploy/synology_dsm.sh @@ -22,7 +22,7 @@ ######## Public functions ##################### _syno_get_cookie_data() { - grep "\W$1=" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';' + grep -i "\W$1=" | grep -i "^Set-Cookie:" | _tail_n 1 | _egrep_o "$1=[^;]*;" | tr -d ';' } #domain keyfile certfile cafile fullchain @@ -79,7 +79,7 @@ synology_dsm_deploy() { encoded_password="$(printf "%s" "$SYNO_Password" | _url_encode)" encoded_did="$(printf "%s" "$SYNO_DID" | _url_encode)" response=$(_get "$_base_url/webman/login.cgi?username=$encoded_username&passwd=$encoded_password&enable_syno_token=yes&device_id=$encoded_did" 1) - token=$(echo "$response" | grep "X-SYNO-TOKEN:" | sed -n 's/^X-SYNO-TOKEN: \(.*\)$/\1/p' | tr -d "\r\n") + token=$(echo "$response" | grep -i "X-SYNO-TOKEN:" | sed -n 's/^X-SYNO-TOKEN: \(.*\)$/\1/pI' | tr -d "\r\n") _debug3 response "$response" _debug token "$token" diff --git a/deploy/vault.sh b/deploy/vault.sh new file mode 100644 index 00000000..70c80444 --- /dev/null +++ b/deploy/vault.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env sh + +# Here is a script to deploy cert to hashicorp vault using curl +# (https://www.vaultproject.io/) +# +# it requires following environment variables: +# +# VAULT_PREFIX - this contains the prefix path in vault +# VAULT_ADDR - vault requires this to find your vault server +# +# additionally, you need to ensure that VAULT_TOKEN is avialable +# to access the vault server + +#returns 0 means success, otherwise error. + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +vault_deploy() { + + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + # validate required env vars + _getdeployconf VAULT_PREFIX + if [ -z "$VAULT_PREFIX" ]; then + _err "VAULT_PREFIX needs to be defined (contains prefix path in vault)" + return 1 + fi + _savedeployconf VAULT_PREFIX "$VAULT_PREFIX" + + _getdeployconf VAULT_ADDR + if [ -z "$VAULT_ADDR" ]; then + _err "VAULT_ADDR needs to be defined (contains vault connection address)" + return 1 + fi + _savedeployconf VAULT_ADDR "$VAULT_ADDR" + + # JSON does not allow multiline strings. + # So replacing new-lines with "\n" here + _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 "$FABIO" ]; then + _post "{\"cert\": \"$_cfullchain\", \"key\": \"$_ckey\"}" "$URL" + else + _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 + +} diff --git a/deploy/vault_cli.sh b/deploy/vault_cli.sh index 5395d87e..8b854137 100644 --- a/deploy/vault_cli.sh +++ b/deploy/vault_cli.sh @@ -43,7 +43,7 @@ vault_cli_deploy() { return 1 fi - VAULT_CMD=$(which vault) + VAULT_CMD=$(command -v vault) if [ ! $? ]; then _err "cannot find vault binary!" return 1 diff --git a/deploy/vsftpd.sh b/deploy/vsftpd.sh index ed44e709..8cf24e4f 100644 --- a/deploy/vsftpd.sh +++ b/deploy/vsftpd.sh @@ -65,9 +65,9 @@ vsftpd_deploy() { cp "$_vsftpd_conf" "$_backup_conf" _info "Modify vsftpd conf: $_vsftpd_conf" - if _setopt "$_vsftpd_conf" "rsa_cert_file" "=" "$_real_fullchain" \ - && _setopt "$_vsftpd_conf" "rsa_private_key_file" "=" "$_real_key" \ - && _setopt "$_vsftpd_conf" "ssl_enable" "=" "YES"; then + if _setopt "$_vsftpd_conf" "rsa_cert_file" "=" "$_real_fullchain" && + _setopt "$_vsftpd_conf" "rsa_private_key_file" "=" "$_real_key" && + _setopt "$_vsftpd_conf" "ssl_enable" "=" "YES"; then _info "Set config success!" else _err "Config vsftpd server error, please report bug to us." diff --git a/dnsapi/dns_1984hosting.sh b/dnsapi/dns_1984hosting.sh index 09f02796..d720c1c5 100755 --- a/dnsapi/dns_1984hosting.sh +++ b/dnsapi/dns_1984hosting.sh @@ -3,7 +3,7 @@ #So, here must be a method dns_1984hosting_add() #Which will be called by acme.sh to add the txt record to your api system. #returns 0 means success, otherwise error. -# + #Author: Adrian Fedoreanu #Report Bugs here: https://github.com/acmesh-official/acme.sh # or here... https://github.com/acmesh-official/acme.sh/issues/2851 @@ -40,8 +40,35 @@ dns_1984hosting_add() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _1984hosting_add_txt_record "$_domain" "$_sub_domain" "$txtvalue" - return $? + _debug "Add TXT record $fulldomain with value '$txtvalue'" + value="$(printf '%s' "$txtvalue" | _url_encode)" + url="https://management.1984hosting.com/domains/entry/" + + postdata="entry=new" + postdata="$postdata&type=TXT" + postdata="$postdata&ttl=3600" + postdata="$postdata&zone=$_domain" + postdata="$postdata&host=$_sub_domain" + postdata="$postdata&rdata=%22$value%22" + _debug2 postdata "$postdata" + + _authpost "$postdata" "$url" + response="$(echo "$_response" | _normalizeJson)" + _debug2 response "$response" + + if _contains "$response" '"haserrors": true'; then + _err "1984Hosting failed to add TXT record for $_sub_domain bad RC from _post" + return 1 + elif _contains "$response" ""; then + _err "1984Hosting failed to add TXT record for $_sub_domain. Check $HTTP_HEADER file" + return 1 + elif _contains "$response" '"auth": false'; then + _err "1984Hosting failed to add TXT record for $_sub_domain. Invalid or expired cookie" + return 1 + fi + + _info "Added acme challenge TXT record for $fulldomain at 1984Hosting" + return 0 } #Usage: fulldomain txtvalue @@ -67,57 +94,10 @@ dns_1984hosting_rm() { _debug _sub_domain "$_sub_domain" _debug _domain "$_domain" - _1984hosting_delete_txt_record "$_domain" "$_sub_domain" - return $? -} - -#################### Private functions below ################################## - -# usage _1984hosting_add_txt_record domain subdomain value -# returns 0 success -_1984hosting_add_txt_record() { - _debug "Add TXT record $1 with value '$3'" - domain="$1" - subdomain="$2" - value="$(printf '%s' "$3" | _url_encode)" - url="https://management.1984hosting.com/domains/entry/" - - postdata="entry=new" - postdata="$postdata&type=TXT" - postdata="$postdata&ttl=3600" - postdata="$postdata&zone=$domain" - postdata="$postdata&host=$subdomain" - postdata="$postdata&rdata=%22$value%22" - _debug2 postdata "$postdata" - - _authpost "$postdata" "$url" - response="$(echo "$_response" | _normalizeJson)" - _debug2 response "$response" - - if _contains "$response" '"haserrors": true'; then - _err "1984Hosting failed to add TXT record for $subdomain bad RC from _post" - return 1 - elif _contains "$response" ""; then - _err "1984Hosting failed to add TXT record for $subdomain. Check $HTTP_HEADER file" - return 1 - elif [ "$response" = '{"auth": false, "ok": false}' ]; then - _err "1984Hosting failed to add TXT record for $subdomain. Invalid or expired cookie" - return 1 - fi - - _info "Added acme challenge TXT record for $fulldomain at 1984Hosting" - return 0 -} - -# usage _1984hosting_delete_txt_record entry_id -# returns 0 success -_1984hosting_delete_txt_record() { _debug "Delete $fulldomain TXT record" - domain="$1" - subdomain="$2" url="https://management.1984hosting.com/domains" - _htmlget "$url" "$domain" + _htmlget "$url" "$_domain" _debug2 _response "$_response" zone_id="$(echo "$_response" | _egrep_o 'zone\/[0-9]+')" _debug2 zone_id "$zone_id" @@ -126,7 +106,7 @@ _1984hosting_delete_txt_record() { return 1 fi - _htmlget "$url/$zone_id" "$subdomain" + _htmlget "$url/$zone_id" "$_sub_domain" _debug2 _response "$_response" entry_id="$(echo "$_response" | _egrep_o 'entry_[0-9]+' | sed 's/entry_//')" _debug2 entry_id "$entry_id" @@ -148,6 +128,8 @@ _1984hosting_delete_txt_record() { return 0 } +#################### Private functions below ################################## + # usage: _1984hosting_login username password # returns 0 success _1984hosting_login() { @@ -167,7 +149,7 @@ _1984hosting_login() { response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" - if [ "$response" = '{"loggedin": true, "ok": true}' ]; then + if _contains "$response" '"loggedin": true'; then One984HOSTING_COOKIE="$(grep -i '^set-cookie:' "$HTTP_HEADER" | _tail_n 1 | _egrep_o 'sessionid=[^;]*;' | tr -d ';')" export One984HOSTING_COOKIE _saveaccountconf_mutable One984HOSTING_COOKIE "$One984HOSTING_COOKIE" @@ -196,7 +178,7 @@ _check_cookie() { _authget "https://management.1984hosting.com/accounts/loginstatus/" response="$(echo "$_response" | _normalizeJson)" - if [ "$_response" = '{"ok": true}' ]; then + if _contains "$response" '"ok": true'; then _debug "Cached cookie still valid" return 0 fi diff --git a/dnsapi/dns_anx.sh b/dnsapi/dns_anx.sh new file mode 100644 index 00000000..c1a1130a --- /dev/null +++ b/dnsapi/dns_anx.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env sh + +# Anexia CloudDNS acme.sh hook +# Author: MA + +#ANX_Token="xxxx" + +ANX_API='https://engine.anexia-it.com/api/clouddns/v1' + +######## Public functions ##################### + +dns_anx_add() { + fulldomain=$1 + txtvalue=$2 + + _info "Using ANX CDNS API" + + ANX_Token="${ANX_Token:-$(_readaccountconf_mutable ANX_Token)}" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + if [ "$ANX_Token" ]; then + _saveaccountconf_mutable ANX_Token "$ANX_Token" + else + _err "You didn't specify a ANEXIA Engine API token." + return 1 + fi + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + # Always add records, wildcard need two records with the same name + _anx_rest POST "zone.json/${_domain}/records" "{\"name\":\"$_sub_domain\",\"type\":\"TXT\",\"rdata\":\"$txtvalue\"}" + if _contains "$response" "$txtvalue"; then + return 0 + else + return 1 + fi +} + +dns_anx_rm() { + fulldomain=$1 + txtvalue=$2 + + _info "Using ANX CDNS API" + + ANX_Token="${ANX_Token:-$(_readaccountconf_mutable ANX_Token)}" + + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _get_record_id + + if _is_uuid "$_record_id"; then + if ! _anx_rest DELETE "zone.json/${_domain}/records/$_record_id"; then + _err "Delete record" + return 1 + fi + else + _info "No record found." + fi + echo "$response" | tr -d " " | grep \"status\":\"OK\" >/dev/null +} + +#################### Private functions below ################################## + +_is_uuid() { + pattern='^\{?[A-Z0-9a-z]{8}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{4}-[A-Z0-9a-z]{12}\}?$' + if echo "$1" | _egrep_o "$pattern" >/dev/null; then + return 0 + fi + return 1 +} + +_get_record_id() { + _debug subdomain "$_sub_domain" + _debug domain "$_domain" + + if _anx_rest GET "zone.json/${_domain}/records?name=$_sub_domain&type=TXT"; then + _debug response "$response" + if _contains "$response" "\"name\":\"$_sub_domain\"" >/dev/null; then + _record_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"identifier\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") + else + _record_id='' + fi + else + _err "Search existing record" + fi +} + +_anx_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + export _H1="Content-Type: application/json" + export _H2="Authorization: Token $ANX_Token" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "${ANX_API}/$ep" "" "$m")" + else + response="$(_get "${ANX_API}/$ep")" + fi + + # shellcheck disable=SC2181 + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug response "$response" + return 0 +} + +_get_root() { + domain=$1 + i=1 + p=1 + + _anx_rest GET "zone.json" + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if _contains "$response" "\"name\":\"$h\""; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + + p=$i + i=$(_math "$i" + 1) + done + return 1 +} diff --git a/dnsapi/dns_arvan.sh b/dnsapi/dns_arvan.sh index edeb56ca..ca1f56c7 100644 --- a/dnsapi/dns_arvan.sh +++ b/dnsapi/dns_arvan.sh @@ -5,7 +5,7 @@ ARVAN_API_URL="https://napi.arvancloud.com/cdn/4.0/domains" #Author: Ehsan Aliakbar -#Report Bugs here: https://github.com/Neilpang/acme.sh +#Report Bugs here: https://github.com/Neilpang/acme.sh # ######## Public functions ##################### diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index ea4736c4..068c337c 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -222,21 +222,21 @@ _use_instance_role() { _use_metadata() { _aws_creds="$( - _get "$1" "" 1 \ - | _normalizeJson \ - | tr '{,}' '\n' \ - | while read -r _line; do + _get "$1" "" 1 | + _normalizeJson | + tr '{,}' '\n' | + while read -r _line; do _key="$(echo "${_line%%:*}" | tr -d '"')" _value="${_line#*:}" _debug3 "_key" "$_key" _secure_debug3 "_value" "$_value" case "$_key" in - AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;; - SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;; - Token) echo "AWS_SESSION_TOKEN=$_value" ;; + AccessKeyId) echo "AWS_ACCESS_KEY_ID=$_value" ;; + SecretAccessKey) echo "AWS_SECRET_ACCESS_KEY=$_value" ;; + Token) echo "AWS_SESSION_TOKEN=$_value" ;; esac - done \ - | paste -sd' ' - + done | + paste -sd' ' - )" _secure_debug "_aws_creds" "$_aws_creds" diff --git a/dnsapi/dns_azure.sh b/dnsapi/dns_azure.sh index bf7cf2bf..ce8a3fa7 100644 --- a/dnsapi/dns_azure.sh +++ b/dnsapi/dns_azure.sh @@ -172,7 +172,7 @@ dns_azure_rm() { _azure_rest GET "$acmeRecordURI" "" "$accesstoken" timestamp="$(_time)" if [ "$_code" = "200" ]; then - vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v "$txtvalue")" + vlist="$(echo "$response" | _egrep_o "\"value\"\\s*:\\s*\\[\\s*\"[^\"]*\"\\s*]" | cut -d : -f 2 | tr -d "[]\"" | grep -v -- "$txtvalue")" values="" comma="" for v in $vlist; do @@ -220,7 +220,7 @@ _azure_rest() { export _H2="accept: application/json" export _H3="Content-Type: application/json" # clear headers from previous request to avoid getting wrong http code on timeouts - :>"$HTTP_HEADER" + : >"$HTTP_HEADER" _debug "$ep" if [ "$m" != "GET" ]; then _secure_debug2 "data $data" diff --git a/dnsapi/dns_cloudns.sh b/dnsapi/dns_cloudns.sh index df824e86..381d17ec 100755 --- a/dnsapi/dns_cloudns.sh +++ b/dnsapi/dns_cloudns.sh @@ -69,7 +69,7 @@ dns_cloudns_rm() { 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 [ ! -z "$record_id" ]; then + if [ -n "$record_id" ]; then _debug zone "$zone" _debug host "$host" _debug record "$record" @@ -91,7 +91,7 @@ dns_cloudns_rm() { #################### Private functions below ################################## _dns_cloudns_init_check() { - if [ ! -z "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then + if [ -n "$CLOUDNS_INIT_CHECK_COMPLETED" ]; then return 0 fi @@ -164,7 +164,7 @@ _dns_cloudns_http_api_call() { _debug CLOUDNS_SUB_AUTH_ID "$CLOUDNS_SUB_AUTH_ID" _debug CLOUDNS_AUTH_PASSWORD "$CLOUDNS_AUTH_PASSWORD" - if [ ! -z "$CLOUDNS_SUB_AUTH_ID" ]; then + if [ -n "$CLOUDNS_SUB_AUTH_ID" ]; then auth_user="sub-auth-id=$CLOUDNS_SUB_AUTH_ID" else auth_user="auth-id=$CLOUDNS_AUTH_ID" diff --git a/dnsapi/dns_conoha.sh b/dnsapi/dns_conoha.sh index d3bee130..ddc32074 100755 --- a/dnsapi/dns_conoha.sh +++ b/dnsapi/dns_conoha.sh @@ -115,9 +115,9 @@ dns_conoha_rm() { return 1 fi - record_id=$(printf "%s" "$response" | _egrep_o '{[^}]*}' \ - | grep '"type":"TXT"' | grep "\"data\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" \ - | _head_n 1 | cut -d : -f 2 | tr -d \") + record_id=$(printf "%s" "$response" | _egrep_o '{[^}]*}' | + grep '"type":"TXT"' | grep "\"data\":\"$txtvalue\"" | _egrep_o "\"id\":\"[^\"]*\"" | + _head_n 1 | cut -d : -f 2 | tr -d \") if [ -z "$record_id" ]; then _err "Can not get record id to remove." return 1 diff --git a/dnsapi/dns_cyon.sh b/dnsapi/dns_cyon.sh index 8db3011d..2c08812b 100644 --- a/dnsapi/dns_cyon.sh +++ b/dnsapi/dns_cyon.sh @@ -18,23 +18,23 @@ ######## dns_cyon_add() { - _cyon_load_credentials \ - && _cyon_load_parameters "$@" \ - && _cyon_print_header "add" \ - && _cyon_login \ - && _cyon_change_domain_env \ - && _cyon_add_txt \ - && _cyon_logout + _cyon_load_credentials && + _cyon_load_parameters "$@" && + _cyon_print_header "add" && + _cyon_login && + _cyon_change_domain_env && + _cyon_add_txt && + _cyon_logout } dns_cyon_rm() { - _cyon_load_credentials \ - && _cyon_load_parameters "$@" \ - && _cyon_print_header "delete" \ - && _cyon_login \ - && _cyon_change_domain_env \ - && _cyon_delete_txt \ - && _cyon_logout + _cyon_load_credentials && + _cyon_load_parameters "$@" && + _cyon_print_header "delete" && + _cyon_login && + _cyon_change_domain_env && + _cyon_delete_txt && + _cyon_logout } ######################### @@ -66,7 +66,7 @@ _cyon_load_credentials() { _debug "Save credentials to account.conf" _saveaccountconf CY_Username "${CY_Username}" _saveaccountconf CY_Password_B64 "$CY_Password_B64" - if [ ! -z "${CY_OTP_Secret}" ]; then + if [ -n "${CY_OTP_Secret}" ]; then _saveaccountconf CY_OTP_Secret "$CY_OTP_Secret" else _clearaccountconf CY_OTP_Secret @@ -164,7 +164,7 @@ _cyon_login() { # 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 [ ! -z "${CY_OTP_Secret}" ]; then + if [ -n "${CY_OTP_Secret}" ]; then _info " - Authorising with OTP code..." if ! _exists oathtool; then diff --git a/dnsapi/dns_da.sh b/dnsapi/dns_da.sh index 4e9c4ef0..4d3e09b1 100755 --- a/dnsapi/dns_da.sh +++ b/dnsapi/dns_da.sh @@ -115,23 +115,23 @@ _da_api() { _debug response "$response" case "${cmd}" in - CMD_API_DNS_CONTROL) - # Parse the result in general - # error=0&text=Records Deleted&details= - # error=1&text=Cannot View Dns Record&details=No domain provided - err_field="$(_getfield "$response" 1 '&')" - txt_field="$(_getfield "$response" 2 '&')" - details_field="$(_getfield "$response" 3 '&')" - error="$(_getfield "$err_field" 2 '=')" - text="$(_getfield "$txt_field" 2 '=')" - details="$(_getfield "$details_field" 2 '=')" - _debug "error: ${error}, text: ${text}, details: ${details}" - if [ "$error" != "0" ]; then - _err "error $response" - return 1 - fi - ;; - CMD_API_SHOW_DOMAINS) ;; + CMD_API_DNS_CONTROL) + # Parse the result in general + # error=0&text=Records Deleted&details= + # error=1&text=Cannot View Dns Record&details=No domain provided + err_field="$(_getfield "$response" 1 '&')" + txt_field="$(_getfield "$response" 2 '&')" + details_field="$(_getfield "$response" 3 '&')" + error="$(_getfield "$err_field" 2 '=')" + text="$(_getfield "$txt_field" 2 '=')" + details="$(_getfield "$details_field" 2 '=')" + _debug "error: ${error}, text: ${text}, details: ${details}" + if [ "$error" != "0" ]; then + _err "error $response" + return 1 + fi + ;; + CMD_API_SHOW_DOMAINS) ;; esac return 0 } diff --git a/dnsapi/dns_dgon.sh b/dnsapi/dns_dgon.sh index 515e87d5..ac14da48 100755 --- a/dnsapi/dns_dgon.sh +++ b/dnsapi/dns_dgon.sh @@ -122,12 +122,12 @@ dns_dgon_rm() { ## check for what we are looking for: "type":"A","name":"$_sub_domain" record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*[0-9]+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" - if [ ! -z "$record" ]; then + if [ -n "$record" ]; then ## we found records rec_ids="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*[0-9]+" | _egrep_o "[0-9]+")" _debug rec_ids "$rec_ids" - if [ ! -z "$rec_ids" ]; then + if [ -n "$rec_ids" ]; then echo "$rec_ids" | while IFS= read -r rec_id; do ## delete the record ## delete URL for removing the one we dont want @@ -218,7 +218,7 @@ _get_base_domain() { ## we got part of a domain back - grep it out found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" ## check if it exists - if [ ! -z "$found" ]; then + if [ -n "$found" ]; then ## exists - exit loop returning the parts sub_point=$(_math $i - 1) _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") diff --git a/dnsapi/dns_do.sh b/dnsapi/dns_do.sh index 3a2f8f49..3850890c 100755 --- a/dnsapi/dns_do.sh +++ b/dnsapi/dns_do.sh @@ -67,14 +67,14 @@ _dns_do_list_rrs() { _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="$(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}" ] } @@ -120,10 +120,10 @@ _get_root() { i=1 _dns_do_soap getDomainList - _all_domains="$(echo "${response}" \ - | tr -d "\n\r\t " \ - | _egrep_o 'domain]+>[^<]+' \ - | sed -e 's/^domain<\/key>]*>//g')" + _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) diff --git a/dnsapi/dns_duckdns.sh b/dnsapi/dns_duckdns.sh index 11b685c0..f0af2741 100755 --- a/dnsapi/dns_duckdns.sh +++ b/dnsapi/dns_duckdns.sh @@ -112,16 +112,21 @@ _duckdns_rest() { param="$2" _debug param "$param" url="$DuckDNS_API?$param" + if [ "$DEBUG" -gt 0 ]; then + url="$url&verbose=true" + fi _debug url "$url" # DuckDNS uses GET to update domain info if [ "$method" = "GET" ]; then response="$(_get "$url")" + _debug2 response "$response" + if [ "$DEBUG" -gt 0 ] && _contains "$response" "UPDATED" && _contains "$response" "OK"; then + response="OK" + fi else _err "Unsupported method" return 1 fi - - _debug2 response "$response" return 0 } diff --git a/dnsapi/dns_dynv6.sh b/dnsapi/dns_dynv6.sh index 819372c2..9efc9aeb 100644 --- a/dnsapi/dns_dynv6.sh +++ b/dnsapi/dns_dynv6.sh @@ -1,35 +1,41 @@ #!/usr/bin/env sh #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_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +#Usage: dns_dynv6_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_dynv6_add() { fulldomain=$1 txtvalue=$2 _info "Using dynv6 api" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" - _get_keyfile - _info "using keyfile $dynv6_keyfile" - _get_domain "$fulldomain" - _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)" - if ! _contains "$_your_hosts" "$_host"; then - _debug "The host is $_host and the record $_record" - _debug "Dynv6 returned $_your_hosts" - _err "The host $_host does not exist on your dynv6 account" - return 1 - fi - _debug "found host on your account" - returnval="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts \""$_host"\" records set \""$_record"\" txt data \""$txtvalue"\")" - _debug "Dynv6 returend this after record was added: $returnval" - if _contains "$returnval" "created"; then - return 0 - elif _contains "$returnval" "updated"; then - return 0 + _get_authentication + if [ "$dynv6_token" ]; then + _dns_dynv6_add_http + return $? else - _err "Something went wrong! it does not seem like the record was added succesfully" + _info "using key file $dynv6_keyfile" + _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)" + if ! _get_domain "$fulldomain" "$_your_hosts"; then + _err "Host not found on your account" + return 1 + fi + _debug "found host on your account" + returnval="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts \""$_host"\" records set \""$_record"\" txt data \""$txtvalue"\")" + _debug "Dynv6 returned this after record was added: $returnval" + if _contains "$returnval" "created"; then + return 0 + elif _contains "$returnval" "updated"; then + return 0 + else + _err "Something went wrong! it does not seem like the record was added successfully" + return 1 + fi return 1 fi return 1 @@ -39,28 +45,29 @@ dns_dynv6_add() { dns_dynv6_rm() { fulldomain=$1 txtvalue=$2 - _info "Using dynv6 api" + _info "Using dynv6 API" _debug fulldomain "$fulldomain" _debug txtvalue "$txtvalue" - _get_keyfile - _info "using keyfile $dynv6_keyfile" - _get_domain "$fulldomain" - _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)" - if ! _contains "$_your_hosts" "$_host"; then - _debug "The host is $_host and the record $_record" - _debug "Dynv6 returned $_your_hosts" - _err "The host $_host does not exist on your dynv6 account" - return 1 + _get_authentication + if [ "$dynv6_token" ]; then + _dns_dynv6_rm_http + return $? + else + _info "using key file $dynv6_keyfile" + _your_hosts="$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts)" + if ! _get_domain "$fulldomain" "$_your_hosts"; then + _err "Host not found on your account" + return 1 + fi + _debug "found host on your account" + _info "$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts "\"$_host\"" records del "\"$_record\"" txt)" + return 0 fi - _debug "found host on your account" - _info "$(ssh -i "$dynv6_keyfile" api@dynv6.com hosts "\"$_host\"" records del "\"$_record\"" txt)" - return 0 - } #################### Private functions below ################################## #Usage: No Input required #returns -#dynv6_keyfile the path to the new keyfile that has been generated +#dynv6_keyfile the path to the new key file that has been generated _generate_new_key() { dynv6_keyfile="$(eval echo ~"$USER")/.ssh/dynv6" _info "Path to key file used: $dynv6_keyfile" @@ -72,50 +79,207 @@ _generate_new_key() { return 1 fi } -#Usage: _acme-challenge.www.example.dynv6.net + +#Usage: _acme-challenge.www.example.dynv6.net "$_your_hosts" +#where _your_hosts is the output of ssh -i ~/.ssh/dynv6.pub api@dynv6.com hosts #returns #_host= example.dynv6.net #_record=_acme-challenge.www #aborts if not a valid domain _get_domain() { + #_your_hosts="$(ssh -i ~/.ssh/dynv6.pub api@dynv6.com hosts)" _full_domain="$1" - _debug "getting domain for $_full_domain" - if ! _contains "$_full_domain" 'dynv6.net' && ! _contains "$_full_domain" 'dns.army' && ! _contains "$_full_domain" 'dns.navy'; then - _err "The hosts does not seem to be a dynv6 host" - return 1 - fi - _record="${_full_domain%.*}" - _record="${_record%.*}" - _record="${_record%.*}" - _debug "The record we are ging to use is $_record" - _host="$_full_domain" - while [ "$(echo "$_host" | grep -o '\.' | wc -l)" != "2" ]; do - _host="${_host#*.}" - done - _debug "And the host is $_host" - return 0 + _your_hosts="$2" + _your_hosts="$(echo "$_your_hosts" | awk '/\./ {print $1}')" + for l in $_your_hosts; do + #echo "host: $l" + if test "${_full_domain#*$l}" != "$_full_domain"; then + _record="${_full_domain%.$l}" + _host=$l + _debug "The host is $_host and the record $_record" + return 0 + fi + done + _err "Either their is no such host on your dnyv6 account or it cannot be accessed with this key" + return 1 } # Usage: No input required #returns #dynv6_keyfile path to the key that will be used -_get_keyfile() { - _debug "get keyfile method called" - dynv6_keyfile="${dynv6_keyfile:-$(_readaccountconf_mutable dynv6_keyfile)}" - _debug Your key is "$dynv6_keyfile" - if [ -z "$dynv6_keyfile" ]; then - if [ -z "$KEY" ]; then - _err "You did not specify a key to use with dynv6" - _info "Creating new dynv6 api key to add to dynv6.com" - _generate_new_key - _info "Please add this key to dynv6.com $(cat "$dynv6_keyfile.pub")" - _info "Hit Enter to contiue" - read -r _ - #save the credentials to the account conf file. - else - dynv6_keyfile="$KEY" +_get_authentication() { + dynv6_token="${DYNV6_TOKEN:-$(_readaccountconf_mutable dynv6_token)}" + if [ "$dynv6_token" ]; then + _debug "Found HTTP Token. Going to use the HTTP API and not the SSH API" + if [ "$DYNV6_TOKEN" ]; then + _saveaccountconf_mutable dynv6_token "$dynv6_token" + fi + else + _debug "no HTTP token found. Looking for an SSH key" + dynv6_keyfile="${dynv6_keyfile:-$(_readaccountconf_mutable dynv6_keyfile)}" + _debug "Your key is $dynv6_keyfile" + if [ -z "$dynv6_keyfile" ]; then + if [ -z "$KEY" ]; then + _err "You did not specify a key to use with dynv6" + _info "Creating new dynv6 API key to add to dynv6.com" + _generate_new_key + _info "Please add this key to dynv6.com $(cat "$dynv6_keyfile.pub")" + _info "Hit Enter to continue" + read -r _ + #save the credentials to the account conf file. + else + dynv6_keyfile="$KEY" + fi + _saveaccountconf_mutable dynv6_keyfile "$dynv6_keyfile" + fi + fi +} + +_dns_dynv6_add_http() { + _debug "Got HTTP token form _get_authentication method. Going to use the HTTP API" + if ! _get_zone_id "$fulldomain"; then + _err "Could not find a matching zone for $fulldomain. Maybe your HTTP Token is not authorized to access the zone" + return 1 + fi + _get_zone_name "$_zone_id" + record="${fulldomain%%.$_zone_name}" + _set_record TXT "$record" "$txtvalue" + if _contains "$response" "$txtvalue"; then + _info "Successfully added record" + return 0 + else + _err "Something went wrong while adding the record" + return 1 + fi +} + +_dns_dynv6_rm_http() { + _debug "Got HTTP token form _get_authentication method. Going to use the HTTP API" + if ! _get_zone_id "$fulldomain"; then + _err "Could not find a matching zone for $fulldomain. Maybe your HTTP Token is not authorized to access the zone" + return 1 + fi + _get_zone_name "$_zone_id" + record="${fulldomain%%.$_zone_name}" + _get_record_id "$_zone_id" "$record" "$txtvalue" + _del_record "$_zone_id" "$_record_id" + if [ -z "$response" ]; then + _info "Successfully deleted record" + return 0 + else + _err "Something went wrong while deleting the record" + return 1 + fi +} + +#get the zoneid for a specifc record or zone +#usage: _get_zone_id §record +#where $record is the record to get the id for +#returns _zone_id the id of the zone +_get_zone_id() { + record="$1" + _debug "getting zone id for $record" + _dynv6_rest GET zones + + zones="$(echo "$response" | tr '}' '\n' | tr ',' '\n' | grep name | sed 's/\[//g' | tr -d '{' | tr -d '"')" + #echo $zones + + selected="" + for z in $zones; do + z="${z#name:}" + _debug zone: "$z" + if _contains "$record" "$z"; then + _debug "$z found in $record" + selected="$z" fi - _saveaccountconf_mutable dynv6_keyfile "$dynv6_keyfile" + done + if [ -z "$selected" ]; then + _err "no zone found" + return 1 + fi + + zone_id="$(echo "$response" | tr '}' '\n' | grep "$selected" | tr ',' '\n' | grep id | tr -d '"')" + _zone_id="${zone_id#id:}" + _debug "zone id: $_zone_id" +} + +_get_zone_name() { + _zone_id="$1" + _dynv6_rest GET zones/"$_zone_id" + _zone_name="$(echo "$response" | tr ',' '\n' | tr -d '{' | grep name | tr -d '"')" + _zone_name="${_zone_name#name:}" +} + +#usaage _get_record_id $zone_id $record +# where zone_id is thevalue returned by _get_zone_id +# and record ist in the form _acme.www for an fqdn of _acme.www.example.com +# returns _record_id +_get_record_id() { + _zone_id="$1" + record="$2" + value="$3" + _dynv6_rest GET "zones/$_zone_id/records" + if ! _get_record_id_from_response "$response"; then + _err "no such record $record found in zone $_zone_id" + return 1 fi } + +_get_record_id_from_response() { + response="$1" + _record_id="$(echo "$response" | tr '}' '\n' | grep "\"name\":\"$record\"" | grep "\"data\":\"$value\"" | tr ',' '\n' | grep id | tr -d '"' | tr -d 'id:')" + #_record_id="${_record_id#id:}" + if [ -z "$_record_id" ]; then + _err "no such record: $record found in zone $_zone_id" + return 1 + fi + _debug "record id: $_record_id" + return 0 +} +#usage: _set_record TXT _acme_challenge.www longvalue 12345678 +#zone id is optional can also be set as vairable bevor calling this method +_set_record() { + type="$1" + record="$2" + value="$3" + if [ "$4" ]; then + _zone_id="$4" + fi + data="{\"name\": \"$record\", \"data\": \"$value\", \"type\": \"$type\"}" + #data='{ "name": "acme.test.thorn.dynv6.net", "type": "A", "data": "192.168.0.1"}' + echo "$data" + #"{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}" + _dynv6_rest POST "zones/$_zone_id/records" "$data" +} +_del_record() { + _zone_id=$1 + _record_id=$2 + _dynv6_rest DELETE zones/"$_zone_id"/records/"$_record_id" +} + +_dynv6_rest() { + m=$1 #method GET,POST,DELETE or PUT + ep="$2" #the endpoint + data="$3" + _debug "$ep" + + token_trimmed=$(echo "$dynv6_token" | tr -d '"') + + export _H1="Authorization: Bearer $token_trimmed" + export _H2="Content-Type: application/json" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$dynv6_api/$ep" "" "$m")" + else + response="$(_get "$dynv6_api/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_easydns.sh b/dnsapi/dns_easydns.sh index f466f1e2..ab47a0bc 100644 --- a/dnsapi/dns_easydns.sh +++ b/dnsapi/dns_easydns.sh @@ -3,7 +3,7 @@ ####################################################### # # 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] diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh index 4a58931f..29cee430 100755 --- a/dnsapi/dns_freedns.sh +++ b/dnsapi/dns_freedns.sh @@ -303,10 +303,10 @@ _freedns_domain_id() { return 1 fi - domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' \ - | grep "$search_domain\|$search_domain(.*)" \ - | sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' \ - | cut -d = -f 2)" + domain_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' | + grep "$search_domain\|$search_domain(.*)" | + sed -n 's/.*\(edit\.php?edit_domain_id=[0-9a-zA-Z]*\).*/\1/p' | + cut -d = -f 2)" # The above beauty extracts domain ID from the html page... # strip out all blank space and new lines. Then insert newlines # before each table row @@ -349,11 +349,11 @@ _freedns_data_id() { return 1 fi - data_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' \ - | grep "$record_type" \ - | grep "$search_domain" \ - | sed -n 's/.*\(edit\.php?data_id=[0-9a-zA-Z]*\).*/\1/p' \ - | cut -d = -f 2)" + data_id="$(echo "$htmlpage" | tr -d " \t\r\n\v\f" | sed 's//@/g' | tr '@' '\n' | + grep "$record_type" | + grep "$search_domain" | + sed -n 's/.*\(edit\.php?data_id=[0-9a-zA-Z]*\).*/\1/p' | + cut -d = -f 2)" # The above beauty extracts data ID from the html page... # strip out all blank space and new lines. Then insert newlines # before each table row diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh index cdda4775..87119521 100644 --- a/dnsapi/dns_gandi_livedns.sh +++ b/dnsapi/dns_gandi_livedns.sh @@ -69,9 +69,9 @@ 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"}' \ - && _info "Removing record $(__green "success")" + "{\"rrset_ttl\": 300, \"rrset_values\": $_new_rrset_values}" && + _contains "$response" '{"message": "DNS Record Created"}' && + _info "Removing record $(__green "success")" } #################### Private functions below ################################## @@ -125,9 +125,9 @@ _dns_gandi_append_record() { fi _debug new_rrset_values "$_rrset_values" _gandi_livedns_rest PUT "domains/$_domain/records/$sub_domain/TXT" \ - "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" \ - && _contains "$response" '{"message": "DNS Record Created"}' \ - && _info "Adding record $(__green "success")" + "{\"rrset_ttl\": 300, \"rrset_values\": $_rrset_values}" && + _contains "$response" '{"message": "DNS Record Created"}' && + _info "Adding record $(__green "success")" } _dns_gandi_existing_rrset_values() { @@ -145,8 +145,8 @@ _dns_gandi_existing_rrset_values() { return 1 fi _debug "Already has TXT record." - _rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' \ - | _egrep_o '\[".*\"]') + _rrset_values=$(echo "$response" | _egrep_o 'rrset_values.*\[.*\]' | + _egrep_o '\[".*\"]') return 0 } diff --git a/dnsapi/dns_gcloud.sh b/dnsapi/dns_gcloud.sh index 6365b338..03060a8c 100755 --- a/dnsapi/dns_gcloud.sh +++ b/dnsapi/dns_gcloud.sh @@ -78,8 +78,8 @@ _dns_gcloud_execute_tr() { for i in $(seq 1 120); do if gcloud dns record-sets changes list \ --zone="$managedZone" \ - --filter='status != done' \ - | grep -q '^.*'; then + --filter='status != done' | + grep -q '^.*'; then _info "_dns_gcloud_execute_tr: waiting for transaction to be comitted ($i/120)..." sleep 5 else @@ -137,11 +137,11 @@ _dns_gcloud_find_zone() { # List domains and find the zone with the deepest sub-domain (in case of some levels of delegation) if ! match=$(gcloud dns managed-zones list \ --format="value(name, dnsName)" \ - --filter="$filter" \ - | while read -r dnsName name; do + --filter="$filter" | + while read -r dnsName name; do printf "%s\t%s\t%s\n" "$(echo "$name" | awk -F"." '{print NF-1}')" "$dnsName" "$name" - done \ - | sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then + done | + sort -n -r | _head_n 1 | cut -f2,3 | grep '^.*'); then _err "_dns_gcloud_find_zone: Can't find a matching managed zone! Perhaps wrong project or gcloud credentials?" return 1 fi diff --git a/dnsapi/dns_he.sh b/dnsapi/dns_he.sh index 5829e00e..ef09fa0a 100755 --- a/dnsapi/dns_he.sh +++ b/dnsapi/dns_he.sh @@ -101,8 +101,8 @@ dns_he_rm() { body="$body&hosted_dns_editzone=1" body="$body&hosted_dns_delrecord=1" body="$body&hosted_dns_delconfirm=delete" - _post "$body" "https://dns.he.net/" \ - | grep '
Successfully removed record.
' \ + _post "$body" "https://dns.he.net/" | + grep '
Successfully removed record.
' \ >/dev/null exit_code="$?" if [ "$exit_code" -eq 0 ]; then diff --git a/dnsapi/dns_hetzner.sh b/dnsapi/dns_hetzner.sh index 5db0418c..911d4a35 100644 --- a/dnsapi/dns_hetzner.sh +++ b/dnsapi/dns_hetzner.sh @@ -123,10 +123,10 @@ _find_record() { return 1 else _record_id=$( - echo "$response" \ - | grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" \ - | grep "\"value\":\"$_record_value\"" \ - | while read -r record; do + echo "$response" | + grep -o "{[^\{\}]*\"name\":\"$_record_name\"[^\}]*}" | + grep "\"value\":\"$_record_value\"" | + while read -r record; do # test for type and if [ -n "$(echo "$record" | _egrep_o '"type":"TXT"')" ]; then echo "$record" | _egrep_o '"id":"[^"]*"' | cut -d : -f 2 | tr -d \" diff --git a/dnsapi/dns_infomaniak.sh b/dnsapi/dns_infomaniak.sh new file mode 100755 index 00000000..765cf39d --- /dev/null +++ b/dnsapi/dns_infomaniak.sh @@ -0,0 +1,199 @@ +#!/usr/bin/env sh + +############################################################################### +# 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//api/dashboard +# Then generate a token with the scope Domain +# this is given as an environment variable INFOMANIAK_API_TOKEN +############################################################################### + +# base variables + +DEFAULT_INFOMANIAK_API_URL="https://api.infomaniak.com" +DEFAULT_INFOMANIAK_TTL=300 + +######## Public functions ##################### + +#Usage: dns_infomaniak_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_infomaniak_add() { + + INFOMANIAK_API_TOKEN="${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}" + INFOMANIAK_API_URL="${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}" + INFOMANIAK_TTL="${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}" + + if [ -z "$INFOMANIAK_API_TOKEN" ]; then + INFOMANIAK_API_TOKEN="" + _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN" + return 1 + fi + + if [ -z "$INFOMANIAK_API_URL" ]; then + INFOMANIAK_API_URL="$DEFAULT_INFOMANIAK_API_URL" + fi + + if [ -z "$INFOMANIAK_TTL" ]; then + INFOMANIAK_TTL="$DEFAULT_INFOMANIAK_TTL" + fi + + #save the token to the account conf file. + _saveaccountconf_mutable INFOMANIAK_API_TOKEN "$INFOMANIAK_API_TOKEN" + + if [ "$INFOMANIAK_API_URL" != "$DEFAULT_INFOMANIAK_API_URL" ]; then + _saveaccountconf_mutable INFOMANIAK_API_URL "$INFOMANIAK_API_URL" + fi + + if [ "$INFOMANIAK_TTL" != "$DEFAULT_INFOMANIAK_TTL" ]; then + _saveaccountconf_mutable INFOMANIAK_TTL "$INFOMANIAK_TTL" + fi + + export _H1="Authorization: Bearer $INFOMANIAK_API_TOKEN" + export _H2="Content-Type: application/json" + + fulldomain="$1" + txtvalue="$2" + + _info "Infomaniak DNS API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + fqdn=${fulldomain#_acme-challenge.} + + # guess which base domain to add record to + zone_and_id=$(_find_zone "$fqdn") + if [ -z "$zone_and_id" ]; then + _err "cannot find zone to modify" + return 1 + fi + zone=${zone_and_id% *} + domain_id=${zone_and_id#* } + + # extract first part of domain + key=${fulldomain%.$zone} + + _debug "zone:$zone id:$domain_id key:$key" + + # payload + data="{\"type\": \"TXT\", \"source\": \"$key\", \"target\": \"$txtvalue\", \"ttl\": $INFOMANIAK_TTL}" + + # API call + response=$(_post "$data" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record") + if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then + _info "Record added" + _debug "Response: $response" + return 0 + fi + _err "could not create record" + _debug "Response: $response" + return 1 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_infomaniak_rm() { + + INFOMANIAK_API_TOKEN="${INFOMANIAK_API_TOKEN:-$(_readaccountconf_mutable INFOMANIAK_API_TOKEN)}" + INFOMANIAK_API_URL="${INFOMANIAK_API_URL:-$(_readaccountconf_mutable INFOMANIAK_API_URL)}" + INFOMANIAK_TTL="${INFOMANIAK_TTL:-$(_readaccountconf_mutable INFOMANIAK_TTL)}" + + if [ -z "$INFOMANIAK_API_TOKEN" ]; then + INFOMANIAK_API_TOKEN="" + _err "Please provide a valid Infomaniak API token in variable INFOMANIAK_API_TOKEN" + return 1 + fi + + if [ -z "$INFOMANIAK_API_URL" ]; then + INFOMANIAK_API_URL="$DEFAULT_INFOMANIAK_API_URL" + fi + + if [ -z "$INFOMANIAK_TTL" ]; then + INFOMANIAK_TTL="$DEFAULT_INFOMANIAK_TTL" + fi + + #save the token to the account conf file. + _saveaccountconf_mutable INFOMANIAK_API_TOKEN "$INFOMANIAK_API_TOKEN" + + if [ "$INFOMANIAK_API_URL" != "$DEFAULT_INFOMANIAK_API_URL" ]; then + _saveaccountconf_mutable INFOMANIAK_API_URL "$INFOMANIAK_API_URL" + fi + + if [ "$INFOMANIAK_TTL" != "$DEFAULT_INFOMANIAK_TTL" ]; then + _saveaccountconf_mutable INFOMANIAK_TTL "$INFOMANIAK_TTL" + fi + + export _H1="Authorization: Bearer $INFOMANIAK_API_TOKEN" + export _H2="ContentType: application/json" + + fulldomain=$1 + txtvalue=$2 + _info "Infomaniak DNS API" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + fqdn=${fulldomain#_acme-challenge.} + + # guess which base domain to add record to + zone_and_id=$(_find_zone "$fqdn") + if [ -z "$zone_and_id" ]; then + _err "cannot find zone to modify" + return 1 + fi + zone=${zone_and_id% *} + domain_id=${zone_and_id#* } + + # extract first part of domain + key=${fulldomain%.$zone} + + _debug "zone:$zone id:$domain_id key:$key" + + # find previous record + # shellcheck disable=SC1004 + record_id=$(_get "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record" | sed 's/.*"data":\[\(.*\)\]}/\1/; s/},{/}\ +{/g' | sed -n 's/.*"id":"*\([0-9]*\)"*.*"source_idn":"'"$fulldomain"'".*"target_idn":"'"$txtvalue"'".*/\1/p') + if [ -z "$record_id" ]; then + _err "could not find record to delete" + return 1 + fi + _debug "record_id: $record_id" + + # API call + response=$(_post "" "${INFOMANIAK_API_URL}/1/domain/$domain_id/dns/record/$record_id" "" DELETE) + if [ -n "$response" ] && echo "$response" | _contains '"result":"success"'; then + _info "Record deleted" + return 0 + fi + _err "could not delete record" + return 1 +} + +#################### Private functions below ################################## + +_get_domain_id() { + domain="$1" + + # shellcheck disable=SC1004 + _get "${INFOMANIAK_API_URL}/1/product?service_name=domain&customer_name=$domain" | sed 's/.*"data":\[{\(.*\)}\]}/\1/; s/,/\ +/g' | sed -n 's/^"id":\(.*\)/\1/p' +} + +_find_zone() { + zone="$1" + + # find domain in list, removing . parts sequentialy + while _contains "$zone" '\.'; do + _debug "testing $zone" + id=$(_get_domain_id "$zone") + if [ -n "$id" ]; then + echo "$zone $id" + return + fi + zone=${zone#*.} + done +} diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh index 2d8d6b0a..bd1e0391 100755 --- a/dnsapi/dns_ispconfig.sh +++ b/dnsapi/dns_ispconfig.sh @@ -95,29 +95,29 @@ _ISPC_getZoneInfo() { server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Server ID: '${server_id}'" case "${server_id}" in - '' | *[!0-9]*) - _err "Server ID is not numeric." - return 1 - ;; - *) _info "Retrieved Server ID" ;; + '' | *[!0-9]*) + _err "Server ID is not numeric." + return 1 + ;; + *) _info "Retrieved Server ID" ;; esac zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Zone: '${zone}'" case "${zone}" in - '' | *[!0-9]*) - _err "Zone ID is not numeric." - return 1 - ;; - *) _info "Retrieved Zone ID" ;; + '' | *[!0-9]*) + _err "Zone ID is not numeric." + return 1 + ;; + *) _info "Retrieved Zone ID" ;; esac client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Client ID: '${client_id}'" case "${client_id}" in - '' | *[!0-9]*) - _err "Client ID is not numeric." - return 1 - ;; - *) _info "Retrieved Client ID." ;; + '' | *[!0-9]*) + _err "Client ID is not numeric." + return 1 + ;; + *) _info "Retrieved Client ID." ;; esac zoneFound="" zoneEnd="" @@ -135,11 +135,11 @@ _ISPC_addTxt() { record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Record ID: '${record_id}'" case "${record_id}" in - '' | *[!0-9]*) - _err "Couldn't add ACME Challenge TXT record to zone." - return 1 - ;; - *) _info "Added ACME Challenge TXT record to zone." ;; + '' | *[!0-9]*) + _err "Couldn't add ACME Challenge TXT record to zone." + return 1 + ;; + *) _info "Added ACME Challenge TXT record to zone." ;; esac } @@ -153,24 +153,24 @@ _ISPC_rmTxt() { record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) _debug "Record ID: '${record_id}'" case "${record_id}" in - '' | *[!0-9]*) - _err "Record ID is not numeric." + '' | *[!0-9]*) + _err "Record ID is not numeric." + return 1 + ;; + *) + unset IFS + _info "Retrieved Record ID." + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\",\"update_serial\":true}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")" + _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'" + _debug "Result of _ISPC_rmTxt: '$curResult'" + if _contains "${curResult}" '"code":"ok"'; then + _info "Removed ACME Challenge TXT record from zone." + else + _err "Couldn't remove ACME Challenge TXT record from zone." return 1 - ;; - *) - unset IFS - _info "Retrieved Record ID." - curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\",\"update_serial\":true}" - curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")" - _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'" - _debug "Result of _ISPC_rmTxt: '$curResult'" - if _contains "${curResult}" '"code":"ok"'; then - _info "Removed ACME Challenge TXT record from zone." - else - _err "Couldn't remove ACME Challenge TXT record from zone." - return 1 - fi - ;; + fi + ;; esac fi } diff --git a/dnsapi/dns_joker.sh b/dnsapi/dns_joker.sh index 5d50953e..78399a1d 100644 --- a/dnsapi/dns_joker.sh +++ b/dnsapi/dns_joker.sh @@ -100,7 +100,7 @@ _get_root() { fi # Try to remove a test record. With correct root domain, username and password this will return "OK: ..." regardless - # of record in question existing or not. + # of record in question existing or not. if _joker_rest "username=$JOKER_USERNAME&password=$JOKER_PASSWORD&zone=$h&label=jokerTXTUpdateTest&type=TXT&value="; then if _startswith "$response" "OK"; then _sub_domain="$(echo "$fulldomain" | sed "s/\\.$h\$//")" diff --git a/dnsapi/dns_kappernet.sh b/dnsapi/dns_kappernet.sh new file mode 100644 index 00000000..83a7e5f8 --- /dev/null +++ b/dnsapi/dns_kappernet.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env sh + +# 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 +# fullhostname: something.example.com +# txtvalue: someacmegenerated string +dns_kappernet_add() { + fullhostname=$1 + txtvalue=$2 + + KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}" + KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}" + + 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." + return 1 + fi + + #store the api key and email to the account conf file. + _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key" + _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret" + _debug "Checking Domain ..." + if ! _get_root "$fullhostname"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "SUBDOMAIN: $_sub_domain" + _debug _domain "DOMAIN: $_domain" + + _info "Trying to add TXT DNS Record" + data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%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 120 seconds for DNS to spread the new record" + _sleep 120 + return 0 + else + _err "Error creating a TXT DNS Record: $fullhostname TXT $txtvalue" + _err "Error Message: $response" + return 1 + fi + fi + _err "Failed creating TXT Record" +} + +############################################################################### +# called with +# fullhostname: something.example.com +dns_kappernet_rm() { + fullhostname=$1 + txtvalue=$2 + + KAPPERNETDNS_Key="${KAPPERNETDNS_Key:-$(_readaccountconf_mutable KAPPERNETDNS_Key)}" + KAPPERNETDNS_Secret="${KAPPERNETDNS_Secret:-$(_readaccountconf_mutable KAPPERNETDNS_Secret)}" + + 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." + return 1 + fi + + #store the api key and email to the account conf file. + _saveaccountconf_mutable KAPPERNETDNS_Key "$KAPPERNETDNS_Key" + _saveaccountconf_mutable KAPPERNETDNS_Secret "$KAPPERNETDNS_Secret" + + _info "Trying to remove the TXT Record: $fullhostname containing $txtvalue" + data="%7B%22name%22%3A%22$fullhostname%22%2C%22type%22%3A%22TXT%22%2C%22content%22%3A%22$txtvalue%22%2C%22ttl%22%3A%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 + else + _err "Error deleting DNS Record: $fullhostname containing $txtvalue" + _err "Problem: $response" + return 1 + fi + fi + _err "Problem deleting TXT DNS record" +} + +#################### Private functions below ################################## +# called with hostname +# e.g._acme-challenge.www.domain.com returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + i=2 + p=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + if [ -z "$h" ]; then + #not valid + return 1 + fi + if ! _kappernet_api GET "action=list&subject=$h"; then + return 1 + fi + if _contains "$response" '"OK":false'; then + _debug "$h not found" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain="$h" + return 0 + fi + p="$i" + i=$(_math "$i" + 1) + done + return 1 +} + +################################################################################ +# calls the kapper.net DNS Panel API +# with +# method +# param +_kappernet_api() { + method=$1 + param="$2" + + _debug param "PARAMETER=$param" + url="$KAPPERNETDNS_Api&$param" + _debug url "URL=$url" + + if [ "$method" = "GET" ]; then + response="$(_get "$url")" + else + _err "Unsupported method" + return 1 + fi + + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_misaka.sh b/dnsapi/dns_misaka.sh index eed4170e..36ba5cfd 100755 --- a/dnsapi/dns_misaka.sh +++ b/dnsapi/dns_misaka.sh @@ -47,7 +47,7 @@ dns_misaka_add() { if [ "$count" = "0" ]; then _info "Adding record" - if _misaka_rest PUT "zones/${_domain}/recordsets/${_sub_domain}/TXT" "{\"records\":[{\"value\":\"\\\"$txtvalue\\\"\"}],\"filters\":[],\"ttl\":1}"; then + if _misaka_rest POST "zones/${_domain}/recordsets/${_sub_domain}/TXT" "{\"records\":[{\"value\":\"\\\"$txtvalue\\\"\"}],\"filters\":[],\"ttl\":1}"; then _debug response "$response" if _contains "$response" "$_sub_domain"; then _info "Added" @@ -61,7 +61,7 @@ dns_misaka_add() { else _info "Updating record" - _misaka_rest POST "zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true" "{\"records\": [{\"value\": \"\\\"$txtvalue\\\"\"}],\"ttl\":1}" + _misaka_rest PUT "zones/${_domain}/recordsets/${_sub_domain}/TXT?append=true" "{\"records\": [{\"value\": \"\\\"$txtvalue\\\"\"}],\"ttl\":1}" if [ "$?" = "0" ] && _contains "$response" "$_sub_domain"; then _info "Updated!" #todo: check if the record takes effect diff --git a/dnsapi/dns_netlify.sh b/dnsapi/dns_netlify.sh new file mode 100644 index 00000000..2ce13e2b --- /dev/null +++ b/dnsapi/dns_netlify.sh @@ -0,0 +1,162 @@ +#!/usr/bin/env sh + +#NETLIFY_ACCESS_TOKEN="xxxx" + +NETLIFY_HOST="api.netlify.com/api/v1/" +NETLIFY_URL="https://$NETLIFY_HOST" + +######## Public functions ##################### + +#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_netlify_add() { + fulldomain=$1 + txtvalue=$2 + + NETLIFY_ACCESS_TOKEN="${NETLIFY_ACCESS_TOKEN:-$(_readaccountconf_mutable NETLIFY_ACCESS_TOKEN)}" + + if [ -z "$NETLIFY_ACCESS_TOKEN" ]; then + NETLIFY_ACCESS_TOKEN="" + _err "Please specify your Netlify Access Token and try again." + return 1 + fi + + _info "Using Netlify" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN" + + if ! _get_root "$fulldomain" "$accesstoken"; then + _err "invalid domain" + return 1 + fi + + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + dnsRecordURI="dns_zones/$_domain_id/dns_records" + + body="{\"type\":\"TXT\", \"hostname\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"ttl\":\"10\"}" + + _netlify_rest POST "$dnsRecordURI" "$body" "$NETLIFY_ACCESS_TOKEN" + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" + if [ "$_code" = "200" ] || [ "$_code" = '201' ]; then + _info "validation value added" + return 0 + else + _err "error adding validation value ($_code)" + return 1 + fi + + _err "Not fully implemented!" + return 1 +} + +#Usage: dns_myapi_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +#Remove the txt record after validation. +dns_netlify_rm() { + _info "Using Netlify" + txtdomain="$1" + txt="$2" + _debug txtdomain "$txtdomain" + _debug txt "$txt" + + _saveaccountconf_mutable NETLIFY_ACCESS_TOKEN "$NETLIFY_ACCESS_TOKEN" + + if ! _get_root "$txtdomain" "$accesstoken"; then + _err "invalid domain" + return 1 + fi + + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + dnsRecordURI="dns_zones/$_domain_id/dns_records" + + _netlify_rest GET "$dnsRecordURI" "" "$NETLIFY_ACCESS_TOKEN" + + _record_id=$(echo "$response" | _egrep_o "\"type\":\"TXT\",[^\}]*\"value\":\"$txt\"" | head -n 1 | _egrep_o "\"id\":\"[^\"\}]*\"" | cut -d : -f 2 | tr -d \") + _debug _record_id "$_record_id" + if [ "$_record_id" ]; then + _netlify_rest DELETE "$dnsRecordURI/$_record_id" "" "$NETLIFY_ACCESS_TOKEN" + _code="$(grep "^HTTP" "$HTTP_HEADER" | _tail_n 1 | cut -d " " -f 2 | tr -d "\\r\\n")" + if [ "$_code" = "200" ] || [ "$_code" = '204' ]; then + _info "validation value removed" + return 0 + else + _err "error removing validation value ($_code)" + return 1 + fi + return 0 + fi + return 1 +} + +#################### Private functions below ################################## + +_get_root() { + domain=$1 + accesstoken=$2 + i=1 + p=1 + + _netlify_rest GET "dns_zones" "" "$accesstoken" + + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug2 "Checking domain: $h" + if [ -z "$h" ]; then + #not valid + _err "Invalid domain" + return 1 + fi + + if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(echo "$response" | _egrep_o "\"[^\"]*\",\"name\":\"$h" | cut -d , -f 1 | tr -d \") + if [ "$_domain_id" ]; then + if [ "$i" = 1 ]; then + #create the record at the domain apex (@) if only the domain name was provided as --domain-alias + _sub_domain="@" + else + _sub_domain=$(echo "$domain" | cut -d . -f 1-$p) + fi + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_netlify_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + token_trimmed=$(echo "$NETLIFY_ACCESS_TOKEN" | tr -d '"') + + export _H1="Content-Type: application/json" + export _H2="Authorization: Bearer $token_trimmed" + + : >"$HTTP_HEADER" + + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$NETLIFY_URL$ep" "" "$m")" + else + response="$(_get "$NETLIFY_URL$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_nic.sh b/dnsapi/dns_nic.sh index 5052ee10..56170f87 100644 --- a/dnsapi/dns_nic.sh +++ b/dnsapi/dns_nic.sh @@ -166,7 +166,7 @@ _get_root() { if _contains "$_all_domains" "^$h$"; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h - _service=$(printf "%s" "$response" | grep "idn-name=\"$_domain\"" | sed -r "s/.*service=\"(.*)\".*$/\1/") + _service=$(printf "%s" "$response" | grep -m 1 "idn-name=\"$_domain\"" | sed -r "s/.*service=\"(.*)\".*$/\1/") return 0 fi p="$i" diff --git a/dnsapi/dns_one.sh b/dnsapi/dns_one.sh index 96ef5969..890cc804 100644 --- a/dnsapi/dns_one.sh +++ b/dnsapi/dns_one.sh @@ -6,10 +6,10 @@ # Created: 2019-02-17 # Fixed by: @der-berni # Modified: 2020-04-07 -# +# # Use ONECOM_KeepCnameProxy to keep the CNAME DNS record # export ONECOM_KeepCnameProxy="1" -# +# # export ONECOM_User="username" # export ONECOM_Password="password" # diff --git a/dnsapi/dns_openstack.sh b/dnsapi/dns_openstack.sh new file mode 100755 index 00000000..38619e6f --- /dev/null +++ b/dnsapi/dns_openstack.sh @@ -0,0 +1,348 @@ +#!/usr/bin/env sh + +# 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 ##################### + +# Usage: dns_openstack_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_openstack_add() { + fulldomain=$1 + txtvalue=$2 + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _dns_openstack_credentials || return $? + _dns_openstack_check_setup || return $? + _dns_openstack_find_zone || return $? + _dns_openstack_get_recordset || return $? + _debug _recordset_id "$_recordset_id" + if [ -n "$_recordset_id" ]; then + _dns_openstack_get_records || return $? + _debug _records "$_records" + fi + _dns_openstack_create_recordset || return $? +} + +# Usage: dns_openstack_rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +# Remove the txt record after validation. +dns_openstack_rm() { + fulldomain=$1 + txtvalue=$2 + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" + + _dns_openstack_credentials || return $? + _dns_openstack_check_setup || return $? + _dns_openstack_find_zone || return $? + _dns_openstack_get_recordset || return $? + _debug _recordset_id "$_recordset_id" + if [ -n "$_recordset_id" ]; then + _dns_openstack_get_records || return $? + _debug _records "$_records" + fi + _dns_openstack_delete_recordset || return $? +} + +#################### Private functions below ################################## + +_dns_openstack_create_recordset() { + + if [ -z "$_recordset_id" ]; then + _info "Creating a new recordset" + if ! _recordset_id=$(openstack recordset create -c id -f value --type TXT --record "$txtvalue" "$_zone_id" "$fulldomain."); then + _err "No recordset ID found after create" + return 1 + fi + else + _info "Updating existing recordset" + # Build new list of --record args for update + _record_args="--record $txtvalue" + for _rec in $_records; do + _record_args="$_record_args --record $_rec" + done + # shellcheck disable=SC2086 + if ! _recordset_id=$(openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain."); then + _err "Recordset update failed" + return 1 + fi + fi + + _max_retries=60 + _sleep_sec=5 + _retry_times=0 + while [ "$_retry_times" -lt "$_max_retries" ]; do + _retry_times=$(_math "$_retry_times" + 1) + _debug3 _retry_times "$_retry_times" + + _record_status=$(openstack recordset show -c status -f value "$_zone_id" "$_recordset_id") + _info "Recordset status is $_record_status" + if [ "$_record_status" = "ACTIVE" ]; then + return 0 + elif [ "$_record_status" = "ERROR" ]; then + return 1 + else + _sleep $_sleep_sec + fi + done + + _err "Recordset failed to become ACTIVE" + return 1 +} + +_dns_openstack_delete_recordset() { + + if [ "$_records" = "$txtvalue" ]; then + _info "Only one record found, deleting recordset" + if ! openstack recordset delete "$_zone_id" "$fulldomain." >/dev/null; then + _err "Failed to delete recordset" + return 1 + fi + else + _info "Found existing records, updating recordset" + # Build new list of --record args for update + _record_args="" + for _rec in $_records; do + if [ "$_rec" = "$txtvalue" ]; then + continue + fi + _record_args="$_record_args --record $_rec" + done + # shellcheck disable=SC2086 + if ! openstack recordset set -c id -f value $_record_args "$_zone_id" "$fulldomain." >/dev/null; then + _err "Recordset update failed" + return 1 + fi + fi +} + +_dns_openstack_get_root() { + # Take the full fqdn and strip away pieces until we get an exact zone name + # match. For example, _acme-challenge.something.domain.com might need to go + # into something.domain.com or domain.com + _zone_name=$1 + _zone_list=$2 + while [ "$_zone_name" != "" ]; do + _zone_name="$(echo "$_zone_name" | sed 's/[^.]*\.*//')" + echo "$_zone_list" | while read -r id name; do + if _startswith "$_zone_name." "$name"; then + echo "$id" + fi + done + done | _head_n 1 +} + +_dns_openstack_find_zone() { + if ! _zone_list="$(openstack zone list -c id -c name -f value)"; then + _err "Can't list zones. Check your OpenStack credentials" + return 1 + fi + _debug _zone_list "$_zone_list" + + if ! _zone_id="$(_dns_openstack_get_root "$fulldomain" "$_zone_list")"; then + _err "Can't find a matching zone. Check your OpenStack credentials" + return 1 + fi + _debug _zone_id "$_zone_id" +} + +_dns_openstack_get_records() { + if ! _records=$(openstack recordset show -c records -f value "$_zone_id" "$fulldomain."); then + _err "Failed to get records" + return 1 + fi + return 0 +} + +_dns_openstack_get_recordset() { + if ! _recordset_id=$(openstack recordset list -c id -f value --name "$fulldomain." "$_zone_id"); then + _err "Failed to get recordset" + return 1 + fi + return 0 +} + +_dns_openstack_check_setup() { + if ! _exists openstack; then + _err "OpenStack client not found" + return 1 + fi +} + +_dns_openstack_credentials() { + _debug "Check OpenStack credentials" + + # If we have OS_AUTH_URL already set in the environment, then assume we want + # to use those, otherwise use stored credentials + if [ -n "$OS_AUTH_URL" ]; then + _debug "OS_AUTH_URL env var found, using environment" + else + _debug "OS_AUTH_URL not found, loading stored credentials" + OS_AUTH_URL="${OS_AUTH_URL:-$(_readaccountconf_mutable OS_AUTH_URL)}" + OS_IDENTITY_API_VERSION="${OS_IDENTITY_API_VERSION:-$(_readaccountconf_mutable OS_IDENTITY_API_VERSION)}" + OS_AUTH_TYPE="${OS_AUTH_TYPE:-$(_readaccountconf_mutable OS_AUTH_TYPE)}" + OS_APPLICATION_CREDENTIAL_ID="${OS_APPLICATION_CREDENTIAL_ID:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID)}" + OS_APPLICATION_CREDENTIAL_SECRET="${OS_APPLICATION_CREDENTIAL_SECRET:-$(_readaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET)}" + OS_USERNAME="${OS_USERNAME:-$(_readaccountconf_mutable OS_USERNAME)}" + OS_PASSWORD="${OS_PASSWORD:-$(_readaccountconf_mutable OS_PASSWORD)}" + OS_PROJECT_NAME="${OS_PROJECT_NAME:-$(_readaccountconf_mutable OS_PROJECT_NAME)}" + OS_PROJECT_ID="${OS_PROJECT_ID:-$(_readaccountconf_mutable OS_PROJECT_ID)}" + OS_USER_DOMAIN_NAME="${OS_USER_DOMAIN_NAME:-$(_readaccountconf_mutable OS_USER_DOMAIN_NAME)}" + OS_USER_DOMAIN_ID="${OS_USER_DOMAIN_ID:-$(_readaccountconf_mutable OS_USER_DOMAIN_ID)}" + OS_PROJECT_DOMAIN_NAME="${OS_PROJECT_DOMAIN_NAME:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_NAME)}" + OS_PROJECT_DOMAIN_ID="${OS_PROJECT_DOMAIN_ID:-$(_readaccountconf_mutable OS_PROJECT_DOMAIN_ID)}" + fi + + # Check each var and either save or clear it depending on whether its set. + # The helps us clear out old vars in the case where a user may want + # to switch between password and app creds + _debug "OS_AUTH_URL" "$OS_AUTH_URL" + if [ -n "$OS_AUTH_URL" ]; then + export OS_AUTH_URL + _saveaccountconf_mutable OS_AUTH_URL "$OS_AUTH_URL" + else + unset OS_AUTH_URL + _clearaccountconf SAVED_OS_AUTH_URL + fi + + _debug "OS_IDENTITY_API_VERSION" "$OS_IDENTITY_API_VERSION" + if [ -n "$OS_IDENTITY_API_VERSION" ]; then + export OS_IDENTITY_API_VERSION + _saveaccountconf_mutable OS_IDENTITY_API_VERSION "$OS_IDENTITY_API_VERSION" + else + unset OS_IDENTITY_API_VERSION + _clearaccountconf SAVED_OS_IDENTITY_API_VERSION + fi + + _debug "OS_AUTH_TYPE" "$OS_AUTH_TYPE" + if [ -n "$OS_AUTH_TYPE" ]; then + export OS_AUTH_TYPE + _saveaccountconf_mutable OS_AUTH_TYPE "$OS_AUTH_TYPE" + else + unset OS_AUTH_TYPE + _clearaccountconf SAVED_OS_AUTH_TYPE + fi + + _debug "OS_APPLICATION_CREDENTIAL_ID" "$OS_APPLICATION_CREDENTIAL_ID" + if [ -n "$OS_APPLICATION_CREDENTIAL_ID" ]; then + export OS_APPLICATION_CREDENTIAL_ID + _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_ID "$OS_APPLICATION_CREDENTIAL_ID" + else + unset OS_APPLICATION_CREDENTIAL_ID + _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_ID + fi + + _secure_debug "OS_APPLICATION_CREDENTIAL_SECRET" "$OS_APPLICATION_CREDENTIAL_SECRET" + if [ -n "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then + export OS_APPLICATION_CREDENTIAL_SECRET + _saveaccountconf_mutable OS_APPLICATION_CREDENTIAL_SECRET "$OS_APPLICATION_CREDENTIAL_SECRET" + else + unset OS_APPLICATION_CREDENTIAL_SECRET + _clearaccountconf SAVED_OS_APPLICATION_CREDENTIAL_SECRET + fi + + _debug "OS_USERNAME" "$OS_USERNAME" + if [ -n "$OS_USERNAME" ]; then + export OS_USERNAME + _saveaccountconf_mutable OS_USERNAME "$OS_USERNAME" + else + unset OS_USERNAME + _clearaccountconf SAVED_OS_USERNAME + fi + + _secure_debug "OS_PASSWORD" "$OS_PASSWORD" + if [ -n "$OS_PASSWORD" ]; then + export OS_PASSWORD + _saveaccountconf_mutable OS_PASSWORD "$OS_PASSWORD" + else + unset OS_PASSWORD + _clearaccountconf SAVED_OS_PASSWORD + fi + + _debug "OS_PROJECT_NAME" "$OS_PROJECT_NAME" + if [ -n "$OS_PROJECT_NAME" ]; then + export OS_PROJECT_NAME + _saveaccountconf_mutable OS_PROJECT_NAME "$OS_PROJECT_NAME" + else + unset OS_PROJECT_NAME + _clearaccountconf SAVED_OS_PROJECT_NAME + fi + + _debug "OS_PROJECT_ID" "$OS_PROJECT_ID" + if [ -n "$OS_PROJECT_ID" ]; then + export OS_PROJECT_ID + _saveaccountconf_mutable OS_PROJECT_ID "$OS_PROJECT_ID" + else + unset OS_PROJECT_ID + _clearaccountconf SAVED_OS_PROJECT_ID + fi + + _debug "OS_USER_DOMAIN_NAME" "$OS_USER_DOMAIN_NAME" + if [ -n "$OS_USER_DOMAIN_NAME" ]; then + export OS_USER_DOMAIN_NAME + _saveaccountconf_mutable OS_USER_DOMAIN_NAME "$OS_USER_DOMAIN_NAME" + else + unset OS_USER_DOMAIN_NAME + _clearaccountconf SAVED_OS_USER_DOMAIN_NAME + fi + + _debug "OS_USER_DOMAIN_ID" "$OS_USER_DOMAIN_ID" + if [ -n "$OS_USER_DOMAIN_ID" ]; then + export OS_USER_DOMAIN_ID + _saveaccountconf_mutable OS_USER_DOMAIN_ID "$OS_USER_DOMAIN_ID" + else + unset OS_USER_DOMAIN_ID + _clearaccountconf SAVED_OS_USER_DOMAIN_ID + fi + + _debug "OS_PROJECT_DOMAIN_NAME" "$OS_PROJECT_DOMAIN_NAME" + if [ -n "$OS_PROJECT_DOMAIN_NAME" ]; then + export OS_PROJECT_DOMAIN_NAME + _saveaccountconf_mutable OS_PROJECT_DOMAIN_NAME "$OS_PROJECT_DOMAIN_NAME" + else + unset OS_PROJECT_DOMAIN_NAME + _clearaccountconf SAVED_OS_PROJECT_DOMAIN_NAME + fi + + _debug "OS_PROJECT_DOMAIN_ID" "$OS_PROJECT_DOMAIN_ID" + if [ -n "$OS_PROJECT_DOMAIN_ID" ]; then + export OS_PROJECT_DOMAIN_ID + _saveaccountconf_mutable OS_PROJECT_DOMAIN_ID "$OS_PROJECT_DOMAIN_ID" + else + unset OS_PROJECT_DOMAIN_ID + _clearaccountconf SAVED_OS_PROJECT_DOMAIN_ID + fi + + if [ "$OS_AUTH_TYPE" = "v3applicationcredential" ]; then + # Application Credential auth + if [ -z "$OS_APPLICATION_CREDENTIAL_ID" ] || [ -z "$OS_APPLICATION_CREDENTIAL_SECRET" ]; then + _err "When using OpenStack application credentials, OS_APPLICATION_CREDENTIAL_ID" + _err "and OS_APPLICATION_CREDENTIAL_SECRET must be set." + _err "Please check your credentials and try again." + return 1 + fi + else + # Password auth + if [ -z "$OS_USERNAME" ] || [ -z "$OS_PASSWORD" ]; then + _err "OpenStack username or password not found." + _err "Please check your credentials and try again." + return 1 + fi + + if [ -z "$OS_PROJECT_NAME" ] && [ -z "$OS_PROJECT_ID" ]; then + _err "When using password authentication, OS_PROJECT_NAME or" + _err "OS_PROJECT_ID must be set." + _err "Please check your credentials and try again." + return 1 + fi + fi + + return 0 +} diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh index 7c18d009..f6f9689a 100755 --- a/dnsapi/dns_ovh.sh +++ b/dnsapi/dns_ovh.sh @@ -41,40 +41,40 @@ _ovh_get_api() { case "${_ogaep}" in - ovh-eu | ovheu) - printf "%s" $OVH_EU - return - ;; - ovh-ca | ovhca) - printf "%s" $OVH_CA - return - ;; - kimsufi-eu | kimsufieu) - printf "%s" $KSF_EU - return - ;; - kimsufi-ca | kimsufica) - printf "%s" $KSF_CA - return - ;; - soyoustart-eu | soyoustarteu) - printf "%s" $SYS_EU - return - ;; - soyoustart-ca | soyoustartca) - printf "%s" $SYS_CA - return - ;; - runabove-ca | runaboveca) - printf "%s" $RAV_CA - return - ;; - - *) - - _err "Unknown parameter : $1" - return 1 - ;; + ovh-eu | ovheu) + printf "%s" $OVH_EU + return + ;; + ovh-ca | ovhca) + printf "%s" $OVH_CA + return + ;; + kimsufi-eu | kimsufieu) + printf "%s" $KSF_EU + return + ;; + kimsufi-ca | kimsufica) + printf "%s" $KSF_CA + return + ;; + soyoustart-eu | soyoustarteu) + printf "%s" $SYS_EU + return + ;; + soyoustart-ca | soyoustartca) + printf "%s" $SYS_CA + return + ;; + runabove-ca | runaboveca) + printf "%s" $RAV_CA + return + ;; + + *) + + _err "Unknown parameter : $1" + return 1 + ;; esac } @@ -248,7 +248,7 @@ _ovh_authentication() { # _domain=domain.com _get_root() { domain=$1 - i=2 + i=1 p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f $i-100) diff --git a/dnsapi/dns_pleskxml.sh b/dnsapi/dns_pleskxml.sh index fe18bef4..f5986827 100644 --- a/dnsapi/dns_pleskxml.sh +++ b/dnsapi/dns_pleskxml.sh @@ -136,11 +136,12 @@ dns_pleskxml_rm() { # Reduce output to one line per DNS record, filtered for TXT records with a record ID only (which they should all have) # Also strip out spaces between tags, redundant and group tags and any tags - reclist="$(_api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' \ - | sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s###g;s#<[a-z][^/<>]*/>##g' \ - | grep "${root_domain_id}" \ - | grep '[0-9]\{1,\}' \ - | grep 'TXT' + reclist="$( + _api_response_split "$pleskxml_prettyprint_result" 'result' 'ok' | + sed 's# \{1,\}<\([a-zA-Z]\)#<\1#g;s###g;s#<[a-z][^/<>]*/>##g' | + grep "${root_domain_id}" | + grep '[0-9]\{1,\}' | + grep 'TXT' )" if [ -z "$reclist" ]; then @@ -151,10 +152,11 @@ dns_pleskxml_rm() { _debug "Got list of DNS TXT records for root domain '$root_domain_name':" _debug "$reclist" - recid="$(_value "$reclist" \ - | grep "${fulldomain}." \ - | grep "${txtvalue}" \ - | sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/' + recid="$( + _value "$reclist" | + grep "${fulldomain}." | + grep "${txtvalue}" | + sed 's/^.*\([0-9]\{1,\}\)<\/id>.*$/\1/' )" if ! _value "$recid" | grep '^[0-9]\{1,\}$' >/dev/null; then @@ -220,11 +222,11 @@ _countdots() { # Last line could change to instead, with suitable escaping of ['"/$], # if future Plesk XML API changes ever require extended regex _api_response_split() { - printf '%s' "$1" \ - | sed 's/^ +//;s/ +$//' \ - | tr -d '\n\r' \ - | sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" \ - | grep "$3" + printf '%s' "$1" | + sed 's/^ +//;s/ +$//' | + tr -d '\n\r' | + sed "s/<\/\{0,1\}$2>/${NEWLINE}/g" | + grep "$3" } #################### Private functions below (DNS functions) ################################## @@ -261,14 +263,15 @@ _call_api() { elif [ "$statuslines_count_okay" -ne "$statuslines_count_total" ]; then # We have some status lines that aren't "ok". Any available details are in API response fields "status" "errcode" and "errtext" - # Workaround for basic regex: + # Workaround for basic regex: # - filter output to keep only lines like this: "SPACEStextSPACES" (shouldn't be necessary with prettyprint but guarantees subsequent code is ok) # - then edit the 3 "useful" error tokens individually and remove closing tags on all lines # - then filter again to remove all lines not edited (which will be the lines not starting A-Z) - errtext="$(_value "$pleskxml_prettyprint_result" \ - | grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' \ - | sed 's/^ */Status: /;s/^ */Error code: /;s/^ */Error text: /;s/<\/.*$//' \ - | grep '^[A-Z]' + errtext="$( + _value "$pleskxml_prettyprint_result" | + grep '^ *<[a-z]\{1,\}>[^<]*<\/[a-z]\{1,\}> *$' | + sed 's/^ */Status: /;s/^ */Error code: /;s/^ */Error text: /;s/<\/.*$//' | + grep '^[A-Z]' )" fi diff --git a/dnsapi/dns_regru.sh b/dnsapi/dns_regru.sh index b5729fda..29f758ea 100644 --- a/dnsapi/dns_regru.sh +++ b/dnsapi/dns_regru.sh @@ -33,8 +33,11 @@ dns_regru_add() { fi _debug _domain "$_domain" + _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//") + _debug _subdomain "$_subdomain" + _info "Adding TXT record to ${fulldomain}" - _regru_rest POST "zone/add_txt" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json" + _regru_rest POST "zone/add_txt" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22text%22:%22${txtvalue}%22,%22output_content_type%22:%22plain%22}&input_format=json" if ! _contains "${response}" 'error'; then return 0 @@ -64,8 +67,11 @@ dns_regru_rm() { fi _debug _domain "$_domain" + _subdomain=$(echo "$fulldomain" | sed -r "s/.$_domain//") + _debug _subdomain "$_subdomain" + _info "Deleting resource record $fulldomain" - _regru_rest POST "zone/remove_record" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22_acme-challenge%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json" + _regru_rest POST "zone/remove_record" "input_data={%22username%22:%22${REGRU_API_Username}%22,%22password%22:%22${REGRU_API_Password}%22,%22domains%22:[{%22dname%22:%22${_domain}%22}],%22subdomain%22:%22${_subdomain}%22,%22content%22:%22${txtvalue}%22,%22record_type%22:%22TXT%22,%22output_content_type%22:%22plain%22}&input_format=json" if ! _contains "${response}" 'error'; then return 0 @@ -87,11 +93,11 @@ _get_root() { for ITEM in ${domains_list}; do case "${domain}" in - *${ITEM}*) - _domain=${ITEM} - _debug _domain "${_domain}" - return 0 - ;; + *${ITEM}*) + _domain=${ITEM} + _debug _domain "${_domain}" + return 0 + ;; esac done diff --git a/dnsapi/dns_yandex.sh b/dnsapi/dns_yandex.sh index 5721f994..0a2c3330 100755 --- a/dnsapi/dns_yandex.sh +++ b/dnsapi/dns_yandex.sh @@ -25,7 +25,7 @@ dns_yandex_add() { _PDD_get_record_ids || return 1 _debug "Record_ids: $record_ids" - if [ ! -z "$record_ids" ]; then + if [ -n "$record_ids" ]; then _info "All existing $subdomain records from $domain will be removed at the very end." fi diff --git a/notify/mail.sh b/notify/mail.sh index 54b2a6d4..d33fd0d2 100644 --- a/notify/mail.sh +++ b/notify/mail.sh @@ -98,24 +98,24 @@ _mail_cmnd() { _MAIL_ARGS="" case $(basename "$_MAIL_BIN") in - sendmail) - if [ -n "$MAIL_FROM" ]; then - _MAIL_ARGS="-f '$MAIL_FROM'" - fi - ;; - mutt | mail) - _MAIL_ARGS="-s '$_subject'" - ;; - msmtp) - if [ -n "$MAIL_FROM" ]; then - _MAIL_ARGS="-f '$MAIL_FROM'" - fi - - if [ -n "$MAIL_MSMTP_ACCOUNT" ]; then - _MAIL_ARGS="$_MAIL_ARGS -a '$MAIL_MSMTP_ACCOUNT'" - fi - ;; - *) ;; + sendmail) + if [ -n "$MAIL_FROM" ]; then + _MAIL_ARGS="-f '$MAIL_FROM'" + fi + ;; + mutt | mail) + _MAIL_ARGS="-s '$_subject'" + ;; + msmtp) + if [ -n "$MAIL_FROM" ]; then + _MAIL_ARGS="-f '$MAIL_FROM'" + fi + + if [ -n "$MAIL_MSMTP_ACCOUNT" ]; then + _MAIL_ARGS="$_MAIL_ARGS -a '$MAIL_MSMTP_ACCOUNT'" + fi + ;; + *) ;; esac echo "'$_MAIL_BIN' $_MAIL_ARGS '$MAIL_TO'" @@ -123,16 +123,16 @@ _mail_cmnd() { _mail_body() { case $(basename "$_MAIL_BIN") in - sendmail | ssmtp | msmtp) - if [ -n "$MAIL_FROM" ]; then - echo "From: $MAIL_FROM" - fi - - echo "To: $MAIL_TO" - echo "Subject: $subject" - echo "Content-Type: $contenttype" - echo - ;; + sendmail | ssmtp | msmtp) + if [ -n "$MAIL_FROM" ]; then + echo "From: $MAIL_FROM" + fi + + echo "To: $MAIL_TO" + echo "Subject: $subject" + echo "Content-Type: $contenttype" + echo + ;; esac echo "$_content" diff --git a/notify/teams.sh b/notify/teams.sh index e50ea703..1bc5ed08 100644 --- a/notify/teams.sh +++ b/notify/teams.sh @@ -52,15 +52,15 @@ teams_send() { _content=$(echo "$_content" | _json_encode) case "$_statusCode" in - 0) - _color="${TEAMS_SUCCESS_COLOR:-$_color_success}" - ;; - 1) - _color="${TEAMS_ERROR_COLOR:-$_color_danger}" - ;; - 2) - _color="${TEAMS_SKIP_COLOR:-$_color_muted}" - ;; + 0) + _color="${TEAMS_SUCCESS_COLOR:-$_color_success}" + ;; + 1) + _color="${TEAMS_ERROR_COLOR:-$_color_danger}" + ;; + 2) + _color="${TEAMS_SKIP_COLOR:-$_color_muted}" + ;; esac _color=$(echo "$_color" | tr -cd 'a-fA-F0-9') diff --git a/notify/xmpp.sh b/notify/xmpp.sh index 580f471e..0ce1119e 100644 --- a/notify/xmpp.sh +++ b/notify/xmpp.sh @@ -71,13 +71,13 @@ _xmpp_bin() { _xmpp_cmnd() { case $(basename "$_XMPP_BIN") in - sendxmpp) - echo "'$_XMPP_BIN' '$XMPP_TO' $XMPP_BIN_ARGS" - ;; - *) - _err "Command $XMPP_BIN is not supported, use sendxmpp." - return 1 - ;; + sendxmpp) + echo "'$_XMPP_BIN' '$XMPP_TO' $XMPP_BIN_ARGS" + ;; + *) + _err "Command $XMPP_BIN is not supported, use sendxmpp." + return 1 + ;; esac }