#!/bin/sh ############################################################## # # # Written 6/18/08 by Jeff Schroeder # # ############################################################## # # # # # bindconfsync-slave.sh - Sync the named.conf from a master # # # dns server and keep the machine # # # specific configuration changes # # # # # ############################################################## # $Id: bindconfsync-slave.sh 68068 2008-06-23 17:05:47Z jschroeder $ # New implementation and new ideas. This is a complete rewrite # of glenn's bindconfsync-slave.sh MASTER_DNS=master.dns.server.here CHROOT=/var/named/chroot # Unset this variable for non-chrooted bind NAMED_CONF=${CHROOT}/etc/named.conf MASTER_DNS_VIP=$(host -t A $MASTER_DNS | awk '{print $NF}') PATH=/bin:/sbin:/usr/bin:/usr/sbin # Only what we need MAGIC_COMMENT='^\/\* This is the end of the machine specific configuration \*\/$' #/* This is the end of the machine specific configuration */ # Make it easy to see which host the error is coming from error() { errorfile="$(echo $1 | awk '{print $1}')" if [ -e "$errorfile" -a $# -eq 1 ]; then oldifs="$IFS" # Let the shell split on newlines. Similar to python's splitlines() IFS=" " # Print every line in the error file for line in `cat $errorfile`; do # Make errors show up in bold. tput is weird and needs # to be called within the loop or not all lines bold tput bold echo "${HOSTNAME:-$(hostname -f)}: $line" tput init done IFS="$oldifs" elif [ ! -z "$*" ]; then # Tput is weird and requires tput bold echo "${HOSTNAME:-$(hostname -f)}: $*" tput init else return 0 fi export ERROR=true } sanity_checks() { # Software to run that isn't always standard needs to be executable [ -x "$(which patch)" ] || error "/usr/bin/patch sanity check failure" [ -x "$(which named-checkconf)" ] || error "named-checkconf sanity check failure" # Places that need to be writable [ -w "$NAMED_CONF" ] || error "$NAMED_CONF not writable" [ -w "/tmp" ] || error "/tmp/not writable" [ -w "$CHROOT" ] || error "$CHROOT not writable" if (! grep -qs "$MAGIC_COMMENT" $NAMED_CONF); then error "magic comment not found, can not continue" fi if [ "$ERROR" = "true" ]; then error "exiting after sanity check failure" exit 1 fi } show_bind_config() { type=$1 conf=$2 shift; shift # This magic comment is required to be above the zone declarations start=$(($(grep -n "$MAGIC_COMMENT" $conf | awk -F: '{print $1}') - 1)) total=$(cat $conf | wc -l | awk '{print $NF}') # Keep the filename out of the wc output bottom=$(($total - $start)) top=$(($total - $bottom)) # If something goes wrong back out before making any changes at all if [ ${start:-0} -le 0 -o ${total:-0} -le 0 -o ${bottom:-0} -le 0 -o ${top:-0} -le 0 ]; then error "problem determining where the zone declarations start" exit 1 fi case "$type" in # Shows the machine specific configuration top) head -n $top $conf ;; # Shows the zone information zones) tail -n $bottom $conf | \ sed -e '/allow-update/d' \ -e "/type master/a\ masters { $MASTER_DNS_VIP; };" \ -e '/type/s/master/slave/' \ -e "/masters/s/10.103.130.100/$MASTER_DNS_VIP/g" ;; esac } # Creates a new config while preserving local changes create_new_config() { tmp_dir=$(mktemp -d /tmp/bind-slavesync.XXXXXXXX) final="${tmp_dir}/named.conf" local="${tmp_dir}/named.conf.local" remote="${tmp_dir}/named.conf.remote" patch="/tmp/.final.patch" # RHEL3 doesn't supports these ssh options if (test -e /etc/redhat-release && \ grep -qs "Red Hat Enterprise Linux ES release 3" /etc/redhat-release); then ssh_options="" else ssh_options="-o ConnectTimeout=15" fi scp -q $ssh_options bindsync@${MASTER_DNS_VIP}:${NAMED_CONF} $remote cp $NAMED_CONF $local show_bind_config top $local > ${tmp_dir}/local.top show_bind_config zones $remote > ${tmp_dir}/remote.zones # If either of these files are empty it is VERY bad if [ ! -s "${tmp_dir}/local.top" ]; then error "could not determine the machine specific configuration" exit 1 elif [ ! -s "${tmp_dir}/remote.zones" ]; then error "could not determine the zone file configuration from $MASTER_DNS" exit 1 fi # Create the final named.conf and a patch to apply cat ${tmp_dir}/local.top >> $final cat ${tmp_dir}/remote.zones >> $final # Create a diff and patch the running named.conf. It is less # problematic if there are severa local changes that need to # be merged in this way diff -u $NAMED_CONF $final > $patch # Make sure the configuration is all gravy named-checkconf ${tmp_dir}/named.conf || { \ error "named-checkconf failed" rm -f $patch exit 1; } cd ${CHROOT}/etc rm -rf $tmp_dir apply_new_config "$patch" } # Given a patch, apply it to the running named.conf with sanity checking apply_new_config() { patch=$1 shift today=$(date +%Y%m%d) backup="${CHROOT}/etc/backups/named.conf-${today}" cd ${CHROOT}/etc mkdir -p ${CHROOT}/etc/backups/ # Try to be clever and not overwrite existing backup configurations unset i while [ -e $backup ]; do i=$(($i + 1)) if [ ! -e ${backup}.${i} ]; then break fi done if [ ! -z "$i" ]; then backup="${backup}.${i}" fi cp "$NAMED_CONF" "$backup" if (patch --dry-run -p0 < $patch > /dev/null 2>&1); then if [ "$DRYRUN" ]; then cat final.patch else patch -p0 < $patch > /dev/null fi # Make it easy to see what changed in the previous push mv -f $patch "$(dirname $backup)/previous.patch" else error "patching named.conf failed. Skipping" rm -f $patch exit 1 fi # Just to make sure chown named:named named.conf } # Support rsync style dryrun options to see what it will do if [ "$1" = "-n" -o "$1" = "--dryrun" -o "$1" = "--dry-run" ]; then DRYRUN=1 shift elif [ "$1" = "-h" -o "$1" = "--help" ]; then cat << EOF >&2 Usage: $(basename $0) [OPTION] -n/--dryrun - Show the diff between the local and master dns server to be applied. This argument is optional. EOF exit 1 fi ########################### MAIN PROGRAM STARTS HERE ########################### sanity_checks # The entire script would be a noop without this call to get the ball rolling create_new_config if [ -s "${CHROOT}/etc/backups/previous.patch" ]; then # After everything is done restart DNS output=/tmp/.named.out service named restart > $output 2>&1 if [ -s "$output" ]; then error $output fi rm -f $output else error "no named.conf changes to apply" fi