#! /bin/bash # Contributed by: Sebastian Wicki . /usr/lib/network/globals . "$SUBR_DIR/wpa" . "$SUBR_DIR/rfkill" : ${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='!') current List currently active profiles switch-to [PROFILE] Switch to a profile, enable it if necessary enable [PROFILE] Enable a profile for automatic selection disable [PROFILE] Disable a profile temporarily for automatic selection enable-all Enable all profiles for automatic selection disable-all Disable all profiles temporarily for automatic selection 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 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 [[ "$1" == "$profile" ]]; then echo "$interface" "$id" return 0 fi done < <(list_wpa_profiles) report_error "Profile '$1' does not exist or is not available" >&2 return 1 } ## Enable or disable profiles in 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 [[ -n "$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 if ! wpa_wait_until_state "$timeout" "$interface" "COMPLETED"; then report_error "WPA association/authentication failed for interface '$interface'" fi reenable_networks } ## List currently active profiles current() { local interface for interface in $(list_netctl_auto_interfaces); do local state=$(wpa_get_state "$interface") if [[ "$state" == "COMPLETED" ]]; then wpa_call "$interface" status | sed -n 's/^id_str=//p' fi done } ## List all available profiles and their status list() { local interface id flag profile while read -r interface id flag profile; do echo "$(echo $flag | tr 'aed' '* !')" "$profile" done < <(list_wpa_profiles) } ## Start and generate config file for the WPA supplicant, start wpa_actiond # $1: interface start() { local interface="$1" local pidfile="$STATE_DIR/wpa_actiond_$1.pid" if wpa_is_active "$interface"; then exit_error "The interface ($interface) is already in use" fi if [[ -x "$PROFILE_DIR/interfaces/$interface" ]]; then source "$PROFILE_DIR/interfaces/$interface" 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 local profile list_profiles | while 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" fi return 1 } ## Stop wpa_supplicant and wpa_actiond # $1: interface stop() { local interface="$1" local pidfile="$STATE_DIR/wpa_actiond_$1.pid" [[ -e "$pidfile" ]] && kill "$(< "$pidfile")" if [[ -x "$PROFILE_DIR/interfaces/$interface" ]]; then source "$PROFILE_DIR/interfaces/$interface" fi timeout_wait 1 '! wpa_is_active "$interface"' || wpa_stop "$interface" ip link set dev "$interface" down [[ $RFKill ]] && rf_disable "$interface" "$RFKill" return 0 } case $# in 1) case $1 in --version) report_notice "netctl version $NETCTL_VERSION";; --help) usage;; list|current) "$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) switch_to "$2";; start|stop) if [[ -t 0 ]]; then exit_error "Use 'systemctl $1 netctl-auto@$2' to $1 netctl-auto." fi ensure_root "$(basename "$0")" "$1" "$2";; *) exit_error "$(usage)";; esac;; *) exit_error "$(usage)";; esac # vim: ft=sh ts=4 et sw=4: