## /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=$(sed -n "0,/^[[:space:]]*ctrl_interface=/s///p" "$WPAConfigFile") 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 ! type wpa_supplicant &> /dev/null; then report_error "You need to install 'wpa_supplicant'" return 127 fi 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 } ## Unquotes a string, i.e. returned from wpa_cli ## Quoted: "string" -> string ## Non-Quoted: string -> string ## Hex: 737472696e67 -> string wpa_unquote() { local string="$1" if [[ $string == \"*\" ]]; then printf "%s" "${string:1:-1}" elif [[ $string == +([[:xdigit:]][[:xdigit:]]) ]]; then while [[ $string ]]; do printf "\x${string:0:2}" string=${string:2} done 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 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" if [[ $Key == +([[:xdigit:]][[:xdigit:]]) ]]; then echo "wep_key0=$Key" else echo "wep_key0=$(wpa_quote "$Key")" fi ;; wpa) echo "proto=RSN WPA" if [[ "${#Key}" -eq 64 && $Key == +([[:xdigit:]]) ]]; 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" [[ $ScanFrequencies ]] && echo "scan_freq=$ScanFrequencies" [[ $Frequency ]] && echo "frequency=$Frequency" [[ $Priority ]] && echo "priority=$Priority" } # vim: ft=sh ts=4 et sw=4: