| @ -0,0 +1,60 @@ | |||||
| 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="..." | |||||
| 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 (2017) 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. | |||||
| @ -0,0 +1,40 @@ | |||||
| #!/bin/bash | |||||
| # Copyright (2017) Timothe Litt litt at acm _dot org | |||||
| # Add token to GoDaddy dns using dns_godaddy | |||||
| # You do not have to customize this script. | |||||
| # | |||||
| # Obtain the Key and Secret from https://developer.godaddy.com/getstarted | |||||
| # You must obtain a "Production" key - NOT the "Test" key you're required | |||||
| # to get first. | |||||
| # | |||||
| # Obtain JSON.sh from https://github.com/dominictarr/JSON.sh | |||||
| # Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT, | |||||
| # or specify its location with GODADDY_JSON The default is | |||||
| # /usr/share/getssl/dns_scripts/ | |||||
| # | |||||
| # Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg | |||||
| # | |||||
| # See GoDaddy-README.txt for complete instructions. | |||||
| fulldomain="$1" | |||||
| token="$2" | |||||
| [ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="/usr/share/getssl/dns_scripts/dns_godaddy" | |||||
| [[ "$GODADDY_SCRIPT" =~ ^~ ]] && \ | |||||
| eval 'GODADDY_SCRIPT=`readlink -nf ' $GODADDY_SCRIPT '`' | |||||
| if [ ! -x "$GODADDY_SCRIPT" ]; then | |||||
| echo "$GODADDY_SCRIPT: not found. Please install, softlink or set GODADDY_SCRIPT to its full path" | |||||
| echo "See GoDaddy-README.txt for complete instructions." | |||||
| exit 3 | |||||
| fi | |||||
| # JSON.sh is not (currently) used by add | |||||
| export GODADDY_KEY | |||||
| export GODADDY_SECRET | |||||
| $GODADDY_SCRIPT -q add "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}" | |||||
| @ -0,0 +1,44 @@ | |||||
| #!/bin/bash | |||||
| FULLDOMAIN=$1 | |||||
| TOKEN=$2 | |||||
| TMPFILE=$(mktemp /tmp/dns_add_joker.XXXXXXX) | |||||
| USERNAME="youruser" | |||||
| PASSWORD="yourpassword" | |||||
| # Verify that required parameters are set | |||||
| if [[ -z "${FULLDOMAIN}" ]]; then | |||||
| echo "DNS script requires full domain name as first parameter" | |||||
| exit 1 | |||||
| fi | |||||
| if [[ -z "${TOKEN}" ]]; then | |||||
| echo "DNS script requires challenge token as second parameter" | |||||
| exit 1 | |||||
| fi | |||||
| DOMAIN_ROOT=$(echo "${FULLDOMAIN}" | awk -F\. '{print $(NF-1) FS $NF}') | |||||
| SID=$(curl --silent -X POST https://dmapi.joker.com/request/login \ | |||||
| -H "Accept: application/json" -H "User-Agent: getssl/0.1" \ | |||||
| -H "application/x-www-form-urlencoded" -d "username=${USERNAME}&password=${PASSWORD}" \ | |||||
| -i -k 2>/dev/null | grep Auth-Sid | awk '{ print $2 }') | |||||
| ## put zone data in tempfile | |||||
| curl --silent -X POST https://dmapi.joker.com/request/dns-zone-get \ | |||||
| -H "Accept: application/json" -H "User-Agent: getssl/0.1" \ | |||||
| -H "application/x-www-form-urlencoded" -d "domain=${DOMAIN_ROOT}&auth-sid=${SID}" | \ | |||||
| tail -n +7 >"${TMPFILE}" | |||||
| ## add txt record | |||||
| printf "_acme-challenge.%s. TXT 0 \"%s \" 300\n\n" "${FULLDOMAIN}" "${TOKEN}" >>"${TMPFILE}" | |||||
| ## generate encoded url data | |||||
| URLDATA=$(cat "${TMPFILE}" | sed 's/ /%20/g' | sed 's/"/%22/g' | sed ':a;N;$!ba;s/\n/%0A/g') | |||||
| ## write new zonefile to joker | |||||
| curl --silent --output /dev/null "https://dmapi.joker.com/request/dns-zone-put?domain=${DOMAIN_ROOT}&zone=${URLDATA}&auth-sid=${SID}" 2>&1 | |||||
| ## remove tempfile | |||||
| rm -f "${TMPFILE}" | |||||
| @ -1,33 +1,33 @@ | |||||
| #!/bin/bash | |||||
| domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g')) | |||||
| challenge="$2" | |||||
| # Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/ | |||||
| # permissions needed are /domain/zone/* in GET,POST,DELETE | |||||
| applicationKey="YourAK" | |||||
| applicationSecret="YourAS" | |||||
| consumerKey="YourCK" | |||||
| topDomain=${domains[2]} | |||||
| subDomain=${domains[1]%%.} | |||||
| function send | |||||
| { | |||||
| method=$1 | |||||
| url=$2 | |||||
| body=$3 | |||||
| ts=$(date +%s) | |||||
| sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1) | |||||
| curl -X ${method} -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" https://eu.api.ovh.com/1.0${url} | |||||
| } | |||||
| # Creation request | |||||
| send POST /domain/zone/${topDomain}/record "{\"fieldType\":\"TXT\",\"subDomain\":\"$subDomain\",\"ttl\":60,\"target\":\"$challenge\"}" | |||||
| # Refresh request | |||||
| send POST /domain/zone/${topDomain}/refresh "" | |||||
| # Pause for 10 seconds, for DNS propagation | |||||
| sleep 10 | |||||
| #!/bin/bash | |||||
| domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g')) | |||||
| challenge="$2" | |||||
| # Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/ | |||||
| # permissions needed are /domain/zone/* in GET,POST,DELETE | |||||
| applicationKey="YourAK" | |||||
| applicationSecret="YourAS" | |||||
| consumerKey="YourCK" | |||||
| topDomain=${domains[2]} | |||||
| subDomain=${domains[1]%%.} | |||||
| function send | |||||
| { | |||||
| method=$1 | |||||
| url=$2 | |||||
| body=$3 | |||||
| ts=$(date +%s) | |||||
| sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1) | |||||
| curl -X "${method}" -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" "https://eu.api.ovh.com/1.0${url}" | |||||
| } | |||||
| # Creation request | |||||
| send POST "/domain/zone/${topDomain}/record" "{\"fieldType\":\"TXT\",\"subDomain\":\"$subDomain\",\"ttl\":60,\"target\":\"$challenge\"}" | |||||
| # Refresh request | |||||
| send POST "/domain/zone/${topDomain}/refresh" "" | |||||
| # Pause for 10 seconds, for DNS propagation | |||||
| sleep 10 | |||||
| @ -0,0 +1,38 @@ | |||||
| #!/bin/bash | |||||
| # Copyright (2017) Timothe Litt litt at acm _dot org | |||||
| # Remove token from GoDaddy dns using dns_godaddy | |||||
| # You do not have to customize this script. | |||||
| # | |||||
| # Obtain the Key and Secret from https://developer.godaddy.com/getstarted | |||||
| # You must obtain a "Production" key - NOT the "Test" key you're required | |||||
| # to get first. | |||||
| # | |||||
| # Obtain JSON.sh from https://github.com/dominictarr/JSON.sh | |||||
| # Place it in (or softlink it to) the same directory as $GODADDY_SCRIPT, | |||||
| # or specify its location with GODADDY_JSON The default is | |||||
| # /usr/share/getssl/dns_scripts/ | |||||
| # | |||||
| # Define GODADDY_KEY and GO_DADDY_SECRET in your account or domain getssl.cfg | |||||
| # | |||||
| # See GoDaddy-README.txt for complete instructions. | |||||
| fulldomain="$1" | |||||
| token="$2" | |||||
| [ -z "$GODADDY_SCRIPT" ] && GODADDY_SCRIPT="/usr/share/getssl/dns_scripts/dns_godaddy" | |||||
| [[ "$GODADDY_SCRIPT" =~ ^~ ]] && \ | |||||
| eval 'GODADDY_SCRIPT=`readlink -nf ' "$GODADDY_SCRIPT" '`' | |||||
| if ! [ -x "$GODADDY_SCRIPT" ]; then | |||||
| echo "$GODADDY_SCRIPT: not found. Please install, softlink or set GODADDY_SCRIPT to its full path" | |||||
| echo "See GoDaddy-README.txt for complete instructions." | |||||
| exit 3 | |||||
| fi | |||||
| export GODADDY_KEY | |||||
| export GODADDY_SECRET | |||||
| $GODADDY_SCRIPT -q del "${fulldomain}" "_acme-challenge.${fulldomain}." "${token}" | |||||
| @ -0,0 +1,44 @@ | |||||
| #!/bin/bash | |||||
| FULLDOMAIN=$1 | |||||
| TOKEN=$2 | |||||
| TMPFILE=$(mktemp /tmp/dns_add_joker.XXXXXXX) | |||||
| USERNAME="youruser" | |||||
| PASSWORD="yourpassword" | |||||
| # Verify that required parameters are set | |||||
| if [[ -z "${FULLDOMAIN}" ]]; then | |||||
| echo "DNS script requires full domain name as first parameter" | |||||
| exit 1 | |||||
| fi | |||||
| if [[ -z "${TOKEN}" ]]; then | |||||
| echo "DNS script requires challenge token as second parameter" | |||||
| exit 1 | |||||
| fi | |||||
| DOMAIN_ROOT=$(echo "${FULLDOMAIN}" | awk -F\. '{print $(NF-1) FS $NF}') | |||||
| SID=$(curl --silent -X POST https://dmapi.joker.com/request/login \ | |||||
| -H "Accept: application/json" -H "User-Agent: getssl/0.1" \ | |||||
| -H "application/x-www-form-urlencoded" -d "username=${USERNAME}&password=${PASSWORD}" \ | |||||
| -i -k 2>/dev/null | grep Auth-Sid | awk '{ print $2 }') | |||||
| ## put zone data in tempfile | |||||
| curl --silent -X POST https://dmapi.joker.com/request/dns-zone-get \ | |||||
| -H "Accept: application/json" -H "User-Agent: getssl/0.1" \ | |||||
| -H "application/x-www-form-urlencoded" -d "domain=${DOMAIN_ROOT}&auth-sid=${SID}" | \ | |||||
| tail -n +7 >"${TMPFILE}" | |||||
| ## remove txt record | |||||
| sed -i "/_acme-challenge.${FULLDOMAIN}.*${TOKEN}.*/d" "${TMPFILE}" | |||||
| ## generate encoded url data | |||||
| URLDATA=$(cat "${TMPFILE}" | sed 's/ /%20/g' | sed 's/"/%22/g' | sed ':a;N;$!ba;s/\n/%0A/g') | |||||
| ## write new zonefile to joker | |||||
| curl --silent --output /dev/null "https://dmapi.joker.com/request/dns-zone-put?domain=${DOMAIN_ROOT}&zone=${URLDATA}&auth-sid=${SID}" 2>&1 | |||||
| ## remove tempfile | |||||
| rm -f "${TMPFILE}" | |||||
| @ -1,9 +1,39 @@ | |||||
| #!/bin/bash | #!/bin/bash | ||||
| # example of script to add token to local dns using nsupdate | |||||
| # example of script to remove token from local dns using nsupdate | |||||
| dnskeyfile="path/to/bla.key" | |||||
| fulldomain="$1" | fulldomain="$1" | ||||
| token="$2" | token="$2" | ||||
| printf "update delete _acme-challenge.%s. 300 in TXT \"%s\"\n\n" "${fulldomain}" "${token}" | nsupdate -k "${dnskeyfile}" -v | |||||
| # VARIABLES: | |||||
| # | |||||
| # DNS_NSUPDATE_KEYFILE - path to a TSIG key file, if required | |||||
| # DNS_NSUPDATE_GETKEY - command to execute if access to the key file requires | |||||
| # some special action: dismounting a disk, encrypting a | |||||
| # file... Called with the operation 'del' and action | |||||
| # 'open" / 'close' | |||||
| if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then | |||||
| if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'open' "${fulldomain}" ; then | |||||
| exit $(( $? + 128 )) | |||||
| fi | |||||
| options="-k ${DNS_NSUPDATE_KEYFILE}" | |||||
| fi | |||||
| # Note that blank line is a "send" command to nsupdate | |||||
| nsupdate "${options}" -v <<EOF | |||||
| update delete "_acme-challenge.${fulldomain}." 300 in TXT "${token}" | |||||
| EOF | |||||
| sts=$? | |||||
| if [ -n "${DNS_NSUPDATE_KEYFILE}" ]; then | |||||
| if [ -n "${DNS_NSUPDATE_KEY_HOOK}" ] && ! "${DNS_NSUPDATE_KEY_HOOK}" 'del' 'close' "${fulldomain}" ; then | |||||
| exit $(( sts + ( $? * 10 ) )) | |||||
| fi | |||||
| fi | |||||
| exit ${sts} | |||||
| @ -1,35 +1,35 @@ | |||||
| #!/bin/bash | |||||
| domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g')) | |||||
| challenge="$2" | |||||
| # Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/ | |||||
| # permissions needed are /domain/zone/* in GET,POST,DELETE | |||||
| applicationKey="YourAK" | |||||
| applicationSecret="YourAS" | |||||
| consumerKey="YourCK" | |||||
| topDomain=${domains[2]} | |||||
| subDomain=${domains[1]%%.} | |||||
| function send | |||||
| { | |||||
| method=$1 | |||||
| url=$2 | |||||
| body=$3 | |||||
| ts=$(date +%s) | |||||
| sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1) | |||||
| curl -X ${method} -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" https://eu.api.ovh.com/1.0${url} | |||||
| } | |||||
| # Creation request | |||||
| oldResult=$(send GET "/domain/zone/${topDomain}/record?fieldType=TXT&subDomain=${subDomain}" ""|sed -e 's/\[//' -e 's/\]//') | |||||
| for num in ${oldResult//,/ } | |||||
| do | |||||
| send DELETE "/domain/zone/${topDomain}/record/${num}" "" | |||||
| done | |||||
| # Refresh request | |||||
| send POST /domain/zone/${topDomain}/refresh "" | |||||
| #!/bin/bash | |||||
| domains=($(echo "$1"|sed -e 's/^\(\([a-zA-Z0-9.-]*\?\)\.\)*\([a-zA-Z0-9-]\+\.[a-zA-Z-]\+\)$/"\1" _acme-challenge.\2 \3/g')) | |||||
| #challenge="$2" | |||||
| # Please, do not forget to ask for your credentials at https://eu.api.ovh.com/createToken/ | |||||
| # permissions needed are /domain/zone/* in GET,POST,DELETE | |||||
| applicationKey="YourAK" | |||||
| applicationSecret="YourAS" | |||||
| consumerKey="YourCK" | |||||
| topDomain=${domains[2]} | |||||
| subDomain=${domains[1]%%.} | |||||
| function send | |||||
| { | |||||
| method=$1 | |||||
| url=$2 | |||||
| body=$3 | |||||
| ts=$(date +%s) | |||||
| sign=\$1\$$(echo -n "${applicationSecret}+${consumerKey}+${method}+https://eu.api.ovh.com/1.0${url}+${body}+${ts}"|sha1sum|cut -d" " -f1) | |||||
| curl -X "${method}" -H "Content-Type: application/json" -H "X-Ovh-Application: ${applicationKey}" -H "X-Ovh-Timestamp: ${ts}" -H "X-Ovh-Signature: ${sign}" -H "X-Ovh-Consumer: ${consumerKey}" -d "${body}" "https://eu.api.ovh.com/1.0${url}" | |||||
| } | |||||
| # Creation request | |||||
| oldResult=$(send GET "/domain/zone/${topDomain}/record?fieldType=TXT&subDomain=${subDomain}" ""|sed -e 's/\[//' -e 's/\]//') | |||||
| for num in ${oldResult//,/ } | |||||
| do | |||||
| send DELETE "/domain/zone/${topDomain}/record/${num}" "" | |||||
| done | |||||
| # Refresh request | |||||
| send POST "/domain/zone/${topDomain}/refresh" "" | |||||
| @ -0,0 +1,418 @@ | |||||
| #!/bin/bash | |||||
| # Copyright (2017) Timothe Litt litt at acm _dot org | |||||
| VERSION="1.0.1" | |||||
| PROG="`basename $0`" | |||||
| # This script is used to update TXT records in GoDaddy DNS server | |||||
| # It depends on JSON.sh from https://github.com/dominictarr/JSON.sh | |||||
| # Place it in (or softlink it to) the same directory as this script, | |||||
| # or specify its location with GODADDY_JSON | |||||
| # | |||||
| # See the usage text below, 00GoDaddy-README.txt, dns_add_godaddy | |||||
| # and dns_del_godaddy for additional information. | |||||
| # | |||||
| # It 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. | |||||
| # | |||||
| # Bug reports are welcome at https://github.com/tlhackque/getssl/issues. | |||||
| API='https://api.godaddy.com/v1/domains' | |||||
| APISIGNUP='https://developer.godaddy.com/getstarted' | |||||
| GETJSON='https://github.com/dominictarr/JSON.sh' | |||||
| VERB="y" | |||||
| DEBUG="$GODADDY_DEBUG" | |||||
| [ -z "$JSON" ] && JSON="$GODADDY_JSON" | |||||
| [ -z "$JSON" ] && JSON="`dirname $0`/JSON.sh" | |||||
| while getopts 'dhj:k:s:t:qv' opt; do | |||||
| case $opt in | |||||
| d) DEBUG="Y" ;; | |||||
| j) JSON="$OPTARG" ;; | |||||
| k) GODADDY_KEY="$OPTARG" ;; | |||||
| s) GODADDY_SECRET="$OPTARG" ;; | |||||
| t) TRACE="$OPTARG" ;; | |||||
| q) VERB= ;; | |||||
| v) echo "dns_godaddy version $VERSION"; exit 0 ;; | |||||
| *) | |||||
| 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 | |||||
| Add or delete TXT records from GoDaddy DNS | |||||
| Obtain the Key and Secret from $APISIGNUP | |||||
| You must obtain a "Production" key - NOT the "Test" key you're required | |||||
| to get first. | |||||
| With getssl, this script is called from the dns_add_godaddy and | |||||
| dns_del_godaddy wrapper scripts. | |||||
| Arguments: | |||||
| add - add the specified record to the domain | |||||
| 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. | |||||
| data is the record data, e.g. "myverificationtoken" | |||||
| ttl is optional, and defaults to the GoDaddy minimum of 600. | |||||
| If it is necessary to turn on debugging externally, define | |||||
| GODADDY_DEBUG="y" (any non-null string will do). | |||||
| For minimal trace output (to override -q), define GODADDY_TRACE="y". | |||||
| Options | |||||
| -d Provide debugging output - all requests and responses | |||||
| -h This help. | |||||
| -j: Location of JSON.sh Default `dirname $0`/JSON.sh, or | |||||
| the GODADDY_JSON variable. | |||||
| -k: The GoDaddy API key Default from GODADDY_KEY | |||||
| -s: The GoDaddy API secret Default from GODADDY_SECRET | |||||
| -t: Detailed protocol trace data is appended to specified file | |||||
| -q Quiet - omit normal success messages, | |||||
| All output, except for this help text, is to stderr. | |||||
| Environment variables | |||||
| GODADDY_JSON location of the JSOH.sh script | |||||
| GODADDY_KEY default API key | |||||
| GODADDY_SCRIPT location of this script, default location of JSON.sh | |||||
| GODADDY_SECRET default API secret | |||||
| GODADDY_TRACE forces -q off if true | |||||
| GODADDY_TFILE appends protocol trace to file. Overrides -t | |||||
| BUGS | |||||
| Due to a limitation of the gOdADDY API, deleting the last TXT record | |||||
| would be too risky for my taste. So in that case, I replace it with | |||||
| _dummy.record_.domain. TXT "Ihis record is not used". This record is | |||||
| not automatically deleted by this script, though it's perfectly OK to | |||||
| do so manually. (Via another mechanism, or if it's no longer the last.) | |||||
| This really shouldn't happen often, since most domains have at least | |||||
| one TXT record - for SPF, tracking APIs, etc. | |||||
| Report any issues to https://github.com/tlhackque/getssl/issues | |||||
| EOF | |||||
| exit 0 | |||||
| ;; | |||||
| esac | |||||
| done | |||||
| shift $((OPTIND-1)) | |||||
| # Check for JSON -- required for delete, but if records are added, | |||||
| # we assume they'll be deleted later & don't want to strand them. | |||||
| [[ "$JSON" =~ ^~ ]] && \ | |||||
| eval 'JSON=`readlink -nf ' $JSON '`' | |||||
| if [ ! -x "$JSON" ]; then | |||||
| cat <<EOF >&2 | |||||
| $0: requires JSON.sh as "$JSON" | |||||
| The full path to JSON.sh can be specified with -j, or the | |||||
| GODADDY_JSON environment variable. | |||||
| You can obtain a copy from $GETJSON | |||||
| Then place or softlink it to $JSON or set GODADDY_JSON. | |||||
| EOF | |||||
| exit 2 | |||||
| fi | |||||
| if [ -z "$GODADDY_KEY" ] || [ -z "$GODADDY_SECRET" ]; then | |||||
| echo "GODADDY_KEY and GODADDY secret must be defined" >&2 | |||||
| exit 3 | |||||
| fi | |||||
| [ -n "$DEBUG" ] && VERB="y" | |||||
| [ -n "$GODADDY_TRACE" ] && VERB="Y" | |||||
| [ -n "$GODADDY_TFILE" ] && TRACE="$GODADDY_TFILE" | |||||
| # Get parameters & validate | |||||
| op="$1" | |||||
| 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" | |||||
| if [ -z "$name" ]; then | |||||
| echo "'name' parameter is required, see -h" >&2 | |||||
| exit 3 | |||||
| fi | |||||
| ! [[ "$name" =~ [.]$ ]] && name="${name}.${domain}." | |||||
| data="$4" | |||||
| if [ -z "$data" ]; then | |||||
| echo "'data' parameter is required, see -h" >&2 | |||||
| exit 3 | |||||
| fi | |||||
| if [ "$op" = 'del' ]; then | |||||
| ttl= | |||||
| elif [ -z "$5" ]; then | |||||
| ttl="600" # GoDaddy minimum TTL is 600 | |||||
| elif ! [[ "$5" =~ ^[0-9]+$ ]]; then | |||||
| echo "TTL $5 is not numeric" >&2 | |||||
| exit 3 | |||||
| elif [ $5 -lt 600 ]; then | |||||
| [ -n "$VERB" ] && \ | |||||
| echo "$5 is less than GoDaddy minimum of 600; increased to 600" >&2 | |||||
| ttl="600" | |||||
| else | |||||
| ttl="$5" | |||||
| fi | |||||
| # --- Done with parameters | |||||
| [ -n "$DEBUG" ] && \ | |||||
| echo "$PROG: $op $domain $name \"$data\" $ttl" >&2 | |||||
| # Authorization header has secret and key | |||||
| authhdr="Authorization: sso-key $GODADDY_KEY:$GODADDY_SECRET" | |||||
| if [ -n "$TRACE" ]; then | |||||
| function timestamp { local tm="`LC_TIME=C date '+%T.%N'`" | |||||
| local class="$1"; shift | |||||
| echo "${tm:0:15} ** ${class}: $*" >>"$TRACE" | |||||
| } | |||||
| timestamp 'Info' "$PROG" "V$VERSION" 'Starting new protocol trace' | |||||
| timestamp 'Args' "$@" | |||||
| curl --help | grep -q -- --trace-time && CURL_TFLAGS="--trace-time" # 7.14.0 | |||||
| function curl { | |||||
| command curl ${CURL_TFLAGS} --trace-ascii % "$@" 2>>"$TRACE" | |||||
| } | |||||
| [ -n "$VERB" ] && echo "Appending protocol trace to $TRACE" | |||||
| fi | |||||
| [ -n "$DEBUG" ] && echo "$authhdr" >&2 | |||||
| if [ "$op" = "add" ]; then | |||||
| # May need to retry due to zone cuts | |||||
| while [[ "$domain" =~ [^.]+\.[^.]+ ]]; do | |||||
| url="$API/$domain/records/TXT/$name" | |||||
| request='{"data":"'$data'","ttl":'$ttl'}' | |||||
| [ -n "$DEBUG" ] && cat >&2 <<EOF | |||||
| Add 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 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 | |||||
| fi | |||||
| echo "Request failed $msg" >&2 | |||||
| exit 1 | |||||
| fi | |||||
| [ -n "$VERB" ] && echo "$domain: added $name $ttl TXT \"$data\"" >&2 | |||||
| exit 0 | |||||
| done | |||||
| 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 | |||||
| current="$(curl -i -s -X GET --config - "$url" <<EOF | |||||
| header = "$authhdr" | |||||
| EOF | |||||
| )" | |||||
| sts=$? | |||||
| if [ $sts -ne 0 ]; then | |||||
| echo "curl error $sts for query" >&2 | |||||
| exit $sts | |||||
| fi | |||||
| [ -n "$DEBUG" ] && cat >&2 <<EOF | |||||
| 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. | |||||
| 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"')"' | |||||
| 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 | |||||
| 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 | |||||