#!/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 <&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 <&2 <&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" <&2 exit $sts fi [ -n "$DEBUG" ] && cat >&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 <&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 <&2 <&2 <&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