#!/bin/bash
# 
# netdump	This starts, stops, and reloads the netconsole
#		and netcrashdump facility
#
# chkconfig: - 50 50
# description: Initialize console side of netconsole and netcrashdump facility
# config: /etc/sysconfig/netdump
#

# Copyright 2002 Red Hat, Inc.
#
# Based in part on a shell script by
# Andreas Dilger <adilger@turbolinux.com>  Sep 26, 2001

PATH=/sbin:/usr/sbin:$PATH
RETVAL=0
SERVER_ADDRESS_RESOLUTION=
prog=netdump

# Check that networking is up.
. /etc/sysconfig/network
if [ ${NETWORKING} = "no" ]
then
    exit 0
fi

# Source function library.
. /etc/rc.d/init.d/functions

# Default values
LOCALPORT=6666
DEV=eth0
NETDUMPADDR=
NETDUMPMACADDR=
NETDUMPPORT=6666
IDLETIMEOUT=
NETDUMPKEYEXCHANGE=

SYSLOGADDR=
SYSLOGPORT=514
SYSLOGMACADDR=

NETLOGADDR=
NETLOGMACADDR=
NETLOGPORT=

kernel=`uname -r | cut -d. -f1-2`

[ -f /etc/sysconfig/netdump ] || exit 0
. /etc/sysconfig/netdump
[ -n "$NETDUMPADDR" ] || [ -n "$SYSLOGADDR" ] || [ -n "$NETLOGADDR" ] || {
    echo "Server address not specified in /etc/sysconfig/netdump" 1>&2
    exit 1
}


usage ()
{
    echo "Usage: service netdump {start|stop|status|restart|condrestart|propagate}" 1>&2
    RETVAL=1
}

dquad_to_hex ()
{
    echo $1 | sed -e "s/[()]//g" -e "s/\./ /g" | while read I0 I1 I2 I3 ; do
	printf "0x%02X%02X%02X%02X" $I0 $I1 $I2 $I3
    done
}

is_numeric_ip()
{
    local hostaddr="$1"

    echo $hostaddr | grep -qE "^([0-9]{1,3}\.){3}[0-9]{1,3}$"
    if [ $? -eq 0 ]; then
	echo "NUMERICIP=1"
    else
	echo "NUMERICIP=0"
    fi
}

print_address_info ()
{
    # fill the arp cache with needed data and print info for host
    # usage: print_address_info host
    local host=$1
    local ping_output line host_ip trc_output mac_ip localaddr
    local arp_output hostname ipaddr at mac iftype on iface

    # Use ping to determine the IP address
    ping_output="$(ping -c 1 -I $DEV $host 2> /dev/null)"
    if [ $? -eq 0 ]; then
	ping_output="$(echo $ping_output | grep '^PING ' | \
	    awk '{print $3}' | sed 's/^(//' | sed 's/)$//')"

	for line in $ping_output; do host_ip=$line; done
    else
	# if the ping failed, but we have a numeric IP address specified in
	# the config file, then we can use that.  Otherwise, we have to fail
	# here.
	eval $(is_numeric_ip $host)
	if [ $NUMERICIP -eq 0 ]; then
	    echo "$prog: cannot ping $host on interface $DEV" 1>&2
	    echo "SERVER_ADDRESS_RESOLUTION=unresolved"
	    exit 1
	fi

	host_ip=$host
    fi

    # the needed MAC address is directly associated with the host
    # IP address only if client and server are on the same subnet
    # if not, the needed MAC address is that of the gateway;
    # either way, this will be the first IP address from traceroute
    trc_output="$(traceroute -i $DEV -n -m 1 $host_ip 2> /dev/null)"
    if [ $? -eq 0 ]; then
	trc_output="$(echo $trc_output | grep '^1 ' | awk '{print $2}')"

	for line in $trc_output; do mac_ip=$line; done
    else
	echo "$prog: cannot traceroute $host_ip on interface $DEV" 1>&2
	mac_ip=$host_ip
    fi

    # If the server is on the same subnet as the client, but is currently
    # offline, then the first hop will show up as our local address.  This
    # would not be a working setup, so we set mac_ip to the server ip.
    localaddr=$(ip_of_device $DEV)
    [ "mac_ip" = "$localaddr" ] && mac_ip=$host_ip

    arping -c 1 -I $DEV $mac_ip &> /dev/null
    [ $? -ne 0 ] && echo "$prog: cannot arp $mac_ip on $DEV" 1>&2

    # output from arp -a of the form:
    # good: host.domain (A.B.C.D) at 00:50:BF:06:48:C1 [ether] on eth0
    #           1           2      3         4            5     6   7
    # bad:  ? (A.B.C.D) at <incomplete> on eth0
    arp_output="$(LC_ALL=C arp -a | grep -v incomplete)"
    echo "$arp_output" | ( while read hostname ipaddr at mac iftype on iface;
    do
	: echo hostname=$hostname ipaddr=$ipaddr at=$at mac=$mac iftype=$iftype
	: echo on=$on iface=$iface
        if [ "$ipaddr" = "($mac_ip)" ] || expr "$hostname" : "$host" &>/dev/null;
	then
            echo HOSTNAME=$host IPADDR=$host_ip AT=$at MAC=$mac \
                 TYPE=$iftype ON=$on IFACE=$iface
	    exit 0
        fi
    done
    exit 1 )

    if [ $? -eq 1 ]; then
	echo 1>&2 "$prog: cannot find $host in arp cache"
	echo "SERVER_ADDRESS_RESOLUTION=unresolved"
    fi
}

random_hex_int ()
{
    dd if=/dev/urandom bs=4 count=1 2>/dev/null | od -x | awk '/0000000/ {print $2$3}'
}

ip_of_device ()
{
    LC_ALL=C /sbin/ifconfig $1 | sed 's/:/ /' | awk '/inet addr/ {print $3}'
}

netdump_failure ()
{
    echo -n "$1"
    failure
    echo
    exit 1
}

start ()
{
    local netdump_numeric_ip

    NETDUMPOPTS=
    if [ -n "$NETDUMPADDR" ]
    then
	# If a netdump mac address is not specified, or if one is specified
	# but the netdump address provided is a DNS name, then we need to
	# call print_address_info to resolve the name and/or get the next
	# hop MAC address.
	eval $(is_numeric_ip $NETDUMPADDR)
	if [ -z "$NETDUMPMACADDR" -o $NUMERICIP -eq 0 ]; then

	    eval $(print_address_info $NETDUMPADDR)
	    if [ "$SERVER_ADDRESS_RESOLUTION" = "unresolved" ] ||
	       [ -z "$MAC" -a -z "$NETDUMPMACADDR" ]; then

		echo "$prog: can't resolve $NETDUMPADDR MAC address" 1>&2
		netdump_failure "netdump server address resolution"
	    fi
    
	    [ -z "$NETDUMPMACADDR" ] && NETDUMPMACADDR=$MAC
	    if [ "$DEV" = "$IFACE" -a "$TYPE" != "[ether]" ] 
	    then
		echo "$prog: $DEV must be an ethernet interface" 1>&2
		netdump_failure "netdump $DEV configuration"
	    fi
	else
	    IPADDR="$NETDUMPADDR"
	fi

	netdump_numeric_ip=$IPADDR

	# Now we are ready to tell the netdump server how to talk to us
	MAGIC1=$(random_hex_int)
	MAGIC2=$(random_hex_int)
	LOCALADDR=$(ip_of_device $DEV)

        case "$NETDUMPKEYEXCHANGE" in
        none)
            # magic can be anything (except 0) if security is disabled
    	    NETDUMPOPTS="magic1=0x11111111 magic2=0x11111111 "
    	    ;;
        *)
    	    ssh -x -i /etc/sysconfig/netdump_id_dsa netdump@$NETDUMPADDR echo "$MAGIC2$MAGIC1" \> /var/crash/magic/$LOCALADDR
    	    if [ $? -ne 0 ]; then
    		echo "$prog: could not ssh to server $NETDUMPADDR"
    		netdump_failure "netdump server ssh key exchange"
    	    fi
    	    NETDUMPOPTS="magic1=0x$MAGIC1 magic2=0x$MAGIC2 "
    	    ;;
        esac
    
        IPHEX=`dquad_to_hex $IPADDR`
        eval $(echo $NETDUMPMACADDR | sed "s/:/ /g" | ( read M0 M1 M2 M3 M4 M5;
               echo M0=$M0\; M1=$M1\; M2=$M2\; M3=$M3\; M4=$M4\; M5=$M5\; ))
        TGTMAC="netdump_target_eth_byte0=0x$M0 netdump_target_eth_byte1=0x$M1 \
    	    netdump_target_eth_byte2=0x$M2 netdump_target_eth_byte3=0x$M3 \
    	    netdump_target_eth_byte4=0x$M4 netdump_target_eth_byte5=0x$M5 \
    	    netlog_target_eth_byte0=0x$M0 netlog_target_eth_byte1=0x$M1 \
    	    netlog_target_eth_byte2=0x$M2 netlog_target_eth_byte3=0x$M3 \
    	    netlog_target_eth_byte4=0x$M4 netlog_target_eth_byte5=0x$M5"
        MHZ="mhz=$(awk '/cpu MHz/ { print int($4) ; exit }' < /proc/cpuinfo)"
        if [ "$MHZ" == 0 ] ; then
    	    # something went wrong; make some reasonable guess
    	    MHZ=1000
        fi
        if [ -n "$IDLETIMEOUT" ] ; then
      	    IDLETIMEOUT="idle_timeout=$IDLETIMEOUT"
        fi

	if [ $kernel = 2.4 ]; then
	    NETDUMPOPTS=$NETDUMPOPTS"\
	    dev=$DEV netdump_target_ip=$IPHEX netlog_target_ip=$IPHEX \
	    source_port=$LOCALPORT netdump_target_port=$NETDUMPPORT \
	    netlog_target_port=$NETDUMPPORT \
	    $TGTMAC $MHZ $IDLETIMEOUT"
	else
	    NETDUMPOPTS=$NETDUMPOPTS"\
	    netdump=$LOCALPORT@$LOCALADDR/$DEV,$NETDUMPPORT@$IPADDR/$NETDUMPMACADDR"
	fi
    else
	# The netdump subsystem of the netconsole module is not configured.
	# However, the netconsole module minimally needs the following 
        # options in order for the syslog subsystem to run alone.
	if [ $kernel = 2.4 ]; then
	    NETDUMPOPTS="magic1=0x11111111 magic2=0x11111111 dev=$DEV source_port=$LOCALPORT"
	else
	    NETDUMPOPTS="magic1=0x11111111 magic2=0x11111111"
	fi
    fi

    # This section must come after NETDUMPOPTS, as it inherits IP address
    # from it.
    if [ $kernel = 2.6 ]; then
	# Make these the same as netdump opts
	if [ -z $NETLOGADDR ]; then
	    NETLOGOPTS="netlog=@$LOCALADDR/$DEV,$NETDUMPPORT@$netdump_numeric_ip/$NETDUMPMACADDR"
	elif [ $NETLOGADDR = "NONE" ]; then
	    NETLOGOPTS=
	elif [ -n "$NETLOGADDR" ]; then
	    eval $(is_numeric_ip $NETLOGADDR)
	    if [ -z "$NETLOGMACADDR" -o $NUMERICIP -eq 0 ]; then
		eval $(print_address_info $NETLOGADDR)
		if [ "$SERVER_ADDRESS_RESOLUTION" = "unresolved" ] ||
		   [ -z "$MAC" -a -z "$NETLOGMACADDR" ]; then
		    echo "$prog: can't resolve $NETLOGADDR MAC address" 1>&2
		    netdump_failure "netlog server address resolution"
		fi

		[ -z "$NETLOGMACADDR" ] && NETLOGMACADDR=$MAC
		if [ "$DEV" = "$IFACE" -a "$TYPE" != "[ether]" ] 
		then
		    echo "$prog: $DEV must be an ethernet interface" 1>&2
		    netdump_failure "netlog $DEV configuration"
		fi
	    else
		IPADDR="$NETLOGADDR"
	    fi
	    NETLOGOPTS="netlog=@$LOCALADDR/$DEV,$NETLOGPORT@$IPADDR/$NETLOGMACADDR "
	fi
    fi

    SYSLOGOPTS=
    # syslogd server, if any
    if [ -n "$SYSLOGADDR" ] ; then
	eval $(is_numeric_ip $SYSLOGADDR)
	if [ -z "$SYSLOGMACADDR" -o $NUMERICIP -eq 0 ]; then

	    eval $(print_address_info $SYSLOGADDR)
	    if [ "$SERVER_ADDRESS_RESOLUTION" = "unresolved" ] ||
	       [ -z "$MAC" -a -z "$SYSLOGMACADDR" ]; then

		echo "$prog: can't resolve $SYSLOGADDR MAC address" 1>&2
		netdump_failure "syslog server address resolution"
	    fi
    
	    [ -z "$SYSLOGMACADDR" ] && SYSLOGMACADDR=$MAC
	    if [ "$DEV" = "$IFACE" -a "$TYPE" != "[ether]" ] 
	    then
		echo "$prog: $DEV must be an ethernet interface" 1>&2
		netdump_failure "syslog $DEV configuration"
	    fi
	else
	    IPADDR="$SYSLOGADDR"
	fi

	SYSLOGIPHEX=`dquad_to_hex $IPADDR`
	eval $(echo $SYSLOGMACADDR | sed "s/:/ /g" | ( read M0 M1 M2 M3 M4 M5;
	       echo M0=$M0\; M1=$M1\; M2=$M2\; M3=$M3\; M4=$M4\; M5=$M5\; ))
	SYSLOGMAC="syslog_target_eth_byte0=0x$M0 syslog_target_eth_byte1=0x$M1 \
		syslog_target_eth_byte2=0x$M2 syslog_target_eth_byte3=0x$M3 \
		syslog_target_eth_byte4=0x$M4 syslog_target_eth_byte5=0x$M5"
	if [ $kernel = 2.4 ]; then
	    SYSLOGOPTS="syslog_target_ip=$SYSLOGIPHEX syslog_target_port=$SYSLOGPORT $SYSLOGMAC"
	else
	    SYSLOGOPTS="netconsole=$LOCALPORT@$LOCALADDR/$DEV,$SYSLOGPORT@$IPADDR/$SYSLOGMACADDR "
	fi
    fi

    logger -p daemon.info -t netdump: inserting netconsole module with arguments \
	$NETDUMPOPTS $SYSLOGOPTS
    if [ $kernel = 2.4 ]; then
	action $"initializing netdump" modprobe netconsole \
	    $NETDUMPOPTS $SYSLOGOPTS
    else
	if [ -n "$NETDUMPADDR" ]; then
	    action $"initializing netdump" modprobe netdump \
		$NETDUMPOPTS
	fi
	if [ -n "$SYSLOGOPTS" ] || [ -n "$NETLOGOPTS" ] ; then
	    action $"initializing netconsole" modprobe netconsole \
		$SYSLOGOPTS $NETLOGOPTS
	fi
    fi
    touch /var/lock/subsys/netdump
}

stop ()
{
    if [ $kernel = 2.4 ]; then
	if /sbin/lsmod | grep netconsole >/dev/null 2>&1 ; then
	    action $"disabling netdump" rmmod netconsole
	fi
    else
	if /sbin/lsmod | grep netconsole >/dev/null 2>&1 ; then
	    action $"disabling netconsole" rmmod netconsole;
	fi

	if /sbin/lsmod | grep netdump >/dev/null 2>&1 ; then
	    action $"disabling netdump" rmmod netdump
	fi
    fi

    rm -f /var/lock/subsys/netdump
}

status ()
{
    if [ $kernel = 2.6 ]; then
    	if /sbin/lsmod | grep netdump >/dev/null 2>&1 ; then
	    echo "netdump module loaded"
    	else
	    echo "netdump module not loaded"
        fi
    fi
    if /sbin/lsmod | grep netconsole >/dev/null 2>&1 ; then
	echo "netconsole module loaded"
    else
	echo "netconsole module not loaded"
    fi
}


restart ()
{
    stop
    start
}

condrestart ()
{
    [ -e /var/lock/subsys/netdump ] && restart
}

propagate ()
{
    # propagate netdump ssh public key to the crashdump server
    cat /etc/sysconfig/netdump_id_dsa.pub | \
	ssh -x netdump@$NETDUMPADDR cat '>>' /var/crash/.ssh/authorized_keys2
}

case "$1" in
    stop) stop ;;
    status) status ;;
    start|restart|reload) restart ;;
    condrestart) condrestart ;;
    propagate) propagate ;;
    *) usage ;;
esac

exit $RETVAL
