Home | History | Annotate | Download | only in nwamd
      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 2009 Sun Microsystems, Inc.  All rights reserved.
     24  * Use is subject to license terms.
     25  */
     26 
     27 /*
     28  * This file contains all the routines to handle wireless (more
     29  * accurately, 802.11 "WiFi" family only at this moment) operations.
     30  * This is only phase 0 work so the handling is pretty simple.
     31  *
     32  * When the daemon starts up, for each WiFi interface detected, it'll
     33  * spawn a thread doing an access point (AP) scanning.  After the scans
     34  * finish and if one of the WiFi interfaces is chosen to be active, the
     35  * code will send a message to the GUI, which then must gather the results.
     36  *
     37  * WEP/WPA is supported to connect to those APs which require it.  The code
     38  * also maintains a list of known WiFi APs in the file KNOWN_WIFI_NETS.
     39  * Whenever the code successfully connects to an AP, the AP's ESSID/BSSID will
     40  * be added to that file.  This file is used in the following way.
     41  *
     42  * If the AP scan results contain one known AP (plus any number of unknown
     43  * APs), the code will automatically connect to that AP without contacting the
     44  * GUI.  But if the detected signal strength of that one known AP is weaker
     45  * than any of the unknown APs, the code will block on the GUI.
     46  *
     47  * If the AP scan results contain more than one known APs or no known APs, the
     48  * GUI is notified.
     49  *
     50  * Note that not all APs broadcast the Beacon.  And some events may
     51  * happen during the AP scan such that not all available APs are found.
     52  * Thus, the GUI can specify an AP's data.
     53  *
     54  * The code also periodically (specified by wlan_scan_interval) checks
     55  * for the health of the AP connection.  If the signal strength of the
     56  * connected AP drops below a threshold (specified by wireless_scan_level),
     57  * the code will try to do another scan to find out other APs available.
     58  * If there is currently no connected AP, a scan will also be done
     59  * periodically to look for available APs.  In both cases, if there are
     60  * new APs, the above AP connection procedure will be performed.
     61  *
     62  * As a way to deal with the innumerable bugs that seem to plague wireless
     63  * interfaces with respect to concurrent operations, we completely exclude all
     64  * connect operations on all interfaces when another connect or scan is
     65  * running, and exclude all scans on all interfaces when another connect or
     66  * scan is running.  This is done using wifi_scan_intf.
     67  *
     68  * Much of the BSSID handling logic in this module is questionable due to
     69  * underlying bugs such as CR 6772510.  There's likely little that we can do
     70  * about this.
     71  *
     72  * Lock ordering note: wifi_mutex and wifi_init_mutex are not held at the same
     73  * time.
     74  */
     75 
     76 #include <unistd.h>
     77 #include <ctype.h>
     78 #include <stdlib.h>
     79 #include <stdio.h>
     80 #include <strings.h>
     81 #include <syslog.h>
     82 #include <limits.h>
     83 #include <errno.h>
     84 #include <sys/stat.h>
     85 #include <syslog.h>
     86 #include <pthread.h>
     87 #include <sys/wait.h>
     88 #include <stropts.h>
     89 #include <sys/types.h>
     90 #include <fcntl.h>
     91 #include <libdladm.h>
     92 #include <libdllink.h>
     93 #include <libinetutil.h>
     94 #include <libgen.h>
     95 
     96 #include "defines.h"
     97 #include "structures.h"
     98 #include "functions.h"
     99 #include "variables.h"
    100 
    101 #define	WLAN_ENC(sec)						\
    102 	((sec == DLADM_WLAN_SECMODE_WPA ? "WPA" : 		\
    103 	(sec == DLADM_WLAN_SECMODE_WEP ? "WEP" : "none")))
    104 
    105 #define	NEED_ENC(sec)						\
    106 	(sec == DLADM_WLAN_SECMODE_WPA || sec == DLADM_WLAN_SECMODE_WEP)
    107 
    108 static pthread_mutex_t wifi_mutex;
    109 
    110 typedef enum {
    111 	ESSID = 0,
    112 	BSSID,
    113 	MAX_FIELDS
    114 } known_wifi_nets_fields_t;
    115 
    116 /*
    117  * List of wireless interfaces; protected by wifi_mutex.
    118  */
    119 static struct qelem wi_list;
    120 static uint_t wi_link_count;
    121 
    122 /*
    123  * Is a wireless interface doing a scan currently?  We only allow one
    124  * wireless interface to do a scan at any one time.  This is to
    125  * avoid unnecessary interference.  The following variable is used
    126  * to store the interface doing the scan.  It is protected by
    127  * wifi_init_mutex.
    128  */
    129 static const char *wifi_scan_intf;
    130 static boolean_t connect_running;
    131 static pthread_mutex_t wifi_init_mutex = PTHREAD_MUTEX_INITIALIZER;
    132 static pthread_cond_t wifi_init_cond = PTHREAD_COND_INITIALIZER;
    133 
    134 /*
    135  * Array of wireless LAN entries; protected by wifi_mutex.
    136  */
    137 static struct wireless_lan *wlans;
    138 static uint_t wireless_lan_count; /* allocated */
    139 static uint_t wireless_lan_used; /* used entries */
    140 static boolean_t new_ap_found;
    141 
    142 static int key_string_to_secobj_value(char *, uint8_t *, uint_t *,
    143     dladm_secobj_class_t);
    144 static int store_key(struct wireless_lan *);
    145 static dladm_wlan_key_t *retrieve_key(const char *, const char *,
    146     dladm_secobj_class_t);
    147 
    148 static struct wireless_lan *add_wlan_entry(const char *, const char *,
    149     const char *, dladm_wlan_attr_t *);
    150 static boolean_t check_wlan(const wireless_if_t *, const char *, const char *,
    151     boolean_t);
    152 static struct wireless_lan *find_wlan_entry(const char *, const char *,
    153     const char *);
    154 static void free_wireless_lan(struct wireless_lan *);
    155 static return_vals_t get_user_key(struct wireless_lan *);
    156 static boolean_t wlan_autoconf(const wireless_if_t *);
    157 static boolean_t get_scan_results(void *, dladm_wlan_attr_t *);
    158 static int add_known_wifi_nets_file(const char *, const char *);
    159 static boolean_t known_wifi_nets_lookup(const char *, const char *, char *);
    160 static return_vals_t connect_chosen_lan(struct wireless_lan *, wireless_if_t *);
    161 
    162 #define	WIRELESS_LAN_INIT_COUNT	8
    163 
    164 /*
    165  * The variable wlan_scan_interval controls the interval in seconds
    166  * between periodic scans.
    167  */
    168 uint_t wlan_scan_interval = 120;
    169 
    170 /*
    171  * The variable wireless_scan_level specifies the lowest signal level
    172  * when a periodic wireless scan needs to be done.
    173  */
    174 dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK;
    175 
    176 /*
    177  * This controls whether we are strict about matching BSSID in the known wifi
    178  * networks file.  By default, we're not strict.
    179  */
    180 boolean_t strict_bssid;
    181 
    182 void
    183 initialize_wireless(void)
    184 {
    185 	pthread_mutexattr_t wifi_mutex_attr;
    186 
    187 	(void) pthread_mutexattr_init(&wifi_mutex_attr);
    188 	(void) pthread_mutexattr_settype(&wifi_mutex_attr,
    189 	    PTHREAD_MUTEX_RECURSIVE);
    190 	(void) pthread_mutex_init(&wifi_mutex, &wifi_mutex_attr);
    191 	wi_list.q_forw = wi_list.q_back = &wi_list;
    192 }
    193 
    194 void
    195 add_wireless_if(const char *ifname)
    196 {
    197 	wireless_if_t *wip;
    198 
    199 	if ((wip = calloc(1, sizeof (*wip))) != NULL) {
    200 		(void) strlcpy(wip->wi_name, ifname, sizeof (wip->wi_name));
    201 		(void) dladm_name2info(dld_handle, ifname, &wip->wi_linkid,
    202 		    NULL, NULL, NULL);
    203 		if (pthread_mutex_lock(&wifi_mutex) == 0) {
    204 			insque(&wip->wi_links, wi_list.q_back);
    205 			wi_link_count++;
    206 			(void) pthread_mutex_unlock(&wifi_mutex);
    207 		} else {
    208 			free(wip);
    209 		}
    210 	}
    211 }
    212 
    213 static wireless_if_t *
    214 find_wireless_if(const char *ifname)
    215 {
    216 	wireless_if_t *wip;
    217 
    218 	for (wip = (wireless_if_t *)wi_list.q_forw;
    219 	    wip != (wireless_if_t *)&wi_list;
    220 	    wip = (wireless_if_t *)wip->wi_links.q_forw) {
    221 		if (strcmp(wip->wi_name, ifname) == 0)
    222 			return (wip);
    223 	}
    224 	return (NULL);
    225 }
    226 
    227 void
    228 remove_wireless_if(const char *ifname)
    229 {
    230 	wireless_if_t *wip;
    231 
    232 	if (pthread_mutex_lock(&wifi_mutex) == 0) {
    233 		if ((wip = find_wireless_if(ifname)) != NULL) {
    234 			remque(&wip->wi_links);
    235 			wi_link_count--;
    236 		}
    237 		(void) pthread_mutex_unlock(&wifi_mutex);
    238 		free(wip);
    239 	}
    240 }
    241 
    242 /*
    243  * wlan is expected to be non-NULL.
    244  */
    245 static return_vals_t
    246 get_user_key(struct wireless_lan *wlan)
    247 {
    248 	dladm_secobj_class_t class;
    249 
    250 	/*
    251 	 * First, test if we have key stored as secobj. If so,
    252 	 * no need to prompt for it.
    253 	 */
    254 	class = (wlan->attrs.wa_secmode == DLADM_WLAN_SECMODE_WEP ?
    255 	    DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA);
    256 	wlan->cooked_key = retrieve_key(wlan->essid, wlan->bssid, class);
    257 	if (wlan->cooked_key != NULL) {
    258 		dprintf("get_user_key: retrieve_key() returns non NULL");
    259 		return (SUCCESS);
    260 	} else if (request_wlan_key(wlan)) {
    261 		return (WAITING);
    262 	} else {
    263 		return (FAILURE);
    264 	}
    265 }
    266 
    267 /*
    268  * This function assumes that wifi_mutex is held.  If bssid is specified, then
    269  * an exact match is returned.  If it's not specified, then the best match is
    270  * returned.
    271  */
    272 static struct wireless_lan *
    273 find_wlan_entry(const char *ifname, const char *essid, const char *bssid)
    274 {
    275 	struct wireless_lan *wlan, *best;
    276 
    277 	best = NULL;
    278 	for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) {
    279 		if (strcmp(wlan->essid, essid) != 0 ||
    280 		    strcmp(wlan->wl_if_name, ifname) != 0)
    281 			continue;
    282 		if (bssid[0] == '\0') {
    283 			if (best == NULL ||
    284 			    wlan->attrs.wa_strength > best->attrs.wa_strength)
    285 				best = wlan;
    286 		} else {
    287 			if (strcmp(wlan->bssid, bssid) == 0)
    288 				return (wlan);
    289 		}
    290 	}
    291 	return (best);
    292 }
    293 
    294 static void
    295 free_wireless_lan(struct wireless_lan *wlp)
    296 {
    297 	free(wlp->essid);
    298 	wlp->essid = NULL;
    299 	/* empty string is not allocated */
    300 	if (wlp->bssid != NULL && wlp->bssid[0] != '\0')
    301 		free(wlp->bssid);
    302 	wlp->bssid = NULL;
    303 	free(wlp->signal_strength);
    304 	wlp->signal_strength = NULL;
    305 	free(wlp->raw_key);
    306 	wlp->raw_key = NULL;
    307 	free(wlp->cooked_key);
    308 	wlp->cooked_key = NULL;
    309 }
    310 
    311 /*
    312  * This function assumes that wifi_mutex is held.
    313  */
    314 static struct wireless_lan *
    315 add_wlan_entry(const char *ifname, const char *essid, const char *bssid,
    316     dladm_wlan_attr_t *attrp)
    317 {
    318 	char strength[DLADM_STRSIZE];
    319 	struct wireless_lan *wlan;
    320 
    321 	if (wireless_lan_used == wireless_lan_count) {
    322 		int newcnt;
    323 
    324 		newcnt = (wireless_lan_count == 0) ?
    325 		    WIRELESS_LAN_INIT_COUNT : wireless_lan_count * 2;
    326 		wlan = realloc(wlans, newcnt * sizeof (*wlans));
    327 		if (wlan == NULL) {
    328 			syslog(LOG_ERR, "add_wlan_entry: realloc failed");
    329 			return (NULL);
    330 		}
    331 		wireless_lan_count = newcnt;
    332 		wlans = wlan;
    333 	}
    334 
    335 	(void) dladm_wlan_strength2str(&attrp->wa_strength, strength);
    336 
    337 	wlan = wlans + wireless_lan_used;
    338 	(void) memset(wlan, 0, sizeof (*wlan));
    339 	wlan->attrs = *attrp;
    340 	wlan->essid = strdup(essid);
    341 	/* do not do allocation for zero-length */
    342 	wlan->bssid = *bssid == '\0' ? "" : strdup(bssid);
    343 	wlan->signal_strength = strdup(strength);
    344 	(void) strlcpy(wlan->wl_if_name, ifname, sizeof (wlan->wl_if_name));
    345 	wlan->scanned = B_TRUE;
    346 	if (wlan->essid == NULL || wlan->bssid == NULL ||
    347 	    wlan->signal_strength == NULL) {
    348 		syslog(LOG_ERR, "add_wlan_entry: strdup failed");
    349 		free_wireless_lan(wlan);
    350 		return (NULL);
    351 	}
    352 	wireless_lan_used++;
    353 	new_ap_found = B_TRUE;
    354 	return (wlan);
    355 }
    356 
    357 /*
    358  * Remove entries that are no longer seen on the network.  The caller does not
    359  * hold wifi_mutex, but is the only thread that can modify the wlan list.
    360  *
    361  * Retain connected entries, as lack of visibility in a scan may just be a
    362  * temporary condition (driver problem) and may not reflect an actual
    363  * disconnect, but only if there are no scanned connected entries.
    364  */
    365 static boolean_t
    366 clear_unscanned_entries(const char *ifname)
    367 {
    368 	struct wireless_lan *wlan, *wlput;
    369 	boolean_t has_unscanned_connected;
    370 	boolean_t copied_scanned_connected;
    371 	uint_t dropcnt;
    372 
    373 	if (pthread_mutex_lock(&wifi_mutex) != 0)
    374 		return (B_FALSE);
    375 	wlput = wlans;
    376 	dropcnt = 0;
    377 	has_unscanned_connected = copied_scanned_connected = B_FALSE;
    378 	for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) {
    379 		if (strcmp(ifname, wlan->wl_if_name) != 0) {
    380 			if (wlput != wlan)
    381 				*wlput = *wlan;
    382 			wlput++;
    383 		} else if (wlan->scanned) {
    384 			if (wlan->connected)
    385 				copied_scanned_connected = B_TRUE;
    386 			if (wlput != wlan)
    387 				*wlput = *wlan;
    388 			wlput++;
    389 		} else {
    390 			if (wlan->connected)
    391 				has_unscanned_connected = B_TRUE;
    392 			dprintf("dropping unseen AP %s %s", wlan->essid,
    393 			    wlan->bssid);
    394 			dropcnt++;
    395 			free_wireless_lan(wlan);
    396 		}
    397 	}
    398 	if (has_unscanned_connected && !copied_scanned_connected) {
    399 		for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) {
    400 			if (strcmp(ifname, wlan->wl_if_name) == 0 &&
    401 			    wlan->connected) {
    402 				dprintf("keeping unscanned but connected AP "
    403 				    "%s %s", wlan->essid, wlan->bssid);
    404 				if (wlput != wlan)
    405 					*wlput = *wlan;
    406 				wlput++;
    407 				dropcnt--;
    408 			}
    409 		}
    410 	}
    411 	wireless_lan_used = wlput - wlans;
    412 	(void) pthread_mutex_unlock(&wifi_mutex);
    413 	return (dropcnt != 0);
    414 }
    415 
    416 /*
    417  * Verify if a WiFi NIC is associated with the given ESSID and BSSID.  If the
    418  * given ESSID is NULL, and if the NIC is already connected, return true.
    419  * Otherwise,
    420  *
    421  * 1. If the NIC is associated with the given ESSID/BSSID, return true.
    422  * 2. If the NIC is not associated with any AP, return false.
    423  * 3. If the NIC is associated with a different AP, tear down IP interface,
    424  *    tell the driver to disassociate with AP, and then return false.
    425  */
    426 static boolean_t
    427 check_wlan(const wireless_if_t *wip, const char *exp_essid,
    428     const char *exp_bssid, boolean_t sendevent)
    429 {
    430 	dladm_wlan_linkattr_t attr;
    431 	dladm_status_t status;
    432 	char cur_essid[DLADM_STRSIZE];
    433 	char cur_bssid[DLADM_STRSIZE];
    434 	char errmsg[DLADM_STRSIZE];
    435 
    436 	status = dladm_wlan_get_linkattr(dld_handle, wip->wi_linkid, &attr);
    437 	if (status != DLADM_STATUS_OK) {
    438 		dprintf("check_wlan: dladm_wlan_get_linkattr() for %s "
    439 		    "failed: %s", wip->wi_name,
    440 		    dladm_status2str(status, errmsg));
    441 		return (B_FALSE);
    442 	}
    443 	if (attr.la_status == DLADM_WLAN_LINK_DISCONNECTED)
    444 		return (B_FALSE);
    445 
    446 	/* If we're expecting "any" connection, then we're done. */
    447 	if (exp_essid == NULL)
    448 		return (B_TRUE);
    449 
    450 	/* Is the NIC associated with the expected access point? */
    451 	(void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid);
    452 	if (strcmp(cur_essid, exp_essid) != 0) {
    453 		dprintf("wrong ESSID: have %s expect %s; taking down",
    454 		    cur_essid, exp_essid);
    455 		goto unexpected;
    456 	}
    457 
    458 	if (exp_bssid == NULL)
    459 		return (B_TRUE);
    460 
    461 	(void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, cur_bssid);
    462 	if (strcmp(cur_bssid, exp_bssid) == 0)
    463 		return (B_TRUE);
    464 	dprintf("wrong BSSID: have %s expect %s; taking down",
    465 	    cur_bssid, exp_bssid);
    466 
    467 unexpected:
    468 	if (sendevent) {
    469 		/* If not, then shut the interface down normally */
    470 		(void) np_queue_add_event(EV_TAKEDOWN, wip->wi_name);
    471 		(void) dladm_wlan_disconnect(dld_handle, wip->wi_linkid);
    472 	}
    473 	return (B_FALSE);
    474 }
    475 
    476 /*
    477  * Examine all WLANs associated with an interface, verify the expected WLAN,
    478  * and update the 'connected' attribute appropriately.  The caller holds
    479  * wifi_mutex and deals with the 'known' flag.  If the expected WLAN is NULL,
    480  * then we expect to be connected to just "any" (autoconf) network.
    481  */
    482 static boolean_t
    483 update_connected_wlan(wireless_if_t *wip, struct wireless_lan *exp_wlan)
    484 {
    485 	dladm_wlan_linkattr_t attr;
    486 	struct wireless_lan *wlan, *lastconn, *newconn;
    487 	char essid[DLADM_STRSIZE];
    488 	char bssid[DLADM_STRSIZE];
    489 	boolean_t connected, wasconn;
    490 
    491 	if (dladm_wlan_get_linkattr(dld_handle, wip->wi_linkid, &attr) !=
    492 	    DLADM_STATUS_OK)
    493 		attr.la_status = DLADM_WLAN_LINK_DISCONNECTED;
    494 	if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) {
    495 		(void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, essid);
    496 		(void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, bssid);
    497 		connected = B_TRUE;
    498 		wip->wi_wireless_done = B_TRUE;
    499 		dprintf("update: %s reports connection to %s %s", wip->wi_name,
    500 		    essid, bssid);
    501 	} else {
    502 		connected = B_FALSE;
    503 		dprintf("update: %s is currently unconnected", wip->wi_name);
    504 	}
    505 
    506 	/*
    507 	 * First, verify that if we're connected, then we should be and that
    508 	 * we're connected to the expected AP.
    509 	 */
    510 	if (exp_wlan != NULL) {
    511 		/*
    512 		 * If we're connected to the wrong one, then disconnect.  Note:
    513 		 * we'd like to verify BSSID, but we cannot due to CR 6772510.
    514 		 */
    515 		if (connected && strcmp(exp_wlan->essid, essid) != 0) {
    516 			dprintf("update: wrong AP on %s; expected %s %s",
    517 			    exp_wlan->wl_if_name, exp_wlan->essid,
    518 			    exp_wlan->bssid);
    519 			(void) dladm_wlan_disconnect(dld_handle,
    520 			    wip->wi_linkid);
    521 			connected = B_FALSE;
    522 		}
    523 		/* If we're not in the expected state, then report disconnect */
    524 		if (exp_wlan->connected != connected) {
    525 			exp_wlan->connected = B_FALSE;
    526 			if (connected) {
    527 				dprintf("update: unexpected connection to %s "
    528 				    "%s; clearing", essid, bssid);
    529 				(void) dladm_wlan_disconnect(dld_handle,
    530 				    wip->wi_linkid);
    531 			} else {
    532 				dprintf("update: not connected to %s %s as "
    533 				    "expected", exp_wlan->essid,
    534 				    exp_wlan->bssid);
    535 				report_wlan_disconnect(exp_wlan);
    536 			}
    537 			connected = B_FALSE;
    538 		}
    539 	}
    540 
    541 	/*
    542 	 * State is now known to be good, so make the list entries match.
    543 	 */
    544 	wasconn = B_FALSE;
    545 	lastconn = newconn = NULL;
    546 	for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) {
    547 		if (strcmp(wlan->wl_if_name, wip->wi_name) != 0)
    548 			continue;
    549 		/* missing bssid check */
    550 		if (connected && strcmp(wlan->essid, essid) == 0) {
    551 			wasconn = wlan->connected;
    552 			wlan->connected = connected;
    553 			newconn = wlan;
    554 		} else if (wlan->connected) {
    555 			lastconn = wlan;
    556 			wlan->connected = B_FALSE;
    557 		}
    558 	}
    559 	if (newconn == NULL && connected) {
    560 		newconn = add_wlan_entry(wip->wi_name, essid, bssid,
    561 		    &attr.la_wlan_attr);
    562 		if (newconn != NULL)
    563 			newconn->connected = connected;
    564 	}
    565 	if (lastconn != NULL)
    566 		report_wlan_disconnect(lastconn);
    567 	if (newconn != NULL && !wasconn && connected)
    568 		report_wlan_connected(newconn);
    569 	return (connected);
    570 }
    571 
    572 /*
    573  * If there is already a scan or connect in progress, defer until the operation
    574  * is done to avoid radio interference *and* significant driver bugs.
    575  *
    576  * Returns B_TRUE when the lock is taken and the caller must call
    577  * scanconnect_exit.  Returns B_FALSE when lock not taken; caller must not call
    578  * scanconnect_exit.
    579  *
    580  * If we happen to be doing a scan, and the interface doing the scan is the
    581  * same as the one requesting a new scan, then wait for it to finish, and then
    582  * report that we're done by returning B_FALSE (no lock taken).
    583  */
    584 static boolean_t
    585 scanconnect_entry(const char *ifname, boolean_t is_connect)
    586 {
    587 	boolean_t already_done;
    588 
    589 	if (pthread_mutex_lock(&wifi_init_mutex) != 0)
    590 		return (B_FALSE);
    591 	already_done = B_FALSE;
    592 	while (wifi_scan_intf != NULL) {
    593 		dprintf("%s in progress on %s; blocking %s of %s",
    594 		    connect_running ? "connect" : "scan", wifi_scan_intf,
    595 		    is_connect ? "connect" : "scan", ifname);
    596 		if (!is_connect && !connect_running &&
    597 		    strcmp(wifi_scan_intf, ifname) == 0)
    598 			already_done = B_TRUE;
    599 		(void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex);
    600 		if (already_done || shutting_down) {
    601 			(void) pthread_mutex_unlock(&wifi_init_mutex);
    602 			return (B_FALSE);
    603 		}
    604 	}
    605 	dprintf("now exclusively %s on %s",
    606 	    is_connect ? "connecting" : "scanning", ifname);
    607 	wifi_scan_intf = ifname;
    608 	connect_running = is_connect;
    609 	(void) pthread_mutex_unlock(&wifi_init_mutex);
    610 	return (B_TRUE);
    611 }
    612 
    613 static void
    614 scanconnect_exit(void)
    615 {
    616 	(void) pthread_mutex_lock(&wifi_init_mutex);
    617 	dprintf("done exclusively %s on %s",
    618 	    connect_running ? "connecting" : "scanning", wifi_scan_intf);
    619 	wifi_scan_intf = NULL;
    620 	(void) pthread_cond_broadcast(&wifi_init_cond);
    621 	(void) pthread_mutex_unlock(&wifi_init_mutex);
    622 }
    623 
    624 /*
    625  * Return B_TRUE if we're in the midst of connecting on a given wireless
    626  * interface.  We shouldn't try to take such an interface down.
    627  */
    628 static boolean_t
    629 connecting_on(const char *ifname)
    630 {
    631 	boolean_t in_progress;
    632 
    633 	if (pthread_mutex_lock(&wifi_init_mutex) != 0)
    634 		return (B_FALSE);
    635 	in_progress = (wifi_scan_intf != NULL && connect_running &&
    636 	    strcmp(ifname, wifi_scan_intf) == 0);
    637 	(void) pthread_mutex_unlock(&wifi_init_mutex);
    638 	return (in_progress);
    639 }
    640 
    641 /*
    642  * Terminate all waiting transient threads as soon as possible.  This assumes
    643  * that the shutting_down flag has already been set.
    644  */
    645 void
    646 terminate_wireless(void)
    647 {
    648 	(void) pthread_cond_broadcast(&wifi_init_cond);
    649 }
    650 
    651 /*
    652  * Given a wireless interface, use it to scan for available networks.  The
    653  * caller must not hold wifi_mutex.
    654  */
    655 static void
    656 scan_wireless_nets(const char *ifname)
    657 {
    658 	boolean_t	dropped;
    659 	boolean_t	new_found;
    660 	dladm_status_t	status;
    661 	int		i;
    662 	datalink_id_t	linkid;
    663 	wireless_if_t	*wip;
    664 
    665 	/*
    666 	 * Wait for scan/connect to finish, and return if error or if this
    667 	 * interface is already done.
    668 	 */
    669 	if (!scanconnect_entry(ifname, B_FALSE))
    670 		return;
    671 
    672 	/* Grab the linkid from the wireless interface */
    673 	if (pthread_mutex_lock(&wifi_mutex) != 0)
    674 		goto scan_end;
    675 	if ((wip = find_wireless_if(ifname)) == NULL) {
    676 		(void) pthread_mutex_unlock(&wifi_mutex);
    677 		dprintf("aborted scan on %s; unable to locate interface",
    678 		    ifname);
    679 		goto scan_end;
    680 	}
    681 	linkid = wip->wi_linkid;
    682 	(void) pthread_mutex_unlock(&wifi_mutex);
    683 
    684 	/*
    685 	 * Since only one scan is allowed at any one time, and only scans can
    686 	 * modify the list, there's no need to grab a lock in checking
    687 	 * wireless_lan_used or the wlans list itself, or for the new_ap_found
    688 	 * global.
    689 	 *
    690 	 * All other threads must hold the mutex when reading this data, and
    691 	 * this thread must hold the mutex only when writing portions that
    692 	 * those other threads may read.
    693 	 */
    694 	for (i = 0; i < wireless_lan_used; i++)
    695 		wlans[i].scanned = B_FALSE;
    696 	new_ap_found = B_FALSE;
    697 	dprintf("starting scan on %s", ifname);
    698 	status = dladm_wlan_scan(dld_handle, linkid, (char *)ifname,
    699 	    get_scan_results);
    700 	if (status == DLADM_STATUS_OK) {
    701 		dropped = clear_unscanned_entries(ifname);
    702 	} else {
    703 		dropped = B_FALSE;
    704 		syslog(LOG_NOTICE, "cannot scan link '%s'", ifname);
    705 	}
    706 
    707 scan_end:
    708 	/* Need to sample this global before clearing out scan lock */
    709 	new_found = new_ap_found;
    710 
    711 	/*
    712 	 * Due to common driver bugs, it's necessary to check the state of the
    713 	 * interface right after doing a scan.  If it's connected and we didn't
    714 	 * expect it to be, or if we're accidentally connected to the wrong AP,
    715 	 * then disconnect now and reconnect.
    716 	 */
    717 	if (pthread_mutex_lock(&wifi_mutex) == 0) {
    718 		if ((wip = find_wireless_if(ifname)) != NULL) {
    719 			dladm_wlan_linkattr_t attr;
    720 			struct wireless_lan *wlan;
    721 			char essid[DLADM_STRSIZE];
    722 			char bssid[DLADM_STRSIZE];
    723 			boolean_t connected;
    724 			int retries = 0;
    725 
    726 			wip->wi_scan_running = B_FALSE;
    727 
    728 			/*
    729 			 * This is awful, but some wireless drivers
    730 			 * (particularly 'ath') will erroneously report
    731 			 * "disconnected" if queried right after a scan.  If we
    732 			 * see 'down' reported here, we retry a few times to
    733 			 * make sure it's really down.
    734 			 */
    735 			while (retries++ < 4) {
    736 				if (dladm_wlan_get_linkattr(dld_handle,
    737 				    wip->wi_linkid, &attr) != DLADM_STATUS_OK)
    738 					attr.la_status =
    739 					    DLADM_WLAN_LINK_DISCONNECTED;
    740 				else if (attr.la_status ==
    741 				    DLADM_WLAN_LINK_CONNECTED)
    742 					break;
    743 			}
    744 			if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) {
    745 				(void) dladm_wlan_essid2str(
    746 				    &attr.la_wlan_attr.wa_essid, essid);
    747 				(void) dladm_wlan_bssid2str(
    748 				    &attr.la_wlan_attr.wa_bssid, bssid);
    749 				connected = B_TRUE;
    750 				dprintf("scan: %s reports connection to %s "
    751 				    "%s", ifname, essid, bssid);
    752 			} else {
    753 				connected = B_FALSE;
    754 				dprintf("scan: %s is currently unconnected",
    755 				    ifname);
    756 			}
    757 			/* Disconnect from wrong AP first */
    758 			for (wlan = wlans; wlan < wlans + wireless_lan_used;
    759 			    wlan++) {
    760 				if (strcmp(wlan->wl_if_name, ifname) != 0)
    761 					continue;
    762 				/* missing bssid check */
    763 				if (strcmp(wlan->essid, essid) == 0) {
    764 					/*
    765 					 * This is the one we are currently
    766 					 * connected to.  See if we should be
    767 					 * here.
    768 					 */
    769 					if (!connected || !wlan->connected)
    770 						(void) dladm_wlan_disconnect(
    771 						    dld_handle, linkid);
    772 					break;
    773 				}
    774 			}
    775 			/* Connect to right AP by reporting disconnect */
    776 			for (wlan = wlans; wlan < wlans + wireless_lan_used;
    777 			    wlan++) {
    778 				if (strcmp(wlan->wl_if_name, ifname) != 0)
    779 					continue;
    780 				if (wlan->connected) {
    781 					/* missing bssid check */
    782 					if (connected &&
    783 					    strcmp(wlan->essid, essid) == 0)
    784 						break;
    785 					/*
    786 					 * We weren't where we were supposed to
    787 					 * be.  Try to reconnect now.
    788 					 */
    789 					(void) np_queue_add_event(EV_LINKDISC,
    790 					    ifname);
    791 				}
    792 			}
    793 		}
    794 		(void) pthread_mutex_unlock(&wifi_mutex);
    795 	}
    796 
    797 	scanconnect_exit();
    798 
    799 	if (status == DLADM_STATUS_OK)
    800 		report_scan_complete(ifname, dropped || new_found, wlans,
    801 		    wireless_lan_used);
    802 
    803 	if (new_found) {
    804 		dprintf("new AP added: %s", ifname);
    805 		(void) np_queue_add_event(EV_NEWAP, ifname);
    806 	}
    807 }
    808 
    809 /*
    810  * Rescan all wireless interfaces.  This routine intentionally does not hold
    811  * wifi_mutex during the scan, as scans can take a long time to accomplish, and
    812  * there may be more than one wireless interface.  The counter is used to make
    813  * sure that we don't run "forever" if the list is changing quickly.
    814  */
    815 static void
    816 rescan_wifi_no_lock(void)
    817 {
    818 	uint_t cnt = 0;
    819 	wireless_if_t *wip;
    820 	char ifname[LIFNAMSIZ];
    821 
    822 	if (pthread_mutex_lock(&wifi_mutex) != 0)
    823 		return;
    824 	wip = (wireless_if_t *)wi_list.q_forw;
    825 	while (cnt++ < wi_link_count && wip != (wireless_if_t *)&wi_list) {
    826 		(void) strlcpy(ifname, wip->wi_name, sizeof (ifname));
    827 		dprintf("periodic wireless scan: %s", ifname);
    828 		/* Even less than "very weak" */
    829 		wip->wi_strength = 0;
    830 		wip->wi_scan_running = B_TRUE;
    831 		(void) pthread_mutex_unlock(&wifi_mutex);
    832 
    833 		scan_wireless_nets(ifname);
    834 
    835 		if (pthread_mutex_lock(&wifi_mutex) != 0)
    836 			return;
    837 		if ((wip = find_wireless_if(ifname)) == NULL)
    838 			wip = (wireless_if_t *)&wi_list;
    839 		else
    840 			wip = (wireless_if_t *)wip->wi_links.q_forw;
    841 	}
    842 	(void) pthread_mutex_unlock(&wifi_mutex);
    843 }
    844 
    845 /*
    846  * This thread is given the name of the interface to scan, and must free that
    847  * name when done.
    848  */
    849 static void *
    850 scan_thread(void *arg)
    851 {
    852 	char *ifname = arg;
    853 
    854 	scan_wireless_nets(ifname);
    855 	free(ifname);
    856 
    857 	return (NULL);
    858 }
    859 
    860 /*
    861  * Launch a thread to scan the given wireless interface.  We copy the interface
    862  * name over to allocated storage because it's not possible to hand off a lock
    863  * on the interface list to the new thread, and the caller's storage (our input
    864  * argument) isn't guaranteed to be stable after we return to the caller.
    865  */
    866 int
    867 launch_wireless_scan(const char *ifname)
    868 {
    869 	int retv;
    870 	wireless_if_t *wip;
    871 	pthread_t if_thr;
    872 	pthread_attr_t attr;
    873 	char *winame;
    874 
    875 	if ((winame = strdup(ifname)) == NULL)
    876 		return (ENOMEM);
    877 
    878 	if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) {
    879 		free(winame);
    880 		return (retv);
    881 	}
    882 
    883 	if ((wip = find_wireless_if(ifname)) == NULL) {
    884 		retv = ENXIO;
    885 	} else if (wip->wi_scan_running) {
    886 		retv = EINPROGRESS;
    887 	} else {
    888 		(void) pthread_attr_init(&attr);
    889 		(void) pthread_attr_setdetachstate(&attr,
    890 		    PTHREAD_CREATE_DETACHED);
    891 		retv = pthread_create(&if_thr, &attr, scan_thread, winame);
    892 		if (retv == 0)
    893 			wip->wi_scan_running = B_TRUE;
    894 	}
    895 	(void) pthread_mutex_unlock(&wifi_mutex);
    896 
    897 	/* If thread not started, then discard the name. */
    898 	if (retv != 0)
    899 		free(winame);
    900 
    901 	return (retv);
    902 }
    903 
    904 /*
    905  * Caller does not hold wifi_mutex.
    906  */
    907 static boolean_t
    908 get_scan_results(void *arg, dladm_wlan_attr_t *attrp)
    909 {
    910 	const char *ifname = arg;
    911 	wireless_if_t *wip;
    912 	struct wireless_lan *wlan;
    913 	char		essid_name[DLADM_STRSIZE];
    914 	char		bssid_name[DLADM_STRSIZE];
    915 	boolean_t	retv;
    916 
    917 	(void) dladm_wlan_essid2str(&attrp->wa_essid, essid_name);
    918 	(void) dladm_wlan_bssid2str(&attrp->wa_bssid, bssid_name);
    919 
    920 	/*
    921 	 * Check whether ESSID is "hidden".
    922 	 * If so try to substitute it with the ESSID from the
    923 	 * known_wifi_nets with the same BSSID
    924 	 */
    925 	if (essid_name[0] == '\0') {
    926 		if (known_wifi_nets_lookup(essid_name, bssid_name,
    927 		    essid_name) &&
    928 		    dladm_wlan_str2essid(essid_name, &attrp->wa_essid) ==
    929 		    DLADM_STATUS_OK) {
    930 			dprintf("Using ESSID %s with BSSID %s",
    931 			    essid_name, bssid_name);
    932 		}
    933 	}
    934 
    935 	if (pthread_mutex_lock(&wifi_mutex) != 0)
    936 		return (B_FALSE);
    937 
    938 	if ((wip = find_wireless_if(ifname)) == NULL) {
    939 		(void) pthread_mutex_unlock(&wifi_mutex);
    940 		return (B_FALSE);
    941 	}
    942 
    943 	/* Remember the strongest we encounter */
    944 	if (attrp->wa_strength > wip->wi_strength)
    945 		wip->wi_strength = attrp->wa_strength;
    946 
    947 	wlan = find_wlan_entry(ifname, essid_name, bssid_name);
    948 	if (wlan != NULL) {
    949 		if (wlan->rescan)
    950 			new_ap_found = B_TRUE;
    951 		wlan->rescan = B_FALSE;
    952 		wlan->scanned = B_TRUE;
    953 		wlan->attrs = *attrp;
    954 		retv = B_TRUE;
    955 	} else if ((wlan = add_wlan_entry(ifname, essid_name, bssid_name,
    956 	    attrp)) != NULL) {
    957 		/* search cannot return NULL at this point due to add */
    958 		wlan->connected =
    959 		    find_wlan_entry(ifname, essid_name, "")->connected;
    960 		retv = B_TRUE;
    961 	} else {
    962 		retv = B_FALSE;
    963 	}
    964 	(void) pthread_mutex_unlock(&wifi_mutex);
    965 	return (retv);
    966 }
    967 
    968 /*
    969  * This is called when IP reports that the link layer is down.  It just
    970  * verifies that we're still connected as expected.  If not, then cover for the
    971  * known driver bugs (by disconnecting) and send an event so that we'll attempt
    972  * to recover.  No scan is done; if a scan is needed, we'll do one the next
    973  * time the timer pops.
    974  *
    975  * Note that we don't retry in case of error.  Since IP has reported the
    976  * interface as down, the best case here is that we detect a link failure and
    977  * start the connection process over again.
    978  */
    979 void
    980 wireless_verify(const char *ifname)
    981 {
    982 	datalink_id_t linkid;
    983 	dladm_wlan_linkattr_t attr;
    984 	wireless_if_t *wip;
    985 	struct wireless_lan *wlan;
    986 	boolean_t is_failure;
    987 
    988 	/*
    989 	 * If these calls fail, it means that the wireless link is down.
    990 	 */
    991 	if (dladm_name2info(dld_handle, ifname, &linkid, NULL, NULL, NULL) !=
    992 	    DLADM_STATUS_OK ||
    993 	    dladm_wlan_get_linkattr(dld_handle, linkid, &attr) !=
    994 	    DLADM_STATUS_OK) {
    995 		attr.la_status = DLADM_WLAN_LINK_DISCONNECTED;
    996 	}
    997 
    998 	/*
    999 	 * If the link is down, then work around a known driver bug (by forcing
   1000 	 * disconnect), and then deliver an event so that the state machine can
   1001 	 * retry.
   1002 	 */
   1003 	if (attr.la_status != DLADM_WLAN_LINK_CONNECTED) {
   1004 		if (connecting_on(ifname))
   1005 			return;
   1006 		is_failure = B_TRUE;
   1007 		if (pthread_mutex_lock(&wifi_mutex) == 0) {
   1008 			if ((wip = find_wireless_if(ifname)) != NULL) {
   1009 				/*
   1010 				 * Link down while waiting for user to supply
   1011 				 * key is *not* a failure case.
   1012 				 */
   1013 				if (!wip->wi_wireless_done &&
   1014 				    wip->wi_need_key) {
   1015 					is_failure = B_FALSE;
   1016 				} else {
   1017 					wip->wi_wireless_done = B_FALSE;
   1018 					wip->wi_need_key = B_FALSE;
   1019 				}
   1020 			}
   1021 			if (is_failure) {
   1022 				for (wlan = wlans;
   1023 				    wlan < wlans + wireless_lan_used; wlan++) {
   1024 					if (strcmp(wlan->wl_if_name, ifname) ==
   1025 					    0) {
   1026 						if (wlan->connected)
   1027 							report_wlan_disconnect(
   1028 							    wlan);
   1029 						wlan->connected = B_FALSE;
   1030 					}
   1031 				}
   1032 			}
   1033 			(void) pthread_mutex_unlock(&wifi_mutex);
   1034 		}
   1035 		if (is_failure) {
   1036 			dprintf("wireless check indicates disconnect");
   1037 			(void) dladm_wlan_disconnect(dld_handle, linkid);
   1038 			(void) np_queue_add_event(EV_LINKDISC, ifname);
   1039 		}
   1040 	}
   1041 }
   1042 
   1043 /* ARGSUSED */
   1044 void *
   1045 periodic_wireless_scan(void *arg)
   1046 {
   1047 	for (;;) {
   1048 		int ret, intv;
   1049 		dladm_wlan_linkattr_t attr;
   1050 		char ifname[LIFNAMSIZ];
   1051 		libnwam_interface_type_t ift;
   1052 		datalink_id_t linkid;
   1053 		char essid[DLADM_STRSIZE];
   1054 		struct wireless_lan *wlan;
   1055 
   1056 		/*
   1057 		 * Stop the scanning process if the user changes the interval
   1058 		 * to zero dynamically.  Reset the thread ID to a known-invalid
   1059 		 * value.  (Copy to a local variable to avoid race condition in
   1060 		 * case SIGINT hits between this test and the call to poll().)
   1061 		 */
   1062 		if ((intv = wlan_scan_interval) == 0) {
   1063 			dprintf("periodic wireless scan halted");
   1064 			break;
   1065 		}
   1066 
   1067 		ret = poll(NULL, 0, intv * MILLISEC);
   1068 		if (ret == -1) {
   1069 			if (errno == EINTR)
   1070 				continue;
   1071 			syslog(LOG_INFO, "periodic_wireless_scan: poll failed");
   1072 			break;
   1073 		}
   1074 
   1075 		/*
   1076 		 * Just one more check before doing a scan that might now be
   1077 		 * unwanted
   1078 		 */
   1079 		if (wlan_scan_interval == 0) {
   1080 			dprintf("periodic wireless scan halted");
   1081 			break;
   1082 		}
   1083 
   1084 		/* Get current profile name, if any */
   1085 		llp_get_name_and_type(ifname, sizeof (ifname), &ift);
   1086 
   1087 		/*
   1088 		 * We do a scan if
   1089 		 *
   1090 		 * 1. There is no active profile.  Or
   1091 		 * 2. Profile is wireless and we're not connected to the AP.  Or
   1092 		 * 3. The signal strength falls below a certain specified level.
   1093 		 */
   1094 		if (ifname[0] != '\0') {
   1095 			if (ift != IF_WIRELESS)
   1096 				continue;
   1097 
   1098 			/*
   1099 			 * If these things fail, it means that our wireless
   1100 			 * link isn't viable.  Proceed in that way.
   1101 			 */
   1102 			if (dladm_name2info(dld_handle, ifname, &linkid, NULL,
   1103 			    NULL, NULL) != DLADM_STATUS_OK ||
   1104 			    dladm_wlan_get_linkattr(dld_handle, linkid,
   1105 			    &attr) != DLADM_STATUS_OK) {
   1106 				attr.la_status = DLADM_WLAN_LINK_DISCONNECTED;
   1107 				attr.la_wlan_attr.wa_strength = 0;
   1108 			}
   1109 
   1110 			if (attr.la_status == DLADM_WLAN_LINK_CONNECTED &&
   1111 			    attr.la_wlan_attr.wa_strength >
   1112 			    wireless_scan_level) {
   1113 				/*
   1114 				 * Double-check the ESSID.  Some drivers
   1115 				 * (notably 'iwh') have a habit of randomly
   1116 				 * reconnecting themselves to APs that you
   1117 				 * never requested.
   1118 				 */
   1119 				(void) dladm_wlan_essid2str(
   1120 				    &attr.la_wlan_attr.wa_essid, essid);
   1121 				if (pthread_mutex_lock(&wifi_mutex) != 0)
   1122 					continue;
   1123 				for (wlan = wlans;
   1124 				    wlan < wlans + wireless_lan_used; wlan++) {
   1125 					if (wlan->connected &&
   1126 					    strcmp(wlan->wl_if_name, ifname) ==
   1127 					    0)
   1128 						break;
   1129 				}
   1130 				if (wlan >= wlans + wireless_lan_used ||
   1131 				    strcmp(wlan->essid, essid) == 0) {
   1132 					(void) pthread_mutex_unlock(
   1133 					    &wifi_mutex);
   1134 					continue;
   1135 				}
   1136 				dprintf("%s is connected to %s instead of %s",
   1137 				    ifname, essid, wlan->essid);
   1138 				(void) pthread_mutex_unlock(&wifi_mutex);
   1139 			}
   1140 		}
   1141 
   1142 		/* Rescan the wireless interfaces */
   1143 		rescan_wifi_no_lock();
   1144 
   1145 		if (ifname[0] != '\0') {
   1146 			wireless_if_t *wip;
   1147 
   1148 			/*
   1149 			 * If we're still connected and there's nothing better
   1150 			 * around, then there's no point in switching now.
   1151 			 */
   1152 			if (pthread_mutex_lock(&wifi_mutex) != 0)
   1153 				continue;
   1154 			if ((wip = find_wireless_if(ifname)) != NULL) {
   1155 				if (attr.la_status ==
   1156 				    DLADM_WLAN_LINK_CONNECTED &&
   1157 				    wip->wi_strength <=
   1158 				    attr.la_wlan_attr.wa_strength) {
   1159 					(void) pthread_mutex_unlock(&
   1160 					    wifi_mutex);
   1161 					continue;
   1162 				}
   1163 				wip->wi_wireless_done = B_FALSE;
   1164 				wip->wi_need_key = B_FALSE;
   1165 			}
   1166 			(void) pthread_mutex_unlock(&wifi_mutex);
   1167 
   1168 			/*
   1169 			 * Try to work around known driver bugs: if the driver
   1170 			 * says we're disconnected, then tell it to disconnect
   1171 			 * for sure.
   1172 			 */
   1173 			(void) dladm_wlan_disconnect(dld_handle, linkid);
   1174 
   1175 			/*
   1176 			 * Tell the state machine that we've lost this link so
   1177 			 * that it can do something about the problem.
   1178 			 */
   1179 			(void) np_queue_add_event(
   1180 			    (attr.la_status == DLADM_WLAN_LINK_CONNECTED ?
   1181 			    EV_LINKFADE : EV_LINKDISC), ifname);
   1182 		}
   1183 	}
   1184 	scan = 0;
   1185 	(void) pthread_detach(pthread_self());
   1186 	return (NULL);
   1187 }
   1188 
   1189 /*
   1190  * Below are functions used to handle storage/retrieval of keys
   1191  * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj()
   1192  * and dladm_get_secobj().
   1193  */
   1194 
   1195 /*
   1196  * Convert key hexascii string to raw secobj value. This
   1197  * code is very similar to convert_secobj() in dladm.c, it would
   1198  * be good to have a libdladm function to convert values.
   1199  */
   1200 static int
   1201 key_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp,
   1202     dladm_secobj_class_t class)
   1203 {
   1204 	size_t buf_len = strlen(buf);
   1205 
   1206 	dprintf("before: key_string_to_secobj_value: buf_len = %d", buf_len);
   1207 	if (buf_len == 0) {
   1208 		/* length zero means "delete" */
   1209 		return (0);
   1210 	}
   1211 
   1212 	if (buf[buf_len - 1] == '\n')
   1213 		buf[--buf_len] = '\0';
   1214 
   1215 	dprintf("after: key_string_to_secobj_value: buf_len = %d", buf_len);
   1216 
   1217 	if (class == DLADM_SECOBJ_CLASS_WPA) {
   1218 		/*
   1219 		 * Per IEEE802.11i spec, the Pre-shared key (PSK) length should
   1220 		 * be between 8 and 63.
   1221 		 */
   1222 		if (buf_len < 8 || buf_len > 63) {
   1223 			syslog(LOG_ERR,
   1224 			    "key_string_to_secobj_value:"
   1225 			    " invalid WPA key length: buf_len = %d", buf_len);
   1226 			return (-1);
   1227 		}
   1228 		(void) memcpy(obj_val, buf, (uint_t)buf_len);
   1229 		*obj_lenp = buf_len;
   1230 		return (0);
   1231 	}
   1232 
   1233 	switch (buf_len) {
   1234 	case 5:		/* ASCII key sizes */
   1235 	case 13:
   1236 		(void) memcpy(obj_val, buf, (uint_t)buf_len);
   1237 		*obj_lenp = (uint_t)buf_len;
   1238 		break;
   1239 	case 10:
   1240 	case 26:	/* Hex key sizes, not preceded by 0x */
   1241 		if (hexascii_to_octet(buf, (uint_t)buf_len, obj_val, obj_lenp)
   1242 		    != 0) {
   1243 			syslog(LOG_ERR,
   1244 			    "key_string_to_secobj_value: invalid WEP key");
   1245 			return (-1);
   1246 		}
   1247 		break;
   1248 	case 12:
   1249 	case 28:	/* Hex key sizes, preceded by 0x */
   1250 		if (strncmp(buf, "0x", 2) != 0 ||
   1251 		    hexascii_to_octet(buf + 2, (uint_t)buf_len - 2, obj_val,
   1252 		    obj_lenp) != 0) {
   1253 			syslog(LOG_ERR,
   1254 			    "key_string_to_secobj_value: invalid WEP key");
   1255 			return (-1);
   1256 		}
   1257 		break;
   1258 	default:
   1259 		syslog(LOG_ERR,
   1260 		    "key_string_to_secobj_value: invalid WEP key length");
   1261 		return (-1);
   1262 	}
   1263 	return (0);
   1264 }
   1265 
   1266 /*
   1267  * Print the key name format into the appropriate field, then convert any ":"
   1268  * characters to ".", as ":[1-4]" is the slot indicator, which otherwise
   1269  * would trip us up.  Invalid characters for secobj names are ignored.
   1270  * The fourth parameter is expected to be of size DLADM_SECOBJ_NAME_MAX.
   1271  *
   1272  * (Note that much of the system uses DLADM_WLAN_MAX_KEYNAME_LEN, which is 64
   1273  * rather than 32, but that dladm_get_secobj will fail if a length greater than
   1274  * DLD_SECOBJ_NAME_MAX is seen, and that's 32.  This is all horribly broken.)
   1275  */
   1276 static void
   1277 set_key_name(const char *essid, const char *bssid, char *name, size_t nsz)
   1278 {
   1279 	int i, j;
   1280 	char secobj_name[DLADM_WLAN_MAX_KEYNAME_LEN];
   1281 
   1282 	/* create a concatenated string with essid and bssid */
   1283 	if (bssid[0] == '\0') {
   1284 		(void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s",
   1285 		    essid);
   1286 	} else {
   1287 		(void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s-%s",
   1288 		    essid, bssid);
   1289 	}
   1290 
   1291 	/* copy only valid chars to the return string, terminating with \0 */
   1292 	i = 0; /* index into secobj_name */
   1293 	j = 0; /* index into name */
   1294 	while (secobj_name[i] != '\0') {
   1295 		if (j == nsz - 1)
   1296 			break;
   1297 
   1298 		if (secobj_name[i] == ':') {
   1299 			name[j] = '.';
   1300 			j++;
   1301 		} else if (isalnum(secobj_name[i]) ||
   1302 		    secobj_name[i] == '.' || secobj_name[i] == '-' ||
   1303 		    secobj_name[i] == '_') {
   1304 			name[j] = secobj_name[i];
   1305 			j++;
   1306 		}
   1307 		i++;
   1308 	}
   1309 	name[j] = '\0';
   1310 }
   1311 
   1312 static int
   1313 store_key(struct wireless_lan *wlan)
   1314 {
   1315 	uint8_t obj_val[DLADM_SECOBJ_VAL_MAX];
   1316 	uint_t obj_len = sizeof (obj_val);
   1317 	char obj_name[DLADM_SECOBJ_NAME_MAX];
   1318 	dladm_status_t status;
   1319 	char errmsg[DLADM_STRSIZE];
   1320 	dladm_secobj_class_t class;
   1321 
   1322 	/*
   1323 	 * Name key object for this WLAN so it can be later retrieved
   1324 	 * (name is unique for each ESSID/BSSID combination).
   1325 	 */
   1326 	set_key_name(wlan->essid, wlan->bssid, obj_name, sizeof (obj_name));
   1327 	dprintf("store_key: obj_name is %s", obj_name);
   1328 
   1329 	class = (wlan->attrs.wa_secmode == DLADM_WLAN_SECMODE_WEP ?
   1330 	    DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA);
   1331 	if (key_string_to_secobj_value(wlan->raw_key, obj_val, &obj_len,
   1332 	    class) != 0) {
   1333 		/* above function logs internally on failure */
   1334 		return (-1);
   1335 	}
   1336 
   1337 	/* we've validated the new key, so remove the old one */
   1338 	status = dladm_unset_secobj(dld_handle, obj_name,
   1339 	    DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST);
   1340 	if (status != DLADM_STATUS_OK && status != DLADM_STATUS_NOTFOUND) {
   1341 		syslog(LOG_ERR, "store_key: could not remove old secure object "
   1342 		    "'%s' for key: %s", obj_name,
   1343 		    dladm_status2str(status, errmsg));
   1344 		return (-1);
   1345 	}
   1346 
   1347 	/* if we're just deleting the key, then we're done */
   1348 	if (wlan->raw_key[0] == '\0')
   1349 		return (0);
   1350 
   1351 	status = dladm_set_secobj(dld_handle, obj_name, class,
   1352 	    obj_val, obj_len,
   1353 	    DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_ACTIVE);
   1354 	if (status != DLADM_STATUS_OK) {
   1355 		syslog(LOG_ERR, "store_key: could not create secure object "
   1356 		    "'%s' for key: %s", obj_name,
   1357 		    dladm_status2str(status, errmsg));
   1358 		return (-1);
   1359 	}
   1360 	/*
   1361 	 * We don't really need to retrieve the key we just stored, but
   1362 	 * we do need to set the cooked key, and the function below takes
   1363 	 * care of allocating memory and setting the length and slot ID
   1364 	 * besides just copying the value, so it is simpler just to call
   1365 	 * the retrieve function instead of doing it all here.
   1366 	 *
   1367 	 * Since we just stored the key, retrieve_key() "shouldn't"
   1368 	 * fail.  If it does fail, it's not the end of the world; a NULL
   1369 	 * value for wlan->cooked_key simply means this particular
   1370 	 * attempt to connect will fail, and alternative connection
   1371 	 * options will be used.
   1372 	 */
   1373 	wlan->cooked_key = retrieve_key(wlan->essid, wlan->bssid, class);
   1374 	return (0);
   1375 }
   1376 
   1377 /*
   1378  * retrieve_key returns NULL if no key was recovered from libdladm
   1379  */
   1380 static dladm_wlan_key_t *
   1381 retrieve_key(const char *essid, const char *bssid, dladm_secobj_class_t req)
   1382 {
   1383 	dladm_status_t status;
   1384 	char errmsg[DLADM_STRSIZE];
   1385 	dladm_wlan_key_t *cooked_key;
   1386 	dladm_secobj_class_t class;
   1387 
   1388 	/*
   1389 	 * Newly-allocated key must be freed by caller, or by
   1390 	 * subsequent call to retrieve_key().
   1391 	 */
   1392 	if ((cooked_key = malloc(sizeof (dladm_wlan_key_t))) == NULL) {
   1393 		syslog(LOG_ERR, "retrieve_key: malloc failed");
   1394 		return (NULL);
   1395 	}
   1396 
   1397 	/*
   1398 	 * Set name appropriately to retrieve key for this WLAN.  Note that we
   1399 	 * cannot use the actual wk_name buffer size, as it's two times too
   1400 	 * large for dladm_get_secobj.
   1401 	 */
   1402 	set_key_name(essid, bssid, cooked_key->wk_name, DLADM_SECOBJ_NAME_MAX);
   1403 	dprintf("retrieve_key: len = %d, object = %s\n",
   1404 	    strlen(cooked_key->wk_name), cooked_key->wk_name);
   1405 	cooked_key->wk_len = sizeof (cooked_key->wk_val);
   1406 	cooked_key->wk_idx = 1;
   1407 
   1408 	/* Try the kernel first, then fall back to persistent storage. */
   1409 	status = dladm_get_secobj(dld_handle, cooked_key->wk_name, &class,
   1410 	    cooked_key->wk_val, &cooked_key->wk_len,
   1411 	    DLADM_OPT_ACTIVE);
   1412 	if (status != DLADM_STATUS_OK) {
   1413 		dprintf("retrieve_key: dladm_get_secobj(TEMP) failed: %s",
   1414 		    dladm_status2str(status, errmsg));
   1415 		status = dladm_get_secobj(dld_handle, cooked_key->wk_name,
   1416 		    &class, cooked_key->wk_val, &cooked_key->wk_len,
   1417 		    DLADM_OPT_PERSIST);
   1418 	}
   1419 
   1420 	switch (status) {
   1421 	case DLADM_STATUS_OK:
   1422 		dprintf("retrieve_key: dladm_get_secobj succeeded: len %d",
   1423 		    cooked_key->wk_len);
   1424 		break;
   1425 	case DLADM_STATUS_NOTFOUND:
   1426 		/*
   1427 		 * We do not want an error in the case that the secobj
   1428 		 * is not found, since we then prompt for it.
   1429 		 */
   1430 		free(cooked_key);
   1431 		return (NULL);
   1432 	default:
   1433 		syslog(LOG_ERR, "retrieve_key: could not get key "
   1434 		    "from secure object '%s': %s", cooked_key->wk_name,
   1435 		    dladm_status2str(status, errmsg));
   1436 		free(cooked_key);
   1437 		return (NULL);
   1438 	}
   1439 
   1440 	if (class != req) {	/* the key mismatch */
   1441 		syslog(LOG_ERR, "retrieve_key: key type mismatch"
   1442 		    " from secure object '%s'", cooked_key->wk_name);
   1443 		free(cooked_key);
   1444 		return (NULL);
   1445 	}
   1446 
   1447 	return (cooked_key);
   1448 }
   1449 
   1450 /*
   1451  * Add an entry to known_wifi_nets file given the parameters.  The caller holds
   1452  * wifi_mutex.
   1453  */
   1454 static int
   1455 add_known_wifi_nets_file(const char *essid, const char *bssid)
   1456 {
   1457 	int retv;
   1458 	FILE *fp = NULL;
   1459 
   1460 	dprintf("add_known_wifi_nets_file(%s, %s)", essid, bssid);
   1461 
   1462 	/* Create the NWAM directory in case it does not exist. */
   1463 	if (mkdir(LLPDIRNAME, LLPDIRMODE) != 0 &&
   1464 	    errno != EEXIST) {
   1465 		retv = errno;
   1466 		syslog(LOG_ERR, "could not create %s: %m", LLPDIRNAME);
   1467 	} else if ((fp = fopen(KNOWN_WIFI_NETS, "a+")) == NULL) {
   1468 		retv = errno;
   1469 		syslog(LOG_ERR, "fopen(%s) failed: %m", KNOWN_WIFI_NETS);
   1470 	} else if (known_wifi_nets_lookup(essid, bssid, NULL)) {
   1471 		retv = EEXIST;
   1472 	} else {
   1473 		/* now add this to the file */
   1474 		(void) fprintf(fp, "%s\t%s\n", essid, bssid);
   1475 		retv = 0;
   1476 	}
   1477 	if (fp != NULL)
   1478 		(void) fclose(fp);
   1479 	return (retv);
   1480 }
   1481 
   1482 static int
   1483 delete_known_wifi_nets_file(const char *essid, const char *bssid)
   1484 {
   1485 	FILE *fpin, *fpout;
   1486 	char line[LINE_MAX];
   1487 	char *cp;
   1488 	int retv;
   1489 	size_t essidlen, bssidlen;
   1490 	boolean_t found;
   1491 
   1492 	if ((fpin = fopen(KNOWN_WIFI_NETS, "r")) == NULL)
   1493 		return (errno);
   1494 
   1495 	if ((fpout = fopen(KNOWN_WIFI_TMP, "w")) == NULL) {
   1496 		retv = errno;
   1497 		(void) fclose(fpin);
   1498 		return (retv);
   1499 	}
   1500 
   1501 	found = B_FALSE;
   1502 	essidlen = strlen(essid);
   1503 	bssidlen = strlen(bssid);
   1504 	while (fgets(line, sizeof (line), fpin) != NULL) {
   1505 		cp = line;
   1506 		while (isspace(*cp))
   1507 			cp++;
   1508 
   1509 		if (*cp == '#' || *cp == '\0' ||
   1510 		    strncmp(essid, cp, essidlen) != 0 ||
   1511 		    (cp[essidlen] != '\0' && !isspace(cp[essidlen]))) {
   1512 			(void) fputs(line, fpout);
   1513 			continue;
   1514 		}
   1515 
   1516 		/* skip over the essid to examine bssid */
   1517 		while (*cp != '\0' && !isspace(*cp))
   1518 			cp++;
   1519 		while (isspace(*cp))
   1520 			cp++;
   1521 
   1522 		/*
   1523 		 * Deleting with bssid empty means "all entries under this
   1524 		 * essid."  As a result, deleting a wildcard entry for a bssid
   1525 		 * means deleting all entries for that bssid.
   1526 		 */
   1527 
   1528 		if (bssidlen == 0 ||
   1529 		    (strncmp(bssid, cp, bssidlen) == 0 &&
   1530 		    (cp[bssidlen] == '\0' || isspace(cp[bssidlen])))) {
   1531 			/* delete this entry */
   1532 			found = B_TRUE;
   1533 			continue;
   1534 		}
   1535 
   1536 		(void) fputs(line, fpout);
   1537 	}
   1538 
   1539 	(void) fclose(fpin);
   1540 	(void) fclose(fpout);
   1541 
   1542 	if (found) {
   1543 		if (rename(KNOWN_WIFI_TMP, KNOWN_WIFI_NETS) == 0) {
   1544 			retv = 0;
   1545 		} else {
   1546 			retv = errno;
   1547 			(void) unlink(KNOWN_WIFI_TMP);
   1548 		}
   1549 	} else {
   1550 		retv = ENXIO;
   1551 		(void) unlink(KNOWN_WIFI_TMP);
   1552 	}
   1553 
   1554 	return (retv);
   1555 }
   1556 
   1557 /*
   1558  * Check if the given AP (ESSID, BSSID pair) is on the known AP list.
   1559  * If found_essid is non-NULL and the match is found (B_TRUE is returned)
   1560  * the matched ESSID is copied out into buffer pointed by found_essid.
   1561  * The buffer is expected to be at least DLADM_STRSIZE bytes long.
   1562  */
   1563 static boolean_t
   1564 known_wifi_nets_lookup(const char *new_essid, const char *new_bssid,
   1565     char *found_essid)
   1566 {
   1567 	FILE *fp;
   1568 	char line[LINE_MAX];
   1569 	char *cp;
   1570 	char *tok[MAX_FIELDS];
   1571 	int line_num;
   1572 	boolean_t found = B_FALSE;
   1573 
   1574 	/*
   1575 	 * For now the file format is:
   1576 	 * essid\tbssid
   1577 	 * (essid followed by tab followed by bssid)
   1578 	 */
   1579 	fp = fopen(KNOWN_WIFI_NETS, "r");
   1580 	if (fp == NULL)
   1581 		return (B_FALSE);
   1582 	for (line_num = 1; fgets(line, sizeof (line), fp) != NULL; line_num++) {
   1583 
   1584 		cp = line;
   1585 		while (isspace(*cp))
   1586 			cp++;
   1587 
   1588 		if (*cp == '#' || *cp == '\0')
   1589 			continue;
   1590 
   1591 		if (bufsplit(cp, MAX_FIELDS, tok) != MAX_FIELDS) {
   1592 			syslog(LOG_ERR, "%s:%d: wrong number of tokens; "
   1593 			    "ignoring entry", KNOWN_WIFI_NETS, line_num);
   1594 			continue;
   1595 		}
   1596 
   1597 		/*
   1598 		 * If we're searching on ESSID alone, then any match on a
   1599 		 * specific ESSID will do.
   1600 		 */
   1601 		if (*new_bssid == '\0') {
   1602 			if (*new_essid != '\0' &&
   1603 			    strcmp(tok[ESSID], new_essid) == 0) {
   1604 				found = B_TRUE;
   1605 				break;
   1606 			}
   1607 		}
   1608 		/*
   1609 		 * If BSSID match is found we check ESSID, which should
   1610 		 * either match as well, or be an empty string.
   1611 		 * In latter case we'll retrieve the ESSID from known_wifi_nets
   1612 		 * later.
   1613 		 */
   1614 		else if (strcmp(tok[BSSID], new_bssid) == 0) {
   1615 			/*
   1616 			 * Got BSSID match, either ESSID was not specified,
   1617 			 * or it should match
   1618 			 */
   1619 			if (*new_essid == '\0' ||
   1620 			    strcmp(tok[ESSID], new_essid) == 0) {
   1621 				found = B_TRUE;
   1622 				break;
   1623 			}
   1624 		}
   1625 	}
   1626 
   1627 	if (found) {
   1628 		if (found_essid != NULL)
   1629 			(void) strlcpy(found_essid, tok[ESSID], DLADM_STRSIZE);
   1630 	}
   1631 
   1632 	(void) fclose(fp);
   1633 	return (found);
   1634 }
   1635 
   1636 static uint_t
   1637 extract_known_aps(FILE *fp, libnwam_known_ap_t *kap, char *sbuf, size_t *totstr)
   1638 {
   1639 	char line[LINE_MAX];
   1640 	char *cp;
   1641 	char *tok[MAX_FIELDS];
   1642 	size_t accstr = 0;
   1643 	uint_t count = 0;
   1644 	char key[DLADM_SECOBJ_NAME_MAX];
   1645 	uint8_t keyval[DLADM_SECOBJ_VAL_MAX];
   1646 	dladm_secobj_class_t class;
   1647 	uint_t keylen;
   1648 
   1649 	while (fgets(line, sizeof (line), fp) != NULL) {
   1650 		cp = line;
   1651 		while (isspace(*cp))
   1652 			cp++;
   1653 
   1654 		if (*cp == '#' || *cp == '\0')
   1655 			continue;
   1656 
   1657 		if (bufsplit(cp, MAX_FIELDS, tok) != MAX_FIELDS)
   1658 			continue;
   1659 
   1660 		if (totstr != NULL)
   1661 			accstr += strlen(tok[BSSID]) + strlen(tok[ESSID]) + 2;
   1662 		count++;
   1663 
   1664 		if (kap != NULL) {
   1665 			kap->ka_essid = strcpy(sbuf, tok[ESSID]);
   1666 			sbuf += strlen(sbuf) + 1;
   1667 			kap->ka_bssid = strcpy(sbuf, tok[BSSID]);
   1668 			sbuf += strlen(sbuf) + 1;
   1669 			set_key_name(tok[ESSID], tok[BSSID], key, sizeof (key));
   1670 			keylen = sizeof (keyval);
   1671 			if (dladm_get_secobj(dld_handle, key, &class, keyval,
   1672 			    &keylen, DLADM_OPT_ACTIVE) == DLADM_STATUS_OK)
   1673 				kap->ka_haskey = B_TRUE;
   1674 			else
   1675 				kap->ka_haskey = B_FALSE;
   1676 			kap++;
   1677 		}
   1678 	}
   1679 	if (totstr != NULL)
   1680 		*totstr = accstr;
   1681 	return (count);
   1682 }
   1683 
   1684 libnwam_known_ap_t *
   1685 get_known_ap_list(size_t *kasizep, uint_t *countp)
   1686 {
   1687 	FILE *fp;
   1688 	libnwam_known_ap_t *kap = NULL;
   1689 	size_t kasize;
   1690 	uint_t count;
   1691 	int retv;
   1692 
   1693 	if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0) {
   1694 		errno = retv;
   1695 		return (kap);
   1696 	}
   1697 	if ((fp = fopen(KNOWN_WIFI_NETS, "r")) != NULL) {
   1698 		count = extract_known_aps(fp, NULL, NULL, &kasize);
   1699 		rewind(fp);
   1700 		kasize += count * sizeof (*kap);
   1701 		if (count != 0 && (kap = malloc(kasize)) != NULL) {
   1702 			(void) extract_known_aps(fp, kap, (char *)(kap + count),
   1703 			    NULL);
   1704 			*kasizep = kasize;
   1705 			*countp = count;
   1706 		}
   1707 		(void) fclose(fp);
   1708 	}
   1709 	(void) pthread_mutex_unlock(&wifi_mutex);
   1710 	return (kap);
   1711 }
   1712 
   1713 int
   1714 add_known_ap(const char *essid, const char *bssid)
   1715 {
   1716 	int retv;
   1717 	char ifname[LIFNAMSIZ];
   1718 	libnwam_interface_type_t ift;
   1719 	struct wireless_lan *wlan, *savedwlan;
   1720 
   1721 	/*
   1722 	 * First check the current LLP.  If there is one, then its connection
   1723 	 * state determines what to do after adding the known AP to the list.
   1724 	 * If not, then we act if there are no connected APs.
   1725 	 */
   1726 	llp_get_name_and_type(ifname, sizeof (ifname), &ift);
   1727 
   1728 	if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0)
   1729 		return (retv);
   1730 
   1731 	retv = add_known_wifi_nets_file(essid, bssid);
   1732 	if (retv == 0 && (ift == IF_UNKNOWN || ift == IF_WIRELESS)) {
   1733 		boolean_t any_connected, one_matches;
   1734 
   1735 		/*
   1736 		 * If this is in our list of scanned APs and if no interface is
   1737 		 * connected, then we have a reevaluation event.
   1738 		 */
   1739 		any_connected = one_matches = B_FALSE;
   1740 		for (wlan = wlans; wlan < wlans + wireless_lan_used;
   1741 		    wlan++) {
   1742 			/*
   1743 			 * If LLP is selected, then ignore all others.  Only
   1744 			 * the state of this one interface is at issue.
   1745 			 */
   1746 			if (ifname[0] != '\0' &&
   1747 			    strcmp(ifname, wlan->wl_if_name) != 0)
   1748 				continue;
   1749 			if (wlan->connected)
   1750 				any_connected = B_TRUE;
   1751 			if (strcmp(essid, wlan->essid) == 0 &&
   1752 			    (bssid[0] == '\0' ||
   1753 			    strcmp(bssid, wlan->bssid) == 0)) {
   1754 				one_matches = B_TRUE;
   1755 				savedwlan = wlan;
   1756 			}
   1757 		}
   1758 		if (!any_connected && one_matches) {
   1759 			(void) np_queue_add_event(EV_RESELECT,
   1760 			    savedwlan->wl_if_name);
   1761 		}
   1762 	}
   1763 	(void) pthread_mutex_unlock(&wifi_mutex);
   1764 	return (retv);
   1765 }
   1766 
   1767 int
   1768 delete_known_ap(const char *essid, const char *bssid)
   1769 {
   1770 	int retv;
   1771 	struct wireless_lan *wlan;
   1772 	wireless_if_t *wip;
   1773 
   1774 	if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0)
   1775 		return (retv);
   1776 
   1777 	retv = delete_known_wifi_nets_file(essid, bssid);
   1778 	if (retv == 0) {
   1779 		for (wlan = wlans; wlan < wlans + wireless_lan_used;
   1780 		    wlan++) {
   1781 			if (wlan->connected &&
   1782 			    strcmp(essid, wlan->essid) == 0 &&
   1783 			    (bssid[0] == '\0' ||
   1784 			    strcmp(bssid, wlan->bssid) == 0)) {
   1785 				wlan->connected = B_FALSE;
   1786 				report_wlan_disconnect(wlan);
   1787 				wip = find_wireless_if(wlan->wl_if_name);
   1788 				if (wip != NULL) {
   1789 					wip->wi_wireless_done = B_FALSE;
   1790 					wip->wi_need_key = B_FALSE;
   1791 					(void) dladm_wlan_disconnect(dld_handle,
   1792 					    wip->wi_linkid);
   1793 				}
   1794 				(void) np_queue_add_event(EV_RESELECT,
   1795 				    wlan->wl_if_name);
   1796 			}
   1797 		}
   1798 	}
   1799 	(void) pthread_mutex_unlock(&wifi_mutex);
   1800 	return (retv);
   1801 }
   1802 
   1803 /*
   1804  * reqlan->essid is required (i.e., cannot be zero-length)
   1805  * reqlan->bssid is optional (i.e., may be zero-length)
   1806  */
   1807 static return_vals_t
   1808 connect_chosen_lan(struct wireless_lan *reqlan, wireless_if_t *wip)
   1809 {
   1810 	uint_t	keycount;
   1811 	dladm_wlan_key_t *key;
   1812 	dladm_wlan_attr_t attr;
   1813 	dladm_status_t status;
   1814 	uint_t flags = DLADM_WLAN_CONNECT_NOSCAN;
   1815 	int timeout = DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT;
   1816 	char errmsg[DLADM_STRSIZE];
   1817 	return_vals_t rval;
   1818 
   1819 	wip->wi_need_key = B_FALSE;
   1820 
   1821 	(void) memset(&attr, 0, sizeof (attr));
   1822 	/* try to apply essid selected by the user */
   1823 	if (reqlan->essid == NULL)
   1824 		return (FAILURE);
   1825 	dprintf("connect_chosen_lan(%s, %s, %s)", reqlan->essid,
   1826 	    reqlan->bssid, wip->wi_name);
   1827 
   1828 	/* If it is already connected to the required AP, just return. */
   1829 	if (check_wlan(wip, reqlan->essid, NULL, B_TRUE))
   1830 		return (SUCCESS);
   1831 
   1832 	if (dladm_wlan_str2essid(reqlan->essid, &attr.wa_essid) !=
   1833 	    DLADM_STATUS_OK) {
   1834 		syslog(LOG_ERR,
   1835 		    "connect_chosen_lan: invalid ESSID '%s' for '%s'",
   1836 		    reqlan->essid, wip->wi_name);
   1837 		return (FAILURE);
   1838 	}
   1839 	attr.wa_valid = DLADM_WLAN_ATTR_ESSID;
   1840 
   1841 	/* note: bssid logic here is non-functional */
   1842 	if (reqlan->bssid[0] != '\0') {
   1843 		if (dladm_wlan_str2bssid(reqlan->bssid, &attr.wa_bssid) !=
   1844 		    DLADM_STATUS_OK) {
   1845 			syslog(LOG_ERR,
   1846 			    "connect_chosen_lan: invalid BSSID '%s' for '%s'",
   1847 			    reqlan->bssid, wip->wi_name);
   1848 			return (FAILURE);
   1849 		}
   1850 		attr.wa_valid |= DLADM_WLAN_ATTR_BSSID;
   1851 	}
   1852 
   1853 	/* First check for the key */
   1854 	if (NEED_ENC(reqlan->attrs.wa_secmode)) {
   1855 		/* Note that this happens only for known APs from the list */
   1856 		if ((rval = get_user_key(reqlan)) != SUCCESS) {
   1857 			if (rval == WAITING)
   1858 				wip->wi_need_key = B_TRUE;
   1859 			return (rval);
   1860 		}
   1861 		attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE;
   1862 		attr.wa_secmode = reqlan->attrs.wa_secmode;
   1863 		key = reqlan->cooked_key;
   1864 		keycount = 1;
   1865 		dprintf("connect_chosen_lan: retrieved key");
   1866 	} else {
   1867 		key = NULL;
   1868 		keycount = 0;
   1869 	}
   1870 
   1871 	/*
   1872 	 * Connect; only scan if a bssid was not specified.
   1873 	 * If it times out and we were trying with a bssid,
   1874 	 * try a second time with just the ESSID.
   1875 	 */
   1876 
   1877 	status = dladm_wlan_connect(dld_handle, wip->wi_linkid, &attr, timeout,
   1878 	    key, keycount, flags);
   1879 	dprintf("connect_chosen_lan: dladm_wlan_connect returned %s",
   1880 	    dladm_status2str(status, errmsg));
   1881 	/*
   1882 	 * This doesn't work due to CR 6772510.
   1883 	 */
   1884 #ifdef CR6772510_FIXED
   1885 	if (status == DLADM_STATUS_TIMEDOUT && reqlan->bssid[0] != '\0') {
   1886 		syslog(LOG_INFO, "connect_chosen_lan: failed for (%s, %s), "
   1887 		    "trying again with just (%s)",
   1888 		    reqlan->essid, reqlan->bssid, reqlan->essid);
   1889 		attr.wa_valid &= ~DLADM_WLAN_ATTR_BSSID;
   1890 		flags = 0;
   1891 		status = dladm_wlan_connect(dld_handle, wip->wi_linkid, &attr,
   1892 		    timeout, key, keycount, flags);
   1893 	}
   1894 #endif /* CR6772510_FIXED */
   1895 	if (status == DLADM_STATUS_OK) {
   1896 		return (SUCCESS);
   1897 	} else {
   1898 		syslog(LOG_ERR,
   1899 		    "connect_chosen_lan: connect to '%s' failed on '%s': %s",
   1900 		    reqlan->essid, wip->wi_name,
   1901 		    dladm_status2str(status, errmsg));
   1902 		return (FAILURE);
   1903 	}
   1904 }
   1905 
   1906 /*
   1907  * Check that the wireless LAN is connected to the desired ESSID/BSSID.  This
   1908  * is used by the GUI to check for connectivity before doing anything
   1909  * destructive.
   1910  */
   1911 boolean_t
   1912 check_wlan_connected(const char *ifname, const char *essid, const char *bssid)
   1913 {
   1914 	wireless_if_t *wip;
   1915 	boolean_t retv;
   1916 
   1917 	if (pthread_mutex_lock(&wifi_mutex) != 0)
   1918 		return (B_FALSE);
   1919 
   1920 	if ((wip = find_wireless_if(ifname)) == NULL) {
   1921 		retv = B_FALSE;
   1922 	} else {
   1923 		if (essid[0] == '\0' && bssid[0] == '\0')
   1924 			essid = NULL;
   1925 		retv = check_wlan(wip, essid, bssid, B_FALSE);
   1926 	}
   1927 	(void) pthread_mutex_unlock(&wifi_mutex);
   1928 	return (retv);
   1929 }
   1930 
   1931 /*
   1932  * This thread performs the blocking actions related to a wireless connection
   1933  * request.  The attempt to connect isn't started until all other connects and
   1934  * scans have finished, and while the connect is in progress, no new connects
   1935  * or scans can be started.
   1936  */
   1937 static void *
   1938 connect_thread(void *arg)
   1939 {
   1940 	struct wireless_lan *req_wlan = arg;
   1941 	wireless_if_t *wip;
   1942 	struct wireless_lan *wlan = NULL;
   1943 
   1944 	if (!scanconnect_entry(req_wlan->wl_if_name, B_TRUE))
   1945 		goto failure_noentry;
   1946 
   1947 	if (pthread_mutex_lock(&wifi_mutex) != 0)
   1948 		goto failure_unlocked;
   1949 
   1950 	if ((wip = find_wireless_if(req_wlan->wl_if_name)) == NULL)
   1951 		goto failure;
   1952 
   1953 	/* This is an autoconf request. */
   1954 	if (req_wlan->essid[0] == '\0' && req_wlan->bssid[0] == '\0') {
   1955 		if (!wlan_autoconf(wip) && !update_connected_wlan(wip, NULL))
   1956 			goto failure;
   1957 		else
   1958 			goto done;
   1959 	}
   1960 
   1961 	wlan = find_wlan_entry(req_wlan->wl_if_name, req_wlan->essid,
   1962 	    req_wlan->bssid);
   1963 	if (wlan == NULL)
   1964 		wlan = req_wlan;
   1965 
   1966 	/*
   1967 	 * now attempt to connect to selection
   1968 	 */
   1969 	switch (connect_chosen_lan(wlan, wip)) {
   1970 	case WAITING:
   1971 		break;
   1972 
   1973 	case SUCCESS: {
   1974 		dladm_status_t		status;
   1975 		dladm_wlan_linkattr_t	attr;
   1976 		char			lclssid[DLADM_STRSIZE];
   1977 		char			unnecessary_buf[DLADM_STRSIZE];
   1978 
   1979 		/*
   1980 		 * Successful connection to user-chosen AP; add entry to
   1981 		 * known_essid_list_file.  First make sure the wlan->bssid
   1982 		 * isn't empty.  Note that empty bssid is never allocated.
   1983 		 *
   1984 		 * We would like to query the driver only in the case where the
   1985 		 * BSSID is not known, but it turns out that due to CR 6772510,
   1986 		 * the actual BSSID we connect to is arbitrary.  Nothing we can
   1987 		 * do about that; just get the new value and live with it.
   1988 		 */
   1989 		status = dladm_wlan_get_linkattr(dld_handle, wip->wi_linkid,
   1990 		    &attr);
   1991 		if (status != DLADM_STATUS_OK) {
   1992 			dprintf("failed to get linkattr on %s after connecting "
   1993 			    "to %s: %s", wlan->wl_if_name, wlan->essid,
   1994 			    dladm_status2str(status, unnecessary_buf));
   1995 			goto failure;
   1996 		}
   1997 		(void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid,
   1998 		    lclssid);
   1999 		if (strcmp(req_wlan->essid, lclssid) != 0) {
   2000 			dprintf("connected to strange network: expected %s got "
   2001 			    "%s", req_wlan->essid, lclssid);
   2002 			goto failure;
   2003 		}
   2004 		(void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid,
   2005 		    lclssid);
   2006 		if (wlan == req_wlan || strcmp(wlan->bssid, lclssid) != 0) {
   2007 			wlan = add_wlan_entry(req_wlan->wl_if_name,
   2008 			    req_wlan->essid, lclssid, &attr.la_wlan_attr);
   2009 			if (wlan == NULL)
   2010 				goto failure;
   2011 		}
   2012 		if (wlan->bssid[0] == '\0' && lclssid[0] != '\0')
   2013 			wlan->bssid = strdup(lclssid);
   2014 		if (wlan->bssid == NULL || wlan->bssid[0] == '\0') {
   2015 			/* Don't leave it as NULL (for simplicity) */
   2016 			wlan->bssid = "";
   2017 			goto failure;
   2018 		}
   2019 		wlan->connected = B_TRUE;
   2020 		if (!update_connected_wlan(wip, wlan))
   2021 			goto failure;
   2022 		wlan->known = B_TRUE;
   2023 		(void) add_known_wifi_nets_file(wlan->essid, wlan->bssid);
   2024 		/* We're done; trigger IP bring-up. */
   2025 		(void) np_queue_add_event(EV_RESELECT, wlan->wl_if_name);
   2026 		report_wlan_connected(wlan);
   2027 		break;
   2028 	}
   2029 
   2030 	default:
   2031 		goto failure;
   2032 	}
   2033 
   2034 done:
   2035 	(void) pthread_mutex_unlock(&wifi_mutex);
   2036 	scanconnect_exit();
   2037 	free_wireless_lan(req_wlan);
   2038 	return (NULL);
   2039 
   2040 failure:
   2041 	/*
   2042 	 * Failed to connect.  Set 'rescan' flag so that we treat this AP as
   2043 	 * new if it's seen again, because the wireless radio may have just
   2044 	 * been off briefly while we were trying to connect.
   2045 	 */
   2046 	if (wip != NULL) {
   2047 		wip->wi_need_key = B_FALSE;
   2048 		wip->wi_wireless_done = B_FALSE;
   2049 		(void) dladm_wlan_disconnect(dld_handle, wip->wi_linkid);
   2050 	}
   2051 	if (wlan != NULL)
   2052 		wlan->rescan = B_TRUE;
   2053 	(void) pthread_mutex_unlock(&wifi_mutex);
   2054 
   2055 failure_unlocked:
   2056 	scanconnect_exit();
   2057 failure_noentry:
   2058 	syslog(LOG_WARNING, "could not connect to chosen WLAN %s on %s",
   2059 	    req_wlan->essid, req_wlan->wl_if_name);
   2060 	report_wlan_connect_fail(req_wlan->wl_if_name);
   2061 	free_wireless_lan(req_wlan);
   2062 	return (NULL);
   2063 }
   2064 
   2065 /*
   2066  * This is the entry point for GUI "select access point" requests.  It verifies
   2067  * the parameters and then launches a new thread to perform the connect
   2068  * operation.  When it returns success (0), the user should expect future
   2069  * events indicating progress.
   2070  *
   2071  * Returns:
   2072  *	0	- ok (or more data requested with new event)
   2073  *	ENXIO	- no such interface
   2074  *	ENODEV	- interface is not wireless
   2075  *	EINVAL	- failed to perform requested action
   2076  */
   2077 int
   2078 set_specific_lan(const char *ifname, const char *essid, const char *bssid)
   2079 {
   2080 	libnwam_interface_type_t ift;
   2081 	pthread_t conn_thr;
   2082 	pthread_attr_t attr;
   2083 	struct wireless_lan *wlan;
   2084 	int retv;
   2085 
   2086 	if ((ift = get_if_type(ifname)) == IF_UNKNOWN)
   2087 		return (ENXIO);
   2088 	if (ift != IF_WIRELESS)
   2089 		return (EINVAL);
   2090 
   2091 	if ((wlan = calloc(1, sizeof (struct wireless_lan))) == NULL)
   2092 		return (ENOMEM);
   2093 	(void) strlcpy(wlan->wl_if_name, ifname, sizeof (wlan->wl_if_name));
   2094 	wlan->essid = strdup(essid);
   2095 	wlan->bssid = *bssid == '\0' ? "" : strdup(bssid);
   2096 	if (wlan->essid == NULL || wlan->bssid == NULL) {
   2097 		free_wireless_lan(wlan);
   2098 		return (ENOMEM);
   2099 	}
   2100 	(void) pthread_attr_init(&attr);
   2101 	(void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
   2102 	retv = pthread_create(&conn_thr, &attr, connect_thread, wlan);
   2103 	if (retv == 0)
   2104 		dprintf("started connect thread %d for %s %s %s", conn_thr,
   2105 		    ifname, essid, bssid);
   2106 	else
   2107 		free_wireless_lan(wlan);
   2108 	return (retv);
   2109 }
   2110 
   2111 int
   2112 set_wlan_key(const char *ifname, const char *essid, const char *bssid,
   2113     const char *key, const char *secmode)
   2114 {
   2115 	libnwam_interface_type_t ift;
   2116 	struct wireless_lan *wlan, local_wlan;
   2117 	wireless_if_t *wip;
   2118 	int retv;
   2119 	boolean_t need_key;
   2120 	dladm_wlan_secmode_t smode = DLADM_WLAN_SECMODE_WEP;
   2121 
   2122 	ift = get_if_type(ifname);
   2123 	if (ift == IF_UNKNOWN)
   2124 		return (ENXIO);
   2125 	if (ift != IF_WIRELESS)
   2126 		return (EINVAL);
   2127 
   2128 	if (*secmode != '\0' &&
   2129 	    dladm_wlan_str2secmode(secmode, &smode) != DLADM_STATUS_OK)
   2130 		return (EINVAL);
   2131 
   2132 	if ((retv = pthread_mutex_lock(&wifi_mutex)) != 0)
   2133 		return (retv);
   2134 
   2135 	if ((wlan = find_wlan_entry(ifname, essid, bssid)) == NULL) {
   2136 		/* If not seen in scan, then secmode is required */
   2137 		if (*secmode == '\0') {
   2138 			retv = ENODEV;
   2139 			goto done;
   2140 		}
   2141 		/* Prohibit a completely blank entry */
   2142 		if (*essid == '\0' && *bssid == '\0') {
   2143 			retv = EINVAL;
   2144 			goto done;
   2145 		}
   2146 		(void) memset(&local_wlan, 0, sizeof (local_wlan));
   2147 		wlan = &local_wlan;
   2148 		(void) strlcpy(wlan->wl_if_name, ifname,
   2149 		    sizeof (wlan->wl_if_name));
   2150 		wlan->essid = (char *)essid;
   2151 		wlan->bssid = (char *)bssid;
   2152 		wlan->raw_key = (char *)key;
   2153 		wlan->attrs.wa_secmode = smode;
   2154 	} else {
   2155 		/* If seen in scan, then secmode given (if any) must match */
   2156 		if (*secmode != '\0' && smode != wlan->attrs.wa_secmode) {
   2157 			retv = EINVAL;
   2158 			goto done;
   2159 		}
   2160 		/* save a copy of the new key in the scan entry */
   2161 		if ((wlan->raw_key = strdup(key)) == NULL) {
   2162 			retv = ENOMEM;
   2163 			goto done;
   2164 		}
   2165 	}
   2166 
   2167 	if (store_key(wlan) != 0)
   2168 		retv = EINVAL;
   2169 	else
   2170 		retv = 0;
   2171 
   2172 done:
   2173 	wip = find_wireless_if(ifname);
   2174 	need_key = wip != NULL && wip->wi_need_key;
   2175 	(void) pthread_mutex_unlock(&wifi_mutex);
   2176 
   2177 	if (retv == 0 && need_key)
   2178 		retv = set_specific_lan(ifname, essid, bssid);
   2179 
   2180 	return (retv);
   2181 }
   2182 
   2183 static boolean_t
   2184 wlan_autoconf(const wireless_if_t *wip)
   2185 {
   2186 	dladm_status_t status;
   2187 	boolean_t autoconf;
   2188 
   2189 	if (lookup_boolean_property(OUR_PG, "autoconf", &autoconf) == 0) {
   2190 		if (!autoconf)
   2191 			return (B_FALSE);
   2192 	}
   2193 
   2194 	/* If the NIC is already associated with something, just return. */
   2195 	if (check_wlan(wip, NULL, NULL, B_TRUE))
   2196 		return (B_TRUE);
   2197 
   2198 	/*
   2199 	 * Do autoconf, relying on the heuristics used by dladm_wlan_connect()
   2200 	 * to cycle through WLANs detected in priority order, attempting
   2201 	 * to connect.
   2202 	 */
   2203 	status = dladm_wlan_connect(dld_handle, wip->wi_linkid, NULL,
   2204 	    DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, NULL, 0, 0);
   2205 	if (status != DLADM_STATUS_OK) {
   2206 		char errmsg[DLADM_STRSIZE];
   2207 
   2208 		syslog(LOG_ERR,
   2209 		    "wlan_autoconf: dladm_wlan_connect failed for '%s': %s",
   2210 		    wip->wi_name, dladm_status2str(status, errmsg));
   2211 		return (B_FALSE);
   2212 	}
   2213 	return (B_TRUE);
   2214 }
   2215 
   2216 /*
   2217  * This function searches through the wlans[] array and determines which ones
   2218  * have been visited before.
   2219  *
   2220  * If exactly one has been visited before, and it has the highest signal
   2221  * strength, then we attempt to connect to it right away.
   2222  *
   2223  * In all other cases -- if none have been visited before, or more than one was
   2224  * visited, or if the one that was visited doesn't have the highest signal
   2225  * strength, or if the automatic connect attempt fails for any reason -- then
   2226  * we hand over the data to the GUI for resolution.  The user will have to be
   2227  * prompted for a choice.
   2228  *
   2229  * If no GUI exists, we'll get back FAILURE (instead of WAITING), which will
   2230  * cause the autoconf mechanism to run instead.
   2231  */
   2232 return_vals_t
   2233 handle_wireless_lan(const char *ifname)
   2234 {
   2235 	wireless_if_t *wip;
   2236 	struct wireless_lan *cur_wlan, *max_wlan, *strong_wlan = NULL;
   2237 	struct wireless_lan *most_recent;
   2238 	boolean_t many_present;
   2239 	dladm_wlan_strength_t strongest = DLADM_WLAN_STRENGTH_VERY_WEAK;
   2240 	return_vals_t connect_result = FAILURE;
   2241 
   2242 	/*
   2243 	 * We wait while a scan or another connect is in progress, and then
   2244 	 * block other connects/scans.  Since we allow a user to initiate a
   2245 	 * re-scan, we can proceed even when no scan has yet been done to fill
   2246 	 * in the AP list.
   2247 	 */
   2248 	if (!scanconnect_entry(ifname, B_TRUE))
   2249 		return (FAILURE);
   2250 
   2251 	if (pthread_mutex_lock(&wifi_mutex) != 0) {
   2252 		scanconnect_exit();
   2253 		return (FAILURE);
   2254 	}
   2255 
   2256 	if ((wip = find_wireless_if(ifname)) == NULL)
   2257 		goto finished;
   2258 
   2259 	if (wip->wi_wireless_done) {
   2260 		dprintf("handle_wireless_lan: skipping policy scan; done");
   2261 		/* special case; avoid interface update */
   2262 		(void) pthread_mutex_unlock(&wifi_mutex);
   2263 		scanconnect_exit();
   2264 		return (SUCCESS);
   2265 	}
   2266 
   2267 	dprintf("handle_wireless_lan: starting policy scan");
   2268 	cur_wlan = wlans;
   2269 	max_wlan = wlans + wireless_lan_used;
   2270 	most_recent = NULL;
   2271 	many_present = B_FALSE;
   2272 
   2273 	/*
   2274 	 * Try to see if any of the wifi nets currently available
   2275 	 * has been used previously. If more than one available
   2276 	 * nets has been used before, then prompt user with
   2277 	 * all the applicable previously wifi nets, and ask which
   2278 	 * one to connect to.
   2279 	 */
   2280 	for (; cur_wlan < max_wlan; cur_wlan++) {
   2281 		/* Find the AP with the highest signal. */
   2282 		if (cur_wlan->attrs.wa_strength > strongest) {
   2283 			strongest = cur_wlan->attrs.wa_strength;
   2284 			strong_wlan = cur_wlan;
   2285 		}
   2286 
   2287 		if (known_wifi_nets_lookup(cur_wlan->essid, cur_wlan->bssid,
   2288 		    NULL))
   2289 			cur_wlan->known = B_TRUE;
   2290 
   2291 		if (!cur_wlan->known && !strict_bssid &&
   2292 		    known_wifi_nets_lookup(cur_wlan->essid, "", NULL)) {
   2293 			dprintf("noticed new BSSID %s for ESSID %s on %s",
   2294 			    cur_wlan->bssid, cur_wlan->essid, ifname);
   2295 			if (add_known_wifi_nets_file(cur_wlan->essid,
   2296 			    cur_wlan->bssid) == 0)
   2297 				cur_wlan->known = B_TRUE;
   2298 		}
   2299 
   2300 		if (cur_wlan->known || cur_wlan->connected) {
   2301 			/*
   2302 			 * The ESSID comparison here mimics what the "already
   2303 			 * in visited wlan list" function once did, but
   2304 			 * slightly better as we also pay attention to signal
   2305 			 * strength to pick the best of the duplicates.
   2306 			 */
   2307 			if (most_recent == NULL) {
   2308 				most_recent = cur_wlan;
   2309 			} else if (strcmp(cur_wlan->essid,
   2310 			    most_recent->essid) != 0) {
   2311 				if (!many_present)
   2312 					dprintf("both %s and %s are known and "
   2313 					    "present on %s", cur_wlan->essid,
   2314 					    most_recent->essid, ifname);
   2315 				many_present = B_TRUE;
   2316 			} else if (cur_wlan->attrs.wa_strength >
   2317 			    most_recent->attrs.wa_strength) {
   2318 				if (most_recent->connected) {
   2319 					dprintf("found better BSS %s for ESS "
   2320 					    "%s; disconnecting %s on %s",
   2321 					    cur_wlan->bssid, cur_wlan->essid,
   2322 					    most_recent->bssid, ifname);
   2323 					(void) dladm_wlan_disconnect(dld_handle,
   2324 					    wip->wi_linkid);
   2325 					most_recent->connected = B_FALSE;
   2326 					report_wlan_disconnect(most_recent);
   2327 					wip->wi_wireless_done = B_FALSE;
   2328 				}
   2329 				most_recent = cur_wlan;
   2330 			} else if (cur_wlan->attrs.wa_strength <
   2331 			    most_recent->attrs.wa_strength &&
   2332 			    cur_wlan->connected) {
   2333 				dprintf("found better BSS %s for ESS %s; "
   2334 				    "disconnecting %s on %s",
   2335 				    most_recent->bssid, most_recent->essid,
   2336 				    cur_wlan->bssid, ifname);
   2337 				(void) dladm_wlan_disconnect(dld_handle,
   2338 				    wip->wi_linkid);
   2339 				cur_wlan->connected = B_FALSE;
   2340 				report_wlan_disconnect(cur_wlan);
   2341 				wip->wi_wireless_done = B_FALSE;
   2342 			}
   2343 		}
   2344 
   2345 		/* Reset any security information we may have had. */
   2346 		free(cur_wlan->raw_key);
   2347 		cur_wlan->raw_key = NULL;
   2348 		free(cur_wlan->cooked_key);
   2349 		cur_wlan->cooked_key = NULL;
   2350 	}
   2351 
   2352 	/*
   2353 	 * The Three Rules:
   2354 	 *
   2355 	 *	1.  If no known AP is in range, then seek help.
   2356 	 *
   2357 	 *	2.  If two or more known APs are in range, then seek help.
   2358 	 *
   2359 	 *	3.  If a known AP is in range, and its signal strength is "weak"
   2360 	 *	    or lower, and the strongest available is "very good" or
   2361 	 *	    better, then seek help.
   2362 	 */
   2363 	if (most_recent != NULL && (!many_present || most_recent->connected) &&
   2364 	    (most_recent->attrs.wa_strength > DLADM_WLAN_STRENGTH_WEAK ||
   2365 	    strongest < DLADM_WLAN_STRENGTH_VERY_GOOD)) {
   2366 		if (most_recent->connected) {
   2367 			dprintf("%s already connected to %s", ifname,
   2368 			    most_recent->essid);
   2369 			connect_result = SUCCESS;
   2370 		} else {
   2371 			dprintf("%s connecting automatically to %s", ifname,
   2372 			    most_recent->essid);
   2373 			connect_result = connect_chosen_lan(most_recent, wip);
   2374 			switch (connect_result) {
   2375 			case FAILURE:
   2376 				report_wlan_connect_fail(wip->wi_name);
   2377 				most_recent->rescan = B_TRUE;
   2378 				syslog(LOG_WARNING, "could not connect to "
   2379 				    "chosen WLAN %s on %s, going to auto-conf",
   2380 				    most_recent->essid, ifname);
   2381 				connect_result = wlan_autoconf(wip) ? SUCCESS :
   2382 				    FAILURE;
   2383 				most_recent = NULL;
   2384 				break;
   2385 			case SUCCESS:
   2386 				most_recent->connected = B_TRUE;
   2387 				report_wlan_connected(most_recent);
   2388 				break;
   2389 			}
   2390 		}
   2391 	} else if (request_wlan_selection(ifname, wlans, wireless_lan_used)) {
   2392 		if (most_recent == NULL)
   2393 			dprintf("%s has no known WLANs; requested help",
   2394 			    ifname);
   2395 		else if (many_present && !most_recent->connected)
   2396 			dprintf("%s has multiple known WLANs and is not "
   2397 			    "connected; requested help", ifname);
   2398 		else
   2399 			dprintf("%s has known WLAN %s, but not strongest %s; "
   2400 			    "requested help", ifname, most_recent->essid,
   2401 			    strong_wlan->essid);
   2402 		connect_result = WAITING;
   2403 	} else {
   2404 		dprintf("%s has no connected AP or GUI; try auto", ifname);
   2405 		connect_result = wlan_autoconf(wip) ? SUCCESS : FAILURE;
   2406 		most_recent = NULL;
   2407 	}
   2408 
   2409 finished:
   2410 	if (connect_result == SUCCESS &&
   2411 	    !update_connected_wlan(wip, most_recent))
   2412 		connect_result = FAILURE;
   2413 	(void) pthread_mutex_unlock(&wifi_mutex);
   2414 	scanconnect_exit();
   2415 
   2416 	return (connect_result);
   2417 }
   2418 
   2419 void
   2420 disconnect_wlan(const char *ifname)
   2421 {
   2422 	wireless_if_t *wip;
   2423 	struct wireless_lan *wlan;
   2424 
   2425 	if (pthread_mutex_lock(&wifi_mutex) == 0) {
   2426 		if ((wip = find_wireless_if(ifname)) != NULL) {
   2427 			wip->wi_wireless_done = B_FALSE;
   2428 			wip->wi_need_key = B_FALSE;
   2429 			(void) dladm_wlan_disconnect(dld_handle,
   2430 			    wip->wi_linkid);
   2431 		}
   2432 		for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) {
   2433 			if (strcmp(ifname, wlan->wl_if_name) == 0 &&
   2434 			    wlan->connected) {
   2435 				wlan->connected = B_FALSE;
   2436 				report_wlan_disconnect(wlan);
   2437 			}
   2438 		}
   2439 		(void) pthread_mutex_unlock(&wifi_mutex);
   2440 	}
   2441 }
   2442 
   2443 void
   2444 get_wireless_state(const char *ifname, boolean_t *need_wlan,
   2445     boolean_t *need_key)
   2446 {
   2447 	wireless_if_t *wip;
   2448 
   2449 	*need_wlan = *need_key = B_FALSE;
   2450 	if (pthread_mutex_lock(&wifi_mutex) == 0) {
   2451 		if ((wip = find_wireless_if(ifname)) != NULL) {
   2452 			*need_key = wip->wi_need_key;
   2453 			if (!wip->wi_need_key && !wip->wi_wireless_done)
   2454 				*need_wlan = B_TRUE;
   2455 		}
   2456 		(void) pthread_mutex_unlock(&wifi_mutex);
   2457 	}
   2458 }
   2459 
   2460 void
   2461 print_wireless_status(void)
   2462 {
   2463 	wireless_if_t *wip;
   2464 	struct wireless_lan *wlan;
   2465 	char strength[DLADM_STRSIZE];
   2466 
   2467 	if (pthread_mutex_lock(&wifi_mutex) == 0) {
   2468 		for (wip = (wireless_if_t *)wi_list.q_forw;
   2469 		    wip != (wireless_if_t *)&wi_list;
   2470 		    wip = (wireless_if_t *)wip->wi_links.q_forw) {
   2471 			(void) dladm_wlan_strength2str(&wip->wi_strength,
   2472 			    strength);
   2473 			dprintf("WIF %s linkid %d scan %srunning "
   2474 			    "wireless %sdone %sneed key strength %s",
   2475 			    wip->wi_name, wip->wi_linkid,
   2476 			    wip->wi_scan_running ? "" : "not ",
   2477 			    wip->wi_wireless_done ? "" : "not ",
   2478 			    wip->wi_need_key ? "" : "don't ",
   2479 			    strength);
   2480 		}
   2481 		for (wlan = wlans; wlan < wlans + wireless_lan_used; wlan++) {
   2482 			dprintf("WLAN I/F %s ESS %s BSS %s signal %s key %sset "
   2483 			    "%sknown %sconnected %sscanned",
   2484 			    wlan->wl_if_name, wlan->essid, wlan->bssid,
   2485 			    wlan->signal_strength,
   2486 			    wlan->raw_key == NULL ? "un" : "",
   2487 			    wlan->known ? "" : "not ",
   2488 			    wlan->connected ? "" : "not ",
   2489 			    wlan->scanned ? "" : "not ");
   2490 		}
   2491 		(void) pthread_mutex_unlock(&wifi_mutex);
   2492 	}
   2493 }
   2494