wireless-ap: Check that secret has the correct length and no invalid characters
[people/ms/network.git] / src / functions / functions.wireless
1 #!/bin/bash
2 ###############################################################################
3 #                                                                             #
4 # IPFire.org - A linux based firewall                                         #
5 # Copyright (C) 2012  IPFire Network Development Team                         #
6 #                                                                             #
7 # This program is free software: you can redistribute it and/or modify        #
8 # it under the terms of the GNU General Public License as published by        #
9 # the Free Software Foundation, either version 3 of the License, or           #
10 # (at your option) any later version.                                         #
11 #                                                                             #
12 # This program is distributed in the hope that it will be useful,             #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of              #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               #
15 # GNU General Public License for more details.                                #
16 #                                                                             #
17 # You should have received a copy of the GNU General Public License           #
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
19 #                                                                             #
20 ###############################################################################
21
22 # Sets the global wireless country code. Default is 00 = world.
23 WIRELESS_REGULATORY_DOMAIN="00"
24 NETWORK_SETTINGS_FILE_PARAMS="${NETWORK_SETTINGS_FILE_PARAMS} WIRELESS_REGULATORY_DOMAIN"
25
26 WIRELESS_REGULATORY_DOMAIN_DATABASE="/usr/lib/crda/regulatory.bin"
27
28 WIRELESS_DEFAULT_ENCRYPTION_MODE="NONE"
29 WIRELESS_VALID_ENCRYPTION_MODES="WPA2-PSK-SHA256 WPA2-PSK \
30         WPA-PSK-SHA256 WPA-PSK NONE"
31
32 declare -A WIRELESS_CHANNEL_BANDWIDTHS=(
33         ["802.11ac"]="20 40 80 160 80+80"
34         ["802.11a/n"]="20 40"
35         ["802.11a"]="20 40"
36         ["802.11g/n"]="20 40"
37         ["802.11g"]="20 40"
38 )
39
40 WIRELESS_ENVIRONMENTS=( "indoor+outdoor" "indoor" "outdoor" )
41 WIRELESS_DEFAULT_ENVIRONMENT="${WIRELESS_ENVIRONMENTS[0]}"
42
43 cli_wireless() {
44         local action=${1}
45         shift 1
46
47         case "${action}" in
48                 network)
49                         cli_wireless_network "$@"
50                         ;;
51                 *)
52                         error "Unrecognized argument: ${action}"
53                         exit ${EXIT_ERROR}
54                         ;;
55         esac
56 }
57
58 wireless_create() {
59         local device=${1}
60         assert isset device
61         shift
62
63         local address
64         local channel
65         local phy
66         local type="managed"
67
68         while [ $# -gt 0 ]; do
69                 case "${1}" in
70                         --address=*)
71                                 address=$(cli_get_val "${1}")
72                                 ;;
73                         --channel=*)
74                                 channel=$(cli_get_val "${1}")
75                                 ;;
76                         --phy=*)
77                                 phy=$(cli_get_val "${1}")
78                                 phy=$(phy_get ${phy})
79                                 ;;
80                         --type=*)
81                                 type=$(cli_get_val "${1}")
82
83                                 # ap --> __ap
84                                 [ "${type}" = "ap" ] && type="__ap"
85                                 ;;
86                         *)
87                                 error "Unrecognized argument: ${1}"
88                                 return ${EXIT_ERROR}
89                                 ;;
90                 esac
91                 shift
92         done
93
94         case "${type}" in
95                 ibss|managed|monitor|__ap)
96                         ;;
97                 mesh-point)
98                         type="mp"
99                         ;;
100                 *)
101                         log ERROR "Unknown type: ${type}"
102                         return ${EXIT_ERROR}
103                         ;;
104
105         esac
106
107         assert phy_exists ${phy}
108         isset address || address=$(mac_generate)
109
110         cmd_quiet iw phy ${phy} interface add ${device} type ${type}
111         local ret=$?
112
113         if [ ${ret} -eq ${EXIT_OK} ]; then
114                 log DEBUG "created wireless device '${device}' (${type})"
115
116                 if isset address; then
117                         device_set_address ${device} ${address}
118                 fi
119         else
120                 log ERROR "could not create wireless device '${device}' (${type}): ${ret}"
121         fi
122
123         # Set the channel
124         if isset channel; then
125                 wireless_set_channel "${device}" "${channel}" "auto" || return $?
126         fi
127
128         return ${ret}
129 }
130
131 wireless_remove() {
132         local device=${1}
133         assert isset device
134
135         if ! device_exists ${device}; then
136                 return ${EXIT_OK}
137         fi
138
139         # Tear down the device (if necessary).
140         device_set_down ${device}
141
142         # Remove it.
143         cmd_quiet iw dev ${device} del
144         local ret=$?
145
146         if [ ${ret} -eq ${EXIT_OK} ]; then
147                 log DEBUG "removed wireless device '${device}'"
148         else
149                 log ERROR "could not remove wireless device '${device}': ${ret}"
150         fi
151
152         return ${ret}
153 }
154
155 wireless_get_reg_domain() {
156         # Returns the country code for the wireless device.
157         # Defaults to 00 = world if unset.
158         print "${WIRELESS_REGULATORY_DOMAIN:-00}"
159 }
160
161 wireless_init_reg_domain() {
162         local country_code="$(wireless_get_reg_domain)"
163
164         wireless_set_reg_domain "${country_code}" --no-reset
165 }
166
167 wireless_set_reg_domain() {
168         local country_code
169         local reset="true"
170
171         while [ $# -gt 0 ]; do
172                 case "${1}" in
173                         --no-reset)
174                                 reset="false"
175                                 ;;
176                         -*)
177                                 log ERROR "Ignoring invalid option: ${1}"
178                                 ;;
179                         *)
180                                 country_code="${1}"
181                                 ;;
182                 esac
183                 shift
184         done
185
186         # Check if configuration value is valid
187         if ! wireless_valid_reg_domain "${country_code}"; then
188                 log ERROR "Invalid wireless regulatory domain: ${country_code}"
189                 return ${EXIT_ERROR}
190         fi
191
192         # Before the wireless reg domain is set, it helps to reset to 00 first.
193         if enabled reset; then
194                 iw reg set 00 &>/dev/null
195         fi
196
197         log INFO "Setting wireless regulatory domain country to '${country_code}'"
198         iw reg set "${country_code}"
199 }
200
201 wireless_valid_reg_domain() {
202         local country_code="${1}"
203
204         # Empty country codes are invalid
205         isset country_code || return ${EXIT_FALSE}
206
207         local valid_country_codes="$(wireless_list_reg_domains)"
208
209         if list_match "${country_code}" ${valid_country_codes}; then
210                 return ${EXIT_TRUE}
211         fi
212
213         return ${EXIT_FALSE}
214 }
215
216 wireless_list_reg_domains() {
217         if [ ! -r "${WIRELESS_REGULATORY_DOMAIN_DATABASE}" ]; then
218                 log ERROR "Could not read ${WIRELESS_REGULATORY_DOMAIN_DATABASE}"
219                 return ${EXIT_ERROR}
220         fi
221
222         local line
223         while read line; do
224                 # Check if line starts with "country"
225                 [ "${line:0:7}" = "country" ] || continue
226
227                 # Print country code
228                 print "${line:8:2}"
229         done <<< "$(regdbdump ${WIRELESS_REGULATORY_DOMAIN_DATABASE})"
230 }
231
232 # http://en.wikipedia.org/wiki/List_of_WLAN_channels
233 wireless_channel_to_frequency() {
234         local channel=${1}
235
236         # Works only for valid channel numbers
237         if ! wireless_channel_is_valid "${channel}"; then
238                 log ERROR "Invalid wireless channel: ${channel}"
239                 return ${EXIT_ERROR}
240         fi
241
242         # 2.4 GHz band
243         case "${channel}" in
244                 [123456789]|1[0123])
245                         print "$(( 2407 + (${channel} * 5)))"
246                         return ${EXIT_OK}
247                         ;;
248                 14)
249                         print "2484"
250                         return ${EXIT_OK}
251                         ;;
252         esac
253
254         # 5 GHz band
255         case "${channel}" in
256                 3[68]|4[02468]|5[26]|6[04]|10[048]|11[26]|12[048]|13[26]|14[09]|15[37]|16[15])
257                         print "$(( 5000 + (${channel} * 5)))"
258                         return ${EXIT_OK}
259                         ;;
260         esac
261
262         return ${EXIT_ERROR}
263 }
264
265 wireless_frequency_to_channel() {
266         local frequency=${1}
267
268         assert isinteger frequency
269
270         # Everything that is too high
271         if [ ${frequency} -gt 5825 ]; then
272                 return ${EXIT_ERROR}
273
274         # 5 GHz Band
275         elif [ ${frequency} -gt 5000 ]; then
276                 (( frequency = frequency - 5000 ))
277
278                 # Must be divisible by 5
279                 [ "$(( frequency % 5 ))" -ne 0 ] && return ${EXIT_ERROR}
280
281                 print "$(( frequency / 5 ))"
282
283         # 2.4 GHz Band - Channel 14
284         elif [ ${frequency} -eq 2484 ]; then
285                 print "14"
286
287         # 2.4 GHz Band
288         elif [ ${frequency} -gt 2407 ]; then
289                 (( frequency = frequency - 2407 ))
290
291                 # Must be divisible by 5
292                 [ "$(( frequency % 5 ))" -ne 0 ] && return ${EXIT_ERROR}
293
294                 print "$(( frequency / 5 ))"
295
296         # Everything else
297         else
298                 return ${EXIT_ERROR}
299         fi
300
301         return ${EXIT_OK}
302 }
303
304 wireless_channel_is_valid() {
305         local channel=${1}
306
307         case "${channel}" in
308                 # 2.4 GHz Band
309                 [123456789]|1[0123]|14)
310                         return ${EXIT_TRUE}
311                         ;;
312
313                 # 5 GHz Band
314                 3[68]|4[02468]|5[26]|6[04]|10[048]|11[26]|12[048]|13[26]|14[09]|15[37]|16[15])
315                         return ${EXIT_TRUE}
316                         ;;
317         esac
318
319         # Invalid channel number given
320         return ${EXIT_FALSE}
321 }
322
323 wireless_channel_bandwidth_is_valid() {
324         local mode="${1}"
325         assert isset mode
326
327         local bandwidth="${2}"
328         assert isset bandwidth
329
330         local bandwidths="${WIRELESS_CHANNEL_BANDWIDTHS["${mode}"]}"
331
332         list_match "${bandwidth}" ${bandwidths}
333 }
334
335 wireless_channel_is_ht40_plus() {
336         local channel="${1}"
337         assert isinteger channel
338
339         # 2.4 GHz
340         if [ ${channel} -le 6 ]; then
341                 return ${EXIT_TRUE}
342         fi
343
344         return ${EXIT_FALSE}
345 }
346
347 wireless_channel_is_ht40_minus() {
348         local channel="${1}"
349         assert isinteger channel
350
351         # 2.4 GHz
352         if [ ${channel} -ge 6 ]; then
353                 return ${EXIT_TRUE}
354         fi
355
356         return ${EXIT_FALSE}
357 }
358
359 wireless_set_channel() {
360         local device="${1}"
361         local channel="${2}"
362         local bandwidth="${3}"
363
364         # Check if the device exists
365         if ! device_exists "${device}"; then
366                 log ERROR "No such device: ${device}"
367                 return ${EXIT_ERROR}
368         fi
369
370         # Check if the channel number is valid
371         if ! wireless_channel_is_valid "${channel}"; then
372                 log ERROR "Invalid wireless channel: ${channel}"
373                 return ${EXIT_ERROR}
374         fi
375
376         local ht_flag
377         if [ "${bandwidth}" = "auto" ]; then
378                 local phy="$(device_get_phy "${device}")"
379
380                 # Offset of a 40 MHz channel
381                 local ht_offset=5
382
383                 if wireless_channel_is_ht40_plus "${channel}" \
384                                 && phy_supports_ht_capability "${phy}" "HT40+" \
385                                 && phy_supports_channel "${phy}" $(( channel + ht_offset )); then
386                         ht_flag="HT40+"
387
388                 elif wireless_channel_is_ht40_minus "${channel}" \
389                                 && phy_supports_ht_capability "${phy}" "HT40-" \
390                                 && phy_supports_channel "${phy}" $(( channel - ht_offset )); then
391                         ht_flags="HT40-"
392                 fi
393         fi
394
395         log DEBUG "Setting wireless channel on device '${device}' to channel '${channel}'"
396         cmd iw dev "${device}" set channel "${channel}" "${ht_flag}"
397 }
398
399 wireless_pre_shared_key_is_valid() {
400         local psk="${1}"
401
402         # Length of the PSK
403         local l="${#psk}"
404
405         # For WPA*, the key must be between 8 and 63 chars
406         if [ ${l} -lt 8 ] || [ ${l} -gt 63 ]; then
407                 return ${EXIT_FALSE}
408         fi
409
410         # Can only contain ASCII chararcters
411         if contains_non_ascii_characters "${psk}"; then
412                 return ${EXIT_FALSE}
413         fi
414
415         # Seems OK
416         return ${EXIT_TRUE}
417 }
418
419 wireless_client_is_connected() {
420         local device="${1}"
421
422         device_has_carrier "${device}"
423 }
424
425 wireless_ibss_join() {
426         local device=${1}
427         assert isset device
428         shift
429
430         local bssid
431         local essid
432         local frequency
433
434         while [ $# -gt 0 ]; do
435                 case "${1}" in
436                         --bssid=*)
437                                 bssid="$(cli_get_val "${1}")"
438                                 ;;
439                         --essid=*)
440                                 essid="$(cli_get_val "${1}")"
441                                 ;;
442                         --channel=*)
443                                 local channel="$(cli_get_val "${1}")"
444
445                                 # Save the frequency of the channel instead
446                                 # of the channel itself.
447                                 if isset channel; then
448                                         frequency="$(wireless_channel_to_frequency ${channel})"
449                                 fi
450                                 ;;
451                 esac
452                 shift
453         done
454
455         # Check input.
456         assert ismac bssid
457         assert isset essid
458         assert isinteger frequency
459
460         # Set device up.
461         device_set_up "${device}"
462
463         log INFO "${device} joining ibss network: ${essid} (${bssid})"
464         cmd_quiet iw dev "${device}" ibss join "${essid}" \
465                 "${frequency}" fixed-freq "${bssid}"
466 }
467
468 wireless_ibss_leave() {
469         local device=${1}
470         assert isset device
471
472         log INFO "${device} leaving ibss network"
473         cmd_quiet iw dev "${device}" ibss leave
474 }
475
476 wireless_is_radar_frequency() {
477         local frequency="${1}"
478         assert isset frequency
479
480         [[ ${frequency} -ge 5260 ]] && [[ ${frequency} -le 5700 ]]
481 }
482
483 wireless_monitor() {
484         local device="${1}"
485         assert isset device
486         shift
487
488         local monitor_device="$(port_find_free "${PORT_PATTERN_WIRELESS_MONITOR}")"
489
490         # Create an 802.11 monitoring device
491         wireless_create "${monitor_device}" --phy="${device}" --type="monitor"
492         local ret=$?
493
494         case "${ret}" in
495                 0)
496                         # Bring up the device
497                         device_set_up "${monitor_device}"
498
499                         # Starting tcpdump
500                         tcpdump -i "${monitor_device}" "$@"
501
502                         # Remove the monitoring interface.
503                         wireless_remove "${monitor_device}"
504                         ;;
505
506                 *)
507                         log ERROR "Could not create a monitoring interface on ${device}"
508                         return ${EXIT_ERROR}
509                         ;;
510         esac
511
512         return ${EXIT_OK}
513 }
514
515 wireless_get_ht_caps() {
516         local device="${1}"
517         assert isset device
518
519         local phy="$(device_get_phy "${device}")"
520         if ! isset phy; then
521                 log ERROR "Could not determine PHY for ${device}"
522                 return ${EXIT_ERROR}
523         fi
524
525         network-phy-list-ht-caps "${phy}"
526 }
527
528 wireless_get_vht_caps() {
529         local device="${1}"
530         assert isset device
531
532         local phy="$(device_get_phy "${device}")"
533         if ! isset phy; then
534                 log ERROR "Could not determine PHY for ${device}"
535                 return ${EXIT_ERROR}
536         fi
537
538         network-phy-list-vht-caps "${phy}"
539 }
540
541 wireless_supports_acs() {
542         local device="${1}"
543         assert isset device
544
545         local phy="$(device_get_phy "${device}")"
546         if ! isset phy; then
547                 log ERROR "Could not determine PHY for ${device}"
548                 return ${EXIT_ERROR}
549         fi
550
551         phy_supports_acs "${phy}"
552 }
553
554 wireless_supports_dfs() {
555         local device="${1}"
556         assert isset device
557
558         local phy="$(device_get_phy "${device}")"
559         if ! isset phy; then
560                 log ERROR "Could not determine PHY for ${device}"
561                 return ${EXIT_ERROR}
562         fi
563
564         phy_supports_dfs "${phy}"
565 }
566
567 wireless_environment_is_valid() {
568         local environment="${1}"
569
570         list_match "${environment}" "${WIRELESS_ENVIRONMENTS[@]}"
571 }