netctl-auto 7.9 KB
Newer Older
1
#! /bin/bash
2
# Contributed by: Sebastian Wicki <gandro@gmx.net>
3
4

. /usr/lib/network/globals
5
. "$SUBR_DIR/wpa"
Jouke Witteveen's avatar
Jouke Witteveen committed
6
. "$SUBR_DIR/rfkill"
James Rayner's avatar
James Rayner committed
7

8
9
10
: ${ACTIOND:=wpa_actiond -p /run/wpa_supplicant}
: ${ACTION_SCRIPT:=$SUBR_DIR/auto.action}

Sebastian Wicki's avatar
Sebastian Wicki committed
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
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 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

51
            local profile=$(wpa_call "$interface" get_network "$id" id_str)
Sebastian Wicki's avatar
Sebastian Wicki committed
52
53
            profile=$(wpa_unquote "$profile")

54
55
            echo "$interface" "$id" "$flag" "$profile"
        done < <(wpa_call "$interface" list_networks | tail -n+2)
Sebastian Wicki's avatar
Sebastian Wicki committed
56
57
58
59
60
61
62
63
64
65
    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
66
            echo "$interface" "$id"
Sebastian Wicki's avatar
Sebastian Wicki committed
67
68
69
70
71
72
73
74
75
76
77
78
            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() {
79
    local action="$1" profile="$2"
Sebastian Wicki's avatar
Sebastian Wicki committed
80
81
82
83
84
85
86
87
88
89
    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)
90
        wpa_cmd=(enable_network "$id");;
Sebastian Wicki's avatar
Sebastian Wicki committed
91
      disable)
92
        wpa_cmd=(disable_network "$id");;
Sebastian Wicki's avatar
Sebastian Wicki committed
93
94
95
96
97
98
99
100
101
102
      enable-all)
        wpa_cmd=(enable_network all);;
      disable-all)
        wpa_cmd=(disable_network all);;
      *)
        return 1;
    esac

    local interface
    for interface in $interfaces; do
103
        wpa_call "$interface" "${wpa_cmd[@]}" >/dev/null
Sebastian Wicki's avatar
Sebastian Wicki committed
104
        if [[ "${wpa_cmd[0]}" == "enable_network" ]]; then
105
            wpa_call "$interface" reassociate >/dev/null
Sebastian Wicki's avatar
Sebastian Wicki committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
        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
121
    local enabled_networks=$(wpa_call "$interface" list_networks | tail -n+2 | \
122
        cut -f 1,4 | grep -Fv "[DISABLED]" | cut -f 1 | tr "\n" ' ')
Sebastian Wicki's avatar
Sebastian Wicki committed
123
124
125

    reenable_networks() {
        for network in $enabled_networks; do
126
            wpa_call "$interface" enable_network "$network" >/dev/null
Sebastian Wicki's avatar
Sebastian Wicki committed
127
128
        done

129
130
131
        if [[ $(wpa_get_state "$interface") != "COMPLETED" ]]; then
            if ! in_array "$id" $enabled_networks; then
                wpa_call "$interface" disable_network "$id" >/dev/null
Sebastian Wicki's avatar
Sebastian Wicki committed
132
133
134
135
136
137
138
139
            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
140
141
    wpa_call "$interface" select_network "$id" >/dev/null
    if ! wpa_wait_until_state "$timeout" "$interface" "COMPLETED"; then
Sebastian Wicki's avatar
Sebastian Wicki committed
142
        report_error "WPA association/authentication failed for interface '$interface'"
James Rayner's avatar
James Rayner committed
143
    fi
Sebastian Wicki's avatar
Sebastian Wicki committed
144
145
146
147
148
149
150
151

    reenable_networks
}

## List currently active profiles
current() {
    local interface
    for interface in $(list_actiond_interfaces); do
152
        local state=$(wpa_get_state "$interface")
Sebastian Wicki's avatar
Sebastian Wicki committed
153
        if [[ "$state" == "COMPLETED" ]]; then
154
            wpa_call "$interface" status | sed -n 's/^id_str=//p'
Sebastian Wicki's avatar
Sebastian Wicki committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        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() {
170
    local interface="$1"
Sebastian Wicki's avatar
Sebastian Wicki committed
171
172
173
174
175
176
177
    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"
178
    fi
Jouke Witteveen's avatar
Jouke Witteveen committed
179
    if [[ $RFKill ]]; then
Jouke Witteveen's avatar
Jouke Witteveen committed
180
        rf_enable "$interface" "$RFKill" || return 1
Jouke Witteveen's avatar
Jouke Witteveen committed
181
    fi
182

Sebastian Wicki's avatar
Sebastian Wicki committed
183
184
185
186

    local wpa_conf
    if ! wpa_conf=$(wpa_make_config_file "$interface"); then
        exit_error "Could not create the configuration file for interface '$interface'"
Jouke Witteveen's avatar
Jouke Witteveen committed
187
    fi
Sebastian Wicki's avatar
Sebastian Wicki committed
188
189

    local profile
190
    list_profiles | while read -r profile; do
Jouke Witteveen's avatar
Jouke Witteveen committed
191
        report_debug "Examining profile '$profile'"
192
        (
Jouke Witteveen's avatar
Jouke Witteveen committed
193
          source "$PROFILE_DIR/$profile"
Sebastian Wicki's avatar
Sebastian Wicki committed
194
          [[ $Interface == "$interface" ]] || continue
Jouke Witteveen's avatar
Jouke Witteveen committed
195
196
197
198
199
          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
200

Sebastian Wicki's avatar
Sebastian Wicki committed
201
          printf "%s\n" "network={" "$(wpa_make_config_block)" "id_str=\"$profile\"" "}" >> "$wpa_conf"
Jouke Witteveen's avatar
Jouke Witteveen committed
202
          report_notice "Included profile '$profile'"
203
204
205
        )
    done

Sebastian Wicki's avatar
Sebastian Wicki committed
206
    # Start the WPA supplicant and wpa_actiond
207
208
    : ${WPADriver:=nl80211,wext}
    WPAOptions+=" -W"
Sebastian Wicki's avatar
Sebastian Wicki committed
209
210
211
    if wpa_start "$interface" "$WPADriver" "$wpa_conf"; then
        if $ACTIOND -i "$interface" -P "$pidfile" -a "$ACTION_SCRIPT"; then
            return 0
212
        fi
Sebastian Wicki's avatar
Sebastian Wicki committed
213
        wpa_stop "$interface"
214
    fi
Sebastian Wicki's avatar
Sebastian Wicki committed
215
216
217
218
219
220
    return 1
}

## Stop wpa_supplicant and wpa_actiond
# $1: interface
stop() {
221
    local interface="$1"
Sebastian Wicki's avatar
Sebastian Wicki committed
222
223
224
225
226
    local pidfile="$STATE_DIR/wpa_actiond_$1.pid"

    [[ -e "$pidfile" ]] && kill "$(< "$pidfile")"
    if [[ -x "$PROFILE_DIR/interfaces/$interface" ]]; then
        source "$PROFILE_DIR/interfaces/$interface"
227
    fi
Sebastian Wicki's avatar
Sebastian Wicki committed
228
229
    timeout_wait 1 '! wpa_is_active "$interface"' || wpa_stop "$interface"
    ip link set dev "$interface" down
Jouke Witteveen's avatar
Jouke Witteveen committed
230
    [[ $RFKill ]] && rf_disable "$interface" "$RFKill"
Sebastian Wicki's avatar
Sebastian Wicki committed
231
232
    return 0
}
James Rayner's avatar
James Rayner committed
233

Sebastian Wicki's avatar
Sebastian Wicki committed
234
235
236
237
238
239
240
241
242
243
case $# in
  1)
    case $1 in
      --version)
        report_notice "netctl version $NETCTL_VERSION";;
      --help)
        usage;;
      list|current)
        "$1";;
      enable-all|disable-all)
244
        profile_enable_disable "$1";;
Sebastian Wicki's avatar
Sebastian Wicki committed
245
246
247
248
249
250
      *)
        exit_error "$(usage)";;
    esac;;
  2)
    case $1 in
      enable|disable)
251
        profile_enable_disable "$1" "$2";;
Sebastian Wicki's avatar
Sebastian Wicki committed
252
253
254
255
      switch-to)
        switch_to "$2";;
      start|stop)
        ensure_root "$(basename "$0")"
256
        "$1" "$2";;
Sebastian Wicki's avatar
Sebastian Wicki committed
257
258
259
260
261
262
      *)
        exit_error "$(usage)";;
    esac;;
  *)
    exit_error "$(usage)";;
esac
James Rayner's avatar
James Rayner committed
263

264
# vim: ft=sh ts=4 et sw=4: