#!/bin/sh

PATH="/usr/lib/alterator-net-common:$PATH"

BR='	'

#########

exit_handler()
{
    local rc=$?
    trap - EXIT
    [ -n "$WATCH_PID" ] && kill "$WATCH_PID" 2>/dev/null
    exit $rc
}

trap exit_handler HUP INT QUIT TERM EXIT

#########

shell_add_or_subst()
{
	local name="$1" && shift
	local value="$1" && shift
	local file="$1" && shift

	[ -f "$file" ] || touch "$file"
	if grep -qs "^$name" "$file"; then
		/bin/sed -r -i "s,^$name.*,$name$value," -- "$file"
		return 0
	fi
	printf %s\\n "$name$value" >> "$file"
}

shell_get()
{
        local name="$1" && shift
        local file="$1" && shift

        [ -f "$file" ] &&
        grep -qs "^$name=" "$file" &&
        grep "^$name=" "$file"|sed "s,^$name=,,"
}

#########

supplicant_driver()
{
    local drv="$(ifdriver "$1")"
    case "${drv##*/}" in
	ath_pci)
	    echo "madwifi"
	    ;;
	ipw2100)
	    echo "ipw"
	    ;;
	*)
	    echo "wext"
	    ;;
    esac
}

supplicant_sure()
{

    local iface="$1";shift

    echo "try to start supplicant for $iface..." >&2

    local ifacedir="/etc/net/ifaces/$iface"
    local options="$ifacedir/options"
    local supp_conf="$ifacedir/wpa_supplicant.conf"

    #prepare interface
    [ -d "$ifacedir" ] ||
	mkdir "$ifacedir"
	 
    if [ ! -s "$options" ]; then
	shell_add_or_subst "TYPE=" "eth" "$options" #wi-fi devices has strange names
	shell_add_or_subst "BOOTPROTO=" "static" "$options" #protection from DHCP timeout
        shell_add_or_subst "WPA_DRIVER=" "$(supplicant_driver "$iface")" "$options"
    fi

    [ -s "$supp_conf" ] ||
	printf 'ctrl_interface=/var/run/wpa_supplicant\nupdate_config=1\n'>"$supp_conf"

    ifdown "$iface"
    ifup "$iface"
    /sbin/ip link set up dev "$iface" #hardware up: for some ugly wifi cards
}

supplicant_request()
{
    local iface="$1"; shift

    if ! /usr/sbin/wpa_cli -i"$iface" "$@";then
	supplicant_sure "$iface"
	/usr/sbin/wpa_cli -i"$iface" "$@"
    fi

    local supp_conf="/etc/net/ifaces/$iface/wpa_supplicant.conf"

    [ -s "$supp_conf" ] && chmod 600 "$supp_conf"
}

supplicant_watch()
{
    while true;do
	alterator-mailbox-send 'wpa-supplicant'
	sleep 3
    done
}

make_psk(){
  wpa_passphrase "$1" "$2" | awk -F'=' '/^[[:space:]]*psk=/{print $2}'
}

#########

scan_results=""

get_scan_results(){
    scan_results=`supplicant_request "$1" scan_results | sed '1d'`
}

list_scan_results()
{
    #bssid / frequency / signal level / flags / ssid
    [ -n "$scan_results" ] || get_scan_results "$1" > /dev/null

    echo "$scan_results" |\
	awk -F"$BR" '{printf "(\"%s\" label \"%s (%s)\" bssid \"%s\" flags \"%s\")\n",$5,$5, $1,$1,$4}'
    printf "(\"\" label \"%s\")" "`_ "Other network"`"
}

list_networks()
{
    #network id / ssid / bssid / flags
    # essid can be empty! Use "-"
    supplicant_request "$1" list_networks | sed '1d;s/\t\t/\t-\t/' |
      while read id essid bssid flags; do
        [ "$essid" == "-" ] && essid=""
        local current="#f"
        local enabled="#f"
        local label="$essid"
        if [ "$flags" != "[DISABLED]" ]; then  enabled="#t"; label="+ $label"
        else label="-- $label"; fi
        if [ "$flags" == "[CURRENT]" ];  then current='#t'; label="* $essid"; fi

        printf "(\"%s\" label \"%s\" essid \"%s\" enabled %s current %s)" \
          "$id" "$label" "$essid" "$enabled" "$current"
      done
}

list_auth_types()
{
    #WPA-PSK WPA-EAP IEEE8021X WPA-NONE NONE
    for i in $(supplicant_request "$1" get_capability key_mgmt);do
	case "$i" in
	    NONE)
	        printf '("nopasswd" label "%s")' "`_ "No password"`"
	        printf '("wep" label "%s")' "`_ "WEP Password"`"
		;;
	    WPA-PSK)
	        printf '("wpa-psk" label "%s")' "`_ "WPA Personal (WPA-PSK)"`"
		printf '("wpa2-psk" label "%s")' "`_ "WPA2 Personal (WPA-PSK)"`"
		;;
#	    WPA-EAP)
#		printf '("wpa-eap" label "%s")' "`_ "WPA Enterprise (WPA-EAP)"`"
#		printf '("wpa2-eap" label "%s")' "`_ "WPA2 Enterprise (WPA-EAP)"`"
#		;;
	esac
    done
}

list_eap_methods()
{
    for i in $(supplicant_request "$1" get_capability eap);do
	printf '("%s")' "$i"
    done
}

list_pairwise_types()
{
    for i in $(supplicant_request "$1" get_capability pairwise);do
	printf '("%s")' "$i"
    done
}

#connection status

status=

get_status(){
  status=`supplicant_request "$1" status`
}

read_status(){
    printf 'iface "%s"\n' "$1"
    [ -n "$status" ] || get_status "$1" > /dev/null
    echo "$status" |
	{
	    local SSID=""
	    local BSSID=""
	    local STATE=""
	    local IP=""
	    while read line; do
		case "$line" in
		    ssid=*)
            		SSID=${line##ssid=}
			;;
		    bssid=*)
                	BSSID=${line##bssid=}
			;;
		    wpa_state=*)
                	STATE=${line##wpa_state=}
			;;
		    ip_address=*)
                	IP=${line##ip_address=}
			;;
		esac
	    done
	    case "$STATE" in
    		COMPLETED)
    		    printf 'iface_status \"%s \\\"%s\\\", %s: %s, %s: %s\" ' \
        	    "`_ "Connected to network"`" "$SSID"\
        	    "`_ "access point"`" "$BSSID" "`_ "IP address"`" $IP
    		    ;;
    		SCANNING)
    		    printf 'iface_status \"%s\" ' "`_ "Scanning..."`"
    		    ;;
		*)
		    printf "iface_status \"$STATE\" "
	    esac
	}
}

new_name=

#network information
read_network()
{
    local iface="$1";shift
    local id="${new_name:-${1:-0}}";shift # use new_name if exists
    [ "$id" == "-1" ] && id="0"
    new_name=
    local value=

    printf 'name \"%s\" ' "$id"

    # essid can be empty! Use "-"
    supplicant_request "$iface" list_networks | sed '1d;s/\t\t/\t-\t/' |
      while read net_id ssid bssid flags; do
        if [ "$net_id" == "$id" ]; then
          [ "$essid" == "-" ] && essid=""
          printf 'bssid \"%s\"' "$bssid"
          if [ "$flags" != "[DISABLED]" ]; then printf 'enabled #t'
          else printf 'enabled #f'; 
          fi
          if [ "$flags" == "[CURRENT]" ]; then printf 'current \"%s\"' "`_ "Current network"`"
          else printf 'current ""'; 
          fi
        fi
      done

    value="$(supplicant_request "$iface" get_network "$id" "ssid")"
    [ "$value" != "FAIL" ] && printf 'essid %s ' "$value"

    value="$(supplicant_request "$iface" get_network "$id" "pairwise")"
    [ "$value" != "FAIL" ] && printf 'pairwise "%s"' "$value"

    local key_mgmt="$(supplicant_request "$iface" get_network "$id" "key_mgmt")"
    local proto="$(supplicant_request "$iface" get_network "$id" "proto")"

    case "$key_mgmt" in
	NONE)
	    value="$(supplicant_request "$iface" get_network "$id" "wep_key0")"

	    if [ "$value" = "FAIL" -o -z "$value" ];then
		echo 'auth_type "nopasswd"'
	    else
		echo 'auth_type "wep"'
	    fi
	    ;;
	WPA-PSK)
	    if [ "$proto" = "RSN" -o "$proto" = "WPA2" ];then
		echo 'auth_type "wpa2-psk"'
	    else
		echo 'auth_type "wpa-psk"'
	    fi
	    ;;
	WPA-EAP)
	    if [ "$proto" = "RSN" -o "$proto" = "WPA2" ];then
		echo 'auth_type "wpa2-eap"'
	    else
		echo 'auth_type "wpa-eap"'
	    fi
	    ;;
    esac
}

write_network()
{
    local iface="$1";shift

#    echo ">>> write_network $iface $in_name $in_auth_type" > /dev/stderr

    if test_bool "$in_enabled"; then supplicant_request "$iface" enable "$in_name" > /dev/null
    else supplicant_request "$iface" disable "$in_name" > /dev/null; fi


    supplicant_request "$iface" set_network "$in_name" ssid "\"$in_essid\"" >/dev/null
    supplicant_request "$iface" set_network "$in_name" scan_ssid 1 >/dev/null

    local passwd_error=0;

    case "$in_auth_type" in
	nopasswd)
	    supplicant_request "$iface" set_network "$in_name" key_mgmt "NONE" >/dev/null
	    supplicant_request "$iface" set_network "$in_name" wep_key0 "\"\"" >/dev/null
	    ;;
        wep)
	    supplicant_request "$iface" set_network "$in_name" key_mgmt "NONE" >/dev/null
	    [ -n "$in_key" ] &&
	      supplicant_request "$iface" set_network "$in_name" wep_key0 "$in_key" >/dev/null
	    ;;
	wpa-psk|wpa2-psk)
	    supplicant_request "$iface" set_network "$in_name" key_mgmt "WPA-PSK" >/dev/null
	    supplicant_request "$iface" set_network "$in_name" pairwise "$in_pairwise" >/dev/null

	    # Меняем пароль, только если он не пуст и wpa_passphrase не 
	    # отдает пустой hash (короткий пароль)
	    # Такие пароли supplicant может записывать в конф.файл, но читать такой файл 
	    # уже не сможет!!!
	    # Кроме того, нельзя допускать, чтобы при записи новой сети с wpa-psk пароль оставался
	    # пустым!!!

	    # Установим пароль, если он не пуст
	    if [ -n "$in_key" ]; then
	      local hash=$(make_psk "$in_essid" "$in_key")
	      if [ -n "$hash" ]; then 
	        supplicant_request "$iface" set_network "$in_name" psk "$hash" >/dev/null
	      else 
	        # короткий пароль -- надо ругаться!
	        passwd_error=1
	      fi
	    fi

	    # Если пароля так и нет
	    local passwd=$(supplicant_request "$iface" get_network "$in_name" psk)
	    if [ "$passwd" != "*" ]; then
	      # Принимаем крайне меры, а то все упадет...
	      supplicant_request "$iface" set_network "$in_name" psk "\"password\"" >/dev/null
	    fi

	    if [ "$in_auth_type" = "wpa-psk" ];then
	      supplicant_request "$iface" set_network "$in_name" proto "WPA" >/dev/null
	    else
	      supplicant_request "$iface" set_network "$in_name" proto "WPA2" >/dev/null
	    fi
	    ;;
    esac

    supplicant_request "$iface" save_config >/dev/null
    return $passwd_error
}

print_excludes()
{
    local name="$1";shift;

    for i in $@;do
	printf ' exclude ("%s" %s) ' "$name" "$i"
    done
}


#turn off auto expansion
set -f

. alterator-sh-functions



on_message()
{

#echo "action:      $in_action" > /dev/stderr
#echo "orig_action: $in_orig_action" > /dev/stderr
#echo "name:        $in_name" > /dev/stderr
#echo "iface:       $in_iface" > /dev/stderr
#echo "scanned_net: $in_scanned_net" > /dev/stderr
#echo "_objects:    $in__objects" > /dev/stderr
#[ -n "$in_btn_apply" ]  && echo "-- Apply" > /dev/stderr
#[ -n "$in_btn_delete" ] && echo "-- Delete" > /dev/stderr
#[ -n "$in_btn_new" ]    && echo "-- New" > /dev/stderr
#[ -n "$in_btn_rescan" ] && echo "-- Rescan" > /dev/stderr
#[ -n "$in_btn_reconfigure" ] && echo "-- Reconf" > /dev/stderr
#echo > /dev/stderr

	case "$in_action" in
		constraints)
			local is_new="$([ "$in_orig_action" = "new" ] && echo "#t" || echo "#f")"

			echo '('
			printf 'essid (label "%s" required %s)\n' "`_ "Network name"`" "$is_new"
			printf 'bssid (label "%s")\n' "`_ "Access point"`"
			printf 'status (label "%s")\n' "`_ "Status"`"
#			printf 'auth_type (label "%s" %s %s %s %s)\n' \
#			    "`_ "Security"`" \
#			    "$(print_excludes "nopasswd" key user pairwise eap)" \
#			    "$(print_excludes "wep" user pairwise eap)" \
#			    "$(print_excludes "wpa-psk" user eap)" \
#			    "$(print_excludes "wpa2-psk" user eap)"
			printf 'user (label "%s")\n' "`_ "User name"`"
			printf 'key (label "%s")\n' "`_ "Network key"`"
			printf 'pairwise (label "%s")\n' "`_ "Encryption"`"
			printf 'eap (label "%s")\n' "`_ "EAP method"`"
			echo ')'
			;;
		monitor)
			if [ -z "$WATCH_PID" -a "$in_mode" = "on" ];then
			    supplicant_watch&
			    WATCH_PID=$!
			else
			    [ -n "$WATCH_PID" ] && kill "$WATCH_PID"
			    WATCH_PID=
			fi

			write_nop
			;;
		list)
			if [ -z "$in_iface" ] || ! ifcheckwireless "$in_iface"; then
			  write_error "`_ "Not a wireless interface"`"
			  return
			fi

			echo '('
			case "${in__objects}" in
			    scan_results)
				list_scan_results "$in_iface"
				;;
			    networks)
				list_networks "$in_iface"
				;;
			    auth_types)
				list_auth_types "$in_iface"
				;;
			    pairwise_types)
				list_pairwise_types "$in_iface"
				;;
			    eap_methods)
				list_eap_methods "$in_iface"
				;;
			esac
			echo ')'
			;;
		read)
			if [ -z "$in_iface" ] || ! ifcheckwireless "$in_iface"; then
			  write_error "`_ "Not a wireless interface"`"
			  return
			fi
			echo '('
			read_status "$in_iface"
			read_network "$in_iface" "$in_name"
			echo ')'
			;;
		write)
			if [ -z "$in_iface" ] || ! ifcheckwireless "$in_iface"; then
			  write_error "`_ "Not a wireless interface"`"
			  return
			fi

			[ -n "$in_select" ] &&\
			  read_network "$in_iface" "$in_name"

			if [ -n "$in_btn_apply" ]; then
			  if ! write_network "$in_iface"; then
			    write_error "`_ "Password is too short"`"
			    return
			  fi
			  get_status "$in_iface" > /dev/null
			fi

			if [ -n "$in_btn_delete" ]; then 
			  supplicant_request "$in_iface" disable "$in_name" > /dev/null
			  supplicant_request "$in_iface" remove_network "$in_name" >/dev/null
			  supplicant_request "$in_iface" save_config >/dev/null
			  new_name=$((in_name-1))
			  [ $(( $new_name < 0 )) == "1" ] && new_name=0
			fi

			if [ -n "$in_btn_reconfigure" ]; then
			  supplicant_request "$in_iface" reconfigure >/dev/null
			  sleep 1
			  get_status "$in_iface" >/dev/null
			fi

			if [ -n "$in_btn_status" ]; then
			  get_status "$in_iface" >/dev/null
			fi

			if [ -n "$in_btn_rescan" ]; then
			   supplicant_request "$in_iface" scan >/dev/null
			   get_scan_results "$in_iface" >/dev/null
			fi

			if [ -n "$in_btn_new" ]; then
			  local id="$(supplicant_request "$in_iface" add_network)"
			  if [ "$id" = "FAIL" ]; then
			    write_error "`_ "Unable to create network"`"
			    return
			  fi

			  new_name="$id"
			  in_name="$id"
			  in_essid=$in_scanned_net
#			  local bssid=`echo "$scan_results" | awk -F"$BR" "\"$in_scanned_net\" == \\\$5 {print \\\$1}"`
			  local flags=`echo "$scan_results" | awk -F"$BR" "\"$in_scanned_net\" == \\\$5 {print \\\$4}"`
			  in_enabled="#t"
			
			  in_auth_type="nopasswd"
			  if [ "${flags/WEP}"      != "$flags" ]; then in_auth_type="wep";fi
			  if [ "${flags/WPA-PSK}"  != "$flags" ]; then in_auth_type="wpa-psk";fi
			  if [ "${flags/WPA2-PSK}" != "$flags" ]; then in_auth_type="wpa2-psk";fi
			  if [ "$in_auth_type" == "wpa-psk" -o "$in_auth_type" == "wpa2-psk" ]; then
			    in_pairwise="TKIP"
			    if [ "${flags/CCMP}" != "$flags" ]; then in_pairwise="CCMP"; fi
			  fi
			  write_network "$in_iface"
			fi
			write_nop
			;;
		*)
			echo '#f'
			;;
	esac
}

message_loop
