diff options
Diffstat (limited to 'src/lib/wpa')
-rw-r--r-- | src/lib/wpa | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/src/lib/wpa b/src/lib/wpa new file mode 100644 index 0000000..65ef8f9 --- /dev/null +++ b/src/lib/wpa @@ -0,0 +1,233 @@ +## /usr/lib/network/globals needs to be sourced before this file + + +## Wrapper around wpa_cli to deal with supplicant configurations that set a +## non-standard control path +# $1: interface name +# $2...: call to the supplicant +wpa_call() { + local args=( "-i" "$1" ) + shift + + if [[ $WPA_CTRL_DIR ]]; then + args+=("-p" "$WPA_CTRL_DIR") + elif [[ $WPAConfigFile ]] && grep -q "^[[:space:]]*ctrl_interface=" "$WPAConfigFile"; then + WPA_CTRL_DIR=$(grep -m1 "^[[:space:]]*ctrl_interface=" "$WPAConfigFile") + WPA_CTRL_DIR=${WPA_CTRL_DIR#*ctrl_interface=} + if [[ $WPA_CTRL_DIR == DIR=* ]]; then + WPA_CTRL_DIR=${WPA_CTRL_DIR:4} + WPA_CTRL_DIR=${WPA_CTRL_DIR%% GROUP=*} + fi + args+=( "-p" "$WPA_CTRL_DIR" ) + fi + do_debug wpa_cli "${args[@]}" "$@" +} + +## Check if an instance of the wpa supplicant is active on an interface +# $1: interface name +wpa_is_active(){ + [[ $(wpa_call "$1" ping 2> /dev/null) == "PONG" ]] +} + +## Retrieves the state of an instance of the wpa supplicant +## Displays one of: DISCONNECTED, INTERFACE_DISABLED, INACTIVE, SCANNING, +## AUTHENTICATING, ASSOCIATING, ASSOCIATED, 4WAY_HANDSHAKE, +## GROUP_HANDSHAKE, COMPLETED +# $1: interface name +wpa_get_state() { + local state + state=$(wpa_call $1 status | grep -m1 "^wpa_state=") || return 1 + echo ${state#wpa_state=} +} + +## Waits until the wpa supplicant reaches a listed state +# $1: timeout +# $2: interface name +# $3...: accepted states +wpa_wait_until_state() { + local timeout=$1 interface=$2 states=( "${@:3}" ) + timeout_wait "$timeout" \ + 'in_array "$(wpa_get_state "$interface")" "${states[@]}"' +} + +## Waits while the wpa supplicant is in a listed state +# $1: timeout +# $2: interface name +# $3...: rejected states +wpa_wait_while_state() { + local timeout=$1 interface=$2 states=( "${@:3}" ) + timeout_wait "$timeout" \ + '! in_array "$(wpa_get_state "$interface")" "${states[@]}"' +} + +## Start an instance of the wpa supplicant +# $1: interface name +# $2: interface driver(s) +# $3: configuration file +wpa_start() { + local interface=$1 driver=$2 configuration=$3 + local pidfile="/run/wpa_supplicant_$interface.pid" + + if [[ $configuration ]]; then + configuration="-c$configuration" + else + WPA_CTRL_DIR="/run/wpa_supplicant" + configuration="-C$WPA_CTRL_DIR" + fi + + do_debug wpa_supplicant -B -P "$pidfile" -i "$interface" -D "$driver" \ + "$configuration" $WPAOptions + + # Wait up to one second for the pid file to appear + if ! timeout_wait 1 '[[ -f "$pidfile" ]]'; then + # Remove the configuration file if it was generated + configuration="$STATE_DIR/wpa_supplicant_$interface.conf" + [[ -f $configuration && -O $configuration ]] && rm -f "$configuration" + return 1 + fi +} + +## Stop an instance of the wpa supplicant +# $1: interface name +wpa_stop() { + local interface=$1 config_file="$STATE_DIR/wpa_supplicant_$1.conf" + # We need this as long as wpa_cli has a different default than netctl + if [[ -z $WPA_CTRL_DIR && -z $WPAConfigFile ]]; then + WPA_CTRL_DIR="/run/wpa_supplicant" + fi + + # Done if wpa_supplicant is already terminated for this interface + [[ -z $WPA_CTR_DIR || -e "$WPA_CTRL_DIR/$interface" ]] || return + + wpa_call "$interface" terminate > /dev/null + [[ -f $config_file && -O $config_file ]] && rm -f "$config_file" + + # Wait up to one second for the pid file to be removed + if ! timeout_wait 1 '[[ ! -f "/run/wpa_supplicant_$interface.pid" ]]'; then + kill "$(< "/run/wpa_supplicant_$interface.pid")" &> /dev/null + fi +} + +## Scan for networks within range +# $1: interface name +# $2: fields of the wpa_supplicant scan_results +wpa_supplicant_scan() { + local interface=$1 fields=$2 spawned_wpa=0 essids + # temp file used, as keeping ESSID's with spaces in their name in arrays + # is hard, obscure and kinda nasty. This is simpler and clearer. + + [[ $interface ]] || return 1 + essids=$(mktemp --tmpdir essid.XXXXXXXX) + + if ! wpa_is_active "$interface"; then + wpa_start "$interface" "${WPADriver:-nl80211,wext}" || return 1 + spawned_wpa=1 + fi + + wpa_call "$interface" scan > /dev/null + # Wait at least 3 seconds for scan results + sleep 3 + # Sometimes, that is not enough + wpa_wait_while_state 7 "$interface" "SCANNING" + wpa_call "$interface" scan_results | + tail -n+2 | + sort -rn -k3 | + sort -u -k5 | + sort -rn -k3 | + cut -f"$fields" > "$essids" + + # Fields are tab delimited + # Remove extraneous output from wpa_cli + # Sort by strength + # Remove duplicates + # Re-sort by strength as the removal disorders the list + # Cut to the AP/essid fields only + + (( spawned_wpa == 1 )) && wpa_stop "$interface" + + # File of 0 length: no ssid's + if [[ ! -s "$essids" ]]; then + rm -f "$essids" + return 1 + fi + + echo "$essids" +} + +## Print a string within quotes, unless it starts with the "-modifier +## Quoted: string "string" '""string"' +## Non-quoted: \"string "\"string" '"string' +# $1: string +wpa_quote() { + local string=$1 + if [[ $string == \"* ]]; then + printf '%s' "${string:1}" + else + printf '"%s"' "$string" + fi +} + +## Create a configuration file for wpa_supplicant without any network blocks +# $1: interface name +wpa_make_config_file() { + local config_file="$STATE_DIR/wpa_supplicant_$1.conf" + + if [[ -e $config_file ]]; then + report_debug "The anticipated filename '$config_file' is not available" + return 1 + fi + mkdir -p "$STATE_DIR" /run/wpa_supplicant + if ! : > "$config_file"; then + report_debug "Could not create the configuration file '$config_file'" + return 1 + fi + chmod 600 "$config_file" + + echo "ctrl_interface=/run/wpa_supplicant" >> "$config_file" + echo "ctrl_interface_group=${WPAGroup:-wheel}" >> "$config_file" + [[ $Country ]] && echo "country=$Country" >> "$config_file" + if is_yes "${AdHoc:-no}"; then + echo "ap_scan=2" >> "$config_file" + fi + echo "$config_file" +} + +## Generate a network block for wpa_supplicant +# $Security: type of wireless security +wpa_make_config_block() { + case $Security in + none) + echo "key_mgmt=NONE" + ;; + wep) + echo "key_mgmt=NONE" + echo "wep_tx_keyidx=0" + echo "wep_key0=$(wpa_quote "$Key")" + ;; + wpa) + echo "proto=RSN WPA" + if [[ "${#Key}" -eq 64 ]]; then + echo "psk=$Key" + else + echo "psk=$(wpa_quote "$Key")" + fi + ;; + wpa-configsection) + printf "%s\n" "${WPAConfigSection[@]}" + return + ;; + *) + report_error "Unsupported security setting: '$Security'" + return 1 + ;; + esac + + echo "ssid=$(wpa_quote "$ESSID")" + [[ $AP ]] && echo "bssid=${AP,,}" + is_yes "${Hidden:-no}" && echo "scan_ssid=1" + is_yes "${AdHoc:-no}" && echo "mode=1" + [[ $Priority ]] && echo "priority=$Priority" +} + + +# vim: ft=sh ts=4 et sw=4: |