1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /****************************************************************************** |
3 | |
4 | Copyright(c) 2004-2005 Intel Corporation. All rights reserved. |
5 | |
6 | Portions of this file are based on the WEP enablement code provided by the |
7 | Host AP project hostap-drivers v0.1.3 |
8 | Copyright (c) 2001-2002, SSH Communications Security Corp and Jouni Malinen |
9 | <j@w1.fi> |
10 | Copyright (c) 2002-2003, Jouni Malinen <j@w1.fi> |
11 | |
12 | |
13 | Contact Information: |
14 | Intel Linux Wireless <ilw@linux.intel.com> |
15 | Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497 |
16 | |
17 | ******************************************************************************/ |
18 | |
19 | #include <linux/hardirq.h> |
20 | #include <linux/kmod.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/module.h> |
23 | #include <linux/jiffies.h> |
24 | |
25 | #include <net/lib80211.h> |
26 | #include <linux/wireless.h> |
27 | |
28 | #include "libipw.h" |
29 | |
30 | static const char *libipw_modes[] = { |
31 | "?" , "a" , "b" , "ab" , "g" , "ag" , "bg" , "abg" |
32 | }; |
33 | |
34 | static inline unsigned int elapsed_jiffies_msecs(unsigned long start) |
35 | { |
36 | unsigned long end = jiffies; |
37 | |
38 | if (end >= start) |
39 | return jiffies_to_msecs(j: end - start); |
40 | |
41 | return jiffies_to_msecs(j: end + (MAX_JIFFY_OFFSET - start) + 1); |
42 | } |
43 | |
44 | #define MAX_CUSTOM_LEN 64 |
45 | static char *libipw_translate_scan(struct libipw_device *ieee, |
46 | char *start, char *stop, |
47 | struct libipw_network *network, |
48 | struct iw_request_info *info) |
49 | { |
50 | char custom[MAX_CUSTOM_LEN]; |
51 | char *p; |
52 | struct iw_event iwe; |
53 | int i, j; |
54 | char *current_val; /* For rates */ |
55 | u8 rate; |
56 | |
57 | /* First entry *MUST* be the AP MAC address */ |
58 | iwe.cmd = SIOCGIWAP; |
59 | iwe.u.ap_addr.sa_family = ARPHRD_ETHER; |
60 | memcpy(iwe.u.ap_addr.sa_data, network->bssid, ETH_ALEN); |
61 | start = iwe_stream_add_event(info, stream: start, ends: stop, iwe: &iwe, IW_EV_ADDR_LEN); |
62 | |
63 | /* Remaining entries will be displayed in the order we provide them */ |
64 | |
65 | /* Add the ESSID */ |
66 | iwe.cmd = SIOCGIWESSID; |
67 | iwe.u.data.flags = 1; |
68 | iwe.u.data.length = min(network->ssid_len, (u8) 32); |
69 | start = iwe_stream_add_point(info, stream: start, ends: stop, |
70 | iwe: &iwe, extra: network->ssid); |
71 | |
72 | /* Add the protocol name */ |
73 | iwe.cmd = SIOCGIWNAME; |
74 | snprintf(buf: iwe.u.name, IFNAMSIZ, fmt: "IEEE 802.11%s" , |
75 | libipw_modes[network->mode]); |
76 | start = iwe_stream_add_event(info, stream: start, ends: stop, iwe: &iwe, IW_EV_CHAR_LEN); |
77 | |
78 | /* Add mode */ |
79 | iwe.cmd = SIOCGIWMODE; |
80 | if (network->capability & (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)) { |
81 | if (network->capability & WLAN_CAPABILITY_ESS) |
82 | iwe.u.mode = IW_MODE_MASTER; |
83 | else |
84 | iwe.u.mode = IW_MODE_ADHOC; |
85 | |
86 | start = iwe_stream_add_event(info, stream: start, ends: stop, |
87 | iwe: &iwe, IW_EV_UINT_LEN); |
88 | } |
89 | |
90 | /* Add channel and frequency */ |
91 | /* Note : userspace automatically computes channel using iwrange */ |
92 | iwe.cmd = SIOCGIWFREQ; |
93 | iwe.u.freq.m = libipw_channel_to_freq(ieee, channel: network->channel); |
94 | iwe.u.freq.e = 6; |
95 | iwe.u.freq.i = 0; |
96 | start = iwe_stream_add_event(info, stream: start, ends: stop, iwe: &iwe, IW_EV_FREQ_LEN); |
97 | |
98 | /* Add encryption capability */ |
99 | iwe.cmd = SIOCGIWENCODE; |
100 | if (network->capability & WLAN_CAPABILITY_PRIVACY) |
101 | iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; |
102 | else |
103 | iwe.u.data.flags = IW_ENCODE_DISABLED; |
104 | iwe.u.data.length = 0; |
105 | start = iwe_stream_add_point(info, stream: start, ends: stop, |
106 | iwe: &iwe, extra: network->ssid); |
107 | |
108 | /* Add basic and extended rates */ |
109 | /* Rate : stuffing multiple values in a single event require a bit |
110 | * more of magic - Jean II */ |
111 | current_val = start + iwe_stream_lcp_len(info); |
112 | iwe.cmd = SIOCGIWRATE; |
113 | /* Those two flags are ignored... */ |
114 | iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; |
115 | |
116 | for (i = 0, j = 0; i < network->rates_len;) { |
117 | if (j < network->rates_ex_len && |
118 | ((network->rates_ex[j] & 0x7F) < |
119 | (network->rates[i] & 0x7F))) |
120 | rate = network->rates_ex[j++] & 0x7F; |
121 | else |
122 | rate = network->rates[i++] & 0x7F; |
123 | /* Bit rate given in 500 kb/s units (+ 0x80) */ |
124 | iwe.u.bitrate.value = ((rate & 0x7f) * 500000); |
125 | /* Add new value to event */ |
126 | current_val = iwe_stream_add_value(info, event: start, value: current_val, |
127 | ends: stop, iwe: &iwe, IW_EV_PARAM_LEN); |
128 | } |
129 | for (; j < network->rates_ex_len; j++) { |
130 | rate = network->rates_ex[j] & 0x7F; |
131 | /* Bit rate given in 500 kb/s units (+ 0x80) */ |
132 | iwe.u.bitrate.value = ((rate & 0x7f) * 500000); |
133 | /* Add new value to event */ |
134 | current_val = iwe_stream_add_value(info, event: start, value: current_val, |
135 | ends: stop, iwe: &iwe, IW_EV_PARAM_LEN); |
136 | } |
137 | /* Check if we added any rate */ |
138 | if ((current_val - start) > iwe_stream_lcp_len(info)) |
139 | start = current_val; |
140 | |
141 | /* Add quality statistics */ |
142 | iwe.cmd = IWEVQUAL; |
143 | iwe.u.qual.updated = IW_QUAL_QUAL_UPDATED | IW_QUAL_LEVEL_UPDATED | |
144 | IW_QUAL_NOISE_UPDATED; |
145 | |
146 | if (!(network->stats.mask & LIBIPW_STATMASK_RSSI)) { |
147 | iwe.u.qual.updated |= IW_QUAL_QUAL_INVALID | |
148 | IW_QUAL_LEVEL_INVALID; |
149 | iwe.u.qual.qual = 0; |
150 | } else { |
151 | if (ieee->perfect_rssi == ieee->worst_rssi) |
152 | iwe.u.qual.qual = 100; |
153 | else |
154 | iwe.u.qual.qual = |
155 | (100 * |
156 | (ieee->perfect_rssi - ieee->worst_rssi) * |
157 | (ieee->perfect_rssi - ieee->worst_rssi) - |
158 | (ieee->perfect_rssi - network->stats.rssi) * |
159 | (15 * (ieee->perfect_rssi - ieee->worst_rssi) + |
160 | 62 * (ieee->perfect_rssi - |
161 | network->stats.rssi))) / |
162 | ((ieee->perfect_rssi - |
163 | ieee->worst_rssi) * (ieee->perfect_rssi - |
164 | ieee->worst_rssi)); |
165 | if (iwe.u.qual.qual > 100) |
166 | iwe.u.qual.qual = 100; |
167 | else if (iwe.u.qual.qual < 1) |
168 | iwe.u.qual.qual = 0; |
169 | } |
170 | |
171 | if (!(network->stats.mask & LIBIPW_STATMASK_NOISE)) { |
172 | iwe.u.qual.updated |= IW_QUAL_NOISE_INVALID; |
173 | iwe.u.qual.noise = 0; |
174 | } else { |
175 | iwe.u.qual.noise = network->stats.noise; |
176 | } |
177 | |
178 | if (!(network->stats.mask & LIBIPW_STATMASK_SIGNAL)) { |
179 | iwe.u.qual.updated |= IW_QUAL_LEVEL_INVALID; |
180 | iwe.u.qual.level = 0; |
181 | } else { |
182 | iwe.u.qual.level = network->stats.signal; |
183 | } |
184 | |
185 | start = iwe_stream_add_event(info, stream: start, ends: stop, iwe: &iwe, IW_EV_QUAL_LEN); |
186 | |
187 | iwe.cmd = IWEVCUSTOM; |
188 | p = custom; |
189 | |
190 | iwe.u.data.length = p - custom; |
191 | if (iwe.u.data.length) |
192 | start = iwe_stream_add_point(info, stream: start, ends: stop, iwe: &iwe, extra: custom); |
193 | |
194 | memset(&iwe, 0, sizeof(iwe)); |
195 | if (network->wpa_ie_len) { |
196 | char buf[MAX_WPA_IE_LEN]; |
197 | memcpy(buf, network->wpa_ie, network->wpa_ie_len); |
198 | iwe.cmd = IWEVGENIE; |
199 | iwe.u.data.length = network->wpa_ie_len; |
200 | start = iwe_stream_add_point(info, stream: start, ends: stop, iwe: &iwe, extra: buf); |
201 | } |
202 | |
203 | memset(&iwe, 0, sizeof(iwe)); |
204 | if (network->rsn_ie_len) { |
205 | char buf[MAX_WPA_IE_LEN]; |
206 | memcpy(buf, network->rsn_ie, network->rsn_ie_len); |
207 | iwe.cmd = IWEVGENIE; |
208 | iwe.u.data.length = network->rsn_ie_len; |
209 | start = iwe_stream_add_point(info, stream: start, ends: stop, iwe: &iwe, extra: buf); |
210 | } |
211 | |
212 | /* Add EXTRA: Age to display seconds since last beacon/probe response |
213 | * for given network. */ |
214 | iwe.cmd = IWEVCUSTOM; |
215 | p = custom; |
216 | p += scnprintf(buf: p, MAX_CUSTOM_LEN - (p - custom), |
217 | fmt: " Last beacon: %ums ago" , |
218 | elapsed_jiffies_msecs(start: network->last_scanned)); |
219 | iwe.u.data.length = p - custom; |
220 | if (iwe.u.data.length) |
221 | start = iwe_stream_add_point(info, stream: start, ends: stop, iwe: &iwe, extra: custom); |
222 | |
223 | /* Add spectrum management information */ |
224 | iwe.cmd = -1; |
225 | p = custom; |
226 | p += scnprintf(buf: p, MAX_CUSTOM_LEN - (p - custom), fmt: " Channel flags: " ); |
227 | |
228 | if (libipw_get_channel_flags(ieee, channel: network->channel) & |
229 | LIBIPW_CH_INVALID) { |
230 | iwe.cmd = IWEVCUSTOM; |
231 | p += scnprintf(buf: p, MAX_CUSTOM_LEN - (p - custom), fmt: "INVALID " ); |
232 | } |
233 | |
234 | if (libipw_get_channel_flags(ieee, channel: network->channel) & |
235 | LIBIPW_CH_RADAR_DETECT) { |
236 | iwe.cmd = IWEVCUSTOM; |
237 | p += scnprintf(buf: p, MAX_CUSTOM_LEN - (p - custom), fmt: "DFS " ); |
238 | } |
239 | |
240 | if (iwe.cmd == IWEVCUSTOM) { |
241 | iwe.u.data.length = p - custom; |
242 | start = iwe_stream_add_point(info, stream: start, ends: stop, iwe: &iwe, extra: custom); |
243 | } |
244 | |
245 | return start; |
246 | } |
247 | |
248 | #define SCAN_ITEM_SIZE 128 |
249 | |
250 | int libipw_wx_get_scan(struct libipw_device *ieee, |
251 | struct iw_request_info *info, |
252 | union iwreq_data *wrqu, char *) |
253 | { |
254 | struct libipw_network *network; |
255 | unsigned long flags; |
256 | int err = 0; |
257 | |
258 | char *ev = extra; |
259 | char *stop = ev + wrqu->data.length; |
260 | int i = 0; |
261 | |
262 | LIBIPW_DEBUG_WX("Getting scan\n" ); |
263 | |
264 | spin_lock_irqsave(&ieee->lock, flags); |
265 | |
266 | list_for_each_entry(network, &ieee->network_list, list) { |
267 | i++; |
268 | if (stop - ev < SCAN_ITEM_SIZE) { |
269 | err = -E2BIG; |
270 | break; |
271 | } |
272 | |
273 | if (ieee->scan_age == 0 || |
274 | time_after(network->last_scanned + ieee->scan_age, jiffies)) |
275 | ev = libipw_translate_scan(ieee, start: ev, stop, network, |
276 | info); |
277 | else { |
278 | LIBIPW_DEBUG_SCAN("Not showing network '%*pE (%pM)' due to age (%ums).\n" , |
279 | network->ssid_len, network->ssid, |
280 | network->bssid, |
281 | elapsed_jiffies_msecs( |
282 | network->last_scanned)); |
283 | } |
284 | } |
285 | |
286 | spin_unlock_irqrestore(lock: &ieee->lock, flags); |
287 | |
288 | wrqu->data.length = ev - extra; |
289 | wrqu->data.flags = 0; |
290 | |
291 | LIBIPW_DEBUG_WX("exit: %d networks returned.\n" , i); |
292 | |
293 | return err; |
294 | } |
295 | |
296 | int libipw_wx_set_encode(struct libipw_device *ieee, |
297 | struct iw_request_info *info, |
298 | union iwreq_data *wrqu, char *keybuf) |
299 | { |
300 | struct iw_point *erq = &(wrqu->encoding); |
301 | struct net_device *dev = ieee->dev; |
302 | struct libipw_security sec = { |
303 | .flags = 0 |
304 | }; |
305 | int i, key, key_provided, len; |
306 | struct lib80211_crypt_data **crypt; |
307 | int host_crypto = ieee->host_encrypt || ieee->host_decrypt; |
308 | |
309 | LIBIPW_DEBUG_WX("SET_ENCODE\n" ); |
310 | |
311 | key = erq->flags & IW_ENCODE_INDEX; |
312 | if (key) { |
313 | if (key > WEP_KEYS) |
314 | return -EINVAL; |
315 | key--; |
316 | key_provided = 1; |
317 | } else { |
318 | key_provided = 0; |
319 | key = ieee->crypt_info.tx_keyidx; |
320 | } |
321 | |
322 | LIBIPW_DEBUG_WX("Key: %d [%s]\n" , key, key_provided ? |
323 | "provided" : "default" ); |
324 | |
325 | crypt = &ieee->crypt_info.crypt[key]; |
326 | |
327 | if (erq->flags & IW_ENCODE_DISABLED) { |
328 | if (key_provided && *crypt) { |
329 | LIBIPW_DEBUG_WX("Disabling encryption on key %d.\n" , |
330 | key); |
331 | lib80211_crypt_delayed_deinit(info: &ieee->crypt_info, crypt); |
332 | } else |
333 | LIBIPW_DEBUG_WX("Disabling encryption.\n" ); |
334 | |
335 | /* Check all the keys to see if any are still configured, |
336 | * and if no key index was provided, de-init them all */ |
337 | for (i = 0; i < WEP_KEYS; i++) { |
338 | if (ieee->crypt_info.crypt[i] != NULL) { |
339 | if (key_provided) |
340 | break; |
341 | lib80211_crypt_delayed_deinit(info: &ieee->crypt_info, |
342 | crypt: &ieee->crypt_info.crypt[i]); |
343 | } |
344 | } |
345 | |
346 | if (i == WEP_KEYS) { |
347 | sec.enabled = 0; |
348 | sec.encrypt = 0; |
349 | sec.level = SEC_LEVEL_0; |
350 | sec.flags |= SEC_ENABLED | SEC_LEVEL | SEC_ENCRYPT; |
351 | } |
352 | |
353 | goto done; |
354 | } |
355 | |
356 | sec.enabled = 1; |
357 | sec.encrypt = 1; |
358 | sec.flags |= SEC_ENABLED | SEC_ENCRYPT; |
359 | |
360 | if (*crypt != NULL && (*crypt)->ops != NULL && |
361 | strcmp((*crypt)->ops->name, "WEP" ) != 0) { |
362 | /* changing to use WEP; deinit previously used algorithm |
363 | * on this key */ |
364 | lib80211_crypt_delayed_deinit(info: &ieee->crypt_info, crypt); |
365 | } |
366 | |
367 | if (*crypt == NULL && host_crypto) { |
368 | struct lib80211_crypt_data *new_crypt; |
369 | |
370 | /* take WEP into use */ |
371 | new_crypt = kzalloc(size: sizeof(struct lib80211_crypt_data), |
372 | GFP_KERNEL); |
373 | if (new_crypt == NULL) |
374 | return -ENOMEM; |
375 | new_crypt->ops = lib80211_get_crypto_ops(name: "WEP" ); |
376 | if (!new_crypt->ops) { |
377 | request_module("lib80211_crypt_wep" ); |
378 | new_crypt->ops = lib80211_get_crypto_ops(name: "WEP" ); |
379 | } |
380 | |
381 | if (new_crypt->ops && try_module_get(module: new_crypt->ops->owner)) |
382 | new_crypt->priv = new_crypt->ops->init(key); |
383 | |
384 | if (!new_crypt->ops || !new_crypt->priv) { |
385 | kfree(objp: new_crypt); |
386 | new_crypt = NULL; |
387 | |
388 | printk(KERN_WARNING "%s: could not initialize WEP: " |
389 | "load module lib80211_crypt_wep\n" , dev->name); |
390 | return -EOPNOTSUPP; |
391 | } |
392 | *crypt = new_crypt; |
393 | } |
394 | |
395 | /* If a new key was provided, set it up */ |
396 | if (erq->length > 0) { |
397 | len = erq->length <= 5 ? 5 : 13; |
398 | memcpy(sec.keys[key], keybuf, erq->length); |
399 | if (len > erq->length) |
400 | memset(sec.keys[key] + erq->length, 0, |
401 | len - erq->length); |
402 | LIBIPW_DEBUG_WX("Setting key %d to '%*pE' (%d:%d bytes)\n" , |
403 | key, len, sec.keys[key], |
404 | erq->length, len); |
405 | sec.key_sizes[key] = len; |
406 | if (*crypt) |
407 | (*crypt)->ops->set_key(sec.keys[key], len, NULL, |
408 | (*crypt)->priv); |
409 | sec.flags |= (1 << key); |
410 | /* This ensures a key will be activated if no key is |
411 | * explicitly set */ |
412 | if (key == sec.active_key) |
413 | sec.flags |= SEC_ACTIVE_KEY; |
414 | |
415 | } else { |
416 | if (host_crypto) { |
417 | len = (*crypt)->ops->get_key(sec.keys[key], WEP_KEY_LEN, |
418 | NULL, (*crypt)->priv); |
419 | if (len == 0) { |
420 | /* Set a default key of all 0 */ |
421 | LIBIPW_DEBUG_WX("Setting key %d to all " |
422 | "zero.\n" , key); |
423 | memset(sec.keys[key], 0, 13); |
424 | (*crypt)->ops->set_key(sec.keys[key], 13, NULL, |
425 | (*crypt)->priv); |
426 | sec.key_sizes[key] = 13; |
427 | sec.flags |= (1 << key); |
428 | } |
429 | } |
430 | /* No key data - just set the default TX key index */ |
431 | if (key_provided) { |
432 | LIBIPW_DEBUG_WX("Setting key %d to default Tx " |
433 | "key.\n" , key); |
434 | ieee->crypt_info.tx_keyidx = key; |
435 | sec.active_key = key; |
436 | sec.flags |= SEC_ACTIVE_KEY; |
437 | } |
438 | } |
439 | if (erq->flags & (IW_ENCODE_OPEN | IW_ENCODE_RESTRICTED)) { |
440 | ieee->open_wep = !(erq->flags & IW_ENCODE_RESTRICTED); |
441 | sec.auth_mode = ieee->open_wep ? WLAN_AUTH_OPEN : |
442 | WLAN_AUTH_SHARED_KEY; |
443 | sec.flags |= SEC_AUTH_MODE; |
444 | LIBIPW_DEBUG_WX("Auth: %s\n" , |
445 | sec.auth_mode == WLAN_AUTH_OPEN ? |
446 | "OPEN" : "SHARED KEY" ); |
447 | } |
448 | |
449 | /* For now we just support WEP, so only set that security level... |
450 | * TODO: When WPA is added this is one place that needs to change */ |
451 | sec.flags |= SEC_LEVEL; |
452 | sec.level = SEC_LEVEL_1; /* 40 and 104 bit WEP */ |
453 | sec.encode_alg[key] = SEC_ALG_WEP; |
454 | |
455 | done: |
456 | if (ieee->set_security) |
457 | ieee->set_security(dev, &sec); |
458 | |
459 | return 0; |
460 | } |
461 | |
462 | int libipw_wx_get_encode(struct libipw_device *ieee, |
463 | struct iw_request_info *info, |
464 | union iwreq_data *wrqu, char *keybuf) |
465 | { |
466 | struct iw_point *erq = &(wrqu->encoding); |
467 | int len, key; |
468 | struct libipw_security *sec = &ieee->sec; |
469 | |
470 | LIBIPW_DEBUG_WX("GET_ENCODE\n" ); |
471 | |
472 | key = erq->flags & IW_ENCODE_INDEX; |
473 | if (key) { |
474 | if (key > WEP_KEYS) |
475 | return -EINVAL; |
476 | key--; |
477 | } else |
478 | key = ieee->crypt_info.tx_keyidx; |
479 | |
480 | erq->flags = key + 1; |
481 | |
482 | if (!sec->enabled) { |
483 | erq->length = 0; |
484 | erq->flags |= IW_ENCODE_DISABLED; |
485 | return 0; |
486 | } |
487 | |
488 | len = sec->key_sizes[key]; |
489 | memcpy(keybuf, sec->keys[key], len); |
490 | |
491 | erq->length = len; |
492 | erq->flags |= IW_ENCODE_ENABLED; |
493 | |
494 | if (ieee->open_wep) |
495 | erq->flags |= IW_ENCODE_OPEN; |
496 | else |
497 | erq->flags |= IW_ENCODE_RESTRICTED; |
498 | |
499 | return 0; |
500 | } |
501 | |
502 | int libipw_wx_set_encodeext(struct libipw_device *ieee, |
503 | struct iw_request_info *info, |
504 | union iwreq_data *wrqu, char *) |
505 | { |
506 | struct net_device *dev = ieee->dev; |
507 | struct iw_point *encoding = &wrqu->encoding; |
508 | struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; |
509 | int i, idx, ret = 0; |
510 | int group_key = 0; |
511 | const char *alg, *module; |
512 | struct lib80211_crypto_ops *ops; |
513 | struct lib80211_crypt_data **crypt; |
514 | |
515 | struct libipw_security sec = { |
516 | .flags = 0, |
517 | }; |
518 | |
519 | idx = encoding->flags & IW_ENCODE_INDEX; |
520 | if (idx) { |
521 | if (idx < 1 || idx > WEP_KEYS) |
522 | return -EINVAL; |
523 | idx--; |
524 | } else |
525 | idx = ieee->crypt_info.tx_keyidx; |
526 | |
527 | if (ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) { |
528 | crypt = &ieee->crypt_info.crypt[idx]; |
529 | group_key = 1; |
530 | } else { |
531 | /* some Cisco APs use idx>0 for unicast in dynamic WEP */ |
532 | if (idx != 0 && ext->alg != IW_ENCODE_ALG_WEP) |
533 | return -EINVAL; |
534 | if (ieee->iw_mode == IW_MODE_INFRA) |
535 | crypt = &ieee->crypt_info.crypt[idx]; |
536 | else |
537 | return -EINVAL; |
538 | } |
539 | |
540 | sec.flags |= SEC_ENABLED | SEC_ENCRYPT; |
541 | if ((encoding->flags & IW_ENCODE_DISABLED) || |
542 | ext->alg == IW_ENCODE_ALG_NONE) { |
543 | if (*crypt) |
544 | lib80211_crypt_delayed_deinit(info: &ieee->crypt_info, crypt); |
545 | |
546 | for (i = 0; i < WEP_KEYS; i++) |
547 | if (ieee->crypt_info.crypt[i] != NULL) |
548 | break; |
549 | |
550 | if (i == WEP_KEYS) { |
551 | sec.enabled = 0; |
552 | sec.encrypt = 0; |
553 | sec.level = SEC_LEVEL_0; |
554 | sec.flags |= SEC_LEVEL; |
555 | } |
556 | goto done; |
557 | } |
558 | |
559 | sec.enabled = 1; |
560 | sec.encrypt = 1; |
561 | |
562 | if (group_key ? !ieee->host_mc_decrypt : |
563 | !(ieee->host_encrypt || ieee->host_decrypt || |
564 | ieee->host_encrypt_msdu)) |
565 | goto skip_host_crypt; |
566 | |
567 | switch (ext->alg) { |
568 | case IW_ENCODE_ALG_WEP: |
569 | alg = "WEP" ; |
570 | module = "lib80211_crypt_wep" ; |
571 | break; |
572 | case IW_ENCODE_ALG_TKIP: |
573 | alg = "TKIP" ; |
574 | module = "lib80211_crypt_tkip" ; |
575 | break; |
576 | case IW_ENCODE_ALG_CCMP: |
577 | alg = "CCMP" ; |
578 | module = "lib80211_crypt_ccmp" ; |
579 | break; |
580 | default: |
581 | LIBIPW_DEBUG_WX("%s: unknown crypto alg %d\n" , |
582 | dev->name, ext->alg); |
583 | ret = -EINVAL; |
584 | goto done; |
585 | } |
586 | |
587 | ops = lib80211_get_crypto_ops(name: alg); |
588 | if (ops == NULL) { |
589 | request_module(module); |
590 | ops = lib80211_get_crypto_ops(name: alg); |
591 | } |
592 | if (ops == NULL) { |
593 | LIBIPW_DEBUG_WX("%s: unknown crypto alg %d\n" , |
594 | dev->name, ext->alg); |
595 | ret = -EINVAL; |
596 | goto done; |
597 | } |
598 | |
599 | if (*crypt == NULL || (*crypt)->ops != ops) { |
600 | struct lib80211_crypt_data *new_crypt; |
601 | |
602 | lib80211_crypt_delayed_deinit(info: &ieee->crypt_info, crypt); |
603 | |
604 | new_crypt = kzalloc(size: sizeof(*new_crypt), GFP_KERNEL); |
605 | if (new_crypt == NULL) { |
606 | ret = -ENOMEM; |
607 | goto done; |
608 | } |
609 | new_crypt->ops = ops; |
610 | if (new_crypt->ops && try_module_get(module: new_crypt->ops->owner)) |
611 | new_crypt->priv = new_crypt->ops->init(idx); |
612 | if (new_crypt->priv == NULL) { |
613 | kfree(objp: new_crypt); |
614 | ret = -EINVAL; |
615 | goto done; |
616 | } |
617 | *crypt = new_crypt; |
618 | } |
619 | |
620 | if (ext->key_len > 0 && (*crypt)->ops->set_key && |
621 | (*crypt)->ops->set_key(ext->key, ext->key_len, ext->rx_seq, |
622 | (*crypt)->priv) < 0) { |
623 | LIBIPW_DEBUG_WX("%s: key setting failed\n" , dev->name); |
624 | ret = -EINVAL; |
625 | goto done; |
626 | } |
627 | |
628 | skip_host_crypt: |
629 | if (ext->ext_flags & IW_ENCODE_EXT_SET_TX_KEY) { |
630 | ieee->crypt_info.tx_keyidx = idx; |
631 | sec.active_key = idx; |
632 | sec.flags |= SEC_ACTIVE_KEY; |
633 | } |
634 | |
635 | if (ext->alg != IW_ENCODE_ALG_NONE) { |
636 | int key_len = clamp_val(ext->key_len, 0, SCM_KEY_LEN); |
637 | |
638 | memcpy(sec.keys[idx], ext->key, key_len); |
639 | sec.key_sizes[idx] = key_len; |
640 | sec.flags |= (1 << idx); |
641 | if (ext->alg == IW_ENCODE_ALG_WEP) { |
642 | sec.encode_alg[idx] = SEC_ALG_WEP; |
643 | sec.flags |= SEC_LEVEL; |
644 | sec.level = SEC_LEVEL_1; |
645 | } else if (ext->alg == IW_ENCODE_ALG_TKIP) { |
646 | sec.encode_alg[idx] = SEC_ALG_TKIP; |
647 | sec.flags |= SEC_LEVEL; |
648 | sec.level = SEC_LEVEL_2; |
649 | } else if (ext->alg == IW_ENCODE_ALG_CCMP) { |
650 | sec.encode_alg[idx] = SEC_ALG_CCMP; |
651 | sec.flags |= SEC_LEVEL; |
652 | sec.level = SEC_LEVEL_3; |
653 | } |
654 | /* Don't set sec level for group keys. */ |
655 | if (group_key) |
656 | sec.flags &= ~SEC_LEVEL; |
657 | } |
658 | done: |
659 | if (ieee->set_security) |
660 | ieee->set_security(dev, &sec); |
661 | |
662 | return ret; |
663 | } |
664 | |
665 | int libipw_wx_get_encodeext(struct libipw_device *ieee, |
666 | struct iw_request_info *info, |
667 | union iwreq_data *wrqu, char *) |
668 | { |
669 | struct iw_point *encoding = &wrqu->encoding; |
670 | struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; |
671 | struct libipw_security *sec = &ieee->sec; |
672 | int idx, max_key_len; |
673 | |
674 | max_key_len = encoding->length - sizeof(*ext); |
675 | if (max_key_len < 0) |
676 | return -EINVAL; |
677 | |
678 | idx = encoding->flags & IW_ENCODE_INDEX; |
679 | if (idx) { |
680 | if (idx < 1 || idx > WEP_KEYS) |
681 | return -EINVAL; |
682 | idx--; |
683 | } else |
684 | idx = ieee->crypt_info.tx_keyidx; |
685 | |
686 | if (!(ext->ext_flags & IW_ENCODE_EXT_GROUP_KEY) && |
687 | ext->alg != IW_ENCODE_ALG_WEP) |
688 | if (idx != 0 || ieee->iw_mode != IW_MODE_INFRA) |
689 | return -EINVAL; |
690 | |
691 | encoding->flags = idx + 1; |
692 | memset(ext, 0, sizeof(*ext)); |
693 | |
694 | if (!sec->enabled) { |
695 | ext->alg = IW_ENCODE_ALG_NONE; |
696 | ext->key_len = 0; |
697 | encoding->flags |= IW_ENCODE_DISABLED; |
698 | } else { |
699 | if (sec->encode_alg[idx] == SEC_ALG_WEP) |
700 | ext->alg = IW_ENCODE_ALG_WEP; |
701 | else if (sec->encode_alg[idx] == SEC_ALG_TKIP) |
702 | ext->alg = IW_ENCODE_ALG_TKIP; |
703 | else if (sec->encode_alg[idx] == SEC_ALG_CCMP) |
704 | ext->alg = IW_ENCODE_ALG_CCMP; |
705 | else |
706 | return -EINVAL; |
707 | |
708 | ext->key_len = sec->key_sizes[idx]; |
709 | memcpy(ext->key, sec->keys[idx], ext->key_len); |
710 | encoding->flags |= IW_ENCODE_ENABLED; |
711 | if (ext->key_len && |
712 | (ext->alg == IW_ENCODE_ALG_TKIP || |
713 | ext->alg == IW_ENCODE_ALG_CCMP)) |
714 | ext->ext_flags |= IW_ENCODE_EXT_TX_SEQ_VALID; |
715 | |
716 | } |
717 | |
718 | return 0; |
719 | } |
720 | |
721 | EXPORT_SYMBOL(libipw_wx_set_encodeext); |
722 | EXPORT_SYMBOL(libipw_wx_get_encodeext); |
723 | |
724 | EXPORT_SYMBOL(libipw_wx_get_scan); |
725 | EXPORT_SYMBOL(libipw_wx_set_encode); |
726 | EXPORT_SYMBOL(libipw_wx_get_encode); |
727 | |