#!/usr/bin/env bash

# shtumble --
# a wireless network stumbler and configuration tool, written in bash.
#
# versiondate: Fri Feb  1 15:29:20 EST 2008
# versiondate: 2012-04-27 03:15:40-07:00 Noah Spurrier <noah@noah.org>
#   Added alternate and normal screen support using 'tput'.
#
# Copyright 2004,2007 by Paul Fox
# Released under the GNU General Public License, Version 2, or later.
# A copy of the GPL may be found at the end of this file.
#

# Intro:
# ------
# in looking around for a network stumbler tool for a small
# system of mine, i was appalled at how they all seemed too big,
# or over-featured, or unfinished.  on a handheld, i really don't
# need many megabytes of gui library just to find and establish a
# network connection at my local starbuck's, or at work.  this
# script is the result of that frustration.  it will work in an
# xterm, or on a linux (or other ansi-style) console.  it doesn't
# use curses, or even termcap/terminfo.  it repaints a lot.  big
# deal.
#
# i've used this with wireless tools (iwconfig and iwlist)
# version 27, on linux kernels 2.4.25 and 2.6.20.  my wireless
# cards are an aironet 350 and an ipw2200.  i believe that any
# wireless setup that supports "iwlist ethX scan" will work.
#
# some of the most recent changes were inspired by features
# of "wicd" (<http://wicd.sourceforge.net>), a wireless
# connection manager similar in spirit to shtumble.  wicd is
# prettier, and more capable, but a bit more complex.
#
# hope you find shtumble useful.  - pgf (July 2004, June 2007)
# pgf@foxharp.boston.ma.us

# Requirements:
# ------------
# shtumble is mostly built on "normal" shell tools (sed, grep,
# cat, etc).  the wireless tools (iwconfig/iwlist) are used to
# manage the wireless interface.  if WPA encryption is needed,
# wpa_supplicant is invoked.

# Display:
# -------
# wireless access points are presented in color:  a red access
# point has encryption turned on (and its encryption information
# hasn't been configured into shtumble yet).  un-encrypted (or
# known) access points are presented in yellow or green,
# depending on signal strength.  when a new AP is detected (or an
# old one returns after a configurable absence period), shtumble
# will beep to alert you of its presence.  the display also shows
# a summary of the output of ifconfig and iwconfig, the current
# contents of /etc/resolv.conf, the current default routes, and
# the last 5 lines produced by the DHCP client, so you can track
# its progress.

# Usage:
# ------
# there are no command-line arguments to shtumble.  once running
# (it must be run as root), configuration can be done via
# internal commands, which will bring up your editor on the
# appropriate file(s).  while shtumble is running, it will
# continuously scan for nearby access points, presenting a menu
# from which to select.
#
# i invoke shtumble directly from my laptop's "Wifi" hotkey, by
# inserting the following code near the top of /etc/acpi/wireless.sh.
# (check the config in /etc/acpi/events/* for the right script to
# modify.)  this gives me easy access to shtumble whether in X or
# at a linux console:
#     # alternately run and kill shtumble
#     pf=/var/run/shtumble.pid
#     if [ -f $pf ]; then
#         kill $(cat $pf)
#     else
#         exec openvt -w -s /usr/local/bin/shtumble &
#     fi
#     exit
# note that this lets shtumble run even if no one is logged in!
# when no one is logged in, shtumble by default will restrict
# operations that can allow shell escapes, but you still might
# want to think about this.  my assumption is that if someone has
# physical control of your laptop, you have worse things to worry
# about than having them run your stumbler.
#
# to help ubuntu, at least, restore the interface after
# suspend/resume, add the following stanza to the interfaces
# file -- the scripts in /etc/acpi down and up the interface
# across suspends, and these entries make the right things
# happen:
#       iface eth1 inet manual
#               up /usr/local/bin/shtumble restart
#               down /usr/local/bin/shtumble disconnect
#
#

# Configuration:
# ------------------
# by default, the config directory is in /usr/local/etc/shtumble.
# if this directory (or whichever you choose, below) does not
# exist, shtumble will ask to create it when first run.
#
# configuration consists of one "global" config file, and
# multiple per-network files, each of which describes how the
# wireless interface should be configured for that network.  the
# "how" includes both the wireless authentication parameters and
# the IP configuration.
#
# it's likely you'll want to at least look at the global config.
# (use the 'c' key once shtumble is running -- it will bring up
# your editor (probably vi) on /usr/local/etc/shtumble/config.)
# most likely thing needing change is the name of your wireless
# device -- "eth1" is the default.
#
# if all you access are public networks, you may not need to
# configure anything else.  but if you need configure WEP keys,
# or static IP addressing, you'll want to use the 'e' or 'n'
# commands.  the 'e' command will edit the the config of the
# currently selected network (i.e., the network must be currently
# "visible" to shtumble).  the 'n' command will create a new
# network config -- you'd probalby only do this to pre-configure
# shtumble when the network is not "in view", or if it hides its
# SSID.
#
# if your system doesn't use dhclient for dhcp, you may need to
# modify the functions do_dhcp/kill_dhcp -- the code for the other
# dhcp clients is not well tested.  please send me any changes
# you make -- i'd like to see them.
if [ -f /boot/olpc_build ]
then
    XO_LAPTOP=true
else
    XO_LAPTOP=;
fi

# change shtumble's "home" directory here.
if [ -d /home/olpc/.shtumble ]
then
    SHTUMBLE_HOME=/home/olpc/.shtumble
else
    # default:
    SHTUMBLE_HOME=/usr/local/etc/shtumble
fi

# very little else should need to change from here down.
# -------------------
SHTUMBLE_CONF=$SHTUMBLE_HOME/config
SHTUMBLE_NETCONFS=$SHTUMBLE_HOME/netconfigs
SHTUMBLE_BSSIDS=$SHTUMBLE_HOME/bssid_links

IWLIST=/tmp/shtumble$$
IFCONF_LOG=/tmp/shtumble$$.iflog
DHCP_PID_FILE=/var/run/shtumble-dhcp.$DEVICE.pid
PIDFILE=/var/run/shtumble.pid

usage()
{
    cat <<EOF >&3
usage: ${0##*/} [restart|connect <essid>|disconnect|off|radiooff|help]
    no arguments will bring up the shtumble network selector.
    "restart" will retry with the last attempted association.
    "connect" and "disconnect" attempt the obvious.
    "radiooff" (or just "off") will power down the wireless.
    "help" shows a copy of the on-screen help.
EOF
    exit

}

# just in case /sbin isn't in $PATH
PATH=/sbin:/usr/sbin:$PATH

check_root()
{
    if [ $(whoami) != root ]
    then
        echo ${0##*/}:  you need to run this as root
        exit 1
    fi
}

# if we're running on a console screen, someone had better
# be logged in on the console, or via X.
check_logged_in()
{

    # bypass check?
    yes_or_true $RESTRICT_IF_NO_LOGIN || return 0

    # we're not on the console
    case $(tty) in *pts*) return 0 ;; esac

    # if someone's logged in locally, then "w" will show the
    # "from" field as '-', and they won't be on a pseudotty
    #test "$(w -hs | egrep ' - ' | grep -v pts)" && return 0
    # more directly:  just look for a console login
    test "$(w -hs | egrep '\<tty[0-9]\>')" && return 0

    echo sorry
    sleep 1
    return 1
}

helptext()
{
    # using a space char as prefix give subtle control over what's
    # reformatted and what's not.
    fmt --prefix=" "  <<EOF

 Use the up and down arrows (or 'j' and 'k') to move among the available
 networks.  Select one with the Enter key.

 ${COLOR_GOOD}Green${COLOR_NOBOLD} networks are open, or are known to you,
 ${COLOR_WARN}Yellow${COLOR_NOBOLD} networks are open, but
 have a relatively weak signal.
 ${COLOR_ALERT}Red${COLOR_NOBOLD} networks are encrypted, and you don't
 know (or haven't configured) the encryption key.

'r' toggles the wireless radio on and off.
'd' disconnects the wireless interface.
'a' toggles whether just accessible (known or open) networks will
     be shown, or all networks, even if unconnectable.
'l' toggles whether all hidden nets will be listed, or only
     those which have been connected to before, and are in range now.
's' toggles full/short status display, allowing more nets on the screen.
'c' brings up an editor on the global config file.
'e' brings up an editor for the selected network's config file.
'n' brings up an editor for a new network's config file, after
     prompting you for its name.
'x' lets you delete an existing network configuration.  This
     is rarely necessary.
'p' (space) will pause the program, so it can be left up without
     looping continuously.
'q' quits the program.  Network will remain connected.


 shtumble is:
   Copyright 2004,2007 by Paul Fox, and is released under
   the GNU General Public License, Version 2.  A copy of GPLv2
   is included in the shtumble source script.

EOF
}

cmd_help()
{
    check_logged_in || return

    echo $COLOR_NOBOLD
    clearscreen
    (
        echo 'shtumble help          Use 'q' to leave this help screen.'
        helptext
        echo 'shtumble help          Use 'q' to leave this help screen.'
    ) | LESS=RC less -X
    look_alive
}

# i guess you'd call this part "skin support"  :-)
init_ansi_escapes()
{
    COLOR_GREEN="$(echo -en '\033[1;32m')"
    COLOR_RED="$(echo -en '\033[1;31m')"
    COLOR_BLUE="$(echo -en '\033[1;34m')"
    COLOR_YELLOW="$(echo -en '\033[1;33m')"
    COLOR_BOLD="$(echo -en '\033[1;39m')"
    COLOR_NOBOLD="$(echo -en '\033[0;39m')"

    COLOR_NORMAL="$COLOR_NOBOLD"
    #COLOR_NORMAL="$COLOR_BOLD"
    COLOR_NEUTRAL=$COLOR_BLUE
    COLOR_GOOD=$COLOR_GREEN
    COLOR_WARN=$COLOR_YELLOW
    COLOR_ALERT=$COLOR_RED
    COLOR_HEADER="$COLOR_NEUTRAL"
    BAR="${COLOR_HEADER} ===== ${COLOR_NORMAL}"
}

yes_or_true()
{
    test $# = 0 && return 1

    case $1 in
    [yYtT]*) return 0 ;;
    *)     return 1 ;;
    esac
}

if [ "$XO_LAPTOP" ]
then
    radio_on()
    {
        rm -f $SHTUMBLE_HOME/radio_off
        echo 1 >/sys/power/wlan-enabled
    }

    radio_off()
    {
        touch $SHTUMBLE_HOME/radio_off
        echo 0 >/sys/power/wlan-enabled
    }
else
    radio_on()
    {
        rm -f $SHTUMBLE_HOME/radio_off
        iwconfig $DEVICE txpower auto || iwconfig $DEVICE txpower on
    }

    radio_off()
    {
        touch $SHTUMBLE_HOME/radio_off
        iwconfig $DEVICE txpower off
    }
fi

is_radio_off()
{
    test -f $SHTUMBLE_HOME/radio_off
}

cmd_toggle_radio()
{
    if is_radio_off
    then
        radio_on
        echo -n Radio has been turned on ...
    else
        radio_off
        echo -n Radio has been turned off ...
    fi
    sleep 1
}

cmd_toggle_status()
{
    yes_or_true $FULLSTATUS && FULLSTATUS=false || FULLSTATUS=true
}

cmd_toggle_list_allhidden()
{
    if yes_or_true $LIST_ALL_HIDDEN
    then
        LIST_ALL_HIDDEN=false
        echo -n Will only show configured-as-hidden networks when in range.
    else
        LIST_ALL_HIDDEN=true
        echo -n Will show configured-as-hidden networks always.
    fi
    sleep 2
}

cmd_toggle_accessible_only()
{
    if yes_or_true $ACCESSIBLE_ONLY
    then
        ACCESSIBLE_ONLY=false
        echo -n Will show all networks, even if encrypted.
    else
        ACCESSIBLE_ONLY=true
        echo -n Will only show known or open networks.
    fi
    sleep 2
}

disconnect()
{
    kill_dhcp
    ifconfig $DEVICE 0.0.0.0 down
    iwconfig $DEVICE key off
    iwconfig $DEVICE essid "---"
    killall wpa_supplicant 2>/dev/null

    last_assoc=;
    : >$IFCONF_LOG
}

cmd_disconnect()
{
    disconnect
    # only remove the essid file on manual disconnects, since from
    # the command line, we'll likely want to be able to use "restart"
    # later.
    rm -f $SHTUMBLE_HOME/last_essid
    echo -n $DEVICE disconnected ...
    sleep 1
}

create_template_global_config()
{
    cat <<-EOF
	# global config file for shtumble

	# the author owns an OLPC XO laptop, so this helps configure
	# that kind of device, by changing some defaults.  it's here,
	# rather than in the main script, for full transparency.
	if [ -f /boot/olpc_build ]
	then
	    /sbin/service NetworkManager stop
	    # /sbin/service network stop
	    /sbin/ifconfig lo up
	    DEVICE=eth0
	fi

	# which is the wireless device?  default to eth1
	# DEVICE=eth1

	# this is the default time between rescans, in seconds.  a rescan
	# will also happen on any arrow-key or Enter keypress.
	# LOOPTIME=4

	# should the screen show just the visible networks, or
	# the networks plus a more full description of the state
	# of the interface?  this is just the initial setting --
	# it can be changed on the fly with the 's' command.
	# FULLSTATUS=false

	# only allow config editing and help browsing if someone
	# is logged in somewhere.
	# RESTRICT_IF_NO_LOGIN=true

	# should shtumble always list all nets that are configured as
	# hidden?  by default, once a hidden net has been connected
	# to successfully, it will only show in the selection list when
	# it is within range.
	# LIST_ALL_HIDDEN=false

	# how many networks should we try and display?
	# (this doesn't include local configuration for hidden nets --
	# only nets which we find via scanning.)
	# MAX_NETWORKS=999

	# should shtumble only list nets that are known, or open?
	# by default, all nets are shown, even if they are encrypted
	# and inaccessible.
	# ACCESSIBLE_ONLY=false

	# time after which a previously seen station is considered new, if
	# it hasn't been heard from in a while.  this will cause a beep
	# for new access points, but not repeated beeps if they drift in and
	# out.  set this to a very large number to suppress beeps altogether.
	# DO_BEEPS=no           # should we beep?
	# BEEPTIME=10           # if so, after how long an absence?

	# which dhcp client should be run?  currently, valid values are
	# dhclient, dhcpcd, or udhcpc.
	# DHCP_CLIENT=dhclient

	# extra options to pass to the dhcp client program.  for
	# instance, if you use udhcpc, you may need "-s scriptname"
	# to tell udhcpc what to do when an address is obtained.
	# empty by default:
	# DHCP_EXTRA=
EOF

}

get_global_config()
{
    # if you change these defaults, change commented defaults in the
    # template, above.
    DEVICE=eth1
    LOOPTIME=4
    DO_BEEPS=no         # should we beep?
    BEEPTIME=10         # if so, after how long an absence?
    LIST_ALL_HIDDEN=false
    MAX_NETWORKS=999
    FULLSTATUS=false
    RESTRICT_IF_NO_LOGIN=true
    ACCESSIBLE_ONLY=false
    DHCP_CLIENT=dhclient
    DHCP_EXTRA=

    test -s $SHTUMBLE_CONF || create_template_global_config > $SHTUMBLE_CONF

    . $SHTUMBLE_CONF

}

cmd_edit_config()
{
    check_logged_in || return

    echo $COLOR_NOBOLD
    ${EDITOR:-vi} $SHTUMBLE_CONF
    get_global_config
}

create_template_netconfig()
{
    cat <<-EOF
	# is this network visible, or does it hide its SSID?  if
	# a network that you know to exist, and which you think
	# should be accessible, doesn't appear in the selection
	# list, try setting this to "true".  networks marked HIDDEN
	# will continue appearing in your selection list until you
	# first associate with them, at which point they will only
	# appear when in-range.  use the sthumble 'l' command to
	# override this behavior.
	HIDDEN=false

	# access point mode.  if there's really an access point,
	# then it's almost never an ad-hoc network.  shtumble can
	# allow connection to other nodes all configured as ad-hoc,
	# without an access point.  (don't set both HIDDEN and ADHOC
	# to true.  you'll just end up seeing the network listed
	# twice.
	ADHOC=false

	#encryption type:  choose one of wep/wpa/open
	# open (no encryption):
	ENCTYPE=open

	# wep:
	# ENCTYPE=wep
	# key used for WEP: format is "s:string" or "0xHEXNUMBER"
	#  (only one WEP key supported)
	#   WEPKEY="s:SomeFunWeHave"

	# wpa:
	# key used for WPA
	# ENCTYPE=wpa
	#   WPAKEY="some passphrase"
	#

	# wpa2:
	# key used for WPA
	# ENCTYPE=wpa2
	#   WPAKEY="some passphrase"
	#

	# does the network use DHCP?  most wireless networks do.
	DHCP=true

	# if DHCP is NOT in use, these parameters configure the interface.
	# there are no defaults
	IP=
	NETMASK=
	GATEWAY=
	# if DHCP is NOT in use, these parameters establish the
	# "search" and "nameserver" lines of /etc/resolv.conf.
	# no defaults.
	SEARCH1=
	SEARCH2=
	SEARCH3=
	NAMESERVER1=
	NAMESERVER2=
	NAMESERVER3=

	# shtumble will add BSSID values to this file automatically.
	# these values are used to make hidden networks behave more
	# like visible networks, in that they will come and go from
	# the selection list depending on whether they are available
	# for selection.  you probably don't want to edit any BSSID
	# values you find here.
	# BSSID=
EOF

}

test_netconfig()
{
    test -s $SHTUMBLE_NETCONFS/"$1"
}

get_netconfig()
{
    # do we have it configured by name?
    if test_netconfig "$1"
    then
        . $SHTUMBLE_NETCONFS/"$1"
    fi

}

cmd_edit_netconfig()
{
    local ssid
    ssid="$1"

    check_logged_in || return

    conf=$SHTUMBLE_NETCONFS/"$ssid"

    if [ ! -s "$conf" ]
    then
        create_template_netconfig >"$conf"
        chmod 600 "$conf"
    fi

    echo $COLOR_NOBOLD
    ${EDITOR:-vi} "$conf"

    create_bssid_symlinks
}

get_networkname()
{
    action=$1
    stty echo
    while :
    do
        echo "Enter the ESSID (name) of the network you wish"
        echo -n "to $action: "
        read entered_ssid
        echo "The name is '$entered_ssid'."
        if [ -f $SHTUMBLE_NETCONFS/"$entered_ssid" ]
        then
            echo "This configuration file exists."
        else
            echo "No configuration file by this name exists."
        fi
        echo -n "Is this correct? "
        read ans
        yes_or_true $ans && break
        echo -n "cancel? "
        read ans
        if yes_or_true $ans
        then
            stty -echo
            return 1
        fi
    done
    stty -echo
    return 0
}

list_netconfigs()
{
    check_logged_in || return
    clearscreen
    echo Currently configured networks:
    (cd $SHTUMBLE_NETCONFS; ls | sed 's/.*/"&"/')
}

cmd_new_netconfig()
{
    check_logged_in || return
    list_netconfigs
    if get_networkname configure
    then
        cmd_edit_netconfig "$entered_ssid"
    fi
}

cmd_delete_netconfig()
{
    check_logged_in || return
    list_netconfigs
    if get_networkname delete
    then
        rm -f $SHTUMBLE_NETCONFS/"$entered_ssid"
    fi
}

# creating a directory of links from BSSID to ESSID makes it
# easier to deal with hidden networks, without having to search
# all the config files every time a hidden AP comes into view.
create_bssid_symlinks()
{
    # whitespace in filenames is a pain
    x=$(cd $SHTUMBLE_NETCONFS; ls | sed 's/.*/"&"/')
    test "$x" || return
    eval netconfs=( $x )

    rm -f $SHTUMBLE_BSSIDS/*

    for netconf in "${netconfs[@]}"
    do
        bssids=$(sed -n \
            -e 's/^[[:space:]]*BSSID=\([[:xdigit:]_:]\+\).*/\1/p' \
                $SHTUMBLE_NETCONFS/"$netconf" |
            sed -e 's/:/_/g' )
        for b in $bssids
        do
            ln -s $SHTUMBLE_NETCONFS/"$netconf" $SHTUMBLE_BSSIDS/$b
        done
    done
}

# these are the default DHCP actions.  if you replace these
# functions, be sure and arrange for a log of the DHCP actions to
# go into $IFCONF_LOG, so it can be shown to the user.
do_dhcp()
{
    # use -d to suppress dhclient's own backgrounding code.
    case $DHCP_CLIENT in
    dhclient)
        setsid dhclient $DHCP_EXTRA -d $DEVICE &
        ;;
    dhcpcd)
        setsid dhcpcd $DHCP_EXTRA -t 999999 $DEVICE &
        ;;
    udhcpc)
        setsid udhcpc $DHCP_EXTRA -f -i $DEVICE &
        ;;
    *)
        echo "Bad DHCP_CLIENT: '$DHCP_CLIENT'.  No DHCP client started."
        echo "(Must be 'dhclient', 'dhcpcd', or 'udhcpc'.)"
        return
        ;;
    esac

    echo $! >$DHCP_PID_FILE
}

kill_dhcp()
{
    test -f $DHCP_PID_FILE && kill $(< $DHCP_PID_FILE)
    rm -f $DHCP_PID_FILE
}

# take care of static network config -- rare for wireless
do_static()
{

    ifconfig $DEVICE down

    echo Doing static configuration.
    # static configuration
    ifconfig $DEVICE ${IP:-0.0.0.0} ${NETMASK+netmask $NETMASK} up ||
        echo ${COLOR_ALERT}ifconfig of interface $DEVICE \
                        failed${COLOR_NORMAL}

    # default route
    if [ "$GATEWAY" ]
    then
        route add default gw $GATEWAY dev $DEVICE ||
            echo ${COLOR_ALERT}Add of default route to $GATEWAY \
                        failed${COLOR_NORMAL}
    fi

    # create resolv.conf
    (
        for search in $SEARCH $SEARCH1 $SEARCH2 $SEARCH3
        do
                echo "search $search"
        done
        for dns in $NAMESERVER $NAMESERVER1 $NAMESERVER2 $NAMESERVER3
        do
                echo "nameserver $dns"
        done
    ) >/etc/resolv.conf.$DEVICE && \
        ln -f /etc/resolv.conf.$DEVICE /etc/resolv.conf || \
            echo ${COLOR_ALERT}Failed to create requested \
                        /etc/resolv.conf${COLOR_NORMAL}
    echo Done.
}

WPA_CF=/var/run/wpa.conf

start_wpa_supplicant()
{
    setsid wpa_supplicant -i $DEVICE -c $WPA_CF -B -D wext
    # -w?
}

create_wpa_conf()
{
    # first the common parts
    cat <<-EOF >$WPA_CF
        ctrl_interface=/var/run/wpa_supplicant
        network={
                ssid="$ESSID"
                scan_ssid=1
EOF

    # add all the name=value pairs, and the closing brace
    for parm in "$@" "}"
    do
        echo "$parm"
    done >>$WPA_CF
}


do_encryption()
{

    killall wpa_supplicant 2>/dev/null

    # other WPA modes can be added to this case, in a fairly
    # obvious way.  the network config template should be extended
    # as well, to make it obvious how to configure the new
    # mechanism.
    case "$ENCTYPE" in
    wpa|WPA|wpapsk|WPAPSK)
        passph=$(wpa_passphrase "$ESSID" "${WPAKEY#s:*}" | egrep '[^#]psk' )
        # passph="psk=\"${WPAKEY#s:*}\""
        create_wpa_conf "key_mgmt=WPA-PSK" \
                        "pairwise=TKIP" \
                        "group=TKIP" \
                        "$passph"

        # "proto=WPA" \

        start_wpa_supplicant
        ;;

    wpa2|WPA2)
        passph=$(wpa_passphrase "$ESSID" "${WPAKEY#s:*}" | egrep '[^#]psk' )
        # passph="psk=\"${WPAKEY#s:*}\""
        create_wpa_conf "key_mgmt=WPA-PSK" \
                        "pairwise=CCMP TKIP" \
                        "group=CCMP TKIP" \
                        "$passph"

        start_wpa_supplicant
        ;;


    # we don't really need wpa_supplicant for these types of
    # nets, but not using it means we're susceptible to losing our
    # connection if handshakes are lost on congested or busy channels.
    # (see next stanza for iwconfig method of config)
    wep|WEP|open|OPEN|"")
        case "$ENCTYPE" in
            wep|WEP)
                case $WEPKEY in
                    s:*) WEPKEY=\"${WEPKEY##s:}\" ;;
                    0x*) WEPKEY=${WEPKEY##0x} ;;
                esac
                wep_key0="wep_key0=$WEPKEY"
                wep_tx_keyidx="wep_tx_keyidx=0"
                ;;
            *)
                wep_key0=;
                wep_tx_keyidx=;
                ;;
        esac
        yes_or_true $ADHOC && mode=1 || mode=0
        create_wpa_conf "key_mgmt=NONE" \
                        "mode=$mode" \
                        $wep_key0 \
                        $wep_tx_keyidx

        start_wpa_supplicant
        ;;

    # unused in favor of wpa_supplicant.
    wep_using_iwconfig|open_using_iwconfig)

        # first break association, to be sure what we run next
        # is what we get.
        iwpriv $DEVICE deauth 2>/dev/null

        if yes_or_true $ADHOC
        then
            iwconfig $DEVICE mode Ad-hoc
        else
            iwconfig $DEVICE mode Managed
        fi

        case "$ENCTYPE" in
        open|OPEN|'')
                iwconfig $DEVICE enc off
                ;;
        *)
                iwconfig $DEVICE key "${WEPKEY#0x*}"
                iwconfig $DEVICE key [1]
                # iwconfig $DEVICE enc on
                ;;
        esac

        iwconfig $DEVICE essid "$ESSID"
        iwpriv $DEVICE commit 2>/dev/null
        ;;

    *)
        echo "${0##*/}: unsupported encryption type, ENC=$ENC"
        ;;
    esac

}

fixup_hidden_config()
{
    # when a network that's configured as hidden is selected
    # (successfully), it should be linked to the bssid_links dir,
    # and the BSSID added to the config file.  note there can be
    # multiple links to a single config, and multiple BSSID vars.
    # multiples will probably only occur if user has selected
    # "always show all hidden".  once all APs for an ESSID are
    # found, that setting can be turned off.

    cur_bssid=$(iwconfig $DEVICE | sed -n -e \
            's/.*Access Point: \([[:digit:]:]\+\)[[:space:]]*/\1/p')
    if [ "$cur_bssid" ]
    then
        cur_bssid=$(echo "$cur_bssid" | sed 's/:/_/g')  # current
        BSSID=$(echo "$BSSID" | sed 's/:/_/g')  # from config
        if [ "$cur_bssid" != "$BSSID" ]
        then
            if ! [ -L $SHTUMBLE_BSSIDS/$cur_bssid ]
            then
                ln -sf $SHTUMBLE_NETCONFS/"$ESSID" $SHTUMBLE_BSSIDS/$cur_bssid
                echo BSSID=$cur_bssid >>$SHTUMBLE_NETCONFS/"$ESSID"
            fi
        fi
    fi
}

reset_config_vars()
{
    for v in DHCP IP NETMASK GATEWAY \
        SEARCH SEARCH1 SEARCH2 SEARCH3 \
        NAMESERVER NAMESERVER1 NAMESERVER2 NAMESERVER3 \
        ADHOC ENCTYPE WEPKEY WPAKEY HIDDEN BSSID
    do
        eval "$v=;"
    done
}

select_network()
{
    local essid

    essid="$1"


    # clean up, before sourcing interface config
    reset_config_vars

    # set defaults
    DHCP=true
    KEY="off"

    get_netconfig "$essid"
    ESSID="$essid"

    if [ "$ESSID" = HIDDEN ]
    then
        echo "No ESSID to connect to when network is HIDDEN."
        sleep 2
        return
    fi

    last_assoc="$ESSID"
    echo "$last_assoc" >$SHTUMBLE_HOME/last_essid

    kill_dhcp

    do_encryption

    # put these actions into the background so we can tail their
    # progress.
    if yes_or_true "$DHCP"
    then
        do_dhcp    >$IFCONF_LOG 2>&1 &
    else
        do_static  >$IFCONF_LOG 2>&1 &
    fi

    # if the net was hidden, try to record the mapping of BSSID to ESSID
    yes_or_true $HIDDEN && (sleep 3; fixup_hidden_config ) &

}

# shell variable indirection
deref()
{
    set +u
    echo ${!1}    # or, more portably: eval echo \$$1
    set -u
}

format_menu_item()
{
    local known encstr essid addr sig encr this

    this=$1

    essid=${net_essid[$this]}
    addr=${net_bssid[$this]}
    sig=${net_sig[$this]}
    encr=${net_encr[$this]}

    known=false
    if test_netconfig "$essid"
    then
        known=true
    fi

    if [ ! "$known" -a $encr != off ]
    then
        yes_or_true $ACCESSIBLE_ONLY && return
        color=$COLOR_ALERT
    elif [ "$addr" = unknown -o "$essid" = "HIDDEN" ]
    then
        color=$COLOR_WARN
    else
        now=$SECONDS
        was=$(deref seen_$addr)
        : ${was:-0}
        if yes_or_true $DO_BEEPS && (( ( $now - $was ) > $BEEPTIME ))
        then
            echo -en '\a'
        fi
        if [ ! "$known" -a "$sig" -gt -35 ]
        then
            color=$COLOR_WARN
        else
            color=$COLOR_GOOD
        fi
        eval seen_$addr=$now
    fi

    if [ "$sig" = hidden ]
    then
        dbm="(hidden, not in sight)"
    elif [ "$sig" = adhoc ]
    then
        dbm="(ad-hoc)"
    else
        dbm="($sig dBm)"
    fi

    encstr=;
    if [ "$encr" != off ]
    then
        encstr="$encr"
    fi
    menuitems[$this]="$( printf '%s%-20s %s %s' \
            "$color"  \
            "$essid" \
            "$dbm"  \
            "$encstr" )"


    displayed[$display_index]=$this
    return $(( ++display_index >= MAX_NETWORKS ))
}

enumerate_hidden_nets()
{
    # get the names of all the nets with HIDDEN set to 'yes' or 'true'.
    # complicated because an ssid can contain spaces
    x=$(cd $SHTUMBLE_NETCONFS;
        egrep -l '^[[:space:]]*HIDDEN=[yt]' * | sed 's/.*/"&"/')
    eval hidden_nets=( $x )
    test "$x"
}

enumerate_adhoc_nets()
{
    # get the names of all the nets with ADHOC set to 'yes' or 'true'.
    # complicated because an ssid can contain spaces
    x=$(cd $SHTUMBLE_NETCONFS;
        egrep -l '^[[:space:]]*ADHOC=[yt]' * | sed 's/.*/"&"/')
    eval adhoc_nets=( $x )
    test "$x"
}

enumerate_access_points()
{
    unset net_bssid net_essid net_sig net_encr menuitems
    menuitems=( )

    # create a uniform set of tagged lines.  there should
    # be four lines per visible network when we're done, one each
    # for address, ssid, encryption on/off, and signal strength
    iwlist $DEVICE scan |
        egrep 'Address:|ESSID:|IE:|Encryption|Signal' |
            sed -e '/Address:/s/:/ /g' \
                -e 's/.*Address /bssid /' \
                -e 's/""/"HIDDEN"/' \
                -e 's/"<hidden>"/"HIDDEN"/' \
                -e 's/.*ESSID:"\(.*\)"/essid \1/' \
                -e 's/.*Signal level.\([-[:digit:]]\+\).*/sig \1/' \
                -e 's/.*Encryption key:*/enc /' \
                -e 's/IE:.*WPA Version.*/enc WPA1/' \
                -e 's/IE:.*WPA2 Version.*/enc WPA2/' \
        > $IWLIST


    num_ap=0
    tmp_bssid=;
    while read tag value
    do
        case $tag in
        bssid)
            if [ "$tmp_bssid" ]
            then
		# maybe later, try to eliminate dups
                #i=0
                #while (( $i < $num_ap ))
                #do
                #    if [ ${net_essid[$i]} != HIDDEN -a ${net_essid[$i]} = "$tmp_essid" ]
                #    then
                #        break;
                #    fi
                #    (( i++ ))
                #done
                i=$num_ap

                if (( $i == $num_ap ))  # not found
                then
                    net_bssid[$num_ap]="$tmp_bssid"
                    net_essid[$num_ap]="$tmp_essid"
                    net_encr[$num_ap]="$tmp_encr"
                    net_sig[$num_ap]="$tmp_sig"
                    tmp_bssid=;
                    tmp_essid=;
                    tmp_encr=;
                    tmp_sig=;
                    (( num_ap++ ))
                fi
            fi

            tmp_bssid=$(echo "$value" | sed 's/ /_/g')

            ;;
        essid)
            tmp_essid="$value"
            ;;
        enc)
            test "$value" = on && value=WEP
            tmp_encr=$value
            ;;
        sig)
            tmp_sig=$value
            ;;
        esac
    done < $IWLIST
            if [ "$tmp_bssid" ]
            then
                i=0
                while (( $i < $num_ap ))
                do
                    if [ ${net_essid[$i]} != HIDDEN -a ${net_essid[$i]} = "$tmp_essid" ]
                    then
                        break;
                    fi
                    (( i++ ))
                done

                if (( $i == $num_ap ))  # not found
                then
                    net_bssid[$num_ap]="$tmp_bssid"
                    net_essid[$num_ap]="$tmp_essid"
                    net_encr[$num_ap]="$tmp_encr"
                    net_sig[$num_ap]="$tmp_sig"
                    tmp_bssid=;
                    tmp_essid=;
                    tmp_encr=;
                    tmp_sig=;
                    (( num_ap++ ))
                fi
            fi


    # $num_ap may be zero here, but we still have to deal with
    # hidden and ad-hoc nets.

    # for each net in scan, list it directly, or if it's hidden and
    # we can look it up by bssid, then give it it's "real" name and
    # list it that way.
    still_hidden=;
    hidden_but_shown=;
    netnum=0
    while (( netnum < num_ap ))
    do
        i=0

        # nets can show up twice, particularly if you're associated
        # with a hidden net.
        did_this_net=;
        while (( $i < $netnum ))
        do
            if [ "${net_bssid[$i]}" = "${net_bssid[$netnum]}" ]
            then
                did_this_net=true
                break
            fi
            let i+=1
        done

        if [ ! "$did_this_net" ]
        then
            # if it's a hidden, try and match it by address
            bssidlink=$SHTUMBLE_BSSIDS/"${net_bssid[$netnum]}"
            if [ "${net_essid[$netnum]}" = HIDDEN ]
            then
                if [ -L $bssidlink ]
                then
                    netconfig="$(readlink -f $bssidlink)"
                    net_essid[$netnum]="${netconfig##*/}"
                    hidden_but_shown="$hidden_but_shown $netnum"
                else
                    still_hidden=true
                fi
            fi

            format_menu_item $netnum || return
        fi

        let netnum+=1
    done


    # loop over all the configured adhoc nets -- they'll never
    # show up in a scan
    if enumerate_adhoc_nets
    then
        for adhessid in "${adhoc_nets[@]}"
        do
            net_essid[$netnum]="$adhessid"
            net_bssid[$netnum]=unknown
            net_encr[$netnum]=unknown
            net_sig[$netnum]=adhoc

            format_menu_item $netnum || return

            let netnum+=1
        done
    fi

    # if there are no undisplayed hidden nets, then there's
    # no need to display configured hiddens -- they're out of range.
    if [ ! "$still_hidden" ] && ! yes_or_true $LIST_ALL_HIDDEN
    then
        return
    fi

    # for each net in config that's marked hidden, list those
    # which dont have BSSID set.  (if there's a BSSID entry,
    # then either we've already shown it, or it's not visible
    # and we don't want to.)  optionally, list all that weren't
    # already shown (using the list of indices available in
    # $hidden_but_shown).

    if enumerate_hidden_nets
    then
        # loop over all the configured hidden nets
        for hessid in "${hidden_nets[@]}"
        do
            showit=false;
            if yes_or_true $LIST_ALL_HIDDEN
            then
                showit=true
                for h in $hidden_but_shown
                do
                    if [ "$hessid" = "$net_essid[$h]" ]
                    then
                        showit=false
                        break
                    fi
                done
            else
                # no bssid in config?
                if ! egrep -q '^[[:space:]]*BSSID=[[:xdigit:]_:]+' \
                        $SHTUMBLE_NETCONFS/"$hessid"
                then
                    showit=true
                fi
            fi

            if [ "$showit" = true ]
            then
                net_essid[$netnum]="$hessid"
                net_bssid[$netnum]=unknown
                net_encr[$netnum]=unknown
                net_sig[$netnum]=hidden

                format_menu_item $netnum || return

                let netnum+=1
            fi
        done
    fi

}

clearscreen()
{
    tput clear
}

look_alive()
{
    clearscreen
    echo -n "${BAR}Scanning for wireless networks..."
}

header()
{
    echo -n "${BAR}Visible networks:"
    echo "                              ${COLOR_NORMAL}(use 'h' for Help)"
}

trailer()
{

    echo -n "${BAR}"
    addr=$(ifconfig $DEVICE |
        sed -n -e 's/.*inet addr:\([[:digit:].]*\).*/\1/p')
    last=${last_assoc:+", trying $COLOR_NORMAL\"$last_assoc\""}
    if is_radio_off
    then
        echo ${COLOR_WARN}Radio off
    else
        iwc=$(iwconfig $DEVICE)

        radiooff=$(echo $iwc |
            sed -n -e "s/.*radio off.*/${COLOR_WARN}Radio off/p")
        txoff=$(echo $iwc |
            sed -n -e "s/.*Tx-Power=off.*/${COLOR_WARN}Tx-Power Off${COLOR_NORMAL}/p")
        unassoc=$(echo $iwc |
            sed -n -e "s/.*[ut][n-][aA]ssociated.*/${COLOR_ALERT}Unassociated${last}/p")
        ouressid=$(echo $iwc |
            sed -n -e 's/.*ESSID:\("[^"]\+"\).*/'${COLOR_NORMAL}'\1/p')
        ourip="${addr:+", IP addr: $addr"}"
        # only one or two of these should be set at a time.
        # echo R $radiooff T $txoff U $unassoc O $ouressid I $ourip
        echo $radiooff $txoff $unassoc $ouressid $ourip
    fi
    echo -n "$COLOR_NORMAL"

    yes_or_true $FULLSTATUS || return

    echo -n "${BAR}Current config for "
    echo -n "$COLOR_NORMAL$DEVICE"
    echo -n "${BAR}"
    echo $COLOR_NORMAL
    ifconfig $DEVICE |
        sed -e "1s/^......../ifconfig/" \
            -e 3q
    iwconfig $DEVICE |
        sed -e "1s/^......../iwconfig/" \
            -e '/Retry/d' \
            -e '/Power Man/d'  \
            -e '/Encryption/d' \
            -e "s/radio off/${COLOR_WARN}&${COLOR_NORMAL}/" \
            -e "s/Tx-Power=off/${COLOR_WARN}&${COLOR_NORMAL}/" \
            -e "s/unassociated/${COLOR_WARN}&${COLOR_NORMAL}/" \
            -e "s/Not-Associated/${COLOR_WARN}&${COLOR_NORMAL}/" \
            -e '7q'

    echo ${BAR} /etc/resolv.conf ${BAR}
    echo $(sed '/^;/d' < /etc/resolv.conf) | fmt  # as much on a line as possible

    echo "${BAR}Default route(s)${BAR}"
    route -n | sed -n -e '/^0\.0\.0\.0 /p'

    echo -n ${BAR}Last config output
    if [ "$last_assoc" ]
    then
        echo -n " (for net \"$last_assoc\")"
    fi
    echo ${BAR}
    test -f $IFCONF_LOG && tail -3 $IFCONF_LOG
}

# map input keystrokes to command verbs.  some of these are tuned
# to my handheld device, which has prominent arrow and "back" keys.
keystroke()
{
    while read -t $LOOPTIME -n 1 a
    do
        case "$a" in

        # network selection
        k)      echo up         ;;
        j)      echo down       ;;
        "")     echo select     ;;  # "Enter" gives the null string
        d)      echo disconnect ;;


        # config editing commands
        c)      echo editconfig ;;
        e)      echo editnet    ;;
        n)      echo new        ;;
        x)      echo delete     ;;

        # misc
        a)      echo accessible ;;
        l)      echo listall    ;;
        r)      echo radio      ;;
        s)      echo status     ;;
        h)      echo help       ;;
        q)      echo quit       ;;

        # ansi arrows are "ESC [ A", "ESC [ B", "ESC [ C", "ESC [ D".
        # ignore the 'ESC' and '['.
        $'\e'|'[') continue     ;;
        A)      echo up         ;;
        B)      echo down       ;;
        C)      echo status     ;;  # right
        D)      echo disconnect ;;  # left

        # map backspace
        $'\b')  echo disconnect
                ;;

        p)      echo "Paused.  Hit any key to continue" >&3
                read -n 1 a
                echo null
                ;;

        *)      echo "Try 'h' for Help" >&3
                continue
                ;;
        esac

        # all valid commands cause a break.  others have "continue", above.
        break
    done
}

# present a menu of networks to choose from.
# other related commands (config editing, etc) are supported
# here as well, with special keys (i.e., they're not in the menu).
showmenu()
{
    curitem=0

    n_items=$#

    # the only loop here results from the up/down keys.  everything
    # else causes an action, and returns.
    while :
    do
        clearscreen
        header

        # we could try and preserve the current menu item across
        # invocations of this function, but since the entire contents
        # might change, that gets tricky.  so the pointer gets reset
        # to the top whenever we get in here.  since the timer restarts
        # with every keypress, it's not too bad -- you have LOOPTIME
        # seconds for every arrow movement, and the final "enter".
        itemno=0
        if [ $n_items != 0 ]
        then
            for item in "$@"
            do
                if [ $curitem = $itemno ]
                then
                    echo -n $COLOR_NORMAL "     --> "
                else
                    echo -n $COLOR_NORMAL "         "
                fi
                echo "$item"
                itemno=$((itemno + 1))
            done
        else
            echo "         Nothing to select"
        fi

        trailer

        case $(keystroke) in
            quit)       screen_normal; exit 0 ;;
            help)       cmd_help ;;
            status)     cmd_toggle_status ;;
            accessible) cmd_toggle_accessible_only ;;
            listall)    cmd_toggle_list_allhidden ;;
            radio)      cmd_toggle_radio ;;
            disconnect) cmd_disconnect ;;
            editconfig) cmd_edit_config ;;
            editnet)    test $n_items = 0 && break
                        which=${displayed[$curitem]}
                        cmd_edit_netconfig "${net_essid[$which]}" ;;
            new)        cmd_new_netconfig ;;
            delete)     cmd_delete_netconfig ;;

            up)
                curitem=$((curitem - 1))
                if [ $curitem -lt 0 ]
                then
                    curitem=$((n_items - 1))
                fi
                continue
                ;;
            down)
                curitem=$((curitem + 1))
                if [ $curitem -ge $n_items ]
                then
                    curitem=0
                fi
                continue
                ;;
            select)
                test $n_items = 0 && break
                which=${displayed[$curitem]}
                select_network "${net_essid[$which]}"
                ;;

            ""|null) # timeout
                ;;
        esac

        # yes, this looks odd
        break

    done

}

init_interface()
{
    if [ -f $SHTUMBLE_HOME/radio_off ]
    then
        radio_off
    elif ! ifconfig $DEVICE | egrep -q ' UP '
    then
        ifconfig $DEVICE 0.0.0.0 up
        iwconfig $DEVICE key off
    fi

}

screen_alternate()
{
    tput smcup
}

screen_normal()
{
    tput rmcup
}

main()
{
    SECONDS=0
    last_assoc=;

    screen_alternate
    look_alive

    init_interface

    while :
    do
        # dhclient, at least, will take the interface down when it
        # has failed to get an address.  at that point, we can no
        # longer do scans, so we force it up at all times.
        ifconfig $DEVICE up

        # since not all networks will end up on the screen, we
        # keep track of which are there using $displayed[]
        unset displayed
        display_index=0

        # get a list of all visible APs
        enumerate_access_points

        if (( ${#menuitems[@]} > 0 ))
        then
            # and present them as a menu
            showmenu "${menuitems[@]}"
        else
            showmenu
        fi

    done

}

start_log()
{

    # uncomment these two lines to get a debugging trace
    exec 2>/tmp/shtumble.log
    set -x
}

# allow log tracing and usage messages to work correctly.
# (log tracing always goes to 2.  we send usage and help to 3.)
exec 3>&2

if [ "$1" ]
then
    get_global_config

    case $1 in
    res*) # restart
        check_root
        start_log
        essid=$(<$SHTUMBLE_HOME/last_essid)
        test "$essid" || exit
        init_interface
        select_network "$essid"
        exit
        ;;

    con*) # connect <essid>
        check_root
        start_log
        essid="$2"
        test "$essid" || usage
        init_interface
        select_network "$essid"
        exit
        ;;

    dis*)
        check_root
        start_log
        disconnect
        exit
        ;;

    off|radiooff)
        check_root
        start_log
        disconnect
        radio_off
        exit
        ;;

    h*|-h*)
        echo Commands:
        echo
        helptext
        echo Invocation:
        echo
        usage
        echo
        exit
        ;;

    *)
        usage
        ;;
    esac

fi

check_root
start_log

# fail on unassigned vars
set -u

if [ ! -d $SHTUMBLE_HOME ]
then
    echo "The config directory $SHTUMBLE_HOME doesn't exist."
    echo "Okay to create?"
    read ans
    yes_or_true $ans || exit
fi

mkdir -p $SHTUMBLE_NETCONFS $SHTUMBLE_BSSIDS

rm -f $PIDFILE
echo $$ >$PIDFILE

init_ansi_escapes
get_global_config

# arrange to clean up temp files when done
trap 'stty echo; echo -n "$COLOR_NOBOLD"; rm -f $IWLIST $IFCONF_LOG $PIDFILE; exit' 0

stty -echo

if [ ! -f $SHTUMBLE_HOME/radio_off ]
then
    while ! iwconfig $DEVICE | egrep -q 'Access Point:|Cell:'
    do
        echo "$DEVICE does not appear to be a wireless interface."
        echo
        echo "Please edit your configuration to correct the DEVICE setting"
        echo "Press enter to invoke an editor on the config file..."

        read ans

        cmd_edit_config
    done
fi

create_bssid_symlinks

main

exit

# ---------------------- license follows -----------------
#
#                     GNU GENERAL PUBLIC LICENSE
#                        Version 2, June 1991
#
#  Copyright (C) 1989, 1991 Free Software Foundation, Inc.
#      51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
#  Everyone is permitted to copy and distribute verbatim copies
#  of this license document, but changing it is not allowed.
#
#                             Preamble
#
#   The licenses for most software are designed to take away your
# freedom to share and change it.  By contrast, the GNU General Public
# License is intended to guarantee your freedom to share and change free
# software--to make sure the software is free for all its users.  This
# General Public License applies to most of the Free Software
# Foundation's software and to any other program whose authors commit to
# using it.  (Some other Free Software Foundation software is covered by
# the GNU Library General Public License instead.)  You can apply it to
# your programs, too.
#
#   When we speak of free software, we are referring to freedom, not
# price.  Our General Public Licenses are designed to make sure that you
# have the freedom to distribute copies of free software (and charge for
# this service if you wish), that you receive source code or can get it
# if you want it, that you can change the software or use pieces of it
# in new free programs; and that you know you can do these things.
#
#   To protect your rights, we need to make restrictions that forbid
# anyone to deny you these rights or to ask you to surrender the rights.
# These restrictions translate to certain responsibilities for you if you
# distribute copies of the software, or if you modify it.
#
#   For example, if you distribute copies of such a program, whether
# gratis or for a fee, you must give the recipients all the rights that
# you have.  You must make sure that they, too, receive or can get the
# source code.  And you must show them these terms so they know their
# rights.
#
#   We protect your rights with two steps: (1) copyright the software, and
# (2) offer you this license which gives you legal permission to copy,
# distribute and/or modify the software.
#
#   Also, for each author's protection and ours, we want to make certain
# that everyone understands that there is no warranty for this free
# software.  If the software is modified by someone else and passed on, we
# want its recipients to know that what they have is not the original, so
# that any problems introduced by others will not reflect on the original
# authors' reputations.
#
#   Finally, any free program is threatened constantly by software
# patents.  We wish to avoid the danger that redistributors of a free
# program will individually obtain patent licenses, in effect making the
# program proprietary.  To prevent this, we have made it clear that any
# patent must be licensed for everyone's free use or not licensed at all.
#
#   The precise terms and conditions for copying, distribution and
# modification follow.
#
#                     GNU GENERAL PUBLIC LICENSE
#    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
#
#   0. This License applies to any program or other work which contains
# a notice placed by the copyright holder saying it may be distributed
# under the terms of this General Public License.  The "Program", below,
# refers to any such program or work, and a "work based on the Program"
# means either the Program or any derivative work under copyright law:
# that is to say, a work containing the Program or a portion of it,
# either verbatim or with modifications and/or translated into another
# language.  (Hereinafter, translation is included without limitation in
# the term "modification".)  Each licensee is addressed as "you".
#
# Activities other than copying, distribution and modification are not
# covered by this License; they are outside its scope.  The act of
# running the Program is not restricted, and the output from the Program
# is covered only if its contents constitute a work based on the
# Program (independent of having been made by running the Program).
# Whether that is true depends on what the Program does.
#
#   1. You may copy and distribute verbatim copies of the Program's
# source code as you receive it, in any medium, provided that you
# conspicuously and appropriately publish on each copy an appropriate
# copyright notice and disclaimer of warranty; keep intact all the
# notices that refer to this License and to the absence of any warranty;
# and give any other recipients of the Program a copy of this License
# along with the Program.
#
# You may charge a fee for the physical act of transferring a copy, and
# you may at your option offer warranty protection in exchange for a fee.
#
#   2. You may modify your copy or copies of the Program or any portion
# of it, thus forming a work based on the Program, and copy and
# distribute such modifications or work under the terms of Section 1
# above, provided that you also meet all of these conditions:
#
#     a) You must cause the modified files to carry prominent notices
#     stating that you changed the files and the date of any change.
#
#     b) You must cause any work that you distribute or publish, that in
#     whole or in part contains or is derived from the Program or any
#     part thereof, to be licensed as a whole at no charge to all third
#     parties under the terms of this License.
#
#     c) If the modified program normally reads commands interactively
#     when run, you must cause it, when started running for such
#     interactive use in the most ordinary way, to print or display an
#     announcement including an appropriate copyright notice and a
#     notice that there is no warranty (or else, saying that you provide
#     a warranty) and that users may redistribute the program under
#     these conditions, and telling the user how to view a copy of this
#     License.  (Exception: if the Program itself is interactive but
#     does not normally print such an announcement, your work based on
#     the Program is not required to print an announcement.)
#
# These requirements apply to the modified work as a whole.  If
# identifiable sections of that work are not derived from the Program,
# and can be reasonably considered independent and separate works in
# themselves, then this License, and its terms, do not apply to those
# sections when you distribute them as separate works.  But when you
# distribute the same sections as part of a whole which is a work based
# on the Program, the distribution of the whole must be on the terms of
# this License, whose permissions for other licensees extend to the
# entire whole, and thus to each and every part regardless of who wrote it.
#
# Thus, it is not the intent of this section to claim rights or contest
# your rights to work written entirely by you; rather, the intent is to
# exercise the right to control the distribution of derivative or
# collective works based on the Program.
#
# In addition, mere aggregation of another work not based on the Program
# with the Program (or with a work based on the Program) on a volume of
# a storage or distribution medium does not bring the other work under
# the scope of this License.
#
#   3. You may copy and distribute the Program (or a work based on it,
# under Section 2) in object code or executable form under the terms of
# Sections 1 and 2 above provided that you also do one of the following:
#
#     a) Accompany it with the complete corresponding machine-readable
#     source code, which must be distributed under the terms of Sections
#     1 and 2 above on a medium customarily used for software interchange; or,
#
#     b) Accompany it with a written offer, valid for at least three
#     years, to give any third party, for a charge no more than your
#     cost of physically performing source distribution, a complete
#     machine-readable copy of the corresponding source code, to be
#     distributed under the terms of Sections 1 and 2 above on a medium
#     customarily used for software interchange; or,
#
#     c) Accompany it with the information you received as to the offer
#     to distribute corresponding source code.  (This alternative is
#     allowed only for noncommercial distribution and only if you
#     received the program in object code or executable form with such
#     an offer, in accord with Subsection b above.)
#
# The source code for a work means the preferred form of the work for
# making modifications to it.  For an executable work, complete source
# code means all the source code for all modules it contains, plus any
# associated interface definition files, plus the scripts used to
# control compilation and installation of the executable.  However, as a
# special exception, the source code distributed need not include
# anything that is normally distributed (in either source or binary
# form) with the major components (compiler, kernel, and so on) of the
# operating system on which the executable runs, unless that component
# itself accompanies the executable.
#
# If distribution of executable or object code is made by offering
# access to copy from a designated place, then offering equivalent
# access to copy the source code from the same place counts as
# distribution of the source code, even though third parties are not
# compelled to copy the source along with the object code.
#
#   4. You may not copy, modify, sublicense, or distribute the Program
# except as expressly provided under this License.  Any attempt
# otherwise to copy, modify, sublicense or distribute the Program is
# void, and will automatically terminate your rights under this License.
# However, parties who have received copies, or rights, from you under
# this License will not have their licenses terminated so long as such
# parties remain in full compliance.
#
#   5. You are not required to accept this License, since you have not
# signed it.  However, nothing else grants you permission to modify or
# distribute the Program or its derivative works.  These actions are
# prohibited by law if you do not accept this License.  Therefore, by
# modifying or distributing the Program (or any work based on the
# Program), you indicate your acceptance of this License to do so, and
# all its terms and conditions for copying, distributing or modifying
# the Program or works based on it.
#
#   6. Each time you redistribute the Program (or any work based on the
# Program), the recipient automatically receives a license from the
# original licensor to copy, distribute or modify the Program subject to
# these terms and conditions.  You may not impose any further
# restrictions on the recipients' exercise of the rights granted herein.
# You are not responsible for enforcing compliance by third parties to
# this License.
#
#   7. If, as a consequence of a court judgment or allegation of patent
# infringement or for any other reason (not limited to patent issues),
# conditions are imposed on you (whether by court order, agreement or
# otherwise) that contradict the conditions of this License, they do not
# excuse you from the conditions of this License.  If you cannot
# distribute so as to satisfy simultaneously your obligations under this
# License and any other pertinent obligations, then as a consequence you
# may not distribute the Program at all.  For example, if a patent
# license would not permit royalty-free redistribution of the Program by
# all those who receive copies directly or indirectly through you, then
# the only way you could satisfy both it and this License would be to
# refrain entirely from distribution of the Program.
#
# If any portion of this section is held invalid or unenforceable under
# any particular circumstance, the balance of the section is intended to
# apply and the section as a whole is intended to apply in other
# circumstances.
#
# It is not the purpose of this section to induce you to infringe any
# patents or other property right claims or to contest validity of any
# such claims; this section has the sole purpose of protecting the
# integrity of the free software distribution system, which is
# implemented by public license practices.  Many people have made
# generous contributions to the wide range of software distributed
# through that system in reliance on consistent application of that
# system; it is up to the author/donor to decide if he or she is willing
# to distribute software through any other system and a licensee cannot
# impose that choice.
#
# This section is intended to make thoroughly clear what is believed to
# be a consequence of the rest of this License.
#
#   8. If the distribution and/or use of the Program is restricted in
# certain countries either by patents or by copyrighted interfaces, the
# original copyright holder who places the Program under this License
# may add an explicit geographical distribution limitation excluding
# those countries, so that distribution is permitted only in or among
# countries not thus excluded.  In such case, this License incorporates
# the limitation as if written in the body of this License.
#
#   9. The Free Software Foundation may publish revised and/or new versions
# of the General Public License from time to time.  Such new versions will
# be similar in spirit to the present version, but may differ in detail to
# address new problems or concerns.
#
# Each version is given a distinguishing version number.  If the Program
# specifies a version number of this License which applies to it and "any
# later version", you have the option of following the terms and conditions
# either of that version or of any later version published by the Free
# Software Foundation.  If the Program does not specify a version number of
# this License, you may choose any version ever published by the Free Software
# Foundation.
#
#   10. If you wish to incorporate parts of the Program into other free
# programs whose distribution conditions are different, write to the author
# to ask for permission.  For software which is copyrighted by the Free
# Software Foundation, write to the Free Software Foundation; we sometimes
# make exceptions for this.  Our decision will be guided by the two goals
# of preserving the free status of all derivatives of our free software and
# of promoting the sharing and reuse of software generally.
#
#                             NO WARRANTY
#
#   11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
# FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
# OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
# PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
# OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
# TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
# PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
# REPAIR OR CORRECTION.
#
#   12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
# WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
# REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
# INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
# OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
# TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
# YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
# PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGES.
#
#                      END OF TERMS AND CONDITIONS
# --------------------------------------------------------------
