summaryrefslogtreecommitdiffstats
path: root/src/netctl-auto
diff options
context:
space:
mode:
Diffstat (limited to 'src/netctl-auto')
-rwxr-xr-xsrc/netctl-auto260
1 files changed, 225 insertions, 35 deletions
diff --git a/src/netctl-auto b/src/netctl-auto
index ea692ca..7519325 100755
--- a/src/netctl-auto
+++ b/src/netctl-auto
@@ -1,4 +1,5 @@
#! /bin/bash
+# Contributed by: Sebastian Wicki <gandro@gmx.net>
. /usr/lib/network/globals
. "$SUBR_DIR/wpa"
@@ -7,68 +8,257 @@
: ${ACTIOND:=wpa_actiond -p /run/wpa_supplicant}
: ${ACTION_SCRIPT:=$SUBR_DIR/auto.action}
-if [[ $# != 2 || $1 != @(start|stop) ]]; then
- exit_error "Usage: netctl-auto [start|stop] <interface>"
-fi
+usage() {
+ cat << END
+Usage: netctl-auto {COMMAND} ...
+ [--help|--version]
-STARTSTOP=$1
-INTERFACE=$2
-PIDFILE="$STATE_DIR/wpa_actiond_$INTERFACE.pid"
-shift 2
+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
+}
-case $STARTSTOP in
- start)
- if wpa_is_active "$INTERFACE"; then
- exit_error "The interface ($INTERFACE) is already in use"
+## Print a list of interfaces for which wpa_actiond is active
+list_actiond_interfaces() {
+ find "$STATE_DIR" -maxdepth 1 -type f -name 'wpa_actiond_*.pid' \
+ -exec basename -s ".pid" -a {} + | cut -d'_' -f3-
+}
+
+## 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_actiond_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_actiond_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_actiond_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"
+ if [[ -x "$PROFILE_DIR/interfaces/$interface" ]]; then
+ source "$PROFILE_DIR/interfaces/$interface"
fi
if [[ $RFKill ]]; then
- enable_rf "$INTERFACE" "$RFKill" || exit 1
+ enable_rf "$interface" "$RFKill" || return 1
fi
- if ! WPA_CONF=$(wpa_make_config_file "$INTERFACE"); then
- exit_error "Could not create the configuration file for interface '$INTERFACE'"
+
+ 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" ]] || continue
+ [[ $Interface == "$interface" ]] || continue
is_yes "${ExcludeAuto:-no}" && exit 1
[[ $Connection != "wireless" ]] && exit 1
: ${Security:=none}
# Exclude wpa-config, the wpa_conf is 'complete' and doesn't fit in this scheme
[[ $Security == "wpa-config" ]] && exit 1
- printf "%s\n" "network={" "$(wpa_make_config_block)" "id_str=\"$profile\"" "}" >> "$WPA_CONF"
+ printf "%s\n" "network={" "$(wpa_make_config_block)" "id_str=\"$profile\"" "}" >> "$wpa_conf"
report_notice "Included profile '$profile'"
)
done
- # Start the WPA supplicant
+ # 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
- exit 0
+ if wpa_start "$interface" "$WPADriver" "$wpa_conf"; then
+ if $ACTIOND -i "$interface" -P "$pidfile" -a "$ACTION_SCRIPT"; then
+ return 0
fi
- wpa_stop "$INTERFACE"
+ wpa_stop "$interface"
fi
- exit 1
- ;;
- stop)
- kill "$(< "$PIDFILE")"
- if [[ -x "$PROFILE_DIR/interfaces/$INTERFACE" ]]; then
- source "$PROFILE_DIR/interfaces/$INTERFACE"
+ 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 ]] && disable_rf "$INTERFACE" "$RFKill"
- exit 0
- ;;
-esac
+ timeout_wait 1 '! wpa_is_active "$interface"' || wpa_stop "$interface"
+ ip link set dev "$interface" down
+ [[ $RFKill ]] && disable_rf "$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)
+ ensure_root "$(basename "$0")"
+ "$1" "$2";;
+ *)
+ exit_error "$(usage)";;
+ esac;;
+ *)
+ exit_error "$(usage)";;
+esac
# vim: ft=sh ts=4 et sw=4: