#TODO: Make the creation and management of reverse zones easier. #TODO: Make the 'zones' file easier to manage. Automate creation, editing and removal of entries. RAND=$(cat /dev/urandom|tr -cd "[:alnum:]"|head -c 8) FILETOEDIT="" ORIGDIR="/usr/local/etc/namedb" SUBDIR="" NAMEDRC="/usr/local/etc/rc.d/named" CHECKBIN="/usr/local/sbin/named-checkconf" CHECKRUN="$CHECKBIN /tmp/named.$RAND" UPDATE_SOA=0 USAGE="wdns - Small bash script for maintaining BIND9 configs. # wdns [-h] - Show this help text # wdns named.conf - Edit the master named config # wdns zones - Edit master zones config # wdns domain.name - Edit zonefile for domain.name # wdns -d domain.name - Delete zonefile for domain.name # wdns -l - list all zones # wdns -n domain.name - create new zonefile for domain.name # wdns -nz domain.name - recreate blank zonefile for domain.name # wdns -s domain.name - secure domain using DNSSEC # wnds -rs domain.name - resign DNSSEC-secured zone" if [ "$1" == "" ]; then echo "$USAGE" return 1 elif [ "$1" == "named.conf" ]; then #Edit the master config by default echo "No zone specified, editing named.conf." FILETOEDIT="named.conf" elif [ "$1" == "-h" ]; then echo "$USAGE" return 0 elif [ "$1" == "-d" ]; then if [ "$2" == "" ]; then echo "$USAGE" return 1 fi echo "Deleting zone for $2." if [ ! -f $ORIGDIR/master/$2 ]; then echo "Error: Zonefile for $2 doesn't exist!" return 1 fi rm $ORIGDIR/master/$2 if [ $? -ne 0 ]; then echo "Error: Failed to delete zonefile $ORIGDIR/master/$2." return 1 fi return 0 elif [ "$1" == "-l" ]; then echo "The following forward zones exist:" ls -l $ORIGDIR/master|egrep -v "\.db$|new.domain|\.last|\.arpa|\.signed|\.jbk|\.jnl|\.signed\.jnl$|^total "|awk '{print $9}'|sort echo echo "The following reverse zones exist:" ls -l $ORIGDIR/master/|egrep "\.arpa$"|egrep -v "\.last|\.signed$"|awk '{print $9}'|sort return 0 elif [ "$1" == "-n" ]; then if [ "$2" == "" ]; then echo "$USAGE" return 1 fi echo "Creating new zone for $2." if [ -f $ORIGDIR/master/$2 ]; then echo "Error: Zonefile for $2 already exists. Use -nz to overwrite with new zone!" return 1 fi #TODO: Validate input. cat $ORIGDIR/master/new.domain|sed "s/new\.domain/$2/g">$ORIGDIR/master/$2 if [ $? -ne 0 ]; then echo "Error: Failed to copy zone template to $ORIGDIR/master/$2" return 1 fi echo "Zone for $2 created. Run the following on all slaves:" #TODO: Add zone to slaves read -p "Hit return to open it for editing, or Ctrl-C to exit." wdns $2 return 0 elif [ "$1" == "-nz" ]; then if [ "$2" == "" ]; then echo "$USAGE" return 1 fi echo "Recreating zone for $2." if [ ! -f $ORIGDIR/master/$2 ]; then echo "Error: Zonefile for $2 does not exist. Use -n to create a new zone!" return 1 fi #TODO: Validate input. cat $ORIGDIR/master/new.domain|sed "s/new\.domain/$2/g">$ORIGDIR/master/$2 if [ $? -ne 0 ]; then echo "Error: Failed to copy zone template to $ORIGDIR/master/$2" return 1 fi read -p "Zone recreated. Hit return to open it for editing, or Ctrl-C to exit." wdns $2 return 0 elif [ "$1" == "-s" ]; then if [ "$2" == "" ]; then echo "$USAGE" return 1 fi if [ ! -f $ORIGDIR/master/$2 ]; then echo "Error: Zonefile for $2 does not exist. Use -n to create a new zone!" return 1 fi ZONE=$2 DNSSEC_DIR="$ORIGDIR/keys" DSKEYG_MSG="$(dnssec-keygen -f KSK -a RSASHA256 -b 2048 -K $DNSSEC_DIR -n ZONE $ZONE. 2>&1)" if [ $? -ne 0 ]; then echo "DNSSEC signing key generation failed! Error: $DSKEYG_MSG" return 1 fi DSKEYG_MSG="$(dnssec-keygen -a RSASHA256 -b 2048 -K $DNSSEC_DIR -n ZONE $ZONE. 2>&1)" if [ $? -ne 0 ]; then echo "DNSSEC zone signing key generation failed! Error: $DSKEYG_MSG" return 1 fi echo "DNSSEC key generation complete. Add the following to the zone configuration in zones.conf: key-directory \"$ORIGDIR/keys\"; auto-dnssec maintain; inline-signing yes;" && echo read -p "Press enter to open named.conf.zones for editing, or Ctrl-C to exit and add these later." wdns zones return 0 elif [ "$1" == "-rs" ]; then if [ "$2" == "" ]; then echo "$USAGE" return 1 fi if [ ! -f $ORIGDIR/master/$2 ]; then echo "Error: Zonefile for $2 does not exist. Use -n to create a new zone!" return 1 fi ZONE=$2 DNSSEC_DIR="$ORIGDIR/dnssec" if [ ! -f $ORIGDIR/master/$ZONE.signed ]; then echo "Zone $ZONE doesn't appear to be DNSSEC-enabled! Use -s to sign an unsigned zone!" return 1 fi if [ ! -f $DNSSEC_DIR/K$ZONE.KSK.key -o ! -f $DNSSEC_DIR/K$ZONE.ZSK.key ]; then echo "Signing keys for $ZONE don't exist! Use -s to sign an unsigned zone!" return 1 fi DNSSEC_OUT="$(dnssec-signzone -o $ZONE -d $DNSSEC_DIR -K $DNSSEC_DIR -k $DNSSEC_DIR/K$ZONE.KSK.key $ORIGDIR/master/$ZONE $DNSSEC_DIR/K$ZONE.ZSK.key 2>&1)" if [ $? -ne 0 ]; then echo "DNSSEC signing failed! Error: $DNSSEC_OUT" return 1 fi echo "Zone $ZONE resigned." #Reload zones echo "Reloading named" /etc/rc.d/named reload if [ $? -ne 0 ]; then echo "Error while reloading named!" return 1 fi return 0 else #Check if we want to edit the master zones config if [ "$1" == "zones" ]; then echo "Editing zones.conf." FILETOEDIT="/named.conf.zones" else #Edit a specific zonefile if [ ! -f $ORIGDIR/master/$1 ]; then #Error out if the zone doesn't exist. Zones should be created using the newzone function echo "Zone for $1 doesn't exist!" return 1 fi echo "Editing zonefile for $1." SUBDIR="/master/" FILETOEDIT="$1" CHECKBIN="/usr/local/sbin/named-checkzone" CHECKRUN="$CHECKBIN $1 /tmp/named.$RAND" UPDATE_SOA=1 fi fi if [ $UPDATE_SOA -eq 1 ]; then #Update SOA SOA="$(cat "$ORIGDIR$SUBDIR$FILETOEDIT"|grep "; Serial"|sed -E 's/;.*//g;s/[[:space:]]*//g')" SOA_VERSION="$(echo $SOA|tail -c2)" SOA_DATE="$(echo $SOA|head -c8)" SOA_NEW_DATE="$(date "+%Y%m%d")" if [[ "$SOA_DATE" == "$SOA_NEW_DATE" ]]; then SOA_VERSION="$(echo "$SOA_VERSION+1"|bc)" if [ "$(echo "$(echo "$SOA_VERSION"|wc -c|tr -cd '[:alnum:]')-1"|bc)" == "1" ]; then SOA_VERSION="0$SOA_VERSION" fi else SOA_VERSION="00" SOA_DATE="$SOA_NEW_DATE" fi cat "$ORIGDIR$SUBDIR$FILETOEDIT"|sed "s/$SOA/$SOA_DATE$SOA_VERSION/">/tmp/named.$RAND else cp "$ORIGDIR$SUBDIR$FILETOEDIT" /tmp/named.$RAND fi #Generate MD5 sum of the original file ORIGHASH=$(md5 -q /tmp/named.$RAND) for (( ; ; )); do #Edit zone vim /tmp/named.$RAND NEWHASH=$(md5 -q /tmp/named.$RAND) if [ "$ORIGHASH" == "$NEWHASH" ]; then echo "No changes made to $FILETOEDIT." return 0 fi echo -n "Checking $FILETOEDIT for errors... " CHECK_OUT=$($CHECKRUN) if [ $? -eq 0 ]; then #Changes cleared named-checkzone echo "No errors." #Make a backup of the previous known-good zone, just in case cp "$ORIGDIR$SUBDIR$FILETOEDIT" "$ORIGDIR$SUBDIR$FILETOEDIT.last" #Move the new zone in over the old one mv /tmp/named.$RAND "$ORIGDIR$SUBDIR$FILETOEDIT" #Reload zones echo "Reloading named" $NAMEDRC reload if [ $? -eq 0 ]; then break else echo "Error while reloading named!" return 1 fi else #Changes didn't clear named-checkzone echo "Errors found." #Output the errors, wait for the user to hit enter, then the loop starts all over. echo "$CHECKRUN_OUT" read -p "Press enter to reopen $FILETOEDIT for editing" fi done if [ -f /tmp/named.$RAND ]; then rm /tmp/named.$RAND fi