xref: /illumos-gate/usr/src/cmd/cmd-inet/lib/nwamd/known_wlans.c (revision bc1f688b4872ace323eaddbb1a6365d054e7bf56)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24  */
25 
26 #include <ctype.h>
27 #include <errno.h>
28 #include <limits.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <libdladm.h>
33 #include <libdllink.h>
34 #include <libdlwlan.h>
35 #include <libgen.h>
36 #include <libnwam.h>
37 
38 #include "events.h"
39 #include "known_wlans.h"
40 #include "ncu.h"
41 #include "objects.h"
42 #include "util.h"
43 
44 /*
45  * known_wlans.c - contains routines which handle the known WLAN abstraction.
46  */
47 
48 #define	KNOWN_WIFI_NETS_FILE		"/etc/nwam/known_wifi_nets"
49 
50 /* enum for parsing each line of /etc/nwam/known_wifi_nets */
51 typedef enum {
52 	ESSID = 0,
53 	BSSID,
54 	MAX_FIELDS
55 } known_wifi_nets_fields_t;
56 
57 /* Structure for one BSSID */
58 typedef struct bssid {
59 	struct qelem	bssid_links;
60 	char		*bssid;
61 } bssid_t;
62 
63 /* Structure for an ESSID and its BSSIDs */
64 typedef struct kw {
65 	struct qelem	kw_links;
66 	char		kw_essid[NWAM_MAX_NAME_LEN];
67 	uint32_t	kw_num_bssids;
68 	struct qelem	kw_bssids;
69 } kw_t;
70 
71 /* Holds the linked-list of ESSIDs to make Known WLANs out of */
72 static struct qelem kw_list;
73 
74 /* Used in walking secobjs looking for an ESSID prefix match. */
75 struct nwamd_secobj_arg {
76 	char nsa_essid_prefix[DLADM_WLAN_MAX_KEYNAME_LEN];
77 	char nsa_keyname[DLADM_WLAN_MAX_KEYNAME_LEN];
78 	dladm_wlan_key_t *nsa_key;
79 	uint64_t nsa_secmode;
80 };
81 
82 static void
83 kw_list_init(void)
84 {
85 	kw_list.q_forw = kw_list.q_back = &kw_list;
86 }
87 
88 static void
89 kw_list_free(void)
90 {
91 	kw_t *kw;
92 	bssid_t *b;
93 
94 	while (kw_list.q_forw != &kw_list) {
95 		kw = (kw_t *)kw_list.q_forw;
96 
97 		/* free kw_bssids */
98 		while (kw->kw_bssids.q_forw != &kw->kw_bssids) {
99 			b = (bssid_t *)kw->kw_bssids.q_forw;
100 			remque(&b->bssid_links);
101 			free(b->bssid);
102 			free(b);
103 		}
104 		remque(&kw->kw_links);
105 		free(kw);
106 	}
107 }
108 
109 /* Returns the entry in kw_list for the given ESSID.  NULL if non-existent */
110 static kw_t *
111 kw_lookup(const char *essid)
112 {
113 	kw_t *kw;
114 
115 	if (essid == NULL)
116 		return (NULL);
117 
118 	for (kw = (kw_t *)kw_list.q_forw;
119 	    kw != (kw_t *)&kw_list;
120 	    kw = (kw_t *)kw->kw_links.q_forw) {
121 		if (strcmp(essid, kw->kw_essid) == 0)
122 			return (kw);
123 	}
124 	return (NULL);
125 }
126 
127 /* Adds an ESSID/BSSID combination to kw_list.  Returns B_TRUE on success. */
128 static boolean_t
129 kw_add(const char *essid, const char *bssid)
130 {
131 	kw_t *kw;
132 	bssid_t *b;
133 
134 	if ((b = calloc(1, sizeof (bssid_t))) == NULL) {
135 		nlog(LOG_ERR, "kw_add: cannot allocate for bssid_t: %m");
136 		return (B_FALSE);
137 	}
138 	if ((kw = calloc(1, sizeof (kw_t))) == NULL) {
139 		nlog(LOG_ERR, "kw_add: cannot allocate for kw_t: %m");
140 		free(b);
141 		return (B_FALSE);
142 	}
143 	kw->kw_bssids.q_forw = kw->kw_bssids.q_back = &kw->kw_bssids;
144 
145 	b->bssid = strdup(bssid);
146 	(void) strlcpy(kw->kw_essid, essid, sizeof (kw->kw_essid));
147 	kw->kw_num_bssids = 1;
148 
149 	insque(&b->bssid_links, kw->kw_bssids.q_back);
150 	insque(&kw->kw_links, kw_list.q_back);
151 
152 	nlog(LOG_DEBUG, "kw_add: added Known WLAN %s, BSSID %s", essid, bssid);
153 	return (B_TRUE);
154 }
155 
156 /*
157  * Add the BSSID to the given kw.  Since /etc/nwam/known_wifi_nets is
158  * populated such that the wifi networks visited later are towards the end
159  * of the file, remove the give kw from its current position and append it
160  * to the end of kw_list.  This ensures that kw_list is in the reverse
161  * order of visited wifi networks.  Returns B_TRUE on success.
162  */
163 static boolean_t
164 kw_update(kw_t *kw, const char *bssid)
165 {
166 	bssid_t *b;
167 
168 	if ((b = calloc(1, sizeof (bssid_t))) == NULL) {
169 		nlog(LOG_ERR, "kw_update: cannot allocate for bssid_t: %m");
170 		return (B_FALSE);
171 	}
172 
173 	b->bssid = strdup(bssid);
174 	insque(&b->bssid_links, kw->kw_bssids.q_back);
175 	kw->kw_num_bssids++;
176 
177 	/* remove kw from current position */
178 	remque(&kw->kw_links);
179 	/* and insert at end */
180 	insque(&kw->kw_links, kw_list.q_back);
181 
182 	nlog(LOG_DEBUG, "kw_update: appended BSSID %s to Known WLAN %s",
183 	    bssid, kw->kw_essid);
184 	return (B_TRUE);
185 }
186 
187 /*
188  * Parses /etc/nwam/known_wifi_nets and populates kw_list, with the oldest
189  * wifi networks first in the list.  Returns the number of unique entries
190  * in kw_list (to use for priority values).
191  */
192 static int
193 parse_known_wifi_nets(void)
194 {
195 	FILE *fp;
196 	char line[LINE_MAX];
197 	char *cp, *tok[MAX_FIELDS];
198 	int lnum, num_kw = 0;
199 	kw_t *kw;
200 
201 	kw_list_init();
202 
203 	/*
204 	 * The file format is:
205 	 * essid\tbssid (essid followed by tab followed by bssid)
206 	 */
207 	fp = fopen(KNOWN_WIFI_NETS_FILE, "r");
208 	if (fp == NULL)
209 		return (0);
210 	for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) {
211 
212 		cp = line;
213 		while (isspace(*cp))
214 			cp++;
215 		if (*cp == '#' || *cp == '\0')
216 			continue;
217 
218 		if (bufsplit(cp, MAX_FIELDS, tok) != MAX_FIELDS) {
219 			syslog(LOG_ERR, "%s:%d: wrong number of tokens; "
220 			    "ignoring entry", KNOWN_WIFI_NETS_FILE, lnum);
221 			continue;
222 		}
223 
224 		if ((kw = kw_lookup(tok[ESSID])) == NULL) {
225 			if (!kw_add(tok[ESSID], tok[BSSID])) {
226 				nlog(LOG_ERR,
227 				    "%s:%d: cannot add entry (%s,%s) to list",
228 				    KNOWN_WIFI_NETS_FILE, lnum,
229 				    tok[ESSID], tok[BSSID]);
230 			} else {
231 				num_kw++;
232 			}
233 		} else {
234 			if (!kw_update(kw, tok[BSSID])) {
235 				nlog(LOG_ERR,
236 				    "%s:%d:cannot update entry (%s,%s) to list",
237 				    KNOWN_WIFI_NETS_FILE, lnum,
238 				    tok[ESSID], tok[BSSID]);
239 			}
240 		}
241 		/* next line ... */
242 	}
243 
244 	(void) fclose(fp);
245 	return (num_kw);
246 }
247 
248 /*
249  * Walk security objects looking for one that matches the essid prefix.
250  * Store the key and keyname if a match is found - we use the last match
251  * as the key for the known WLAN, since it is the most recently updated.
252  */
253 /* ARGSUSED0 */
254 static boolean_t
255 find_secobj_matching_prefix(dladm_handle_t dh, void *arg,
256     const char *secobjname)
257 {
258 	struct nwamd_secobj_arg *nsa = arg;
259 
260 	if (strncmp(nsa->nsa_essid_prefix, secobjname,
261 	    strlen(nsa->nsa_essid_prefix)) == 0) {
262 		nlog(LOG_DEBUG, "find_secobj_matching_prefix: "
263 		    "found secobj with prefix %s : %s\n",
264 		    nsa->nsa_essid_prefix, secobjname);
265 		/* Free last key found (if any) */
266 		if (nsa->nsa_key != NULL)
267 			free(nsa->nsa_key);
268 		/* Retrive key so we can get security mode */
269 		nsa->nsa_key = nwamd_wlan_get_key_named(secobjname, 0);
270 		(void) strlcpy(nsa->nsa_keyname, secobjname,
271 		    sizeof (nsa->nsa_keyname));
272 		switch (nsa->nsa_key->wk_class) {
273 		case DLADM_SECOBJ_CLASS_WEP:
274 			nsa->nsa_secmode = DLADM_WLAN_SECMODE_WEP;
275 			nlog(LOG_DEBUG, "find_secobj_matching_prefix: "
276 			    "got WEP key %s", nsa->nsa_keyname);
277 			break;
278 		case DLADM_SECOBJ_CLASS_WPA:
279 			nsa->nsa_secmode = DLADM_WLAN_SECMODE_WPA;
280 			nlog(LOG_DEBUG, "find_secobj_matching_prefix: "
281 			    "got WPA key %s", nsa->nsa_keyname);
282 			break;
283 		default:
284 			/* shouldn't happen */
285 			nsa->nsa_secmode = DLADM_WLAN_SECMODE_NONE;
286 			nlog(LOG_ERR, "find_secobj_matching_prefix: "
287 			    "key class for key %s was invalid",
288 			    nsa->nsa_keyname);
289 			break;
290 		}
291 	}
292 	return (B_TRUE);
293 }
294 
295 
296 /* Upgrade /etc/nwam/known_wifi_nets file to new libnwam-based config model */
297 void
298 upgrade_known_wifi_nets_config(void)
299 {
300 	kw_t *kw;
301 	bssid_t *b;
302 	nwam_known_wlan_handle_t kwh;
303 	char **bssids;
304 	nwam_error_t err;
305 	uint64_t priority;
306 	int i, num_kw;
307 	struct nwamd_secobj_arg nsa;
308 
309 	nlog(LOG_INFO, "Upgrading %s to Known WLANs", KNOWN_WIFI_NETS_FILE);
310 
311 	/* Parse /etc/nwam/known_wifi_nets */
312 	num_kw = parse_known_wifi_nets();
313 
314 	/* Create Known WLANs for each unique ESSID */
315 	for (kw = (kw_t *)kw_list.q_forw, priority = num_kw-1;
316 	    kw != (kw_t *)&kw_list;
317 	    kw = (kw_t *)kw->kw_links.q_forw, priority--) {
318 		nwam_value_t priorityval = NULL;
319 		nwam_value_t bssidsval = NULL;
320 		nwam_value_t secmodeval = NULL;
321 		nwam_value_t keynameval = NULL;
322 
323 		nlog(LOG_DEBUG, "Creating Known WLAN %s", kw->kw_essid);
324 
325 		if ((err = nwam_known_wlan_create(kw->kw_essid, &kwh))
326 		    != NWAM_SUCCESS) {
327 			nlog(LOG_ERR, "upgrade wlan %s: "
328 			    "could not create known wlan: %s", kw->kw_essid,
329 			    nwam_strerror(err));
330 			continue;
331 		}
332 
333 		/* priority of this ESSID */
334 		if ((err = nwam_value_create_uint64(priority, &priorityval))
335 		    != NWAM_SUCCESS) {
336 			nlog(LOG_ERR, "upgrade wlan %s: "
337 			    "could not create priority value: %s", kw->kw_essid,
338 			    nwam_strerror(err));
339 			nwam_known_wlan_free(kwh);
340 			continue;
341 		}
342 		err = nwam_known_wlan_set_prop_value(kwh,
343 		    NWAM_KNOWN_WLAN_PROP_PRIORITY, priorityval);
344 		nwam_value_free(priorityval);
345 		if (err != NWAM_SUCCESS) {
346 			nlog(LOG_ERR, "upgrade wlan %s: "
347 			    "could not set priority value: %s", kw->kw_essid,
348 			    nwam_strerror(err));
349 			nwam_known_wlan_free(kwh);
350 			continue;
351 		}
352 
353 		/* loop through kw->kw_bssids and create an array of bssids */
354 		bssids = calloc(kw->kw_num_bssids, sizeof (char *));
355 		if (bssids == NULL) {
356 			nwam_known_wlan_free(kwh);
357 			nlog(LOG_ERR, "upgrade wlan %s: "
358 			    "could not calloc for bssids: %m", kw->kw_essid);
359 			continue;
360 		}
361 		for (b = (bssid_t *)kw->kw_bssids.q_forw, i = 0;
362 		    b != (bssid_t *)&kw->kw_bssids;
363 		    b = (bssid_t *)b->bssid_links.q_forw, i++) {
364 			bssids[i] = strdup(b->bssid);
365 		}
366 
367 		/* BSSIDs for this ESSID */
368 		if ((err = nwam_value_create_string_array(bssids,
369 		    kw->kw_num_bssids, &bssidsval)) != NWAM_SUCCESS) {
370 			nlog(LOG_ERR, "upgrade wlan %s: "
371 			    "could not create bssids value: %s", kw->kw_essid,
372 			    nwam_strerror(err));
373 			for (i = 0; i < kw->kw_num_bssids; i++)
374 				free(bssids[i]);
375 			free(bssids);
376 			nwam_known_wlan_free(kwh);
377 			continue;
378 		}
379 		err = nwam_known_wlan_set_prop_value(kwh,
380 		    NWAM_KNOWN_WLAN_PROP_BSSIDS, bssidsval);
381 		nwam_value_free(bssidsval);
382 		for (i = 0; i < kw->kw_num_bssids; i++)
383 			free(bssids[i]);
384 		free(bssids);
385 		if (err != NWAM_SUCCESS) {
386 			nlog(LOG_ERR, "upgrade wlan %s: "
387 			    "could not set bssids: %s", kw->kw_essid,
388 			    nwam_strerror(err));
389 			nwam_known_wlan_free(kwh);
390 			continue;
391 		}
392 
393 		/*
394 		 * Retrieve last key matching ESSID prefix if any, and set
395 		 * the retrieved key name and security mode.
396 		 */
397 		nwamd_set_key_name(kw->kw_essid, NULL, nsa.nsa_essid_prefix,
398 		    sizeof (nsa.nsa_essid_prefix));
399 		nsa.nsa_key = NULL;
400 		nsa.nsa_secmode = DLADM_WLAN_SECMODE_NONE;
401 		(void) dladm_walk_secobj(dld_handle, &nsa,
402 		    find_secobj_matching_prefix, DLADM_OPT_PERSIST);
403 		if (nsa.nsa_key != NULL) {
404 			if ((err = nwam_value_create_string(nsa.nsa_keyname,
405 			    &keynameval)) == NWAM_SUCCESS) {
406 				(void) nwam_known_wlan_set_prop_value(kwh,
407 				    NWAM_KNOWN_WLAN_PROP_KEYNAME, keynameval);
408 			}
409 			free(nsa.nsa_key);
410 			nwam_value_free(keynameval);
411 		}
412 
413 		if ((err = nwam_value_create_uint64(nsa.nsa_secmode,
414 		    &secmodeval)) != NWAM_SUCCESS ||
415 		    (err = nwam_known_wlan_set_prop_value(kwh,
416 		    NWAM_KNOWN_WLAN_PROP_SECURITY_MODE, secmodeval))
417 		    != NWAM_SUCCESS) {
418 			nlog(LOG_ERR, "upgrade wlan %s: "
419 			    "could not set security mode: %s",
420 			    kw->kw_essid, nwam_strerror(err));
421 			nwam_value_free(secmodeval);
422 			nwam_known_wlan_free(kwh);
423 			continue;
424 		}
425 
426 		/* commit, no collision checking by libnwam */
427 		err = nwam_known_wlan_commit(kwh,
428 		    NWAM_FLAG_KNOWN_WLAN_NO_COLLISION_CHECK);
429 		nwam_known_wlan_free(kwh);
430 		if (err != NWAM_SUCCESS) {
431 			nlog(LOG_ERR, "upgrade wlan %s: "
432 			    "could not commit wlan: %s", kw->kw_essid,
433 			    nwam_strerror(err));
434 		}
435 		/* next ... */
436 	}
437 
438 	kw_list_free();
439 }
440 
441 nwam_error_t
442 known_wlan_get_keyname(const char *essid, char *name)
443 {
444 	nwam_known_wlan_handle_t kwh = NULL;
445 	nwam_value_t keynameval = NULL;
446 	char *keyname;
447 	nwam_error_t err;
448 
449 	if ((err = nwam_known_wlan_read(essid, 0, &kwh)) != NWAM_SUCCESS)
450 		return (err);
451 	if ((err = nwam_known_wlan_get_prop_value(kwh,
452 	    NWAM_KNOWN_WLAN_PROP_KEYNAME, &keynameval)) == NWAM_SUCCESS &&
453 	    (err = nwam_value_get_string(keynameval, &keyname))
454 	    == NWAM_SUCCESS) {
455 		(void) strlcpy(name, keyname, NWAM_MAX_VALUE_LEN);
456 	}
457 	if (keynameval != NULL)
458 		nwam_value_free(keynameval);
459 
460 	if (kwh != NULL)
461 		nwam_known_wlan_free(kwh);
462 
463 	return (err);
464 }
465 
466 /* Performs a scan on a wifi link NCU */
467 /* ARGSUSED */
468 static int
469 nwamd_ncu_known_wlan_committed(nwamd_object_t object, void *data)
470 {
471 	nwamd_ncu_t *ncu_data = object->nwamd_object_data;
472 
473 	if (ncu_data->ncu_type != NWAM_NCU_TYPE_LINK)
474 		return (0);
475 
476 	/* network selection will be done only if possible */
477 	if (ncu_data->ncu_link.nwamd_link_media == DL_WIFI)
478 		(void) nwamd_wlan_scan(ncu_data->ncu_name);
479 	return (0);
480 }
481 
482 /* Handle known WLAN initialization/refresh event */
483 /* ARGSUSED */
484 void
485 nwamd_known_wlan_handle_init_event(nwamd_event_t known_wlan_event)
486 {
487 	/*
488 	 * Since the Known WLAN list has changed, do a rescan so that the
489 	 * best network is selected.
490 	 */
491 	(void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU,
492 	    nwamd_ncu_known_wlan_committed, NULL);
493 }
494 
495 void
496 nwamd_known_wlan_handle_action_event(nwamd_event_t known_wlan_event)
497 {
498 	switch (known_wlan_event->event_msg->nwe_data.nwe_object_action.
499 	    nwe_action) {
500 	case NWAM_ACTION_ADD:
501 	case NWAM_ACTION_REFRESH:
502 		nwamd_known_wlan_handle_init_event(known_wlan_event);
503 		break;
504 	case NWAM_ACTION_DESTROY:
505 		/* Nothing needs to be done for destroy */
506 		break;
507 	/* all other events are invalid for known WLANs */
508 	case NWAM_ACTION_ENABLE:
509 	case NWAM_ACTION_DISABLE:
510 	default:
511 		nlog(LOG_INFO, "nwam_known_wlan_handle_action_event: "
512 		    "unexpected action");
513 		break;
514 	}
515 }
516 
517 int
518 nwamd_known_wlan_action(const char *known_wlan, nwam_action_t action)
519 {
520 	nwamd_event_t known_wlan_event = nwamd_event_init_object_action
521 	    (NWAM_OBJECT_TYPE_KNOWN_WLAN, known_wlan, NULL, action);
522 	if (known_wlan_event == NULL)
523 		return (1);
524 	nwamd_event_enqueue(known_wlan_event);
525 	return (0);
526 }
527