#!/bin/bash # This script is for secure DDNS updates using GSS/TSIG # Version: 0.1 ## CONFIGURATION ## # Kerberos realm REALM="EXAMPLE.COM" # Kerberos principal PRINCIPAL="dns-update@${REALM}" # Kerberos keytab KEYTAB="/etc/krb5.dhcpd.keytab" # Kerberos credentials cache KRB5CC="/var/run/isc-dhcpd/dhcpd.krb5cc" # Use MIT kerberos args instead of heimdal. #KRB5MIT="YES" # Domain appended to hostname DOMAIN="example.com" # Space separated list of DNS servers for sending updates to NSRVS="ns1.example.com ns2.example.com" # Default DNS resource records TTL RRTTL="3600" # Do not use TXT RRs (rfc4701) NOTXTRRS="YES" # Default to IPv4 PROTO="IPv4" # Additional nsupdate flags (-g already applied), e.g. "-d" for debug #NSUPDFLAGS="-d" # Run in the foreground (for manual run only!!!), it's better to use "-d" as script's first argument #DEBUG="YES" ###################################################### _usage() { if [ "${NOTXTRRS}" == "YES" ] then echo "Usage:" echo "`basename ${0}` add ip-address -h hostname [-m dhcid|mac-address] [-p IP version | --ipv4 | --ipv6] [-t dns-ttl] [-d]" echo "`basename ${0}` delete ip-address [-m dhcid|mac-address] [-p IP version | --ipv4 | --ipv6]" else echo "Usage:" echo "`basename ${0}` add ip-address -h hostname -m dhcid|mac-address [-p IP version | --ipv4 | --ipv6] [-t dns-ttl] [-d]" echo "`basename ${0}` delete ip-address -m dhcid|mac-address [-p IP version | --ipv4 | --ipv6]" fi } ## VARIABLES ## ACTION=${1} IP=${2} shift && shift while [ ! -z ${1} ] do case "${1}" in "-d") DEBUG="YES" shift ;; "-m") DHCID=${2} shift && shift ;; "-h") NAME=${2%%.*} shift && shift ;; "-t") RRTTL="${2}" shift && shift ;; "-p") PROTO=${2} shift && shift ;; "--ipv4") PROTO="IPv4" shift ;; "--ipv6") PROTO="IPv6" shift ;; "--help") _usage exit 0 ;; *) echo "Unknown option: ${1}" _usage exit 1 esac done _kerberos() { export KRB5_KTNAME="${KEYTAB}" export KRB5CCNAME="${KRB5CC}" if [ "${KRB5MIT}" = "YES" ]; then KLISTARG="-s" else KLISTARG="-t" fi klist ${KLISTARG} || kinit -k -t "${KEYTAB}" -c "${KRB5CC}" "${PRINCIPAL}" || { echo "DDNS: kinit failed"; exit 1; } } _expandv6() { # Adapted from https://github.com/mutax/IPv6-Address-checks by # Florian Streibelt local __revipv6=${2} local INPUT="${1}" local O="" while [ "${O}" != "${INPUT}" ]; do O="${INPUT}" # fill all words with zeroes INPUT="$( sed 's|:\([0-9a-f]\{3\}\):|:0\1:|g' <<< "${INPUT}" )" INPUT="$( sed 's|:\([0-9a-f]\{3\}\)$|:0\1|g' <<< "${INPUT}")" INPUT="$( sed 's|^\([0-9a-f]\{3\}\):|0\1:|g' <<< "${INPUT}" )" INPUT="$( sed 's|:\([0-9a-f]\{2\}\):|:00\1:|g' <<< "${INPUT}")" INPUT="$( sed 's|:\([0-9a-f]\{2\}\)$|:00\1|g' <<< "${INPUT}")" INPUT="$( sed 's|^\([0-9a-f]\{2\}\):|00\1:|g' <<< "${INPUT}")" INPUT="$( sed 's|:\([0-9a-f]\):|:000\1:|g' <<< "${INPUT}")" INPUT="$( sed 's|:\([0-9a-f]\)$|:000\1|g' <<< "${INPUT}")" INPUT="$( sed 's|^\([0-9a-f]\):|000\1:|g' <<< "${INPUT}")" done # now expand the :: grep -qs "::" <<< "${INPUT}" if [ "$?" -eq 0 ]; then GRPS="$(sed 's|[0-9a-f]||g' <<< "${INPUT}" | wc -m)" ((GRPS--)) # carriage return ((MISSING=8-GRPS)) for ((i=0;i<$MISSING;i++)); do ZEROES="$ZEROES:0000" done # be careful where to place the : INPUT="$( sed 's|\(.\)::\(.\)|\1'$ZEROES':\2|g' <<< "${INPUT}")" INPUT="$( sed 's|\(.\)::$|\1'$ZEROES':0000|g' <<< "${INPUT}")" INPUT="$( sed 's|^::\(.\)|'$ZEROES':0000:\1|g;s|^:||g' <<< "${INPUT}")" fi # an expanded address has 39 chars + CR if [ $(echo ${INPUT} | wc -m) != 40 ]; then echo "Invalid IPv6 Address" exit 1 fi LEN=${#INPUT} for ((i=LEN-1;i>=0;i--)) do [ ${INPUT:i:1} != ":" ] && TMPSTR=${TMPSTR}"."${INPUT:i:1} done INPUT=${TMPSTR:1}".ip6.arpa" # echo the fully expanded version of the address eval ${__revipv6}="'${INPUT}'" } _main() { umask 77 if [ -z "${IP}" ] then _usage exit 1 fi if [ "${NOTXTRRS}" != "YES" ] && [ -z "${DHCID}" ] then _usage exit 1 fi ## NSUPDATE ## case "${ACTION}" in add) if [ -z "${NAME}" ] then _usage exit 1 fi RRPTR="${NAME}.${DOMAIN}" if [ "${NOTXTRRS}" != "YES" ]; then NOTXTRRS="" RRAOLD=`host ${RRPTR} | awk '/has address/ {print $4}'` if [ -n "${RRAOLD}" ]; then RRTXTOLD=`host -t txt "${RRPTR}" | sed -n '/descriptive text/s/^.*[[:space:]]descriptive text[[:space:]]*"\(.*\)"$/\1/p'` [ -z "${RRTXTOLD}" ] && echo "DDNS: adding records for $ip (${RRPTR}) FAILED: has A record but no DHCID, not mine" && exit 1 RRTXT=`echo "${DHCID}${RRPTR}" | sha256sum` RRTXT="000101${RRTXT%% *}" [ "${RRTXT}" != "${RRTXTOLD}" ] && echo "DDNS: adding records for $ip (${RRPTR}) FAILED: has A record but DHCID is wrong" && exit 1 else RRTXT=`echo "${DHCID}${RRPTR}" | sha256sum` RRTXT="000101${RRTXT%% *}" fi else NOTXTRRS=";" fi echo "${PROTO}!" case "${PROTO}" in "IPv4") RRPTRNAME=`echo ${IP} | awk -F '.' '{print $4"."$3"."$2"."$1".in-addr.arpa"}'` RECNAME="A" ;; "IPv6") _expandv6 ${IP} RRPTRNAME RECNAME="AAAA" ;; *) echo "Unknown IP version: ${PROTO}" exit 1 esac _kerberos for NSRV in ${NSRVS}; do nsupdate -g ${NSUPDFLAGS} << UPDATE server ${NSRV} realm ${REALM} update delete ${RRPTR}. ${RRTTL} ${RECNAME} ${NOTXTRRS}update delete ${RRPTR}. ${RRTTL} TXT ${NOTXTRRS}update add ${RRPTR}. ${RRTTL} TXT ${RRTXT} update add ${RRPTR}. ${RRTTL} ${RECNAME} ${IP} send update delete ${RRPTRNAME}. ${RRTTL} PTR update add ${RRPTRNAME}. ${RRTTL} PTR ${NAME}.${DOMAIN}. send UPDATE RESULT=${?} [ "${RESULT}" -eq "0" ] && echo "DDNS: adding records for ${IP} (${RRPTR}) succeeded" && exit 0 done [ "${RESULT}" != "0" ] && echo "DDNS: adding records for ${IP} (${RRPTR}) FAILED: nsupdate status ${RESULT}" && exit "${RESULT}" ;; delete) RRPTR=`host ${IP} | awk '/domain name pointer/ { sub(/\.$/, "", $5); print $5}'` if [ "${NOTXTRRS}" != "YES" ]; then NOTXTRRS="" if [ -n "${RRPTR}" ]; then RRTXTOLD=`host -t txt "${RRPTR}" | sed -n '/descriptive text/s/^.*[[:space:]]descriptive text[[:space:]]*"\(.*\)"$/\1/p'` [ -z "${RRTXTOLD}" ] && echo "DDNS: removing records for $ip (${RRPTR}) FAILED: has A record but no DHCID, not mine" && exit 1 RRTXT=`echo "${DHCID}${RRPTR}" | sha256sum` RRTXT="000101${RRTXT%% *}" [ "${RRTXT}" != "${RRTXTOLD}" ] && echo "DDNS: removing records for ${IP} (${RRPTR}) FAILED: has A record but DHCID is wrong" && exit 1 else echo "DDNS: removing records for ${IP} FAILED: has no PTR, can not determine A record" && exit 1 fi else NOTXTRRS=";" fi case "${PROTO}" in "IPv4") RRPTRNAME=`echo ${IP} | awk -F '.' '{print $4"."$3"."$2"."$1".in-addr.arpa"}'` RECNAME="A" ;; "IPv6") _expandv6 ${IP} RRPTRNAME RECNAME="AAAA" ;; *) echo "Unknown IP version: ${PROTO}" exit 1 esac _kerberos for NSRV in ${NSRVS}; do nsupdate -g ${NSUPDFLAGS} << UPDATE server ${NSRV} realm ${REALM} update delete ${RRPTR}. ${RRTTL} ${RECNAME} ${NOTXTRRS}update delete ${RRPTR}. ${RRTTL} TXT send update delete ${RRPTRNAME}. ${RRTTL} PTR send UPDATE RESULT=${?} [ "${RESULT}" -eq "0" ] && echo "DDNS: removing records for ${IP} (${RRPTR}) succeeded" && exit 0 done [ "${RESULT}" != "0" ] && echo "DDNS: removing records for ${IP} (${RRPTR}) FAILED: nsupdate status ${RESULT}" && exit "${RESULT}" ;; *) _usage && exit 1 ;; esac } if [ "$DEBUG" = "YES" ]; then _main else : _main | logger -s -t dhcpd & fi