diff --git a/Dockerfile b/Dockerfile index feb89b0d..4cb33139 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,17 +4,17 @@ RUN apk update -f \ && apk --no-cache add -f \ openssl \ curl \ - netcat-openbsd + netcat-openbsd \ + && rm -rf /var/cache/apk/* ENV LE_CONFIG_HOME /acme.sh ENV AUTO_UPGRADE 1 #Install -RUN mkdir -p /install_acme.sh/ ADD ./ /install_acme.sh/ -RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) -RUN rm -rf /install_acme.sh/ +RUN cd /install_acme.sh && ([ -f /install_acme.sh/acme.sh ] && /install_acme.sh/acme.sh --install || curl https://get.acme.sh | sh) && rm -rf /install_acme.sh/ + RUN ln -s /root/.acme.sh/acme.sh /usr/local/bin/acme.sh @@ -55,5 +55,7 @@ else \n \ /root/.acme.sh/acme.sh --config-home /acme.sh \"\$@\"\n \ fi" >/entry.sh && chmod +x /entry.sh +VOLUME /acme.sh + ENTRYPOINT ["/entry.sh"] CMD ["--help"] diff --git a/README.md b/README.md index e0c1e7d6..13188982 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ # 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) + +[![Join the chat at https://gitter.im/acme-sh/Lobby](https://badges.gitter.im/acme-sh/Lobby.svg)](https://gitter.im/acme-sh/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - An ACME protocol client written purely in Shell (Unix shell) language. - Full ACME protocol implementation. - Simple, powerful and very easy to use. You only need 3 minutes to learn it. @@ -8,8 +10,9 @@ - Just one script to issue, renew and install your certificates automatically. - DOES NOT require `root/sudoer` access. - Docker friendly +- IPv6 support -It's probably the `easiest&smallest&smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. +It's probably the `easiest & smartest` shell script to automatically issue & renew the free certificates from Let's Encrypt. Wiki: https://github.com/Neilpang/acme.sh/wiki @@ -31,6 +34,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) - [Centminmod](http://centminmod.com/letsencrypt-acmetool-https.html) - [splynx](https://forum.splynx.com/t/free-ssl-cert-for-splynx-lets-encrypt/297) - [archlinux](https://aur.archlinux.org/packages/acme.sh-git/) +- [opnsense.org](https://github.com/opnsense/plugins/tree/master/security/acme-client/src/opnsense/scripts/OPNsense/AcmeClient) - [more...](https://github.com/Neilpang/acme.sh/wiki/Blogs-and-tutorials) # Tested OS @@ -58,7 +62,7 @@ Twitter: [@neilpangxa](https://twitter.com/neilpangxa) |19|[![](https://cdn.rawgit.com/Neilpang/acmetest/master/status/gentoo-stage3-amd64.svg)](https://github.com/Neilpang/letest#here-are-the-latest-status)|Gentoo Linux |20|[![Build Status](https://travis-ci.org/Neilpang/acme.sh.svg?branch=master)](https://travis-ci.org/Neilpang/acme.sh)|Mac OSX -For all build statuses, check our [daily build project](https://github.com/Neilpang/acmetest): +For all build statuses, check our [weekly build project](https://github.com/Neilpang/acmetest): https://github.com/Neilpang/acmetest @@ -135,13 +139,25 @@ root@v1:~# acme.sh -h acme.sh --issue -d example.com -w /home/wwwroot/example.com ``` +or: + +```bash +acme.sh --issue -d example.com -w /home/username/public_html +``` + +or: + +```bash +acme.sh --issue -d example.com -w /var/www/html +``` + **Example 2:** Multiple domains in the same cert. ```bash acme.sh --issue -d example.com -d www.example.com -d cp.example.com -w /home/wwwroot/example.com ``` -The parameter `/home/wwwroot/example.com` is the web root folder. You **MUST** have `write access` to this folder. +The parameter `/home/wwwroot/example.com` or `/home/username/public_html` or `/var/www/html` is the web root folder where you host your website files. You **MUST** have `write access` to this folder. Second argument **"example.com"** is the main domain you want to issue the cert for. You must have at least one domain there. @@ -183,7 +199,7 @@ The ownership and permission info of existing files are preserved. You may want Install/copy the issued cert/key to the production Apache or Nginx path. -The cert will be `renewed every **60** days by default` (which is configurable). Once the cert is renewed, the Apache/Nginx service will be restarted automatically by the command: `service apache2 restart` or `service nginx restart`. +The cert will be renewed every **60** days by default (which is configurable). Once the cert is renewed, the Apache/Nginx service will be reloaded automatically by the command: `service apache2 force-reload` or `service nginx force-reload`. # 4. Use Standalone server to issue cert @@ -293,14 +309,12 @@ You don't have to do anything manually! 1. DNSPod.cn API 1. CloudXNS.com API 1. GoDaddy.com API -1. OVH, kimsufi, soyoustart and runabove API -1. AWS Route 53 1. PowerDNS.com API -1. lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api - (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.) +1. OVH, kimsufi, soyoustart and runabove API +1. nsupdate API 1. LuaDNS.com API 1. DNSMadeEasy.com API -1. nsupdate API +1. AWS Route 53 1. aliyun.com(阿里云) API 1. ISPConfig 3.1 API 1. Alwaysdata.com API @@ -313,7 +327,20 @@ You don't have to do anything manually! 1. DigitalOcean API (native) 1. ClouDNS.net API 1. Infoblox NIOS API (https://www.infoblox.com/) +1. VSCALE (https://vscale.io/) +1. Dynu API (https://www.dynu.com) +1. DNSimple API +1. NS1.com API + + + +And: + +1. lexicon DNS API: https://github.com/Neilpang/acme.sh/wiki/How-to-use-lexicon-dns-api + (DigitalOcean, DNSimple, DNSMadeEasy, DNSPark, EasyDNS, Namesilo, NS1, PointHQ, Rage4 and Vultr etc.) + + **More APIs coming soon...** 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. diff --git a/acme.sh b/acme.sh index 27bc4fb3..5f500045 100755 --- a/acme.sh +++ b/acme.sh @@ -1,6 +1,6 @@ #!/usr/bin/env sh -VER=2.6.9 +VER=2.7.1 PROJECT_NAME="acme.sh" @@ -166,7 +166,14 @@ _syslog() { fi _logclass="$1" shift - logger -i -t "$PROJECT_NAME" -p "$_logclass" "$(_printargs "$@")" >/dev/null 2>&1 + if [ -z "$__logger_i" ]; then + if _contains "$(logger --help 2>&1)" "-i"; then + __logger_i="logger -i" + else + __logger_i="logger" + fi + fi + $__logger_i -t "$PROJECT_NAME" -p "$_logclass" "$(_printargs "$@")" >/dev/null 2>&1 } _log() { @@ -436,34 +443,48 @@ if [ "$(printf '\x41')" != 'A' ]; then _URGLY_PRINTF=1 fi +_ESCAPE_XARGS="" +if [ "$(printf %s '\\x41' | xargs printf)" = 'A' ]; then + _ESCAPE_XARGS=1 +fi + _h2b() { - hex=$(cat) - i=1 - j=2 + if _exists xxd; then + xxd -r -p + return + fi - _debug3 _URGLY_PRINTF "$_URGLY_PRINTF" - while true; do - if [ -z "$_URGLY_PRINTF" ]; then - h="$(printf "%s" "$hex" | cut -c $i-$j)" - if [ -z "$h" ]; then - break - fi - printf "\x$h%s" + hex=$(cat) + ic="" + jc="" + _debug2 _URGLY_PRINTF "$_URGLY_PRINTF" + if [ -z "$_URGLY_PRINTF" ]; then + if [ "$_ESCAPE_XARGS" ] && _exists xargs; then + _debug2 "xargs" + echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/\\\\\\x\1/g' | xargs printf else - ic="$(printf "%s" "$hex" | cut -c $i)" - jc="$(printf "%s" "$hex" | cut -c $j)" - if [ -z "$ic$jc" ]; then - break + for h in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\{2\}\)/ \1/g'); do + if [ -z "$h" ]; then + break + fi + printf "\x$h%s" + done + fi + else + for c in $(echo "$hex" | _upper_case | sed 's/\([0-9A-F]\)/ \1/g'); do + if [ -z "$ic" ]; then + ic=$c + continue fi + jc=$c ic="$(_h_char_2_dec "$ic")" jc="$(_h_char_2_dec "$jc")" printf '\'"$(printf "%o" "$(_math "$ic" \* 16 + $jc)")""%s" - fi - - i="$(_math "$i" + 2)" - j="$(_math "$j" + 2)" + ic="" + jc="" + done + fi - done } _is_solaris() { @@ -1237,17 +1258,20 @@ createDomainKey() { fi domain=$1 - length=$2 + _cdl=$2 - if [ -z "$length" ]; then + if [ -z "$_cdl" ]; then _debug "Use DEFAULT_DOMAIN_KEY_LENGTH=$DEFAULT_DOMAIN_KEY_LENGTH" - length="$DEFAULT_DOMAIN_KEY_LENGTH" + _cdl="$DEFAULT_DOMAIN_KEY_LENGTH" fi - _initpath "$domain" "$length" + _initpath "$domain" "$_cdl" if [ ! -f "$CERT_KEY_PATH" ] || ([ "$FORCE" ] && ! [ "$IS_RENEW" ]); then - _createkey "$length" "$CERT_KEY_PATH" + if _createkey "$_cdl" "$CERT_KEY_PATH"; then + _savedomainconf Le_Keylength "$_cdl" + _info "The domain key is here: $(__green $CERT_KEY_PATH)" + fi else if [ "$IS_RENEW" ]; then _info "Domain key exists, skip" @@ -2617,10 +2641,10 @@ _checkConf() { _isRealNginxConf() { _debug "_isRealNginxConf $1 $2" if [ -f "$2" ]; then - for _fln in $(grep -n "^ *server_name.* $1" "$2" | cut -d : -f 1); do + for _fln in $(tr "\t" ' ' <"$2" | grep -n "^ *server_name.* $1" | cut -d : -f 1); do _debug _fln "$_fln" if [ "$_fln" ]; then - _start=$(cat "$2" | _head_n "$_fln" | grep -n "^ *server *{" | _tail_n 1) + _start=$(tr "\t" ' ' <"$2" | _head_n "$_fln" | grep -n "^ *server *{" | _tail_n 1) _debug "_start" "$_start" _start_n=$(echo "$_start" | cut -d : -f 1) _start_nn=$(_math $_start_n + 1) @@ -2629,8 +2653,8 @@ _isRealNginxConf() { _left="$(sed -n "${_start_nn},99999p" "$2")" _debug2 _left "$_left" - if echo "$_left" | grep -n "^ *server *{" >/dev/null; then - _end=$(echo "$_left" | grep -n "^ *server *{" | _head_n 1) + if echo "$_left" | tr "\t" ' ' | grep -n "^ *server *{" >/dev/null; then + _end=$(echo "$_left" | tr "\t" ' ' | grep -n "^ *server *{" | _head_n 1) _debug "_end" "$_end" _end_n=$(echo "$_end" | cut -d : -f 1) _debug "_end_n" "$_end_n" diff --git a/deploy/README.md b/deploy/README.md index d8c2f57c..232fdb4a 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -21,8 +21,11 @@ acme.sh --deploy -d example.com --deploy-hook cpanel ## 2. Deploy ssl cert on kong proxy engine based on api. Before you can deploy your cert, you must [issue the cert first](https://github.com/Neilpang/acme.sh/wiki/How-to-issue-a-cert). +Currently supports Kong-v0.10.x. -(TODO) +```sh +acme.sh --deploy -d ftp.example.com --deploy-hook kong +``` ## 3. Deploy the cert to remote server through SSH access. diff --git a/deploy/kong.sh b/deploy/kong.sh index e1873f35..d3a6bc47 100755 --- a/deploy/kong.sh +++ b/deploy/kong.sh @@ -1,13 +1,7 @@ #!/usr/bin/env sh - -# This deploy hook will deploy ssl cert on kong proxy engine based on api request_host parameter. -# Note that ssl plugin should be available on Kong instance -# The hook will match cdomain to request_host, in case of multiple domain it will always take the first -# one (acme.sh behaviour). -# If ssl config already exist it will update only cert and key not touching other parameter -# If ssl config doesn't exist it will only upload cert and key and not set other parameter -# Not that we deploy full chain -# See https://getkong.org/plugins/dynamic-ssl/ for other options +# If certificate already exist it will update only cert and key not touching other parameter +# If certificate doesn't exist it will only upload cert and key and not set other parameter +# Note that we deploy full chain # Written by Geoffroi Genot ######## Public functions ##################### @@ -31,14 +25,15 @@ kong_deploy() { _debug _cca "$_cca" _debug _cfullchain "$_cfullchain" - #Get uuid linked to the domain - uuid=$(_get "$KONG_URL/apis?request_host=$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') - if [ -z "$uuid" ]; then - _err "Unable to get Kong uuid for domain $_cdomain" - _err "Make sure that KONG_URL is correctly configured" - _err "Make sure that a Kong api request_host match the domain" - _err "Kong url: $KONG_URL" - return 1 + #Get ssl_uuid linked to the domain + ssl_uuid=$(_get "$KONG_URL/certificates/$_cdomain" | _normalizeJson | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + if [ -z "$ssl_uuid" ]; then + _debug "Unable to get Kong ssl_uuid for domain $_cdomain" + _debug "Make sure that KONG_URL is correctly configured" + _debug "Make sure that a Kong certificate match the sni" + _debug "Kong url: $KONG_URL" + _info "No existing certificate, creating..." + #return 1 fi #Save kong url if it's succesful (First run case) _saveaccountconf KONG_URL "$KONG_URL" @@ -48,12 +43,14 @@ kong_deploy() { #Set Header _H1="Content-Type: multipart/form-data; boundary=$delim" #Generate data for request (Multipart/form-data with mixed content) - #set name to ssl - content="--$delim${nl}Content-Disposition: form-data; name=\"name\"${nl}${nl}ssl" + if [ -z "$ssl_uuid" ]; then + #set sni to domain + content="--$delim${nl}Content-Disposition: form-data; name=\"snis\"${nl}${nl}$_cdomain" + fi #add key - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"key\"; filename=\"$(basename "$_ckey")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_ckey")" #Add cert - content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"config.cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" + content="$content${nl}--$delim${nl}Content-Disposition: form-data; name=\"cert\"; filename=\"$(basename "$_cfullchain")\"${nl}Content-Type: application/octet-stream${nl}${nl}$(cat "$_cfullchain")" #Close multipart content="$content${nl}--$delim--${nl}" #Convert CRLF @@ -61,17 +58,16 @@ kong_deploy() { #DEBUG _debug header "$_H1" _debug content "$content" - #Check if ssl plugins is aready enabled (if not => POST else => PATCH) - ssl_uuid=$(_get "$KONG_URL/apis/$uuid/plugins" | _egrep_o '"id":"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"[a-zA-Z0-9\-\,\"_\:]*"name":"ssl"' | _egrep_o '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') - _debug ssl_uuid "$ssl_uuid" + #Check if sslcreated (if not => POST else => PATCH) + if [ -z "$ssl_uuid" ]; then #Post certificate to Kong - response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins" "" "POST") + response=$(_post "$content" "$KONG_URL/certificates" "" "POST") else #patch - response=$(_post "$content" "$KONG_URL/apis/$uuid/plugins/$ssl_uuid" "" "PATCH") + response=$(_post "$content" "$KONG_URL/certificates/$ssl_uuid" "" "PATCH") fi - if ! [ "$(echo "$response" | _egrep_o "ssl")" = "ssl" ]; then + if ! [ "$(echo "$response" | _egrep_o "created_at")" = "created_at" ]; then _err "An error occurred with cert upload. Check response:" _err "$response" return 1 diff --git a/dnsapi/README.md b/dnsapi/README.md index 9eb77915..5dca829a 100644 --- a/dnsapi/README.md +++ b/dnsapi/README.md @@ -140,7 +140,7 @@ Finally, make the DNS server and update Key available to `acme.sh` ``` export NSUPDATE_SERVER="dns.example.com" -export NSUPDATE_KEY="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa==" +export NSUPDATE_KEY="/path/to/your/nsupdate.key" ``` Ok, let's issue a cert now: @@ -422,22 +422,89 @@ acme.sh --issue --dns dns_cloudns -d example.com -d www.example.com ``` ## 22. Use Infoblox API - + First you need to create/obtain API credentials on your Infoblox appliance. - + ``` export Infoblox_Creds="username:password" export Infoblox_Server="ip or fqdn of infoblox appliance" ``` - + Ok, let's issue a cert now: ``` acme.sh --issue --dns dns_infoblox -d example.com -d www.example.com ``` - + Note: This script will automatically create and delete the ephemeral txt record. The `Infoblox_Creds` and `Infoblox_Server` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 23. Use VSCALE API + +First you need to create/obtain API tokens on your [settings panel](https://vscale.io/panel/settings/tokens/). + +``` +VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_vscale -d example.com -d www.example.com +``` + +## 24. Use Dynu API + +First you need to create/obtain API credentials from your Dynu account. See: https://www.dynu.com/resources/api/documentation + +``` +export Dynu_ClientId="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" +export Dynu_Secret="yyyyyyyyyyyyyyyyyyyyyyyyy" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_dynu -d example.com -d www.example.com +``` + +The `Dynu_ClientId` and `Dynu_Secret` will be saved in `~/.acme.sh/account.conf` and will be reused when needed. + +## 25. Use DNSimple API + +First you need to login to your DNSimple account and generate a new oauth token. + +https://dnsimple.com/a/{your account id}/account/access_tokens + +Note that this is an _account_ token and not a user token. The account token is +needed to infer the `account_id` used in requests. A user token will not be able +to determine the correct account to use. + +``` +export DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" +``` + +To issue the cert just specify the `dns_dnsimple` API. + +``` +acme.sh --issue --dns dns_dnsimple -d example.com +``` + +The `DNSimple_OAUTH_TOKEN` will be saved in `~/.acme.sh/account.conf` and will +be reused when needed. + +If you have any issues with this integration please report them to +https://github.com/pho3nixf1re/acme.sh/issues. + +## 26. Use NS1.com API + +``` +export NS1_Key="fdmlfsdklmfdkmqsdfk" +``` + +Ok, let's issue a cert now: +``` +acme.sh --issue --dns dns_nsone -d example.com -d www.example.com +``` + # Use custom API If your API is not supported yet, you can write your own DNS API. diff --git a/dnsapi/dns_aws.sh b/dnsapi/dns_aws.sh index 21e86686..800c3d09 100755 --- a/dnsapi/dns_aws.sh +++ b/dnsapi/dns_aws.sh @@ -106,7 +106,7 @@ _get_root() { fi if _contains "$response" "$h."; then - hostedzone="$(echo "$response" | sed 's//#&/g' | tr '#' '\n' | _egrep_o "[^<]*<.Id>$h.<.Name>.*<.HostedZone>")" + hostedzone="$(echo "$response" | sed 's//#&/g' | tr '#' '\n' | _egrep_o "[^<]*<.Id>$h.<.Name>.*false<.PrivateZone>.*<.HostedZone>")" _debug hostedzone "$hostedzone" if [ -z "$hostedzone" ]; then _err "Error, can not get hostedzone." diff --git a/dnsapi/dns_dnsimple.sh b/dnsapi/dns_dnsimple.sh new file mode 100644 index 00000000..0bfe2b99 --- /dev/null +++ b/dnsapi/dns_dnsimple.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env sh + +# DNSimple domain api +# https://github.com/pho3nixf1re/acme.sh/issues +# +# This is your oauth token which can be acquired on the account page. Please +# note that this must be an _account_ token and not a _user_ token. +# https://dnsimple.com/a//account/access_tokens +# DNSimple_OAUTH_TOKEN="sdfsdfsdfljlbjkljlkjsdfoiwje" + +DNSimple_API="https://api.dnsimple.com/v2" + +######## Public functions ##################### + +# Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_dnsimple_add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$DNSimple_OAUTH_TOKEN" ]; then + DNSimple_OAUTH_TOKEN="" + _err "You have not set the dnsimple oauth token yet." + _err "Please visit https://dnsimple.com/user to generate it." + return 1 + fi + + # save the oauth token for later + _saveaccountconf DNSimple_OAUTH_TOKEN "$DNSimple_OAUTH_TOKEN" + + if ! _get_account_id; then + _err "failed to retrive account id" + return 1 + fi + + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _get_records "$_account_id" "$_domain" "$_sub_domain" + + if [ "$_records_count" = "0" ]; then + _info "Adding record" + if _dnsimple_rest POST "$_account_id/zones/$_domain/records" "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then + if printf -- "%s" "$response" | grep "\"name\":\"$_sub_domain\"" >/dev/null; then + _info "Added" + return 0 + else + _err "Unexpected response while adding text record." + return 1 + fi + fi + _err "Add txt record error." + else + _info "Updating record" + _extract_record_id "$_records" "$_sub_domain" + + if _dnsimple_rest \ + PATCH \ + "$_account_id/zones/$_domain/records/$_record_id" \ + "{\"type\":\"TXT\",\"name\":\"$_sub_domain\",\"content\":\"$txtvalue\",\"ttl\":120}"; then + + _info "Updated!" + return 0 + fi + + _err "Update error" + return 1 + fi +} + +# fulldomain +dns_dnsimple_rm() { + fulldomain=$1 + + if ! _get_account_id; then + _err "failed to retrive account id" + return 1 + fi + + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + + _get_records "$_account_id" "$_domain" "$_sub_domain" + _extract_record_id "$_records" "$_sub_domain" + + if [ "$_record_id" ]; then + + if _dnsimple_rest DELETE "$_account_id/zones/$_domain/records/$_record_id"; then + _info "removed record" "$_record_id" + return 0 + fi + fi + + _err "failed to remove record" "$_record_id" + return 1 + +} + +#################### Private functions bellow ################################## +# _acme-challenge.www.domain.com +# returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +_get_root() { + domain=$1 + i=2 + previous=1 + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + if [ -z "$h" ]; then + # not valid + return 1 + fi + + if ! _dnsimple_rest GET "$_account_id/zones/$h"; then + return 1 + fi + + if _contains "$response" 'not found'; then + _debug "$h not found" + else + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$previous) + _domain="$h" + + _debug _domain "$_domain" + _debug _sub_domain "$_sub_domain" + + return 0 + fi + + previous="$i" + i=$(_math "$i" + 1) + done + return 1 +} + +# returns _account_id +_get_account_id() { + _debug "retrive account id" + if ! _dnsimple_rest GET "whoami"; then + return 1 + fi + + if _contains "$response" "\"account\":null"; then + _err "no account associated with this token" + return 1 + fi + + if _contains "$response" "timeout"; then + _err "timeout retrieving account id" + return 1 + fi + + _account_id=$(printf "%s" "$response" | _egrep_o "\"id\":[^,]*,\"email\":" | cut -d: -f2 | cut -d, -f1) + _debug _account_id "$_account_id" + + return 0 +} + +# returns +# _records +# _records_count +_get_records() { + account_id=$1 + domain=$2 + sub_domain=$3 + + _debug "fetching txt records" + _dnsimple_rest GET "$account_id/zones/$domain/records?per_page=100" + + if ! _contains "$response" "\"id\":"; then + _err "failed to retrieve records" + return 1 + fi + + _records_count=$(printf "%s" "$response" | _egrep_o "\"name\":\"$sub_domain\"" | wc -l | _egrep_o "[0-9]+") + _records=$response + _debug _records_count "$_records_count" +} + +# returns _record_id +_extract_record_id() { + _record_id=$(printf "%s" "$_records" | _egrep_o "\"id\":[^,]*,\"zone_id\":\"[^,]*\",\"parent_id\":null,\"name\":\"$_sub_domain\"" | cut -d: -f2 | cut -d, -f1) + _debug "_record_id" "$_record_id" +} + +# returns response +_dnsimple_rest() { + method=$1 + path="$2" + data="$3" + request_url="$DNSimple_API/$path" + _debug "$path" + + export _H1="Accept: application/json" + export _H2="Authorization: Bearer $DNSimple_OAUTH_TOKEN" + + if [ "$data" ] || [ "$method" = "DELETE" ]; then + _H1="Content-Type: application/json" + _debug data "$data" + response="$(_post "$data" "$request_url" "" "$method")" + else + response="$(_get "$request_url" "" "" "$method")" + fi + + if [ "$?" != "0" ]; then + _err "error $request_url" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_dynu.sh b/dnsapi/dns_dynu.sh new file mode 100644 index 00000000..f6eabde2 --- /dev/null +++ b/dnsapi/dns_dynu.sh @@ -0,0 +1,216 @@ +#!/usr/bin/env sh + +#Client ID +#Dynu_ClientId="0b71cae7-a099-4f6b-8ddf-94571cdb760d" +# +#Secret +#Dynu_Secret="aCUEY4BDCV45KI8CSIC3sp2LKQ9" +# +#Token +Dynu_Token="" +# +#Endpoint +Dynu_EndPoint="https://api.dynu.com/v1" +# +#Author: Dynu Systems, Inc. +#Report Bugs here: https://github.com/shar0119/acme.sh +# +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_dynu_add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then + Dynu_ClientId="" + Dynu_Secret="" + _err "Dynu client id and secret is not specified." + _err "Please create you API client id and secret and try again." + return 1 + fi + + #save the client id and secret to the account conf file. + _saveaccountconf Dynu_ClientId "$Dynu_ClientId" + _saveaccountconf Dynu_Secret "$Dynu_Secret" + + if [ -z "$Dynu_Token" ]; then + _info "Getting Dynu token." + if ! _dynu_authentication; then + _err "Can not get token." + fi + fi + + _debug "Detect root zone" + if ! _get_root "$fulldomain"; then + _err "Invalid domain." + return 1 + fi + + _debug _node "$_node" + _debug _domain_name "$_domain_name" + + _info "Creating TXT record." + if ! _dynu_rest POST "dns/record/add" "{\"domain_name\":\"$_domain_name\",\"node_name\":\"$_node\",\"record_type\":\"TXT\",\"text_data\":\"$txtvalue\",\"state\":true,\"ttl\":90}"; then + return 1 + fi + + if ! _contains "$response" "text_data"; then + _err "Could not add TXT record." + return 1 + fi + + return 0 +} + +#Usage: rm _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_dynu_rm() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$Dynu_ClientId" ] || [ -z "$Dynu_Secret" ]; then + Dynu_ClientId="" + Dynu_Secret="" + _err "Dynu client id and secret is not specified." + _err "Please create you API client id and secret and try again." + return 1 + fi + + #save the client id and secret to the account conf file. + _saveaccountconf Dynu_ClientId "$Dynu_ClientId" + _saveaccountconf Dynu_Secret "$Dynu_Secret" + + if [ -z "$Dynu_Token" ]; then + _info "Getting Dynu token." + if ! _dynu_authentication; then + _err "Can not get token." + fi + fi + + _debug "Detect root zone." + if ! _get_root "$fulldomain"; then + _err "Invalid domain." + return 1 + fi + + _debug _node "$_node" + _debug _domain_name "$_domain_name" + + _info "Checking for TXT record." + if ! _get_recordid "$fulldomain" "$txtvalue"; then + _err "Could not get TXT record id." + return 1 + fi + + if [ "$_dns_record_id" = "" ]; then + _err "TXT record not found." + return 1 + fi + + _info "Removing TXT record." + if ! _delete_txt_record "$_dns_record_id"; then + _err "Could not remove TXT record $_dns_record_id." + fi + + return 0 +} + +######## Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _node=_acme-challenge.www +# _domain_name=domain.com +_get_root() { + domain=$1 + if ! _dynu_rest GET "dns/getroot/$domain"; then + return 1 + fi + + if ! _contains "$response" "domain_name"; then + _debug "Domain name not found." + return 1 + fi + + _domain_name=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 1 | cut -d : -f 2 | cut -d '"' -f 2) + _node=$(printf "%s" "$response" | tr -d "{}" | cut -d , -f 3 | cut -d : -f 2 | cut -d '"' -f 2) + return 0 +} + +_get_recordid() { + fulldomain=$1 + txtvalue=$2 + + if ! _dynu_rest GET "dns/record/get?hostname=$fulldomain&rrtype=TXT"; then + return 1 + fi + + if ! _contains "$response" "$txtvalue"; then + _dns_record_id=0 + return 0 + fi + + _dns_record_id=$(printf "%s" "$response" | _egrep_o "{[^}]*}" | grep "\"text_data\":\"$txtvalue\"" | _egrep_o ",[^,]*," | grep ',"id":' | tr -d ",," | cut -d : -f 2) + + return 0 +} + +_delete_txt_record() { + _dns_record_id=$1 + + if ! _dynu_rest GET "dns/record/delete/$_dns_record_id"; then + return 1 + fi + + if ! _contains "$response" "true"; then + return 1 + fi + + return 0 +} + +_dynu_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + export _H1="Authorization: Bearer $Dynu_Token" + export _H2="Content-Type: application/json" + + if [ "$data" ]; then + _debug data "$data" + response="$(_post "$data" "$Dynu_EndPoint/$ep" "" "$m")" + else + _info "Getting $Dynu_EndPoint/$ep" + response="$(_get "$Dynu_EndPoint/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} + +_dynu_authentication() { + realm="$(printf "%s" "$Dynu_ClientId:$Dynu_Secret" | _base64)" + + export _H1="Authorization: Basic $realm" + export _H2="Content-Type: application/json" + + response="$(_get "$Dynu_EndPoint/oauth2/token")" + if [ "$?" != "0" ]; then + _err "Authentication failed." + return 1 + fi + if _contains "$response" "accessToken"; then + Dynu_Token=$(printf "%s" "$response" | tr -d "[]" | cut -d , -f 2 | cut -d : -f 2 | cut -d '"' -f 2) + fi + if _contains "$Dynu_Token" "null"; then + Dynu_Token="" + fi + + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_gandi_livedns.sh b/dnsapi/dns_gandi_livedns.sh index 28b8f99d..82ed599c 100755 --- a/dnsapi/dns_gandi_livedns.sh +++ b/dnsapi/dns_gandi_livedns.sh @@ -37,7 +37,7 @@ dns_gandi_livedns_add() { _debug sub_domain "$_sub_domain" _gandi_livedns_rest PUT "domains/$_domain/records/$_sub_domain/TXT" "{\"rrset_ttl\": 300, \"rrset_values\":[\"$txtvalue\"]}" \ - && _contains "$response" '{"message": "Zone Record Created"}' \ + && _contains "$response" '{"message": "DNS Record Created"}' \ && _info "Add $(__green "success")" } diff --git a/dnsapi/dns_nsone.sh b/dnsapi/dns_nsone.sh new file mode 100644 index 00000000..adf1f422 --- /dev/null +++ b/dnsapi/dns_nsone.sh @@ -0,0 +1,158 @@ +#!/usr/bin/env sh + +# bug reports to dev@1e.ca + +# +#NS1_Key="sdfsdfsdfljlbjkljlkjsdfoiwje" +# + +NS1_Api="https://api.nsone.net/v1" + +######## Public functions ##################### + +#Usage: add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_nsone_add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$NS1_Key" ]; then + NS1_Key="" + _err "You didn't specify nsone dns api key yet." + _err "Please create you key and try again." + return 1 + fi + + #save the api key and email to the account conf file. + _saveaccountconf NS1_Key "$NS1_Key" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + _nsone_rest GET "zones/${_domain}" + + if ! _contains "$response" "\"records\":"; then + _err "Error" + return 1 + fi + + count=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",[^{]*\"type\":\"TXT\"" | wc -l | tr -d " ") + _debug count "$count" + if [ "$count" = "0" ]; then + _info "Adding record" + + if _nsone_rest PUT "zones/$_domain/$fulldomain/TXT" "{\"answers\":[{\"answer\":[\"$txtvalue\"]}],\"type\":\"TXT\",\"domain\":\"$fulldomain\",\"zone\":\"$_domain\"}"; then + if _contains "$response" "$fulldomain"; then + _info "Added" + #todo: check if the record takes effect + return 0 + else + _err "Add txt record error." + return 1 + fi + fi + _err "Add txt record error." + else + _info "Updating record" + record_id=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain.\",[^{]*\"type\":\"TXT\",\"id\":\"[^,]*\"" | _head_n 1 | cut -d: -f7 | cut -d, -f1) + _debug "record_id" "$record_id" + + _nsone_rest POST "zones/$_domain/$fulldomain/TXT" "{\"answers\": [{\"answer\": [\"$txtvalue\"]}],\"type\": \"TXT\",\"domain\":\"$fulldomain\",\"zone\": \"$_domain\"}" + if [ "$?" = "0" ] && _contains "$response" "$fulldomain"; then + _info "Updated!" + #todo: check if the record takes effect + return 0 + fi + _err "Update error" + return 1 + fi + +} + +#fulldomain +dns_nsone_rm() { + fulldomain=$1 + txtvalue=$2 + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + _nsone_rest GET "zones/${_domain}/$fulldomain/TXT" + + count=$(printf "%s\n" "$response" | _egrep_o "\"domain\":\"$fulldomain\",.*\"type\":\"TXT\"" | wc -l | tr -d " ") + _debug count "$count" + if [ "$count" = "0" ]; then + _info "Don't need to remove." + else + if ! _nsone_rest DELETE "zones/${_domain}/$fulldomain/TXT"; then + _err "Delete record error." + return 1 + fi + _contains "$response" "" + fi +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=sdjkglgdfewsdfg +_get_root() { + domain=$1 + i=2 + p=1 + if ! _nsone_rest GET "zones"; then + return 1 + fi + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + if _contains "$response" "\"zone\":\"$h\""; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain="$h" + return 0 + fi + p=$i + i=$(_math "$i" + 1) + done + return 1 +} + +_nsone_rest() { + m=$1 + ep="$2" + data="$3" + _debug "$ep" + + export _H1="Accept: application/json" + export _H2="X-NSONE-Key: $NS1_Key" + if [ "$m" != "GET" ]; then + _debug data "$data" + response="$(_post "$data" "$NS1_Api/$ep" "" "$m")" + else + response="$(_get "$NS1_Api/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +} diff --git a/dnsapi/dns_ovh.sh b/dnsapi/dns_ovh.sh index 18f9c7dc..eaa90bdf 100755 --- a/dnsapi/dns_ovh.sh +++ b/dnsapi/dns_ovh.sh @@ -119,7 +119,7 @@ dns_ovh_add() { _info "Checking authentication" - response="$(_ovh_rest GET "domain/")" + response="$(_ovh_rest GET "domain")" if _contains "$response" "INVALID_CREDENTIAL"; then _err "The consumer key is invalid: $OVH_CK" _err "Please retry to create a new one." @@ -191,7 +191,7 @@ _ovh_authentication() { _H3="" _H4="" - _ovhdata='{"accessRules": [{"method": "GET","path": "/*"},{"method": "POST","path": "/*"},{"method": "PUT","path": "/*"},{"method": "DELETE","path": "/*"}],"redirection":"'$ovh_success'"}' + _ovhdata='{"accessRules": [{"method": "GET","path": "/auth/time"},{"method": "GET","path": "/domain"},{"method": "GET","path": "/domain/zone/*"},{"method": "GET","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/record"},{"method": "POST","path": "/domain/zone/*/refresh"},{"method": "PUT","path": "/domain/zone/*/record/*"}],"redirection":"'$ovh_success'"}' response="$(_post "$_ovhdata" "$OVH_API/auth/credential")" _debug3 response "$response" @@ -238,7 +238,7 @@ _get_root() { return 1 fi - if ! _contains "$response" "This service does not exist" >/dev/null; then + if ! _contains "$response" "This service does not exist" >/dev/null && ! _contains "$response" "NOT_GRANTED_CALL" >/dev/null; then _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) _domain="$h" return 0 diff --git a/dnsapi/dns_vscale.sh b/dnsapi/dns_vscale.sh new file mode 100755 index 00000000..e50b7d8b --- /dev/null +++ b/dnsapi/dns_vscale.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env sh + +#This is the vscale.io api wrapper for acme.sh +# +#Author: Alex Loban +#Report Bugs here: https://github.com/LAV45/acme.sh + +#VSCALE_API_KEY="sdfsdfsdfljlbjkljlkjsdfoiwje" +VSCALE_API_URL="https://api.vscale.io/v1" + +######## Public functions ##################### + +#Usage: dns_myapi_add _acme-challenge.www.domain.com "XKrxpRBosdIKFzxW_CT3KLZNf6q0HG9i01zxXp5CPBs" +dns_vscale_add() { + fulldomain=$1 + txtvalue=$2 + + if [ -z "$VSCALE_API_KEY" ]; then + VSCALE_API_KEY="" + _err "You didn't specify the VSCALE api key yet." + _err "Please create you key and try again." + return 1 + fi + + _saveaccountconf VSCALE_API_KEY "$VSCALE_API_KEY" + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _vscale_tmpl_json="{\"type\":\"TXT\",\"name\":\"$_sub_domain.$_domain\",\"content\":\"$txtvalue\"}" + + if _vscale_rest POST "domains/$_domain_id/records/" "$_vscale_tmpl_json"; then + response=$(printf "%s\n" "$response" | _egrep_o "{\"error\": \".+\"" | cut -d : -f 2) + if [ -z "$response" ]; then + _info "txt record updated success." + return 0 + fi + fi + + return 1 +} + +#fulldomain txtvalue +dns_vscale_rm() { + fulldomain=$1 + txtvalue=$2 + + _debug "First detect the root zone" + if ! _get_root "$fulldomain"; then + _err "invalid domain" + return 1 + fi + _debug _domain_id "$_domain_id" + _debug _sub_domain "$_sub_domain" + _debug _domain "$_domain" + + _debug "Getting txt records" + _vscale_rest GET "domains/$_domain_id/records/" + + if [ -n "$response" ]; then + record_id=$(printf "%s\n" "$response" | _egrep_o "\"TXT\", \"id\": [0-9]+, \"name\": \"$_sub_domain.$_domain\"" | cut -d : -f 2 | tr -d ", \"name\"") + _debug record_id "$record_id" + if [ -z "$record_id" ]; then + _err "Can not get record id to remove." + return 1 + fi + if _vscale_rest DELETE "domains/$_domain_id/records/$record_id" && [ -z "$response" ]; then + _info "txt record deleted success." + return 0 + fi + _debug response "$response" + return 1 + fi + + return 1 +} + +#################### Private functions below ################################## +#_acme-challenge.www.domain.com +#returns +# _sub_domain=_acme-challenge.www +# _domain=domain.com +# _domain_id=12345 +_get_root() { + domain=$1 + i=2 + p=1 + + if _vscale_rest GET "domains/"; then + response="$(echo "$response" | tr -d "\n" | sed 's/{/\n&/g')" + while true; do + h=$(printf "%s" "$domain" | cut -d . -f $i-100) + _debug h "$h" + if [ -z "$h" ]; then + #not valid + return 1 + fi + + hostedzone="$(echo "$response" | _egrep_o "{.*\"name\":\s*\"$h\".*}")" + if [ "$hostedzone" ]; then + _domain_id=$(printf "%s\n" "$hostedzone" | _egrep_o "\"id\":\s*[0-9]+" | _head_n 1 | cut -d : -f 2 | tr -d \ ) + if [ "$_domain_id" ]; then + _sub_domain=$(printf "%s" "$domain" | cut -d . -f 1-$p) + _domain=$h + return 0 + fi + return 1 + fi + p=$i + i=$(_math "$i" + 1) + done + fi + return 1 +} + +#method uri qstr data +_vscale_rest() { + mtd="$1" + ep="$2" + data="$3" + + _debug mtd "$mtd" + _debug ep "$ep" + + export _H1="Accept: application/json" + export _H2="Content-Type: application/json" + export _H3="X-Token: ${VSCALE_API_KEY}" + + if [ "$mtd" != "GET" ]; then + # both POST and DELETE. + _debug data "$data" + response="$(_post "$data" "$VSCALE_API_URL/$ep" "" "$mtd")" + else + response="$(_get "$VSCALE_API_URL/$ep")" + fi + + if [ "$?" != "0" ]; then + _err "error $ep" + return 1 + fi + _debug2 response "$response" + return 0 +}