├── .github ├── PULL_REQUEST_TEMPLATE.md └── ISSUE_TEMPLATE.md ├── deploy ├── dovecot.sh ├── nginx.sh ├── apache.sh ├── haproxy.sh ├── mysqld.sh ├── opensshd.sh ├── pureftpd.sh ├── myapi.sh ├── cpanel.sh ├── keychain.sh ├── README.md ├── kong.sh ├── vsftpd.sh └── exim4.sh ├── dnsapi ├── dns_myapi.sh ├── dns_nsupdate.sh ├── dns_knot.sh ├── dns_lexicon.sh ├── dns_gd.sh ├── dns_gandi_livedns.sh ├── dns_ad.sh ├── dns_do.sh ├── dns_pdns.sh ├── dns_me.sh ├── dns_lua.sh ├── dns_ali.sh ├── dns_linode.sh ├── dns_cf.sh ├── dns_cx.sh ├── dns_dp.sh ├── dns_ispconfig.sh ├── dns_dgon.sh ├── dns_aws.sh ├── dns_ovh.sh ├── dns_cyon.sh ├── README.md └── dns_freedns.sh ├── .travis.yml └── README.md /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deploy/dovecot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to dovecot server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | ######## Public functions ##################### 8 | 9 | #domain keyfile certfile cafile fullchain 10 | dovecot_deploy() { 11 | _cdomain="$1" 12 | _ckey="$2" 13 | _ccert="$3" 14 | _cca="$4" 15 | _cfullchain="$5" 16 | 17 | _debug _cdomain "$_cdomain" 18 | _debug _ckey "$_ckey" 19 | _debug _ccert "$_ccert" 20 | _debug _cca "$_cca" 21 | _debug _cfullchain "$_cfullchain" 22 | 23 | _err "Not implemented yet" 24 | return 1 25 | 26 | } 27 | -------------------------------------------------------------------------------- /deploy/nginx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to nginx server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | ######## Public functions ##################### 8 | 9 | #domain keyfile certfile cafile fullchain 10 | nginx_deploy() { 11 | _cdomain="$1" 12 | _ckey="$2" 13 | _ccert="$3" 14 | _cca="$4" 15 | _cfullchain="$5" 16 | 17 | _debug _cdomain "$_cdomain" 18 | _debug _ckey "$_ckey" 19 | _debug _ccert "$_ccert" 20 | _debug _cca "$_cca" 21 | _debug _cfullchain "$_cfullchain" 22 | 23 | _err "deploy cert to nginx server, Not implemented yet" 24 | return 1 25 | 26 | } 27 | -------------------------------------------------------------------------------- /deploy/apache.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to apache server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | ######## Public functions ##################### 8 | 9 | #domain keyfile certfile cafile fullchain 10 | apache_deploy() { 11 | _cdomain="$1" 12 | _ckey="$2" 13 | _ccert="$3" 14 | _cca="$4" 15 | _cfullchain="$5" 16 | 17 | _debug _cdomain "$_cdomain" 18 | _debug _ckey "$_ckey" 19 | _debug _ccert "$_ccert" 20 | _debug _cca "$_cca" 21 | _debug _cfullchain "$_cfullchain" 22 | 23 | _err "Deploy cert to apache server, Not implemented yet" 24 | return 1 25 | 26 | } 27 | -------------------------------------------------------------------------------- /deploy/haproxy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to haproxy server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | ######## Public functions ##################### 8 | 9 | #domain keyfile certfile cafile fullchain 10 | haproxy_deploy() { 11 | _cdomain="$1" 12 | _ckey="$2" 13 | _ccert="$3" 14 | _cca="$4" 15 | _cfullchain="$5" 16 | 17 | _debug _cdomain "$_cdomain" 18 | _debug _ckey "$_ckey" 19 | _debug _ccert "$_ccert" 20 | _debug _cca "$_cca" 21 | _debug _cfullchain "$_cfullchain" 22 | 23 | _err "deploy cert to haproxy server, Not implemented yet" 24 | return 1 25 | 26 | } 27 | -------------------------------------------------------------------------------- /deploy/mysqld.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to mysqld server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | ######## Public functions ##################### 8 | 9 | #domain keyfile certfile cafile fullchain 10 | mysqld_deploy() { 11 | _cdomain="$1" 12 | _ckey="$2" 13 | _ccert="$3" 14 | _cca="$4" 15 | _cfullchain="$5" 16 | 17 | _debug _cdomain "$_cdomain" 18 | _debug _ckey "$_ckey" 19 | _debug _ccert "$_ccert" 20 | _debug _cca "$_cca" 21 | _debug _cfullchain "$_cfullchain" 22 | 23 | _err "deploy cert to mysqld server, Not implemented yet" 24 | return 1 25 | 26 | } 27 | -------------------------------------------------------------------------------- /deploy/opensshd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to opensshd server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | ######## Public functions ##################### 8 | 9 | #domain keyfile certfile cafile fullchain 10 | opensshd_deploy() { 11 | _cdomain="$1" 12 | _ckey="$2" 13 | _ccert="$3" 14 | _cca="$4" 15 | _cfullchain="$5" 16 | 17 | _debug _cdomain "$_cdomain" 18 | _debug _ckey "$_ckey" 19 | _debug _ccert "$_ccert" 20 | _debug _cca "$_cca" 21 | _debug _cfullchain "$_cfullchain" 22 | 23 | _err "deploy cert to opensshd server, Not implemented yet" 24 | return 1 25 | 26 | } 27 | -------------------------------------------------------------------------------- /deploy/pureftpd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to pureftpd server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | ######## Public functions ##################### 8 | 9 | #domain keyfile certfile cafile fullchain 10 | pureftpd_deploy() { 11 | _cdomain="$1" 12 | _ckey="$2" 13 | _ccert="$3" 14 | _cca="$4" 15 | _cfullchain="$5" 16 | 17 | _debug _cdomain "$_cdomain" 18 | _debug _ckey "$_ckey" 19 | _debug _ccert "$_ccert" 20 | _debug _cca "$_cca" 21 | _debug _cfullchain "$_cfullchain" 22 | 23 | _err "deploy cert to pureftpd server, Not implemented yet" 24 | return 1 25 | 26 | } 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | Steps to reproduce 16 | ------------------ 17 | 18 | Debug log 19 | ----------------- 20 | 21 | ``` 22 | acme.sh --issue ..... --debug 2 23 | ``` 24 | 25 | 26 | -------------------------------------------------------------------------------- /deploy/myapi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a sample custom api script. 4 | #This file name is "myapi.sh" 5 | #So, here must be a method myapi_deploy() 6 | #Which will be called by acme.sh to deploy the cert 7 | #returns 0 means success, otherwise error. 8 | 9 | ######## Public functions ##################### 10 | 11 | #domain keyfile certfile cafile fullchain 12 | myapi_deploy() { 13 | _cdomain="$1" 14 | _ckey="$2" 15 | _ccert="$3" 16 | _cca="$4" 17 | _cfullchain="$5" 18 | 19 | _debug _cdomain "$_cdomain" 20 | _debug _ckey "$_ckey" 21 | _debug _ccert "$_ccert" 22 | _debug _cca "$_cca" 23 | _debug _cfullchain "$_cfullchain" 24 | 25 | _err "Not implemented yet" 26 | return 1 27 | 28 | } 29 | -------------------------------------------------------------------------------- /deploy/cpanel.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is the script to deploy the cert to your cpanel account by the cpanel APIs. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | #export DEPLOY_CPANEL_USER=myusername 8 | #export DEPLOY_CPANEL_PASSWORD=PASSWORD 9 | 10 | ######## Public functions ##################### 11 | 12 | #domain keyfile certfile cafile fullchain 13 | cpanel_deploy() { 14 | _cdomain="$1" 15 | _ckey="$2" 16 | _ccert="$3" 17 | _cca="$4" 18 | _cfullchain="$5" 19 | 20 | _debug _cdomain "$_cdomain" 21 | _debug _ckey "$_ckey" 22 | _debug _ccert "$_ccert" 23 | _debug _cca "$_cca" 24 | _debug _cfullchain "$_cfullchain" 25 | 26 | _err "Not implemented yet" 27 | return 1 28 | 29 | } 30 | -------------------------------------------------------------------------------- /deploy/keychain.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a sample custom api script. 4 | #This file name is "myapi.sh" 5 | #So, here must be a method myapi_deploy() 6 | #Which will be called by acme.sh to deploy the cert 7 | #returns 0 means success, otherwise error. 8 | 9 | ######## Public functions ##################### 10 | 11 | #domain keyfile certfile cafile fullchain 12 | keychain_deploy() { 13 | _cdomain="$1" 14 | _ckey="$2" 15 | _ccert="$3" 16 | _cca="$4" 17 | _cfullchain="$5" 18 | 19 | _debug _cdomain "$_cdomain" 20 | _debug _ckey "$_ckey" 21 | _debug _ccert "$_ccert" 22 | _debug _cca "$_cca" 23 | _debug _cfullchain "$_cfullchain" 24 | 25 | /usr/bin/security import "$_ckey" -k "/Library/Keychains/System.keychain" 26 | /usr/bin/security import "$_ccert" -k "/Library/Keychains/System.keychain" 27 | /usr/bin/security import "$_cca" -k "/Library/Keychains/System.keychain" 28 | /usr/bin/security import "$_cfullchain" -k "/Library/Keychains/System.keychain" 29 | 30 | return 0 31 | } 32 | -------------------------------------------------------------------------------- /dnsapi/dns_myapi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a sample custom api script. 4 | #This file name is "dns_myapi.sh" 5 | #So, here must be a method dns_myapi_add() 6 | #Which will be called by acme.sh to add the txt record to your api system. 7 | #returns 0 means success, otherwise error. 8 | # 9 | #Author: Neilpang 10 | #Report Bugs here: https://github.com/Neilpang/acme.sh 11 | # 12 | ######## Public functions ##################### 13 | 14 | #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 15 | dns_myapi_add() { 16 | fulldomain=$1 17 | txtvalue=$2 18 | _info "Using myapi" 19 | _debug fulldomain "$fulldomain" 20 | _debug txtvalue "$txtvalue" 21 | _err "Not implemented!" 22 | return 1 23 | } 24 | 25 | #Usage: fulldomain txtvalue 26 | #Remove the txt record after validation. 27 | dns_myapi_rm() { 28 | fulldomain=$1 29 | txtvalue=$2 30 | _info "Using myapi" 31 | _debug fulldomain "$fulldomain" 32 | _debug txtvalue "$txtvalue" 33 | } 34 | 35 | #################### Private functions below ################################## 36 | -------------------------------------------------------------------------------- /dnsapi/dns_nsupdate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ######## Public functions ##################### 4 | 5 | #Usage: dns_nsupdate_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 6 | dns_nsupdate_add() { 7 | fulldomain=$1 8 | txtvalue=$2 9 | _checkKeyFile || return 1 10 | [ -n "${NSUPDATE_SERVER}" ] || NSUPDATE_SERVER="localhost" 11 | # save the dns server and key to the account conf file. 12 | _saveaccountconf NSUPDATE_SERVER "${NSUPDATE_SERVER}" 13 | _saveaccountconf NSUPDATE_KEY "${NSUPDATE_KEY}" 14 | _info "adding ${fulldomain}. 60 in txt \"${txtvalue}\"" 15 | nsupdate -k "${NSUPDATE_KEY}" <&1 || true; 31 | $ACME_OPENSSL_BIN version 2>&1 || true; 32 | export PATH="$_old_path"; 33 | fi 34 | 35 | script: 36 | - echo "NGROK_TOKEN=$(echo "$NGROK_TOKEN" | wc -c)" 37 | - command -V openssl && openssl version 38 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then curl -sSL $SHFMT_URL -o ~/shfmt ; fi 39 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then chmod +x ~/shfmt ; fi 40 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then ~/shfmt -l -w -i 2 . ; fi 41 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then git diff --exit-code && echo "shfmt OK" ; fi 42 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck -V ; fi 43 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then shellcheck **/*.sh && echo "shellcheck OK" ; fi 44 | - cd .. 45 | - git clone https://github.com/Neilpang/acmetest.git && cp -r acme.sh acmetest/ && cd acmetest 46 | - if [ "$TRAVIS_OS_NAME" = "linux" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ./letest.sh ; fi 47 | - if [ "$TRAVIS_OS_NAME" = "osx" -a "$NGROK_TOKEN" ]; then sudo TEST_LOCAL="$TEST_LOCAL" NGROK_TOKEN="$NGROK_TOKEN" ACME_OPENSSL_BIN="$ACME_OPENSSL_BIN" ./letest.sh ; fi 48 | 49 | 50 | matrix: 51 | fast_finish: true 52 | 53 | 54 | -------------------------------------------------------------------------------- /deploy/README.md: -------------------------------------------------------------------------------- 1 | # Using deploy api 2 | 3 | Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). 4 | 5 | Here are the scripts to deploy the certs/key to the server/services. 6 | 7 | ## 1. Deploy the certs to your cpanel host. 8 | 9 | (cpanel deploy hook is not finished yet, this is just an example.) 10 | 11 | 12 | 13 | Then you can deploy now: 14 | 15 | ```sh 16 | export DEPLOY_CPANEL_USER=myusername 17 | export DEPLOY_CPANEL_PASSWORD=PASSWORD 18 | acme.sh --deploy -d example.com --deploy-hook cpanel 19 | ``` 20 | 21 | ## 2. Deploy ssl cert on kong proxy engine based on api. 22 | 23 | Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). 24 | 25 | (TODO) 26 | 27 | ## 3. Deploy the cert to remote server through SSH access. 28 | 29 | (TODO) 30 | 31 | ## 4. Deploy the cert to local vsftpd server. 32 | 33 | ```sh 34 | acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd 35 | ``` 36 | 37 | The default vsftpd conf file is `/etc/vsftpd.conf`, if your vsftpd conf is not in the default location, you can specify one: 38 | 39 | ```sh 40 | export DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf" 41 | 42 | acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd 43 | ``` 44 | 45 | The default command to restart vsftpd server is `service vsftpd restart`, if it doesn't work, you can specify one: 46 | 47 | ```sh 48 | export DEPLOY_VSFTPD_RELOAD="/etc/init.d/vsftpd restart" 49 | 50 | acme.sh --deploy -d ftp.example.com --deploy-hook vsftpd 51 | ``` 52 | 53 | ## 5. Deploy the cert to local exim4 server. 54 | 55 | ```sh 56 | acme.sh --deploy -d ftp.example.com --deploy-hook exim4 57 | ``` 58 | 59 | The default exim4 conf file is `/etc/exim/exim.conf`, if your exim4 conf is not in the default location, you can specify one: 60 | 61 | ```sh 62 | export DEPLOY_EXIM4_CONF="/etc/exim4/exim4.conf.template" 63 | 64 | acme.sh --deploy -d ftp.example.com --deploy-hook exim4 65 | ``` 66 | 67 | The default command to restart exim4 server is `service exim4 restart`, if it doesn't work, you can specify one: 68 | 69 | ```sh 70 | export DEPLOY_EXIM4_RELOAD="/etc/init.d/exim4 restart" 71 | 72 | acme.sh --deploy -d ftp.example.com --deploy-hook exim4 73 | ``` 74 | 75 | ## 6. Deploy the cert to OSX Keychain 76 | 77 | ```sh 78 | acme.sh --deploy -d ftp.example.com --deploy-hook keychain 79 | ``` 80 | -------------------------------------------------------------------------------- /dnsapi/dns_knot.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ######## Public functions ##################### 4 | 5 | #Usage: dns_knot_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 6 | dns_knot_add() { 7 | fulldomain=$1 8 | txtvalue=$2 9 | _checkKey || return 1 10 | [ -n "${KNOT_SERVER}" ] || KNOT_SERVER="localhost" 11 | # save the dns server and key to the account.conf file. 12 | _saveaccountconf KNOT_SERVER "${KNOT_SERVER}" 13 | _saveaccountconf KNOT_KEY "${KNOT_KEY}" 14 | 15 | if ! _get_root "$fulldomain"; then 16 | _err "Domain does not exist." 17 | return 1 18 | fi 19 | 20 | _info "Adding ${fulldomain}. 60 TXT \"${txtvalue}\"" 21 | 22 | knsupdate -y "${KNOT_KEY}" < 10 | #Report Bugs here: https://github.com/fcrozat/acme.sh 11 | # 12 | ######## Public functions ##################### 13 | 14 | GANDI_LIVEDNS_API="https://dns.beta.gandi.net/api/v5" 15 | 16 | #Usage: dns_gandi_livedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 17 | dns_gandi_livedns_add() { 18 | fulldomain=$1 19 | txtvalue=$2 20 | 21 | if [ -z "$GANDI_LIVEDNS_KEY" ]; then 22 | _err "No API key specifed for Gandi LiveDNS." 23 | _err "Create your key and export it as GANDI_LIVEDNS_KEY" 24 | return 1 25 | fi 26 | 27 | _saveaccountconf GANDI_LIVEDNS_KEY "$GANDI_LIVEDNS_KEY" 28 | 29 | _debug "First detect the root zone" 30 | if ! _get_root "$fulldomain"; then 31 | _err "invalid domain" 32 | return 1 33 | fi 34 | _debug fulldomain "$fulldomain" 35 | _debug txtvalue "$txtvalue" 36 | _debug domain "$_domain" 37 | _debug sub_domain "$_sub_domain" 38 | 39 | _gandi_livedns_rest PUT "domains/$_domain/records/$_sub_domain/TXT" "{\"rrset_ttl\": 300, \"rrset_values\":[\"$txtvalue\"]}" \ 40 | && _contains "$response" '{"message": "Zone Record Created"}' \ 41 | && _info "Add $(__green "success")" 42 | } 43 | 44 | #Usage: fulldomain txtvalue 45 | #Remove the txt record after validation. 46 | dns_gandi_livedns_rm() { 47 | fulldomain=$1 48 | txtvalue=$2 49 | 50 | _debug "First detect the root zone" 51 | if ! _get_root "$fulldomain"; then 52 | _err "invalid domain" 53 | return 1 54 | fi 55 | 56 | _debug fulldomain "$fulldomain" 57 | _debug domain "$_domain" 58 | _debug sub_domain "$_sub_domain" 59 | 60 | _gandi_livedns_rest DELETE "domains/$_domain/records/$_sub_domain/TXT" "" 61 | 62 | } 63 | 64 | #################### Private functions below ################################## 65 | #_acme-challenge.www.domain.com 66 | #returns 67 | # _sub_domain=_acme-challenge.www 68 | # _domain=domain.com 69 | _get_root() { 70 | domain=$1 71 | i=2 72 | p=1 73 | while true; do 74 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 75 | _debug h "$h" 76 | if [ -z "$h" ]; then 77 | #not valid 78 | return 1 79 | fi 80 | 81 | if ! _gandi_livedns_rest GET "domains/$h"; then 82 | return 1 83 | fi 84 | 85 | if _contains "$response" '"code": 401'; then 86 | _err "$response" 87 | return 1 88 | elif _contains "$response" '"code": 404'; then 89 | _debug "$h not found" 90 | else 91 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 92 | _domain="$h" 93 | return 0 94 | fi 95 | p="$i" 96 | i=$(_math "$i" + 1) 97 | done 98 | return 1 99 | } 100 | 101 | _gandi_livedns_rest() { 102 | m=$1 103 | ep="$2" 104 | data="$3" 105 | _debug "$ep" 106 | 107 | export _H1="Content-Type: application/json" 108 | export _H2="X-Api-Key: $GANDI_LIVEDNS_KEY" 109 | 110 | if [ "$m" = "GET" ]; then 111 | response="$(_get "$GANDI_LIVEDNS_API/$ep")" 112 | else 113 | _debug data "$data" 114 | response="$(_post "$data" "$GANDI_LIVEDNS_API/$ep" "" "$m")" 115 | fi 116 | 117 | if [ "$?" != "0" ]; then 118 | _err "error $ep" 119 | return 1 120 | fi 121 | _debug2 response "$response" 122 | return 0 123 | } 124 | -------------------------------------------------------------------------------- /deploy/kong.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This deploy hook will deploy ssl cert on kong proxy engine based on api request_host parameter. 4 | # Note that ssl plugin should be available on Kong instance 5 | # The hook will match cdomain to request_host, in case of multiple domain it will always take the first 6 | # one (acme.sh behaviour). 7 | # If ssl config already exist it will update only cert and key not touching other parameter 8 | # If ssl config doesn't exist it will only upload cert and key and not set other parameter 9 | # Not that we deploy full chain 10 | # See https://getkong.org/plugins/dynamic-ssl/ for other options 11 | # Written by Geoffroi Genot 12 | 13 | ######## Public functions ##################### 14 | 15 | #domain keyfile certfile cafile fullchain 16 | kong_deploy() { 17 | _cdomain="$1" 18 | _ckey="$2" 19 | _ccert="$3" 20 | _cca="$4" 21 | _cfullchain="$5" 22 | _info "Deploying certificate on Kong instance" 23 | if [ -z "$KONG_URL" ]; then 24 | _debug "KONG_URL Not set, using default http://localhost:8001" 25 | KONG_URL="http://localhost:8001" 26 | fi 27 | 28 | _debug _cdomain "$_cdomain" 29 | _debug _ckey "$_ckey" 30 | _debug _ccert "$_ccert" 31 | _debug _cca "$_cca" 32 | _debug _cfullchain "$_cfullchain" 33 | 34 | #Get uuid linked to the domain 35 | 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}') 36 | if [ -z "$uuid" ]; then 37 | _err "Unable to get Kong uuid for domain $_cdomain" 38 | _err "Make sure that KONG_URL is correctly configured" 39 | _err "Make sure that a Kong api request_host match the domain" 40 | _err "Kong url: $KONG_URL" 41 | return 1 42 | fi 43 | #Save kong url if it's succesful (First run case) 44 | _saveaccountconf KONG_URL "$KONG_URL" 45 | #Generate DEIM 46 | delim="-----MultipartDelimeter$(date "+%s%N")" 47 | nl="\015\012" 48 | #Set Header 49 | _H1="Content-Type: multipart/form-data; boundary=$delim" 50 | #Generate data for request (Multipart/form-data with mixed content) 51 | #set name to ssl 52 | content="--$delim${nl}Content-Disposition: form-data; name=\"name\"${nl}${nl}ssl" 53 | #add key 54 | 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")" 55 | #Add cert 56 | 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")" 57 | #Close multipart 58 | content="$content${nl}--$delim--${nl}" 59 | #Convert CRLF 60 | content=$(printf %b "$content") 61 | #DEBUG 62 | _debug header "$_H1" 63 | _debug content "$content" 64 | #Check if ssl plugins is aready enabled (if not => POST else => PATCH) 65 | 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}') 66 | _debug ssl_uuid "$ssl_uuid" 67 | if [ -z "$ssl_uuid" ]; then 68 | #Post certificate to Kong 69 | response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST") 70 | else 71 | #patch 72 | response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH") 73 | fi 74 | if ! [ "$(echo "$response" | _egrep_o "ssl")" = "ssl" ]; then 75 | _err "An error occured with cert upload. Check response:" 76 | _err "$response" 77 | return 1 78 | fi 79 | _debug response "$response" 80 | _info "Certificate successfully deployed" 81 | } 82 | -------------------------------------------------------------------------------- /deploy/vsftpd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to vsftpd server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | #DEPLOY_VSFTPD_CONF="/etc/vsftpd.conf" 8 | #DEPLOY_VSFTPD_RELOAD="service vsftpd restart" 9 | 10 | ######## Public functions ##################### 11 | 12 | #domain keyfile certfile cafile fullchain 13 | vsftpd_deploy() { 14 | _cdomain="$1" 15 | _ckey="$2" 16 | _ccert="$3" 17 | _cca="$4" 18 | _cfullchain="$5" 19 | 20 | _debug _cdomain "$_cdomain" 21 | _debug _ckey "$_ckey" 22 | _debug _ccert "$_ccert" 23 | _debug _cca "$_cca" 24 | _debug _cfullchain "$_cfullchain" 25 | 26 | _ssl_path="/etc/acme.sh/vsftpd" 27 | if ! mkdir -p "$_ssl_path"; then 28 | _err "Can not create folder:$_ssl_path" 29 | return 1 30 | fi 31 | 32 | _info "Copying key and cert" 33 | _real_key="$_ssl_path/vsftpd.key" 34 | if ! cat "$_ckey" >"$_real_key"; then 35 | _err "Error: write key file to: $_real_key" 36 | return 1 37 | fi 38 | _real_fullchain="$_ssl_path/vsftpd.chain.pem" 39 | if ! cat "$_cfullchain" >"$_real_fullchain"; then 40 | _err "Error: write key file to: $_real_fullchain" 41 | return 1 42 | fi 43 | 44 | DEFAULT_VSFTPD_RELOAD="service vsftpd restart" 45 | _reload="${DEPLOY_VSFTPD_RELOAD:-$DEFAULT_VSFTPD_RELOAD}" 46 | 47 | if [ -z "$IS_RENEW" ]; then 48 | DEFAULT_VSFTPD_CONF="/etc/vsftpd.conf" 49 | _vsftpd_conf="${DEPLOY_VSFTPD_CONF:-$DEFAULT_VSFTPD_CONF}" 50 | if [ ! -f "$_vsftpd_conf" ]; then 51 | if [ -z "$DEPLOY_VSFTPD_CONF" ]; then 52 | _err "vsftpd conf is not found, please define DEPLOY_VSFTPD_CONF" 53 | return 1 54 | else 55 | _err "It seems that the specified vsftpd conf is not valid, please check." 56 | return 1 57 | fi 58 | fi 59 | if [ ! -w "$_vsftpd_conf" ]; then 60 | _err "The file $_vsftpd_conf is not writable, please change the permission." 61 | return 1 62 | fi 63 | _backup_conf="$DOMAIN_BACKUP_PATH/vsftpd.conf.bak" 64 | _info "Backup $_vsftpd_conf to $_backup_conf" 65 | cp "$_vsftpd_conf" "$_backup_conf" 66 | 67 | _info "Modify vsftpd conf: $_vsftpd_conf" 68 | if _setopt "$_vsftpd_conf" "rsa_cert_file" "=" "$_real_fullchain" \ 69 | && _setopt "$_vsftpd_conf" "rsa_private_key_file" "=" "$_real_key" \ 70 | && _setopt "$_vsftpd_conf" "ssl_enable" "=" "YES"; then 71 | _info "Set config success!" 72 | else 73 | _err "Config vsftpd server error, please report bug to us." 74 | _info "Restoring vsftpd conf" 75 | if cat "$_backup_conf" >"$_vsftpd_conf"; then 76 | _info "Restore conf success" 77 | eval "$_reload" 78 | else 79 | _err "Opps, error restore vsftpd conf, please report bug to us." 80 | fi 81 | return 1 82 | fi 83 | fi 84 | 85 | _info "Run reload: $_reload" 86 | if eval "$_reload"; then 87 | _info "Reload success!" 88 | if [ "$DEPLOY_VSFTPD_CONF" ]; then 89 | _savedomainconf DEPLOY_VSFTPD_CONF "$DEPLOY_VSFTPD_CONF" 90 | else 91 | _cleardomainconf DEPLOY_VSFTPD_CONF 92 | fi 93 | if [ "$DEPLOY_VSFTPD_RELOAD" ]; then 94 | _savedomainconf DEPLOY_VSFTPD_RELOAD "$DEPLOY_VSFTPD_RELOAD" 95 | else 96 | _cleardomainconf DEPLOY_VSFTPD_RELOAD 97 | fi 98 | return 0 99 | else 100 | _err "Reload error, restoring" 101 | if cat "$_backup_conf" >"$_vsftpd_conf"; then 102 | _info "Restore conf success" 103 | eval "$_reload" 104 | else 105 | _err "Opps, error restore vsftpd conf, please report bug to us." 106 | fi 107 | return 1 108 | fi 109 | return 0 110 | } 111 | -------------------------------------------------------------------------------- /deploy/exim4.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #Here is a script to deploy cert to exim4 server. 4 | 5 | #returns 0 means success, otherwise error. 6 | 7 | #DEPLOY_EXIM4_CONF="/etc/exim/exim.conf" 8 | #DEPLOY_EXIM4_RELOAD="service exim4 restart" 9 | 10 | ######## Public functions ##################### 11 | 12 | #domain keyfile certfile cafile fullchain 13 | exim4_deploy() { 14 | _cdomain="$1" 15 | _ckey="$2" 16 | _ccert="$3" 17 | _cca="$4" 18 | _cfullchain="$5" 19 | 20 | _debug _cdomain "$_cdomain" 21 | _debug _ckey "$_ckey" 22 | _debug _ccert "$_ccert" 23 | _debug _cca "$_cca" 24 | _debug _cfullchain "$_cfullchain" 25 | 26 | _ssl_path="/etc/acme.sh/exim4" 27 | if ! mkdir -p "$_ssl_path"; then 28 | _err "Can not create folder:$_ssl_path" 29 | return 1 30 | fi 31 | 32 | _info "Copying key and cert" 33 | _real_key="$_ssl_path/exim4.key" 34 | if ! cat "$_ckey" >"$_real_key"; then 35 | _err "Error: write key file to: $_real_key" 36 | return 1 37 | fi 38 | _real_fullchain="$_ssl_path/exim4.pem" 39 | if ! cat "$_cfullchain" >"$_real_fullchain"; then 40 | _err "Error: write key file to: $_real_fullchain" 41 | return 1 42 | fi 43 | 44 | DEFAULT_EXIM4_RELOAD="service exim4 restart" 45 | _reload="${DEPLOY_EXIM4_RELOAD:-$DEFAULT_EXIM4_RELOAD}" 46 | 47 | if [ -z "$IS_RENEW" ]; then 48 | DEFAULT_EXIM4_CONF="/etc/exim/exim.conf" 49 | if [ ! -f "$DEFAULT_EXIM4_CONF" ]; then 50 | DEFAULT_EXIM4_CONF="/etc/exim4/exim4.conf.template" 51 | fi 52 | _exim4_conf="${DEPLOY_EXIM4_CONF:-$DEFAULT_EXIM4_CONF}" 53 | _debug _exim4_conf "$_exim4_conf" 54 | if [ ! -f "$_exim4_conf" ]; then 55 | if [ -z "$DEPLOY_EXIM4_CONF" ]; then 56 | _err "exim4 conf is not found, please define DEPLOY_EXIM4_CONF" 57 | return 1 58 | else 59 | _err "It seems that the specified exim4 conf is not valid, please check." 60 | return 1 61 | fi 62 | fi 63 | if [ ! -w "$_exim4_conf" ]; then 64 | _err "The file $_exim4_conf is not writable, please change the permission." 65 | return 1 66 | fi 67 | _backup_conf="$DOMAIN_BACKUP_PATH/exim4.conf.bak" 68 | _info "Backup $_exim4_conf to $_backup_conf" 69 | cp "$_exim4_conf" "$_backup_conf" 70 | 71 | _info "Modify exim4 conf: $_exim4_conf" 72 | if _setopt "$_exim4_conf" "tls_certificate" "=" "$_real_fullchain" \ 73 | && _setopt "$_exim4_conf" "tls_privatekey" "=" "$_real_key"; then 74 | _info "Set config success!" 75 | else 76 | _err "Config exim4 server error, please report bug to us." 77 | _info "Restoring exim4 conf" 78 | if cat "$_backup_conf" >"$_exim4_conf"; then 79 | _info "Restore conf success" 80 | eval "$_reload" 81 | else 82 | _err "Opps, error restore exim4 conf, please report bug to us." 83 | fi 84 | return 1 85 | fi 86 | fi 87 | 88 | _info "Run reload: $_reload" 89 | if eval "$_reload"; then 90 | _info "Reload success!" 91 | if [ "$DEPLOY_EXIM4_CONF" ]; then 92 | _savedomainconf DEPLOY_EXIM4_CONF "$DEPLOY_EXIM4_CONF" 93 | else 94 | _cleardomainconf DEPLOY_EXIM4_CONF 95 | fi 96 | if [ "$DEPLOY_EXIM4_RELOAD" ]; then 97 | _savedomainconf DEPLOY_EXIM4_RELOAD "$DEPLOY_EXIM4_RELOAD" 98 | else 99 | _cleardomainconf DEPLOY_EXIM4_RELOAD 100 | fi 101 | return 0 102 | else 103 | _err "Reload error, restoring" 104 | if cat "$_backup_conf" >"$_exim4_conf"; then 105 | _info "Restore conf success" 106 | eval "$_reload" 107 | else 108 | _err "Opps, error restore exim4 conf, please report bug to us." 109 | fi 110 | return 1 111 | fi 112 | return 0 113 | 114 | } 115 | -------------------------------------------------------------------------------- /dnsapi/dns_ad.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | #AD_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" 5 | 6 | #This is the Alwaysdata api wrapper for acme.sh 7 | # 8 | #Author: Paul Koppen 9 | #Report Bugs here: https://github.com/wpk-/acme.sh 10 | 11 | AD_API_URL="https://$AD_API_KEY:@api.alwaysdata.com/v1" 12 | 13 | ######## Public functions ##################### 14 | 15 | #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 16 | dns_ad_add() { 17 | fulldomain=$1 18 | txtvalue=$2 19 | 20 | if [ -z "$AD_API_KEY" ]; then 21 | AD_API_KEY="" 22 | _err "You didn't specify the AD api key yet." 23 | _err "Please create you key and try again." 24 | return 1 25 | fi 26 | 27 | _saveaccountconf AD_API_KEY "$AD_API_KEY" 28 | 29 | _debug "First detect the root zone" 30 | if ! _get_root "$fulldomain"; then 31 | _err "invalid domain" 32 | return 1 33 | fi 34 | _debug _domain_id "$_domain_id" 35 | _debug _sub_domain "$_sub_domain" 36 | _debug _domain "$_domain" 37 | 38 | _ad_tmpl_json="{\"domain\":$_domain_id,\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\"}" 39 | 40 | if _ad_rest POST "record/" "$_ad_tmpl_json" && [ -z "$response" ]; then 41 | _info "txt record updated success." 42 | return 0 43 | fi 44 | 45 | return 1 46 | } 47 | 48 | #fulldomain txtvalue 49 | dns_ad_rm() { 50 | fulldomain=$1 51 | txtvalue=$2 52 | 53 | _debug "First detect the root zone" 54 | if ! _get_root "$fulldomain"; then 55 | _err "invalid domain" 56 | return 1 57 | fi 58 | _debug _domain_id "$_domain_id" 59 | _debug _sub_domain "$_sub_domain" 60 | _debug _domain "$_domain" 61 | 62 | _debug "Getting txt records" 63 | _ad_rest GET "record/?domain=$_domain_id&name=$_sub_domain" 64 | 65 | if [ -n "$response" ]; then 66 | record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) 67 | _debug record_id "$record_id" 68 | if [ -z "$record_id" ]; then 69 | _err "Can not get record id to remove." 70 | return 1 71 | fi 72 | if _ad_rest DELETE "record/$record_id/" && [ -z "$response" ]; then 73 | _info "txt record deleted success." 74 | return 0 75 | fi 76 | _debug response "$response" 77 | return 1 78 | fi 79 | 80 | return 1 81 | } 82 | 83 | #################### Private functions below ################################## 84 | #_acme-challenge.www.domain.com 85 | #returns 86 | # _sub_domain=_acme-challenge.www 87 | # _domain=domain.com 88 | # _domain_id=12345 89 | _get_root() { 90 | domain=$1 91 | i=2 92 | p=1 93 | 94 | if _ad_rest GET "domain/"; then 95 | response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" 96 | while true; do 97 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 98 | _debug h "$h" 99 | if [ -z "$h" ]; then 100 | #not valid 101 | return 1 102 | fi 103 | 104 | hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")" 105 | if [ "$hostedzone" ]; then 106 | _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) 107 | if [ "$_domain_id" ]; then 108 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 109 | _domain=$h 110 | return 0 111 | fi 112 | return 1 113 | fi 114 | p=$i 115 | i=$(_math "$i" + 1) 116 | done 117 | fi 118 | return 1 119 | } 120 | 121 | #method uri qstr data 122 | _ad_rest() { 123 | mtd="$1" 124 | ep="$2" 125 | data="$3" 126 | 127 | _debug mtd "$mtd" 128 | _debug ep "$ep" 129 | 130 | export _H1="Accept: application/json" 131 | export _H2="Content-Type: application/json" 132 | 133 | if [ "$mtd" != "GET" ]; then 134 | # both POST and DELETE. 135 | _debug data "$data" 136 | response="$(_post "$data" "$AD_API_URL/$ep" "" "$mtd")" 137 | else 138 | response="$(_get "$AD_API_URL/$ep")" 139 | fi 140 | 141 | if [ "$?" != "0" ]; then 142 | _err "error $ep" 143 | return 1 144 | fi 145 | _debug2 response "$response" 146 | return 0 147 | } 148 | -------------------------------------------------------------------------------- /dnsapi/dns_do.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # DNS API for Domain-Offensive / Resellerinterface / Domainrobot 4 | 5 | # Report bugs at https://github.com/seidler2547/acme.sh/issues 6 | 7 | # set these environment variables to match your customer ID and password: 8 | # DO_PID="KD-1234567" 9 | # DO_PW="cdfkjl3n2" 10 | 11 | DO_URL="https://soap.resellerinterface.de/" 12 | 13 | ######## Public functions ##################### 14 | 15 | #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 16 | dns_do_add() { 17 | fulldomain=$1 18 | txtvalue=$2 19 | if _dns_do_authenticate; then 20 | _info "Adding TXT record to ${_domain} as ${fulldomain}" 21 | _dns_do_soap createRR origin "${_domain}" name "${fulldomain}" type TXT data "${txtvalue}" ttl 300 22 | if _contains "${response}" '>success<'; then 23 | return 0 24 | fi 25 | _err "Could not create resource record, check logs" 26 | fi 27 | return 1 28 | } 29 | 30 | #fulldomain 31 | dns_do_rm() { 32 | fulldomain=$1 33 | if _dns_do_authenticate; then 34 | if _dns_do_list_rrs; then 35 | _dns_do_had_error=0 36 | for _rrid in ${_rr_list}; do 37 | _info "Deleting resource record $_rrid for $_domain" 38 | _dns_do_soap deleteRR origin "${_domain}" rrid "${_rrid}" 39 | if ! _contains "${response}" '>success<'; then 40 | _dns_do_had_error=1 41 | _err "Could not delete resource record for ${_domain}, id ${_rrid}" 42 | fi 43 | done 44 | return $_dns_do_had_error 45 | fi 46 | fi 47 | return 1 48 | } 49 | 50 | #################### Private functions below ################################## 51 | _dns_do_authenticate() { 52 | _info "Authenticating as ${DO_PID}" 53 | _dns_do_soap authPartner partner "${DO_PID}" password "${DO_PW}" 54 | if _contains "${response}" '>success<'; then 55 | _get_root "$fulldomain" 56 | _debug "_domain $_domain" 57 | return 0 58 | else 59 | _err "Authentication failed, are DO_PID and DO_PW set correctly?" 60 | fi 61 | return 1 62 | } 63 | 64 | _dns_do_list_rrs() { 65 | _dns_do_soap getRRList origin "${_domain}" 66 | if ! _contains "${response}" 'SOAP-ENC:Array'; then 67 | _err "getRRList origin ${_domain} failed" 68 | return 1 69 | fi 70 | _rr_list="$(echo "${response}" \ 71 | | tr -d "\n\r\t" \ 72 | | sed -e 's//\n/g' \ 73 | | grep ">$(_regexcape "$fulldomain")" \ 74 | | sed -e 's/<\/item>/\n/g' \ 75 | | grep '>id[0-9]{1,16}<' \ 77 | | tr -d '><')" 78 | [ "${_rr_list}" ] 79 | } 80 | 81 | _dns_do_soap() { 82 | func="$1" 83 | shift 84 | # put the parameters to xml 85 | body="" 86 | while [ "$1" ]; do 87 | _k="$1" 88 | shift 89 | _v="$1" 90 | shift 91 | body="$body<$_k>$_v" 92 | done 93 | body="$body" 94 | _debug2 "SOAP request ${body}" 95 | 96 | # build SOAP XML 97 | _xml=' 98 | 99 | '"$body"' 100 | ' 101 | 102 | # set SOAP headers 103 | export _H1="SOAPAction: ${DO_URL}#${func}" 104 | 105 | if ! response="$(_post "${_xml}" "${DO_URL}")"; then 106 | _err "Error <$1>" 107 | return 1 108 | fi 109 | _debug2 "SOAP response $response" 110 | 111 | # retrieve cookie header 112 | _H2="$(_egrep_o 'Cookie: [^;]+' <"$HTTP_HEADER" | _head_n 1)" 113 | export _H2 114 | 115 | return 0 116 | } 117 | 118 | _get_root() { 119 | domain=$1 120 | i=1 121 | 122 | _dns_do_soap getDomainList 123 | _all_domains="$(echo "${response}" \ 124 | | tr -d "\n\r\t " \ 125 | | _egrep_o 'domain]+>[^<]+' \ 126 | | sed -e 's/^domain<\/key>]*>//g')" 127 | 128 | while true; do 129 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 130 | if [ -z "$h" ]; then 131 | return 1 132 | fi 133 | 134 | if _contains "${_all_domains}" "^$(_regexcape "$h")\$"; then 135 | _domain="$h" 136 | return 0 137 | fi 138 | 139 | i=$(_math $i + 1) 140 | done 141 | _debug "$domain not found" 142 | 143 | return 1 144 | } 145 | 146 | _regexcape() { 147 | echo "$1" | sed -e 's/\([]\.$*^[]\)/\\\1/g' 148 | } 149 | -------------------------------------------------------------------------------- /dnsapi/dns_pdns.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #PowerDNS Emdedded API 4 | #https://doc.powerdns.com/md/httpapi/api_spec/ 5 | # 6 | #PDNS_Url="http://ns.example.com:8081" 7 | #PDNS_ServerId="localhost" 8 | #PDNS_Token="0123456789ABCDEF" 9 | #PDNS_Ttl=60 10 | 11 | DEFAULT_PDNS_TTL=60 12 | 13 | ######## Public functions ##################### 14 | #Usage: add _acme-challenge.www.domain.com "123456789ABCDEF0000000000000000000000000000000000000" 15 | #fulldomain 16 | #txtvalue 17 | dns_pdns_add() { 18 | fulldomain=$1 19 | txtvalue=$2 20 | 21 | if [ -z "$PDNS_Url" ]; then 22 | PDNS_Url="" 23 | _err "You don't specify PowerDNS address." 24 | _err "Please set PDNS_Url and try again." 25 | return 1 26 | fi 27 | 28 | if [ -z "$PDNS_ServerId" ]; then 29 | PDNS_ServerId="" 30 | _err "You don't specify PowerDNS server id." 31 | _err "Please set you PDNS_ServerId and try again." 32 | return 1 33 | fi 34 | 35 | if [ -z "$PDNS_Token" ]; then 36 | PDNS_Token="" 37 | _err "You don't specify PowerDNS token." 38 | _err "Please create you PDNS_Token and try again." 39 | return 1 40 | fi 41 | 42 | if [ -z "$PDNS_Ttl" ]; then 43 | PDNS_Ttl="$DEFAULT_PDNS_TTL" 44 | fi 45 | 46 | #save the api addr and key to the account conf file. 47 | _saveaccountconf PDNS_Url "$PDNS_Url" 48 | _saveaccountconf PDNS_ServerId "$PDNS_ServerId" 49 | _saveaccountconf PDNS_Token "$PDNS_Token" 50 | 51 | if [ "$PDNS_Ttl" != "$DEFAULT_PDNS_TTL" ]; then 52 | _saveaccountconf PDNS_Ttl "$PDNS_Ttl" 53 | fi 54 | 55 | _debug "Detect root zone" 56 | if ! _get_root "$fulldomain"; then 57 | _err "invalid domain" 58 | return 1 59 | fi 60 | _debug _domain "$_domain" 61 | 62 | if ! set_record "$_domain" "$fulldomain" "$txtvalue"; then 63 | return 1 64 | fi 65 | 66 | return 0 67 | } 68 | 69 | #fulldomain 70 | dns_pdns_rm() { 71 | fulldomain=$1 72 | 73 | _debug "Detect root zone" 74 | if ! _get_root "$fulldomain"; then 75 | _err "invalid domain" 76 | return 1 77 | fi 78 | _debug _domain "$_domain" 79 | 80 | if ! rm_record "$_domain" "$fulldomain"; then 81 | return 1 82 | fi 83 | 84 | return 0 85 | } 86 | 87 | set_record() { 88 | _info "Adding record" 89 | root=$1 90 | full=$2 91 | txtvalue=$3 92 | 93 | if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"REPLACE\", \"name\": \"$full.\", \"type\": \"TXT\", \"ttl\": $PDNS_Ttl, \"records\": [{\"name\": \"$full.\", \"type\": \"TXT\", \"content\": \"\\\"$txtvalue\\\"\", \"disabled\": false, \"ttl\": $PDNS_Ttl}]}]}"; then 94 | _err "Set txt record error." 95 | return 1 96 | fi 97 | 98 | if ! notify_slaves "$root"; then 99 | return 1 100 | fi 101 | 102 | return 0 103 | } 104 | 105 | rm_record() { 106 | _info "Remove record" 107 | root=$1 108 | full=$2 109 | 110 | if ! _pdns_rest "PATCH" "/api/v1/servers/$PDNS_ServerId/zones/$root." "{\"rrsets\": [{\"changetype\": \"DELETE\", \"name\": \"$full.\", \"type\": \"TXT\"}]}"; then 111 | _err "Delete txt record error." 112 | return 1 113 | fi 114 | 115 | if ! notify_slaves "$root"; then 116 | return 1 117 | fi 118 | 119 | return 0 120 | } 121 | 122 | notify_slaves() { 123 | root=$1 124 | 125 | if ! _pdns_rest "PUT" "/api/v1/servers/$PDNS_ServerId/zones/$root./notify"; then 126 | _err "Notify slaves error." 127 | return 1 128 | fi 129 | 130 | return 0 131 | } 132 | 133 | #################### Private functions below ################################## 134 | #_acme-challenge.www.domain.com 135 | #returns 136 | # _domain=domain.com 137 | _get_root() { 138 | domain=$1 139 | i=1 140 | 141 | if _pdns_rest "GET" "/api/v1/servers/$PDNS_ServerId/zones"; then 142 | _zones_response="$response" 143 | fi 144 | 145 | while true; do 146 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 147 | if [ -z "$h" ]; then 148 | return 1 149 | fi 150 | 151 | if _contains "$_zones_response" "\"name\": \"$h.\""; then 152 | _domain="$h" 153 | return 0 154 | fi 155 | 156 | i=$(_math $i + 1) 157 | done 158 | _debug "$domain not found" 159 | 160 | return 1 161 | } 162 | 163 | _pdns_rest() { 164 | method=$1 165 | ep=$2 166 | data=$3 167 | 168 | export _H1="X-API-Key: $PDNS_Token" 169 | 170 | if [ ! "$method" = "GET" ]; then 171 | _debug data "$data" 172 | response="$(_post "$data" "$PDNS_Url$ep" "" "$method")" 173 | else 174 | response="$(_get "$PDNS_Url$ep")" 175 | fi 176 | 177 | if [ "$?" != "0" ]; then 178 | _err "error $ep" 179 | return 1 180 | fi 181 | _debug2 response "$response" 182 | 183 | return 0 184 | } 185 | -------------------------------------------------------------------------------- /dnsapi/dns_me.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # bug reports to dev@1e.ca 4 | 5 | # ME_Key=qmlkdjflmkqdjf 6 | # ME_Secret=qmsdlkqmlksdvnnpae 7 | 8 | ME_Api=https://api.dnsmadeeasy.com/V2.0/dns/managed 9 | 10 | ######## Public functions ##################### 11 | 12 | #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 13 | dns_me_add() { 14 | fulldomain=$1 15 | txtvalue=$2 16 | 17 | if [ -z "$ME_Key" ] || [ -z "$ME_Secret" ]; then 18 | ME_Key="" 19 | ME_Secret="" 20 | _err "You didn't specify DNSMadeEasy api key and secret yet." 21 | _err "Please create you key and try again." 22 | return 1 23 | fi 24 | 25 | #save the api key and email to the account conf file. 26 | _saveaccountconf ME_Key "$ME_Key" 27 | _saveaccountconf ME_Secret "$ME_Secret" 28 | 29 | _debug "First detect the root zone" 30 | if ! _get_root "$fulldomain"; then 31 | _err "invalid domain" 32 | return 1 33 | fi 34 | _debug _domain_id "$_domain_id" 35 | _debug _sub_domain "$_sub_domain" 36 | _debug _domain "$_domain" 37 | 38 | _debug "Getting txt records" 39 | _me_rest GET "${_domain_id}/records?recordName=$_sub_domain&type=TXT" 40 | 41 | if ! _contains "$response" "\"totalRecords\":"; then 42 | _err "Error" 43 | return 1 44 | fi 45 | 46 | count=$(printf "%s\n" "$response" | _egrep_o "\"totalRecords\":[^,]*" | cut -d : -f 2) 47 | _debug count "$count" 48 | if [ "$count" = "0" ]; then 49 | _info "Adding record" 50 | if _me_rest POST "$_domain_id/records/" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}"; then 51 | if printf -- "%s" "$response" | grep \"id\": >/dev/null; then 52 | _info "Added" 53 | #todo: check if the record takes effect 54 | return 0 55 | else 56 | _err "Add txt record error." 57 | return 1 58 | fi 59 | fi 60 | _err "Add txt record error." 61 | else 62 | _info "Updating record" 63 | record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | head -n 1) 64 | _debug "record_id" "$record_id" 65 | 66 | _me_rest PUT "$_domain_id/records/$record_id/" "{\"id\":\"$record_id\",\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"value\":\"$txtvalue\",\"gtdLocation\":\"DEFAULT\",\"ttl\":120}" 67 | if [ "$?" = "0" ]; then 68 | _info "Updated" 69 | #todo: check if the record takes effect 70 | return 0 71 | fi 72 | _err "Update error" 73 | return 1 74 | fi 75 | 76 | } 77 | 78 | #fulldomain 79 | dns_me_rm() { 80 | fulldomain=$1 81 | txtvalue=$2 82 | _debug "First detect the root zone" 83 | if ! _get_root "$fulldomain"; then 84 | _err "invalid domain" 85 | return 1 86 | fi 87 | _debug _domain_id "$_domain_id" 88 | _debug _sub_domain "$_sub_domain" 89 | _debug _domain "$_domain" 90 | 91 | _debug "Getting txt records" 92 | _me_rest GET "${_domain_id}/records?recordName=$_sub_domain&type=TXT" 93 | 94 | count=$(printf "%s\n" "$response" | _egrep_o "\"totalRecords\":[^,]*" | cut -d : -f 2) 95 | _debug count "$count" 96 | if [ "$count" = "0" ]; then 97 | _info "Don't need to remove." 98 | else 99 | record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | cut -d : -f 2 | head -n 1) 100 | _debug "record_id" "$record_id" 101 | if [ -z "$record_id" ]; then 102 | _err "Can not get record id to remove." 103 | return 1 104 | fi 105 | if ! _me_rest DELETE "$_domain_id/records/$record_id"; then 106 | _err "Delete record error." 107 | return 1 108 | fi 109 | _contains "$response" '' 110 | fi 111 | } 112 | 113 | #################### Private functions below ################################## 114 | #_acme-challenge.www.domain.com 115 | #returns 116 | # _sub_domain=_acme-challenge.www 117 | # _domain=domain.com 118 | # _domain_id=sdjkglgdfewsdfg 119 | _get_root() { 120 | domain=$1 121 | i=2 122 | p=1 123 | while true; do 124 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 125 | if [ -z "$h" ]; then 126 | #not valid 127 | return 1 128 | fi 129 | 130 | if ! _me_rest GET "name?domainname=$h"; then 131 | return 1 132 | fi 133 | 134 | if _contains "$response" "\"name\":\"$h\""; then 135 | _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*" | head -n 1 | cut -d : -f 2 | tr -d '}') 136 | if [ "$_domain_id" ]; then 137 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 138 | _domain="$h" 139 | return 0 140 | fi 141 | return 1 142 | fi 143 | p=$i 144 | i=$(_math "$i" + 1) 145 | done 146 | return 1 147 | } 148 | 149 | _me_rest() { 150 | m=$1 151 | ep="$2" 152 | data="$3" 153 | _debug "$ep" 154 | 155 | cdate=$(date -u +"%a, %d %b %Y %T %Z") 156 | hmac=$(printf "%s" "$cdate" | _hmac sha1 "$(printf "%s" "$ME_Secret" | _hex_dump | tr -d " ")" hex) 157 | 158 | export _H1="x-dnsme-apiKey: $ME_Key" 159 | export _H2="x-dnsme-requestDate: $cdate" 160 | export _H3="x-dnsme-hmac: $hmac" 161 | 162 | if [ "$m" != "GET" ]; then 163 | _debug data "$data" 164 | response="$(_post "$data" "$ME_Api/$ep" "" "$m")" 165 | else 166 | response="$(_get "$ME_Api/$ep")" 167 | fi 168 | 169 | if [ "$?" != "0" ]; then 170 | _err "error $ep" 171 | return 1 172 | fi 173 | _debug2 response "$response" 174 | return 0 175 | } 176 | -------------------------------------------------------------------------------- /dnsapi/dns_lua.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # bug reports to dev@1e.ca 4 | 5 | # 6 | #LUA_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" 7 | # 8 | #LUA_Email="user@luadns.net" 9 | 10 | LUA_Api="https://api.luadns.com/v1" 11 | LUA_auth=$(printf "%s" "$LUA_Email:$LUA_Key" | _base64) 12 | 13 | ######## Public functions ##################### 14 | 15 | #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 16 | dns_lua_add() { 17 | fulldomain=$1 18 | txtvalue=$2 19 | 20 | if [ -z "$LUA_Key" ] || [ -z "$LUA_Email" ]; then 21 | LUA_Key="" 22 | LUA_Email="" 23 | _err "You don't specify luadns api key and email yet." 24 | _err "Please create you key and try again." 25 | return 1 26 | fi 27 | 28 | #save the api key and email to the account conf file. 29 | _saveaccountconf LUA_Key "$LUA_Key" 30 | _saveaccountconf LUA_Email "$LUA_Email" 31 | 32 | _debug "First detect the root zone" 33 | if ! _get_root "$fulldomain"; then 34 | _err "invalid domain" 35 | return 1 36 | fi 37 | _debug _domain_id "$_domain_id" 38 | _debug _sub_domain "$_sub_domain" 39 | _debug _domain "$_domain" 40 | 41 | _debug "Getting txt records" 42 | _LUA_rest GET "zones/${_domain_id}/records" 43 | 44 | if ! _contains "$response" "\"id\":"; then 45 | _err "Error" 46 | return 1 47 | fi 48 | 49 | count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ") 50 | _debug count "$count" 51 | if [ "$count" = "0" ]; then 52 | _info "Adding record" 53 | if _LUA_rest POST "zones/$_domain_id/records" "{\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"ttl\":120}"; then 54 | if _contains "$response" "$fulldomain"; then 55 | _info "Added" 56 | #todo: check if the record takes effect 57 | return 0 58 | else 59 | _err "Add txt record error." 60 | return 1 61 | fi 62 | fi 63 | _err "Add txt record error." 64 | else 65 | _info "Updating record" 66 | record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | _head_n 1 | cut -d: -f2 | cut -d, -f1) 67 | _debug "record_id" "$record_id" 68 | 69 | _LUA_rest PUT "zones/$_domain_id/records/$record_id" "{\"id\":$record_id,\"type\":\"TXT\",\"name\":\"$fulldomain.\",\"content\":\"$txtvalue\",\"zone_id\":$_domain_id,\"ttl\":120}" 70 | if [ "$?" = "0" ] && _contains "$response" "updated_at"; then 71 | _info "Updated!" 72 | #todo: check if the record takes effect 73 | return 0 74 | fi 75 | _err "Update error" 76 | return 1 77 | fi 78 | 79 | } 80 | 81 | #fulldomain 82 | dns_lua_rm() { 83 | fulldomain=$1 84 | txtvalue=$2 85 | _debug "First detect the root zone" 86 | if ! _get_root "$fulldomain"; then 87 | _err "invalid domain" 88 | return 1 89 | fi 90 | _debug _domain_id "$_domain_id" 91 | _debug _sub_domain "$_sub_domain" 92 | _debug _domain "$_domain" 93 | 94 | _debug "Getting txt records" 95 | _LUA_rest GET "zones/${_domain_id}/records" 96 | 97 | count=$(printf "%s\n" "$response" | _egrep_o "\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | wc -l | tr -d " ") 98 | _debug count "$count" 99 | if [ "$count" = "0" ]; then 100 | _info "Don't need to remove." 101 | else 102 | record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$fulldomain.\",\"type\":\"TXT\"" | _head_n 1 | cut -d: -f2 | cut -d, -f1) 103 | _debug "record_id" "$record_id" 104 | if [ -z "$record_id" ]; then 105 | _err "Can not get record id to remove." 106 | return 1 107 | fi 108 | if ! _LUA_rest DELETE "/zones/$_domain_id/records/$record_id"; then 109 | _err "Delete record error." 110 | return 1 111 | fi 112 | _contains "$response" "$record_id" 113 | fi 114 | } 115 | 116 | #################### Private functions below ################################## 117 | #_acme-challenge.www.domain.com 118 | #returns 119 | # _sub_domain=_acme-challenge.www 120 | # _domain=domain.com 121 | # _domain_id=sdjkglgdfewsdfg 122 | _get_root() { 123 | domain=$1 124 | i=2 125 | p=1 126 | if ! _LUA_rest GET "zones"; then 127 | return 1 128 | fi 129 | while true; do 130 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 131 | _debug h "$h" 132 | if [ -z "$h" ]; then 133 | #not valid 134 | return 1 135 | fi 136 | 137 | if _contains "$response" "\"name\":\"$h\""; then 138 | _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":[^,]*,\"name\":\"$h\"" | cut -d : -f 2 | cut -d , -f 1) 139 | _debug _domain_id "$_domain_id" 140 | if [ "$_domain_id" ]; then 141 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 142 | _domain="$h" 143 | return 0 144 | fi 145 | return 1 146 | fi 147 | p=$i 148 | i=$(_math "$i" + 1) 149 | done 150 | return 1 151 | } 152 | 153 | _LUA_rest() { 154 | m=$1 155 | ep="$2" 156 | data="$3" 157 | _debug "$ep" 158 | 159 | export _H1="Accept: application/json" 160 | export _H2="Authorization: Basic $LUA_auth" 161 | if [ "$m" != "GET" ]; then 162 | _debug data "$data" 163 | response="$(_post "$data" "$LUA_Api/$ep" "" "$m")" 164 | else 165 | response="$(_get "$LUA_Api/$ep")" 166 | fi 167 | 168 | if [ "$?" != "0" ]; then 169 | _err "error $ep" 170 | return 1 171 | fi 172 | _debug2 response "$response" 173 | return 0 174 | } 175 | -------------------------------------------------------------------------------- /dnsapi/dns_ali.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | Ali_API="https://alidns.aliyuncs.com/" 4 | 5 | #Ali_Key="LTqIA87hOKdjevsf5" 6 | #Ali_Secret="0p5EYueFNq501xnCPzKNbx6K51qPH2" 7 | 8 | #Usage: dns_ali_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 9 | dns_ali_add() { 10 | fulldomain=$1 11 | txtvalue=$2 12 | 13 | if [ -z "$Ali_Key" ] || [ -z "$Ali_Secret" ]; then 14 | Ali_Key="" 15 | Ali_Secret="" 16 | _err "You don't specify aliyun api key and secret yet." 17 | return 1 18 | fi 19 | 20 | #save the api key and secret to the account conf file. 21 | _saveaccountconf Ali_Key "$Ali_Key" 22 | _saveaccountconf Ali_Secret "$Ali_Secret" 23 | 24 | _debug "First detect the root zone" 25 | if ! _get_root "$fulldomain"; then 26 | return 1 27 | fi 28 | 29 | _debug "Add record" 30 | _add_record_query "$_domain" "$_sub_domain" "$txtvalue" && _ali_rest "Add record" 31 | } 32 | 33 | dns_ali_rm() { 34 | fulldomain=$1 35 | _clean 36 | } 37 | 38 | #################### Private functions below ################################## 39 | 40 | _get_root() { 41 | domain=$1 42 | i=2 43 | p=1 44 | while true; do 45 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 46 | if [ -z "$h" ]; then 47 | #not valid 48 | return 1 49 | fi 50 | 51 | _describe_records_query "$h" 52 | if ! _ali_rest "Get root" "ignore"; then 53 | return 1 54 | fi 55 | 56 | if _contains "$response" "PageNumber"; then 57 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 58 | _debug _sub_domain "$_sub_domain" 59 | _domain="$h" 60 | _debug _domain "$_domain" 61 | return 0 62 | fi 63 | p="$i" 64 | i=$(_math "$i" + 1) 65 | done 66 | return 1 67 | } 68 | 69 | _ali_rest() { 70 | signature=$(printf "%s" "GET&%2F&$(_ali_urlencode "$query")" | _hmac "sha1" "$(printf "%s" "$Ali_Secret&" | _hex_dump | tr -d " ")" | _base64) 71 | signature=$(_ali_urlencode "$signature") 72 | url="$Ali_API?$query&Signature=$signature" 73 | 74 | if ! response="$(_get "$url")"; then 75 | _err "Error <$1>" 76 | return 1 77 | fi 78 | 79 | if [ -z "$2" ]; then 80 | message="$(printf "%s" "$response" | _egrep_o "\"Message\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \")" 81 | if [ -n "$message" ]; then 82 | _err "$message" 83 | return 1 84 | fi 85 | fi 86 | 87 | _debug2 response "$response" 88 | return 0 89 | } 90 | 91 | _ali_urlencode() { 92 | _str="$1" 93 | _str_len=${#_str} 94 | _u_i=1 95 | while [ "$_u_i" -le "$_str_len" ]; do 96 | _str_c="$(printf "%s" "$_str" | cut -c "$_u_i")" 97 | case $_str_c in [a-zA-Z0-9.~_-]) 98 | printf "%s" "$_str_c" 99 | ;; 100 | *) 101 | printf "%%%02X" "'$_str_c" 102 | ;; 103 | esac 104 | _u_i="$(_math "$_u_i" + 1)" 105 | done 106 | } 107 | 108 | _ali_nonce() { 109 | #_head_n 1 4 | 5 | LINODE_API_URL="https://api.linode.com/?api_key=$LINODE_API_KEY&api_action=" 6 | 7 | ######## Public functions ##################### 8 | 9 | #Usage: dns_linode_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 10 | dns_linode_add() { 11 | fulldomain="${1}" 12 | txtvalue="${2}" 13 | 14 | if ! _Linode_API; then 15 | return 1 16 | fi 17 | 18 | _info "Using Linode" 19 | _debug "Calling: dns_linode_add() '${fulldomain}' '${txtvalue}'" 20 | 21 | _debug "First detect the root zone" 22 | if ! _get_root "$fulldomain"; then 23 | _err "Domain does not exist." 24 | return 1 25 | fi 26 | _debug _domain_id "$_domain_id" 27 | _debug _sub_domain "$_sub_domain" 28 | _debug _domain "$_domain" 29 | 30 | _parameters="&DomainID=$_domain_id&Type=TXT&Name=$_sub_domain&Target=$txtvalue" 31 | 32 | if _rest GET "domain.resource.create" "$_parameters" && [ -n "$response" ]; then 33 | _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) 34 | _debug _resource_id "$_resource_id" 35 | 36 | if [ -z "$_resource_id" ]; then 37 | _err "Error adding the domain resource." 38 | return 1 39 | fi 40 | 41 | _info "Domain resource successfully added." 42 | return 0 43 | fi 44 | 45 | return 1 46 | } 47 | 48 | #Usage: dns_linode_rm _acme-challenge.www.domain.com 49 | dns_linode_rm() { 50 | fulldomain="${1}" 51 | 52 | if ! _Linode_API; then 53 | return 1 54 | fi 55 | 56 | _info "Using Linode" 57 | _debug "Calling: dns_linode_rm() '${fulldomain}'" 58 | 59 | _debug "First detect the root zone" 60 | if ! _get_root "$fulldomain"; then 61 | _err "Domain does not exist." 62 | return 1 63 | fi 64 | _debug _domain_id "$_domain_id" 65 | _debug _sub_domain "$_sub_domain" 66 | _debug _domain "$_domain" 67 | 68 | _parameters="&DomainID=$_domain_id" 69 | 70 | if _rest GET "domain.resource.list" "$_parameters" && [ -n "$response" ]; then 71 | response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" 72 | 73 | resource="$(echo "$response" | _egrep_o "{.*\"NAME\":\s*\"$_sub_domain\".*}")" 74 | if [ "$resource" ]; then 75 | _resource_id=$(printf "%s\n" "$resource" | _egrep_o "\"RESOURCEID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) 76 | if [ "$_resource_id" ]; then 77 | _debug _resource_id "$_resource_id" 78 | 79 | _parameters="&DomainID=$_domain_id&ResourceID=$_resource_id" 80 | 81 | if _rest GET "domain.resource.delete" "$_parameters" && [ -n "$response" ]; then 82 | _resource_id=$(printf "%s\n" "$response" | _egrep_o "\"ResourceID\":\s*[0-9]+" | cut -d : -f 2 | tr -d " " | _head_n 1) 83 | _debug _resource_id "$_resource_id" 84 | 85 | if [ -z "$_resource_id" ]; then 86 | _err "Error deleting the domain resource." 87 | return 1 88 | fi 89 | 90 | _info "Domain resource successfully deleted." 91 | return 0 92 | fi 93 | fi 94 | 95 | return 1 96 | fi 97 | 98 | return 0 99 | fi 100 | 101 | return 1 102 | } 103 | 104 | #################### Private functions below ################################## 105 | 106 | _Linode_API() { 107 | if [ -z "$LINODE_API_KEY" ]; then 108 | LINODE_API_KEY="" 109 | 110 | _err "You didn't specify the Linode API key yet." 111 | _err "Please create your key and try again." 112 | 113 | return 1 114 | fi 115 | 116 | _saveaccountconf LINODE_API_KEY "$LINODE_API_KEY" 117 | } 118 | 119 | #################### Private functions below ################################## 120 | #_acme-challenge.www.domain.com 121 | #returns 122 | # _sub_domain=_acme-challenge.www 123 | # _domain=domain.com 124 | # _domain_id=12345 125 | _get_root() { 126 | domain=$1 127 | i=2 128 | p=1 129 | 130 | if _rest GET "domain.list"; then 131 | response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" 132 | while true; do 133 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 134 | _debug h "$h" 135 | if [ -z "$h" ]; then 136 | #not valid 137 | return 1 138 | fi 139 | 140 | hostedzone="$(echo "$response" | _egrep_o "{.*\"DOMAIN\":\s*\"$h\".*}")" 141 | if [ "$hostedzone" ]; then 142 | _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"DOMAINID\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) 143 | if [ "$_domain_id" ]; then 144 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 145 | _domain=$h 146 | return 0 147 | fi 148 | return 1 149 | fi 150 | p=$i 151 | i=$(_math "$i" + 1) 152 | done 153 | fi 154 | return 1 155 | } 156 | 157 | #method method action data 158 | _rest() { 159 | mtd="$1" 160 | ep="$2" 161 | data="$3" 162 | 163 | _debug mtd "$mtd" 164 | _debug ep "$ep" 165 | 166 | export _H1="Accept: application/json" 167 | export _H2="Content-Type: application/json" 168 | 169 | if [ "$mtd" != "GET" ]; then 170 | # both POST and DELETE. 171 | _debug data "$data" 172 | response="$(_post "$data" "$LINODE_API_URL$ep" "" "$mtd")" 173 | else 174 | response="$(_get "$LINODE_API_URL$ep$data")" 175 | fi 176 | 177 | if [ "$?" != "0" ]; then 178 | _err "error $ep" 179 | return 1 180 | fi 181 | _debug2 response "$response" 182 | return 0 183 | } 184 | -------------------------------------------------------------------------------- /dnsapi/dns_cf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | #CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" 5 | # 6 | #CF_Email="xxxx@sss.com" 7 | 8 | CF_Api="https://api.cloudflare.com/client/v4" 9 | 10 | ######## Public functions ##################### 11 | 12 | #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 13 | dns_cf_add() { 14 | fulldomain=$1 15 | txtvalue=$2 16 | 17 | if [ -z "$CF_Key" ] || [ -z "$CF_Email" ]; then 18 | CF_Key="" 19 | CF_Email="" 20 | _err "You don't specify cloudflare api key and email yet." 21 | _err "Please create you key and try again." 22 | return 1 23 | fi 24 | 25 | if ! _contains "$CF_Email" "@"; then 26 | _err "It seems that the CF_Email=$CF_Email is not a valid email address." 27 | _err "Please check and retry." 28 | return 1 29 | fi 30 | 31 | #save the api key and email to the account conf file. 32 | _saveaccountconf CF_Key "$CF_Key" 33 | _saveaccountconf CF_Email "$CF_Email" 34 | 35 | _debug "First detect the root zone" 36 | if ! _get_root "$fulldomain"; then 37 | _err "invalid domain" 38 | return 1 39 | fi 40 | _debug _domain_id "$_domain_id" 41 | _debug _sub_domain "$_sub_domain" 42 | _debug _domain "$_domain" 43 | 44 | _debug "Getting txt records" 45 | _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain" 46 | 47 | if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then 48 | _err "Error" 49 | return 1 50 | fi 51 | 52 | count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) 53 | _debug count "$count" 54 | if [ "$count" = "0" ]; then 55 | _info "Adding record" 56 | if _cf_rest POST "zones/$_domain_id/dns_records" "{\"type\":\"TXT\",\"name\":\"$fulldomain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then 57 | if printf -- "%s" "$response" | grep "$fulldomain" >/dev/null; then 58 | _info "Added, OK" 59 | return 0 60 | else 61 | _err "Add txt record error." 62 | return 1 63 | fi 64 | fi 65 | _err "Add txt record error." 66 | else 67 | _info "Updating record" 68 | record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1) 69 | _debug "record_id" "$record_id" 70 | 71 | _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\"}" 72 | if [ "$?" = "0" ]; then 73 | _info "Updated, OK" 74 | return 0 75 | fi 76 | _err "Update error" 77 | return 1 78 | fi 79 | 80 | } 81 | 82 | #fulldomain txtvalue 83 | dns_cf_rm() { 84 | fulldomain=$1 85 | txtvalue=$2 86 | _debug "First detect the root zone" 87 | if ! _get_root "$fulldomain"; then 88 | _err "invalid domain" 89 | return 1 90 | fi 91 | _debug _domain_id "$_domain_id" 92 | _debug _sub_domain "$_sub_domain" 93 | _debug _domain "$_domain" 94 | 95 | _debug "Getting txt records" 96 | _cf_rest GET "zones/${_domain_id}/dns_records?type=TXT&name=$fulldomain&content=$txtvalue" 97 | 98 | if ! printf "%s" "$response" | grep \"success\":true >/dev/null; then 99 | _err "Error" 100 | return 1 101 | fi 102 | 103 | count=$(printf "%s\n" "$response" | _egrep_o "\"count\":[^,]*" | cut -d : -f 2) 104 | _debug count "$count" 105 | if [ "$count" = "0" ]; then 106 | _info "Don't need to remove." 107 | else 108 | record_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \" | head -n 1) 109 | _debug "record_id" "$record_id" 110 | if [ -z "$record_id" ]; then 111 | _err "Can not get record id to remove." 112 | return 1 113 | fi 114 | if ! _cf_rest DELETE "zones/$_domain_id/dns_records/$record_id"; then 115 | _err "Delete record error." 116 | return 1 117 | fi 118 | _contains "$response" '"success":true' 119 | fi 120 | 121 | } 122 | 123 | #################### Private functions below ################################## 124 | #_acme-challenge.www.domain.com 125 | #returns 126 | # _sub_domain=_acme-challenge.www 127 | # _domain=domain.com 128 | # _domain_id=sdjkglgdfewsdfg 129 | _get_root() { 130 | domain=$1 131 | i=2 132 | p=1 133 | while true; do 134 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 135 | _debug h "$h" 136 | if [ -z "$h" ]; then 137 | #not valid 138 | return 1 139 | fi 140 | 141 | if ! _cf_rest GET "zones?name=$h"; then 142 | return 1 143 | fi 144 | 145 | if _contains "$response" "\"name\":\"$h\"" >/dev/null; then 146 | _domain_id=$(printf "%s\n" "$response" | _egrep_o "\[.\"id\":\"[^\"]*\"" | head -n 1 | cut -d : -f 2 | tr -d \") 147 | if [ "$_domain_id" ]; then 148 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 149 | _domain=$h 150 | return 0 151 | fi 152 | return 1 153 | fi 154 | p=$i 155 | i=$(_math "$i" + 1) 156 | done 157 | return 1 158 | } 159 | 160 | _cf_rest() { 161 | m=$1 162 | ep="$2" 163 | data="$3" 164 | _debug "$ep" 165 | 166 | export _H1="X-Auth-Email: $CF_Email" 167 | export _H2="X-Auth-Key: $CF_Key" 168 | export _H3="Content-Type: application/json" 169 | 170 | if [ "$m" != "GET" ]; then 171 | _debug data "$data" 172 | response="$(_post "$data" "$CF_Api/$ep" "" "$m")" 173 | else 174 | response="$(_get "$CF_Api/$ep")" 175 | fi 176 | 177 | if [ "$?" != "0" ]; then 178 | _err "error $ep" 179 | return 1 180 | fi 181 | _debug2 response "$response" 182 | return 0 183 | } 184 | -------------------------------------------------------------------------------- /dnsapi/dns_cx.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Cloudxns.com Domain api 4 | # 5 | #CX_Key="1234" 6 | # 7 | #CX_Secret="sADDsdasdgdsf" 8 | 9 | CX_Api="https://www.cloudxns.net/api2" 10 | 11 | #REST_API 12 | ######## Public functions ##################### 13 | 14 | #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 15 | dns_cx_add() { 16 | fulldomain=$1 17 | txtvalue=$2 18 | 19 | if [ -z "$CX_Key" ] || [ -z "$CX_Secret" ]; then 20 | CX_Key="" 21 | CX_Secret="" 22 | _err "You don't specify cloudxns.com api key or secret yet." 23 | _err "Please create you key and try again." 24 | return 1 25 | fi 26 | 27 | REST_API="$CX_Api" 28 | 29 | #save the api key and email to the account conf file. 30 | _saveaccountconf CX_Key "$CX_Key" 31 | _saveaccountconf CX_Secret "$CX_Secret" 32 | 33 | _debug "First detect the root zone" 34 | if ! _get_root "$fulldomain"; then 35 | _err "invalid domain" 36 | return 1 37 | fi 38 | 39 | existing_records "$_domain" "$_sub_domain" 40 | _debug count "$count" 41 | if [ "$?" != "0" ]; then 42 | _err "Error get existing records." 43 | return 1 44 | fi 45 | 46 | if [ "$count" = "0" ]; then 47 | add_record "$_domain" "$_sub_domain" "$txtvalue" 48 | else 49 | update_record "$_domain" "$_sub_domain" "$txtvalue" 50 | fi 51 | 52 | if [ "$?" = "0" ]; then 53 | return 0 54 | fi 55 | return 1 56 | } 57 | 58 | #fulldomain 59 | dns_cx_rm() { 60 | fulldomain=$1 61 | REST_API="$CX_Api" 62 | if _get_root "$fulldomain"; then 63 | record_id="" 64 | existing_records "$_domain" "$_sub_domain" 65 | if ! [ "$record_id" = "" ]; then 66 | _rest DELETE "record/$record_id/$_domain_id" "{}" 67 | _info "Deleted record ${fulldomain}" 68 | fi 69 | fi 70 | } 71 | 72 | #usage: root sub 73 | #return if the sub record already exists. 74 | #echos the existing records count. 75 | # '0' means doesn't exist 76 | existing_records() { 77 | _debug "Getting txt records" 78 | root=$1 79 | sub=$2 80 | count=0 81 | if ! _rest GET "record/$_domain_id?:domain_id?host_id=0&offset=0&row_num=100"; then 82 | return 1 83 | fi 84 | 85 | seg=$(printf "%s\n" "$response" | _egrep_o '"record_id":[^{]*host":"'"$_sub_domain"'"[^}]*\}') 86 | _debug seg "$seg" 87 | if [ -z "$seg" ]; then 88 | return 0 89 | fi 90 | 91 | if printf "%s" "$response" | grep '"type":"TXT"' >/dev/null; then 92 | count=1 93 | record_id=$(printf "%s\n" "$seg" | _egrep_o '"record_id":"[^"]*"' | cut -d : -f 2 | tr -d \" | _head_n 1) 94 | _debug record_id "$record_id" 95 | return 0 96 | fi 97 | 98 | } 99 | 100 | #add the txt record. 101 | #usage: root sub txtvalue 102 | add_record() { 103 | root=$1 104 | sub=$2 105 | txtvalue=$3 106 | fulldomain="$sub.$root" 107 | 108 | _info "Adding record" 109 | 110 | if ! _rest POST "record" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}"; then 111 | return 1 112 | fi 113 | 114 | return 0 115 | } 116 | 117 | #update the txt record 118 | #Usage: root sub txtvalue 119 | update_record() { 120 | root=$1 121 | sub=$2 122 | txtvalue=$3 123 | fulldomain="$sub.$root" 124 | 125 | _info "Updating record" 126 | 127 | if _rest PUT "record/$record_id" "{\"domain_id\": $_domain_id, \"host\":\"$_sub_domain\", \"value\":\"$txtvalue\", \"type\":\"TXT\",\"ttl\":600, \"line_id\":1}"; then 128 | return 0 129 | fi 130 | 131 | return 1 132 | } 133 | 134 | #################### Private functions below ################################## 135 | #_acme-challenge.www.domain.com 136 | #returns 137 | # _sub_domain=_acme-challenge.www 138 | # _domain=domain.com 139 | # _domain_id=sdjkglgdfewsdfg 140 | _get_root() { 141 | domain=$1 142 | i=2 143 | p=1 144 | 145 | if ! _rest GET "domain"; then 146 | return 1 147 | fi 148 | 149 | while true; do 150 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 151 | _debug h "$h" 152 | if [ -z "$h" ]; then 153 | #not valid 154 | return 1 155 | fi 156 | 157 | if _contains "$response" "$h."; then 158 | seg=$(printf "%s\n" "$response" | _egrep_o '"id":[^{]*"'"$h"'."[^}]*}') 159 | _debug seg "$seg" 160 | _domain_id=$(printf "%s\n" "$seg" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") 161 | _debug _domain_id "$_domain_id" 162 | if [ "$_domain_id" ]; then 163 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 164 | _debug _sub_domain "$_sub_domain" 165 | _domain="$h" 166 | _debug _domain "$_domain" 167 | return 0 168 | fi 169 | return 1 170 | fi 171 | p="$i" 172 | i=$(_math "$i" + 1) 173 | done 174 | return 1 175 | } 176 | 177 | #Usage: method URI data 178 | _rest() { 179 | m=$1 180 | ep="$2" 181 | _debug ep "$ep" 182 | url="$REST_API/$ep" 183 | _debug url "$url" 184 | 185 | cdate=$(date -u "+%Y-%m-%d %H:%M:%S UTC") 186 | _debug cdate "$cdate" 187 | 188 | data="$3" 189 | _debug data "$data" 190 | 191 | sec="$CX_Key$url$data$cdate$CX_Secret" 192 | _debug sec "$sec" 193 | hmac=$(printf "%s" "$sec" | _digest md5 hex) 194 | _debug hmac "$hmac" 195 | 196 | export _H1="API-KEY: $CX_Key" 197 | export _H2="API-REQUEST-DATE: $cdate" 198 | export _H3="API-HMAC: $hmac" 199 | export _H4="Content-Type: application/json" 200 | 201 | if [ "$data" ]; then 202 | response="$(_post "$data" "$url" "" "$m")" 203 | else 204 | response="$(_get "$url")" 205 | fi 206 | 207 | if [ "$?" != "0" ]; then 208 | _err "error $ep" 209 | return 1 210 | fi 211 | _debug2 response "$response" 212 | 213 | _contains "$response" '"code":1' 214 | 215 | } 216 | -------------------------------------------------------------------------------- /dnsapi/dns_dp.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Dnspod.cn Domain api 4 | # 5 | #DP_Id="1234" 6 | # 7 | #DP_Key="sADDsdasdgdsf" 8 | 9 | REST_API="https://dnsapi.cn" 10 | 11 | ######## Public functions ##################### 12 | 13 | #Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 14 | dns_dp_add() { 15 | fulldomain=$1 16 | txtvalue=$2 17 | 18 | if [ -z "$DP_Id" ] || [ -z "$DP_Key" ]; then 19 | DP_Id="" 20 | DP_Key="" 21 | _err "You don't specify dnspod api key and key id yet." 22 | _err "Please create you key and try again." 23 | return 1 24 | fi 25 | 26 | #save the api key and email to the account conf file. 27 | _saveaccountconf DP_Id "$DP_Id" 28 | _saveaccountconf DP_Key "$DP_Key" 29 | 30 | _debug "First detect the root zone" 31 | if ! _get_root "$fulldomain"; then 32 | _err "invalid domain" 33 | return 1 34 | fi 35 | 36 | existing_records "$_domain" "$_sub_domain" 37 | _debug count "$count" 38 | if [ "$?" != "0" ]; then 39 | _err "Error get existing records." 40 | return 1 41 | fi 42 | 43 | if [ "$count" = "0" ]; then 44 | add_record "$_domain" "$_sub_domain" "$txtvalue" 45 | else 46 | update_record "$_domain" "$_sub_domain" "$txtvalue" 47 | fi 48 | } 49 | 50 | #fulldomain txtvalue 51 | dns_dp_rm() { 52 | fulldomain=$1 53 | txtvalue=$2 54 | _debug "First detect the root zone" 55 | if ! _get_root "$fulldomain"; then 56 | _err "invalid domain" 57 | return 1 58 | fi 59 | 60 | if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain"; then 61 | _err "Record.Lis error." 62 | return 1 63 | fi 64 | 65 | if _contains "$response" 'No records'; then 66 | _info "Don't need to remove." 67 | return 0 68 | fi 69 | 70 | record_id=$(echo "$response" | _egrep_o '{[^{]*"value":"'"$txtvalue"'"' | cut -d , -f 1 | cut -d : -f 2 | tr -d \") 71 | _debug record_id "$record_id" 72 | if [ -z "$record_id" ]; then 73 | _err "Can not get record id." 74 | return 1 75 | fi 76 | 77 | if ! _rest POST "Record.Remove" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&record_id=$record_id"; then 78 | _err "Record.Remove error." 79 | return 1 80 | fi 81 | 82 | _contains "$response" "Action completed successful" 83 | 84 | } 85 | 86 | #usage: root sub 87 | #return if the sub record already exists. 88 | #echos the existing records count. 89 | # '0' means doesn't exist 90 | existing_records() { 91 | _debug "Getting txt records" 92 | root=$1 93 | sub=$2 94 | 95 | if ! _rest POST "Record.List" "login_token=$DP_Id,$DP_Key&domain_id=$_domain_id&sub_domain=$_sub_domain"; then 96 | return 1 97 | fi 98 | 99 | if _contains "$response" 'No records'; then 100 | count=0 101 | return 0 102 | fi 103 | 104 | if _contains "$response" "Action completed successful"; then 105 | count=$(printf "%s" "$response" | grep -c 'TXT' | tr -d ' ') 106 | record_id=$(printf "%s" "$response" | grep '^' | tail -1 | cut -d '>' -f 2 | cut -d '<' -f 1) 107 | _debug record_id "$record_id" 108 | return 0 109 | else 110 | _err "get existing records error." 111 | return 1 112 | fi 113 | 114 | count=0 115 | } 116 | 117 | #add the txt record. 118 | #usage: root sub txtvalue 119 | add_record() { 120 | root=$1 121 | sub=$2 122 | txtvalue=$3 123 | fulldomain="$sub.$root" 124 | 125 | _info "Adding record" 126 | 127 | if ! _rest POST "Record.Create" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认"; then 128 | return 1 129 | fi 130 | 131 | if _contains "$response" "Action completed successful"; then 132 | 133 | return 0 134 | fi 135 | 136 | return 1 #error 137 | } 138 | 139 | #update the txt record 140 | #Usage: root sub txtvalue 141 | update_record() { 142 | root=$1 143 | sub=$2 144 | txtvalue=$3 145 | fulldomain="$sub.$root" 146 | 147 | _info "Updating record" 148 | 149 | if ! _rest POST "Record.Modify" "login_token=$DP_Id,$DP_Key&format=json&domain_id=$_domain_id&sub_domain=$_sub_domain&record_type=TXT&value=$txtvalue&record_line=默认&record_id=$record_id"; then 150 | return 1 151 | fi 152 | 153 | if _contains "$response" "Action completed successful"; then 154 | 155 | return 0 156 | fi 157 | 158 | return 1 #error 159 | } 160 | 161 | #################### Private functions below ################################## 162 | #_acme-challenge.www.domain.com 163 | #returns 164 | # _sub_domain=_acme-challenge.www 165 | # _domain=domain.com 166 | # _domain_id=sdjkglgdfewsdfg 167 | _get_root() { 168 | domain=$1 169 | i=2 170 | p=1 171 | while true; do 172 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 173 | if [ -z "$h" ]; then 174 | #not valid 175 | return 1 176 | fi 177 | 178 | if ! _rest POST "Domain.Info" "login_token=$DP_Id,$DP_Key&format=json&domain=$h"; then 179 | return 1 180 | fi 181 | 182 | if _contains "$response" "Action completed successful"; then 183 | _domain_id=$(printf "%s\n" "$response" | _egrep_o "\"id\":\"[^\"]*\"" | cut -d : -f 2 | tr -d \") 184 | _debug _domain_id "$_domain_id" 185 | if [ "$_domain_id" ]; then 186 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 187 | _debug _sub_domain "$_sub_domain" 188 | _domain="$h" 189 | _debug _domain "$_domain" 190 | return 0 191 | fi 192 | return 1 193 | fi 194 | p="$i" 195 | i=$(_math "$i" + 1) 196 | done 197 | return 1 198 | } 199 | 200 | #Usage: method URI data 201 | _rest() { 202 | m="$1" 203 | ep="$2" 204 | data="$3" 205 | _debug "$ep" 206 | url="$REST_API/$ep" 207 | 208 | _debug url "$url" 209 | 210 | if [ "$m" = "GET" ]; then 211 | response="$(_get "$url" | tr -d '\r')" 212 | else 213 | _debug2 data "$data" 214 | response="$(_post "$data" "$url" | tr -d '\r')" 215 | fi 216 | 217 | if [ "$?" != "0" ]; then 218 | _err "error $ep" 219 | return 1 220 | fi 221 | _debug2 response "$response" 222 | return 0 223 | } 224 | -------------------------------------------------------------------------------- /dnsapi/dns_ispconfig.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # ISPConfig 3.1 API 4 | # User must provide login data and URL to the ISPConfig installation incl. port. The remote user in ISPConfig must have access to: 5 | # - DNS zone Functions 6 | # - DNS txt Functions 7 | 8 | # Report bugs to https://github.com/sjau/acme.sh 9 | 10 | # Values to export: 11 | # export ISPC_User="remoteUser" 12 | # export ISPC_Password="remotePassword" 13 | # export ISPC_Api="https://ispc.domain.tld:8080/remote/json.php" 14 | # 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) 15 | 16 | ######## Public functions ##################### 17 | 18 | #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 19 | dns_ispconfig_add() { 20 | fulldomain="${1}" 21 | txtvalue="${2}" 22 | _debug "Calling: dns_ispconfig_add() '${fulldomain}' '${txtvalue}'" 23 | _ISPC_credentials && _ISPC_login && _ISPC_getZoneInfo && _ISPC_addTxt 24 | } 25 | 26 | #Usage: dns_myapi_rm _acme-challenge.www.domain.com 27 | dns_ispconfig_rm() { 28 | fulldomain="${1}" 29 | _debug "Calling: dns_ispconfig_rm() '${fulldomain}'" 30 | _ISPC_credentials && _ISPC_login && _ISPC_rmTxt 31 | } 32 | 33 | #################### Private functions below ################################## 34 | 35 | _ISPC_credentials() { 36 | if [ -z "${ISPC_User}" ] || [ -z "$ISPC_Password" ] || [ -z "${ISPC_Api}" ] || [ -z "${ISPC_Api_Insecure}" ]; then 37 | ISPC_User="" 38 | ISPC_Password="" 39 | ISPC_Api="" 40 | ISPC_Api_Insecure="" 41 | _err "You haven't specified the ISPConfig Login data, URL and whether you want check the ISPC SSL cert. Please try again." 42 | return 1 43 | else 44 | _saveaccountconf ISPC_User "${ISPC_User}" 45 | _saveaccountconf ISPC_Password "${ISPC_Password}" 46 | _saveaccountconf ISPC_Api "${ISPC_Api}" 47 | _saveaccountconf ISPC_Api_Insecure "${ISPC_Api_Insecure}" 48 | # Set whether curl should use secure or insecure mode 49 | export HTTPS_INSECURE="${ISPC_Api_Insecure}" 50 | fi 51 | } 52 | 53 | _ISPC_login() { 54 | _info "Getting Session ID" 55 | curData="{\"username\":\"${ISPC_User}\",\"password\":\"${ISPC_Password}\",\"client_login\":false}" 56 | curResult="$(_post "${curData}" "${ISPC_Api}?login")" 57 | _debug "Calling _ISPC_login: '${curData}' '${ISPC_Api}?login'" 58 | _debug "Result of _ISPC_login: '$curResult'" 59 | if _contains "${curResult}" '"code":"ok"'; then 60 | sessionID=$(echo "${curResult}" | _egrep_o "response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) 61 | _info "Retrieved Session ID." 62 | _debug "Session ID: '${sessionID}'" 63 | else 64 | _err "Couldn't retrieve the Session ID." 65 | return 1 66 | fi 67 | } 68 | 69 | _ISPC_getZoneInfo() { 70 | _info "Getting Zoneinfo" 71 | zoneEnd=false 72 | curZone="${fulldomain}" 73 | while [ "${zoneEnd}" = false ]; do 74 | # we can strip the first part of the fulldomain, since it's just the _acme-challenge string 75 | curZone="${curZone#*.}" 76 | # suffix . needed for zone -> domain.tld. 77 | curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"origin\":\"${curZone}.\"}}" 78 | curResult="$(_post "${curData}" "${ISPC_Api}?dns_zone_get")" 79 | _debug "Calling _ISPC_getZoneInfo: '${curData}' '${ISPC_Api}?login'" 80 | _debug "Result of _ISPC_getZoneInfo: '$curResult'" 81 | if _contains "${curResult}" '"id":"'; then 82 | zoneFound=true 83 | zoneEnd=true 84 | _info "Retrieved zone data." 85 | _debug "Zone data: '${curResult}'" 86 | fi 87 | if [ "${curZone#*.}" != "$curZone" ]; then 88 | _debug2 "$curZone still contains a '.' - so we can check next higher level" 89 | else 90 | zoneEnd=true 91 | _err "Couldn't retrieve zone data." 92 | return 1 93 | fi 94 | done 95 | if [ "${zoneFound}" ]; then 96 | server_id=$(echo "${curResult}" | _egrep_o "server_id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) 97 | _debug "Server ID: '${server_id}'" 98 | case "${server_id}" in 99 | '' | *[!0-9]*) 100 | _err "Server ID is not numeric." 101 | return 1 102 | ;; 103 | *) _info "Retrieved Server ID" ;; 104 | esac 105 | zone=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) 106 | _debug "Zone: '${zone}'" 107 | case "${zone}" in 108 | '' | *[!0-9]*) 109 | _err "Zone ID is not numeric." 110 | return 1 111 | ;; 112 | *) _info "Retrieved Zone ID" ;; 113 | esac 114 | client_id=$(echo "${curResult}" | _egrep_o "sys_userid.*" | cut -d ':' -f 2 | cut -d '"' -f 2) 115 | _debug "Client ID: '${client_id}'" 116 | case "${client_id}" in 117 | '' | *[!0-9]*) 118 | _err "Client ID is not numeric." 119 | return 1 120 | ;; 121 | *) _info "Retrieved Client ID." ;; 122 | esac 123 | zoneFound="" 124 | zoneEnd="" 125 | fi 126 | } 127 | 128 | _ISPC_addTxt() { 129 | curSerial="$(date +%s)" 130 | curStamp="$(date +'%F %T')" 131 | params="\"server_id\":\"${server_id}\",\"zone\":\"${zone}\",\"name\":\"${fulldomain}.\",\"type\":\"txt\",\"data\":\"${txtvalue}\",\"aux\":\"0\",\"ttl\":\"3600\",\"active\":\"y\",\"stamp\":\"${curStamp}\",\"serial\":\"${curSerial}\"" 132 | curData="{\"session_id\":\"${sessionID}\",\"client_id\":\"${client_id}\",\"params\":{${params}}}" 133 | curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_add")" 134 | _debug "Calling _ISPC_addTxt: '${curData}' '${ISPC_Api}?dns_txt_add'" 135 | _debug "Result of _ISPC_addTxt: '$curResult'" 136 | record_id=$(echo "${curResult}" | _egrep_o "\"response.*" | cut -d ':' -f 2 | cut -d '"' -f 2) 137 | _debug "Record ID: '${record_id}'" 138 | case "${record_id}" in 139 | '' | *[!0-9]*) 140 | _err "Couldn't add ACME Challenge TXT record to zone." 141 | return 1 142 | ;; 143 | *) _info "Added ACME Challenge TXT record to zone." ;; 144 | esac 145 | } 146 | 147 | _ISPC_rmTxt() { 148 | # Need to get the record ID. 149 | curData="{\"session_id\":\"${sessionID}\",\"primary_id\":{\"name\":\"${fulldomain}.\",\"type\":\"TXT\"}}" 150 | curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_get")" 151 | _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_get'" 152 | _debug "Result of _ISPC_rmTxt: '$curResult'" 153 | if _contains "${curResult}" '"code":"ok"'; then 154 | record_id=$(echo "${curResult}" | _egrep_o "\"id.*" | cut -d ':' -f 2 | cut -d '"' -f 2) 155 | _debug "Record ID: '${record_id}'" 156 | case "${record_id}" in 157 | '' | *[!0-9]*) 158 | _err "Record ID is not numeric." 159 | return 1 160 | ;; 161 | *) 162 | unset IFS 163 | _info "Retrieved Record ID." 164 | curData="{\"session_id\":\"${sessionID}\",\"primary_id\":\"${record_id}\"}" 165 | curResult="$(_post "${curData}" "${ISPC_Api}?dns_txt_delete")" 166 | _debug "Calling _ISPC_rmTxt: '${curData}' '${ISPC_Api}?dns_txt_delete'" 167 | _debug "Result of _ISPC_rmTxt: '$curResult'" 168 | if _contains "${curResult}" '"code":"ok"'; then 169 | _info "Removed ACME Challenge TXT record from zone." 170 | else 171 | _err "Couldn't remove ACME Challenge TXT record from zone." 172 | return 1 173 | fi 174 | ;; 175 | esac 176 | fi 177 | } 178 | -------------------------------------------------------------------------------- /dnsapi/dns_dgon.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ## Will be called by acme.sh to add the txt record to your api system. 4 | ## returns 0 means success, otherwise error. 5 | 6 | ## Author: thewer 7 | ## GitHub: https://github.com/gitwer/acme.sh 8 | 9 | ## 10 | ## Environment Variables Required: 11 | ## 12 | ## DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc" 13 | ## 14 | 15 | ##################### Public functions ##################### 16 | 17 | ## Create the text record for validation. 18 | ## Usage: fulldomain txtvalue 19 | ## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" 20 | dns_dgon_add() { 21 | fulldomain="$(echo "$1" | _lower_case)" 22 | txtvalue=$2 23 | _info "Using digitalocean dns validation - add record" 24 | _debug fulldomain "$fulldomain" 25 | _debug txtvalue "$txtvalue" 26 | 27 | ## save the env vars (key and domain split location) for later automated use 28 | _saveaccountconf DO_API_KEY "$DO_API_KEY" 29 | 30 | ## split the domain for DO API 31 | if ! _get_base_domain "$fulldomain"; then 32 | _err "domain not found in your account for addition" 33 | return 1 34 | fi 35 | _debug _sub_domain "$_sub_domain" 36 | _debug _domain "$_domain" 37 | 38 | ## Set the header with our post type and key auth key 39 | export _H1="Content-Type: application/json" 40 | export _H2="Authorization: Bearer $DO_API_KEY" 41 | PURL='https://api.digitalocean.com/v2/domains/'$_domain'/records' 42 | PBODY='{"type":"TXT","name":"'$_sub_domain'","data":"'$txtvalue'"}' 43 | 44 | _debug PURL "$PURL" 45 | _debug PBODY "$PBODY" 46 | 47 | ## the create request - post 48 | ## args: BODY, URL, [need64, httpmethod] 49 | response="$(_post "$PBODY" "$PURL")" 50 | 51 | ## check response 52 | if [ "$?" != "0" ]; then 53 | _err "error in response: $response" 54 | return 1 55 | fi 56 | _debug2 response "$response" 57 | 58 | ## finished correctly 59 | return 0 60 | } 61 | 62 | ## Remove the txt record after validation. 63 | ## Usage: fulldomain txtvalue 64 | ## EG: "_acme-challenge.www.other.domain.com" "XKrxpRBosdq0HG9i01zxXp5CPBs" 65 | dns_dgon_rm() { 66 | fulldomain="$(echo "$1" | _lower_case)" 67 | txtvalue=$2 68 | _info "Using digitalocean dns validation - remove record" 69 | _debug fulldomain "$fulldomain" 70 | _debug txtvalue "$txtvalue" 71 | 72 | ## split the domain for DO API 73 | if ! _get_base_domain "$fulldomain"; then 74 | _err "domain not found in your account for removal" 75 | return 1 76 | fi 77 | _debug _sub_domain "$_sub_domain" 78 | _debug _domain "$_domain" 79 | 80 | ## Set the header with our post type and key auth key 81 | export _H1="Content-Type: application/json" 82 | export _H2="Authorization: Bearer $DO_API_KEY" 83 | ## get URL for the list of domains 84 | ## may get: "links":{"pages":{"last":".../v2/domains/DOM/records?page=2","next":".../v2/domains/DOM/records?page=2"}} 85 | GURL="https://api.digitalocean.com/v2/domains/$_domain/records" 86 | 87 | ## while we dont have a record ID we keep going 88 | while [ -z "$record" ]; do 89 | ## 1) get the URL 90 | ## the create request - get 91 | ## args: URL, [onlyheader, timeout] 92 | domain_list="$(_get "$GURL")" 93 | ## 2) find record 94 | ## check for what we are looing for: "type":"A","name":"$_sub_domain" 95 | record="$(echo "$domain_list" | _egrep_o "\"id\"\s*\:\s*\"*\d+\"*[^}]*\"name\"\s*\:\s*\"$_sub_domain\"[^}]*\"data\"\s*\:\s*\"$txtvalue\"")" 96 | ## 3) check record and get next page 97 | if [ -z "$record" ]; then 98 | ## find the next page if we dont have a match 99 | nextpage="$(echo "$domain_list" | _egrep_o "\"links\".*" | _egrep_o "\"next\".*" | _egrep_o "http.*page\=\d+")" 100 | if [ -z "$nextpage" ]; then 101 | _err "no record and no nextpage in digital ocean DNS removal" 102 | return 1 103 | fi 104 | _debug2 nextpage "$nextpage" 105 | GURL="$nextpage" 106 | fi 107 | ## we break out of the loop when we have a record 108 | done 109 | 110 | ## we found the record 111 | rec_id="$(echo "$record" | _egrep_o "id\"\s*\:\s*\"*\d+" | _egrep_o "\d+")" 112 | _debug rec_id "$rec_id" 113 | 114 | ## delete the record 115 | ## delete URL for removing the one we dont want 116 | DURL="https://api.digitalocean.com/v2/domains/$_domain/records/$rec_id" 117 | 118 | ## the create request - delete 119 | ## args: BODY, URL, [need64, httpmethod] 120 | response="$(_post "" "$DURL" "" "DELETE")" 121 | 122 | ## check response (sort of) 123 | if [ "$?" != "0" ]; then 124 | _err "error in remove response: $response" 125 | return 1 126 | fi 127 | _debug2 response "$response" 128 | 129 | ## finished correctly 130 | return 0 131 | } 132 | 133 | ##################### Private functions below ##################### 134 | 135 | ## Split the domain provided into the "bade domain" and the "start prefix". 136 | ## This function searches for the longest subdomain in your account 137 | ## for the full domain given and splits it into the base domain (zone) 138 | ## and the prefix/record to be added/removed 139 | ## USAGE: fulldomain 140 | ## EG: "_acme-challenge.two.three.four.domain.com" 141 | ## returns 142 | ## _sub_domain="_acme-challenge.two" 143 | ## _domain="three.four.domain.com" *IF* zone "three.four.domain.com" exists 144 | ## if only "domain.com" exists it will return 145 | ## _sub_domain="_acme-challenge.two.three.four" 146 | ## _domain="domain.com" 147 | _get_base_domain() { 148 | # args 149 | fulldomain="$(echo "$1" | tr '[:upper:]' '[:lower:]')" 150 | _debug fulldomain "$fulldomain" 151 | 152 | # domain max legal length = 253 153 | MAX_DOM=255 154 | 155 | ## get a list of domains for the account to check thru 156 | ## Set the headers 157 | export _H1="Content-Type: application/json" 158 | export _H2="Authorization: Bearer $DO_API_KEY" 159 | _debug DO_API_KEY "$DO_API_KEY" 160 | ## get URL for the list of domains 161 | ## havent seen this request paginated, tested with 18 domains (more requres manual requests with DO) 162 | DOMURL="https://api.digitalocean.com/v2/domains" 163 | 164 | ## get the domain list (DO gives basically a full XFER!) 165 | domain_list="$(_get "$DOMURL")" 166 | 167 | ## check response 168 | if [ "$?" != "0" ]; then 169 | _err "error in domain_list response: $domain_list" 170 | return 1 171 | fi 172 | _debug2 domain_list "$domain_list" 173 | 174 | ## for each shortening of our $fulldomain, check if it exists in the $domain_list 175 | ## can never start on 1 (aka whole $fulldomain) as $fulldomain starts with "_acme-challenge" 176 | i=2 177 | while [ $i -gt 0 ]; do 178 | ## get next longest domain 179 | _domain=$(printf "%s" "$fulldomain" | cut -d . -f "$i"-"$MAX_DOM") 180 | ## check we got something back from our cut (or are we at the end) 181 | if [ -z "$_domain" ]; then 182 | ## we got to the end of the domain - invalid domain 183 | _err "domain not found in DigitalOcean account" 184 | return 1 185 | fi 186 | ## we got part of a domain back - grep it out 187 | found="$(echo "$domain_list" | _egrep_o "\"name\"\s*\:\s*\"$_domain\"")" 188 | ## check if it exists 189 | if [ ! -z "$found" ]; then 190 | ## exists - exit loop returning the parts 191 | sub_point=$(_math $i - 1) 192 | _sub_domain=$(printf "%s" "$fulldomain" | cut -d . -f 1-"$sub_point") 193 | _debug _domain "$_domain" 194 | _debug _sub_domain "$_sub_domain" 195 | return 0 196 | fi 197 | ## increment cut point $i 198 | i=$(_math $i + 1) 199 | done 200 | 201 | ## we went through the entire domain zone list and dint find one that matched 202 | ## doesnt look like we can add in the record 203 | _err "domain not found in DigitalOcean account, but we should never get here" 204 | return 1 205 | } 206 | -------------------------------------------------------------------------------- /dnsapi/dns_aws.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | #AWS_ACCESS_KEY_ID="sdfsdfsdfljlbjkljlkjsdfoiwje" 5 | # 6 | #AWS_SECRET_ACCESS_KEY="xxxxxxx" 7 | 8 | #This is the Amazon Route53 api wrapper for acme.sh 9 | 10 | AWS_HOST="route53.amazonaws.com" 11 | AWS_URL="https://$AWS_HOST" 12 | 13 | AWS_WIKI="https://github.com/Neilpang/acme.sh/wiki/How-to-use-Amazon-Route53-API" 14 | 15 | ######## Public functions ##################### 16 | 17 | #Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 18 | dns_aws_add() { 19 | fulldomain=$1 20 | txtvalue=$2 21 | 22 | if [ -z "$AWS_ACCESS_KEY_ID" ] || [ -z "$AWS_SECRET_ACCESS_KEY" ]; then 23 | AWS_ACCESS_KEY_ID="" 24 | AWS_SECRET_ACCESS_KEY="" 25 | _err "You don't specify aws route53 api key id and and api key secret yet." 26 | _err "Please create you key and try again. see $(__green $AWS_WIKI)" 27 | return 1 28 | fi 29 | 30 | if [ -z "$AWS_SESSION_TOKEN" ]; then 31 | _saveaccountconf AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID" 32 | _saveaccountconf AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" 33 | fi 34 | 35 | _debug "First detect the root zone" 36 | if ! _get_root "$fulldomain"; then 37 | _err "invalid domain" 38 | return 1 39 | fi 40 | _debug _domain_id "$_domain_id" 41 | _debug _sub_domain "$_sub_domain" 42 | _debug _domain "$_domain" 43 | 44 | _aws_tmpl_xml="UPSERT$fulldomainTXT300\"$txtvalue\"" 45 | 46 | if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then 47 | _info "txt record updated success." 48 | return 0 49 | fi 50 | 51 | return 1 52 | } 53 | 54 | #fulldomain txtvalue 55 | dns_aws_rm() { 56 | fulldomain=$1 57 | txtvalue=$2 58 | 59 | _debug "First detect the root zone" 60 | if ! _get_root "$fulldomain"; then 61 | _err "invalid domain" 62 | return 1 63 | fi 64 | _debug _domain_id "$_domain_id" 65 | _debug _sub_domain "$_sub_domain" 66 | _debug _domain "$_domain" 67 | 68 | _aws_tmpl_xml="DELETE\"$txtvalue\"$fulldomain.TXT300" 69 | 70 | if aws_rest POST "2013-04-01$_domain_id/rrset/" "" "$_aws_tmpl_xml" && _contains "$response" "ChangeResourceRecordSetsResponse"; then 71 | _info "txt record deleted success." 72 | return 0 73 | fi 74 | 75 | return 1 76 | 77 | } 78 | 79 | #################### Private functions below ################################## 80 | 81 | _get_root() { 82 | domain=$1 83 | i=2 84 | p=1 85 | 86 | if aws_rest GET "2013-04-01/hostedzone"; then 87 | _debug "response" "$response" 88 | while true; do 89 | h=$(printf "%s" "$domain" | cut -d . -f $i-100) 90 | if [ -z "$h" ]; then 91 | #not valid 92 | return 1 93 | fi 94 | 95 | if _contains "$response" "$h."; then 96 | hostedzone="$(echo "$response" | sed 's//#&/g' | tr '#' '\n' | _egrep_o "[^<]*<.Id>$h.<.Name>.*<.HostedZone>")" 97 | _debug hostedzone "$hostedzone" 98 | if [ -z "$hostedzone" ]; then 99 | _err "Error, can not get hostedzone." 100 | return 1 101 | fi 102 | _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o ".*<.Id>" | head -n 1 | _egrep_o ">.*<" | tr -d "<>") 103 | if [ "$_domain_id" ]; then 104 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 105 | _domain=$h 106 | return 0 107 | fi 108 | return 1 109 | fi 110 | p=$i 111 | i=$(_math "$i" + 1) 112 | done 113 | fi 114 | return 1 115 | } 116 | 117 | #method uri qstr data 118 | aws_rest() { 119 | mtd="$1" 120 | ep="$2" 121 | qsr="$3" 122 | data="$4" 123 | 124 | _debug mtd "$mtd" 125 | _debug ep "$ep" 126 | _debug qsr "$qsr" 127 | _debug data "$data" 128 | 129 | CanonicalURI="/$ep" 130 | _debug2 CanonicalURI "$CanonicalURI" 131 | 132 | CanonicalQueryString="$qsr" 133 | _debug2 CanonicalQueryString "$CanonicalQueryString" 134 | 135 | RequestDate="$(date -u +"%Y%m%dT%H%M%SZ")" 136 | _debug2 RequestDate "$RequestDate" 137 | 138 | #RequestDate="20161120T141056Z" ############## 139 | 140 | export _H1="x-amz-date: $RequestDate" 141 | 142 | aws_host="$AWS_HOST" 143 | CanonicalHeaders="host:$aws_host\nx-amz-date:$RequestDate\n" 144 | SignedHeaders="host;x-amz-date" 145 | if [ -n "$AWS_SESSION_TOKEN" ]; then 146 | export _H2="x-amz-security-token: $AWS_SESSION_TOKEN" 147 | CanonicalHeaders="${CanonicalHeaders}x-amz-security-token:$AWS_SESSION_TOKEN\n" 148 | SignedHeaders="${SignedHeaders};x-amz-security-token" 149 | fi 150 | _debug2 CanonicalHeaders "$CanonicalHeaders" 151 | _debug2 SignedHeaders "$SignedHeaders" 152 | 153 | RequestPayload="$data" 154 | _debug2 RequestPayload "$RequestPayload" 155 | 156 | Hash="sha256" 157 | 158 | CanonicalRequest="$mtd\n$CanonicalURI\n$CanonicalQueryString\n$CanonicalHeaders\n$SignedHeaders\n$(printf "%s" "$RequestPayload" | _digest "$Hash" hex)" 159 | _debug2 CanonicalRequest "$CanonicalRequest" 160 | 161 | HashedCanonicalRequest="$(printf "$CanonicalRequest%s" | _digest "$Hash" hex)" 162 | _debug2 HashedCanonicalRequest "$HashedCanonicalRequest" 163 | 164 | Algorithm="AWS4-HMAC-SHA256" 165 | _debug2 Algorithm "$Algorithm" 166 | 167 | RequestDateOnly="$(echo "$RequestDate" | cut -c 1-8)" 168 | _debug2 RequestDateOnly "$RequestDateOnly" 169 | 170 | Region="us-east-1" 171 | Service="route53" 172 | 173 | CredentialScope="$RequestDateOnly/$Region/$Service/aws4_request" 174 | _debug2 CredentialScope "$CredentialScope" 175 | 176 | StringToSign="$Algorithm\n$RequestDate\n$CredentialScope\n$HashedCanonicalRequest" 177 | 178 | _debug2 StringToSign "$StringToSign" 179 | 180 | kSecret="AWS4$AWS_SECRET_ACCESS_KEY" 181 | 182 | #kSecret="wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY" ############################ 183 | 184 | _secure_debug2 kSecret "$kSecret" 185 | 186 | kSecretH="$(printf "%s" "$kSecret" | _hex_dump | tr -d " ")" 187 | _secure_debug2 kSecretH "$kSecretH" 188 | 189 | kDateH="$(printf "$RequestDateOnly%s" | _hmac "$Hash" "$kSecretH" hex)" 190 | _debug2 kDateH "$kDateH" 191 | 192 | kRegionH="$(printf "$Region%s" | _hmac "$Hash" "$kDateH" hex)" 193 | _debug2 kRegionH "$kRegionH" 194 | 195 | kServiceH="$(printf "$Service%s" | _hmac "$Hash" "$kRegionH" hex)" 196 | _debug2 kServiceH "$kServiceH" 197 | 198 | kSigningH="$(printf "aws4_request%s" | _hmac "$Hash" "$kServiceH" hex)" 199 | _debug2 kSigningH "$kSigningH" 200 | 201 | signature="$(printf "$StringToSign%s" | _hmac "$Hash" "$kSigningH" hex)" 202 | _debug2 signature "$signature" 203 | 204 | Authorization="$Algorithm Credential=$AWS_ACCESS_KEY_ID/$CredentialScope, SignedHeaders=$SignedHeaders, Signature=$signature" 205 | _debug2 Authorization "$Authorization" 206 | 207 | _H3="Authorization: $Authorization" 208 | _debug _H3 "$_H3" 209 | 210 | url="$AWS_URL/$ep" 211 | 212 | if [ "$mtd" = "GET" ]; then 213 | response="$(_get "$url")" 214 | else 215 | response="$(_post "$data" "$url")" 216 | fi 217 | 218 | _ret="$?" 219 | if [ "$_ret" = "0" ]; then 220 | if _contains "$response" "/dev/null; then 242 | _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) 243 | _domain="$h" 244 | return 0 245 | fi 246 | p=$i 247 | i=$(_math "$i" + 1) 248 | done 249 | return 1 250 | } 251 | 252 | _ovh_timestamp() { 253 | _H1="" 254 | _H2="" 255 | _H3="" 256 | _H4="" 257 | _H5="" 258 | _get "$OVH_API/auth/time" "" 30 259 | } 260 | 261 | _ovh_rest() { 262 | m=$1 263 | ep="$2" 264 | data="$3" 265 | _debug "$ep" 266 | 267 | _ovh_url="$OVH_API/$ep" 268 | _debug2 _ovh_url "$_ovh_url" 269 | _ovh_t="$(_ovh_timestamp)" 270 | _debug2 _ovh_t "$_ovh_t" 271 | _ovh_p="$OVH_AS+$OVH_CK+$m+$_ovh_url+$data+$_ovh_t" 272 | _secure_debug _ovh_p "$_ovh_p" 273 | _ovh_hex="$(printf "%s" "$_ovh_p" | _digest sha1 hex)" 274 | _debug2 _ovh_hex "$_ovh_hex" 275 | 276 | export _H1="X-Ovh-Application: $OVH_AK" 277 | export _H2="X-Ovh-Signature: \$1\$$_ovh_hex" 278 | _debug2 _H2 "$_H2" 279 | export _H3="X-Ovh-Timestamp: $_ovh_t" 280 | export _H4="X-Ovh-Consumer: $OVH_CK" 281 | export _H5="Content-Type: application/json;charset=utf-8" 282 | if [ "$data" ] || [ "$m" = "POST" ] || [ "$m" = "PUT" ]; then 283 | _debug data "$data" 284 | response="$(_post "$data" "$_ovh_url" "" "$m")" 285 | else 286 | response="$(_get "$_ovh_url")" 287 | fi 288 | 289 | if [ "$?" != "0" ]; then 290 | _err "error $ep" 291 | return 1 292 | fi 293 | _debug2 response "$response" 294 | return 0 295 | } 296 | -------------------------------------------------------------------------------- /dnsapi/dns_cyon.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ######## 4 | # Custom cyon.ch DNS API for use with [acme.sh](https://github.com/Neilpang/acme.sh) 5 | # 6 | # Usage: acme.sh --issue --dns dns_cyon -d www.domain.com 7 | # 8 | # Dependencies: 9 | # ------------- 10 | # - oathtool (When using 2 Factor Authentication) 11 | # 12 | # Issues: 13 | # ------- 14 | # Any issues / questions / suggestions can be posted here: 15 | # https://github.com/noplanman/cyon-api/issues 16 | # 17 | # Author: Armando Lüscher 18 | ######## 19 | 20 | dns_cyon_add() { 21 | _cyon_load_credentials \ 22 | && _cyon_load_parameters "$@" \ 23 | && _cyon_print_header "add" \ 24 | && _cyon_login \ 25 | && _cyon_change_domain_env \ 26 | && _cyon_add_txt \ 27 | && _cyon_logout 28 | } 29 | 30 | dns_cyon_rm() { 31 | _cyon_load_credentials \ 32 | && _cyon_load_parameters "$@" \ 33 | && _cyon_print_header "delete" \ 34 | && _cyon_login \ 35 | && _cyon_change_domain_env \ 36 | && _cyon_delete_txt \ 37 | && _cyon_logout 38 | } 39 | 40 | ######################### 41 | ### PRIVATE FUNCTIONS ### 42 | ######################### 43 | 44 | _cyon_load_credentials() { 45 | # Convert loaded password to/from base64 as needed. 46 | if [ "${CY_Password_B64}" ]; then 47 | CY_Password="$(printf "%s" "${CY_Password_B64}" | _dbase64 "multiline")" 48 | elif [ "${CY_Password}" ]; then 49 | CY_Password_B64="$(printf "%s" "${CY_Password}" | _base64)" 50 | fi 51 | 52 | if [ -z "${CY_Username}" ] || [ -z "${CY_Password}" ]; then 53 | # Dummy entries to satify script checker. 54 | CY_Username="" 55 | CY_Password="" 56 | CY_OTP_Secret="" 57 | 58 | _err "" 59 | _err "You haven't set your cyon.ch login credentials yet." 60 | _err "Please set the required cyon environment variables." 61 | _err "" 62 | return 1 63 | fi 64 | 65 | # Save the login credentials to the account.conf file. 66 | _debug "Save credentials to account.conf" 67 | _saveaccountconf CY_Username "${CY_Username}" 68 | _saveaccountconf CY_Password_B64 "$CY_Password_B64" 69 | if [ ! -z "${CY_OTP_Secret}" ]; then 70 | _saveaccountconf CY_OTP_Secret "$CY_OTP_Secret" 71 | else 72 | _clearaccountconf CY_OTP_Secret 73 | fi 74 | } 75 | 76 | _cyon_is_idn() { 77 | _idn_temp="$(printf "%s" "${1}" | tr -d "0-9a-zA-Z.,-_")" 78 | _idn_temp2="$(printf "%s" "${1}" | grep -o "xn--")" 79 | [ "$_idn_temp" ] || [ "$_idn_temp2" ] 80 | } 81 | 82 | _cyon_load_parameters() { 83 | # Read the required parameters to add the TXT entry. 84 | # shellcheck disable=SC2018,SC2019 85 | fulldomain="$(printf "%s" "${1}" | tr "A-Z" "a-z")" 86 | fulldomain_idn="${fulldomain}" 87 | 88 | # Special case for IDNs, as cyon needs a domain environment change, 89 | # which uses the "pretty" instead of the punycode version. 90 | if _cyon_is_idn "${fulldomain}"; then 91 | if ! _exists idn; then 92 | _err "Please install idn to process IDN names." 93 | _err "" 94 | return 1 95 | fi 96 | 97 | fulldomain="$(idn -u "${fulldomain}")" 98 | fulldomain_idn="$(idn -a "${fulldomain}")" 99 | fi 100 | 101 | _debug fulldomain "${fulldomain}" 102 | _debug fulldomain_idn "${fulldomain_idn}" 103 | 104 | txtvalue="${2}" 105 | _debug txtvalue "${txtvalue}" 106 | 107 | # This header is required for curl calls. 108 | _H1="X-Requested-With: XMLHttpRequest" 109 | export _H1 110 | } 111 | 112 | _cyon_print_header() { 113 | if [ "${1}" = "add" ]; then 114 | _info "" 115 | _info "+---------------------------------------------+" 116 | _info "| Adding DNS TXT entry to your cyon.ch domain |" 117 | _info "+---------------------------------------------+" 118 | _info "" 119 | _info " * Full Domain: ${fulldomain}" 120 | _info " * TXT Value: ${txtvalue}" 121 | _info "" 122 | elif [ "${1}" = "delete" ]; then 123 | _info "" 124 | _info "+-------------------------------------------------+" 125 | _info "| Deleting DNS TXT entry from your cyon.ch domain |" 126 | _info "+-------------------------------------------------+" 127 | _info "" 128 | _info " * Full Domain: ${fulldomain}" 129 | _info "" 130 | fi 131 | } 132 | 133 | _cyon_get_cookie_header() { 134 | printf "Cookie: %s" "$(grep "cyon=" "$HTTP_HEADER" | grep "^Set-Cookie:" | _tail_n 1 | _egrep_o 'cyon=[^;]*;' | tr -d ';')" 135 | } 136 | 137 | _cyon_login() { 138 | _info " - Logging in..." 139 | 140 | username_encoded="$(printf "%s" "${CY_Username}" | _url_encode)" 141 | password_encoded="$(printf "%s" "${CY_Password}" | _url_encode)" 142 | 143 | login_url="https://my.cyon.ch/auth/index/dologin-async" 144 | login_data="$(printf "%s" "username=${username_encoded}&password=${password_encoded}&pathname=%2F")" 145 | 146 | login_response="$(_post "$login_data" "$login_url")" 147 | _debug login_response "${login_response}" 148 | 149 | # Bail if login fails. 150 | if [ "$(printf "%s" "${login_response}" | _cyon_get_response_success)" != "success" ]; then 151 | _err " $(printf "%s" "${login_response}" | _cyon_get_response_message)" 152 | _err "" 153 | return 1 154 | fi 155 | 156 | _info " success" 157 | 158 | # NECESSARY!! Load the main page after login, to get the new cookie. 159 | _H2="$(_cyon_get_cookie_header)" 160 | export _H2 161 | 162 | _get "https://my.cyon.ch/" >/dev/null 163 | 164 | # todo: instead of just checking if the env variable is defined, check if we actually need to do a 2FA auth request. 165 | 166 | # 2FA authentication with OTP? 167 | if [ ! -z "${CY_OTP_Secret}" ]; then 168 | _info " - Authorising with OTP code..." 169 | 170 | if ! _exists oathtool; then 171 | _err "Please install oathtool to use 2 Factor Authentication." 172 | _err "" 173 | return 1 174 | fi 175 | 176 | # Get OTP code with the defined secret. 177 | otp_code="$(oathtool --base32 --totp "${CY_OTP_Secret}" 2>/dev/null)" 178 | 179 | login_otp_url="https://my.cyon.ch/auth/multi-factor/domultifactorauth-async" 180 | login_otp_data="totpcode=${otp_code}&pathname=%2F&rememberme=0" 181 | 182 | login_otp_response="$(_post "$login_otp_data" "$login_otp_url")" 183 | _debug login_otp_response "${login_otp_response}" 184 | 185 | # Bail if OTP authentication fails. 186 | if [ "$(printf "%s" "${login_otp_response}" | _cyon_get_response_success)" != "success" ]; then 187 | _err " $(printf "%s" "${login_otp_response}" | _cyon_get_response_message)" 188 | _err "" 189 | return 1 190 | fi 191 | 192 | _info " success" 193 | fi 194 | 195 | _info "" 196 | } 197 | 198 | _cyon_logout() { 199 | _info " - Logging out..." 200 | 201 | _get "https://my.cyon.ch/auth/index/dologout" >/dev/null 202 | 203 | _info " success" 204 | _info "" 205 | } 206 | 207 | _cyon_change_domain_env() { 208 | _info " - Changing domain environment..." 209 | 210 | # Get the "example.com" part of the full domain name. 211 | domain_env="$(printf "%s" "${fulldomain}" | sed -E -e 's/.*\.(.*\..*)$/\1/')" 212 | _debug "Changing domain environment to ${domain_env}" 213 | 214 | gloo_item_key="$(_get "https://my.cyon.ch/domain/" | tr '\n' ' ' | sed -E -e "s/.*data-domain=\"${domain_env}\"[^<]*data-itemkey=\"([^\"]*).*/\1/")" 215 | _debug gloo_item_key "${gloo_item_key}" 216 | 217 | domain_env_url="https://my.cyon.ch/user/environment/setdomain/d/${domain_env}/gik/${gloo_item_key}" 218 | 219 | domain_env_response="$(_get "${domain_env_url}")" 220 | _debug domain_env_response "${domain_env_response}" 221 | 222 | if ! _cyon_check_if_2fa_missed "${domain_env_response}"; then return 1; fi 223 | 224 | domain_env_success="$(printf "%s" "${domain_env_response}" | _egrep_o '"authenticated":\w*' | cut -d : -f 2)" 225 | 226 | # Bail if domain environment change fails. 227 | if [ "${domain_env_success}" != "true" ]; then 228 | _err " $(printf "%s" "${domain_env_response}" | _cyon_get_response_message)" 229 | _err "" 230 | return 1 231 | fi 232 | 233 | _info " success" 234 | _info "" 235 | } 236 | 237 | _cyon_add_txt() { 238 | _info " - Adding DNS TXT entry..." 239 | 240 | add_txt_url="https://my.cyon.ch/domain/dnseditor/add-record-async" 241 | add_txt_data="zone=${fulldomain_idn}.&ttl=900&type=TXT&value=${txtvalue}" 242 | 243 | add_txt_response="$(_post "$add_txt_data" "$add_txt_url")" 244 | _debug add_txt_response "${add_txt_response}" 245 | 246 | if ! _cyon_check_if_2fa_missed "${add_txt_response}"; then return 1; fi 247 | 248 | add_txt_message="$(printf "%s" "${add_txt_response}" | _cyon_get_response_message)" 249 | add_txt_status="$(printf "%s" "${add_txt_response}" | _cyon_get_response_status)" 250 | 251 | # Bail if adding TXT entry fails. 252 | if [ "${add_txt_status}" != "true" ]; then 253 | _err " ${add_txt_message}" 254 | _err "" 255 | return 1 256 | fi 257 | 258 | _info " success (TXT|${fulldomain_idn}.|${txtvalue})" 259 | _info "" 260 | } 261 | 262 | _cyon_delete_txt() { 263 | _info " - Deleting DNS TXT entry..." 264 | 265 | list_txt_url="https://my.cyon.ch/domain/dnseditor/list-async" 266 | 267 | list_txt_response="$(_get "${list_txt_url}" | sed -e 's/data-hash/\\ndata-hash/g')" 268 | _debug list_txt_response "${list_txt_response}" 269 | 270 | if ! _cyon_check_if_2fa_missed "${list_txt_response}"; then return 1; fi 271 | 272 | # Find and delete all acme challenge entries for the $fulldomain. 273 | _dns_entries="$(printf "%b\n" "${list_txt_response}" | sed -n 's/data-hash=\\"\([^"]*\)\\" data-identifier=\\"\([^"]*\)\\".*/\1 \2/p')" 274 | 275 | printf "%s" "${_dns_entries}" | while read -r _hash _identifier; do 276 | dns_type="$(printf "%s" "$_identifier" | cut -d'|' -f1)" 277 | dns_domain="$(printf "%s" "$_identifier" | cut -d'|' -f2)" 278 | 279 | if [ "${dns_type}" != "TXT" ] || [ "${dns_domain}" != "${fulldomain_idn}." ]; then 280 | continue 281 | fi 282 | 283 | hash_encoded="$(printf "%s" "${_hash}" | _url_encode)" 284 | identifier_encoded="$(printf "%s" "${_identifier}" | _url_encode)" 285 | 286 | delete_txt_url="https://my.cyon.ch/domain/dnseditor/delete-record-async" 287 | delete_txt_data="$(printf "%s" "hash=${hash_encoded}&identifier=${identifier_encoded}")" 288 | 289 | delete_txt_response="$(_post "$delete_txt_data" "$delete_txt_url")" 290 | _debug delete_txt_response "${delete_txt_response}" 291 | 292 | if ! _cyon_check_if_2fa_missed "${delete_txt_response}"; then return 1; fi 293 | 294 | delete_txt_message="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_message)" 295 | delete_txt_status="$(printf "%s" "${delete_txt_response}" | _cyon_get_response_status)" 296 | 297 | # Skip if deleting TXT entry fails. 298 | if [ "${delete_txt_status}" != "true" ]; then 299 | _err " ${delete_txt_message} (${_identifier})" 300 | else 301 | _info " success (${_identifier})" 302 | fi 303 | done 304 | 305 | _info " done" 306 | _info "" 307 | } 308 | 309 | _cyon_get_response_message() { 310 | _egrep_o '"message":"[^"]*"' | cut -d : -f 2 | tr -d '"' 311 | } 312 | 313 | _cyon_get_response_status() { 314 | _egrep_o '"status":\w*' | cut -d : -f 2 315 | } 316 | 317 | _cyon_get_response_success() { 318 | _egrep_o '"onSuccess":"[^"]*"' | cut -d : -f 2 | tr -d '"' 319 | } 320 | 321 | _cyon_check_if_2fa_missed() { 322 | # Did we miss the 2FA? 323 | if test "${1#*multi_factor_form}" != "${1}"; then 324 | _err " Missed OTP authentication!" 325 | _err "" 326 | return 1 327 | fi 328 | } 329 | -------------------------------------------------------------------------------- /dnsapi/README.md: -------------------------------------------------------------------------------- 1 | # How to use DNS API 2 | 3 | ## 1. Use CloudFlare domain API to automatically issue cert 4 | 5 | First you need to login to your CloudFlare account to get your API key. 6 | 7 | ``` 8 | export CF_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" 9 | export CF_Email="xxxx@sss.com" 10 | ``` 11 | 12 | Ok, let's issue a cert now: 13 | ``` 14 | acme.sh --issue --dns dns_cf -d example.com -d www.example.com 15 | ``` 16 | 17 | The `CF_Key` and `CF_Email` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. 18 | 19 | 20 | ## 2. Use DNSPod.cn domain API to automatically issue cert 21 | 22 | First you need to login to your DNSPod account to get your API Key and ID. 23 | 24 | ``` 25 | export DP_Id="1234" 26 | export DP_Key="sADDsdasdgdsf" 27 | ``` 28 | 29 | Ok, let's issue a cert now: 30 | ``` 31 | acme.sh --issue --dns dns_dp -d example.com -d www.example.com 32 | ``` 33 | 34 | The `DP_Id` and `DP_Key` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. 35 | 36 | 37 | ## 3. Use CloudXNS.com domain API to automatically issue cert 38 | 39 | First you need to login to your CloudXNS account to get your API Key and Secret. 40 | 41 | ``` 42 | export CX_Key="1234" 43 | export CX_Secret="sADDsdasdgdsf" 44 | ``` 45 | 46 | Ok, let's issue a cert now: 47 | ``` 48 | acme.sh --issue --dns dns_cx -d example.com -d www.example.com 49 | ``` 50 | 51 | The `CX_Key` and `CX_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. 52 | 53 | 54 | ## 4. Use GoDaddy.com domain API to automatically issue cert 55 | 56 | First you need to login to your GoDaddy account to get your API Key and Secret. 57 | 58 | https://developer.godaddy.com/keys/ 59 | 60 | Please create a Production key, instead of a Test key. 61 | 62 | ``` 63 | export GD_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" 64 | export GD_Secret="asdfsdafdsfdsfdsfdsfdsafd" 65 | ``` 66 | 67 | Ok, let's issue a cert now: 68 | ``` 69 | acme.sh --issue --dns dns_gd -d example.com -d www.example.com 70 | ``` 71 | 72 | The `GD_Key` and `GD_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. 73 | 74 | 75 | ## 5. Use PowerDNS embedded API to automatically issue cert 76 | 77 | First you need to login to your PowerDNS account to enable the API and set your API-Token in the configuration. 78 | 79 | https://doc.powerdns.com/md/httpapi/README/ 80 | 81 | ``` 82 | export PDNS_Url="http://ns.example.com:8081" 83 | export PDNS_ServerId="localhost" 84 | export PDNS_Token="0123456789ABCDEF" 85 | export PDNS_Ttl=60 86 | ``` 87 | 88 | Ok, let's issue a cert now: 89 | ``` 90 | acme.sh --issue --dns dns_pdns -d example.com -d www.example.com 91 | ``` 92 | 93 | The `PDNS_Url`, `PDNS_ServerId`, `PDNS_Token` and `PDNS_Ttl` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. 94 | 95 | 96 | ## 6. Use OVH/kimsufi/soyoustart/runabove API to automatically issue cert 97 | 98 | https://github.com/Neilpang/acme.sh/wiki/How-to-use-OVH-domain-api 99 | 100 | 101 | ## 7. Use nsupdate to automatically issue cert 102 | 103 | First, generate a key for updating the zone 104 | ``` 105 | b=$(dnssec-keygen -a hmac-sha512 -b 512 -n USER -K /tmp foo) 106 | cat > /etc/named/keys/update.key < /etc/knot/acme.key 358 | ``` 359 | 360 | Include this key in your knot configuration file. 361 | 362 | ``` 363 | include: /etc/knot/acme.key 364 | ``` 365 | 366 | Next, configure your zone to allow dynamic updates. 367 | 368 | Dynamic updates for the zone are allowed via proper ACL rule with the `update` action. For in-depth instructions, please see [Knot DNS's documentation](https://www.knot-dns.cz/documentation/). 369 | 370 | ``` 371 | acl: 372 | - id: acme_acl 373 | address: 192.168.1.0/24 374 | key: acme_key 375 | action: update 376 | 377 | zone: 378 | - domain: example.com 379 | file: example.com.zone 380 | acl: acme_acl 381 | ``` 382 | 383 | Finally, make the DNS server and TSIG Key available to `acme.sh` 384 | 385 | ``` 386 | export KNOT_SERVER="dns.example.com" 387 | export KNOT_KEY=`grep \# /etc/knot/acme.key | cut -d' ' -f2` 388 | ``` 389 | 390 | Ok, let's issue a cert now: 391 | ``` 392 | acme.sh --issue --dns dns_knot -d example.com -d www.example.com 393 | ``` 394 | 395 | The `KNOT_SERVER` and `KNOT_KEY` settings will be saved in `~/.acme.sh/account.conf` and will be reused when needed. 396 | 397 | ## 20. Use DigitalOcean API (native) 398 | 399 | You need to obtain a read and write capable API key from your DigitalOcean account. See: https://www.digitalocean.com/help/api/ 400 | 401 | ``` 402 | export DO_API_KEY="75310dc4ca779ac39a19f6355db573b49ce92ae126553ebd61ac3a3ae34834cc" 403 | ``` 404 | 405 | Ok, let's issue a cert now: 406 | ``` 407 | acme.sh --issue --dns dns_dgon -d example.com -d www.example.com 408 | ``` 409 | 410 | # Use custom API 411 | 412 | If your API is not supported yet, you can write your own DNS API. 413 | 414 | Let's assume you want to name it 'myapi': 415 | 416 | 1. Create a bash script named `~/.acme.sh/dns_myapi.sh`, 417 | 2. In the script you must have a function named `dns_myapi_add()` which will be called by acme.sh to add the DNS records. 418 | 3. Then you can use your API to issue cert like this: 419 | 420 | ``` 421 | acme.sh --issue --dns dns_myapi -d example.com -d www.example.com 422 | ``` 423 | 424 | For more details, please check our sample script: [dns_myapi.sh](dns_myapi.sh) 425 | 426 | 427 | # Use lexicon DNS API 428 | 429 | https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api 430 | -------------------------------------------------------------------------------- /dnsapi/dns_freedns.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | #This file name is "dns_freedns.sh" 4 | #So, here must be a method dns_freedns_add() 5 | #Which will be called by acme.sh to add the txt record to your api system. 6 | #returns 0 means success, otherwise error. 7 | # 8 | #Author: David Kerr 9 | #Report Bugs here: https://github.com/dkerr64/acme.sh 10 | # 11 | ######## Public functions ##################### 12 | 13 | # Export FreeDNS userid and password in folowing variables... 14 | # FREEDNS_User=username 15 | # FREEDNS_Password=password 16 | # login cookie is saved in acme account config file so userid / pw 17 | # need to be set only when changed. 18 | 19 | #Usage: dns_freedns_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" 20 | dns_freedns_add() { 21 | fulldomain="$1" 22 | txtvalue="$2" 23 | 24 | _info "Add TXT record using FreeDNS" 25 | _debug "fulldomain: $fulldomain" 26 | _debug "txtvalue: $txtvalue" 27 | 28 | if [ -z "$FREEDNS_User" ] || [ -z "$FREEDNS_Password" ]; then 29 | FREEDNS_User="" 30 | FREEDNS_Password="" 31 | if [ -z "$FREEDNS_COOKIE" ]; then 32 | _err "You did not specify the FreeDNS username and password yet." 33 | _err "Please export as FREEDNS_User / FREEDNS_Password and try again." 34 | return 1 35 | fi 36 | using_cached_cookies="true" 37 | else 38 | FREEDNS_COOKIE="$(_freedns_login "$FREEDNS_User" "$FREEDNS_Password")" 39 | if [ -z "$FREEDNS_COOKIE" ]; then 40 | return 1 41 | fi 42 | using_cached_cookies="false" 43 | fi 44 | 45 | _debug "FreeDNS login cookies: $FREEDNS_COOKIE (cached = $using_cached_cookies)" 46 | 47 | _saveaccountconf FREEDNS_COOKIE "$FREEDNS_COOKIE" 48 | 49 | # split our full domain name into two parts... 50 | i="$(echo "$fulldomain" | tr '.' ' ' | wc -w)" 51 | i="$(_math "$i" - 1)" 52 | top_domain="$(echo "$fulldomain" | cut -d. -f "$i"-100)" 53 | i="$(_math "$i" - 1)" 54 | sub_domain="$(echo "$fulldomain" | cut -d. -f -"$i")" 55 | 56 | # Sometimes FreeDNS does not reurn the subdomain page but rather 57 | # returns a page regarding becoming a premium member. This usually 58 | # happens after a period of inactivity. Immediately trying again 59 | # returns the correct subdomain page. So, we will try twice to 60 | # load the page and obtain our domain ID 61 | attempts=2 62 | while [ "$attempts" -gt "0" ]; do 63 | attempts="$(_math "$attempts" - 1)" 64 | 65 | htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" 66 | if [ "$?" != "0" ]; then 67 | if [ "$using_cached_cookies" = "true" ]; then 68 | _err "Has your FreeDNS username and password channged? If so..." 69 | _err "Please export as FREEDNS_User / FREEDNS_Password and try again." 70 | fi 71 | return 1 72 | fi 73 | 74 | # Now convert the tables in the HTML to CSV. This litte gem from 75 | # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv 76 | subdomain_csv="$(echo "$htmlpage" \ 77 | | grep -i -e ']*>/\n/Ig' \ 81 | | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ 82 | | sed 's/^]*>\|<\/\?T[DH][^>]*>$//Ig' \ 83 | | sed 's/<\/T[DH][^>]*>]*>/,/Ig' \ 84 | | grep 'edit.php?' \ 85 | | grep "$top_domain")" 86 | # The above beauty ends with striping out rows that do not have an 87 | # href to edit.php and do not have the top domain we are looking for. 88 | # So all we should be left with is CSV of table of subdomains we are 89 | # interested in. 90 | 91 | # Now we have to read through this table and extract the data we need 92 | lines="$(echo "$subdomain_csv" | wc -l)" 93 | nl=' 94 | ' 95 | i=0 96 | found=0 97 | while [ "$i" -lt "$lines" ]; do 98 | i="$(_math "$i" + 1)" 99 | line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" 100 | tmp="$(echo "$line" | cut -d ',' -f 1)" 101 | if [ $found = 0 ] && _startswith "$tmp" "$top_domain"; then 102 | # this line will contain DNSdomainid for the top_domain 103 | DNSdomainid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*domain_id=//;s/>.*//')" 104 | found=1 105 | else 106 | # lines contain DNS records for all subdomains 107 | DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" 108 | DNStype="$(echo "$line" | cut -d ',' -f 3)" 109 | if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then 110 | DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" 111 | # Now get current value for the TXT record. This method may 112 | # not produce accurate results as the value field is truncated 113 | # on this webpage. To get full value we would need to load 114 | # another page. However we don't really need this so long as 115 | # there is only one TXT record for the acme chalenge subdomain. 116 | DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" 117 | if [ $found != 0 ]; then 118 | break 119 | # we are breaking out of the loop at the first match of DNS name 120 | # and DNS type (if we are past finding the domainid). This assumes 121 | # that there is only ever one TXT record for the LetsEncrypt/acme 122 | # challenge subdomain. This seems to be a reasonable assumption 123 | # as the acme client deletes the TXT record on successful validation. 124 | fi 125 | else 126 | DNSname="" 127 | DNStype="" 128 | fi 129 | fi 130 | done 131 | 132 | _debug "DNSname: $DNSname DNStype: $DNStype DNSdomainid: $DNSdomainid DNSdataid: $DNSdataid" 133 | _debug "DNSvalue: $DNSvalue" 134 | 135 | if [ -z "$DNSdomainid" ]; then 136 | # If domain ID is empty then something went wrong (top level 137 | # domain not found at FreeDNS). 138 | if [ "$attempts" = "0" ]; then 139 | # exhausted maximum retry attempts 140 | _debug "$htmlpage" 141 | _debug "$subdomain_csv" 142 | _err "Domain $top_domain not found at FreeDNS" 143 | return 1 144 | fi 145 | else 146 | # break out of the 'retry' loop... we have found our domain ID 147 | break 148 | fi 149 | _info "Domain $top_domain not found at FreeDNS" 150 | _info "Retry loading subdomain page ($attempts attempts remaining)" 151 | done 152 | 153 | if [ -z "$DNSdataid" ]; then 154 | # If data ID is empty then specific subdomain does not exist yet, need 155 | # to create it this should always be the case as the acme client 156 | # deletes the entry after domain is validated. 157 | _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" 158 | return $? 159 | else 160 | if [ "$txtvalue" = "$DNSvalue" ]; then 161 | # if value in TXT record matches value requested then DNS record 162 | # does not need to be updated. But... 163 | # Testing value match fails. Website is truncating the value field. 164 | # So for now we will always go down the else path. Though in theory 165 | # should never come here anyway as the acme client deletes 166 | # the TXT record on successful validation, so we should not even 167 | # have found a TXT record !! 168 | _info "No update necessary for $fulldomain at FreeDNS" 169 | return 0 170 | else 171 | # Delete the old TXT record (with the wrong value) 172 | _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" 173 | if [ "$?" = "0" ]; then 174 | # And add in new TXT record with the value provided 175 | _freedns_add_txt_record "$FREEDNS_COOKIE" "$DNSdomainid" "$sub_domain" "$txtvalue" 176 | fi 177 | return $? 178 | fi 179 | fi 180 | return 0 181 | } 182 | 183 | #Usage: fulldomain txtvalue 184 | #Remove the txt record after validation. 185 | dns_freedns_rm() { 186 | fulldomain="$1" 187 | txtvalue="$2" 188 | 189 | _info "Delete TXT record using FreeDNS" 190 | _debug "fulldomain: $fulldomain" 191 | _debug "txtvalue: $txtvalue" 192 | 193 | # Need to read cookie from conf file again in case new value set 194 | # during login to FreeDNS when TXT record was created. 195 | # acme.sh does not have a _readaccountconf() fuction 196 | FREEDNS_COOKIE="$(_read_conf "$ACCOUNT_CONF_PATH" "FREEDNS_COOKIE")" 197 | _debug "FreeDNS login cookies: $FREEDNS_COOKIE" 198 | 199 | # Sometimes FreeDNS does not reurn the subdomain page but rather 200 | # returns a page regarding becoming a premium member. This usually 201 | # happens after a period of inactivity. Immediately trying again 202 | # returns the correct subdomain page. So, we will try twice to 203 | # load the page and obtain our TXT record. 204 | attempts=2 205 | while [ "$attempts" -gt "0" ]; do 206 | attempts="$(_math "$attempts" - 1)" 207 | 208 | htmlpage="$(_freedns_retrieve_subdomain_page "$FREEDNS_COOKIE")" 209 | if [ "$?" != "0" ]; then 210 | return 1 211 | fi 212 | 213 | # Now convert the tables in the HTML to CSV. This litte gem from 214 | # http://stackoverflow.com/questions/1403087/how-can-i-convert-an-html-table-to-csv 215 | subdomain_csv="$(echo "$htmlpage" \ 216 | | grep -i -e ']*>/\n/Ig' \ 220 | | sed 's/<\/\?\(TABLE\|TR\)[^>]*>//Ig' \ 221 | | sed 's/^]*>\|<\/\?T[DH][^>]*>$//Ig' \ 222 | | sed 's/<\/T[DH][^>]*>]*>/,/Ig' \ 223 | | grep 'edit.php?' \ 224 | | grep "$fulldomain")" 225 | # The above beauty ends with striping out rows that do not have an 226 | # href to edit.php and do not have the domain name we are looking for. 227 | # So all we should be left with is CSV of table of subdomains we are 228 | # interested in. 229 | 230 | # Now we have to read through this table and extract the data we need 231 | lines="$(echo "$subdomain_csv" | wc -l)" 232 | nl=' 233 | ' 234 | i=0 235 | found=0 236 | while [ "$i" -lt "$lines" ]; do 237 | i="$(_math "$i" + 1)" 238 | line="$(echo "$subdomain_csv" | cut -d "$nl" -f "$i")" 239 | DNSname="$(echo "$line" | cut -d ',' -f 2 | sed 's/^[^>]*>//;s/<\/a>.*//')" 240 | DNStype="$(echo "$line" | cut -d ',' -f 3)" 241 | if [ "$DNSname" = "$fulldomain" ] && [ "$DNStype" = "TXT" ]; then 242 | DNSdataid="$(echo "$line" | cut -d ',' -f 2 | sed 's/^.*data_id=//;s/>.*//')" 243 | DNSvalue="$(echo "$line" | cut -d ',' -f 4 | sed 's/^[^"]*"//;s/".*//;s/<\/td>.*//')" 244 | _debug "DNSvalue: $DNSvalue" 245 | # if [ "$DNSvalue" = "$txtvalue" ]; then 246 | # Testing value match fails. Website is truncating the value 247 | # field. So for now we will assume that there is only one TXT 248 | # field for the sub domain and just delete it. Currently this 249 | # is a safe assumption. 250 | _freedns_delete_txt_record "$FREEDNS_COOKIE" "$DNSdataid" 251 | return $? 252 | # fi 253 | fi 254 | done 255 | done 256 | 257 | # If we get this far we did not find a match (after two attempts) 258 | # Not necessarily an error, but log anyway. 259 | _debug2 "$subdomain_csv" 260 | _info "Cannot delete TXT record for $fulldomain/$txtvalue. Does not exist at FreeDNS" 261 | return 0 262 | } 263 | 264 | #################### Private functions below ################################## 265 | 266 | # usage: _freedns_login username password 267 | # print string "cookie=value" etc. 268 | # returns 0 success 269 | _freedns_login() { 270 | export _H1="Accept-Language:en-US" 271 | username="$1" 272 | password="$2" 273 | url="https://freedns.afraid.org/zc.php?step=2" 274 | 275 | _debug "Login to FreeDNS as user $username" 276 | 277 | htmlpage="$(_post "username=$(printf '%s' "$username" | _url_encode)&password=$(printf '%s' "$password" | _url_encode)&submit=Login&action=auth" "$url")" 278 | 279 | if [ "$?" != "0" ]; then 280 | _err "FreeDNS login failed for user $username bad RC from _post" 281 | return 1 282 | fi 283 | 284 | cookies="$(grep -i '^Set-Cookie.*dns_cookie.*$' "$HTTP_HEADER" | _head_n 1 | tr -d "\r\n" | cut -d " " -f 2)" 285 | 286 | # if cookies is not empty then logon successful 287 | if [ -z "$cookies" ]; then 288 | _debug "$htmlpage" 289 | _err "FreeDNS login failed for user $username. Check $HTTP_HEADER file" 290 | return 1 291 | fi 292 | 293 | printf "%s" "$cookies" 294 | return 0 295 | } 296 | 297 | # usage _freedns_retrieve_subdomain_page login_cookies 298 | # echo page retrieved (html) 299 | # returns 0 success 300 | _freedns_retrieve_subdomain_page() { 301 | export _H1="Cookie:$1" 302 | export _H2="Accept-Language:en-US" 303 | url="https://freedns.afraid.org/subdomain/" 304 | 305 | _debug "Retrieve subdmoain page from FreeDNS" 306 | 307 | htmlpage="$(_get "$url")" 308 | 309 | if [ "$?" != "0" ]; then 310 | _err "FreeDNS retrieve subdomins failed bad RC from _get" 311 | return 1 312 | elif [ -z "$htmlpage" ]; then 313 | _err "FreeDNS returned empty subdomain page" 314 | return 1 315 | fi 316 | 317 | _debug2 "$htmlpage" 318 | 319 | printf "%s" "$htmlpage" 320 | return 0 321 | } 322 | 323 | # usage _freedns_add_txt_record login_cookies domain_id subdomain value 324 | # returns 0 success 325 | _freedns_add_txt_record() { 326 | export _H1="Cookie:$1" 327 | export _H2="Accept-Language:en-US" 328 | domain_id="$2" 329 | subdomain="$3" 330 | value="$(printf '%s' "$4" | _url_encode)" 331 | url="http://freedns.afraid.org/subdomain/save.php?step=2" 332 | 333 | htmlpage="$(_post "type=TXT&domain_id=$domain_id&subdomain=$subdomain&address=%22$value%22&send=Save%21" "$url")" 334 | 335 | if [ "$?" != "0" ]; then 336 | _err "FreeDNS failed to add TXT record for $subdomain bad RC from _post" 337 | return 1 338 | elif ! grep "200 OK" "$HTTP_HEADER" >/dev/null; then 339 | _debug "$htmlpage" 340 | _err "FreeDNS failed to add TXT record for $subdomain. Check $HTTP_HEADER file" 341 | return 1 342 | elif _contains "$htmlpage" "security code was incorrect"; then 343 | _debug "$htmlpage" 344 | _err "FreeDNS failed to add TXT record for $subdomain as FreeDNS requested seurity code" 345 | _err "Note that you cannot use automatic DNS validation for FreeDNS public domains" 346 | return 1 347 | fi 348 | 349 | _debug2 "$htmlpage" 350 | _info "Added acme challenge TXT record for $fulldomain at FreeDNS" 351 | return 0 352 | } 353 | 354 | # usage _freedns_delete_txt_record login_cookies data_id 355 | # returns 0 success 356 | _freedns_delete_txt_record() { 357 | export _H1="Cookie:$1" 358 | export _H2="Accept-Language:en-US" 359 | data_id="$2" 360 | url="https://freedns.afraid.org/subdomain/delete2.php" 361 | 362 | htmlheader="$(_get "$url?data_id%5B%5D=$data_id&submit=delete+selected" "onlyheader")" 363 | 364 | if [ "$?" != "0" ]; then 365 | _err "FreeDNS failed to delete TXT record for $data_id bad RC from _get" 366 | return 1 367 | elif ! _contains "$htmlheader" "200 OK"; then 368 | _debug "$htmlheader" 369 | _err "FreeDNS failed to delete TXT record $data_id" 370 | return 1 371 | fi 372 | 373 | _info "Deleted acme challenge TXT record for $fulldomain at FreeDNS" 374 | return 0 375 | } 376 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 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) 2 | - An ACME protocol client written purely in Shell (Unix shell) language. 3 | - Full ACME protocol implementation. 4 | - Simple, powerful and very easy to use. You only need 3 minutes to learn it. 5 | - Bash, dash and sh compatible. 6 | - Simplest shell script for Let's Encrypt free certificate client. 7 | - Purely written in Shell with no dependencies on python or the official Let's Encrypt client. 8 | - Just one script to issue, renew and install your certificates automatically. 9 | - DOES NOT require `root/sudoer` access. 10 | 11 | It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. 12 | 13 | Wiki: https://github.com/Neilpang/acme.sh/wiki 14 | 15 | 16 | Twitter: [@neilpangxa](https://twitter.com/neilpangxa) 17 | 18 | 19 | # [中文说明](https://github.com/Neilpang/acme.sh/wiki/%E8%AF%B4%E6%98%8E) 20 | 21 | # Who are using **acme.sh** 22 | - [FreeBSD.org](https://blog.crashed.org/letsencrypt-in-freebsd-org/) 23 | - [ruby-china.org](https://ruby-china.org/topics/31983) 24 | - [Proxmox](https://pve.proxmox.com/wiki/HTTPS_Certificate_Configuration_(Version_4.x_and_newer)) 25 | - [pfsense](https://github.com/pfsense/FreeBSD-ports/pull/89) 26 | - [webfaction](https://community.webfaction.com/questions/19988/using-letsencrypt) 27 | - [Loadbalancer.org](https://www.loadbalancer.org/blog/loadbalancer-org-with-lets-encrypt-quick-and-dirty) 28 | - [discourse.org](https://meta.discourse.org/t/setting-up-lets-encrypt/40709) 29 | - [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html) 30 | - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) 31 | - [archlinux](https://aur.archlinux.org/packages/acme.sh-git/) 32 | - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials) 33 | 34 | # Tested OS 35 | 36 | | NO | Status| Platform| 37 | |----|-------|---------| 38 | |1|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/ubuntu-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Ubuntu 39 | |2|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/debian-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Debian 40 | |3|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/centos-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|CentOS 41 | |4|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/windows-cygwin.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Windows (cygwin with curl, openssl and crontab included) 42 | |5|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/freebsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|FreeBSD 43 | |6|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/pfsense.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|pfsense 44 | |7|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/opensuse-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|openSUSE 45 | |8|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/alpine-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Alpine Linux (with curl) 46 | |9|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/base-archlinux.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Archlinux 47 | |10|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/fedora-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|fedora 48 | |11|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/kalilinux-kali-linux-docker.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Kali Linux 49 | |12|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/oraclelinux-latest.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Oracle Linux 50 | |13|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/proxmox.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)| Proxmox https://pve.proxmox.com/wiki/HTTPSCertificateConfiguration#Let.27s_Encrypt_using_acme.sh 51 | |14|-----| Cloud Linux https://github.com/Neilpang/le/issues/111 52 | |15|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/openbsd.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|OpenBSD 53 | |16|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/mageia.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Mageia 54 | |17|-----| OpenWRT: Tested and working. See [wiki page](https://github.com/Neilpang/acme.sh/wiki/How-to-run-on-OpenWRT) 55 | |18|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/solaris.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|SunOS/Solaris 56 | |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 57 | |20|[![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX 58 | 59 | For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest): 60 | 61 | https://github.com/Neilpang/acmetest 62 | 63 | 64 | # Supported modes 65 | 66 | - Webroot mode 67 | - Standalone mode 68 | - Apache mode 69 | - Nginx mode ( Beta ) 70 | - DNS mode 71 | - [Stateless mode](https://github.com/Neilpang/acme.sh/wiki/Stateless-Mode) 72 | 73 | 74 | # 1. How to install 75 | 76 | ### 1. Install online 77 | 78 | Check this project: https://github.com/Neilpang/get.acme.sh 79 | 80 | ```bash 81 | curl https://get.acme.sh | sh 82 | ``` 83 | 84 | Or: 85 | 86 | ```bash 87 | wget -O - https://get.acme.sh | sh 88 | ``` 89 | 90 | 91 | ### 2. Or, Install from git 92 | 93 | Clone this project and launch installation: 94 | 95 | ```bash 96 | git clone https://github.com/Neilpang/acme.sh.git 97 | cd ./acme.sh 98 | ./acme.sh --install 99 | ``` 100 | 101 | You `don't have to be root` then, although `it is recommended`. 102 | 103 | Advanced Installation: https://github.com/Neilpang/acme.sh/wiki/How-to-install 104 | 105 | The installer will perform 3 actions: 106 | 107 | 1. Create and copy `acme.sh` to your home dir (`$HOME`): `~/.acme.sh/`. 108 | All certs will be placed in this folder too. 109 | 2. Create alias for: `acme.sh=~/.acme.sh/acme.sh`. 110 | 3. Create daily cron job to check and renew the certs if needed. 111 | 112 | Cron entry example: 113 | 114 | ```bash 115 | 0 0 * * * "/home/user/.acme.sh"/acme.sh --cron --home "/home/user/.acme.sh" > /dev/null 116 | ``` 117 | 118 | After the installation, you must close the current terminal and reopen it to make the alias take effect. 119 | 120 | Ok, you are ready to issue certs now. 121 | 122 | Show help message: 123 | 124 | ``` 125 | root@v1:~# acme.sh -h 126 | ``` 127 | 128 | # 2. Just issue a cert 129 | 130 | **Example 1:** Single domain. 131 | 132 | ```bash 133 | acme.sh --issue -d example.com -w /home/wwwroot/example.com 134 | ``` 135 | 136 | **Example 2:** Multiple domains in the same cert. 137 | 138 | ```bash 139 | acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com 140 | ``` 141 | 142 | The parameter `/home/wwwroot/example.com` is the web root folder. You **MUST** have `write access` to this folder. 143 | 144 | Second argument **"example.com"** is the main domain you want to issue the cert for. 145 | You must have at least one domain there. 146 | 147 | You must point and bind all the domains to the same webroot dir: `/home/wwwroot/example.com`. 148 | 149 | Generated/issued certs will be placed in `~/.acme.sh/example.com/` 150 | 151 | The issued cert will be renewed automatically every **60** days. 152 | 153 | More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert 154 | 155 | 156 | # 3. Install the issued cert to Apache/Nginx etc. 157 | 158 | After you issue a cert, you probably want to install/copy the cert to your Apache/Nginx or other servers. 159 | 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. 160 | 161 | **Apache** example: 162 | ```bash 163 | acme.sh --install-cert -d example.com \ 164 | --certpath /path/to/certfile/in/apache/cert.pem \ 165 | --keypath /path/to/keyfile/in/apache/key.pem \ 166 | --fullchainpath /path/to/fullchain/certfile/apache/fullchain.pem \ 167 | --reloadcmd "service apache2 force-reload" 168 | ``` 169 | 170 | **Nginx** example: 171 | ```bash 172 | acme.sh --install-cert -d example.com \ 173 | --keypath /path/to/keyfile/in/nginx/key.pem \ 174 | --fullchainpath /path/to/fullchain/nginx/cert.pem \ 175 | --reloadcmd "service nginx force-reload" 176 | ``` 177 | 178 | Only the domain is required, all the other parameters are optional. 179 | 180 | The ownership and permission info of existing files are preserved. You may want to precreate the files to have defined ownership and permission. 181 | 182 | Install/copy the issued cert/key to the production Apache or Nginx path. 183 | 184 | 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`. 185 | 186 | 187 | # 4. Use Standalone server to issue cert 188 | 189 | **(requires you to be root/sudoer or have permission to listen on port 80 (TCP))** 190 | 191 | Port `80` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. 192 | 193 | ```bash 194 | acme.sh --issue --standalone -d example.com -d www.example.com -d cp.example.com 195 | ``` 196 | 197 | More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert 198 | 199 | 200 | # 5. Use Standalone TLS server to issue cert 201 | 202 | **(requires you to be root/sudoer or have permission to listen on port 443 (TCP))** 203 | 204 | acme.sh supports `tls-sni-01` validation. 205 | 206 | Port `443` (TCP) **MUST** be free to listen on, otherwise you will be prompted to free it and try again. 207 | 208 | ```bash 209 | acme.sh --issue --tls -d example.com -d www.example.com -d cp.example.com 210 | ``` 211 | 212 | More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert 213 | 214 | 215 | # 6. Use Apache mode 216 | 217 | **(requires you to be root/sudoer, since it is required to interact with Apache server)** 218 | 219 | If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. 220 | 221 | 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. 222 | 223 | Just set string "apache" as the second argument and it will force use of apache plugin automatically. 224 | 225 | ``` 226 | acme.sh --issue --apache -d example.com -d www.example.com -d cp.example.com 227 | ``` 228 | 229 | More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert 230 | 231 | # 7. Use Nginx mode 232 | 233 | **(requires you to be root/sudoer, since it is required to interact with Nginx server)** 234 | 235 | If you are running a web server, Apache or Nginx, it is recommended to use the `Webroot mode`. 236 | 237 | Particularly, if you are running an nginx server, you can use nginx mode instead. This mode doesn't write any files to your web root folder. 238 | 239 | Just set string "nginx" as the second argument. 240 | 241 | It will configure nginx server automatically to verify the domain and then restore the nginx config to the original version. 242 | 243 | So, the config is not changed. 244 | 245 | ``` 246 | acme.sh --issue --nginx -d example.com -d www.example.com -d cp.example.com 247 | ``` 248 | 249 | More examples: https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert 250 | 251 | # 8. Use DNS mode: 252 | 253 | Support the `dns-01` challenge. 254 | 255 | ```bash 256 | acme.sh --issue --dns -d example.com -d www.example.com -d cp.example.com 257 | ``` 258 | 259 | You should get an output like below: 260 | 261 | ``` 262 | Add the following txt record: 263 | Domain:_acme-challenge.example.com 264 | Txt value:9ihDbjYfTExAYeDs4DBUeuTo18KBzwvTEjUnSwd32-c 265 | 266 | Add the following txt record: 267 | Domain:_acme-challenge.www.example.com 268 | Txt value:9ihDbjxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 269 | 270 | Please add those txt records to the domains. Waiting for the dns to take effect. 271 | ``` 272 | 273 | Then just rerun with `renew` argument: 274 | 275 | ```bash 276 | acme.sh --renew -d example.com 277 | ``` 278 | 279 | Ok, it's finished. 280 | 281 | 282 | # 9. Automatic DNS API integration 283 | 284 | If your DNS provider supports API access, we can use that API to automatically issue the certs. 285 | 286 | You don't have to do anything manually! 287 | 288 | ### Currently acme.sh supports: 289 | 290 | 1. CloudFlare.com API 291 | 1. DNSPod.cn API 292 | 1. CloudXNS.com API 293 | 1. GoDaddy.com API 294 | 1. OVH, kimsufi, soyoustart and runabove API 295 | 1. AWS Route 53 296 | 1. PowerDNS.com API 297 | 1. lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api 298 | (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.) 299 | 1. LuaDNS.com API 300 | 1. DNSMadeEasy.com API 301 | 1. nsupdate API 302 | 1. aliyun.com(阿里云) API 303 | 1. ISPConfig 3.1 API 304 | 1. Alwaysdata.com API 305 | 1. Linode.com API 306 | 1. FreeDNS (https://freedns.afraid.org/) 307 | 1. cyon.ch 308 | 1. Domain-Offensive/Resellerinterface/Domainrobot API 309 | 1. Gandi LiveDNS API 310 | 1. Knot DNS API 311 | 1. DigitalOcean API (native) 312 | 313 | **More APIs coming soon...** 314 | 315 | 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. 316 | 317 | For more details: [How to use DNS API](dnsapi) 318 | 319 | 320 | # 10. Issue ECC certificates 321 | 322 | `Let's Encrypt` can now issue **ECDSA** certificates. 323 | 324 | And we support them too! 325 | 326 | Just set the `length` parameter with a prefix `ec-`. 327 | 328 | For example: 329 | 330 | ### Single domain ECC cerfiticate 331 | 332 | ```bash 333 | acme.sh --issue -w /home/wwwroot/example.com -d example.com --keylength ec-256 334 | ``` 335 | 336 | ### SAN multi domain ECC certificate 337 | 338 | ```bash 339 | acme.sh --issue -w /home/wwwroot/example.com -d example.com -d www.example.com --keylength ec-256 340 | ``` 341 | 342 | Please look at the last parameter above. 343 | 344 | Valid values are: 345 | 346 | 1. **ec-256 (prime256v1, "ECDSA P-256")** 347 | 2. **ec-384 (secp384r1, "ECDSA P-384")** 348 | 3. **ec-521 (secp521r1, "ECDSA P-521", which is not supported by Let's Encrypt yet.)** 349 | 350 | 351 | # 11. How to renew the issued certs 352 | 353 | No, you don't need to renew the certs manually. All the certs will be renewed automatically every **60** days. 354 | 355 | However, you can also force to renew any cert: 356 | 357 | ``` 358 | acme.sh --renew -d example.com --force 359 | ``` 360 | 361 | or, for ECC cert: 362 | 363 | ``` 364 | acme.sh --renew -d example.com --force --ecc 365 | ``` 366 | 367 | 368 | # 12. How to upgrade `acme.sh` 369 | 370 | acme.sh is in constant development, so it's strongly recommended to use the latest code. 371 | 372 | You can update acme.sh to the latest code: 373 | 374 | ``` 375 | acme.sh --upgrade 376 | ``` 377 | 378 | You can also enable auto upgrade: 379 | 380 | ``` 381 | acme.sh --upgrade --auto-upgrade 382 | ``` 383 | 384 | Then **acme.sh** will be kept up to date automatically. 385 | 386 | Disable auto upgrade: 387 | 388 | ``` 389 | acme.sh --upgrade --auto-upgrade 0 390 | ``` 391 | 392 | 393 | # 13. Issue a cert from an existing CSR 394 | 395 | https://github.com/Neilpang/acme.sh/wiki/Issue-a-cert-from-existing-CSR 396 | 397 | 398 | # 14. Under the Hood 399 | 400 | Speak ACME language using shell, directly to "Let's Encrypt". 401 | 402 | TODO: 403 | 404 | 405 | # 15. Acknowledgments 406 | 407 | 1. Acme-tiny: https://github.com/diafygi/acme-tiny 408 | 2. ACME protocol: https://github.com/ietf-wg-acme/acme 409 | 3. Certbot: https://github.com/certbot/certbot 410 | 411 | 412 | # 16. License & Others 413 | 414 | License is GPLv3 415 | 416 | Please Star and Fork me. 417 | 418 | [Issues](https://github.com/Neilpang/acme.sh/issues) and [pull requests](https://github.com/Neilpang/acme.sh/pulls) are welcome. 419 | 420 | 421 | # 17. Donate 422 | Your donation makes **acme.sh** better: 423 | 424 | 1. PayPal/Alipay(支付宝)/Wechat(微信): [https://donate.acme.sh/](https://donate.acme.sh/) 425 | 426 | [Donate List](https://github.com/Neilpang/acme.sh/wiki/Donate-list) 427 | --------------------------------------------------------------------------------