#! /bin/bash

. /usr/lib/network/globals
. "$SUBR_DIR/wpa"


usage()
{
    cat << END
Usage: wifi-menu [-h | --help] [-o | --obscure] [INTERFACE]

Interactively connect to a wireless network on INTERFACE using netctl.
If only one wireless interface is available, INTERFACE can be omitted.

Arguments:
  -h, --help     Show this help
  -o, --obscure  Show asterisks for the characters of the password
                 and store the password as a hexadecimal string
END
}

# Fills PROFILES and ESSIDS with the profile names and essids of the profiles
# for interface $1.
init_profiles()
{
    local i=0 essid profile
    while read profile; do
        essid=$(
            unset INTERFACE ESSID
            source "$PROFILE_DIR/$profile" > /dev/null
            if [[ "$Interface" = "$1" && -n "$ESSID" ]]; then
                printf "%s" "$ESSID"
                if [[ "$Description" =~ "Automatically generated" ]]; then
                    return 2
                else
                    return 1
                fi
            fi
            return 0
        )
        case $? in
            2)
                GENERATED+=("$profile")
                ;&
            1)
                PROFILES[i]=$profile
                ESSIDS[i]=$essid
                (( ++i ))
                ;;
        esac
    done < <(list_profiles)
}

# Builds ENTRIES as an argument list for dialog based on scan results in $1.
init_entries()
{
    local i=0 flags signal ssid
    while IFS=$'\t' read signal flags ssid; do
        ENTRIES[i++]="--"  # $ssid might look like an option to dialog.
        ENTRIES[i++]=$ssid
        if in_array "$ssid" "${ESSIDS[@]}"; then
            if in_array "$(ssid_to_profile "$ssid")" "${GENERATED[@]}"; then
                ENTRIES[i]="+"  # Automatically generated
            else
                ENTRIES[i]="*"  # Handmade
            fi
        else
            ENTRIES[i]="-"  # Not present
        fi
        if [[ "$ssid" = "$CONNECTION" ]]; then
            ENTRIES[i]="!"  # Currently connected
        fi
        if [[ "$flags" =~ WPA2|WPA|WEP ]]; then
            ENTRIES[i]+=":${BASH_REMATCH[0],,}"
        else
            ENTRIES[i]+=":none"
        fi
        ENTRIES[i]+="   :$signal"
        (( ++i ))
    done < "$1"
}

# Finds a profile name for ssid $1.
ssid_to_profile()
{
    local i
    for i in $(seq 0 $((${#ESSIDS[@]}-1))); do
        if [[ "$1" = "${ESSIDS[i]}" ]]; then
            printf "%s" "${PROFILES[i]}"
            return 0
        fi
    done
    return 1
}

# Ask the user for the name of the new profile
confirm_profile()
{
    local msg="Enter a name for the new profile\n"
    PROFILE=$(dialog --inputbox "$msg" 10 50 "$PROFILE" --stdout) || return $?
    if [[ $PROFILE = */* ]]; then
        PROFILE=${PROFILE//\//_}
        confirm_profile
    elif [[ -e "$PROFILE_DIR/$PROFILE" ]]; then
        msg="A profile by the name '$PROFILE' already exists.
Do you want to overwrite it?"
        dialog --yesno "$msg" 10 50 --stdout || confirm_profile
    fi
}

# Creates a profile for ssid $1.
create_profile()
{
    local box flags key msg security
    PROFILE="$INTERFACE-${1//\//_}"
    [[ -e "$PROFILE_DIR/$PROFILE" ]] && PROFILE+=".wifi-menu"
    confirm_profile || return $?
    flags=$(grep -m 1 $'\t'"$1\$" "$NETWORKS" | cut -f 2)
    if [[ "$flags" =~ WPA|WEP ]]; then
        security=${BASH_REMATCH[0],,}
    else
        security=none
    fi
    if [[ "$flags" =~ PSK|WEP ]]; then
        [[ "$OBSCURE" ]] && box="--insecure --passwordbox" || box="--inputbox"
        msg="Enter $security security key for\n'$1'"
        key=$(dialog $box "$msg" 10 40 --stdout) || return $?
        if [[ "${#key}" -ge 8 && "${#key}" -le 63 ]]; then
            if [[ "$OBSCURE" ]]; then
                key=$(wpa_passphrase "$1" "$key" | sed -n "s/^[[:space:]]*psk=//p")
            else
                key=$(printf "%q" "$key")
            fi
        elif ! [[ "${#key}" -eq 64 && "$key" = +([[:xdigit:]]) ]]; then
            return 4
        fi
    fi
    cat << EOF > "$PROFILE_DIR/$PROFILE"
Description='Automatically generated profile by wifi-menu'
Interface=$INTERFACE
Connection=wireless
Security=$security
ESSID=$(printf "%q" "$1")
IP=dhcp
${key+Key=$key}
EOF
    printf "%s" "$PROFILE"
    return 0
}

# Connects to ssid $1 using an available profile or an automatically created
# one if none exists.
connect_to_ssid()
{
    local msg
    PROFILE=$(ssid_to_profile "$1")
    if [[ $? -ne 0 ]]; then
        PROFILE=$(create_profile "$1") || return $?
        NEW_PROFILE=1
    fi
    clear
    if systemctl is-active --quiet "netctl-auto@$INTERFACE.service"; then
        report_notice "Interface '$INTERFACE' is controlled by netctl-auto"
        if (( NEW_PROFILE )); then
            do_debug systemctl restart "netctl-auto@$INTERFACE.service"
        else
            report_error "A profile already exists for SSID '$1'"
        fi
    elif ! netctl switch-to "$PROFILE"; then
        if (( NEW_PROFILE )); then
            msg="         CONNECTING FAILED

Do you want to keep the generated profile ('$PROFILE')?"
            dialog --yesno "$msg" 10 40 --stdout || rm "$PROFILE_DIR/$PROFILE"
            clear
        fi
        return 2
    fi
    return 0
}

while [[ "$1" = -* ]]; do
    case "$1" in
        -h|--help)
            usage
            exit
            ;;
        -o|--obscure)
            OBSCURE=1
            shift
            ;;
        -*)
            report_error "Invalid option: $1"
            usage
            exit 255
            ;;
    esac
done
if [[ $# -gt 1 ]]; then
    report_error "Too many arguments"
    usage
    exit 255
fi

ensure_root "$(basename "$0")"
if ! type dialog &> /dev/null; then
    exit_error "Please install 'dialog' to use wifi-menu"
fi

INTERFACE=$1
if [[ -z "$INTERFACE" ]]; then
    INTERFACE=(/sys/class/net/*/wireless/)
    if [[ "${#INTERFACE[@]}" -ne 1 || ! -d "$INTERFACE" ]]; then
        report_error "Invalid interface specification"
        usage
        exit 255
    fi
    INTERFACE=${INTERFACE:15:-10}
    report_debug "Using interface '$INTERFACE'"
fi

cd /  # We do not want to spawn anything that can block unmounting
if [[ ! -d "/sys/class/net/$INTERFACE" ]]; then
    exit_error "No such interface: $INTERFACE"
fi

echo -n "Scanning for networks... "
CONNECTION=$(wpa_call "$INTERFACE" status 2> /dev/null | sed -n "s/^ssid=//p")
NETWORKS=$(wpa_supplicant_scan "$INTERFACE" 3,4,5)
if [[ $? -eq 0 ]]; then
    trap 'rm -f "$NETWORKS"' EXIT
    echo "done"
    init_profiles "$INTERFACE"
    init_entries "$NETWORKS"
    MSG="Select the network you wish to use
Flags description:
 * - handmade profile present
 + - automatically generated profile present
 - - no profile present
 ! - active connection present"
    CHOICE=$(dialog --column-separator : --menu "$MSG" 24 50 12 \
                 "${ENTRIES[@]}" --stdout)
    RETURN=$?
    if (( RETURN == 0 )); then
        connect_to_ssid "$CHOICE"
        RETURN=$?
    fi
else
    echo "failed"
    RETURN=3
fi

case $RETURN in
    0|2)  # Connected | Connecting failed
        ;;
    1)  # Canceled
        clear
        ;;
    3)  # No networks found
        report_error "No networks found"
        ;;
    4)  # Invalid passphrase length (WEP keys have tighter restrictions)
        clear
        report_error "Passphrase must be 8..63 characters"
        ;;
    255)  # ESC or error
        clear
        report_error "Aborted"
        ;;
    *)  # Should not happen
        report_error "Unexpected return code from dialog: $RETURN"
        RETURN=7
        ;;
esac
exit $RETURN


# vim: ft=sh ts=4 et sw=4:
