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

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

9
10
: ${ACTION_SCRIPT:=$SUBR_DIR/auto.action}

Jouke Witteveen's avatar
Jouke Witteveen committed
11

Sebastian Wicki's avatar
Sebastian Wicki committed
12
13
14
15
16
17
usage() {
    cat << END
Usage: netctl-auto {COMMAND} ...
                   [--help|--version]

Commands:
18
19
20
21
22
23
24
25
  list                  List available profiles (active='*', disabled='!')
  switch-to [PROFILE]   Switch to a profile, enable it if necessary
  is-active [PROFILE]   Check whether a profile is active
  enable [PROFILE]      Enable a profile for automatic selection
  disable [PROFILE]     Disable a profile for automatic selection
  enable-all            Enable all profiles for automatic selection
  disable-all           Disable all profiles for automatic selection
  is-enabled [PROFILE]  Check whether a profile is enabled
Sebastian Wicki's avatar
Sebastian Wicki committed
26
27
28
END
}

29
30
## Print a list of interfaces for which netctl-auto is active
list_netctl_auto_interfaces() {
31
32
33
34
    systemctl --full --no-legend --no-pager --plain --state=running \
              list-units 'netctl-auto@*.service' | while read -r name _; do
        systemd-escape --unescape --instance "$name"
    done
Sebastian Wicki's avatar
Sebastian Wicki committed
35
36
37
38
39
40
41
42
43
44
}

## 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
45
    for interface in $(list_netctl_auto_interfaces); do
Sebastian Wicki's avatar
Sebastian Wicki committed
46
47
48
49
50
51
52
53
54
        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

55
            local profile=$(wpa_call "$interface" get_network "$id" id_str)
Sebastian Wicki's avatar
Sebastian Wicki committed
56
57
            profile=$(wpa_unquote "$profile")

58
59
            echo "$interface" "$id" "$flag" "$profile"
        done < <(wpa_call "$interface" list_networks | tail -n+2)
Sebastian Wicki's avatar
Sebastian Wicki committed
60
61
62
    done
}

63
## Get the WPA supplicant network id and interface for the given profile
Sebastian Wicki's avatar
Sebastian Wicki committed
64
65
66
67
68
## Output format: INTERFACE ID
# $1: profile name
get_wpa_network_id() {
    local interface id flag profile
    while read -r interface id flag profile; do
69
        if [[ $profile == "$1" ]]; then
70
            echo "$interface" "$id"
Sebastian Wicki's avatar
Sebastian Wicki committed
71
72
73
74
            return 0
        fi
    done < <(list_wpa_profiles)

75
    report_error "Profile '$1' does not exist or is not available"
Sebastian Wicki's avatar
Sebastian Wicki committed
76
77
78
    return 1
}

79
## Enable or disable profiles in the WPA supplicant
Sebastian Wicki's avatar
Sebastian Wicki committed
80
81
82
# $1: profile action: "enable", "disable", "enable-all" or "disable-all"
# $2: profile name if action is "enable" or "disable"
profile_enable_disable() {
83
    local action="$1" profile="$2"
Sebastian Wicki's avatar
Sebastian Wicki committed
84
85
    local id interfaces wpa_cmd

Jouke Witteveen's avatar
Jouke Witteveen committed
86
    if [[ $profile ]]; then
Sebastian Wicki's avatar
Sebastian Wicki committed
87
88
        read -r interfaces id < <(get_wpa_network_id "$profile") || return 1
    else
89
        interfaces=$(list_netctl_auto_interfaces)
Sebastian Wicki's avatar
Sebastian Wicki committed
90
91
92
93
    fi

    case $action in
      enable)
94
        wpa_cmd=(enable_network "$id");;
Sebastian Wicki's avatar
Sebastian Wicki committed
95
      disable)
96
        wpa_cmd=(disable_network "$id");;
Sebastian Wicki's avatar
Sebastian Wicki committed
97
98
99
100
101
102
103
104
105
106
      enable-all)
        wpa_cmd=(enable_network all);;
      disable-all)
        wpa_cmd=(disable_network all);;
      *)
        return 1;
    esac

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

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

133
134
135
        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
136
137
138
139
140
141
142
143
            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
144
    wpa_call "$interface" select_network "$id" >/dev/null
145
    wpa_wait_until_completed "$timeout" "$interface"
Sebastian Wicki's avatar
Sebastian Wicki committed
146
147
148
149

    reenable_networks
}

150
151
152
153
154
155
156
157
158
159
160
161
162
## Check whether a profile is active
# $1: profile name
is_active() {
    local interface id flag profile
    while read -r interface id flag profile; do
        if [[ $profile == "$1" ]]; then
            if [[ $flag == "a" ]]; then
                echo "active"
                return 0
            else
                echo "inactive"
                return 1
            fi
Sebastian Wicki's avatar
Sebastian Wicki committed
163
        fi
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
    done < <(list_wpa_profiles)
    echo "unknown profile: '$1'"
    return 1
}

## Check whether a profile is enabled
# $1: profile name
is_enabled() {
    local interface id flag profile
    while read -r interface id flag profile; do
        if [[ $profile == "$1" ]]; then
            if [[ $flag != "d" ]]; then
                echo "enabled"
                return 0
            else
                echo "disabled"
                return 1
            fi
        fi
    done < <(list_wpa_profiles)
    echo "unknown profile: '$1'"
    return 1
Sebastian Wicki's avatar
Sebastian Wicki committed
186
187
188
189
190
}

## List all available profiles and their status
list() {
    local interface id flag profile
191
    list_wpa_profiles | while read -r interface id flag profile; do
Sebastian Wicki's avatar
Sebastian Wicki committed
192
        echo "$(echo $flag | tr 'aed' '* !')" "$profile"
193
    done
Sebastian Wicki's avatar
Sebastian Wicki committed
194
195
}

196
## Start and generate config file for the WPA supplicant, monitor for changes
Sebastian Wicki's avatar
Sebastian Wicki committed
197
198
# $1: interface
start() {
199
    local interface="$1"
Sebastian Wicki's avatar
Sebastian Wicki committed
200

201
202
    if interface_is_up "$interface"; then
        exit_error "The interface '$interface' is already up"
203
    fi
Jouke Witteveen's avatar
Jouke Witteveen committed
204
    if [[ $RFKill ]]; then
Jouke Witteveen's avatar
Jouke Witteveen committed
205
        rf_enable "$interface" "$RFKill" || return 1
Jouke Witteveen's avatar
Jouke Witteveen committed
206
    fi
207

208
    if ! WPAConfigFile=$(wpa_make_config_file "$interface"); then
Sebastian Wicki's avatar
Sebastian Wicki committed
209
        exit_error "Could not create the configuration file for interface '$interface'"
Jouke Witteveen's avatar
Jouke Witteveen committed
210
    fi
211
212
    # Disable p2p to prevent wpa_supplicant from creating another control interface
    echo "p2p_disabled=1" >> "$WPAConfigFile"
Sebastian Wicki's avatar
Sebastian Wicki committed
213

214
    filter_profiles "$interface" wireless | while IFS= read -r profile; do
Jouke Witteveen's avatar
Jouke Witteveen committed
215
        report_debug "Examining profile '$profile'"
216
        (
217
          load_profile "$profile"
218
219
220
          is_yes "${ExcludeAuto:-no}" && exit
          # Set default and exclude wpa-config as it does not fit this scheme
          [[ ${Security:=none} != "wpa-config" ]] || exit
221
          printf '%s\n' "network={" "$(wpa_make_config_block)" "id_str=\"$profile\"" "}" >> "$WPAConfigFile"
Jouke Witteveen's avatar
Jouke Witteveen committed
222
          report_notice "Included profile '$profile'"
223
224
225
        )
    done

226
    # Start the WPA supplicant and wpa_cli
227
228
    : ${WPADriver:=nl80211,wext}
    WPAOptions+=" -W"
229
230
    if wpa_start "$interface" "$WPADriver" "$WPAConfigFile"; then
        if wpa_call "$interface" -B -a "$ACTION_SCRIPT"; then
Sebastian Wicki's avatar
Sebastian Wicki committed
231
            return 0
232
        fi
Sebastian Wicki's avatar
Sebastian Wicki committed
233
        wpa_stop "$interface"
234
        bring_interface_down "$interface"
235
    fi
236
    # Systemd executes cleanup on failure
Sebastian Wicki's avatar
Sebastian Wicki committed
237
238
239
    return 1
}

240
## Stop the WPA supplicant, which automatically stops wpa_cli
Sebastian Wicki's avatar
Sebastian Wicki committed
241
242
# $1: interface
stop() {
243
    local interface="$1"
Sebastian Wicki's avatar
Sebastian Wicki committed
244

245
    wpa_stop "$interface"
246
    bring_interface_down "$interface"
247
248
249
    if [[ $RFKill ]]; then
        rf_disable "$interface" "$RFKill"
    fi
Sebastian Wicki's avatar
Sebastian Wicki committed
250
}
James Rayner's avatar
James Rayner committed
251

252
253
254
255
256
257
## Remove WPA supplicant configuration files
# $1: interface
clean() {
    wpa_destroy_config_file "$1"
}

Jouke Witteveen's avatar
Jouke Witteveen committed
258

Sebastian Wicki's avatar
Sebastian Wicki committed
259
260
261
262
263
264
265
case $# in
  1)
    case $1 in
      --version)
        report_notice "netctl version $NETCTL_VERSION";;
      --help)
        usage;;
266
      list)
Sebastian Wicki's avatar
Sebastian Wicki committed
267
268
        "$1";;
      enable-all|disable-all)
269
        profile_enable_disable "$1";;
Sebastian Wicki's avatar
Sebastian Wicki committed
270
271
272
273
274
275
      *)
        exit_error "$(usage)";;
    esac;;
  2)
    case $1 in
      enable|disable)
276
        profile_enable_disable "$1" "$2";;
277
278
      switch-to|is-active|is-enabled)
        "${1//-/_}" "$2";;
279
      start|stop|clean)
280
        if [[ -t 0 ]]; then
281
            exit_error "Use 'systemctl ${1/clean/stop} netctl-auto@$2' to $1 netctl-auto."
282
        fi
Sebastian Wicki's avatar
Sebastian Wicki committed
283
        ensure_root "$(basename "$0")"
284
        load_interface_config "$2"
285
        "$1" "$2";;
Sebastian Wicki's avatar
Sebastian Wicki committed
286
287
288
289
290
291
      *)
        exit_error "$(usage)";;
    esac;;
  *)
    exit_error "$(usage)";;
esac
James Rayner's avatar
James Rayner committed
292

Jouke Witteveen's avatar
Jouke Witteveen committed
293

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