diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..29ea4e42 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 62feca32..8d6d30ad 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,24 +1,54 @@ -language: shell - -env: - global: - - SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64 - -addons: - apt: - sources: - - debian-sid # Grab shellcheck from the Debian repo (o_O) - packages: - - shellcheck - -script: - - curl -sSL $SHFMT_URL -o ~/shfmt - - chmod +x ~/shfmt - - shellcheck -V - - shellcheck -e SC2021,SC2126,SC2034 **/*.sh && echo "shellcheck OK" - - ~/shfmt -l -w -i 2 . && echo "shfmt OK" || git diff --exit-code || (echo "Run shfmt to fix the formatting issues" && false) - -matrix: - fast_finish: true - - +language: shell +sudo: required + +os: + - linux + - osx + +env: + global: + - SHFMT_URL=https://github.com/mvdan/sh/releases/download/v0.4.0/shfmt_v0.4.0_linux_amd64 + +addons: + apt: + sources: + - debian-sid # Grab shellcheck from the Debian repo (o_O) + packages: + - shellcheck + +install: + - if [ "$TRAVIS_OS_NAME" = 'osx' ]; then + brew update && brew install openssl; + brew info openssl; + ln -s /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; + ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib /usr/local/lib/; + ln -s /usr/local/Cellar/openssl/1.0.2j/bin/openssl /usr/local/openssl; + _old_path="$PATH"; + echo "PATH=$PATH"; + export PATH=""; + export OPENSSL_BIN="/usr/local/openssl"; + openssl version 2>&1 || true; + $OPENSSL_BIN version 2>&1 || true; + export PATH="$_old_path"; + fi + +script: + - echo "TEST_LOCAL=$TEST_LOCAL" + - 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 ; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then chmod +x ~/shfmt ; fi + - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/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 **/*.sh && echo "shellcheck OK" ; fi + - cd .. + - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest + - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi + - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo NGROK_TOKEN="$NGROK_TOKEN" OPENSSL_BIN="$OPENSSL_BIN" ./letest.sh ; fi + + +matrix: + fast_finish: true + + diff --git a/README.md b/README.md index 258cc027..c6362ed7 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,26 @@ # An ACME Shell script: acme.sh [![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh) - An ACME protocol client written purely in Shell (Unix shell) language. -- Fully ACME protocol implementation. -- Simple, powerful and very easy to use. You only need 3 minutes to learn. -- Bash, dash and sh compatible. +- Full ACME protocol implementation. +- 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 Let's Encrypt official client. -- Just one script, to issue, renew and install your certificates automatically. +- 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. It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. - Wiki: https://github.com/Neilpang/acme.sh/wiki + +Twitter: [@neilpangxa](https://twitter.com/neilpangxa) + + # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) -#Tested OS + +# Tested OS + | NO | Status| Platform| |----|-------|---------| |1|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/ubuntu-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu @@ -37,42 +42,41 @@ Wiki: https://github.com/Neilpang/acme.sh/wiki |17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT) |18|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/solaris.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris |19|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/gentoo-stage3-amd64.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux +|20|[![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX -For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest): +For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest): https://github.com/Neilpang/acmetest -# Supported Mode -1. Webroot mode -2. Standalone mode -3. Apache mode -4. Dns mode +# Supported modes +- Webroot mode +- Standalone mode +- Apache mode +- DNS mode # 1. How to install -### 1. Install online: +### 1. Install online Check this project: https://github.com/Neilpang/get.acme.sh ```bash curl https://get.acme.sh | sh - ``` Or: ```bash wget -O - https://get.acme.sh | sh - ``` -### 2. Or, Install from git: +### 2. Or, Install from git -Clone this project: +Clone this project and launch installation: ```bash git clone https://github.com/Neilpang/acme.sh.git @@ -82,14 +86,14 @@ cd ./acme.sh You `don't have to be root` then, although `it is recommended`. -Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install +Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install The installer will perform 3 actions: -1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. -All certs will be placed in this folder. -2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. -3. Create everyday cron job to check and renew the cert if needed. +1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. +All certs will be placed in this folder too. +2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. +3. Create daily cron job to check and renew the certs if needed. Cron entry example: @@ -97,18 +101,17 @@ Cron entry example: 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null ``` -After the installation, you must close current terminal and reopen again to make the alias take effect. +After the installation, you must close the current terminal and reopen it to make the alias take effect. + +Ok, you are ready to issue certs now. -Ok, you are ready to issue cert now. Show help message: ``` - root@v1:~# acme.sh -h - ``` - -# 2. Just issue a cert: + +# 2. Just issue a cert **Example 1:** Single domain. @@ -119,56 +122,59 @@ acme.sh --issue -d example.com -w /home/wwwroot/example.com **Example 2:** Multiple domains in the same cert. ```bash -acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com +acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com ``` The parameter `/home/wwwroot/example.com` is the web root folder. You **MUST** have `write access` to this folder. -Second argument **"example.com"** is the main domain you want to issue cert for. -You must have at least a domain there. +Second argument **"example.com"** is the main domain you want to issue the cert for. +You must have at least one domain there. You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. -Generate/issued certs will be placed in `~/.acme.sh/example.com/` +Generated/issued certs will be placed in `~/.acme.sh/example.com/` -The issued cert will be renewed every **60** days automatically. +The issued cert will be renewed automatically every **60** days. More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert -# 3. Install the issued cert to apache/nginx etc. +# 3. Install the issued cert to Apache/Nginx etc. -After you issue a cert, you probably want to install/copy the cert to your nginx/apache or other servers. -You **MUST** use this command to copy the certs to the target files, **Do NOT** use the certs files in **.acme.sh/** folder, they are for internal use only, the folder structure may change in future. +After you issue a cert, you probably want to install/copy the cert to your Apache/Nginx or other servers. +You **MUST** use this command to copy the certs to the target files, **DO NOT** use the certs files in **~/.acme.sh/** folder, they are for internal use only, the folder structure may change in the future. -**nginx** example +**Apache** example: ```bash acme.sh --installcert -d example.com \ ---keypath /path/to/keyfile/in/nginx/key.pem \ ---fullchainpath path/to/fullchain/nginx/cert.pem \ ---reloadcmd "service nginx restart" +--certpath /path/to/certfile/in/apache/cert.pem \ +--keypath /path/to/keyfile/in/apache/key.pem \ +--fullchainpath /path/to/fullchain/certfile/apache/fullchain.pem \ +--reloadcmd "service apache2 force-reload" ``` -**apache** example +**Nginx** example: ```bash acme.sh --installcert -d example.com \ ---certpath /path/to/certfile/in/apache/cert.pem \ ---keypath /path/to/keyfile/in/apache/key.pem \ ---fullchainpath path/to/fullchain/certfile/apache/fullchain.pem \ ---reloadcmd "service apache2 restart" +--keypath /path/to/keyfile/in/nginx/key.pem \ +--fullchainpath /path/to/fullchain/nginx/cert.pem \ +--reloadcmd "service nginx force-reload" ``` Only the domain is required, all the other parameters are optional. -Install/copy the issued cert/key to the production apache or nginx path. +The ownership and permission info of existing files are preserved. You may want to precreate the files to have defined ownership and permission. + +Install/copy the issued cert/key to the production Apache or Nginx path. + +The cert will be `renewed every **60** days by default` (which is configurable). Once the cert is renewed, the Apache/Nginx service will be restarted automatically by the command: `service apache2 restart` or `service nginx restart`. -The cert will be `renewed every **60** days by default` (which is configurable). Once the cert is renewed, the apache/nginx will be automatically reloaded by the command: `service apache2 reload` or `service nginx reload`. # 4. Use Standalone server to issue cert -**(requires you be root/sudoer, or you have permission to listen tcp 80 port)** +**(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** -The tcp `80` port **MUST** be free to listen, otherwise you will be prompted to free the `80` port and try again. +Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com @@ -176,13 +182,14 @@ acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert -# 5. Use Standalone tls server to issue cert -**(requires you be root/sudoer, or you have permission to listen tcp 443 port)** +# 5. Use Standalone TLS server to issue cert + +**(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** acme.sh supports `tls-sni-01` validation. -The tcp `443` port **MUST** be free to listen, otherwise you will be prompted to free the `443` port and try again. +Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. ```bash acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com @@ -190,31 +197,33 @@ acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert + # 6. Use Apache mode -**(requires you be root/sudoer, since it is required to interact with apache server)** +**(requires you to be root/sudoer, since it is required to interact with Apache server)** -If you are running a web server, apache or nginx, it is recommended to use the `Webroot mode`. +If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. -Particularly, if you are running an apache server, you should use apache mode instead. This mode doesn't write any files to your web root folder. +Particularly, if you are running an Apache server, you should use Apache mode instead. This mode doesn't write any files to your web root folder. -Just set string "apache" as the second argument, it will force use of apache plugin automatically. +Just set string "apache" as the second argument and it will force use of apache plugin automatically. ``` -acme.sh --issue --apache -d example.com -d www.example.com -d user.example.com +acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com ``` More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert + # 7. Use DNS mode: Support the `dns-01` challenge. ```bash -acme.sh --issue --dns -d example.com -d www.example.com -d user.example.com +acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com ``` -You should get the output like below: +You should get an output like below: ``` Add the following txt record: @@ -226,7 +235,6 @@ Domain:_acme-challenge.www.example.com Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx Please add those txt records to the domains. Waiting for the dns to take effect. - ``` Then just rerun with `renew` argument: @@ -237,52 +245,60 @@ acme.sh --renew -d example.com Ok, it's finished. + # 8. Automatic DNS API integration -If your DNS provider supports API access, we can use API to automatically issue the certs. +If your DNS provider supports API access, we can use that API to automatically issue the certs. -You don't have do anything manually! +You don't have to do anything manually! ### Currently acme.sh supports: -1. Cloudflare.com API -2. Dnspod.cn API -3. Cloudxns.com API -4. Godaddy.com API -5. OVH, kimsufi, soyoustart and runabove API -6. AWS Route 53, see: https://github.com/Neilpang/acme.sh/issues/65 -7. PowerDNS API -8. lexicon dns api: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api - (DigitalOcean, DNSimple, DnsMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.) -9. LuaDNS.com API -10. DNSMadeEasy.com API +1. CloudFlare.com API +1. DNSPod.cn API +1. CloudXNS.com API +1. GoDaddy.com API +1. OVH, kimsufi, soyoustart and runabove API +1. AWS Route 53 +1. PowerDNS.com API +1. lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api + (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.) +1. LuaDNS.com API +1. DNSMadeEasy.com API +1. nsupdate API +1. aliyun.com(阿里云) API +1. ISPConfig 3.1 API +1. Alwaysdata.com API +1. Linode.com API +1. FreeDNS (https://freedns.afraid.org/) -##### More APIs are coming soon... +**More APIs coming soon...** -If your DNS provider is not on the supported list above, you can write your own script API easily. If you do please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute to the project. +If your DNS provider is not on the supported list above, you can write your own DNS API script easily. If you do, please consider submitting a [Pull Request](https://github.com/Neilpang/acme.sh/pulls) and contribute it to the project. -For more details: [How to use dns api](dnsapi) +For more details: [How to use DNS API](dnsapi) -# 9. Issue ECC certificate: -`Let's Encrypt` now can issue **ECDSA** certificates. +# 9. Issue ECC certificates -And we also support it. +`Let's Encrypt` can now issue **ECDSA** certificates. + +And we support them too! Just set the `length` parameter with a prefix `ec-`. For example: -### Single domain ECC cerfiticate: +### Single domain ECC cerfiticate ```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 +acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 ``` -SAN multi domain ECC certificate: +### SAN multi domain ECC certificate ```bash -acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 +acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 ``` Please look at the last parameter above. @@ -294,40 +310,48 @@ Valid values are: 3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** -# 10. How to renew the cert +# 10. How to renew the issued certs -No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. +No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. However, you can also force to renew any cert: ``` -acme.sh --renew -d example.com --force +acme.sh --renew -d example.com --force ``` or, for ECC cert: + ``` -acme.sh --renew -d example.com --force --ecc +acme.sh --renew -d example.com --force --ecc ``` + # 11. How to upgrade `acme.sh` -acme.sh is in developing, it's strongly recommended to use the latest code. + +acme.sh is in constant development, so it's strongly recommended to use the latest code. You can update acme.sh to the latest code: + ``` acme.sh --upgrade ``` -You can enable auto upgrade: +You can also enable auto upgrade: + ``` -acme.sh --upgrade --auto-upgrade +acme.sh --upgrade --auto-upgrade ``` -Then **acme.sh** will keep up to date automatically. + +Then **acme.sh** will be kept up to date automatically. Disable auto upgrade: + ``` -acme.sh --upgrade --auto-upgrade 0 +acme.sh --upgrade --auto-upgrade 0 ``` + # 12. Issue a cert from an existing CSR https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR @@ -339,22 +363,25 @@ Speak ACME language using shell, directly to "Let's Encrypt". TODO: -# Acknowledgment + +# Acknowledgments + 1. Acme-tiny: https://github.com/diafygi/acme-tiny 2. ACME protocol: https://github.com/ietf-wg-acme/acme 3. Certbot: https://github.com/certbot/certbot + # License & Others License is GPLv3 Please Star and Fork me. -[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcomed. +[Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome. # Donate -1. PayPal: donate@acme.sh + +1. PayPal: donate@acme.sh [Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list) - diff --git a/acme.sh b/acme.sh index e5dabb3a..df4a0b4a 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=2.6.5 +VER=2.6.6 PROJECT_NAME="acme.sh" @@ -22,6 +22,8 @@ DEFAULT_ACCOUNT_EMAIL="" DEFAULT_ACCOUNT_KEY_LENGTH=2048 DEFAULT_DOMAIN_KEY_LENGTH=2048 +DEFAULT_OPENSSL_BIN="openssl" + STAGE_CA="https://acme-staging.api.letsencrypt.org" VTYPE_HTTP="http-01" @@ -85,21 +87,24 @@ __red() { } _printargs() { + if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then + printf -- "%s" "[$(date)] " + fi if [ -z "$2" ]; then - printf -- "[$(date)] $1" + printf -- "%s" "$1" else - printf -- "[$(date)] $1='$2'" + printf -- "%s" "$1='$2'" fi printf "\n" } _dlg_versions() { echo "Diagnosis versions: " - echo "openssl:" - if _exists openssl; then - openssl version 2>&1 + echo "openssl:$OPENSSL_BIN" + if _exists "$OPENSSL_BIN"; then + $OPENSSL_BIN version 2>&1 else - echo "openssl doesn't exists." + echo "$OPENSSL_BIN doesn't exists." fi echo "apache:" @@ -129,7 +134,9 @@ _info() { _err() { _log "$@" - printf -- "[$(date)] " >&2 + if [ -z "$NO_TIMESTAMP" ] || [ "$NO_TIMESTAMP" = "0" ]; then + printf -- "%s" "[$(date)] " >&2 + fi if [ -z "$2" ]; then __red "$1" >&2 else @@ -247,9 +254,12 @@ _exists() { _usage "Usage: _exists cmd" return 1 fi - if command >/dev/null 2>&1; then + + if eval type type >/dev/null 2>&1; then + eval type "$cmd" >/dev/null 2>&1 + elif command >/dev/null 2>&1; then command -v "$cmd" >/dev/null 2>&1 - elif which >/dev/null 2>&1; then + else which "$cmd" >/dev/null 2>&1 fi ret="$?" @@ -326,6 +336,262 @@ _h2b() { done } +_is_solaris() { + _contains "${__OS__:=$(uname -a)}" "solaris" || _contains "${__OS__:=$(uname -a)}" "SunOS" +} + +#_ascii_hex str +#this can only process ascii chars, should only be used when od command is missing as a backup way. +_ascii_hex() { + _debug2 "Using _ascii_hex" + _str="$1" + _str_len=${#_str} + _h_i=1 + while [ "$_h_i" -le "$_str_len" ]; do + _str_c="$(printf "%s" "$_str" | cut -c "$_h_i")" + printf " %02x" "'$_str_c" + _h_i="$(_math "$_h_i" + 1)" + done +} + +#stdin output hexstr splited by one space +#input:"abc" +#output: " 61 62 63" +_hex_dump() { + #in wired some system, the od command is missing. + if ! od -A n -v -t x1 | tr -d "\r\t" | tr -s " " | sed "s/ $//" | tr -d "\n" 2>/dev/null; then + str=$(cat) + _ascii_hex "$str" + fi +} + +#url encode, no-preserved chars +#A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +#41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 51 52 53 54 55 56 57 58 59 5a + +#a b c d e f g h i j k l m n o p q r s t u v w x y z +#61 62 63 64 65 66 67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76 77 78 79 7a + +#0 1 2 3 4 5 6 7 8 9 - _ . ~ +#30 31 32 33 34 35 36 37 38 39 2d 5f 2e 7e + +#stdin stdout +_url_encode() { + _hex_str=$(_hex_dump) + _debug3 "_url_encode" + _debug3 "_hex_str" "$_hex_str" + 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" + ;; + + #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" + ;; + #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" + ;; + esac + done +} + #options file _sed_i() { options="$1" @@ -346,10 +612,8 @@ _sed_i() { } _egrep_o() { - if _contains "$(egrep -o 2>&1)" "egrep: illegal option -- o"; then + if ! egrep -o "$1" 2>/dev/null; then sed -n 's/.*\('"$1"'\).*/\1/p' - else - egrep -o "$1" fi } @@ -385,19 +649,22 @@ _getfile() { #Usage: multiline _base64() { + [ "" ] #urgly if [ "$1" ]; then - openssl base64 -e + _debug3 "base64 multiline:'$1'" + $OPENSSL_BIN base64 -e else - openssl base64 -e | tr -d '\r\n' + _debug3 "base64 single line." + $OPENSSL_BIN base64 -e | tr -d '\r\n' fi } #Usage: multiline _dbase64() { if [ "$1" ]; then - openssl base64 -d -A + $OPENSSL_BIN base64 -d -A else - openssl base64 -d + $OPENSSL_BIN base64 -d fi } @@ -414,9 +681,9 @@ _digest() { if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ] || [ "$alg" = "md5" ]; then if [ "$outputhex" ]; then - openssl dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' ' + $OPENSSL_BIN dgst -"$alg" -hex | cut -d = -f 2 | tr -d ' ' else - openssl dgst -"$alg" -binary | _base64 + $OPENSSL_BIN dgst -"$alg" -binary | _base64 fi else _err "$alg is not supported yet" @@ -425,23 +692,23 @@ _digest() { } -#Usage: hashalg secret [outputhex] -#Output Base64-encoded hmac +#Usage: hashalg secret_hex [outputhex] +#Output binary hmac _hmac() { alg="$1" - hmac_sec="$2" + secret_hex="$2" outputhex="$3" - if [ -z "$hmac_sec" ]; then + if [ -z "$secret_hex" ]; then _usage "Usage: _hmac hashalg secret [outputhex]" return 1 fi if [ "$alg" = "sha256" ] || [ "$alg" = "sha1" ]; then if [ "$outputhex" ]; then - openssl dgst -"$alg" -hmac "$hmac_sec" | cut -d = -f 2 | tr -d ' ' + $OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" | cut -d = -f 2 | tr -d ' ' else - openssl dgst -"$alg" -hmac "$hmac_sec" -binary | _base64 + $OPENSSL_BIN dgst -"$alg" -mac HMAC -macopt "hexkey:$secret_hex" -binary fi else _err "$alg is not supported yet" @@ -460,7 +727,7 @@ _sign() { return 1 fi - _sign_openssl="openssl dgst -sign $keyfile " + _sign_openssl="$OPENSSL_BIN dgst -sign $keyfile " if [ "$alg" = "sha256" ]; then _sign_openssl="$_sign_openssl -$alg" else @@ -471,7 +738,7 @@ _sign() { if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then $_sign_openssl | _base64 elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then - if ! _signedECText="$($_sign_openssl | openssl asn1parse -inform DER)"; then + if ! _signedECText="$($_sign_openssl | $OPENSSL_BIN asn1parse -inform DER)"; then _err "Sign failed: $_sign_openssl" _err "Key file: $keyfile" _err "Key content:$(wc -l <"$keyfile") lises" @@ -509,6 +776,7 @@ _isEccKey() { _createkey() { length="$1" f="$2" + _debug2 "_createkey for file:$f" eccname="$length" if _startswith "$length" "ec-"; then length=$(printf "%s" "$length" | cut -d '-' -f 2-100) @@ -533,10 +801,10 @@ _createkey() { if _isEccKey "$length"; then _debug "Using ec name: $eccname" - openssl ecparam -name "$eccname" -genkey 2>/dev/null >"$f" + $OPENSSL_BIN ecparam -name "$eccname" -genkey 2>/dev/null >"$f" else _debug "Using RSA: $length" - openssl genrsa "$length" 2>/dev/null >"$f" + $OPENSSL_BIN genrsa "$length" 2>/dev/null >"$f" fi if [ "$?" != "0" ]; then @@ -549,7 +817,7 @@ _createkey() { _is_idn() { _is_idn_d="$1" _debug2 _is_idn_d "$_is_idn_d" - _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '[0-9]' | tr -d '[a-z]' | tr -d '[A-Z]' | tr -d '.,-') + _idn_temp=$(printf "%s" "$_is_idn_d" | tr -d '0-9' | tr -d 'a-z' | tr -d 'A-Z' | tr -d '.,-') _debug2 _idn_temp "$_idn_temp" [ "$_idn_temp" ] } @@ -614,14 +882,15 @@ _createcsr() { _info "Multi domain" "$alt" printf -- "\nsubjectAltName=$alt" >>"$csrconf" fi - if [ "$Le_OCSP_Stable" ]; then - _savedomainconf Le_OCSP_Stable "$Le_OCSP_Stable" + if [ "$Le_OCSP_Staple" ] || [ "$Le_OCSP_Stable" ]; then + _savedomainconf Le_OCSP_Staple "$Le_OCSP_Staple" + _cleardomainconf Le_OCSP_Stable printf -- "\nbasicConstraints = CA:FALSE\n1.3.6.1.5.5.7.1.24=DER:30:03:02:01:05" >>"$csrconf" fi _csr_cn="$(_idn "$domain")" _debug2 _csr_cn "$_csr_cn" - openssl req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" + $OPENSSL_BIN req -new -sha256 -key "$csrkey" -subj "/CN=$_csr_cn" -config "$csrconf" -out "$csr" } #_signcsr key csr conf cert @@ -632,7 +901,7 @@ _signcsr() { cert="$4" _debug "_signcsr" - _msg="$(openssl x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)" + _msg="$($OPENSSL_BIN x509 -req -days 365 -in "$csr" -signkey "$key" -extensions v3_req -extfile "$conf" -out "$cert" 2>&1)" _ret="$?" _debug "$_msg" return $_ret @@ -645,7 +914,7 @@ _readSubjectFromCSR() { _usage "_readSubjectFromCSR mycsr.csr" return 1 fi - openssl req -noout -in "$_csrfile" -subject | _egrep_o "CN=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n' + $OPENSSL_BIN req -noout -in "$_csrfile" -subject | _egrep_o "CN=.*" | cut -d = -f 2 | cut -d / -f 1 | tr -d '\n' } #_csrfile @@ -660,7 +929,7 @@ _readSubjectAltNamesFromCSR() { _csrsubj="$(_readSubjectFromCSR "$_csrfile")" _debug _csrsubj "$_csrsubj" - _dnsAltnames="$(openssl req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')" + _dnsAltnames="$($OPENSSL_BIN req -noout -text -in "$_csrfile" | grep "^ *DNS:.*" | tr -d ' \n')" _debug _dnsAltnames "$_dnsAltnames" if _contains "$_dnsAltnames," "DNS:$_csrsubj,"; then @@ -681,7 +950,7 @@ _readKeyLengthFromCSR() { return 1 fi - _outcsr="$(openssl req -noout -text -in "$_csrfile")" + _outcsr="$($OPENSSL_BIN req -noout -text -in "$_csrfile")" if _contains "$_outcsr" "Public Key Algorithm: id-ecPublicKey"; then _debug "ECC CSR" echo "$_outcsr" | _egrep_o "^ *ASN1 OID:.*" | cut -d ':' -f 2 | tr -d ' ' @@ -735,9 +1004,9 @@ toPkcs() { _initpath "$domain" "$_isEcc" if [ "$pfxPassword" ]; then - openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword" + $OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" -password "pass:$pfxPassword" else - openssl pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" + $OPENSSL_BIN pkcs12 -export -out "$CERT_PFX_PATH" -inkey "$CERT_KEY_PATH" -in "$CERT_PATH" -certfile "$CA_CERT_PATH" fi if [ "$?" = "0" ]; then @@ -843,7 +1112,7 @@ createCSR() { } -_urlencode() { +_url_replace() { tr '/+' '_-' | tr -d '= ' } @@ -899,7 +1168,7 @@ _calcjwk() { if grep "BEGIN RSA PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then _debug "RSA key" - pub_exp=$(openssl rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1) + pub_exp=$($OPENSSL_BIN rsa -in "$keyfile" -noout -text | grep "^publicExponent:" | cut -d '(' -f 2 | cut -d 'x' -f 2 | cut -d ')' -f 1) if [ "${#pub_exp}" = "5" ]; then pub_exp=0$pub_exp fi @@ -908,9 +1177,11 @@ _calcjwk() { e=$(echo "$pub_exp" | _h2b | _base64) _debug3 e "$e" - modulus=$(openssl rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2) + modulus=$($OPENSSL_BIN rsa -in "$keyfile" -modulus -noout | cut -d '=' -f 2) _debug3 modulus "$modulus" - n="$(printf "%s" "$modulus" | _h2b | _base64 | _urlencode)" + n="$(printf "%s" "$modulus" | _h2b | _base64 | _url_replace)" + _debug3 n "$n" + jwk='{"e": "'$e'", "kty": "RSA", "n": "'$n'"}' _debug3 jwk "$jwk" @@ -919,12 +1190,12 @@ _calcjwk() { JWK_HEADERPLACE_PART2='", "alg": "RS256", "jwk": '$jwk'}' elif grep "BEGIN EC PRIVATE KEY" "$keyfile" >/dev/null 2>&1; then _debug "EC key" - crv="$(openssl ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")" + crv="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep "^NIST CURVE:" | cut -d ":" -f 2 | tr -d " \r\n")" _debug3 crv "$crv" if [ -z "$crv" ]; then _debug "Let's try ASN1 OID" - crv_oid="$(openssl ec -in "$keyfile" -noout -text 2>/dev/null | grep "^ASN1 OID:" | cut -d ":" -f 2 | tr -d " \r\n")" + crv_oid="$($OPENSSL_BIN 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") @@ -944,15 +1215,15 @@ _calcjwk() { _debug3 crv "$crv" fi - pubi="$(openssl ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)" + pubi="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n pub: | cut -d : -f 1)" pubi=$(_math "$pubi" + 1) _debug3 pubi "$pubi" - pubj="$(openssl ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)" + pubj="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | grep -n "ASN1 OID:" | cut -d : -f 1)" pubj=$(_math "$pubj" - 1) _debug3 pubj "$pubj" - pubtext="$(openssl ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")" + pubtext="$($OPENSSL_BIN ec -in "$keyfile" -noout -text 2>/dev/null | sed -n "$pubi,${pubj}p" | tr -d " \n\r")" _debug3 pubtext "$pubtext" xlen="$(printf "%s" "$pubtext" | tr -d ':' | wc -c)" @@ -963,14 +1234,14 @@ _calcjwk() { x="$(printf "%s" "$pubtext" | cut -d : -f 2-"$xend")" _debug3 x "$x" - x64="$(printf "%s" "$x" | tr -d : | _h2b | _base64 | _urlencode)" + x64="$(printf "%s" "$x" | tr -d : | _h2b | _base64 | _url_replace)" _debug3 x64 "$x64" xend=$(_math "$xend" + 1) y="$(printf "%s" "$pubtext" | cut -d : -f "$xend"-10000)" _debug3 y "$y" - y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _urlencode)" + y64="$(printf "%s" "$y" | tr -d : | _h2b | _base64 | _url_replace)" _debug3 y64 "$y64" jwk='{"crv": "'$crv'", "kty": "EC", "x": "'$x64'", "y": "'$y64'"}' @@ -1036,9 +1307,6 @@ _inithttp() { _ACME_CURL="$_ACME_CURL --cacert $CA_BUNDLE " fi - if [ "$HTTPS_INSECURE" ]; then - _ACME_CURL="$_ACME_CURL --insecure " - fi fi if [ -z "$_ACME_WGET" ] && _exists "wget"; then @@ -1049,9 +1317,6 @@ _inithttp() { if [ "$CA_BUNDLE" ]; then _ACME_WGET="$_ACME_WGET --ca-certificate $CA_BUNDLE " fi - if [ "$HTTPS_INSECURE" ]; then - _ACME_WGET="$_ACME_WGET --no-check-certificate " - fi fi __HTTP_INITIALIZED=1 @@ -1076,6 +1341,9 @@ _post() { if [ "$_ACME_CURL" ]; then _CURL="$_ACME_CURL" + if [ "$HTTPS_INSECURE" ]; then + _CURL="$_CURL --insecure " + fi _debug "_CURL" "$_CURL" if [ "$needbase64" ]; then response="$($_CURL --user-agent "$USER_AGENT" -X $httpmethod -H "$_H1" -H "$_H2" -H "$_H3" -H "$_H4" -H "$_H5" --data "$body" "$url" | _base64)" @@ -1091,18 +1359,22 @@ _post() { fi fi elif [ "$_ACME_WGET" ]; then - _debug "_ACME_WGET" "$_ACME_WGET" + _WGET="$_ACME_WGET" + if [ "$HTTPS_INSECURE" ]; then + _WGET="$_WGET --no-check-certificate " + fi + _debug "_WGET" "$_WGET" if [ "$needbase64" ]; then if [ "$httpmethod" = "POST" ]; then - response="$($_ACME_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$url" 2>"$HTTP_HEADER" | _base64)" + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$url" 2>"$HTTP_HEADER" | _base64)" else - response="$($_ACME_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$url" 2>"$HTTP_HEADER" | _base64)" + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$url" 2>"$HTTP_HEADER" | _base64)" fi else if [ "$httpmethod" = "POST" ]; then - response="$($_ACME_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$url" 2>"$HTTP_HEADER")" + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --post-data="$body" "$url" 2>"$HTTP_HEADER")" else - response="$($_ACME_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$url" 2>"$HTTP_HEADER")" + response="$($_WGET -S -O - --user-agent="$USER_AGENT" --header "$_H5" --header "$_H4" --header "$_H3" --header "$_H2" --header "$_H1" --method $httpmethod --body-data="$body" "$url" 2>"$HTTP_HEADER")" fi fi _ret="$?" @@ -1136,6 +1408,9 @@ _get() { if [ "$_ACME_CURL" ]; then _CURL="$_ACME_CURL" + if [ "$HTTPS_INSECURE" ]; then + _CURL="$_CURL --insecure " + fi if [ "$t" ]; then _CURL="$_CURL --connect-timeout $t" fi @@ -1155,6 +1430,9 @@ _get() { fi elif [ "$_ACME_WGET" ]; then _WGET="$_ACME_WGET" + if [ "$HTTPS_INSECURE" ]; then + _WGET="$_WGET --no-check-certificate " + fi if [ "$t" ]; then _WGET="$_WGET --timeout=$t" fi @@ -1207,7 +1485,7 @@ _send_signed_request() { return 1 fi - payload64=$(printf "%s" "$payload" | _base64 | _urlencode) + payload64=$(printf "%s" "$payload" | _base64 | _url_replace) _debug3 payload64 "$payload64" if [ -z "$_CACHED_NONCE" ]; then @@ -1233,7 +1511,7 @@ _send_signed_request() { protected="$JWK_HEADERPLACE_PART1$nonce$JWK_HEADERPLACE_PART2" _debug3 protected "$protected" - protected64="$(printf "%s" "$protected" | _base64 | _urlencode)" + protected64="$(printf "%s" "$protected" | _base64 | _url_replace)" _debug3 protected64 "$protected64" if ! _sig_t="$(printf "%s" "$protected64.$payload64" | _sign "$keyfile" "sha256")"; then @@ -1242,7 +1520,7 @@ _send_signed_request() { fi _debug3 _sig_t "$_sig_t" - sig="$(printf "%s" "$_sig_t" | _urlencode)" + sig="$(printf "%s" "$_sig_t" | _url_replace)" _debug3 sig "$sig" body="{\"header\": $JWK_HEADER, \"protected\": \"$protected64\", \"payload\": \"$payload64\", \"signature\": \"$sig\"}" @@ -1325,7 +1603,7 @@ _clear_conf() { _sdkey="$2" if [ "$_c_c_f" ]; then _conf_data="$(cat "$_c_c_f")" - echo "$_conf_data" | sed "s/^$_sdkey *=.*$//" > "$_c_c_f" + echo "$_conf_data" | sed "s/^$_sdkey *=.*$//" >"$_c_c_f" else _err "config file is empty, can not clear" fi @@ -1423,32 +1701,29 @@ _startserver() { #for centos ncat if _contains "$nchelp" "nmap.org"; then _debug "Using ncat: nmap.org" - if [ "$DEBUG" ]; then - if printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $_NC "$Le_HTTPPort"; then - return - fi - else - if printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $_NC "$Le_HTTPPort" >/dev/null 2>&1; then - return - fi + if ! _exec "printf \"%s\r\n\r\n%s\" \"HTTP/1.1 200 OK\" \"$content\" | $_NC \"$Le_HTTPPort\" >&2"; then + _exec_err + return 1 fi - _err "ncat listen error." + if [ "$DEBUG" ]; then + _exec_err + fi + return fi # while true ; do - if [ "$DEBUG" ]; then - if ! printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $_NC -p "$Le_HTTPPort"; then - printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $_NC "$Le_HTTPPort" - fi - else - if ! printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $_NC -p "$Le_HTTPPort" >/dev/null 2>&1; then - printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $_NC "$Le_HTTPPort" >/dev/null 2>&1 - fi + if ! _exec "printf \"%s\r\n\r\n%s\" \"HTTP/1.1 200 OK\" \"$content\" | $_NC -p \"$Le_HTTPPort\" >&2"; then + _exec "printf \"%s\r\n\r\n%s\" \"HTTP/1.1 200 OK\" \"$content\" | $_NC \"$Le_HTTPPort\" >&2" fi + if [ "$?" != "0" ]; then _err "nc listen error." + _exec_err exit 1 fi + if [ "$DEBUG" ]; then + _exec_err + fi # done } @@ -1532,7 +1807,7 @@ _starttlsserver() { return 1 fi - __S_OPENSSL="openssl s_server -cert $TLS_CERT -key $TLS_KEY " + __S_OPENSSL="$OPENSSL_BIN s_server -cert $TLS_CERT -key $TLS_KEY " if [ "$opaddr" ]; then __S_OPENSSL="$__S_OPENSSL -accept $opaddr:$port" else @@ -1547,7 +1822,6 @@ _starttlsserver() { __S_OPENSSL="$__S_OPENSSL -6" fi - #start openssl _debug "$__S_OPENSSL" if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then (printf "%s\r\n\r\n%s" "HTTP/1.1 200 OK" "$content" | $__S_OPENSSL -tlsextdebug) & @@ -1564,14 +1838,18 @@ _starttlsserver() { _readlink() { _rf="$1" if ! readlink -f "$_rf" 2>/dev/null; then - if _startswith "$_rf" "\./$PROJECT_ENTRY"; then - printf -- "%s" "$(pwd)/$PROJECT_ENTRY" + if _startswith "$_rf" "/"; then + echo "$_rf" return 0 fi - readlink "$_rf" + echo "$(pwd)/$_rf" | _conapath fi } +_conapath() { + sed "s#/\./#/#g" +} + __initHome() { if [ -z "$_SCRIPT_HOME" ]; then if _exists readlink && _exists dirname; then @@ -1589,14 +1867,14 @@ __initHome() { fi fi - if [ -z "$LE_WORKING_DIR" ]; then - if [ -f "$DEFAULT_INSTALL_HOME/account.conf" ]; then - _debug "It seems that $PROJECT_NAME is already installed in $DEFAULT_INSTALL_HOME" - LE_WORKING_DIR="$DEFAULT_INSTALL_HOME" - else - LE_WORKING_DIR="$_SCRIPT_HOME" - fi - fi + # if [ -z "$LE_WORKING_DIR" ]; then + # if [ -f "$DEFAULT_INSTALL_HOME/account.conf" ]; then + # _debug "It seems that $PROJECT_NAME is already installed in $DEFAULT_INSTALL_HOME" + # LE_WORKING_DIR="$DEFAULT_INSTALL_HOME" + # else + # LE_WORKING_DIR="$_SCRIPT_HOME" + # fi + # fi if [ -z "$LE_WORKING_DIR" ]; then _debug "Using default home:$DEFAULT_INSTALL_HOME" @@ -1604,7 +1882,13 @@ __initHome() { fi export LE_WORKING_DIR - _DEFAULT_ACCOUNT_CONF_PATH="$LE_WORKING_DIR/account.conf" + if [ -z "$LE_CONFIG_HOME" ]; then + LE_CONFIG_HOME="$LE_WORKING_DIR" + fi + _debug "Using config home:$LE_CONFIG_HOME" + export LE_CONFIG_HOME + + _DEFAULT_ACCOUNT_CONF_PATH="$LE_CONFIG_HOME/account.conf" if [ -z "$ACCOUNT_CONF_PATH" ]; then if [ -f "$_DEFAULT_ACCOUNT_CONF_PATH" ]; then @@ -1616,12 +1900,12 @@ __initHome() { ACCOUNT_CONF_PATH="$_DEFAULT_ACCOUNT_CONF_PATH" fi - DEFAULT_LOG_FILE="$LE_WORKING_DIR/$PROJECT_NAME.log" + DEFAULT_LOG_FILE="$LE_CONFIG_HOME/$PROJECT_NAME.log" - DEFAULT_CA_HOME="$LE_WORKING_DIR/ca" + DEFAULT_CA_HOME="$LE_CONFIG_HOME/ca" if [ -z "$LE_TEMP_DIR" ]; then - LE_TEMP_DIR="$LE_WORKING_DIR/tmp" + LE_TEMP_DIR="$LE_CONFIG_HOME/tmp" fi } @@ -1662,6 +1946,7 @@ _initpath() { if [ -z "$CA_CONF" ]; then CA_CONF="$_DEFAULT_CA_CONF" fi + _debug3 CA_CONF "$CA_CONF" if [ -f "$CA_CONF" ]; then . "$CA_CONF" @@ -1672,7 +1957,7 @@ _initpath() { fi if [ -z "$APACHE_CONF_BACKUP_DIR" ]; then - APACHE_CONF_BACKUP_DIR="$LE_WORKING_DIR" + APACHE_CONF_BACKUP_DIR="$LE_CONFIG_HOME" fi if [ -z "$USER_AGENT" ]; then @@ -1680,7 +1965,7 @@ _initpath() { fi if [ -z "$HTTP_HEADER" ]; then - HTTP_HEADER="$LE_WORKING_DIR/http.header" + HTTP_HEADER="$LE_CONFIG_HOME/http.header" fi _OLD_ACCOUNT_KEY="$LE_WORKING_DIR/account.key" @@ -1696,17 +1981,19 @@ _initpath() { ACCOUNT_JSON_PATH="$_DEFAULT_ACCOUNT_JSON_PATH" fi - _DEFAULT_CERT_HOME="$LE_WORKING_DIR" + _DEFAULT_CERT_HOME="$LE_CONFIG_HOME" if [ -z "$CERT_HOME" ]; then CERT_HOME="$_DEFAULT_CERT_HOME" fi + if [ -z "$OPENSSL_BIN" ]; then + OPENSSL_BIN="$DEFAULT_OPENSSL_BIN" + fi + if [ -z "$1" ]; then return 0 fi - mkdir -p "$CA_DIR" - domain="$1" _ilength="$2" @@ -1726,13 +2013,6 @@ _initpath() { _debug DOMAIN_PATH "$DOMAIN_PATH" fi - if [ ! -d "$DOMAIN_PATH" ]; then - if ! mkdir -p "$DOMAIN_PATH"; then - _err "Can not create domain path: $DOMAIN_PATH" - return 1 - fi - fi - if [ -z "$DOMAIN_CONF" ]; then DOMAIN_CONF="$DOMAIN_PATH/$domain.conf" fi @@ -1781,14 +2061,14 @@ _exec() { fi if [ "$_EXEC_TEMP_ERR" ]; then - "$@" 2>"$_EXEC_TEMP_ERR" + eval "$@ 2>>$_EXEC_TEMP_ERR" else - "$@" + eval "$@" fi } _exec_err() { - [ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" + [ "$_EXEC_TEMP_ERR" ] && _err "$(cat "$_EXEC_TEMP_ERR")" && echo "" >"$_EXEC_TEMP_ERR" } _apachePath() { @@ -1893,7 +2173,7 @@ _setApache() { fi _info "JFYI, Config file $httpdconf is backuped to $APACHE_CONF_BACKUP_DIR/$httpdconfname" _info "In case there is an error that can not be restored automatically, you may try restore it yourself." - _info "The backup file will be deleted on sucess, just forget it." + _info "The backup file will be deleted on success, just forget it." #add alias @@ -1973,7 +2253,8 @@ _clearupdns() { keyauthorization=$(echo "$ventry" | cut -d "$sep" -f 2) vtype=$(echo "$ventry" | cut -d "$sep" -f 4) _currentRoot=$(echo "$ventry" | cut -d "$sep" -f 5) - + txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)" + _debug txt "$txt" if [ "$keyauthorization" = "$STATE_VERIFIED" ]; then _info "$d is already verified, skip $vtype." continue @@ -2006,7 +2287,7 @@ _clearupdns() { txtdomain="_acme-challenge.$d" - if ! $rmcommand "$txtdomain"; then + if ! $rmcommand "$txtdomain" "$txt"; then _err "Error removing txt for domain:$txtdomain" return 1 fi @@ -2048,6 +2329,17 @@ _clearupwebbroot() { _on_before_issue() { _debug _on_before_issue + #run pre hook + if [ "$Le_PreHook" ]; then + _info "Run pre hook:'$Le_PreHook'" + if ! ( + cd "$DOMAIN_PATH" && eval "$Le_PreHook" + ); then + _err "Error when run pre hook." + return 1 + fi + fi + if _hasfield "$Le_Webroot" "$NO_VALUE"; then if ! _exists "nc"; then _err "Please install netcat(nc) tools first." @@ -2115,16 +2407,6 @@ _on_before_issue() { usingApache="" fi - #run pre hook - if [ "$Le_PreHook" ]; then - _info "Run pre hook:'$Le_PreHook'" - if ! ( - cd "$DOMAIN_PATH" && eval "$Le_PreHook" - ); then - _err "Error when run pre hook." - return 1 - fi - fi } _on_issue_err() { @@ -2132,7 +2414,7 @@ _on_issue_err() { if [ "$LOG_FILE" ]; then _err "Please check log file for more details: $LOG_FILE" else - _err "Please use add '--debug' or '--log' to check more details." + _err "Please add '--debug' or '--log' to check more details." _err "See: $_DEBUG_WIKI" fi @@ -2199,11 +2481,13 @@ _regAccount() { _reg_length="$1" if [ ! -f "$ACCOUNT_KEY_PATH" ] && [ -f "$_OLD_ACCOUNT_KEY" ]; then + mkdir -p "$CA_DIR" _info "mv $_OLD_ACCOUNT_KEY to $ACCOUNT_KEY_PATH" mv "$_OLD_ACCOUNT_KEY" "$ACCOUNT_KEY_PATH" fi if [ ! -f "$ACCOUNT_JSON_PATH" ] && [ -f "$_OLD_ACCOUNT_JSON" ]; then + mkdir -p "$CA_DIR" _info "mv $_OLD_ACCOUNT_JSON to $ACCOUNT_JSON_PATH" mv "$_OLD_ACCOUNT_JSON" "$ACCOUNT_JSON_PATH" fi @@ -2326,6 +2610,12 @@ __get_domain_new_authz() { _err "Can not get domain new authz." return 1 fi + if _contains "$response" "No registration exists matching provided key"; then + _err "It seems there is an error, but it's recovered now, please try again." + _err "If you see this message for a second time, please report bug: $(__green "$PROJECT")" + _clearcaconf "CA_KEY_HASH" + break + fi if ! _contains "$response" "An error occurred while processing your request"; then _info "The new-authz request is ok." break @@ -2355,6 +2645,10 @@ issue() { Le_Webroot="$1" Le_Domain="$2" Le_Alt="$3" + if _contains "$Le_Domain" ","; then + Le_Domain=$(echo "$2,$3" | cut -d , -f 1) + Le_Alt=$(echo "$2,$3" | cut -d , -f 2- | sed "s/,${NO_VALUE}$//") + fi Le_Keylength="$4" Le_RealCertPath="$5" Le_RealKeyPath="$6" @@ -2503,7 +2797,7 @@ issue() { if [ -z "$thumbprint" ]; then accountkey_json=$(printf "%s" "$jwk" | tr -d ' ') - thumbprint=$(printf "%s" "$accountkey_json" | _digest "sha256" | _urlencode) + thumbprint=$(printf "%s" "$accountkey_json" | _digest "sha256" | _url_replace) fi entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"type":"'$vtype'"[^\}]*')" @@ -2554,7 +2848,7 @@ issue() { dnsadded='0' txtdomain="_acme-challenge.$d" _debug txtdomain "$txtdomain" - txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _urlencode)" + txt="$(printf "%s" "$keyauthorization" | _digest "sha256" | _url_replace)" _debug txt "$txt" d_api="$(_findHook "$d" dnsapi "$_currentRoot")" @@ -2784,7 +3078,7 @@ issue() { status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"') if [ "$status" = "valid" ]; then - _info "Success" + _info "$(__green Success)" _stopserver "$serverproc" serverproc="" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" @@ -2829,7 +3123,7 @@ issue() { _clearup _info "Verify finished, start to sign." - der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _urlencode)" + der="$(_getfile "${CSR_PATH}" "${BEGIN_CSR}" "${END_CSR}" | tr -d "\r\n" | _url_replace)" if ! _send_signed_request "$API/acme/new-cert" "{\"resource\": \"new-cert\", \"csr\": \"$der\"}" "needbase64"; then _err "Sign failed." @@ -2971,6 +3265,12 @@ renew() { if [ "$Le_API" ]; then API="$Le_API" + #reload ca configs + ACCOUNT_KEY_PATH="" + ACCOUNT_JSON_PATH="" + CA_CONF="" + _debug3 "initpath again." + _initpath "$Le_Domain" "$_isEcc" fi if [ -z "$FORCE" ] && [ "$Le_NextRenewTime" ] && [ "$(_time)" -lt "$Le_NextRenewTime" ]; then @@ -3005,6 +3305,10 @@ renewAll() { for di in "${CERT_HOME}"/*.*/; do _debug di "$di" + if ! [ -d "$di" ]; then + _debug "Not directory, skip: $di" + continue + fi d=$(basename "$di") _debug d "$d" ( @@ -3127,6 +3431,10 @@ list() { if [ "$_raw" ]; then printf "%s\n" "Main_Domain${_sep}KeyLength${_sep}SAN_Domains${_sep}Created${_sep}Renew" for di in "${CERT_HOME}"/*.*/; do + if ! [ -d "$di" ]; then + _debug "Not directory, skip: $di" + continue + fi d=$(basename "$di") _debug d "$d" ( @@ -3289,9 +3597,14 @@ _installcert() { fi if [ "$Le_ReloadCmd" ]; then - _info "Run Le_ReloadCmd: $Le_ReloadCmd" - if (cd "$DOMAIN_PATH" && eval "$Le_ReloadCmd"); then + if ( + export CERT_PATH + export CERT_KEY_PATH + export CA_CERT_PATH + export CERT_FULLCHAIN_PATH + cd "$DOMAIN_PATH" && eval "$Le_ReloadCmd" + ); then _info "$(__green "Reload success")" else _err "Reload error for :$Le_Domain" @@ -3300,7 +3613,9 @@ _installcert() { } +#confighome installcronjob() { + _c_home="$1" _initpath if ! _exists "crontab"; then _err "crontab doesn't exist, so, we can not install cron jobs." @@ -3317,15 +3632,21 @@ installcronjob() { _err "Can not install cronjob, $PROJECT_ENTRY not found." return 1 fi - if _exists uname && uname -a | grep solaris >/dev/null; then + + if [ "$_c_home" ]; then + _c_entry="--config-home \"$_c_home\" " + fi + _t=$(_time) + random_minute=$(_math $_t % 60) + if _exists uname && uname -a | grep SunOS >/dev/null; then crontab -l | { cat - echo "0 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null" + echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" } | crontab -- else crontab -l | { cat - echo "0 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" > /dev/null" + echo "$random_minute 0 * * * $lesh --cron --home \"$LE_WORKING_DIR\" $_c_entry> /dev/null" } | crontab - fi fi @@ -3351,6 +3672,10 @@ uninstallcronjob() { fi LE_WORKING_DIR="$(echo "$cr" | cut -d ' ' -f 9 | tr -d '"')" _info LE_WORKING_DIR "$LE_WORKING_DIR" + if _contains "$cr" "--config-home"; then + LE_CONFIG_HOME="$(echo "$cr" | cut -d ' ' -f 11 | tr -d '"')" + _debug LE_CONFIG_HOME "$LE_CONFIG_HOME" + fi fi _initpath @@ -3359,7 +3684,7 @@ uninstallcronjob() { revoke() { Le_Domain="$1" if [ -z "$Le_Domain" ]; then - _usage "Usage: $PROJECT_ENTRY --revoke -d domain.com" + _usage "Usage: $PROJECT_ENTRY --revoke -d domain.com [--ecc]" return 1 fi @@ -3376,7 +3701,7 @@ revoke() { return 1 fi - cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _urlencode)" + cert="$(_getfile "${CERT_PATH}" "${BEGIN_CERT}" "${END_CERT}" | tr -d "\r\n" | _url_replace)" if [ -z "$cert" ]; then _err "Cert for $Le_Domain is empty found, skip." @@ -3417,6 +3742,37 @@ revoke() { return 1 } +#domain ecc +remove() { + Le_Domain="$1" + if [ -z "$Le_Domain" ]; then + _usage "Usage: $PROJECT_ENTRY --remove -d domain.com [--ecc]" + return 1 + fi + + _isEcc="$2" + + _initpath "$Le_Domain" "$_isEcc" + _removed_conf="$DOMAIN_CONF.removed" + if [ ! -f "$DOMAIN_CONF" ]; then + if [ -f "$_removed_conf" ]; then + _err "$Le_Domain is already removed, You can remove the folder by yourself: $DOMAIN_PATH" + else + _err "$Le_Domain is not a issued domain, skip." + fi + return 1 + fi + + if mv "$DOMAIN_CONF" "$_removed_conf"; then + _info "$Le_Domain is removed, the key and cert files are in $(__green $DOMAIN_PATH)" + _info "You can remove them by yourself." + return 0 + else + _err "Remove $Le_Domain failed." + return 1 + fi +} + #domain vtype _deactivate() { _d_domain="$1" @@ -3442,7 +3798,7 @@ _deactivate() { return 1 fi - entry="$(printf "%s\n" "$response" | _egrep_o '[^\{]*"status":"valid","uri"[^\}]*')" + entry="$(printf "%s\n" "$response" | _egrep_o '{"type":"[^"]*","status":"valid","uri"[^}]*')" _debug entry "$entry" if [ -z "$entry" ]; then @@ -3542,62 +3898,23 @@ _initconf() { if [ ! -f "$ACCOUNT_CONF_PATH" ]; then echo "#ACCOUNT_CONF_PATH=xxxx -#Account configurations: -#Here are the supported macros, uncomment them to make them take effect. - #ACCOUNT_EMAIL=aaa@example.com # the account email used to register account. #ACCOUNT_KEY_PATH=\"/path/to/account.key\" #CERT_HOME=\"/path/to/cert/home\" - #LOG_FILE=\"$DEFAULT_LOG_FILE\" #LOG_LEVEL=1 #AUTO_UPGRADE=\"1\" -#STAGE=1 # Use the staging api -#FORCE=1 # Force to issue cert -#DEBUG=1 # Debug mode - +#NO_TIMESTAMP=1 +#OPENSSL_BIN=openssl #USER_AGENT=\"$USER_AGENT\" #USER_PATH= -#dns api -####################### -#Cloudflare: -#api key -#CF_Key=\"sdfsdfsdfljlbjkljlkjsdfoiwje\" -#account email -#CF_Email=\"xxxx@sss.com\" - -####################### -#Dnspod.cn: -#api key id -#DP_Id=\"1234\" -#api key -#DP_Key=\"sADDsdasdgdsf\" - -####################### -#Cloudxns.com: -#CX_Key=\"1234\" -# -#CX_Secret=\"sADDsdasdgdsf\" - -####################### -#Godaddy.com: -#GD_Key=\"sdfdsgdgdfdasfds\" -# -#GD_Secret=\"sADDsdasdfsdfdssdgdsf\" - -####################### -#PowerDNS: -#PDNS_Url=\"http://ns.example.com:8081\" -#PDNS_ServerId=\"localhost\" -#PDNS_Token=\"0123456789ABCDEF\" -#PDNS_Ttl=60 " >"$ACCOUNT_CONF_PATH" fi @@ -3625,7 +3942,7 @@ _precheck() { fi fi - if ! _exists "openssl"; then + if ! _exists "$OPENSSL_BIN"; then _err "Please install openssl first." _err "We need openssl to generate keys." return 1 @@ -3653,7 +3970,9 @@ _setShebang() { rm -f "$_file.tmp" } +#confighome _installalias() { + _c_home="$1" _initpath _envfile="$LE_WORKING_DIR/$PROJECT_ENTRY.env" @@ -3663,8 +3982,15 @@ _installalias() { echo "$(cat "$_envfile")" | sed "s|^alias le.sh.*$||" >"$_envfile" fi + if [ "$_c_home" ]; then + _c_entry=" --config-home '$_c_home'" + fi + _setopt "$_envfile" "export LE_WORKING_DIR" "=" "\"$LE_WORKING_DIR\"" - _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY\"" + if [ "$_c_home" ]; then + _setopt "$_envfile" "export LE_CONFIG_HOME" "=" "\"$LE_CONFIG_HOME\"" + fi + _setopt "$_envfile" "alias $PROJECT_ENTRY" "=" "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _profile="$(_detect_profile)" if [ "$_profile" ]; then @@ -3682,7 +4008,10 @@ _installalias() { if [ -f "$_csh_profile" ]; then _info "Installing alias to '$_csh_profile'" _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\"" - _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY\"" + if [ "$_c_home" ]; then + _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\"" + fi + _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_csh_profile" "source \"$_cshfile\"" fi @@ -3691,13 +4020,16 @@ _installalias() { if [ -f "$_tcsh_profile" ]; then _info "Installing alias to '$_tcsh_profile'" _setopt "$_cshfile" "setenv LE_WORKING_DIR" " " "\"$LE_WORKING_DIR\"" - _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY\"" + if [ "$_c_home" ]; then + _setopt "$_cshfile" "setenv LE_CONFIG_HOME" " " "\"$LE_CONFIG_HOME\"" + fi + _setopt "$_cshfile" "alias $PROJECT_ENTRY" " " "\"$LE_WORKING_DIR/$PROJECT_ENTRY$_c_entry\"" _setopt "$_tcsh_profile" "source \"$_cshfile\"" fi } -# nocron +# nocron confighome install() { if [ -z "$LE_WORKING_DIR" ]; then @@ -3705,6 +4037,7 @@ install() { fi _nocron="$1" + _c_home="$2" if ! _initpath; then _err "Install failed." return 1 @@ -3743,6 +4076,13 @@ install() { chmod 700 "$LE_WORKING_DIR" + if ! mkdir -p "$LE_CONFIG_HOME"; then + _err "Can not create config dir: $LE_CONFIG_HOME" + return 1 + fi + + chmod 700 "$LE_CONFIG_HOME" + cp "$PROJECT_ENTRY" "$LE_WORKING_DIR/" && chmod +x "$LE_WORKING_DIR/$PROJECT_ENTRY" if [ "$?" != "0" ]; then @@ -3752,7 +4092,7 @@ install() { _info "Installed to $LE_WORKING_DIR/$PROJECT_ENTRY" - _installalias + _installalias "$_c_home" for subf in $_SUB_FOLDERS; do if [ -d "$subf" ]; then @@ -3778,13 +4118,13 @@ install() { fi if [ -z "$_nocron" ]; then - installcronjob + installcronjob "$_c_home" fi if [ -z "$NO_DETECT_SH" ]; then #Modify shebang if _exists bash; then - _info "Good, bash is found, so change the shebang to use bash as prefered." + _info "Good, bash is found, so change the shebang to use bash as preferred." _shebang='#!/usr/bin/env bash' _setShebang "$LE_WORKING_DIR/$PROJECT_ENTRY" "$_shebang" for subf in $_SUB_FOLDERS; do @@ -3811,7 +4151,7 @@ uninstall() { _uninstallalias rm -f "$LE_WORKING_DIR/$PROJECT_ENTRY" - _info "The keys and certs are in $LE_WORKING_DIR, you can remove them by yourself." + _info "The keys and certs are in \"$(__green "$LE_CONFIG_HOME")\", you can remove them by yourself." } @@ -3884,18 +4224,19 @@ Commands: --issue Issue a cert. --signcsr Issue a cert from an existing csr. --deploy Deploy the cert to your server. - --installcert Install the issued cert to apache/nginx or any other server. + --install-cert Install the issued cert to apache/nginx or any other server. --renew, -r Renew a cert. - --renewAll Renew all the certs. + --renew-all Renew all the certs. --revoke Revoke a cert. + --remove Remove the cert from $PROJECT --list List all the certs. --showcsr Show the content of a csr. - --installcronjob Install the cron job to renew certs, you don't need to call this. The 'install' command can automatically install the cron job. - --uninstallcronjob Uninstall the cron job. The 'uninstall' command can do this automatically. + --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. - --updateaccount Update account info. - --registeraccount Register account key. + --update-account Update account info. + --register-account Register account key. --createAccountKey, -cak Create an account private key, professional use. --createDomainKey, -cdk Create an domain private key, professional use. --createCSR, -ccsr Create CSR , professional use. @@ -3930,7 +4271,8 @@ Parameters: --accountconf Specifies a customized account config file. --home Specifies the home dir for $PROJECT_NAME . - --certhome Specifies the home dir to save all the certs, only valid for '--install' command. + --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 for registering, Only valid for the '--install' command. --accountkey Specifies the account key path, Only valid for the '--install' command. @@ -3939,11 +4281,11 @@ Parameters: --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 '--renewall' command. Stop if one cert has error in renewal. + --stopRenewOnError, -se 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 Specifices the path to the CA certificate bundle to verify api server's certificate. --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. - --ecc Specifies to use the ECC cert. Valid for '--installcert', '--renew', '--revoke', '--toPkcs' and '--createCSR' + --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 obain/renew is success or failed. @@ -3953,6 +4295,7 @@ Parameters: --auto-upgrade [0|1] Valid for '--upgrade' command, indicating whether to upgrade automatically in future. --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. " } @@ -3973,7 +4316,10 @@ _installOnline() { fi ( _info "Extracting $localname" - tar xzf $localname + if ! (tar xzf $localname || gtar xzf $localname); then + _err "Extraction error." + exit 1 + fi cd "$PROJECT_NAME-$BRANCH" chmod +x $PROJECT_ENTRY @@ -4016,6 +4362,12 @@ _processAccountConf() { _saveaccountconf "ACCOUNT_EMAIL" "$ACCOUNT_EMAIL" fi + if [ "$_openssl_bin" ]; then + _saveaccountconf "OPENSSL_BIN" "$_openssl_bin" + elif [ "$OPENSSL_BIN" ] && [ "$OPENSSL_BIN" != "$DEFAULT_OPENSSL_BIN" ]; then + _saveaccountconf "OPENSSL_BIN" "$OPENSSL_BIN" + fi + if [ "$_auto_upgrade" ]; then _saveaccountconf "AUTO_UPGRADE" "$_auto_upgrade" elif [ "$AUTO_UPGRADE" ]; then @@ -4042,6 +4394,7 @@ _process() { _accountemail="" _accountkey="" _certhome="" + _confighome="" _httpport="" _tlsport="" _dnssleep="" @@ -4063,6 +4416,7 @@ _process() { _auto_upgrade="" _listen_v4="" _listen_v6="" + _openssl_bin="" while [ ${#} -gt 0 ]; do case "${1}" in @@ -4095,25 +4449,28 @@ _process() { --showcsr) _CMD="showcsr" ;; - --installcert | -i) + --installcert | -i | --install-cert) _CMD="installcert" ;; --renew | -r) _CMD="renew" ;; - --renewAll | --renewall) + --renewAll | --renewall | --renew-all) _CMD="renewAll" ;; --revoke) _CMD="revoke" ;; + --remove) + _CMD="remove" + ;; --list) _CMD="list" ;; - --installcronjob) + --installcronjob | --install-cronjob) _CMD="installcronjob" ;; - --uninstallcronjob) + --uninstallcronjob | --uninstall-cronjob) _CMD="uninstallcronjob" ;; --cron) @@ -4134,10 +4491,10 @@ _process() { --deactivate) _CMD="deactivate" ;; - --updateaccount) + --updateaccount | --update-account) _CMD="updateaccount" ;; - --registeraccount) + --registeraccount | --register-account) _CMD="registeraccount" ;; --domain | -d) @@ -4279,11 +4636,16 @@ _process() { LE_WORKING_DIR="$2" shift ;; - --certhome) + --certhome | --cert-home) _certhome="$2" CERT_HOME="$_certhome" shift ;; + --config-home) + _confighome="$2" + LE_CONFIG_HOME="$_confighome" + shift + ;; --useragent) _useragent="$2" USER_AGENT="$_useragent" @@ -4326,7 +4688,7 @@ _process() { HTTPS_INSECURE="1" ;; --ca-bundle) - _ca_bundle="$(readlink -f "$2")" + _ca_bundle="$(_readlink -f "$2")" CA_BUNDLE="$_ca_bundle" shift ;; @@ -4357,7 +4719,7 @@ _process() { shift ;; --ocsp-must-staple | --ocsp) - Le_OCSP_Stable="1" + Le_OCSP_Staple="1" ;; --log | --logfile) _log="1" @@ -4394,7 +4756,10 @@ _process() { _listen_v6="1" Le_Listen_V6="$_listen_v6" ;; - + --openssl-bin) + _openssl_bin="$2" + OPENSSL_BIN="$_openssl_bin" + ;; *) _err "Unknown parameter : $1" return 1 @@ -4431,7 +4796,7 @@ _process() { fi case "${_CMD}" in - install) install "$_nocron" ;; + install) install "$_nocron" "$_confighome" ;; uninstall) uninstall "$_nocron" ;; upgrade) upgrade ;; issue) @@ -4458,6 +4823,9 @@ _process() { revoke) revoke "$_domain" "$_ecc" ;; + remove) + remove "$_domain" "$_ecc" + ;; deactivate) deactivate "$_domain,$_altdomains" ;; @@ -4470,7 +4838,7 @@ _process() { list) list "$_listraw" ;; - installcronjob) installcronjob ;; + installcronjob) installcronjob "$_confighome" ;; uninstallcronjob) uninstallcronjob ;; cron) cron ;; toPkcs) @@ -4487,7 +4855,9 @@ _process() { ;; *) - _err "Invalid command: $_CMD" + if [ "$_CMD" ]; then + _err "Invalid command: $_CMD" + fi showhelp return 1 ;; @@ -4515,7 +4885,7 @@ _process() { if [ "$INSTALLONLINE" ]; then INSTALLONLINE="" - _installOnline $BRANCH + _installOnline exit fi diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 00000000..580eaac8 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1 @@ +#Using deploy api diff --git a/deploy/kong.sh b/deploy/kong.sh new file mode 100644 index 00000000..3b9c5c79 --- /dev/null +++ b/deploy/kong.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env sh + +# This deploy hook will deploy ssl cert on kong proxy engine based on api request_host parameter. +# Note that ssl plugin should be available on Kong instance +# The hook will match cdomain to request_host, in case of multiple domain it will always take the first +# one (acme.sh behaviour). +# If ssl config already exist it will update only cert and key not touching other parameter +# If ssl config doesn't exist it will only upload cert and key and not set other parameter +# Not that we deploy full chain +# See https://getkong.org/plugins/dynamic-ssl/ for other options +# Written by Geoffroi Genot + +######## Public functions ##################### + +#domain keyfile certfile cafile fullchain +kong_deploy() { + _cdomain="$1" + _ckey="$2" + _ccert="$3" + _cca="$4" + _cfullchain="$5" + _info "Deploying certificate on Kong instance" + if [ -z "$KONG_URL" ]; then + _debug "KONG_URL Not set, using default http://localhost:8001" + KONG_URL="http://localhost:8001" + fi + + _debug _cdomain "$_cdomain" + _debug _ckey "$_ckey" + _debug _ccert "$_ccert" + _debug _cca "$_cca" + _debug _cfullchain "$_cfullchain" + + #Get uuid linked to the domain + uuid=$(_get "$KONG_URL/apis?request_host=$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + if [ -z "$uuid" ]; then + _err "Unable to get Kong uuid for domain $_cdomain" + _err "Make sure that KONG_URL is correctly configured" + _err "Make sure that a Kong api request_host match the domain" + _err "Kong url: $KONG_URL" + return 1 + fi + #Save kong url if it's succesful (First run case) + _saveaccountconf KONG_URL "$KONG_URL" + #Generate DEIM + delim="-----MultipartDelimeter$(date "+%s%N")" + nl="\015\012" + #Set Header + _H1="Content-Type: multipart/form-data; boundary=$delim" + #Generate data for request (Multipart/form-data with mixed content) + #set name to ssl + content="--$delim${nl}Content-Disposition: form-data; name=\"name\"${nl}${nl}ssl" + #add key + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" + #Add cert + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" + #Close multipart + content="$content${nl}--$delim--${nl}" + #Convert CRLF + content=$(printf %b "$content") + #DEBUG + _debug header "$_H1" + _debug content "$content" + #Check if ssl plugins is aready enabled (if not => POST else => PATCH) + ssl_uuid=$(_get "$KONG_URL/apis/$uuid/plugins" | _egrep_o '"id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"[a-zA-Z0-9\-\,\"_\:]*"name":"ssl"' | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + _debug ssl_uuid "$ssl_uuid" + if [ -z "$ssl_uuid" ]; then + #Post certificate to Kong + response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST") + else + #patch + response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH") + fi + if ! [ "$(echo "$response" | _egrep_o "ssl")" = "ssl" ]; then + _err "An error occured with cert upload. Check response:" + _err "$response" + return 1 + fi + _debug response "$response" + _info "Certificate successfully deployed" +} diff --git a/dnsapi/README.md b/dnsapi/README.md index 7eff6de1..6a86bf4c 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -1,99 +1,80 @@ -# How to use dns api +# How to use DNS API -## Use CloudFlare domain api to automatically issue cert +## 1. Use CloudFlare domain API to automatically issue cert -For now, we support clourflare integeration. - -First you need to login to your clourflare account to get your api key. +First you need to login to your CloudFlare account to get your API key. ``` export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" - export CF_Email="xxxx@sss.com" - ``` -Ok, let's issue cert now: +Ok, let's issue a cert now: ``` -acme.sh --issue --dns dns_cf -d example.com -d www.example.com +acme.sh --issue --dns dns_cf -d example.com -d www.example.com ``` -The `CF_Key` and `CF_Email` will be saved in `~/.acme.sh/account.conf`, when next time you use cloudflare api, it will reuse this key. +The `CF_Key` and `CF_Email` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. +## 2. Use DNSPod.cn domain API to automatically issue cert -## Use Dnspod.cn domain api to automatically issue cert - -For now, we support dnspod.cn integeration. - -First you need to login to your dnspod.cn account to get your api key and key id. +First you need to login to your DNSPod account to get your API Key and ID. ``` export DP_Id="1234" - export DP_Key="sADDsdasdgdsf" - ``` -Ok, let's issue cert now: +Ok, let's issue a cert now: ``` -acme.sh --issue --dns dns_dp -d example.com -d www.example.com +acme.sh --issue --dns dns_dp -d example.com -d www.example.com ``` -The `DP_Id` and `DP_Key` will be saved in `~/.acme.sh/account.conf`, when next time you use dnspod.cn api, it will reuse this key. +The `DP_Id` and `DP_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -## Use Cloudxns.com domain api to automatically issue cert +## 3. Use CloudXNS.com domain API to automatically issue cert -For now, we support Cloudxns.com integeration. - -First you need to login to your Cloudxns.com account to get your api key and key secret. +First you need to login to your CloudXNS account to get your API Key and Secret. ``` export CX_Key="1234" - export CX_Secret="sADDsdasdgdsf" - ``` -Ok, let's issue cert now: +Ok, let's issue a cert now: ``` -acme.sh --issue --dns dns_cx -d example.com -d www.example.com +acme.sh --issue --dns dns_cx -d example.com -d www.example.com ``` -The `CX_Key` and `CX_Secret` will be saved in `~/.acme.sh/account.conf`, when next time you use Cloudxns.com api, it will reuse this key. +The `CX_Key` and `CX_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -## Use Godaddy.com domain api to automatically issue cert +## 4. Use GoDaddy.com domain API to automatically issue cert -We support Godaddy integration. - -First you need to login to your Godaddy account to get your api key and api secret. +First you need to login to your GoDaddy account to get your API Key and Secret. https://developer.godaddy.com/keys/ -Please Create a Production key, instead of a Test key. - +Please create a Production key, instead of a Test key. ``` export GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" - export GD_Secret="asdfsdafdsfdsfdsfdsfdsafd" - ``` -Ok, let's issue cert now: +Ok, let's issue a cert now: ``` -acme.sh --issue --dns dns_gd -d example.com -d www.example.com +acme.sh --issue --dns dns_gd -d example.com -d www.example.com ``` -The `GD_Key` and `GD_Secret` will be saved in `~/.acme.sh/account.conf`, when next time you use cloudflare api, it will reuse this key. +The `GD_Key` and `GD_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -## Use PowerDNS embedded api to automatically issue cert -We support PowerDNS embedded API integration. +## 5. Use PowerDNS embedded API to automatically issue cert -First you need to enable api and set your api-token in PowerDNS configuration. +First you need to login to your PowerDNS account to enable the API and set your API-Token in the configuration. https://doc.powerdns.com/md/httpapi/README/ @@ -102,75 +83,245 @@ export PDNS_Url="http://ns.example.com:8081" export PDNS_ServerId="localhost" export PDNS_Token="0123456789ABCDEF" export PDNS_Ttl=60 - ``` -Ok, let's issue cert now: +Ok, let's issue a cert now: ``` -acme.sh --issue --dns dns_pdns -d example.com -d www.example.com +acme.sh --issue --dns dns_pdns -d example.com -d www.example.com ``` -The `PDNS_Url`, `PDNS_ServerId`, `PDNS_Token` and `PDNS_Ttl` will be saved in `~/.acme.sh/account.conf`. +The `PDNS_Url`, `PDNS_ServerId`, `PDNS_Token` and `PDNS_Ttl` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. -## Use OVH/kimsufi/soyoustart/runabove API + +## 6. Use OVH/kimsufi/soyoustart/runabove API to automatically issue cert https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api -# Use custom api -If your api is not supported yet, you can write your own dns api. - -Let's assume you want to name it 'myapi', - -1. Create a bash script named `~/.acme.sh/dns_myapi.sh`, -2. In the script, you must have a function named `dns_myapi_add()`. Which will be called by acme.sh to add dns records. -3. Then you can use your api to issue cert like: +## 7. Use nsupdate to automatically issue cert +First, generate a key for updating the zone ``` -acme.sh --issue --dns dns_myapi -d example.com -d www.example.com +b=$(dnssec-keygen -a hmac-sha512 -b 512 -n USER -K /tmp foo) +cat > /etc/named/keys/update.key <" + return 1 + fi + + if [ -z "$2" ]; then + message="$(printf "%s" "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" + if [ -n "$message" ]; then + _err "$message" + return 1 + fi + fi + + _debug2 response "$response" + return 0 +} + +_ali_urlencode() { + _str="$1" + _str_len=${#_str} + _u_i=1 + while [ "$_u_i" -le "$_str_len" ]; do + _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")" + case $_str_c in [a-zA-Z0-9.~_-]) + printf "%s" "$_str_c" + ;; + *) + printf "%%%02X" "'$_str_c" + ;; + esac + _u_i="$(_math "$_u_i" + 1)" + done +} + +_ali_nonce() { + #_head_n 1 UPSERT$fulldomainTXT300\"$txtvalue\"" + + if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then + _info "txt record updated success." + return 0 + fi + + return 1 +} + +#fulldomain txtvalue +dns_aws_rm() { + fulldomain=$1 + txtvalue=$2 + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _aws_tmpl_xml="DELETE\"$txtvalue\"$fulldomain.TXT300" + + if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then + _info "txt record deleted success." + return 0 + fi + + return 1 + +} + +#################### Private functions below ################################## + +_get_root() { + domain=$1 + i=2 + p=1 + + if aws_rest GET "2013-04-01/hostedzone"; then + _debug "response" "$response" + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if _contains "$response" "$h."; then + hostedzone="$(echo "$response" | _egrep_o "[^<]*<.Id>$h.<.Name>.*<.HostedZone>")" + _debug hostedzone "$hostedzone" + if [ -z "$hostedzone" ]; then + _err "Error, can not get hostedzone." + return 1 + fi + _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o ".*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>") + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + fi + return 1 +} + +#method uri qstr data +aws_rest() { + mtd="$1" + ep="$2" + qsr="$3" + data="$4" + + _debug mtd "$mtd" + _debug ep "$ep" + _debug qsr "$qsr" + _debug data "$data" + + CanonicalURI="/$ep" + _debug2 CanonicalURI "$CanonicalURI" + + CanonicalQueryString="$qsr" + _debug2 CanonicalQueryString "$CanonicalQueryString" + + RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")" + _debug2 RequestDate "$RequestDate" + + #RequestDate="20161120T141056Z" ############## + + export _H1="x-amz-date: $RequestDate" + + aws_host="$AWS_HOST" + CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n" + SignedHeaders="host;x-amz-date" + if [ -n "$AWS_SESSION_TOKEN" ]; then + export _H2="x-amz-security-token: $AWS_SESSION_TOKEN" + CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n" + SignedHeaders="${SignedHeaders};x-amz-security-token" + fi + _debug2 CanonicalHeaders "$CanonicalHeaders" + _debug2 SignedHeaders "$SignedHeaders" + + RequestPayload="$data" + _debug2 RequestPayload "$RequestPayload" + + Hash="sha256" + + CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)" + _debug2 CanonicalRequest "$CanonicalRequest" + + HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)" + _debug2 HashedCanonicalRequest "$HashedCanonicalRequest" + + Algorithm="AWS4-HMAC-SHA256" + _debug2 Algorithm "$Algorithm" + + RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)" + _debug2 RequestDateOnly "$RequestDateOnly" + + Region="us-east-1" + Service="route53" + + CredentialScope="$RequestDateOnly/$Region/$Service/aws4_request" + _debug2 CredentialScope "$CredentialScope" + + StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest" + + _debug2 StringToSign "$StringToSign" + + kSecret="AWS4$AWS_SECRET_ACCESS_KEY" + + #kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################ + + _debug2 kSecret "$kSecret" + + kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")" + _debug2 kSecretH "$kSecretH" + + kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)" + _debug2 kDateH "$kDateH" + + kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)" + _debug2 kRegionH "$kRegionH" + + kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)" + _debug2 kServiceH "$kServiceH" + + kSigningH="$(printf "aws4_request%s" | _hmac "$Hash" "$kServiceH" hex)" + _debug2 kSigningH "$kSigningH" + + signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)" + _debug2 signature "$signature" + + Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature" + _debug2 Authorization "$Authorization" + + _H3="Authorization: $Authorization" + _debug _H3 "$_H3" + + url="$AWS_URL/$ep" + + if [ "$mtd" = "GET" ]; then + response="$(_get "$url")" + else + response="$(_post "$data" "$url")" + fi + + _ret="$?" + if [ "$_ret" = "0" ]; then + if _contains "$response" "/dev/null; then - _info "Added, sleeping 10 seconds" - sleep 10 - #todo: check if the record takes effect + _info "Added, OK" return 0 else _err "Add txt record error." @@ -66,9 +70,7 @@ dns_cf_add() { _cf_rest PUT "zones/$_domain_id/dns_records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"zone_name\":\"$_domain\"}" if [ "$?" = "0" ]; then - _info "Updated, sleeping 10 seconds" - sleep 10 - #todo: check if the record takes effect + _info "Updated, OK" return 0 fi _err "Update error" @@ -77,13 +79,48 @@ dns_cf_add() { } -#fulldomain +#fulldomain txtvalue dns_cf_rm() { fulldomain=$1 + txtvalue=$2 + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain&content=$txtvalue" + + if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then + _err "Error" + return 1 + fi + + count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) + _debug count "$count" + if [ "$count" = "0" ]; then + _info "Don't need to remove." + else + record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1) + _debug "record_id" "$record_id" + if [ -z "$record_id" ]; then + _err "Can not get record id to remove." + return 1 + fi + if ! _cf_rest DELETE "zones/$_domain_id/dns_records/$record_id"; then + _err "Delete record error." + return 1 + fi + _contains "$response" '"success":true' + fi } -#################### Private functions bellow ################################## +#################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www @@ -95,6 +132,7 @@ _get_root() { p=1 while true; do h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" if [ -z "$h" ]; then #not valid return 1 @@ -104,8 +142,8 @@ _get_root() { return 1 fi - if printf "%s" "$response" | grep "\"name\":\"$h\"" >/dev/null; then - _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") + if _contains "$response" "\"name\":\"$h\"" >/dev/null; then + _domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain=$h @@ -125,11 +163,11 @@ _cf_rest() { data="$3" _debug "$ep" - _H1="X-Auth-Email: $CF_Email" - _H2="X-Auth-Key: $CF_Key" - _H3="Content-Type: application/json" + export _H1="X-Auth-Email: $CF_Email" + export _H2="X-Auth-Key: $CF_Key" + export _H3="Content-Type: application/json" - if [ "$data" ]; then + if [ "$m" != "GET" ]; then _debug data "$data" response="$(_post "$data" "$CF_Api/$ep" "" "$m")" else diff --git a/dnsapi/dns_cx.sh b/dnsapi/dns_cx.sh index 0caf0c02..2b6d5691 100755 --- a/dnsapi/dns_cx.sh +++ b/dnsapi/dns_cx.sh @@ -58,7 +58,15 @@ dns_cx_add() { #fulldomain dns_cx_rm() { fulldomain=$1 - + REST_API="$CX_Api" + if _get_root "$fulldomain"; then + record_id="" + existing_records "$_domain" "$_sub_domain" + if ! [ "$record_id" = "" ]; then + _rest DELETE "record/$record_id/$_domain_id" "{}" + _info "Deleted record ${fulldomain}" + fi + fi } #usage: root sub @@ -69,12 +77,12 @@ existing_records() { _debug "Getting txt records" root=$1 sub=$2 - + count=0 if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then return 1 fi - count=0 - seg=$(printf "%s\n" "$response" | _egrep_o "{[^\{]*host\":\"$_sub_domain\"[^\}]*\}") + + seg=$(printf "%s\n" "$response" | _egrep_o '"record_id":[^{]*host":"'"$_sub_domain"'"[^}]*\}') _debug seg "$seg" if [ -z "$seg" ]; then return 0 @@ -82,7 +90,7 @@ existing_records() { if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then count=1 - record_id=$(printf "%s\n" "$seg" | _egrep_o "\"record_id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") + record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1) _debug record_id "$record_id" return 0 fi @@ -123,7 +131,7 @@ update_record() { return 1 } -#################### Private functions bellow ################################## +#################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www @@ -147,9 +155,9 @@ _get_root() { fi if _contains "$response" "$h."; then - seg=$(printf "%s" "$response" | _egrep_o "\{[^\{]*\"$h\.\"[^\}]*\}") + seg=$(printf "%s\n" "$response" | _egrep_o '"id":[^{]*"'"$h"'."[^}]*}') _debug seg "$seg" - _domain_id=$(printf "%s" "$seg" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") + _domain_id=$(printf "%s\n" "$seg" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) @@ -170,7 +178,7 @@ _get_root() { _rest() { m=$1 ep="$2" - _debug "$ep" + _debug ep "$ep" url="$REST_API/$ep" _debug url "$url" @@ -185,10 +193,10 @@ _rest() { hmac=$(printf "%s" "$sec" | _digest md5 hex) _debug hmac "$hmac" - _H1="API-KEY: $CX_Key" - _H2="API-REQUEST-DATE: $cdate" - _H3="API-HMAC: $hmac" - _H4="Content-Type: application/json" + export _H1="API-KEY: $CX_Key" + export _H2="API-REQUEST-DATE: $cdate" + export _H3="API-HMAC: $hmac" + export _H4="Content-Type: application/json" if [ "$data" ]; then response="$(_post "$data" "$url" "" "$m")" diff --git a/dnsapi/dns_dp.sh b/dnsapi/dns_dp.sh index aa06d5fc..301a1f6c 100755 --- a/dnsapi/dns_dp.sh +++ b/dnsapi/dns_dp.sh @@ -6,9 +6,8 @@ # #DP_Key="sADDsdasdgdsf" -DP_Api="https://dnsapi.cn" +REST_API="https://dnsapi.cn" -#REST_API ######## Public functions ##################### #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" @@ -24,8 +23,6 @@ dns_dp_add() { return 1 fi - REST_API="$DP_Api" - #save the api key and email to the account conf file. _saveaccountconf DP_Id "$DP_Id" _saveaccountconf DP_Key "$DP_Key" @@ -50,9 +47,39 @@ dns_dp_add() { fi } -#fulldomain +#fulldomain txtvalue dns_dp_rm() { fulldomain=$1 + txtvalue=$2 + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then + _err "Record.Lis error." + return 1 + fi + + if _contains "$response" 'No records'; then + _info "Don't need to remove." + return 0 + fi + + record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \") + _debug record_id "$record_id" + if [ -z "$record_id" ]; then + _err "Can not get record id." + return 1 + fi + + if ! _rest POST "Record.Remove" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then + _err "Record.Remove error." + return 1 + fi + + _contains "$response" "Action completed successful" } @@ -75,8 +102,9 @@ existing_records() { fi if _contains "$response" "Action completed successful"; then - count=$(printf "%s" "$response" | grep 'TXT' | wc -l) + count=$(printf "%s" "$response" | grep -c 'TXT' | tr -d ' ') record_id=$(printf "%s" "$response" | grep '^' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1) + _debug record_id "$record_id" return 0 else _err "get existing records error." @@ -130,7 +158,7 @@ update_record() { return 1 #error } -#################### Private functions bellow ################################## +#################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www @@ -171,7 +199,7 @@ _get_root() { #Usage: method URI data _rest() { - m=$1 + m="$1" ep="$2" data="$3" _debug "$ep" @@ -179,11 +207,11 @@ _rest() { _debug url "$url" - if [ "$data" ]; then - _debug2 data "$data" - response="$(_post "$data" "$url")" + if [ "$m" = "GET" ]; then + response="$(_get "$url" | tr -d '\r')" else - response="$(_get "$url")" + _debug2 data "$data" + response="$(_post "$data" "$url" | tr -d '\r')" fi if [ "$?" != "0" ]; then diff --git a/dnsapi/dns_freedns.sh b/dnsapi/dns_freedns.sh new file mode 100755 index 00000000..f30c8958 --- /dev/null +++ b/dnsapi/dns_freedns.sh @@ -0,0 +1,375 @@ +#!/usr/bin/env sh + +#This file name is "dns_freedns.sh" +#So, here must be a method dns_freedns_add() +#Which will be called by acme.sh to add the txt record to your api system. +#returns 0 means success, otherwise error. +# +#Author: David Kerr +#Report Bugs here: https://github.com/dkerr64/acme.sh +# +######## Public functions ##################### + +# Export FreeDNS userid and password in folowing variables... +# FREEDNS_User=username +# FREEDNS_Password=password +# login cookie is saved in acme account config file so userid / pw +# need to be set only when changed. + +#Usage: dns_freedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_freedns_add() { + fulldomain="$1" + txtvalue="$2" + + _info "Add TXT record using FreeDNS" + _debug "fulldomain: $fulldomain" + _debug "txtvalue: $txtvalue" + + if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then + FREEDNS_User="" + FREEDNS_Password="" + if [ -z "$FREEDNS_COOKIE" ]; then + _err "You did not specify the FreeDNS username and password yet." + _err "Please export as FREEDNS_User / FREEDNS_Password and try again." + return 1 + fi + using_cached_cookies="true" + else + FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")" + if [ -z "$FREEDNS_COOKIE" ]; then + return 1 + fi + using_cached_cookies="false" + fi + + _debug "FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)" + + _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE" + + # split our full domain name into two parts... + i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" + i="$(_math "$i" - 1)" + top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)" + i="$(_math "$i" - 1)" + sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")" + + # Sometimes FreeDNS does not reurn the subdomain page but rather + # returns a page regarding becoming a premium member. This usually + # happens after a period of inactivity. Immediately trying again + # returns the correct subdomain page. So, we will try twice to + # load the page and obtain our domain ID + attempts=2 + while [ "$attempts" -gt "0" ]; do + attempts="$(_math "$attempts" - 1)" + + htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" + if [ "$?" != "0" ]; then + if [ "$using_cached_cookies" = "true" ]; then + _err "Has your FreeDNS username and password channged? If so..." + _err "Please export as FREEDNS_User / FREEDNS_Password and try again." + fi + return 1 + fi + + # Now convert the tables in the HTML to CSV. This litte gem from + # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv + subdomain_csv="$(echo "$htmlpage" \ + | grep -i -e ']*>/\n/Ig' \ + | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ + | sed 's/^]*>\|<\/\?T[DH][^>]*>$//Ig' \ + | sed 's/<\/T[DH][^>]*>]*>/,/Ig' \ + | grep 'edit.php?' \ + | grep "$top_domain")" + # The above beauty ends with striping out rows that do not have an + # href to edit.php and do not have the top domain we are looking for. + # So all we should be left with is CSV of table of subdomains we are + # interested in. + + # Now we have to read through this table and extract the data we need + lines="$(echo "$subdomain_csv" | wc -l)" + nl=' +' + i=0 + found=0 + while [ "$i" -lt "$lines" ]; do + i="$(_math "$i" + 1)" + line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" + tmp="$(echo "$line" | cut -d ',' -f 1)" + if [ $found = 0 ] && _startswith "$tmp" "$top_domain"; then + # this line will contain DNSdomainid for the top_domain + DNSdomainid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*domain_id=//;s/>.*//')" + found=1 + else + # lines contain DNS records for all subdomains + DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" + DNStype="$(echo "$line" | cut -d ',' -f 3)" + if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then + DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" + # Now get current value for the TXT record. This method may + # not produce accurate results as the value field is truncated + # on this webpage. To get full value we would need to load + # another page. However we don't really need this so long as + # there is only one TXT record for the acme chalenge subdomain. + DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" + if [ $found != 0 ]; then + break + # we are breaking out of the loop at the first match of DNS name + # and DNS type (if we are past finding the domainid). This assumes + # that there is only ever one TXT record for the LetsEncrypt/acme + # challenge subdomain. This seems to be a reasonable assumption + # as the acme client deletes the TXT record on successful validation. + fi + else + DNSname="" + DNStype="" + fi + fi + done + + _debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid" + _debug "DNSvalue: $DNSvalue" + + if [ -z "$DNSdomainid" ]; then + # If domain ID is empty then something went wrong (top level + # domain not found at FreeDNS). + if [ "$attempts" = "0" ]; then + # exhausted maximum retry attempts + _debug "$htmlpage" + _debug "$subdomain_csv" + _err "Domain $top_domain not found at FreeDNS" + return 1 + fi + else + # break out of the 'retry' loop... we have found our domain ID + break + fi + _info "Domain $top_domain not found at FreeDNS" + _info "Retry loading subdomain page ($attempts attempts remaining)" + done + + if [ -z "$DNSdataid" ]; then + # If data ID is empty then specific subdomain does not exist yet, need + # to create it this should always be the case as the acme client + # deletes the entry after domain is validated. + _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" + return $? + else + if [ "$txtvalue" = "$DNSvalue" ]; then + # if value in TXT record matches value requested then DNS record + # does not need to be updated. But... + # Testing value match fails. Website is truncating the value field. + # So for now we will always go down the else path. Though in theory + # should never come here anyway as the acme client deletes + # the TXT record on successful validation, so we should not even + # have found a TXT record !! + _info "No update necessary for $fulldomain at FreeDNS" + return 0 + else + # Delete the old TXT record (with the wrong value) + _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" + if [ "$?" = "0" ]; then + # And add in new TXT record with the value provided + _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" + fi + return $? + fi + fi + return 0 +} + +#Usage: fulldomain txtvalue +#Remove the txt record after validation. +dns_freedns_rm() { + fulldomain="$1" + txtvalue="$2" + + _info "Delete TXT record using FreeDNS" + _debug "fulldomain: $fulldomain" + _debug "txtvalue: $txtvalue" + + # Need to read cookie from conf file again in case new value set + # during login to FreeDNS when TXT record was created. + # acme.sh does not have a _readaccountconf() fuction + FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")" + _debug "FreeDNS login cookies: $FREEDNS_COOKIE" + + # Sometimes FreeDNS does not reurn the subdomain page but rather + # returns a page regarding becoming a premium member. This usually + # happens after a period of inactivity. Immediately trying again + # returns the correct subdomain page. So, we will try twice to + # load the page and obtain our TXT record. + attempts=2 + while [ "$attempts" -gt "0" ]; do + attempts="$(_math "$attempts" - 1)" + + htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" + if [ "$?" != "0" ]; then + return 1 + fi + + # Now convert the tables in the HTML to CSV. This litte gem from + # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv + subdomain_csv="$(echo "$htmlpage" \ + | grep -i -e ']*>/\n/Ig' \ + | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ + | sed 's/^]*>\|<\/\?T[DH][^>]*>$//Ig' \ + | sed 's/<\/T[DH][^>]*>]*>/,/Ig' \ + | grep 'edit.php?' \ + | grep "$fulldomain")" + # The above beauty ends with striping out rows that do not have an + # href to edit.php and do not have the domain name we are looking for. + # So all we should be left with is CSV of table of subdomains we are + # interested in. + + # Now we have to read through this table and extract the data we need + lines="$(echo "$subdomain_csv" | wc -l)" + nl=' +' + i=0 + found=0 + while [ "$i" -lt "$lines" ]; do + i="$(_math "$i" + 1)" + line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" + DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" + DNStype="$(echo "$line" | cut -d ',' -f 3)" + if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then + DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" + DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" + _debug "DNSvalue: $DNSvalue" + # if [ "$DNSvalue" = "$txtvalue" ]; then + # Testing value match fails. Website is truncating the value + # field. So for now we will assume that there is only one TXT + # field for the sub domain and just delete it. Currently this + # is a safe assumption. + _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" + return $? + # fi + fi + done + done + + # If we get this far we did not find a match (after two attempts) + # Not necessarily an error, but log anyway. + _debug2 "$subdomain_csv" + _info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS" + return 0 +} + +#################### Private functions below ################################## + +# usage: _freedns_login username password +# print string "cookie=value" etc. +# returns 0 success +_freedns_login() { + export _H1="Accept-Language:en-US" + username="$1" + password="$2" + url="https://freedns.afraid.org/zc.php?step=2" + + _debug "Login to FreeDNS as user $username" + + htmlpage="$(_post "username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" "$url")" + + if [ "$?" != "0" ]; then + _err "FreeDNS login failed for user $username bad RC from _post" + return 1 + fi + + cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)" + + # if cookies is not empty then logon successful + if [ -z "$cookies" ]; then + _debug "$htmlpage" + _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file" + return 1 + fi + + printf "%s" "$cookies" + return 0 +} + +# usage _freedns_retrieve_subdomain_page login_cookies +# echo page retrieved (html) +# returns 0 success +_freedns_retrieve_subdomain_page() { + export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" + url="https://freedns.afraid.org/subdomain/" + + _debug "Retrieve subdmoain page from FreeDNS" + + htmlpage="$(_get "$url")" + + if [ "$?" != "0" ]; then + _err "FreeDNS retrieve subdomins failed bad RC from _get" + return 1 + elif [ -z "$htmlpage" ]; then + _err "FreeDNS returned empty subdomain page" + return 1 + fi + + _debug2 "$htmlpage" + + printf "%s" "$htmlpage" + return 0 +} + +# usage _freedns_add_txt_record login_cookies domain_id subdomain value +# returns 0 success +_freedns_add_txt_record() { + export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" + domain_id="$2" + subdomain="$3" + value="$(printf '%s' "$4" | _url_encode)" + url="http://freedns.afraid.org/subdomain/save.php?step=2" + + htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")" + + if [ "$?" != "0" ]; then + _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" + return 1 + elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then + _debug "$htmlpage" + _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" + return 1 + elif _contains "$htmlpage" "security code was incorrect"; then + _debug "$htmlpage" + _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested seurity code" + _err "Note that you cannot use automatic DNS validation for FreeDNS public domains" + return 1 + fi + + _debug2 "$htmlpage" + _info "Added acme challenge TXT record for $fulldomain at FreeDNS" + return 0 +} + +# usage _freedns_delete_txt_record login_cookies data_id +# returns 0 success +_freedns_delete_txt_record() { + export _H1="Cookie:$1" + export _H2="Accept-Language:en-US" + data_id="$2" + url="https://freedns.afraid.org/subdomain/delete2.php" + + htmlheader="$(_get "$url?data_id%5B%5D=$data_id&submit=delete+selected" "onlyheader")" + + if [ "$?" != "0" ]; then + _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" + return 1 + elif ! _contains "$htmlheader" "200 OK"; then + _debug "$htmlheader" + _err "FreeDNS failed to delete TXT record $data_id" + return 1 + fi + + _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS" + return 0 +} diff --git a/dnsapi/dns_gd.sh b/dnsapi/dns_gd.sh index 9470ed22..1abeeacf 100755 --- a/dnsapi/dns_gd.sh +++ b/dnsapi/dns_gd.sh @@ -59,7 +59,7 @@ dns_gd_rm() { } -#################### Private functions bellow ################################## +#################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www @@ -98,8 +98,8 @@ _gd_rest() { data="$3" _debug "$ep" - _H1="Authorization: sso-key $GD_Key:$GD_Secret" - _H2="Content-Type: application/json" + export _H1="Authorization: sso-key $GD_Key:$GD_Secret" + export _H2="Content-Type: application/json" if [ "$data" ]; then _debug data "$data" diff --git a/dnsapi/dns_ispconfig.sh b/dnsapi/dns_ispconfig.sh new file mode 100755 index 00000000..6d1f34c5 --- /dev/null +++ b/dnsapi/dns_ispconfig.sh @@ -0,0 +1,177 @@ +#!/usr/bin/env sh + +# ISPConfig 3.1 API +# User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to: +# - DNS zone Functions +# - DNS txt Functions + +# Report bugs to https://github.com/sjau/acme.sh + +# Values to export: +# export ISPC_User="remoteUser" +# export ISPC_Password="remotePassword" +# export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" +# export ISPC_Api_Insecure=1 # Set 1 for insecure and 0 for secure -> difference is whether ssl cert is checked for validity (0) or whether it is just accepted (1) + +######## Public functions ##################### + +#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_ispconfig_add() { + fulldomain="${1}" + txtvalue="${2}" + _debug "Calling: dns_ispconfig_add() '${fulldomain}' '${txtvalue}'" + _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt +} + +#Usage: dns_myapi_rm _acme-challenge.www.domain.com +dns_ispconfig_rm() { + fulldomain="${1}" + _debug "Calling: dns_ispconfig_rm() '${fulldomain}'" + _ISPC_credentials && _ISPC_login && _ISPC_rmTxt +} + +#################### Private functions below ################################## + +_ISPC_credentials() { + if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then + ISPC_User="" + ISPC_Password="" + ISPC_Api="" + ISPC_Api_Insecure="" + _err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again." + return 1 + else + _saveaccountconf ISPC_User "${ISPC_User}" + _saveaccountconf ISPC_Password "${ISPC_Password}" + _saveaccountconf ISPC_Api "${ISPC_Api}" + _saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}" + # Set whether curl should use secure or insecure mode + export HTTPS_INSECURE="${ISPC_Api_Insecure}" + fi +} + +_ISPC_login() { + _info "Getting Session ID" + curData="{\"username\":\"${ISPC_User}\",\"password\":\"${ISPC_Password}\",\"client_login\":false}" + curResult="$(_post "${curData}" "${ISPC_Api}?login")" + _debug "Calling _ISPC_login: '${curData}' '${ISPC_Api}?login'" + _debug "Result of _ISPC_login: '$curResult'" + if _contains "${curResult}" '"code":"ok"'; then + sessionID=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) + _info "Retrieved Session ID." + _debug "Session ID: '${sessionID}'" + else + _err "Couldn't retrieve the Session ID." + return 1 + fi +} + +_ISPC_getZoneInfo() { + _info "Getting Zoneinfo" + zoneEnd=false + curZone="${fulldomain}" + while [ "${zoneEnd}" = false ]; do + # we can strip the first part of the fulldomain, since it's just the _acme-challenge string + curZone="${curZone#*.}" + # suffix . needed for zone -> domain.tld. + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")" + _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?login'" + _debug "Result of _ISPC_getZoneInfo: '$curResult'" + if _contains "${curResult}" '"id":"'; then + zoneFound=true + zoneEnd=true + _info "Retrieved zone data." + _debug "Zone data: '${curResult}'" + fi + if [ "${curZone#*.}" != "$curZone" ]; then + _debug2 "$curZone still contains a '.' - so we can check next higher level" + else + zoneEnd=true + _err "Couldn't retrieve zone data." + return 1 + fi + done + if [ "${zoneFound}" ]; then + 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" ;; + 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" ;; + 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." ;; + esac + zoneFound="" + zoneEnd="" + fi +} + +_ISPC_addTxt() { + curSerial="$(date +%s)" + curStamp="$(date +'%F %T')" + params="\"server_id\":\"${server_id}\",\"zone\":\"${zone}\",\"name\":\"${fulldomain}.\",\"type\":\"txt\",\"data\":\"${txtvalue}\",\"aux\":\"0\",\"ttl\":\"3600\",\"active\":\"y\",\"stamp\":\"${curStamp}\",\"serial\":\"${curSerial}\"" + curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}}}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")" + _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'" + _debug "Result of _ISPC_addTxt: '$curResult'" + 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." ;; + esac +} + +_ISPC_rmTxt() { + # Need to get the record ID. + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"name\":\"${fulldomain}.\",\"type\":\"TXT\"}}" + curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_get")" + _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_get'" + _debug "Result of _ISPC_rmTxt: '$curResult'" + if _contains "${curResult}" '"code":"ok"'; then + 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." + return 1 + ;; + *) + unset IFS + _info "Retrieved Record ID." + curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}" + 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 + ;; + esac + fi +} diff --git a/dnsapi/dns_lexicon.sh b/dnsapi/dns_lexicon.sh index 4ab65645..c38ff3e3 100755 --- a/dnsapi/dns_lexicon.sh +++ b/dnsapi/dns_lexicon.sh @@ -2,7 +2,7 @@ # dns api wrapper of lexicon for acme.sh -lexicon_url="https://github.com/AnalogJ/lexicon" +# https://github.com/AnalogJ/lexicon lexicon_cmd="lexicon" wiki="https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api" @@ -30,7 +30,9 @@ dns_lexicon_add() { _savedomainconf PROVIDER "$PROVIDER" export PROVIDER - Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr '[a-z]' '[A-Z]') + # e.g. busybox-ash does not know [:upper:] + # shellcheck disable=SC2018,SC2019 + Lx_name=$(echo LEXICON_"${PROVIDER}"_USERNAME | tr 'a-z' 'A-Z') Lx_name_v=$(eval echo \$"$Lx_name") _debug "$Lx_name" "$Lx_name_v" if [ "$Lx_name_v" ]; then @@ -38,7 +40,8 @@ dns_lexicon_add() { eval export "$Lx_name" fi - Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr '[a-z]' '[A-Z]') + # shellcheck disable=SC2018,SC2019 + Lx_token=$(echo LEXICON_"${PROVIDER}"_TOKEN | tr 'a-z' 'A-Z') Lx_token_v=$(eval echo \$"$Lx_token") _debug "$Lx_token" "$Lx_token_v" if [ "$Lx_token_v" ]; then @@ -46,7 +49,8 @@ dns_lexicon_add() { eval export "$Lx_token" fi - Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr '[a-z]' '[A-Z]') + # shellcheck disable=SC2018,SC2019 + Lx_password=$(echo LEXICON_"${PROVIDER}"_PASSWORD | tr 'a-z' 'A-Z') Lx_password_v=$(eval echo \$"$Lx_password") _debug "$Lx_password" "$Lx_password_v" if [ "$Lx_password_v" ]; then @@ -54,7 +58,8 @@ dns_lexicon_add() { eval export "$Lx_password" fi - Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr '[a-z]' '[A-Z]') + # shellcheck disable=SC2018,SC2019 + Lx_domaintoken=$(echo LEXICON_"${PROVIDER}"_DOMAINTOKEN | tr 'a-z' 'A-Z') Lx_domaintoken_v=$(eval echo \$"$Lx_domaintoken") _debug "$Lx_domaintoken" "$Lx_domaintoken_v" if [ "$Lx_domaintoken_v" ]; then diff --git a/dnsapi/dns_linode.sh b/dnsapi/dns_linode.sh new file mode 100755 index 00000000..6d54e6c1 --- /dev/null +++ b/dnsapi/dns_linode.sh @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +#Author: Philipp Grosswiler + +LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action=" + +######## Public functions ##################### + +#Usage: dns_linode_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_linode_add() { + fulldomain="${1}" + txtvalue="${2}" + + if ! _Linode_API; then + return 1 + fi + + _info "Using Linode" + _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain does not exist." + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue" + + if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _resource_id "$_resource_id" + + if [ -z "$_resource_id" ]; then + _err "Error adding the domain resource." + return 1 + fi + + _info "Domain resource successfully added." + return 0 + fi + + return 1 +} + +#Usage: dns_linode_rm _acme-challenge.www.domain.com +dns_linode_rm() { + fulldomain="${1}" + + if ! _Linode_API; then + return 1 + fi + + _info "Using Linode" + _debug "Calling: dns_linode_rm() '${fulldomain}'" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "Domain does not exist." + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _parameters="&DomainID=$_domain_id" + + if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then + response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" + + resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")" + if [ "$resource" ]; then + _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$_resource_id" ]; then + _debug _resource_id "$_resource_id" + + _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id" + + if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then + _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) + _debug _resource_id "$_resource_id" + + if [ -z "$_resource_id" ]; then + _err "Error deleting the domain resource." + return 1 + fi + + _info "Domain resource successfully deleted." + return 0 + fi + fi + + return 1 + fi + + return 0 + fi + + return 1 +} + +#################### Private functions below ################################## + +_Linode_API() { + if [ -z "$LINODE_API_KEY" ]; then + LINODE_API_KEY="" + + _err "You didn't specify the Linode API key yet." + _err "Please create your key and try again." + + return 1 + fi + + _saveaccountconf LINODE_API_KEY "$LINODE_API_KEY" +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=12345 +_get_root() { + domain=$1 + i=2 + p=1 + + if _rest GET "domain.list"; then + response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")" + if [ "$hostedzone" ]; then + _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + fi + return 1 +} + +#method method action data +_rest() { + mtd="$1" + ep="$2" + data="$3" + + _debug mtd "$mtd" + _debug ep "$ep" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + + if [ "$mtd" != "GET" ]; then + # both POST and DELETE. + _debug data "$data" + response="$(_post "$data" "$LINODE_API_URL$ep" "" "$mtd")" + else + response="$(_get "$LINODE_API_URL$ep$data")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_lua.sh b/dnsapi/dns_lua.sh index 2c7ec4b3..828e8012 100755 --- a/dnsapi/dns_lua.sh +++ b/dnsapi/dns_lua.sh @@ -46,12 +46,12 @@ dns_lua_add() { return 1 fi - count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain\"" | wc -l) + count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ") _debug count "$count" if [ "$count" = "0" ]; then _info "Adding record" if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then - if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then + if _contains "$response" "$fulldomain"; then _info "Added" #todo: check if the record takes effect return 0 @@ -63,11 +63,11 @@ dns_lua_add() { _err "Add txt record error." else _info "Updating record" - record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | cut -d: -f2 | cut -d, -f1) + record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | _head_n 1 | cut -d: -f2 | cut -d, -f1) _debug "record_id" "$record_id" - _LUA_rest PUT "zones/$_domain_id/records/$record_id" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"zone_id\":\"$_domain_id\",\"ttl\":120}" - if [ "$?" = "0" ]; then + _LUA_rest PUT "zones/$_domain_id/records/$record_id" "{\"id\":$record_id,\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"zone_id\":$_domain_id,\"ttl\":120}" + if [ "$?" = "0" ] && _contains "$response" "updated_at"; then _info "Updated!" #todo: check if the record takes effect return 0 @@ -84,7 +84,7 @@ dns_lua_rm() { } -#################### Private functions bellow ################################## +#################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www @@ -99,6 +99,7 @@ _get_root() { fi while true; do h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" if [ -z "$h" ]; then #not valid return 1 @@ -106,6 +107,7 @@ _get_root() { if _contains "$response" "\"name\":\"$h\""; then _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1) + _debug _domain_id "$_domain_id" if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" @@ -125,8 +127,8 @@ _LUA_rest() { data="$3" _debug "$ep" - _H1="Accept: application/json" - _H2="Authorization: Basic $LUA_auth" + export _H1="Accept: application/json" + export _H2="Authorization: Basic $LUA_auth" if [ "$data" ]; then _debug data "$data" response="$(_post "$data" "$LUA_Api/$ep" "" "$m")" diff --git a/dnsapi/dns_me.sh b/dnsapi/dns_me.sh index edd88d98..f63621d9 100644 --- a/dnsapi/dns_me.sh +++ b/dnsapi/dns_me.sh @@ -81,7 +81,7 @@ dns_me_rm() { } -#################### Private functions bellow ################################## +#################### Private functions below ################################## #_acme-challenge.www.domain.com #returns # _sub_domain=_acme-challenge.www @@ -103,7 +103,7 @@ _get_root() { fi if _contains "$response" "\"name\":\"$h\""; then - _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2) + _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2 | tr -d '}') if [ "$_domain_id" ]; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" @@ -124,11 +124,11 @@ _me_rest() { _debug "$ep" cdate=$(date -u +"%a, %d %b %Y %T %Z") - hmac=$(printf "%s" "$cdate" | _hmac sha1 "$ME_Secret" 1) + hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) - _H1="x-dnsme-apiKey: $ME_Key" - _H2="x-dnsme-requestDate: $cdate" - _H3="x-dnsme-hmac: $hmac" + export _H1="x-dnsme-apiKey: $ME_Key" + export _H2="x-dnsme-requestDate: $cdate" + export _H3="x-dnsme-hmac: $hmac" if [ "$data" ]; then _debug data "$data" diff --git a/dnsapi/dns_myapi.sh b/dnsapi/dns_myapi.sh index 813a2ed1..6bf62508 100755 --- a/dnsapi/dns_myapi.sh +++ b/dnsapi/dns_myapi.sh @@ -5,48 +5,31 @@ #So, here must be a method dns_myapi_add() #Which will be called by acme.sh to add the txt record to your api system. #returns 0 means success, otherwise error. - +# +#Author: Neilpang +#Report Bugs here: https://github.com/Neilpang/acme.sh +# ######## Public functions ##################### #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" dns_myapi_add() { fulldomain=$1 txtvalue=$2 + _info "Using myapi" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" _err "Not implemented!" return 1 } -#fulldomain +#Usage: fulldomain txtvalue +#Remove the txt record after validation. dns_myapi_rm() { fulldomain=$1 - + txtvalue=$2 + _info "Using myapi" + _debug fulldomain "$fulldomain" + _debug txtvalue "$txtvalue" } -#################### Private functions bellow ################################## -_info() { - if [ -z "$2" ]; then - echo "[$(date)] $1" - else - echo "[$(date)] $1='$2'" - fi -} - -_err() { - _info "$@" >&2 - return 1 -} - -_debug() { - if [ -z "$DEBUG" ]; then - return - fi - _err "$@" - return 0 -} - -_debug2() { - if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then - _debug "$@" - fi - return -} +#################### Private functions below ################################## diff --git a/dnsapi/dns_nsupdate.sh b/dnsapi/dns_nsupdate.sh new file mode 100755 index 00000000..7acb2ef7 --- /dev/null +++ b/dnsapi/dns_nsupdate.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env sh + +######## Public functions ##################### + +#Usage: dns_nsupdate_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_nsupdate_add() { + fulldomain=$1 + txtvalue=$2 + _checkKeyFile || return 1 + [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost" + # save the dns server and key to the account conf file. + _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}" + _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}" + _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" + nsupdate -k "${NSUPDATE_KEY}" <