#!/usr/bin/env bash #rdns.sh - script for dealing with rDNS. #TODO #Add more _BIN variables to decrease reliance on $PATH #IPv6 support #Finish domain validation function #RDNS_QUERY_SERVER round-robin? #variables PROVIDERNAME="" DEFAULT_RDNS="hosted-by.$PROVIDERNAME." ZONE_LOCATION="/var/named/" ZONE_TAIL="in-addr.arpa" ZONE_FTAIL=".db" SYNC_SCRIPT="/scripts/dnscluster synczone" RDNS_QUERY_SERVER="8.8.4.4" DIG_BIN="$(command -v dig)" if [ $? -ne 0 -o -z "$DIG_BIN" ]; then echo "Failed to locate 'dig'." exit 1 fi #functions function usage () { # Usage function. Obvious. echo "$0 - Usage $0 ip.address : Show current rDNS for the given IP address. $0 ip.address dns.address : Set given IP address's rDNS to the given DNS address. $0 -v ip.address dns.address : Set given IP's rDNS, and verify it after syncing. $0 -nv ip.address dns.address : Set given IP's rDNS without checking forward DNS first. $0 -r ip.address : Reset given IP's rDNS to the default ($DEFAULT_RDNS). $0 -rS ip.address : Reset given IP's rDNS as -r does, but don't sync to DNS cluster. $0 -R ip.address : Remove the given IP's rDNS entry altogether. $0 -S ip.address : Sync the authoritative zone for the given IP address to the DNS cluster. $0 [-h|--help] : Show this help text." } function validateIP () { #validateIP - validates a given IP to ensure it isn't invalid. local IP=$1 local stat=1 #Regex to do basic IP validation. if [[ $IP =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then OIFS=$IFS IFS="." IP=($IP) IFS=$OIFS if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]; then stat=0 else stat=1 fi fi return $stat } function convertIPToDNSZone () { #Converts a given IP to an in-addr.arpa zone (ie, convertIPToDNSZone 192.210.132.5 would return "132.210.192.in-addr.arpa." OIFS=$IFS set `IFS=".";echo $1` IFS=$OIFS echo $3.$2.$1.$ZONE_TAIL } function getLastOctet () { #Gets the last octet of an IP. Since all our IP ranges are /24s, this is okay. OIFS=$IFS set `IFS=".";echo $1` IFS=$OIFS echo $4 } function validateDomain () { #Validate a given domain to ensure it's a real domain. #This will be implemented later. For now, just please make sure the PTR value is valid. return 0 } function setRDNS () { #Modify the rDNS record for the given IP. TARGET_IP=$1 NEW_PTR=$2 ZONEFILE=$(convertIPToDNSZone $TARGET_IP) RECORD=$(getLastOctet $TARGET_IP) if [ ! -w "$ZONE_LOCATION$ZONEFILE$ZONE_FTAIL" ]; then echo "Failed to locate: $ZONE_LOCATION$ZONEFILE$ZONE_FTAIL" return 1 fi CUR_REC=$(getRDNS $TARGET_IP) if [ $? -ne 0 ]; then #we create the record APPENDREC="$RECORD 14400 IN PTR $DEFAULT_RDNS" #This is disabled for now. We warn, but do not touch. echo "$APPENDREC" >> $ZONE_LOCATION$ZONEFILE$ZONE_FTAIL echo "Warning: Record for $TARGET_IP did not exist, new record has been created." CUR_REC=$(getRDNS $TARGET_IP) fi #I would use sed for this, but for some reason I can't get match groups to work in sed. perl -pi.bak -e "s/^($RECORD\s+.*)\s$CUR_REC/\1\t$NEW_PTR/" $ZONE_LOCATION$ZONEFILE$ZONE_FTAIL return 0 } function getRDNS () { #Retrieve the current rDNS record (if any) for the given IP ZONEFILE=$(convertIPToDNSZone $1) RECORD=$(getLastOctet $1) if [ ! -w "$ZONE_LOCATION$ZONEFILE$ZONE_FTAIL" ]; then echo "Failed to locate: $ZONE_LOCATION$ZONEFILE$ZONE_FTAIL" return 1 fi ZRECORD=$(egrep "^$RECORD\s.*PTR" $ZONE_LOCATION$ZONEFILE$ZONE_FTAIL) if [ $? -ne 0 ]; then echo "ZONEFILE: $ZONEFILE$ZONE_FTAIL ; LAST OCTET: $RECORD" return 1 fi echo $ZRECORD|perl -pe 's/^.*PTR\s+//' return 0 } function removeRDNS () { TARGET_IP=$1 ZONEFILE=$(convertIPToDNSZone $TARGET_IP) RECORD=$(getLastOctet $TARGET_IP) CUR_PTR="$(getRDNS $TARGET_IP)" if [ $? -ne 0 ]; then echo "rDNS entry doesn't exist for $TARGET_IP!" return 1 fi perl -pi.bak -e "s/^($RECORD\s+.*\n)//" $ZONE_LOCATION$ZONEFILE$ZONE_FTAIL } function verifyRDNS () { #Check that the rDNS was actually set, using 'host'. HOSTOUT=$($DIG_BIN @$RDNS_QUERY_SERVER +short -x $1|sed 's/^[\s ]+//'|sed 's/[\s ]+$//') if [ "$HOSTOUT" != "$2" ]; then echo $HOSTOUT return 1 fi return 0 } function checkForwardDNS () { #Verify that forward DNS is set properly, using 'dig'. This specifically requests an A record, we won't need to worry about AAAA records for a while. DIGOUT="$($DIG_BIN @$RDNS_QUERY_SERVER +short $1 A)" if [ "$DIGOUT" != "$2" ]; then return 1 fi return 0 } #main script MODE="" GOPTS="" IPADDR="" DESTDNS="" ZONE_FILE="" DO_SYNC=0 #Get options and arguments. I do this old school because I'm too lazy to l2getopts if [ "$1" == "-h" -o "$1" == "-r" -o "$1" == "-v" -o "$1" == "-R" -o "$1" == "-nv" -o "$1" == "-rS" -o "$1" == "-S" ]; then GOPTS="$1" IPADDR="$2" DESTDNS="$3" else IPADDR="$1" DESTDNS="$2" fi #First, we ensure that DESTDNS ends with a period. #We make sure the new PTR value ends with a period. if [ ! -z "$DESTDNS" ]; then TAILREC="$(echo $DESTDNS|tail -c2)" if [ "$TAILREC" != "." ]; then DESTDNS="$DESTDNS." fi fi #First we get usage out of the way. Usage is shown if there are no options, or if the only option given is -h, or if $IPADDR is empty. if [ -z "$IPADDR" -o "$GOPTS" == "-h" ]; then usage exit fi #Then we validate input. $IPADDR should -always- be an IP address, and $DESTDNS should always be a valid DNS address that exists. validateIP $IPADDR if [ $? -ne 0 ]; then echo "Error: IP address given ($IPADDR) was not valid." usage exit 1 fi #Now we can be sure the IP address given exists. Get our "mode" and then start working. case $GOPTS in -r) MODE="RESET" ;; -rS) MODE="RESETNOSYNC" ;; -R) MODE="REMOVE" ;; -S) MODE="NOOP" DO_SYNC="1" ;; -v) MODE="SETANDVALIDATE" ;; *) if [ ! -z "$DESTDNS" ]; then MODE="SET" elif [ ! -z "$IPADDR" ]; then MODE="GET" fi ;; esac if [ "$MODE" == "SET" -a "$GOPTS" == "-nv" ]; then MODE="SETNOVERIFY" fi #We do the work. case $MODE in RESET) setRDNS $IPADDR $DEFAULT_RDNS if [ $? -ne 0 ]; then echo "Failed to set rDNS!" exit 1 fi echo "rDNS set!" DO_SYNC=1 ;; RESETNOSYNC) setRDNS $IPADDR $DEFAULT_RDNS if [ $? -ne 0 ]; then echo "Failed to set rDNS!" exit 1 fi echo "rDNS set!" ;; GET) RDNS=$(getRDNS $IPADDR) if [ $? -ne 0 ]; then echo "Failed to get rDNS for '$IPADDR'! Message: $RDNS" exit 1 fi echo "rDNS for '$IPADDR': $RDNS" ;; SET) checkForwardDNS $DESTDNS $IPADDR if [ $? -ne 0 ]; then echo "Forward DNS does not match requested reverse!" exit 1 fi setRDNS $IPADDR $DESTDNS if [ $? -ne 0 ]; then echo "Failed to set rDNS!" exit 1 fi echo "rDNS set!" DO_SYNC=1 ;; SETNOVERIFY) checkForwardDNS $DESTDNS $IPADDR if [ $? -ne 0 ]; then echo "Warning: Forward DNS does not match requested reverse! Continuing anyway.." fi setRDNS $IPADDR $DESTDNS if [ $? -ne 0 ]; then echo "Failed to set rDNS!" exit 1 fi echo "rDNS set!" DO_SYNC=1 ;; SETANDVALIDATE) checkForwardDNS $DESTDNS $IPADDR if [ $? -ne 0 ]; then echo "Forward DNS does not match requested reverse!" exit 1 fi setRDNS $IPADDR $DESTDNS if [ $? -ne 0 ]; then echo "Failed to set rDNS!" exit 1 fi echo "rDNS set!" DO_SYNC=1 ;; REMOVE) removeRDNS $IPADDR if [ $? -ne 0 ]; then echo "Failed to remove rDNS record!" exit 1 fi echo "rDNS record removed!" DO_SYNC=1 ;; NOOP) ;; *) echo "Clearly, you used this script incorrectly." usage exit 1 ;; esac if [ $DO_SYNC -eq 1 ]; then $SYNC_SCRIPT $(convertIPToDNSZone $IPADDR) if [ $? -ne 0 ]; then echo "Sync failed!" fi if [ "$MODE" == "SETANDVALIDATE" ]; then sleep 5 FORWARDDNS=$(verifyRDNS $IPADDR $DESTDNS) if [ $? -ne 0 ]; then echo "Current rDNS for $IPADDR ($FORWARDDNS) does not match '$DESTDNS'!" else echo "rDNS was properly synchronised and is set to '$DESTDNS'." fi fi fi