#!/bin/bash
# start/stop script for virtual IP for censhare
#
# Environment variable(s):
#     CONFIG_FILE=/etc/s2s/sysconfig/$progname[.$HOSTNAME]
#         use this variable to read local configuration. if not set it
#         defaults to ones in dirs /etc/sysconfig/ or /etc/s2s/sysconfig/ 
#
# (c) 2007..2010 by censhare AG, Munich
#
# 2010-Okt-11 pmo  changed compatibility for perl v5.12.1
# 2008-Nov-10 pmo  added: Linux: arping after ifup of virtual interface
# 2008-Feb-21 aka  DelVirtIP(): sleep added to improve restart capability
# 2007-Jul-05 aka  reworked for s2s-2.0.1
# 2007-Feb-26 aka  initial version

progname="${0##*/}"					# cut path
progname=${progname#rc}
progname=${progname#[KS][0-9][0-9]}
progname="${progname%%.*}"				# cut filetype
test "$UID" -ne 0  && { echo >&2 "$progname: You must be root."; exit 1; }

action="$1"						# save action
ec=0							# default: success

# status handling a la LSB-2.x, -3.x
# http://refspecs.freestandards.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html
 ret_other=(success error)                              # return values
ret_status=(running dead_but_run dead_but_lock unused          unknown           reserved5     not_configured)
 ret_start=(started error        inv_args      not_implemented insuff_privileges not_installed not_configured not_running)
  ret_stop=(stopped error        inv_args      not_implemented insuff_privileges not_installed not_configured not_running)
#           0       1            2             3               4                 5             6              7
rc_status () {                                          # inform user
    local s
    case "$action" in
        start)  s=${ret_start[$ec]};;
        stop)   s=${ret_stop[$ec]};;
        status) s=${ret_status[$ec]};;
        *)      test "$ec" = 0 && s=${ret_other[0]} || s=${ret_other[1]};;
    esac
    echo ": ${s:-unknown_exit_code_$ec}"
}

# read configuration
ConfErr () { echo >&2 "$progname: $*"; exit 6; }
cfglist=""						# collect tried pathes, output on error for debugging
ChkCfgfile () {						# first try /etc/sysconfig then /etc/s2s/sysconfig
    local cf="$1"; test -z "$cf" && ConfErr "$FUNCNAME(): filename missing."
    CONFIG_FILE=/etc/sysconfig/$cf;     cfglist="$cfglist $CONFIG_FILE"; test -r "$CONFIG_FILE" && return 0
    CONFIG_FILE=/etc/s2s/sysconfig/$cf; cfglist="$cfglist $CONFIG_FILE"; test -r "$CONFIG_FILE" && return 0
    return 1						# failed
}
test -z "$CONFIG_FILE" && {				# if no config file defined by environment, look for
    ChkCfgfile $progname.$HOSTNAME || {			#     host specific one or
	ChkCfgfile $progname || {			#     general one or error
	    ConfErr "configuration missing, searched: '${cfglist# }'."; }; }
}							# endif
test -s "$CONFIG_FILE" && . $CONFIG_FILE		# read config
# check for mandatory config vars
test -z "$VIRT_IPSETUP" && ConfErr "var 'VIRT_IPSETUP' unset."
test -z "$VIRT_PHYS_IF" && ConfErr "var 'VIRT_PHYS_IF' unset."
test -z "$VIRT_IF"      && ConfErr "var 'VIRT_IF' unset."
VIRT_IP=${VIRT_IPSETUP%% *}
test -z "$VIRT_IP"      && ConfErr "var 'VIRT_IP' cannot derived by '\$VIRT_IPSETUP'."

# configure for platform
sys_os="`uname`"
case "$sys_os" in
    SunOS)
        PATH="$PATH:/sbin:/usr/sbin:/usr/ucb"
	ping () { /usr/sbin/ping "$1" 2; }
        ping=ping
	ifup="ifconfig \$VIRT_IF plumb"
	ifdn="ifconfig \$VIRT_IF unplumb"
	;;
    Linux)
        ping="ping -c1 -W2"
	ifup=""
	ifdn="ifconfig \$VIRT_IF down"
	;;
    Darwin)						# MacOSX
	ping="ping -c1 -t2"
	ifup=""
	ifdn="ifconfig \$VIRT_IF \$VIRT_IP delete"
	;;
    ForDocumentationOnly)
	# $ping should send 1..2 packets with a timeout 1..3 seconds each
	# $ifup is exec'ed before interface configuration is done
	# $ifdn is always needed and exec'ed to remove virtual interface
	;;
    *)
        echo >&2 "Unknown operating system '$sys_os'."; exit 6;;
esac
PATH="$PATH:/usr/sbin:/sbin"				# order is important for Solaris
type ifconfig >&/dev/null || exit 6

# check perl + version
for perl in perl /usr/bin/perl /usr/local/bin/perl notfound; do
    type $perl >&/dev/null && {				# perl executable >= perl-5.8 needed
	perlversion=$($perl -we 'printf "%d", $]*1000' 2>/dev/null)
	#echo "perl=$perl-$perlversion"
	test "$perlversion"0 -ge "5008"0 && break;	# the appended '0' prevents shell error
    }
done
test $perl = notfound && { echo >&2 "perl >= perl-5.8 not found."; exit 6; }

# utility functions

ChkVirtIP () {						# return 0 if correctly set up, 4 on config problem and 3 if not active
    echo -n "'$VIRT_IF $VIRT_IP'"
    ifconfig -a | $perl -wne '				# parse ifconfig output and retrieve state for $VIRT_IF/$VIRT_IP
BEGIN { 
    $dbg = 0;						# debugging 0=off, 1=on
    $ifn = "";						# current interface name while parsing
    $eif = "'"$VIRT_IF"'";				# ethernet interface
    if ($eif !~ /^\w+(:[0-9]+)?$/) {
	print "interface naming convention (ex eth_0[:1]) violated: $eif\n";
	exit;
    }
    $vip = "'"$VIRT_IP"'";				# virtual ip; check and rewrite to $vip, $vip0
    $vip =~ /([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)/ or do {
	print "syntax error on \$VIRT_IP=$vip\n";
	exit;
    };
    if ($1 >= 0 && $1 <= 255 and $2 >= 0 && $2 <= 255 and $3 >= 0 && $3 <= 255 and $4 >= 0 && $4 <= 255) {
	$vip  = sprintf("%d.%d.%d.%d",         $1, $2, $3, $4);
	$vip0 = sprintf("%03d.%03d.%03d.%03d", $1, $2, $3, $4);
    } else {
	print "one or more addr tupels out of range 0..255: $vip\n";
	exit;
    }
}
/^(\w+(:[0-9]+)?)/ and do {				# get/select interface name $ifn
    $ifn = $1;
    $ipcnt = -1;					# Mac: ip alias counter
};
next if $ifn eq "";					# skip lines until first encountered interface name
$cif = \%{$eth{$ifn}};					# establish hash ref for current interface attributes
/flags=/ and do {					# get "interface-UP" flag (Solaris, MacOSX)
    /\bUP\b/ and $$cif{UP} = 1;
};
/\bUP\b.*\bMTU:/ and $$cif{UP} = 1;			# same but with Linux
/\binet.*?([0-9.]{7,15})/ and do {			# get ip configuration
    $addon = ($ipcnt < 0) ? 				# Mac: all virt if are listed at phys if section
	"" : sprintf(".%d",$ipcnt); $ipcnt++;
    $$cif{"inetaddr".$addon} = $1;
    $ifa{$1} = $ifn;					# if addr hash => if name
    /\bnetmask.*?(([0-9.]{7,15})|(0x[[:xdigit:]]{8}))/ and $$cif{"netmask".$addon} = $1;
    /\bbroadcast.*?([0-9.]{7,15})/ and $$cif{"broadcast".$addon} = $1;
};

sub PrIfAddr ($$) {					# output single address config, return 0 if found, 1 if not
    my ($cif, $addon) = @_;
    return 1 if ! defined $$cif{"inetaddr".$addon};
    print "    inet ".$$cif{"inetaddr".$addon};
    print " netmask ".$$cif{"netmask".$addon} if defined $$cif{"netmask".$addon};
    print " broadcast ".$$cif{"broadcast".$addon} if defined $$cif{"broadcast".$addon};
    print "\n";
    return 0;
}

END {
    exit 6 if(! %eth);					# config problems
    if ($dbg) {						# output parsed info
	foreach (keys %eth) {
	    $cif = \%{$eth{$_}};
	    print "$_";
	    print " UP" if $$cif{UP};
	    print "\n";
	    PrIfAddr ($cif, "");
	    for ($ipcnt = 0; ; $ipcnt++) {
		last if PrIfAddr ($cif, sprintf(".%d",$ipcnt));
	    }
	}
    }
    # Checks:
    #     - ip on correct/wrong if
    #     - phys if (not) avail
    #     - ip (not) up
    $ifn = "";						# check for configured ip addr
    $ifn = $ifa{$vip0} if defined $ifa{$vip0};
    $ifn = $ifa{$vip}  if defined $ifa{$vip};
    $ret = 3;						# default return = 3 == virt ip not active
    if ($ifn eq "") {					# ip not found
	$eif =~ /^(\w+)(:.*)?/; $pif = $1;		# check for physical interface
	if (! defined $eth{$pif}) {			# no phys if 
	    print "physical ethernet interface for \"$eif\" not avail.\n";
	    $ret = 6;					# ==> config error
	}
	if ($eif =~ /:/ and defined $eth{$eif}) { # virt if found but other ip configured
	    print "virtual interface \"$eif\" configured for other ip.\n";
	    $ret = 6;					# ==> config error
	}
    } else {						# ip configured
	if ($ifn eq $eif) {				# ... on correct if
	    if (defined ${$eth{$ifn}}{UP}) {		# ...     and up
		print "ip on correct interface: virtIP $vip active on $eif.\n" if $dbg;
		$ret = 0;				#         ==> if already active
	    } else {					# ...     but not up
		print "ip on correct interface but not active.\n";
		$ret = 6;				#         ==> config error
	    }
	} else {					# ... on wrong if
	    print "ip on wrong interface.\n";
	    $ret = 6;					#     ==> config error
	}
    }
    print "ChkVirtIP() ret $ret.\n" if $dbg;
    exit $ret;
}'; local ec=$?
    #set +x
    return $ec
}

AddVirtIP () {
    ChkVirtIP; local ret=$?
    test $ret = 0 && return 0;				# return true if already set up
    test $ret = 6 && return 6;				# return error on config problems
    # try ping twice:
    $ping $VIRT_IP &>/dev/null &&			# ip active on network - stop!
        return 1
    $ping $VIRT_IP &>/dev/null &&			# ip active on network - stop!
        return 1
    test -n "$ifup" && {				# activate interface - if neccessary
	eval $ifup || return 6; 			# error in configuration
    }
    ifconfig $VIRT_IF $VIRT_IPSETUP || return 6		# error in configuration
sys_os="`uname`"
case "$sys_os" in
    Linux)						# if os = linux
	type arping >&/dev/null && {                    # check if arping is installed
            arping -q -A -c 1 -I ${VIRT_IF%%:*} $VIRT_IP || echo "Error: arping: ec=$?."
        } || echo -e "\nWarning: arping command not found."  # update arp-cache on switch else error
esac
}

DelVirtIP () {						# delete virtual ip address
    ChkVirtIP || return 0				# remove interface only if currently active
    eval $ifdn >&/dev/null				# no error handling
    sleep 2                                             # wait 2sec for security - else restart may fail
    return 0
}


# handle action
case "$action" in
    start)
	echo -n "    Starting $progname "
	AddVirtIP; ec=$?
	rc_status
	;;
    stop)
	echo -n "    Shutting down $progname "
	DelVirtIP; ec=$?
	rc_status
	;;
    restart)
	## Stop the service and regardless of whether it was
	## running or not, start it again.
	$0 stop; $0 start; ec=$?
	;;
    status)
	echo -n "    Checking for service $progname "
	ChkVirtIP; ec=$?
	rc_status
	;;
    *)
	echo "Usage: $0 {start|stop|status|restart}"
	ec=1
	;;
esac
exit $ec

# Local Variables:
# mode:                 shell-script
# mode:                 font-lock
# comment-column:       56
# tab-width:            8
# End:
