#! /bin/bash # Contributed by: Sebastian Wicki . /usr/lib/netctl/globals . "$SUBR_DIR/interface" . "$SUBR_DIR/rfkill" . "$SUBR_DIR/wpa" : ${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 --plain --state=running \ list-units 'netctl-auto@*.service' | while read -r name _; do systemd-escape --unescape --instance "$name" done } ## 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, monitor for changes # $1: interface start() { local interface="$1" 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 if ! WPAConfigFile=$(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" >> "$WPAConfigFile" 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\"" "}" >> "$WPAConfigFile" report_notice "Included profile '$profile'" ) done # Start the WPA supplicant and wpa_cli : ${WPADriver:=nl80211,wext} WPAOptions+=" -W" if wpa_start "$interface" "$WPADriver" "$WPAConfigFile"; then if wpa_call "$interface" -B -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, which automatically stops wpa_cli # $1: interface stop() { local interface="$1" wpa_stop "$interface" bring_interface_down "$interface" if [[ $RFKill ]]; then rf_disable "$interface" "$RFKill" fi } ## 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: