#!/bin/sh -e
# $Id: autonet,v 1.13 2006/11/20 12:06:26 abs Exp $
#
# (c) 2005, 2006 David Brownlee (abs@absd.org). May be freely distributed.
# No warranty, implied or otherwise. Stick no bills. Suggestions welcome.

VERSION=0.22
autonet_localscript=/etc/autonet.local
autonet_speaker=/dev/speaker
autonet_media="Ethernet IEEE802.11"

# Extract autonet_* variables from rc.conf
. /etc/rc.subr

autonet_restart()
    {
    autonet_stop ; autonet_start
    }

autonet_start()
    {
    phase start
    pkill dhclient || true
    delete_default_route
    for media in $autonet_media ; do

	case $media in
	    IEEE802.11)
		interfaces="$(list_interfaces $media)"
		set -- $(try_wireless $interfaces)
		interface=$1
		ssid=$2
		encryption=$3
		;;
	    *)
		interfaces="$(list_interfaces $media active)"
		if [ -z "$interfaces" -a -n "$opt_u" ] ; then
		    interfaces=$(list_interfaces $media)
		    if [ -n "$interfaces" ] ; then
			verbose "Bring $interfaces up and sleep $opt_u"
			for i in $interfaces ; do
			    ifconfig $i up
			done
			sleep $opt_u
			interfaces=$(list_interfaces $media active)
		    fi
		fi
		interface=$(echo $interfaces | sed 's/ .*//')
		ssid=
		encryption=
		;;
	esac

	if [ -n "$interface" ] ; then
	    echo "Configure: $interface $media $ssid"
	    configure_interface $interface $media $ssid
	    return
	fi
    done

    phase start_fail
    down_interfaces
    }

autonet_stop()
    {
    phase stop 
    pkill dhclient || true
    delete_default_route
    down_interfaces
    phase stop_done
    }

configure_interface()
    {
    interface=$1
    media=$2
    ssid=$3
    # Check to see if autonet_<fu>_ifconfig is set
    #
    if [ -n "$ssid" ] ; then
	eval ifconfig=\$autonet_${ssid}_ifconfig
    fi
    if [ -z "$ifconfig" ] ; then
	eval ifconfig=\$autonet_${interface}_ifconfig
    fi

    ifconfig $interface up
    # Configure interface based on autonet_<fu>_ifconfig or run dhclient
    #
    if [ -n "$ifconfig" ] ; then
	verbose ifconfig $interface $ifconfig
	ifconfig $interface $ifconfig
    else
	phase start_dhclient $interface $media $ssid
	verbose dhclient -q $interface
	dhclient -q $interface
    fi

    # Check to see if autonet_<fu>_defaultroute is set
    #
    if [ -n "$ssid" ] ; then
	eval defaultroute=\$autonet_${ssid}_defaultroute
    fi
    if [ -z "$defaultroute" ] ; then
	eval defaultroute=\$autonet_${interface}_defaultroute
    fi
    if [ -n "$defaultroute" ] ; then
	delete_default_route
	verbose route add default $defaultroute
	route add default $defaultroute
    fi

    phase start_configured $interface $media
    phase start_done $interface $media
    }

delete_default_route()
    {
    if route -n show | grep -q '^default' ; then
	route -n delete default
    fi
    }

down_interfaces()
    {
    action=$(ifconfig -a | awk -F: '
	/^[a-z]/  {i=$1;if(i=="lo0"){i=""}
			else{if(/<UP/){print "ifconfig "i" down;"}}}
	/^	inet / {if(i){print "ifconfig "i" delete;"}}
	')
    if [ -n "$action" ] ; then
	verbose $action
	sh -c "$action"
    fi
    }

ip()
    {
    ifconfig -a | awk '/inet/&&$2!="127.0.0.1"{print $2;exit}'
    }

list_interfaces()
    {
    media=$1
    status=$2
    for i in $(ifconfig -l) ; do
	# VMware pcn0 interface does not give any 'status:' line
	interfaces="$interfaces $(ifconfig $i | \
	awk -v i=$i -v m=$media -v s=$status '
	/media: / {media=$2}
	/status: / {status=$2}
	END {if(m == media && (s == "" || status == "" || s == status))
	     {print i}}')"
    done

    # Trim leading and trailing whitespace
    interfaces=$(echo $interfaces | sed -e 's/^  *//' -e 's/  *$//')

    verb="List \"$media\" interfaces"
    if [ -n "$status" ]; then
	verb="$verb (status=$2)"
    fi
    if [ -n "$interfaces" ]; then
	verbose "$verb: $interfaces"
    else
	verbose "$verb: none"
    fi
    echo $interfaces
    }

list_wireless_ssids()
    {
    interface=$1
    enc=$2

    # For non encrypted match just 'ESS', otherwise look for "ESS xxx"
    if [ -z "$enc" ] ; then
	enc=ESS
    else
	enc="ESS $enc"
    fi
    wiconfig $interface -D | awk -v "enc=$enc" '
    /netname \(SSID\):/	{sub("[^[]*. ","");sub(" ].*","");
			 if($1 != ""){ssid=$1} # Filter out blank SSID
			 }	# 
    /Capinfo:/		{sub("[^[]*. ","");sub(" ].*",""); 
			 if($0 == enc){list[ssid]=ssid} # Handle dup SSID
			 }
    END{for (s in list) {printf s " "}}
    '
    }

phase()
    {
    phase=$1
    media=$3
    if [ -n "$autonet_speaker" ] ; then
	case $phase in
	    start)		echo L16MLE	> $autonet_speaker || true ;;
	    start_fail)		echo '<<<L8E'	> $autonet_speaker || true ;;
	    stop)		echo L16MLA	> $autonet_speaker || true ;;
	    stop_done)		echo L32MLAL12B	> $autonet_speaker || true ;;
	    start_dhclient)
		if [ "$media" = "IEEE802.11" ] ; then
		    		echo L16MLF	> $autonet_speaker || true
		else
		    		echo L16MLD	> $autonet_speaker || true
		fi;;
	    start_configured)	echo L16MLF	> $autonet_speaker || true ;;
	    start_done)		echo L64MLEFEF	> $autonet_speaker || true ;;
	esac
    fi
    verbose Call $autonet_localscript "$@"
    if [ -f $autonet_localscript ] ; then
	sh $autonet_localscript "$@" || true
    fi
    }

try_wireless()
    {
    interfaces="$*"
    for i in $interfaces ; do
	verbose "Probe WEP SSIDs on $i"
	ifconfig $i ssid '' -nwkey
	ssids="$(list_wireless_ssids $i WEP)"
	verbose "WEP SSIDs on $i: $ssids"
	for ssid in $ssids ; do
	    eval nwkey=\$autonet_${ssid}_nwkey
	    if [ -n "$nwkey" ] ; then
		ifconfig $i ssid $ssid nwkey "$nwkey"
		echo $i $ssid WEP
		return
	    fi
	done
    done
    for i in $interfaces ; do
	verbose "Probe unencrypted SSIDs on $i"
	ssids="$(list_wireless_ssids $i)"
	verbose "Unencrypted SSIDs on $i: $ssids"
	if [ -n "$ssids" ] ; then
	    case "$ssids" in
		*" "*)	echo $interfaces;;
		*)	echo $interfaces $ssids;;
	    esac
	fi
    done
    }

usage()
    {
    if [ -n "$1" ] ; then
	echo "$@"
	echo
    fi
    echo "Usage: autonet [opts] [start|stop|restart]
	-h      This help
	-s dev  Device for sounds. Set '' to disable (default: /dev/speaker)
	-u secs Bring Ethernet interfaces up & sleep secs before testing active
	-v      Verbose
	-V      Display version ($VERSION)
"
    [ -z "$1" ]	 # Exit code
    exit
    }

verbose()
    {
    if [ -n "$opt_v" ] ; then
	echo "$@" >&2
    fi
    }

args=$(getopt Vhsu:v $autonet_flags $*)
if [ $? != 0 ]; then
    usage "Invalid option"
fi
load_rc_config autonet
set -- $args
while [ $# != 0 ]; do
    case "$1" in
	-h )	opt_h=1 ;;
	-s )	opt_s=1 ;;
	-u )	opt_u=$1; shift ;;
	-v )	opt_v=1 ;;
	-V )	opt_V=1 ;;
	-- )	shift; break ;;
    esac
    shift
done

if [ -n "$opt_h" ];then
    usage
fi

if [ -n "$opt_V" ];then
    echo $VERSION
elif [ "$#" = 3 ] ; then
    # Assume we have be called by ifwatchd - to be completed
    autonet_restart
else
    case "$1" in
	restart) autonet_restart ;; 
	start)	 autonet_start ;;
	stop)	 autonet_stop ;;
	*)	usage "Unknown option $1" ;;
    esac
fi
