#! /bin/sh
##
## $Id: control_rancid.in,v 1.64 2004/03/12 23:13:09 heas Exp $
##
## Copyright (C) 1997-2004 by Terrapin Communications, Inc.
## All rights reserved.
##
## This software may be freely copied, modified and redistributed
## without fee for non-commerical purposes provided that this license
## remains intact and unmodified with any RANCID distribution.
##
## There is no warranty or other guarantee of fitness of this software.
## It is provided solely "as is".  The author(s) disclaim(s) all
## responsibility and liability with respect to this software's usage
## or its effect upon hardware, computer systems, other software, or
## anything else.
##
## Except where noted otherwise, rancid was written by and is maintained by
## Henry Kilmer, John Heasley, Andrew Partan, Pete Whiting, and Austin Schutz.
##
#
# control_rancid $GROUP
#

# print a usage message to stderr
pr_usage() {
    echo "usage: $0 [-r device_name] [-m mail rcpt] [group [group ...]]" >&2;
}

# command-line options
# -r <device name>
# -m <mail recipients>
alt_mailrcpt=0
if [ $# -ge 1 ] ; then

    while [ 1 ] ; do
	case $1 in
	-r)
	    shift
	    # next arg is the device name
	    device="$1"
	    shift
	    ;;
	-m)
	    shift
	    # next arg is the mail recipient
	    alt_mailrcpt=1
	    if [ -z "$mailrcpt" ] ; then
		mailrcpt="$1"
	    else
		mailrcpt="$mailrcpt,$1"
	    fi
	    shift
	    ;;
	--)
	    shift; break;
	    ;;
	-h)
	    pr_usage
	    exit
	    ;;
	-*)
	    echo "unknown option: $1" >&2
	    pr_usage
	    exit 1
	    ;;
	*)
	    break;
	    ;;
	esac
    done
fi

# Must specify a group on which to run rancid
if [ $# -lt 1 ]; then
    echo 'must specify group'; exit 1
else
    GROUP=$1
fi
DIR=$BASEDIR/$GROUP
TMP=${TMPDIR:=/tmp}/rancid.$GROUP.$$
trap 'rm -fr $TMP;' 1 2 15

# the receipient(s) of diffs
mailrcpt=${mailrcpt:-"rancid-${GROUP}${MAILDOMAIN}"}; export mailrcpt
adminmailrcpt=${mailrcpt:-"rancid-admin-${GROUP}${MAILDOMAIN}"};
export adminmailrcpt

# Number of things par should run in parallel.
PAR_COUNT=${PAR_COUNT:-5}

# Bail if we do not have the necessary info to run
if [ ! -d $DIR ]
then
    echo "$DIR does not exist."
    echo "Run bin/rancid-cvs $GROUP to make all of the needed directories."
    (
	echo "To: $adminmailrcpt"
	echo "Subject: no $GROUP directory"
	echo "Precedence: bulk"
	echo ""
	echo "$DIR does not exist."
	echo "Run bin/rancid-cvs $GROUP to make all of the needed directories."
    ) | sendmail -t
    exit 1
fi

# do cvs update of router.db in case anyone has fiddled.
cd $DIR
cvs update router.db > $TMP 2>&1
grep "^C" $TMP > /dev/null
if [ $? -eq 0 ] ; then
    echo "There were CVS conflicts during update."
    echo ""
    cat $TMP
    rm -f $TMP
    exit 1
fi
rm -f $TMP

if [ ! -f $DIR/router.db ]
then
    (
	echo "To: $adminmailrcpt"
	echo "Subject: no $GROUP/router.db file"
	echo "Precedence: bulk"
	echo ""
	echo "$DIR/router.db does not exist."
    ) | sendmail -t
    exit 1;
fi

# generate the list of all, up, & down routers
cd $DIR
trap 'rm -fr routers.db routers.all.new routers.down.new routers.up.new \
	routers.mail routers.added routers.deleted $TMP;' 1 2 15
sed -e '/^#/d' -e 's/^ *//' -e 's/ *$//' -e 's/ *: */:/g' router.db | 
	sort -u > routers.db
cut -d: -f1,2 routers.db > routers.all.new
if [ ! -f routers.all ] ; then touch routers.all; fi
diff -U 4 routers.all routers.all.new > /dev/null 2>&1; RALL=$?
perl -F: -ane '{($F[0] =~ tr@A-Z@a-z@,print $_)
    if ($F[2] !~ /^up$/i);}' routers.db > routers.down.new
if [ ! -f routers.down ] ; then touch routers.down; fi
diff -U 4 routers.down routers.down.new > /dev/null 2>&1; RDOWN=$?
perl -F: -ane '{($F[0] =~ tr@A-Z@a-z@,print "$F[0]:$F[1]\n")
    if ($F[2] =~ /^up$/i);}' routers.db > routers.up.new
if [ ! -f routers.up ] ; then touch routers.up; fi
diff -U 4 routers.up routers.up.new > /dev/null 2>&1; RUP=$?

if [ $RALL -ne 0 -o $RDOWN -ne 0 -o $RUP -ne 0 ]
then
    (
	if [ $RUP -ne 0 ] ; then
	    if [ ! -s routers.up ] ; then
		echo Routers changed to up:
		sed -e 's/^/        /' routers.up.new
		echo
	    else
		WCUP=`comm -13 routers.up routers.up.new | wc -l | \
			sed -e 's/^ *\([^ ]*\)/\1/'`
		if [ $WCUP -gt 0 ] ; then
		    echo Routers changed to up:
		    comm -13 routers.up routers.up.new | sed -e 's/^/        /'
		    echo
		fi
	    fi
	fi
	if [ $RDOWN -ne 0 ] ; then
	    if [ ! -s routers.down ] ; then
		echo Routers changed to down:
		sed -e 's/^/        /' routers.down.new
		echo
	    else
		WCDOWN=`comm -13 routers.down routers.down.new | wc -l | \
			sed -e 's/^ *\([^ ]*\)/\1/'`
		if [ $WCDOWN -eq 1 ] ; then
	            echo Routers changed to down:
	            comm -13 routers.down routers.down.new | \
			sed -e 's/^/        /'
	            echo
		fi
	    fi
	fi
	if [ $RALL -eq 1 ] ; then
	    comm -13 routers.all routers.all.new | sed -e 's/^/        /' \
		> routers.added
	    comm -23 routers.all routers.all.new | sed -e 's/^/        /' \
		> routers.deleted

	    WCADDED=`wc -l routers.added | sed -e 's/^ *\([^ ]*\) .*$/\1/'`
	    WCDELETED=`wc -l routers.deleted | sed -e 's/^ *\([^ ]*\) .*$/\1/'`

	    if [ $WCADDED -gt 0 ]
	    then
	        echo Added routers:
	        cat routers.added
		echo
	    fi
	    if [ $WCDELETED -gt 0 ]
	    then
	        echo Deleted routers:
	        cat routers.deleted
		echo
	    fi

	    rm -f routers.added routers.deleted
	fi
    ) > routers.mail

    if [ -s routers.mail ] ; then
	(
	  echo "To: $adminmailrcpt"
	  echo "Subject: changes in $GROUP routers"
	  echo "Precedence: bulk"
	  echo ""
	  cat routers.mail
	) | sendmail -t
    fi
    rm -f routers.mail

    cd $DIR/configs

    # Add new routers to the CVS structure.
    for router in `comm -13 $DIR/routers.up $DIR/routers.up.new`
    do
	OFS=$IFS
	IFS=:
	set $router
	IFS=$OFS
	router=$1

	touch $router
	cvs add -ko $router
	cvs commit -m 'new router' $router
	echo "Added $router"
    done
    echo
    cd $DIR

fi
mv -f routers.all.new routers.all
if [ $? -ne 0 ]; then
    echo "Error: could not rename routers.all.new"
fi
mv -f routers.down.new routers.down
if [ $? -ne 0 ]; then
    echo "Error: could not rename routers.down.new"
fi
mv -f routers.up.new routers.up
if [ $? -ne 0 ]; then
    echo "Error: could not rename routers.up.new"
fi
rm -f routers.db
trap 'rm -fr $TMP;' 1 2 15

cd $DIR/configs
# check for 'up' routers missing in cvs.  no idea how this happens to some folks
for router in `cut -d: -f1 ../routers.up` ; do
    cvs status $router | grep -i 'status: unknown' > /dev/null 2>&1
    if [ $? -eq 0 ]; then
	touch $router
	cvs add -ko $router
	echo "CVS added missing router $router"
    fi
done
echo
# cvs delete configs for routers not listed in routers.up.
for router in `find . \( -name \*.new -prune -o -name CVS -prune \) -o -type f -print | sed -e 's/^.\///'` ; do
    grep -i "^$router:" ../router.db > /dev/null 2>&1
    if [ $? -eq 1 ]; then
	rm -f $router
	cvs delete $router
	cvs commit -m 'deleted router' $router
	echo "Deleted $router"
    fi
done
cd $DIR

# no routers, empty list or all 'down'
if [ ! -s routers.up ]
then
    # commit router.db
    cvs commit -m updates router.db > /dev/null
    exit;
fi

# if a device (-r) was specified, see if that device is in this group
if [ "X$device" != "X" ] ; then
    trap 'rm -fr $TMP $DIR/routers.single;' 1 2 15
    devlistfile="$DIR/routers.single"
    grep "^$device:" routers.up > $devlistfile
    if [ $? -eq 1 ] ; then
	exit;
    fi
else
    devlistfile="$DIR/routers.up"
fi

# Now we can actually try to get the configs
cd $DIR/configs

# The number of processes running at any given time can be
# tailored to the specific installation.
echo ""
echo "Trying to get all of the configs."
par -q -n $PAR_COUNT -c "rancid-fe \{}" $devlistfile

# This section will generate a list of missed routers
# and try to grab them again.  It will run through
# $pass times.
pass=4
round=1
if [ -f $DIR/routers.up.missed ]; then
    rm -f $DIR/routers.up.missed
fi
while [ $round -le $pass ]
do
    for router in `cat $devlistfile`
    do
	OFS=$IFS
	IFS=':'
	set $router
	IFS=$OFS
	router=$1; mfg=$2

	if [ ! -s $router.new ]
	then
	    echo "$router:$mfg" >> $DIR/routers.up.missed
	    rm -f $router.new
	fi
    done

    if [ -f $DIR/routers.up.missed ]; then
	echo "====================================="
	echo "Getting missed routers: round $round."
	par -q -n $PAR_COUNT -c "rancid-fe \{}" $DIR/routers.up.missed
	rm -f $DIR/routers.up.missed
	round=`expr $round + 1`
    else
	echo "All routers sucessfully completed."
	round=`expr $pass + 1`
    fi
done
echo

# Make sure that no empty configs are accepted.  Those that are non-empty
# are renamed from device_name.new -> device_name.
for router in `cat $devlistfile`
do 
    OFS=$IFS
    IFS=':'
    set $router 
    IFS=$OFS
    router=$1;

    if [ ! -s $router.new ]
    then
	rm -f $router.new
    else
	mv $router.new $router
	if [ $? -ne 0 ]; then
	    echo "Error: could not rename $router.new to $router"
	fi
    fi
done

# This has been different for different machines...
# Diff the directory and then checkin.
trap 'rm -fr $TMP $TMP.diff $DIR/routers.single;' 1 2 15
cd $DIR
cvs -f diff -U 4 | sed -e '/^RCS file: /d' -e '/^--- /d' \
	-e '/^+++ /d' -e 's/^\([-+ ]\)/\1 /' >$TMP.diff

if [ $alt_mailrcpt -eq 1 ] ; then
    subject="router config diffs - courtesy of $mailrcpt"
else
    subject="router config diffs"
fi
if [ "X$device" != "X" ] ; then
    cvs commit -m "updates - courtesy of $mailrcpt"
    subject="$GROUP/$device $subject"
else
    cvs commit -m updates
    subject="$GROUP $subject"
fi

# Mail out the diffs (if there are any).
if [ -s $TMP.diff ]; then
    sendmail -t <<EMAIL
To: $mailrcpt
Subject: $subject
Precedence: bulk

`cat $TMP.diff`
EMAIL
fi

# If any machines have not been reached within the last $OLDTIME
# hours, mail out a list of them.
cd $DIR/configs
rm -f $DIR/routers.failed
if [ "X$OLDTIME" = "X" ] ; then
    OLDTIME=24
fi
perl -F: -ane "{\$t = (stat(\$F[0]))[9]; print \`ls -ld \$F[0]\`
	if (time() - \$t >= $OLDTIME*60*60);}" $devlistfile | sort -u > $DIR/routers.failed
if [ -s $DIR/routers.failed ]
then
	(
	  echo "To: $adminmailrcpt"
	  echo "Subject: config fetcher problems - $GROUP"
	  echo "Precedence: bulk"
	  echo ""
	  echo "The following routers have not been successfully contacted for"
	  echo "more than $OLDTIME hours."

	  cat $DIR/routers.failed
	) | sendmail -t
fi

# Cleanup
rm -f $TMP.diff $DIR/routers.single
trap '' 1 2 15
