#!/bin/sh
# ifupdown hook script for enhanced MadWifi (madwifi-ng) support in
# Debian and Debian-based distributions
#
# $Id: madwifi 30 2006-09-27 10:31:23Z mrenzmann $
#
# Copyright (c) 2006 Michael Renzmann <mrenzmann@otaku42.de>
# Portions Copyright (c) 2005 Matt Brown <matt@mattb.net.nz>
#
# For further information see:
# http://projects.otaku42.de/wiki/madwifi-ifupdown
#
# This is free software; you can redistribute it and/or modify it under the
# terms of the GNU General Public License version 2 as published by the Free
# Software Foundation.
#
# This is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# the madwifi-ifupdown package; if not, write to the Free Software Foundation,
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
LIBDIR=@LIBDIR@
if [ ! -d "$LIBDIR" ]; then
	echo "ERROR: $LIBDIR: no such directory"
	exit 1
fi

if [ ! -f $LIBDIR/defaults ]; then
	echo "ERROR: $LIBDIR/defaults: no such file"
	exit 1
fi
. $LIBDIR/defaults

if [ ! -f "$LIBDIR/functions.inc.sh" ]; then
	echo "ERROR: unable to load functions.inc.sh" >&2
	exit 1
fi
. $LIBDIR/functions.inc.sh


[ -x "$WLANCONFIG" ] || exit 0
[ -x "$HOSTAPD" ] || exit 0
[ -x "$IWPRIV" ] || exit 0

[ -n "$IF_ATH_PARENT" ] || exit 0

if [ "$VERBOSITY" -eq "0" ]; then
	exec 1>/dev/null
fi


# make most of the variables lowercase, to ease checking the settings later
IF_ATH_VAPTYPE=$(echo $IF_ATH_VAPTYPE | tr [A-Z] [a-z])
IF_ATH_VAPOPTS=$(echo $IF_ATH_VAPOPTS | tr [A-Z] [a-z])
IF_ATH_WIRELESSMODE=$(echo $IF_ATH_WIRELESSMODE | tr [A-Z] [a-z])
IF_ATH_HIDESSID=$(echo $IF_ATH_HIDESSID | tr [A-Z] [a-z])
IF_ATH_CHANNEL=$(echo $IF_ATH_CHANNEL | tr [A-Z] [a-z])
IF_ATH_SECURITY=$(echo $IF_ATH_SECURITY | tr [A-Z] [a-z])
IF_ATH_PAIRWISE=$(echo $IF_ATH_PAIRWISE | tr [A-Z] [a-z])
IF_ATH_WEP_MODE=$(echo $IF_ATH_WEP_MODE | tr [A-Z] [a-z])
IF_ATH_ACL=$(echo $IF_ATH_ACL | tr [A-Z] [a-z])

# make sure that all specified parameters are valid
if [ "$IFACE" = "$IF_ATH_PARENT" ]; then
	error "interface name is identical to parent device"
fi


if [ -n "$IF_ATH_VAPTYPE" ]; then
	case "$IF_ATH_VAPTYPE" in
	ap|master)
		VAPTYPE=ap
	;;
	sta|client)
		VAPTYPE=sta
	;;
	*)
		error "ath_vaptype $IF_ATH_VAPTYPE: invalid value"
	;;
	esac
# else use default from $LIBDIR/defaults
fi

if [ -n "$IF_ATH_VAPOPTS" ]; then
	OPT_BSSID=1
	OPT_NOSBEACON=0
	for opt in $IF_ATH_VAPOPTS; do
		case "$opt" in
		-bssid)
			OPT_BSSID=0
		;;
	
		bssid)
			OPT_BSSID=1
		;;
	
		nosbeacon) 
			OPT_NOSBEACON=1
		;;
	
		*)
			warning "ath_vapopts $opt: invalid value, ignored"
		;;
		esac
	done

	if [ "$OPT_BSSID" -eq "0" ]; then
		VAPOPTS="$VAPOPTS -bssid"
	else
		VAPOPTS="$VAPOPTS bssid"
	fi
	
	if [ "$OPT_NOSBEACON" -eq "1" ]; then
		VAPOPTS="$VAPOPTS nosbeacon"
	fi
# else use default from $LIBDIR/defaults
fi

if [ -n "$IF_ATH_WIRELESSMODE" ]; then
	case "$IF_ATH_WIRELESSMODE" in
	11a)
		WIRELESSMODE=11a
		G_ONLY=0
	;;
	11b)
		WIRELESSMODE=11b
		G_ONLY=0
	;;
	11bg)
		WIRELESSMODE=11g
		G_ONLY=0
	;;
	11g)
		WIRELESSMODE=11g
		G_ONLY=1
	;;
	*)
		error "ath_wirelessmode $IF_ATH_WIRELESSMODE: unknown value"
	;;
	esac
else
	# don't try to apply the defaults if there is the parent device has
	# already VAPs attached to it
	if parent_has_vaps $IF_ATH_PARENT; then
		WIRELESSMODE=
		G_ONLY=
	# else use defaults from $LIBDIR/defaults
	fi
fi


if [ -n "$IF_ATH_NICKNAME" ]; then
	NICKNAME="$IF_ATH_NICKNAME"
# else use default from $LIBDIR/defaults
fi


if [ -n "$IF_ATH_SSID" ]; then
	SSID="$IF_ATH_SSID"
# else use default from $LIBDIR/defaults
fi


if [ -n "$IF_ATH_HIDESSID" ]; then
	case "$IF_ATH_HIDESSID" in
	1|yes|on)
		HIDESSID=1
	;;
	0|no|off)
		HIDESSID=0
	;;
	*)
		error "ath_hidessid $IF_ATH_HIDESSID: invalid value"
	;;
	esac
# else use default from $LIBDIR/defaults
fi


if [ -n "$IF_ATH_RELAY" ]; then
	case "$IF_ATH_RELAY" in
	1|yes|on)
		RELAY=1
	;;
	0|no|off)
		RELAY=0
	;;
	*)
		error "ath_relay $IF_ATH_RELAY: invalid value"
	;;
	esac
# else use default from $LIBDIR/defaults
fi


if [ -n "$IF_ATH_CHANNEL" ]; then
	if isdigit $IF_ATH_CHANNEL; then
		CHANNEL=$IF_ATH_CHANNEL
	else
		error "ath_channel $IF_ATH_CHANNEL: invalid value"
	fi
# else use default from $LIBDIR/defaults
fi


if [ -n "$IF_ATH_TXPOWER" ]; then
	if ! isdigit $IF_ATH_TXPOWER; then
		error "ath_txpower $IF_ATH_TXPOWER: invalid value"
	fi
# else use whatever the card selects by itself
fi


if [ -n "$IF_ATH_ACL" ]; then
	case "$IF_ATH_ACL" in
	1|yes|on|white)
		ACL=1
		ACLMODE=white
	;;
	black)
		ACL=1
		ACLMODE=black
	;;
	0|no|off)
		ACL=0
		ACLMODE=
	;;
	*)
		error "ath_acl $IF_ATH_ACL: invalid value"
	;;
	esac
# else use default from $LIBDIR/defaults
fi

if [ "$ACL" -eq "1" ]; then
	if [ -z "$IF_ATH_ACL_FILE" ]; then
		error "ath_acl enabled, but ath_acl_file not set"
	fi
	if [ ! -f "$IF_ATH_ACL_FILE" ]; then
		error "ath_acl_file $IF_ATH_ACL_FILE: file does not exist"
	fi
fi


if [ -n "$IF_ATH_SECURITY" ]; then
	case "$IF_ATH_SECURITY" in
	none)
		SECURITY=none
	;;
	wep)
		SECURITY=wep
	
		if [ "$IF_ATH_WEP_KEY1$IF_ATH_WEP_KEY2$IF_ATH_WEP_KEY3$IF_ATH_WEP_KEY4" = "" ]; then
			error "ath_security wep: no keys defined"
		fi
	
		if [ -n "$IF_ATH_WEP_TXKEY" ]; then
			case "$IF_ATH_WEP_TXKEY" in
			[1-4])
				WEPTXKEY=$IF_ATH_WEP_TXKEY
			;;
			*)
				error "ath_wep_txkey $IF_ATH_WEP_TXKEY: invalid value"
			;;
			esac
		# else use default from $LIBDIR/defaults
		fi
	
		if [ -n "$IF_ATH_WEP_MODE" ]; then
			case "$IF_ATH_WEP_MODE" in
			open)
				WEPMODE=open
			;;
			shared)
				WEPMODE=restricted
			;;
			*)
				error "ath_wepmode $IF_ATH_WEP_MODE: invalid value"
			;;
			esac
		# else use default from $LIBDIR/defaults
		fi
	;;
	wpa1and2|wpa2|wpa1|wpa)
		SECURITY=wpa
		
		case "$IF_ATH_SECURITY" in
		wpa1and2)
			WPATYPE=3
		;;
		wpa2)
			WPATYPE=2
		;;
		wpa|wpa1)
			WPATYPE=1
		;;
		esac
	
		if [ -n "$IF_ATH_PAIRWISE" ]; then
			case "$IF_ATH_PAIRWISE" in
			tkip)
				PAIRWISE="TKIP"
			;;
			ccmp)
				PAIRWISE="CCMP"
			;;
			both)
				PAIRWISE="TKIP CCMP"
			;;
			*)
				error "ath_pairwise $IF_ATH_PAIRWISE:" \
				      "invalid value"
			;;
			esac
		# else use default from $LIBDIR/defaults
		fi

		HOSTAPD_CFGFILE="$HOSTAPD_CFGDIR/hostapd-$IFACE.conf"

		if [ "$VAPTYPE" = "ap" ]; then
			if [ -n "$IF_ATH_WPA_CFGFILE" ]; then
				if [ -n "$IF_ATH_WPA_PSKFILE$IF_ATH_WPA_PSK$IF_ATH_WPA_PASSPHRASE" ]; then
					warning "ath_wpa_cfgfile has been defined, " \
					        "other WPA-related settings will be ignored!"
				fi
				if [ ! -f "$IF_ATH_WPA_CFGFILE" ]; then
					error "ath_wpa_cfgfile $IF_ATH_CFGFILE: file does not exist"
				fi
				
				prepare_hostapd_cfgdir
				ln -s "$IF_ATH_WPA_CFGFILE" "$HOSTAPD_CFGFILE" || \
					error "failed to link user-defined hostapd.conf"
			else
				if [ -z "$IF_ATH_WPA_PSKFILE$IF_ATH_WPA_PSK$IF_ATH_WPA_PASSPHRASE" ]; then
					error "ath_security wpa: passphrase/PSK missing"
				fi
				if [ -n "$IF_ATH_WPA_PSKFILE" ] &&
				   [ ! -f "$IF_ATH_WPA_PSKFILE" ]; then
					error "ath_wpa_pskfile $IF_ATH_WPA_PSKFILE: file does not exist"
				fi
				if [ -n "$IF_ATH_WPA_PSK" ]; then
					if [ "${#IF_ATH_WPA_PSK}" -ne "64" ]; then
						error "ath_wpa_psk ...: exactly 64 hex characters needed"
					fi
					if ! ishex "$IF_ATH_WPA_PSK"; then
						error "ath_wpa_psk ...: only hex characters accepted"
					fi
				fi
				if [ -n "$IF_ATH_WPA_PASSPHRASE" ]; then
					n=${#IF_ATH_WPA_PASSPHRASE}
					if [ $n -lt 8 ] || [ $n -gt 63 ]; then
						error "ath_wpa_passphrase ...: 8 to 63 characters expected"
					fi
				fi
				
				write_hostapd_conf || \
					error "failed to write hostapd configuration"
			fi
		else
			# WPA for non-AP VAPs
			error "WPA not (yet) supported for $VAPTYPE-VAPs"
		fi
		
	;;
	*)
		error "ath_security $IF_ATH_SECURITY: invalid value"
	;;
	esac
# else use default from $LIBDIR/defaults
fi


# warn about using WEP encryption for AP VAPs
if [ "$SECURITY" = "wep" ] && [ "$VAPTYPE" = "ap" ]; then
	warning "You should really not rely on WEP encryption, as" \
	        "it can be broken within minutes easily. Use at least" \
		"WPA, even better WPA2, whenever possible!"
fi


# vap-type-related sanity checking
case "$VAPTYPE" in
ap)
	# if there is a sta vap on the same parent device, warn the user about
	# the "nosbeacon" option
	if parent_has_sta $IF_ATH_PARENT; then
		warning "$IF_ATH_PARENT already has a STA VAP," \
		        "creation of AP VAPs only succeed if that" \
				"STA VAP has been created with the 'nosbeacon'" \
				"option set!"
	fi
;;
sta)
	# only one sta vap allowed per parent device
	if parent_has_sta $IF_ATH_PARENT; then
		error "$IF_ATH_PARENT already has a STA VAP, " \
		      "only one STA allowed per parent"
	fi
	
	# the nosbeacon parameter must be used in the call to wlanconfig
	# when creating a sta vap on a parent device that already has at least
	# one ap vaps
	if parent_has_ap $IF_ATH_PARENT; then
		if [ -z "$(echo $VAPOPTS | grep nosbeacon)" ]; then
			VAPOPTS="$VAPOPTS nosbeacon"
		fi
	fi
;;
esac


# now apply the desired settings
if ! $IFCONFIG $IF_ATH_PARENT &>/dev/null; then
	error "ath_parent $IF_ATH_PARENT: no such device"
fi

if $IFCONFIG $IFACE &>/dev/null; then
	# we simply bring down the interface and destroy the VAP here
	# ifup takes care that we don't accidentally work with an interface
	#     that has been brought up by a previous ifup run
	$IFCONFIG $IFACE down || \
		error "failed to bring down existing interface $IFACE"
	$WLANCONFIG $IFACE destroy || \
		error "failed to destroy VAP $IFACE"
fi

case "$VAPTYPE" in
ap)
	modprobe wlan_scan_ap || \
		error "failed to enable scanning support for AP mode"
;;
sta)
	modprobe wlan_scan_sta || \
		error "failed to enable scanning support for STA mode"
;;
esac

$WLANCONFIG $IFACE create wlandev $IF_ATH_PARENT wlanmode $VAPTYPE $VAPOPTS || \
	error "failed to create VAP $IFACE (parent $IF_ATH_PARENT)"

if [ -n "$WIRELESSMODE" ]; then
	$IWPRIV $IFACE mode $WIRELESSMODE || \
		error "failed to set wireless mode to $WIRELESSMODE"
fi
if [ -n "$G_ONLY" ]; then
	$IWPRIV $IFACE pureg $G_ONLY || \
		error "failed to set pureg to $G_ONLY"
fi

if [ -n "$CHANNEL" ]; then
	$IWCONFIG $IFACE channel $CHANNEL || \
		error "failed to set channel to $CHANNEL"
fi


$IWCONFIG $IFACE essid "$IF_ATH_SSID" || \
	error "failed to set ssid to \"$IF_ATH_SSID\""



case "$VAPTYPE" in
ap)
	$IWPRIV $IFACE hide_ssid $HIDESSID || \
		error "failed to set hide_ssid to $HIDESSID"

	$IWPRIV $IFACE ap_bridge $RELAY || \
		error "failed to set ap_bridge to $RELAY"
;;

sta)
	$IWCONFIG $IFACE nick "$NICKNAME" || \
		error "failed to set nickname to \"$NICKNAME\""

;;
esac

# in case hostapd is not going to be started (when working with no or WEP
# encryption) we need to set the MAC ACL stuff manually
if [ "$SECURITY" != "wpa" ] && [ "$ACL" -eq "1" ]; then
	modprobe wlan_acl || \
		error "failed to enable MAC ACL support"
	
	case "$ACLMODE" in
	white)
		$IWPRIV $IFACE maccmd 1 || \
			error "failed to set ACL mode to white"
	;;
	black)
		$IWPRIV $IFACE maccmd 2 || \
			error "failed to set ACL mode to black"
	;;
	esac
	
	while read line; do
		line=${line## *}
		if [ -z "$line" ] || [ "${line:0:1}" = "#" ]; then
			continue
		fi
		
		mac=${line%% *}
		
		$IWPRIV $IFACE addmac $mac || \
			error "failed to add $mac to access control list"
	done < $IF_ATH_ACL_FILE
fi


case "$SECURITY" in
none)
	# nothing to do here
;;
wep)
	modprobe wlan_wep || \
		error "failed to load WEP encryption support module"
		
	for idx in 1 2 3 4; do
		NAME="IF_ATH_WEP_KEY$idx"
		eval KEY=\$$NAME
		
		if [ -n "$KEY" ]; then
			$IWCONFIG $IFACE key [$idx] $KEY || \
				error "failed to set WEP key 1 to $KEY"
		fi
	done
	
	$IWCONFIG $IFACE key [$WEPTXKEY] || \
		error "failed to set TX WEP key"

	$IWCONFIG $IFACE key $WEPMODE || \
		error "failed to set WEP mode"
		
	case "$WEPMODE" in
	open)
		$IWPRIV $IFACE authmode 1 || \
			error "failed to set authentication mode"
	;;
	restricted)
		$IWPRIV $IFACE authmode 2 || \
			error "failed to set authentication mode"
	;;
	esac	
;;

wpa)
#	if [ "$VAPTYPE" = "ap" ]; then
#	
#	fi
;;
esac


# that's all, folks!
exit 0
