From 945dde0077f96153ab87e1d5692a9db2fa4fe2e3 Mon Sep 17 00:00:00 2001
From: Steve Kennedy <skennedy@xyide.com>
Date: Tue, 14 Dec 2021 10:16:19 -0500
Subject: [PATCH 1/2] GoDaddy Updates

- Support for domains longer than 3 segments (ex. 1.www.mydomain.com).
- No need for GODADDY_BASE.  Domain will be automatically determined from full RR name.
- Update to delete challenge records.
---
 dns_scripts/00GoDaddy-README.txt |  63 -------
 dns_scripts/GoDaddy-README.txt   |   2 +
 dns_scripts/dns_add_godaddy      |   2 +-
 dns_scripts/dns_del_godaddy      |   2 +-
 dns_scripts/dns_godaddy          | 312 ++++++++++++-------------------
 5 files changed, 119 insertions(+), 262 deletions(-)
 delete mode 100644 dns_scripts/00GoDaddy-README.txt

diff --git a/dns_scripts/00GoDaddy-README.txt b/dns_scripts/00GoDaddy-README.txt
deleted file mode 100644
index 9973556e..00000000
--- a/dns_scripts/00GoDaddy-README.txt
+++ /dev/null
@@ -1,63 +0,0 @@
-Using GoDaddy DNS for LetsEncrypt domain validation.
-
-Quick guide to setting up getssl for domain validation of
-GoDaddy DNS domains.
-
-There are two prerequisites to using getssl with GoDaddy DNS:
-
-1) Obtain an API access key from developer.godaddy.com
-   At first sign-up, you will be required to take a "test" key.
-   This is NOT what you need.  Accept it, then get a "Production"
-   key.  At this writing, there is no charge - but you must have
-   a GoDaddy customer account.
-
-   You must get the API key for the account which owns the domain
-   that you want to get certificates for.  If the domains that you
-   manage are owned by more than one account, get a key for each.
-
-   The access key consists of a "Key" and a "Secret".  You need
-   both.
-
-2) Obtain JSON.sh - https://github.com/dominictarr/JSON.sh
-
-With those in hand, the installation procedure is:
-
-1) Put JSON.sh in the getssl DNS scripts directory 
-   Default: /usr/share/getssl/dns_scripts
-
-2) Open your config file (the global file in ~/.getssl/getssl.cfg
-   or the per-account file in ~/.getssl/example.net/getssl.cfg
-
-3) Set the following options:
-   VALIDATE_VIA_DNS="true"
-   DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_godaddy"
-   DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_godaddy"
-   # The API key for your account/this domain
-   export GODADDY_KEY="..." GODADDY_SECRET="..."
-   # The base domain name(s) in which the challege records are stored
-   # E.g. if www.example.net is in the example.net zone:
-   export GODADDY_BASE="example.com example.net"
-
- 4) Set any other options that you wish (per the standard
-   directions.)  Use the test CA to make sure that
-   everything is setup correctly.
-
-That's it.  getssl example.net will now validate with DNS.
-
-To trace record additions and removals, run getssl as
-GODADDY_TRACE=Y getssl example.net
-
-There are additional options, which are documented in the
-*godaddy" files and dns_godaddy -h.
-
-Copyright (C) 2017, 2018 Timothe Litt  litt at acm _dot org
-
-This sofware may be freely used providing this notice is included with
-all copies.  The name of the author may not be used to endorse
-any other product or derivative work.  No warranty is provided
-and the user assumes all responsibility for use of this software.
-
-Report any issues to https://github.com/tlhackque/getssl/issues.
-
-Enjoy.
-
diff --git a/dns_scripts/GoDaddy-README.txt b/dns_scripts/GoDaddy-README.txt
index d58ba737..54db3b8e 100644
--- a/dns_scripts/GoDaddy-README.txt
+++ b/dns_scripts/GoDaddy-README.txt
@@ -34,6 +34,8 @@ With those in hand, the installation procedure is:
    DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_godaddy"
    # The API key for your account/this domain
    export GODADDY_KEY="..." GODADDY_SECRET="..."
+   # If you have been using GODADDY_BASE previously, then it is no
+   longer necessary.  The base domain will automatically be determined.
 
  4) Set any other options that you wish (per the standard
    directions.)  Use the test CA to make sure that
diff --git a/dns_scripts/dns_add_godaddy b/dns_scripts/dns_add_godaddy
index f9be7453..0baf3126 100755
--- a/dns_scripts/dns_add_godaddy
+++ b/dns_scripts/dns_add_godaddy
@@ -38,4 +38,4 @@ export GODADDY_KEY
 export GODADDY_SECRET
 export GODADDY_BASE
 
-$GODADDY_SCRIPT -q add "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}"
+$GODADDY_SCRIPT -q add "_acme-challenge.${fulldomain}." "${token}"
diff --git a/dns_scripts/dns_del_godaddy b/dns_scripts/dns_del_godaddy
index 4a3228f7..692dff89 100755
--- a/dns_scripts/dns_del_godaddy
+++ b/dns_scripts/dns_del_godaddy
@@ -36,4 +36,4 @@ export GODADDY_KEY
 export GODADDY_SECRET
 export GODADDY_BASE
 
-$GODADDY_SCRIPT -q del "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}"
+$GODADDY_SCRIPT -q del "_acme-challenge.${fulldomain}." "${token}"
diff --git a/dns_scripts/dns_godaddy b/dns_scripts/dns_godaddy
index 0d41e6a1..cdcfed31 100755
--- a/dns_scripts/dns_godaddy
+++ b/dns_scripts/dns_godaddy
@@ -2,7 +2,7 @@
 
 # Copyright (C) 2017,2018 Timothe Litt  litt at acm _dot org
 
-VERSION="1.0.3"
+VERSION="2.0"
 PROG="`basename $0`"
 
 # This script is used to update TXT records in GoDaddy DNS server
@@ -42,8 +42,8 @@ while getopts 'dhj:k:s:t:qv' opt; do
         *)
             cat <<EOF
 Usage
-    $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] add domain name data [ttl]
-    $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] del domain name data
+    $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] add name data [ttl]
+    $PROG [-dt -h -j JSON -k:KEY -s:SECRET -q] del name data
 
 Add or delete TXT records from GoDaddy DNS
 
@@ -59,8 +59,6 @@ Arguments:
 
     del - remove the specified record from the domain
 
-    domain is the domain name, e.g. example.org
-
     name is the DNS record name to add, e.g. _acme-challenge.example.org.
          Note that trailing '.' is significant in DNS.
 
@@ -73,9 +71,6 @@ Arguments:
     For minimal trace output (to override -q), define GODADDY_TRACE="y".
 
 Options
-    -b    Domain name(s) in which challenge records are stored
-          E.g. often, www.example.net is stored in example.net.
-          Default from GODADDY_BASE
     -d    Provide debugging output - all requests and responses
     -h    This help.
     -j:   Location of JSON.sh Default `dirname $0`/JSON.sh, or
@@ -88,7 +83,6 @@ Options
     All output, except for this help text, is to stderr.
 
 Environment variables
-    GODADDY_BASE    Domain name(s) in which challenge records are stored
     GODADDY_JSON    location of the JSOH.sh script
     GODADDY_KEY     default API key
     GODADDY_SCRIPT  location of this script, default location of JSON.sh
@@ -149,19 +143,13 @@ if ! [[ "$op" =~ ^(add|del)$ ]]; then
     echo "Operation must be \"add\" or \"del\"" >&2
     exit 3
 fi
-domain="$2"
-domain="${domain%'.'}"
-if [ -z "$domain" ]; then
-    echo "'domain' parameter is required, see -h" >&2
-    exit 3
-fi
-name="$3"
+name="$2"
 if [ -z "$name" ]; then
     echo "'name' parameter is required, see -h" >&2
     exit 3
 fi
 ! [[ "$name" =~ [.]$ ]] && name="${name}.${domain}."
-data="$4"
+data="$3"
 if [ -z "$data" ]; then
     echo "'data' parameter is required, see -h" >&2
     exit 3
@@ -207,28 +195,92 @@ fi
 
 [ -n "$DEBUG" ] &&  echo "$authhdr" >&2
 
-if [ "$op" = "add" ]; then
-    # May need to retry due to zone cuts
-
-    while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do
-
-        reqname="$name"
-        # The API doesn't trim the base domain from the name (it used to)
-        # If specified, remove any listed base.
-        if [ -n "$GODADDY_BASE" ]; then
-            for GDB in $GODADDY_BASE; do
-                gdb="`echo "$GDB" | sed -e's/\\.$//;s/\\./\\\\./g;'`"
-                gdb="^(.+)\\.$gdb\\.?$"
-                if [[ "$name" =~ $gdb ]]; then
-                    reqname="${BASH_REMATCH[1]}"
-                    break;
-                fi
-            done
-        else
-            eval 'reqname="$''{name%'"'.$domain.'}"'"'
+#strip off the last period
+if [[ "$name" =~ ^.+\. ]]; then
+        name=${name%?}
+fi
+
+reqdomain=
+reqname=
+
+# GoDaddy REST API URL is in the format /v1/domains/{domain}/records/{type}/{name}
+# for adding/updating (PUT) or deleting (DELETE) a record. The API will support up 
+# to three segments in domain names (ex. mydomain.com and www.mydomain.com)
+# in order to determine which domain the API call will affect (both mydomain.com and 
+# www.mydomain.com will result in the modification of the mydomain.com domain.  Any 
+# more than three segments (ex. sub.something.mydomain.com will result in 
+# the API throwing a MISMATCH_FORMAT error.
+#
+# Examples
+# 1. If mydomain.com was provided to this script as the domain parameter, and 
+#    _acme-challengemydomain.com was provided as the name, then the URL 
+#    /v1/domains/mydomain.com/records/TXT/_acme-challenge will be used which
+#
+# 2. If www.mydomain.com was provided to this script as the domain parameter, 
+#    and _acme-challenge.www.mydomain.com was provided as the name, then the 
+#    URL /v1/domains/mydomain.com/records/TXT/_acme-challenge.www will be used.
+
+# Determine the domain and the name to use for the API the URL
+# The name parameter given to us is in the format challenge.domain.
+# (ex _acme-challenge.mydomain.com. - note the trailing period).  We will just
+# use the name given us to determine the domain
+
+while [[ "$name" =~ ^([^.]+)\.([^.]+.*) ]]; do
+        if [ -n "${reqname}" ]; then reqname="${reqname}."; fi
+        reqname="${reqname}${BASH_REMATCH[1]}"
+        testdomain="${BASH_REMATCH[2]}"
+        name=$testdomain
+        if [[ ! "$name" =~ [^.]+\.[^.]+ ]]; then
+                exit 1
+        fi
+
+        url="$API/$testdomain"
+
+        [ -n "$DEBUG" ] && echo "Looking for domain ${testdomain}"
+
+        response="$(curl -i -s -X GET --config - "${url}" <<EOF
+                header = "Content-Type: application/json"
+                header = "$authhdr"
+EOF
+)"
+        sts=$?
+
+        [ -n "$DEBUG" ] && cat >&2 <<EOF
+
+Response:
+curl status = $sts
+--------
+$response
+--------
+EOF
+        if [ $sts -ne 0 ]; then
+            echo "curl error $sts querying domain" >&2
+            exit $sts
         fi
 
-        url="$API/$domain/records/TXT/$reqname"
+        if echo "$response" | grep -q '^HTTP/.* 200 '; then
+            [ -n "$DEBUG" ] && echo "Found domain ${testdomain}"
+            reqdomain=${testdomain}
+            break
+        fi
+
+        code="`echo "$response" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
+        if [ "$code" = 'NOT_FOUND' ]; then
+                continue
+        fi
+done
+
+
+if [ -z "$reqdomain" ] || [ -z "$reqname" ]; then
+    echo "Unable to determine domain or RR name" >&2
+    exit 3
+fi
+
+
+
+if [ "$op" = "add" ]; then
+
+        url="$API/$reqdomain/records/TXT/$reqname"
 
         request='[{"data":"'$data'","ttl":'$ttl'}]'
         [ -n "$DEBUG" ] && cat >&2 <<EOF
@@ -252,54 +304,38 @@ curl status = $sts
 $result
 --------
 EOF
-        if [ $sts -ne 0 ]; then
-            echo "curl error $sts adding record" >&2
-            exit $sts
-        fi
-        if ! echo "$result" | grep -q '^HTTP/.* 200 '; then
-            code="`echo "$result" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
-            msg="`echo "$result" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`"
-            if [ "$code" = "DUPLICATE_RECORD" ]; then
-                if [ -n "$VERB" ]; then
-                    echo "$msg in $domain" >&2
-                fi
-                exit 0 # Duplicate record is still success
-            fi
-            if [ "$code" = 'UNKNOWN_DOMAIN' ]; then
-                if [[ "$domain" =~ ^([^.]+)\.([^.]+\.[^.]+.*) ]]; then
-                    [ -n "$DEBUG" ] && \
-                        echo "$domain unknown, trying ${BASH_REMATCH[2]}" >&2
-                    domain="${BASH_REMATCH[2]}"
-                    continue;
-                fi
+    if [ $sts -ne 0 ]; then
+        echo "curl error $sts adding record" >&2
+        exit $sts
+    fi
+    if ! echo "$result" | grep -q '^HTTP/.* 200 '; then
+        code="`echo "$result" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
+        msg="`echo "$result" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`"
+        if [ "$code" = "DUPLICATE_RECORD" ]; then
+            if [ -n "$VERB" ]; then
+                echo "$msg in $domain" >&2
             fi
-            echo "Request failed $msg" >&2
-            exit 1
+            exit 0 # Duplicate record is still success
         fi
-        [ -n "$VERB" ] && echo "$domain: added $name $ttl TXT \"$data\"" >&2
-        exit 0
-    done
+        echo "Request failed $msg" >&2
+        exit 1
+    fi
+    [ -n "$VERB" ] && echo "$reqdomain: added $reqname $ttl TXT \"$data\"" >&2
+    exit 0
+    
 fi
 
 
-# ----- Delete
-
-# There is no delete API
-# But, it is possible to replace all TXT records.
-#
-# So, first query for all TXT records
-
-# May need to retry due to zone cuts
-
-while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do
 
-    url="$API/$domain/records/TXT"
-    [ -n "$DEBUG" ] && echo "Query for TXT records to: $url" >&2
+if [ "$op" = "del" ]; then
+    url="$API/$reqdomain/records/TXT/$reqname"
+    [ -n "$DEBUG" ] && echo "Deleting challenge TXT records at: $url" >&2    
 
-    current="$(curl -i -s -X GET --config - "$url" <<EOF
-                        header = "$authhdr" 
+    current="$(curl -i -s -X DELETE --config - "$url" <<EOF
+                    header = "$authhdr" 
 EOF
 )"
+
     sts=$?
     if [ $sts -ne 0 ]; then
         echo "curl error $sts for query" >&2
@@ -312,132 +348,14 @@ Response
 $current
 --------
 EOF
+
     if ! echo "$current" | grep -q '^HTTP/.* 200 '; then
         code="`echo "$current" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
         msg="`echo "$current" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`"
-        if [ "$code" = "UNKNOWN_DOMAIN" ]; then
-            if [[ "$domain" =~ ^([^.]+)\.([^.]+\.[^.]+.*) ]]; then
-                [ -n "$DEBUG" ] && echo \
-                     "$domain unknown, trying ${BASH_REMATCH[2]}" >&2
-                domain="${BASH_REMATCH[2]}"
-                continue;
-            fi
-        fi
         echo "Request failed $msg" >&2
         exit 1
     fi
-    # Remove headers
-
-    current="$(echo "$current" | sed -e'0,/^\r*$/d')"
-    break
-done
-
-    # The zone cut is known, so the replace can't fail due to UNKNOWN domain
-
-if [ "$current" = '[]' ]; then # No TXT records in zone
-    [ -n "$VERB" ] && echo "$domain: $name TXT \"$data\" does not exist" >&2
-    [ -n "$DEBUG" ] && echo "No TXT records in $domain" >&2
-    exit 1  # Intent was to change, so error status
-fi
-
-[ -n "$DEBUG" ] && echo "Response is valid"
-
-# Prepare request to replace TXT RRSET
-
-# Parse JSON and select only the record structures, which are [index] { ...}
 
-current="$(echo "$current" | $JSON | sed -n -e'/^\[[0-9][0-9]*\]/{ s/^\[[0-9][0-9]*\]//; p}')"
-base="$current"
-
-[ -n "$DEBUG" ] && cat >&2 <<EOF
-Old TXT RRSET:
-$current
-EOF
-
-# Remove the desired record.  The name must be relative. Order varies.
-
-eval 'name="$''{name%'"'.$domain.'}"'"'
-
-match="$(printf '"name":"%s","data":"%s","ttl":' "$name" "$data")"
-cmd="$(printf 'echo %s%s%s | grep -v %s%s%s' "'" "$current" "'" "'" "$match" "'")"
-eval 'new="$('"$cmd"')"'
-
-match="$(printf '"data":"%s","name":"%s","ttl":' "$data" "$name")"
-cmd="$(printf 'echo %s%s%s | grep -v %s%s%s' "'" "$current" "'" "'" "$match" "'")"
-eval 'new="$('"$cmd"')"'
-
-if [ "$new" = "$base" ]; then
-    [ -n "$VERB" ] && echo "$domain: $name TXT \"$data\" does not exist" >&2
-    exit 1 # Intent was to change DNS, so this is an error
-fi
-
-# Remove whitespace and insert needed commmas
-#
-fmtnew="$new"
-new=$(echo "$new" | sed -e"s/}/},/g; \$s/},/}/;" | tr -d '\t\n')
-
-if [ -z "$new" ]; then
-    [ -n "$VERB" ] && echo "Replacing last TXT record with a dummy (see -h)" >&2
-    new='{"type":"TXT","name":"_dummy.record_","data":"_This record is not used_","ttl":601}'
-    dummy="t"
-    TAB=$'\t'
-    fmtnew="${TAB}$new"
-    if [ "$fmtnew" = "$base" ]; then
-        [ -n "$VERB" ] && echo "This tool can't delete a placeholder when it is the only TXT record" >&2
-        exit 0 # Not really success, but retrying won't help.
-    fi
+    [ -n "$VERB" ] && echo "$reqdomain: deleted $reqname TXT \"$data\"" >&2
+    exit 0
 fi
-
-request="[$new]"
-
-[ -n "$DEBUG" ] && cat >&2 <<EOF
-New TXT RRSET will be
-$fmtnew
-
-EOF
-
-url="$API/$domain/records/TXT"
-
-[ -n "$DEBUG" ] && cat >&2 <<EOF
-Replace (delete) request to: $url
---------
-$request
---------
-EOF
-
-result="$(curl -i -s -X PUT -d  "$request" --config - "$url" <<EOF
-               header = "Content-Type: application/json"
-               header = "$authhdr"
-EOF
-)"
-sts=$?
-[ -n "$DEBUG" ] && cat >&2 <<EOF
-
-Result:
-curl status = $sts
---------
-$result
---------
-EOF
-
-if [ $sts -ne 0 ]; then
-    echo "curl error $sts deleting record" >&2
-    exit $sts
-fi
-if ! echo "$result" | grep -q '^HTTP/.* 200 '; then
-    result="$(echo "$result" | sed -e'0,/^\r*$/d')"
-    code="`echo "$result" | grep '"code":' | sed -e's/^.*"code":"//; s/\".*$//'`"
-    msg="`echo "$result" | grep '"message":' | sed -e's/^.*"message":"//; s/\".*$//'`"
-    echo "Request failed $msg" >&2
-    exit 1
-fi
-
-if [ -n "$VERB" ]; then
-    if [ -n "$dummy" ]; then
-        echo "$domain: replaced $name TXT \"$data\" with a placeholder" >&2
-    else
-        echo "$domain: deleted $name TXT \"$data\"" >&2
-    fi
-fi
-exit 0
-

From cc6a3d054cc87046a02ac08036b33038166ba7f1 Mon Sep 17 00:00:00 2001
From: Steve Kennedy <skennedy@xyide.com>
Date: Mon, 20 May 2024 00:46:11 -0400
Subject: [PATCH 2/2] Add BASH implementation of Route 53 scripts

---
 dns_scripts/Route53-README.md |  37 +++++++
 dns_scripts/dns_add_route53   |  18 +++
 dns_scripts/dns_del_route53   |  18 +++
 dns_scripts/dns_route53       | 199 ++++++++++++++++++++++++++++++++++
 4 files changed, 272 insertions(+)
 create mode 100644 dns_scripts/Route53-README.md
 create mode 100755 dns_scripts/dns_add_route53
 create mode 100755 dns_scripts/dns_del_route53
 create mode 100644 dns_scripts/dns_route53

diff --git a/dns_scripts/Route53-README.md b/dns_scripts/Route53-README.md
new file mode 100644
index 00000000..b078a782
--- /dev/null
+++ b/dns_scripts/Route53-README.md
@@ -0,0 +1,37 @@
+# Using Route53 BASH scripts for LetsEncrypt domain validation.
+
+## Quick guide to setting up getssl for domain validation of Route53 DNS domains.
+
+There a few prerequisites to using getssl with Route53 DNS:
+
+1. You will need to set up an IAM user with the necessary permissions to modify resource records in the hosted zone.
+
+   - route53:ListHostedZones
+   - route53:ChangeResourceRecordSets
+
+1. You will need the AWS CLI Client installed on your machine.
+
+1. You will need to configure the client for the IAM user that has permission to modify the resource records.
+
+With those in hand, the installation procedure is:
+
+1. Open your config file (the global file in ~/.getssl/getssl.cfg
+   or the per-account file in ~/.getssl/example.net/getssl.cfg)
+
+1. Set the following options:
+
+   - VALIDATE_VIA_DNS="true"
+   - DNS_ADD_COMMAND="/usr/share/getssl/dns_scripts/dns_add_route53"
+   - DNS_DEL_COMMAND="/usr/share/getssl/dns_scripts/dns_del_route53"
+
+   The AWS CLI profile to use (will use _default_ if not specified)
+
+   - export AWS*CLI_PROFILE="\_profile name*"
+
+1. Set any other options that you wish (per the standard
+   directions.) Use the test CA to make sure that
+   everything is setup correctly.
+
+That's it. getssl example.net will now validate with DNS.
+
+There are additional options, which are documented in `dns_route53 -h`
diff --git a/dns_scripts/dns_add_route53 b/dns_scripts/dns_add_route53
new file mode 100755
index 00000000..88e3f0b5
--- /dev/null
+++ b/dns_scripts/dns_add_route53
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Add token to Route53 dns using dns_route53 bash version
+
+fulldomain="$1"
+token="$2"
+
+[ -z "$ROUTE53_SCRIPT" ] && ROUTE53_SCRIPT="/usr/share/getssl/dns_scripts/dns_route53"
+[[ "$ROUTE53_SCRIPT" =~ ^~ ]] && \
+    eval 'ROUTE53_SCRIPT=`readlink -nf ' $ROUTE53_SCRIPT '`'
+
+if [ ! -x "$ROUTE53_SCRIPT" ]; then
+    echo "$ROUTE53_SCRIPT: not found.  Please install, softlink or set ROUTE53_SCRIPT to its full path"
+    echo "See ROUTE53-README.txt for complete instructions."
+    exit 3
+fi
+
+$ROUTE53_SCRIPT -q add "${fulldomain}." "${token}"
diff --git a/dns_scripts/dns_del_route53 b/dns_scripts/dns_del_route53
new file mode 100755
index 00000000..79a05700
--- /dev/null
+++ b/dns_scripts/dns_del_route53
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+# Delete token from Route53 dns using dns_route53 bash version
+
+fulldomain="$1"
+token="$2"
+
+[ -z "$ROUTE53_SCRIPT" ] && ROUTE53_SCRIPT="/usr/share/getssl/dns_scripts/dns_route53"
+[[ "$ROUTE53_SCRIPT" =~ ^~ ]] && \
+    eval 'ROUTE53_SCRIPT=`readlink -nf ' $ROUTE53_SCRIPT '`'
+
+if [ ! -x "$ROUTE53_SCRIPT" ]; then
+    echo "$ROUTE53_SCRIPT: not found.  Please install, softlink or set ROUTE53_SCRIPT to its full path"
+    echo "See ROUTE53-README.txt for complete instructions."
+    exit 3
+fi
+
+$ROUTE53_SCRIPT -q del "${fulldomain}." "${token}"
diff --git a/dns_scripts/dns_route53 b/dns_scripts/dns_route53
new file mode 100644
index 00000000..5f1aaab7
--- /dev/null
+++ b/dns_scripts/dns_route53
@@ -0,0 +1,199 @@
+#!/usr/bin/env bash
+VERSION="1.0"
+PROG="$(basename "$0")"
+
+QUIET=n
+
+while getopts 'dhp:t:z:i:qv' opt; do
+    case $opt in
+        d) DEBUG="Y"                                   ;;
+        p) AWS_CLI_PROFILE="$OPTARG"                   ;;
+        q) QUIET=                                      ;;
+        v) echo "dns_route53 version $VERSION"; exit 0 ;;
+        z) ROUTE53_HOSTED_ZONE_NAME="$OPTARG"          ;;
+        i) ROUTE53_HOSTED_ZONE_ID="$OPTARG"            ;;
+        *)
+            cat <<EOF
+Usage
+    $PROG [-dt -q] add name data [ttl]
+    $PROG [-dt -h -p "aws-profile-name" -q] del name data
+
+Add or delete TXT records from Route53 Hosted Zone
+
+You must have the AWS CLI installed and a profile configured for this script to work.
+	The IAM user that the profile uses requires the following action permissions in AWS:
+	- route53:ListHostedZones - Not necessary if zone ID is available to this script
+	- route53:ChangeResourceRecordSets
+
+With getssl, this script is called from the dns_add_route53 and
+dns_del_route53 wrapper scripts.
+
+Arguments:
+    add - add the specified record to the domain
+
+    del - remove the specified record from the domain
+
+    name is the fully qualified record name to create the challenge for e.g. www.example.org. Note that trailing '.' is necessary.  Also note that _acme-challenge. will automatically be prepended by this script
+
+    data is the record data, e.g. "myverificationtoken"
+
+    ttl is optional and will default to 120 if not specified
+
+    If it is necessary to turn on debugging externally, define
+    ROUTE53_DEBUG="y" (any non-null string will do).
+    For minimal trace output (to override -q), define ROUTE53_TRACE="y".
+
+Options
+    -d    Provide debugging output - all requests and responses
+    -h    This help.
+    -i:   The hosted zone ID
+    -p:   The AWS CLI profile to use.  Will use default if not specified
+    -q:   Quiet - omit normal success messages
+    -z:   The hosted zone name. Will be used to determine the zone ID if ID was not provided
+
+    All output, except for this help text, is to stderr.
+
+Environment variables
+    ROUTE53_SCRIPT   location of this script
+    ROUTE53_HOSTED_ZONE_NAME The name of the hosted zone name.  If not specified, then the name will be determined from the record name provided to this script
+    ROUTE53_HOSTED_ZONE_ID The id of the hosted zone to be used instead of trying to automatically determine the ID
+    AWS_CLI_PROFILE  the aws cli profile to use if not using default
+
+BUGS
+    Report any issues to https://github.com/xyide/getssl/issues
+EOF
+            exit 0
+            ;;
+    esac
+done
+shift $((OPTIND-1))
+
+if [ -z "$AWS_CLI_PROFILE" ]; then
+    echo "AWS_CLI_PROFILE not defined.  Using default" >&2
+    AWS_CLI_PROFILE=default
+fi
+
+op="$1"
+if ! [[ "$op" =~ ^(add|del)$ ]]; then
+    echo "Operation must be \"add\" or \"del\"" >&2
+    exit 3
+fi
+name="$2"
+if [ -z "$name" ]; then
+    echo "'name' parameter is required, see -h" >&2
+    exit 3
+fi
+data="$3"
+if [ -z "$data" ]; then
+    echo "'data' parameter is required, see -h" >&2
+    exit 3
+fi
+
+if [ "$op" = 'del' ]; then
+    ttl=120
+elif [ -z "$5" ]; then
+    ttl="120"
+elif ! [[ "$5" =~ ^[0-9]+$ ]]; then
+    echo "TTL $5 is not numeric" >&2
+    exit 3
+elif [ "$5" -lt 120 ]; then
+    [ -n "$VERB" ] && \
+        echo "$5 is too small.  Using TTL of 120 instead" >&2
+    ttl="120"
+else
+    ttl="$5"
+fi
+
+# end processing parameters
+
+[ -n "$DEBUG" ] && \
+    echo "$PROG: $op $name \"$data\" $ttl" >&2
+
+# Determine what actual hosted zone to use.
+
+HOSTED_ZONE_NAME=$ROUTE53_HOSTED_ZONE_NAME
+HOSTED_ZONE_ID=$ROUTE53_HOSTED_ZONE_ID
+RR_NAME="_acme-challenge.${name}"
+RR_VALUE="${data}"
+
+# Function to parse through the segments in the supplied name
+# to determine the zone and its id
+function determine_hosted_zone_name_and_id() {
+    TMP_NAME=$name
+    TMP_RR_NAME=
+	while [[ "$TMP_NAME" =~ ^([^.]+)\.([^.]+.*) ]]; do
+		if [ -n "${TMP_RR_NAME}" ]; then 
+			TMP_RR_NAME="${TMP_RR_NAME}."; 
+		fi
+		TMP_RR_NAME="${TMP_RR_NAME}${BASH_REMATCH[1]}"
+		testdomain="${BASH_REMATCH[2]}"
+		[ -n "$DEBUG" ] && echo "Testing hosted zone ${testdomain}"
+		TMP_NAME=$testdomain
+		if [[ ! "$TMP_NAME" =~ [^.]+\.[^.]+ ]]; then
+			[ -n "$DEBUG" ] && echo "No segments left"
+				exit 1
+		fi
+	
+		TMP_ZONE_ID=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Name=='${testdomain}'].Id | [0]" | sed -e 's/^"//' -e 's/"$//')
+	
+		
+		if [ "${TMP_ZONE_ID}" != "null" ]; then
+			[ -n "$DEBUG" ] && echo "Found hosted zone ${testdomain}"
+			HOSTED_ZONE_NAME=${testdomain}
+			HOSTED_ZONE_ID=$TMP_ZONE_ID
+			break
+		fi
+	done
+}
+
+# If zone ID is specified, then use it to determine the hosted zone name
+if [ -n "${HOSTED_ZONE_ID}" ]; then
+	HOSTED_ZONE_NAME=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Id=='${ZONE_ID}'].Name | [0]" | sed -e 's/^"//' -e 's/"$//')
+# If zone name is specified, then use it to get the zone id
+elif [ -n "${HOSTED_ZONE_NAME}" ]; then
+	HOSTED_ZONE_ID=$(aws --profile=${AWS_CLI_PROFILE} route53 list-hosted-zones --query "HostedZones[?Name=='${HOSTED_ZONE_NAME}'].Id | [0]" | sed -e 's/^"//' -e 's/"$//')
+else
+	determine_hosted_zone_name_and_id
+fi
+
+
+	if [ -z "${HOSTED_ZONE_ID}" ]; then
+	echo "Hosted zone id not specified or determined" >&2
+    exit 3
+fi
+
+if [ "$op" = "add" ]; then
+	ACTION="UPSERT"
+elif [ "$op" = "del" ]; then
+	ACTION="DELETE"
+else
+	echo "Unsupported Operation: $op" >&2
+fi
+
+CHANGE_BATCH='
+{
+    "Comment": "GetSSL LetsEncrypt DNS Challenge",
+    "Changes": [{
+      "Action"             : "'"$ACTION"'",
+      "ResourceRecordSet"  : {
+        "Name"             : "'"$RR_NAME"'",
+        "Type"             : "TXT",
+        "TTL"              : '${ttl}',
+        "ResourceRecords"  : [{
+            "Value"         : "\"'$RR_VALUE'\""
+        }]
+      }
+    }]
+  }
+'
+
+
+[ -n "$DEBUG" ] && echo "${CHANGE_BATCH}" >&2
+
+aws \
+	--profile=${AWS_CLI_PROFILE} \
+	route53 \
+	change-resource-record-sets \
+	--hosted-zone-id=${HOSTED_ZONE_ID} \
+	--change-batch "${CHANGE_BATCH}"
+exit $?
\ No newline at end of file