#! /bin/bash
# Contributed by: Sebastian Wicki <gandro@gmx.net>

. /usr/lib/netctl/globals
. "$SUBR_DIR/interface"
. "$SUBR_DIR/rfkill"
. "$SUBR_DIR/wpa"

: ${ACTIOND:=wpa_actiond -p /run/wpa_supplicant}
: ${ACTION_SCRIPT:=$SUBR_DIR/auto.action}


usage() {
    cat << END
Usage: netctl-auto {COMMAND} ...
                   [--help|--version]

Commands:
  list                  List available profiles (active='*', disabled='!')
  switch-to [PROFILE]   Switch to a profile, enable it if necessary
  is-active [PROFILE]   Check whether a profile is active
  enable [PROFILE]      Enable a profile for automatic selection
  disable [PROFILE]     Disable a profile for automatic selection
  enable-all            Enable all profiles for automatic selection
  disable-all           Disable all profiles for automatic selection
  is-enabled [PROFILE]  Check whether a profile is enabled
END
}

## Print a list of interfaces for which netctl-auto is active
list_netctl_auto_interfaces() {
    systemctl --full --no-legend --no-pager \
        --type=service --state=running list-units | \
        sed -nr 's/^netctl-auto@([[:alnum:]]+).*/\1/p'
}

## List all profiles available to the WPA supplicant
## Output format: INTERFACE ID FLAG PROFILE..
##  INTERFACE   network interface of the profile
##  ID          wpa_supplicant numerical network id
##  FLAG        'e'=enabled, 'd'=disabled, 'a'=active
##  PROFILE..   profile name, may contain spaces
list_wpa_profiles() {
    local interface
    for interface in $(list_netctl_auto_interfaces); do
        local id ssid bssid flags
        while IFS=$'\t' read -r id ssid bssid flags; do
            local flag="e"
            if [[ "$flags" =~ \[CURRENT\] ]]; then
                flag="a"
            elif [[ "$flags" =~ \[DISABLED\] ]]; then
                flag="d"
            fi

            local profile=$(wpa_call "$interface" get_network "$id" id_str)
            profile=$(wpa_unquote "$profile")

            echo "$interface" "$id" "$flag" "$profile"
        done < <(wpa_call "$interface" list_networks | tail -n+2)
    done
}

## Get the WPA supplicant network id and interface for the given profile
## Output format: INTERFACE ID
# $1: profile name
get_wpa_network_id() {
    local interface id flag profile
    while read -r interface id flag profile; do
        if [[ $profile == "$1" ]]; then
            echo "$interface" "$id"
            return 0
        fi
    done < <(list_wpa_profiles)

    report_error "Profile '$1' does not exist or is not available"
    return 1
}

## Enable or disable profiles in the WPA supplicant
# $1: profile action: "enable", "disable", "enable-all" or "disable-all"
# $2: profile name if action is "enable" or "disable"
profile_enable_disable() {
    local action="$1" profile="$2"
    local id interfaces wpa_cmd

    if [[ $profile ]]; then
        read -r interfaces id < <(get_wpa_network_id "$profile") || return 1
    else
        interfaces=$(list_netctl_auto_interfaces)
    fi

    case $action in
      enable)
        wpa_cmd=(enable_network "$id");;
      disable)
        wpa_cmd=(disable_network "$id");;
      enable-all)
        wpa_cmd=(enable_network all);;
      disable-all)
        wpa_cmd=(disable_network all);;
      *)
        return 1;
    esac

    local interface
    for interface in $interfaces; do
        wpa_call "$interface" "${wpa_cmd[@]}" >/dev/null
        if [[ "${wpa_cmd[0]}" == "enable_network" ]]; then
            wpa_call "$interface" reassociate >/dev/null
        fi
    done
}

## Select profile in WPA supplicant, but preserve state of all other networks
# $1: profile name
switch_to() {
    local profile="$1"
    local id interface timeout

    # Load profile interface, WPA network id and timeout
    read -r interface id < <(get_wpa_network_id "$profile") || return 1
    timeout=$(. "$PROFILE_DIR/$profile" >/dev/null; echo ${TimeoutWPA:=15})

    # List of enabled networks
    local enabled_networks=$(wpa_call "$interface" list_networks | tail -n+2 | \
        cut -f 1,4 | grep -Fv "[DISABLED]" | cut -f 1 | tr "\n" ' ')

    reenable_networks() {
        for network in $enabled_networks; do
            wpa_call "$interface" enable_network "$network" >/dev/null
        done

        if [[ $(wpa_get_state "$interface") != "COMPLETED" ]]; then
            if ! in_array "$id" $enabled_networks; then
                wpa_call "$interface" disable_network "$id" >/dev/null
            fi
        fi
    }

    # Reenable networks in case user aborts
    trap "reenable_networks; exit 1" SIGINT SIGTERM

    # select_network will disable all other networks on that interface
    wpa_call "$interface" select_network "$id" >/dev/null
    wpa_wait_until_completed "$timeout" "$interface"

    reenable_networks
}

## Check whether a profile is active
# $1: profile name
is_active() {
    local interface id flag profile
    while read -r interface id flag profile; do
        if [[ $profile == "$1" ]]; then
            if [[ $flag == "a" ]]; then
                echo "active"
                return 0
            else
                echo "inactive"
                return 1
            fi
        fi
    done < <(list_wpa_profiles)
    echo "unknown profile: '$1'"
    return 1
}

## Check whether a profile is enabled
# $1: profile name
is_enabled() {
    local interface id flag profile
    while read -r interface id flag profile; do
        if [[ $profile == "$1" ]]; then
            if [[ $flag != "d" ]]; then
                echo "enabled"
                return 0
            else
                echo "disabled"
                return 1
            fi
        fi
    done < <(list_wpa_profiles)
    echo "unknown profile: '$1'"
    return 1
}

## List all available profiles and their status
list() {
    local interface id flag profile
    list_wpa_profiles | while read -r interface id flag profile; do
        echo "$(echo $flag | tr 'aed' '* !')" "$profile"
    done
}

## Start and generate config file for the WPA supplicant, start wpa_actiond
# $1: interface
start() {
    local interface="$1"
    local pidfile="$STATE_DIR/wpa_actiond-$interface.pid"

    if interface_is_up "$interface"; then
        exit_error "The interface '$interface' is already up"
    fi
    if [[ $RFKill ]]; then
        rf_enable "$interface" "$RFKill" || return 1
    fi

    local wpa_conf
    if ! wpa_conf=$(wpa_make_config_file "$interface"); then
        exit_error "Could not create the configuration file for interface '$interface'"
    fi
    # Disable p2p to prevent wpa_supplicant from creating another control interface.
    echo "p2p_disabled=1" >> "$wpa_conf"

    local profile
    list_profiles | while IFS= read -r profile; do
        report_debug "Examining profile '$profile'"
        (
          source "$PROFILE_DIR/$profile"
          [[ $Interface == "$interface" && $Connection == "wireless" ]] || exit
          is_yes "${ExcludeAuto:-no}" && exit
          # Set default and exclude wpa-config as it does not fit this scheme
          [[ ${Security:=none} != "wpa-config" ]] || exit
          printf "%s\n" "network={" "$(wpa_make_config_block)" "id_str=\"$profile\"" "}" >> "$wpa_conf"
          report_notice "Included profile '$profile'"
        )
    done

    # Start the WPA supplicant and wpa_actiond
    : ${WPADriver:=nl80211,wext}
    WPAOptions+=" -W"
    if wpa_start "$interface" "$WPADriver" "$wpa_conf"; then
        if $ACTIOND -i "$interface" -P "$pidfile" -a "$ACTION_SCRIPT"; then
            return 0
        fi
        wpa_stop "$interface"
        bring_interface_down "$interface"
    fi
    # Systemd executes cleanup on failure
    return 1
}

## Stop the WPA supplicant and wpa_actiond
# $1: interface
stop() {
    local interface="$1"
    local pidfile="$STATE_DIR/wpa_actiond-$interface.pid"

    [[ -e "$pidfile" ]] && kill "$(< "$pidfile")"
    timeout_wait 1 '! wpa_is_active "$interface"' || wpa_stop "$interface"
    bring_interface_down "$interface"
    [[ $RFKill ]] && rf_disable "$interface" "$RFKill"
    return 0
}

## Remove WPA supplicant configuration files
# $1: interface
clean() {
    wpa_destroy_config_file "$1"
}


case $# in
  1)
    case $1 in
      --version)
        report_notice "netctl version $NETCTL_VERSION";;
      --help)
        usage;;
      list)
        "$1";;
      enable-all|disable-all)
        profile_enable_disable "$1";;
      *)
        exit_error "$(usage)";;
    esac;;
  2)
    case $1 in
      enable|disable)
        profile_enable_disable "$1" "$2";;
      switch-to|is-active|is-enabled)
        "${1//-/_}" "$2";;
      start|stop|clean)
        if [[ -t 0 ]]; then
            exit_error "Use 'systemctl ${1/clean/stop} netctl-auto@$2' to $1 netctl-auto."
        fi
        ensure_root "$(basename "$0")"
        load_interface_config "$2"
        "$1" "$2";;
      *)
        exit_error "$(usage)";;
    esac;;
  *)
    exit_error "$(usage)";;
esac


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