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