#!/bin/sh
# wifichoice.sh by Stefan Tomanek (stefan@pico.ruhr.de)
# http://stefans.datenbruch.de/interfaces/
#
# Wifichoice selects the proper configuration profile for your WLAN
# interface by scanning for the right environment.
# To achieve this, it can either scan for the correct access point
# or ESSID, or try to associate with the configured network.
#
# Configuration is done in a central way via /etc/network/interfaces:
#
# mapping wlan0
#    script /usr/local/sbin/ifichoice.sh
#    map default: none
#    map timeout: 2
#
# Timeout is the number of seconds the script waits after a test
# association for the device to settle, and default is the profile
# that shoud be loaded is if no known surrounding is detected.
#
# The test parameters are defined in the profiles themselves:
#
# iface home inet dhcp
#    wireless yes
#    wireless_mode managed
#    wireless_key open 1234567890ABCDEF1234567890
#    wireless_essid mywifinet
#    wifichoice ap 00:12:34:56:78:9A
#
# Since your access point does not not broadcast its ESSID,
# and/or your card does not support "iwlist scan", wifichoice tries to
# associate with the network, using the supplied config, and then checks
# whether the network is managed by the right access point.
#
# If you can identify the network by its broadcasted ESSID or AP, you can
# also try to scan for it. Just add a line like this to your profile:
#
# wifichoice scan essid mywifinet
#
# Wifichoice then invokes a scan for neighbouring access points, and
# identifies those that serve the requested ESSID. It is also possible
# to scan for a specific access point address, this is useful if the AP
# does not transmit its ESSID:
#
# wifichoice scan ap 00:12:34:56:78:9A


# Which physical interface do we operate on?
IFACE=$1
# How long do we wait for the interface to settle?
TIMEOUT=2
# What is the default profile?
DEFAULT=""
DEBUG=0

# Paths to files
ENI=/etc/network/interfaces
IWCONFIG=/sbin/iwconfig
IFCONFIG=/sbin/ifconfig
IWLIST=/sbin/iwlist
IWGETID=/sbin/iwgetid

readConfig() {
    while read L <&0; do
	OPTION="$(echo $L | cut -d: -f1)"
	VALUE="$(echo $L | cut -d ' ' -f2)"
	if [ "$OPTION" = "default" ]; then
	    DEFAULT=$VALUE
	elif [ "$OPTION" = "timeout" ]; then
	    TIMEOUT=$VALUE
	elif [ "$OPTION" = "debug" ]; then
	    DEBUG=$VALUE
	fi
	
    done
    debugMSG "Debug output is enabled"
    debugMSG "Timout is $TIMEOUT seconds"
    debugMSG "Default profile is »$DEFAULT«"
}

reset() {
    IF="$1"
    CONF="mode managed enc off ap auto essid auto"
    $IWCONFIG $IF $CONF
    $IFCONFIG $IF down
}

config() {
    IF="$1"
    reset $IF
    CONF="$2"
    debugMSG "Trying wireless configuration: $CONF";
    $IWCONFIG $IF $CONF
    $IFCONFIG $IF up
    sleep $TIMEOUT 
}

checkAP() {
    IF="$1"
    AP="$2"
    [ "$($IWGETID $IF -ra)" = "$AP" ]
}

scanESSID() {
    IF="$1"
    ESSID="$2"
    FOUND=0
    # certain PC-Cards need this
    $IFCONFIG $IF up
    $IWLIST $IF scan | egrep -q '^[[:space:]]*ESSID:"'$ESSID'"$' && FOUND=1
    $IFCONFIG $IF down
    [ $FOUND -eq 1 ]
}

scanAP() {
    IF="$1"
    AP="$2"
    FOUND=0
    $IFCONFIG $IF up
    $IWLIST $IF scan | egrep -q '^[[:space:]]*Cell [[:digit:]]+ - Address: '$AP'$' && FOUND=1
    $IFCONFIG $IF down
    [ $FOUND -eq 1 ]
}

debugMSG() {
    if [ $DEBUG -eq 1 ]; then echo "$*" >&2 ; fi
}

checkProfile() {
    FOUND=0
    if [ "$METHOD" = "ap" ]; then
	debugMSG "Trying to associate with »$PROFILE«"
	AP="$(echo $TEST | cut -d ' ' -f 2)"
	config $IFACE "$CONFIG"
	checkAP $IFACE $AP && FOUND=1
	
    elif [ "$METHOD" = "scan" ]; then
	debugMSG "Scanning for »$PROFILE«"
	# What are we scanning for?
	# We support essid and ap
	FOR="$(echo $TEST | cut -d ' ' -f 2)"
	VAL="$(echo $TEST | cut -d ' ' -f 3)"
	if [ "$FOR" = "essid" ]; then
	    debugMSG "Scanning for essid »$VAL«"
	    scanESSID $IFACE $VAL && FOUND=1
	elif [ "$FOR" = "ap" ]; then
	    debugMSG "Scanning for access point »$VAL«"
	    scanAP $IFACE $VAL && FOUND=1
	fi
    fi
    
    if [ $FOUND -eq 1 ]; then
	debugMSG "Success!"
	echo $PROFILE >&3
    fi
    [ $FOUND -eq 1 ]
}


# Kill STDOUT
exec 3>&1 1>/dev/null

readConfig

PROFILE=""
CONFIG=""
FOUND=0
TEST=""
METHOD=""

sed 's/^[[:space:]]*//; s/#.*$//g' $ENI | while read LINE; do
    if echo $LINE | egrep -q '^iface [[:alnum:]]+'; then
	
	# new profile started, check for past profile
	if [ "$PROFILE" ] && [ "$TEST" ]; then
	    checkProfile $PROFILE $TEST $METHOD && break;
	fi
	# reset vars    
	PROFILE="$(echo $LINE | cut -d " " -f 2)"
	CONFIG=""
	TEST=""
	METHOD=""
    elif echo $LINE | egrep -q '^wireless(_|-)([[:alnum:]])+'; then
	CMD="$(echo $LINE | sed 's/^wireless\(_\|-\)//')"
	CONFIG="$CONFIG$CMD "
    elif echo $LINE | egrep -q '^wifichoice'; then
	TEST="$(echo $LINE | cut -d ' ' -f 2-)"
	METHOD="$(echo $TEST | cut -d ' ' -f 1)"

	reset $IFACE
    fi
done

reset $IFACE
