summaryrefslogtreecommitdiff
path: root/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/cmd-inet/lib/nwamd/wireless.c')
-rw-r--r--usr/src/cmd/cmd-inet/lib/nwamd/wireless.c1596
1 files changed, 1596 insertions, 0 deletions
diff --git a/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c
new file mode 100644
index 0000000000..72e6708f10
--- /dev/null
+++ b/usr/src/cmd/cmd-inet/lib/nwamd/wireless.c
@@ -0,0 +1,1596 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+/*
+ * This file containes all the routines to handle wireless (more
+ * accurately, 802.11 "WiFi" family only at this moment) operations.
+ * This is only phase 0 work so the handling is pretty simple.
+ *
+ * When the daemon starts up, for each WiFi interface detected, it'll
+ * spawn a thread doing an access point (AP) scanning. After the scans
+ * finish and if one of the WiFi interfaces is chosen to be active, the
+ * code will pop up a window showing the scan results and wait for the
+ * user's input on which AP to connect to and then complete the AP
+ * connection and IP interface set up. WEP is supported to connect to
+ * those APs which require it. The code also maintains a list of known
+ * WiFi APs in the file KNOWN_WIFI_NETS. Whenever the code successfully
+ * connects to an AP, the AP's ESSID/BSSID will be added to that file.
+ * This file is used in the following way.
+ *
+ * If the AP scan results contain one known AP, the code will automatically
+ * connect to that AP without asking the user. But if the detected signal
+ * strength of that AP is weaker than some other AP's, the code will still
+ * pop up a window asking for user input.
+ *
+ * If the AP scan results contain more than one known APs, the code will
+ * pop up a window listing those known APs only. If the user does not
+ * make a choice, the full list of available APs will be shown.
+ *
+ * If the AP scan results contain no known AP, the full list of available
+ * APs is shown and the user is asked which AP to connect to.
+ *
+ * Note that not all APs broadcast the Beacon. And some events may
+ * happen during the AP scan such that not all available APs are found.
+ * So the code also allows a user to manually input an AP's data in the
+ * pop up window. This allows a user to connect to the aforementioned
+ * "hidden" APs.
+ *
+ * The code also periodically (specified by wlan_scan_interval) checks
+ * for the health of the AP connection. If the signal strength of the
+ * connected AP drops below a threshold (specified by wireless_scan_level),
+ * the code will try to do another scan to find out other APs available.
+ * If there is currently no connected AP, a scan will also be done
+ * periodically to look for available APs. In both cases, if there are
+ * new APs, the above AP connection procedure will be performed.
+ *
+ * One limitation of the current code is that a user cannot initiate a
+ * WiFi APs scan manually. A manual scan can only be done when the code
+ * shows a pop up window asking for user input. Suppose there is no
+ * connected AP and periodic scan is going on. If a user wants to
+ * connect to a hidden AP, this is only possible if there is another
+ * non-hidden AP available such that after a periodic scan, the pop up
+ * window is shown. This will be fixed in a later phase.
+ */
+
+#include <unistd.h>
+#include <ctype.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <syslog.h>
+#include <assert.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <syslog.h>
+#include <pthread.h>
+#include <sys/wait.h>
+#include <stropts.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <libintl.h>
+#include <libdladm.h>
+#include <libdllink.h>
+#include <libinetutil.h>
+
+#include "defines.h"
+#include "structures.h"
+#include "functions.h"
+#include "variables.h"
+
+static pthread_mutex_t wifi_mutex;
+static pthread_mutexattr_t wifi_mutex_attr;
+static pthread_mutex_t wifi_init_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t wifi_init_cond = PTHREAD_COND_INITIALIZER;
+
+typedef enum {
+ SUCCESS = 0,
+ FAILURE,
+ TRY_AGAIN
+} return_vals_t;
+
+/*
+ * Is a wireless interface doing a scan currently? We only allow one
+ * wireless interface to do a scan at any one time. This is to
+ * avoid unnecessary interference. The following variable is used
+ * to store the interface doing the scan. It is protected by
+ * wifi_init_mutex.
+ */
+static struct interface *wifi_scan_intf = NULL;
+
+/* used entries have non NULL memebers */
+static struct wireless_lan *wlans = NULL;
+static uint_t wireless_lan_count = 0; /* allocated */
+static uint_t wireless_lan_used = 0; /* used entries */
+
+static int wepkey_string_to_secobj_value(char *, uint8_t *, uint_t *);
+static int store_wepkey(char *, char *, char *);
+static dladm_wlan_wepkey_t *retrieve_wepkey(const char *, const char *);
+
+static boolean_t add_wlan_entry(struct interface *, char *, char *, char *,
+ boolean_t);
+static boolean_t already_in_visited_wlan_list(const struct wireless_lan *);
+static boolean_t check_wlan(const char *, const char *);
+static boolean_t connect_or_autoconf(struct wireless_lan *, const char *);
+static return_vals_t connect_to_new_wlan(const struct wireless_lan *, int,
+ const char *);
+static boolean_t find_wlan_entry(struct interface *, char *, char *);
+static void free_wireless_lan(struct wireless_lan *);
+static struct wireless_lan *get_specific_lan(void);
+static void get_user_wepkey(struct wireless_lan *);
+static char *get_zenity_response(const char *);
+static boolean_t wlan_autoconf(const char *ifname);
+static int zenity_height(int);
+static boolean_t get_scan_results(void *, dladm_wlan_attr_t *);
+
+#define WIRELESS_LAN_INIT_COUNT 8
+
+struct visited_wlans_list *visited_wlan_list = NULL;
+
+/*
+ * The variable wlan_scan_interval controls the interval in seconds
+ * between periodic scans.
+ */
+uint_t wlan_scan_interval = 120;
+
+/*
+ * The variable wireless_scan_level specifies the lowest signal level
+ * when a periodic wireless scan needs to be done.
+ */
+dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK;
+
+void
+init_mutexes(void)
+{
+ (void) pthread_mutexattr_init(&wifi_mutex_attr);
+ (void) pthread_mutexattr_settype(&wifi_mutex_attr,
+ PTHREAD_MUTEX_RECURSIVE);
+ (void) pthread_mutex_init(&wifi_mutex, &wifi_mutex_attr);
+}
+
+/*
+ * wlan is expected to be non-NULL.
+ */
+static void
+get_user_wepkey(struct wireless_lan *wlan)
+{
+ char zenity_cmd[1024];
+ char buf[1024];
+ FILE *zcptr;
+
+ /*
+ * First, test if we have wepkey stored as secobj. If so,
+ * no need to prompt for it.
+ */
+ wlan->cooked_wepkey = retrieve_wepkey(wlan->essid, wlan->bssid);
+ if (wlan->cooked_wepkey != NULL) {
+ dprintf("get_user_wepkey: retrieve_wepkey() returns non NULL");
+ return;
+ }
+
+ (void) snprintf(zenity_cmd, sizeof (zenity_cmd),
+ "%s --entry --text=\"%s %s\""
+ " --title=\"%s\" --hide-text", ZENITY,
+ gettext("Enter WEP key for WiFi network"), wlan->essid,
+ gettext("Enter WEP key"));
+
+ if (!valid_graphical_user(B_TRUE))
+ return;
+
+ zcptr = popen(zenity_cmd, "r");
+ if (zcptr != NULL) {
+ if (fgets(buf, sizeof (buf), zcptr) != NULL) {
+ wlan->raw_wepkey = strdup(buf);
+ if (wlan->raw_wepkey != NULL) {
+ /* Store WEP key persistently */
+ if (store_wepkey(wlan->essid, wlan->bssid,
+ wlan->raw_wepkey) != 0) {
+ syslog(LOG_ERR,
+ "get_user_wepkey: failed to store"
+ " user specified WEP key");
+ }
+ } else {
+ syslog(LOG_ERR,
+ "get_user_wepkey: strdup failed");
+ }
+ }
+ (void) pclose(zcptr);
+ } else {
+ syslog(LOG_ERR, "Could not run %s: %m", ZENITY);
+ }
+}
+
+static boolean_t
+find_wlan_entry(struct interface *intf, char *essid, char *bssid)
+{
+ int i;
+
+ (void) pthread_mutex_lock(&wifi_mutex);
+ /* Check if the new entry is already there. */
+ for (i = 0; i < wireless_lan_used; i++) {
+ /*
+ * Assume that essid and bssid are already NULL terminated.
+ * Note that we also check for the interface name here.
+ * If there is only one wireless interface, it should not
+ * matter. But if there are more than 1, then it is safer
+ * to use the interface which finds the AP to connect to
+ * it.
+ */
+ if (strcmp(wlans[i].essid, essid) == 0 &&
+ strcmp(wlans[i].bssid, bssid) == 0 &&
+ strcmp(wlans[i].wl_if_name, intf->if_name) == 0) {
+ (void) pthread_mutex_unlock(&wifi_mutex);
+ return (B_TRUE);
+ }
+ }
+ (void) pthread_mutex_unlock(&wifi_mutex);
+ return (B_FALSE);
+}
+
+static void
+free_wireless_lan(struct wireless_lan *wlp)
+{
+ free(wlp->essid);
+ wlp->essid = NULL;
+ free(wlp->bssid);
+ wlp->bssid = NULL;
+ free(wlp->signal_strength);
+ wlp->signal_strength = NULL;
+ free(wlp->raw_wepkey);
+ wlp->raw_wepkey = NULL;
+ free(wlp->cooked_wepkey);
+ wlp->cooked_wepkey = NULL;
+ free(wlp->wl_if_name);
+ wlp->wl_if_name = NULL;
+}
+
+static boolean_t
+add_wlan_entry(struct interface *intf, char *essid, char *bssid,
+ char *signal_strength, boolean_t wep)
+{
+ int n;
+
+ (void) pthread_mutex_lock(&wifi_mutex);
+
+ if (wireless_lan_used == wireless_lan_count) {
+ int newcnt;
+ struct wireless_lan *r;
+
+ newcnt = (wireless_lan_count == 0) ?
+ WIRELESS_LAN_INIT_COUNT : wireless_lan_count * 2;
+ r = realloc(wlans, newcnt * sizeof (*wlans));
+ if (r == NULL) {
+ syslog(LOG_ERR, "add_wlan_entry: realloc failed");
+ (void) pthread_mutex_unlock(&wifi_mutex);
+ return (B_FALSE);
+ }
+ (void) memset((void *)(r + wireless_lan_count), 0,
+ (newcnt - wireless_lan_count) * sizeof (*r));
+ wireless_lan_count = newcnt;
+ wlans = r;
+ }
+
+ n = wireless_lan_used;
+ wlans[n].essid = strdup(essid);
+ wlans[n].bssid = strdup(bssid);
+ wlans[n].signal_strength = strdup(signal_strength);
+ wlans[n].wl_if_name = strdup(intf->if_name);
+ wlans[n].need_wepkey = wep;
+ wlans[n].raw_wepkey = NULL;
+ wlans[n].cooked_wepkey = NULL;
+ if (wlans[n].essid == NULL || wlans[n].bssid == NULL ||
+ wlans[n].signal_strength == NULL || wlans[n].wl_if_name == NULL) {
+ syslog(LOG_ERR, "add_wlan_entry: strdup failed");
+ free_wireless_lan(&(wlans[n]));
+ (void) pthread_mutex_unlock(&wifi_mutex);
+ return (B_FALSE);
+ }
+ wireless_lan_used++;
+ (void) pthread_mutex_unlock(&wifi_mutex);
+ return (B_TRUE);
+}
+
+static void
+clear_lan_entries(void)
+{
+ int i;
+
+ (void) pthread_mutex_lock(&wifi_mutex);
+ for (i = 0; i < wireless_lan_used; i++)
+ free_wireless_lan(&(wlans[i]));
+ wireless_lan_used = 0;
+ (void) pthread_mutex_unlock(&wifi_mutex);
+}
+
+/*
+ * Verify if a WiFi NIC is associated with the given ESSID. If the given
+ * ESSID is NULL, and if the NIC is already connected, return true.
+ * Otherwise,
+ *
+ * 1. If the NIC is associated with the given ESSID, return true.
+ * 2. If the NIC is not associated with any AP, return false.
+ * 3. If the NIC is associated with a different AP, tell the driver
+ * to disassociate with it and then return false.
+ */
+static boolean_t
+check_wlan(const char *intf, const char *exp_essid)
+{
+ dladm_wlan_linkattr_t attr;
+ dladm_status_t status;
+ char cur_essid[DLADM_STRSIZE];
+ char errmsg[DLADM_STRSIZE];
+
+ status = dladm_wlan_get_linkattr(intf, &attr);
+ if (status != DLADM_STATUS_OK) {
+ dprintf("check_wlan: dladm_wlan_get_linkattr() failed: %s",
+ dladm_status2str(status, errmsg));
+ return (B_FALSE);
+ }
+ if (attr.la_status == DLADM_WLAN_LINKSTATUS_DISCONNECTED)
+ return (B_FALSE);
+ if (exp_essid == NULL)
+ return (B_TRUE);
+ (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid);
+
+ /* Is the NIC associated with the expected one? */
+ if (strcmp(cur_essid, exp_essid) == 0)
+ return (B_TRUE);
+
+ /* Tell the driver to disassociate with the current AP. */
+ if (dladm_wlan_disconnect(intf) != DLADM_STATUS_OK)
+ dprintf("check_wlan: dladm_wlan_disconnect() fails");
+ return (B_FALSE);
+}
+
+/*
+ * Given a wireless interface, use it to scan for available networks.
+ */
+boolean_t
+scan_wireless_nets(struct interface *intf)
+{
+ boolean_t new_ap = B_FALSE;
+ dladm_status_t status;
+ int num_ap;
+
+ assert(intf->if_type == IF_WIRELESS);
+ /*
+ * If there is already a scan in progress, wait until the
+ * scan is done to avoid interference. But if the interface
+ * doing the scan is the same as the one requesting the new
+ * scan, just return.
+ *
+ * Whenever a wireless scan is in progress, all the other
+ * threads checking the wireless AP list should wait.
+ */
+ (void) pthread_mutex_lock(&wifi_init_mutex);
+ while (wifi_scan_intf != NULL) {
+ dprintf("scan_wireless_nets in progress: old %s new %s",
+ wifi_scan_intf->if_name, intf->if_name);
+ if (strcmp(wifi_scan_intf->if_name, intf->if_name) == 0) {
+ (void) pthread_mutex_unlock(&wifi_init_mutex);
+ return (B_FALSE);
+ }
+ (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex);
+ }
+ wifi_scan_intf = intf;
+ (void) pthread_mutex_unlock(&wifi_init_mutex);
+
+ /*
+ * Since only one scan is allowed at any one time, no need to grab
+ * a lock in checking wireless_lan_used.
+ */
+ num_ap = wireless_lan_used;
+ status = dladm_wlan_scan(intf->if_name, intf, get_scan_results);
+ if (status != DLADM_STATUS_OK)
+ syslog(LOG_NOTICE, "cannot scan link '%s'", intf->if_name);
+ else
+ new_ap = (wireless_lan_used > num_ap);
+
+ (void) pthread_mutex_lock(&wifi_init_mutex);
+ wifi_scan_intf = NULL;
+ (void) pthread_cond_signal(&wifi_init_cond);
+ (void) pthread_mutex_unlock(&wifi_init_mutex);
+
+ return (new_ap);
+}
+
+/* ARGSUSED */
+static void
+wireless_scan(struct interface *ifp, void *arg)
+{
+ if (ifp->if_type == IF_WIRELESS) {
+ dprintf("periodic_wireless_scan: %s", ifp->if_name);
+ if (scan_wireless_nets(ifp)) {
+ dprintf("new AP added: %s", ifp->if_name);
+ gen_newif_event(ifp);
+ }
+ }
+}
+
+static boolean_t
+get_scan_results(void *arg, dladm_wlan_attr_t *attrp)
+{
+
+ boolean_t wep;
+ char essid_name[DLADM_STRSIZE];
+ char bssid_name[DLADM_STRSIZE];
+ char strength[DLADM_STRSIZE];
+
+ (void) dladm_wlan_essid2str(&attrp->wa_essid, essid_name);
+ (void) dladm_wlan_bssid2str(&attrp->wa_bssid, bssid_name);
+ (void) dladm_wlan_strength2str(&attrp->wa_strength, strength);
+
+ wep = (attrp->wa_secmode == DLADM_WLAN_SECMODE_WEP);
+
+ if (!find_wlan_entry(arg, essid_name, bssid_name) &&
+ add_wlan_entry(arg, essid_name, bssid_name, strength, wep)) {
+ return (B_TRUE);
+ }
+ return (B_FALSE);
+}
+
+/* ARGSUSED */
+void *
+periodic_wireless_scan(void *arg)
+{
+ /*
+ * No periodic scan if the "-i" option is used to change the
+ * interval to 0.
+ */
+ if (wlan_scan_interval == 0)
+ return (NULL);
+
+ for (;;) {
+ int ret;
+ dladm_wlan_linkattr_t attr;
+ llp_t *cur_llp;
+ struct interface *ifp;
+
+ ret = poll(NULL, 0, wlan_scan_interval * MILLISEC);
+
+ /*
+ * We assume that once an llp is created, it will never be
+ * deleted in the lifetime of the process. So it is OK
+ * to do this assignment without a lock.
+ */
+ cur_llp = link_layer_profile;
+
+ /*
+ * We do a scan if
+ *
+ * 1. There is no active profile. Or
+ * 2. We are now disconnected from the AP. Or
+ * 3. The signal strength falls below a certain specified level.
+ */
+ if (ret == 0) {
+ if (cur_llp != NULL) {
+ if (cur_llp->llp_type != IF_WIRELESS ||
+ dladm_wlan_get_linkattr(cur_llp->llp_lname,
+ &attr) != DLADM_STATUS_OK) {
+ continue;
+ }
+ if (attr.la_status ==
+ DLADM_WLAN_LINKSTATUS_CONNECTED &&
+ attr.la_wlan_attr.wa_strength >
+ wireless_scan_level) {
+ continue;
+ }
+ /*
+ * Clear the IF_DHCPFAILED and IF_DHCPSTARTED
+ * flags on this interface; this is a "fresh
+ * start" for the interface, so we should
+ * retry dhcp.
+ */
+ ifp = get_interface(cur_llp->llp_lname);
+ if (ifp != NULL) {
+ ifp->if_lflags &= ~IF_DHCPFAILED;
+ ifp->if_lflags &= ~IF_DHCPSTARTED;
+ }
+ /*
+ * Deactivate the original llp.
+ * If we reached this point, we either were
+ * not connected, or were connected with
+ * "very weak" signal strength; so we're
+ * assuming that having this llp active was
+ * not very useful. So we deactivate.
+ */
+ llp_deactivate();
+ }
+ /* We should start from fresh. */
+ clear_lan_entries();
+ walk_interface(wireless_scan, NULL);
+ } else if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ syslog(LOG_INFO, "periodic_wireless_scan: poll failed");
+ return (NULL);
+ }
+ }
+ /* NOTREACHED */
+ return (NULL);
+}
+
+/*
+ * Below are functions used to handle storage/retrieval of WEP keys
+ * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj()
+ * and dladm_get_secobj().
+ */
+
+/*
+ * Convert wepkey hexascii string to raw secobj value. This
+ * code is very similar to convert_secobj() in dladm.c, it would
+ * be good to have a libdladm function to convert values.
+ */
+static int
+wepkey_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp)
+{
+ size_t buf_len = strlen(buf);
+
+ dprintf("before: wepkey_string_to_secobj_value: buf_len = %d", buf_len);
+ if (buf_len == 0) {
+ syslog(LOG_ERR,
+ "wepkey_string_to_secobj_value: empty WEP key");
+ return (-1);
+ }
+
+ if (buf[buf_len - 1] == '\n')
+ buf[--buf_len] = '\0';
+
+ dprintf("after: wepkey_string_to_secobj_value: buf_len = %d", buf_len);
+ switch (buf_len) {
+ case 5: /* ASCII key sizes */
+ case 13:
+ (void) memcpy(obj_val, buf, (uint_t)buf_len);
+ *obj_lenp = (uint_t)buf_len;
+ break;
+ case 10:
+ case 26: /* Hex key sizes, not preceded by 0x */
+ if (hexascii_to_octet(buf, (uint_t)buf_len, obj_val, obj_lenp)
+ != 0) {
+ syslog(LOG_ERR,
+ "wepkey_string_to_secobj_value: invalid WEP key");
+ return (-1);
+ }
+ break;
+ case 12:
+ case 28: /* Hex key sizes, preceded by 0x */
+ if (strncmp(buf, "0x", 2) != 0 ||
+ hexascii_to_octet(buf + 2, (uint_t)buf_len - 2, obj_val,
+ obj_lenp) != 0) {
+ syslog(LOG_ERR,
+ "wepkey_string_to_secobj_value: invalid WEP key");
+ return (-1);
+ }
+ break;
+ default:
+ syslog(LOG_ERR,
+ "wepkey_string_to_secobj_value: invalid WEP key length");
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * Print the key format into the appropriate field, then convert any ":"
+ * characters to ".", as ":[1-4]" is the slot indicator, which otherwise
+ * would trip us up. The third parameter is expected to be of size
+ * DLADM_SECOBJ_NAME_MAX.
+ */
+static void
+set_key_name(const char *essid, const char *bssid, char *name, size_t nsz)
+{
+ int i;
+
+ if (bssid == NULL)
+ (void) snprintf(name, nsz, "nwam-%s", essid);
+ else
+ (void) snprintf(name, nsz, "nwam-%s-%s", essid, bssid);
+ for (i = 0; i < strlen(name); i++)
+ if (name[i] == ':')
+ name[i] = '.';
+}
+
+static int
+store_wepkey(char *essid, char *bssid, char *raw_wepkey)
+{
+ uint8_t obj_val[DLADM_SECOBJ_VAL_MAX];
+ uint_t obj_len;
+ char obj_name[DLADM_SECOBJ_NAME_MAX];
+ dladm_status_t status;
+ char errmsg[DLADM_STRSIZE];
+
+ /*
+ * Name wepkey object for this WLAN so it can be later retrieved
+ * (name is unique for each ESSID/BSSID combination).
+ */
+ set_key_name(essid, bssid, obj_name, sizeof (obj_name));
+ dprintf("store_wepkey: obj_name is %s", obj_name);
+
+ if (wepkey_string_to_secobj_value(raw_wepkey, obj_val, &obj_len) != 0) {
+ /* above function logs internally on failure */
+ return (-1);
+ }
+
+ status = dladm_set_secobj(obj_name, DLADM_SECOBJ_CLASS_WEP,
+ obj_val, obj_len,
+ DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_TEMP);
+ if (status != DLADM_STATUS_OK) {
+ syslog(LOG_ERR, "store_wepkey: could not create secure object "
+ "'%s' for wepkey: %s", obj_name,
+ dladm_status2str(status, errmsg));
+ return (-1);
+ }
+ return (0);
+}
+
+/*
+ * retrieve_wepkey returns NULL if no wepkey was recovered from dladm
+ */
+static dladm_wlan_wepkey_t *
+retrieve_wepkey(const char *essid, const char *bssid)
+{
+ dladm_status_t status;
+ char errmsg[DLADM_STRSIZE];
+ dladm_wlan_wepkey_t *cooked_wepkey;
+ dladm_secobj_class_t class;
+
+ /*
+ * Newly-allocated wepkey must be freed by caller, or by
+ * subsequent call to retrieve_wepkey().
+ */
+ if ((cooked_wepkey = malloc(sizeof (dladm_wlan_wepkey_t))) == NULL) {
+ syslog(LOG_ERR, "retrieve_wepkey: malloc failed");
+ return (NULL);
+ }
+
+ /* Set name appropriately to retrieve wepkey for this WLAN */
+ set_key_name(essid, bssid, cooked_wepkey->wk_name,
+ DLADM_SECOBJ_NAME_MAX);
+ dprintf("retrieve_wepkey: len = %d, object = %s\n",
+ strlen(cooked_wepkey->wk_name), cooked_wepkey->wk_name);
+ cooked_wepkey->wk_len = DLADM_SECOBJ_NAME_MAX;
+ cooked_wepkey->wk_idx = 1;
+
+ /* Try the kernel first, then fall back to persistent storage. */
+ status = dladm_get_secobj(cooked_wepkey->wk_name, &class,
+ cooked_wepkey->wk_val, &cooked_wepkey->wk_len,
+ DLADM_OPT_TEMP);
+ if (status != DLADM_STATUS_OK) {
+ dprintf("retrieve_wepkey: dladm_get_secobj(TEMP) failed: %s",
+ dladm_status2str(status, errmsg));
+ status = dladm_get_secobj(cooked_wepkey->wk_name, &class,
+ cooked_wepkey->wk_val, &cooked_wepkey->wk_len,
+ DLADM_OPT_PERSIST);
+ }
+
+ switch (status) {
+ case DLADM_STATUS_OK:
+ dprintf("retrieve_wepkey: dladm_get_secobj succeeded: len %d",
+ cooked_wepkey->wk_len);
+ break;
+ case DLADM_STATUS_NOTFOUND:
+ /*
+ * We do not want an error in the case that the secobj
+ * is not found, since we then prompt for it.
+ */
+ free(cooked_wepkey);
+ return (NULL);
+ default:
+ syslog(LOG_ERR, "retrieve_wepkey: could not get wepkey "
+ "from secure object '%s': %s", cooked_wepkey->wk_name,
+ dladm_status2str(status, errmsg));
+ free(cooked_wepkey);
+ return (NULL);
+ }
+
+ return (cooked_wepkey);
+}
+
+/* Create the KNOWN_WIFI_NETS using info from the interface list. */
+void
+create_known_wifi_nets_file(void)
+{
+ FILE *fp;
+ int dirmode = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
+
+ /* Create the NWAM directory in case it does not exist. */
+ if (mkdir(LLPDIR, dirmode) != 0) {
+ if (errno != EEXIST) {
+ syslog(LOG_ERR, "could not create %s: %m", LLPDIR);
+ return;
+ }
+ }
+ if ((fp = fopen(KNOWN_WIFI_NETS, "a+")) == NULL) {
+ syslog(LOG_ERR, "could not open %s: %m", KNOWN_WIFI_NETS);
+ return;
+ }
+ dprintf("Creating %s", KNOWN_WIFI_NETS);
+ (void) fclose(fp);
+}
+
+/*
+ * Add an entry to known_wifi_nets file given the parameters.
+ */
+void
+update_known_wifi_nets_file(const char *essid, const char *bssid)
+{
+ FILE *fp;
+
+ dprintf("update_known_wifi_nets_file(%s, %s)", essid, STRING(bssid));
+ fp = fopen(KNOWN_WIFI_NETS, "a+");
+ if (fp == NULL) {
+ if (errno != ENOENT) {
+ syslog(LOG_ERR, "fopen(%s) failed: %m",
+ KNOWN_WIFI_NETS);
+ return;
+ }
+
+ /*
+ * If there is none, we should create one instead.
+ * For now, we will use the order of seeing each new net
+ * for the priority. We should have a priority field
+ * in the known_wifi_nets file eventually...
+ */
+ create_known_wifi_nets_file();
+ fp = fopen(KNOWN_WIFI_NETS, "a");
+ if (fp == NULL) {
+ syslog(LOG_ERR, "second fopen(%s) failed: %m",
+ KNOWN_WIFI_NETS);
+ return;
+ }
+ }
+ /* now see if this info is already in the file */
+ if (known_wifi_nets_lookup(essid, bssid) == B_FALSE) {
+ /* now add this to the file */
+ (void) fprintf(fp, "%s\t%s\n", essid,
+ bssid == NULL ? "" : bssid);
+ }
+ (void) fclose(fp);
+}
+
+/*
+ * Check if the given AP (ESSID, BSSID pair) is on the known AP list.
+ */
+boolean_t
+known_wifi_nets_lookup(const char *new_essid, const char *new_bssid)
+{
+ FILE *fp;
+ char line[LINE_MAX];
+ char *cp, *lasts, *essid, *bssid;
+ int line_num;
+ boolean_t found = B_FALSE;
+
+ /*
+ * For now the file format is:
+ * essid\tbssid
+ * (essid followed by tab followed by bssid)
+ */
+ fp = fopen(KNOWN_WIFI_NETS, "r+");
+ if (fp == NULL) {
+ if (errno != ENOENT) {
+ syslog(LOG_ERR, "fopen(%s) failed: %m",
+ KNOWN_WIFI_NETS);
+ return (B_FALSE);
+ }
+ create_known_wifi_nets_file();
+ return (B_FALSE);
+ }
+ for (line_num = 1; fgets(line, sizeof (line), fp) != NULL; line_num++) {
+ if (line[strlen(line) - 1] == '\n')
+ line[strlen(line) - 1] = '\0';
+
+ cp = line;
+ while (isspace(*cp))
+ cp++;
+
+ if (*cp == '#' || *cp == '\0')
+ continue;
+
+ if ((essid = strtok_r(cp, "\t", &lasts)) == NULL) {
+ syslog(LOG_ERR, "%s:%d: not enough tokens; "
+ "ignoring entry", KNOWN_WIFI_NETS, line_num);
+ continue;
+ }
+ bssid = strtok_r(NULL, "\t", &lasts);
+ if (strcmp(essid, new_essid) != 0)
+ continue;
+ if (new_bssid == NULL) {
+ /*
+ * no BSSID specified => ESSID match
+ * is good enough.
+ */
+ found = B_TRUE;
+ } else if (bssid != NULL && strcmp(bssid, new_bssid) == 0) {
+ /* Match on both is always good. */
+ found = B_TRUE;
+ }
+ if (found)
+ break;
+ }
+ (void) fclose(fp);
+ return (found);
+}
+
+/*
+ * reqlan->essid is required (i.e., cannot be NULL)
+ * reqlan->bssid is optional (i.e., may be NULL)
+ */
+boolean_t
+connect_chosen_lan(struct wireless_lan *reqlan, const char *ifname)
+{
+ uint_t keycount;
+ dladm_wlan_wepkey_t *key;
+ dladm_wlan_attr_t attr;
+ dladm_status_t status;
+ uint_t flags = DLADM_WLAN_CONNECT_NOSCAN;
+ int timeout = DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT;
+ char errmsg[DLADM_STRSIZE];
+
+ (void) memset(&attr, 0, sizeof (attr));
+ /* try to apply essid selected by the user */
+ if (reqlan->essid == NULL)
+ return (B_FALSE);
+ dprintf("connect_chosen_lan(%s, %s, %s)", reqlan->essid,
+ STRING(reqlan->bssid), ifname);
+
+ /* If it is already connected to the required AP, just return. */
+ if (check_wlan(ifname, reqlan->essid))
+ return (B_TRUE);
+
+ if (dladm_wlan_str2essid(reqlan->essid, &attr.wa_essid) !=
+ DLADM_STATUS_OK) {
+ syslog(LOG_ERR,
+ "connect_chosen_lan: invalid ESSID '%s' for '%s'",
+ reqlan->essid, ifname);
+ return (B_FALSE);
+ }
+ attr.wa_valid = DLADM_WLAN_ATTR_ESSID;
+ if (reqlan->bssid != NULL) {
+ if (dladm_wlan_str2bssid(reqlan->bssid, &attr.wa_bssid) !=
+ DLADM_STATUS_OK) {
+ syslog(LOG_ERR,
+ "connect_chosen_lan: invalid BSSID '%s' for '%s'",
+ reqlan->bssid, ifname);
+ return (B_FALSE);
+ }
+ attr.wa_valid |= DLADM_WLAN_ATTR_BSSID;
+ }
+
+ /* First check for the wepkey */
+ if (reqlan->need_wepkey) {
+ get_user_wepkey(reqlan);
+ if (reqlan->cooked_wepkey == NULL)
+ return (B_FALSE);
+ attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE;
+ attr.wa_secmode = DLADM_WLAN_SECMODE_WEP;
+ key = reqlan->cooked_wepkey;
+ keycount = 1;
+ dprintf("connect_chosen_lan: retrieved key");
+ } else {
+ key = NULL;
+ keycount = 0;
+ }
+
+ /*
+ * Connect; only scan if a bssid was not specified.
+ * If it times out and we were trying with a bssid,
+ * try a second time with just the ESSID.
+ */
+
+ status = dladm_wlan_connect(ifname, &attr, timeout, key, keycount,
+ flags);
+ dprintf("connect_chosen_lan: dladm_wlan_connect returned %s",
+ dladm_status2str(status, errmsg));
+ if (status == DLADM_STATUS_TIMEDOUT && reqlan->bssid != NULL) {
+ syslog(LOG_INFO, "connect_chosen_lan: failed for (%s, %s), "
+ "trying again with just (%s)",
+ reqlan->essid, reqlan->bssid, reqlan->essid);
+ attr.wa_valid &= ~DLADM_WLAN_ATTR_BSSID;
+ flags = 0;
+ status = dladm_wlan_connect(ifname, &attr, timeout, key,
+ keycount, flags);
+ }
+ if (status != DLADM_STATUS_OK) {
+ syslog(LOG_ERR,
+ "connect_chosen_lan: connect to '%s' failed on '%s': %s",
+ reqlan->essid, ifname, dladm_status2str(status, errmsg));
+ return (B_FALSE);
+ }
+ return (B_TRUE);
+}
+
+/*
+ * First attempt to connect to the network specified by essid.
+ * If that fails, attempt to connect using autoconf.
+ */
+static boolean_t
+connect_or_autoconf(struct wireless_lan *reqlan, const char *ifname)
+{
+ if (!connect_chosen_lan(reqlan, ifname)) {
+ syslog(LOG_WARNING,
+ "Could not connect to chosen WLAN %s, going to auto-conf",
+ reqlan->essid);
+ return (wlan_autoconf(ifname));
+ }
+ return (B_TRUE);
+}
+
+/*
+ * The +1 is for the extra "compare" row, the 24 is for the font spacing
+ * and the 125 if extra for the buttons et al.
+ */
+static int
+zenity_height(int rows)
+{
+ return (((rows + 1) * 24) + 125);
+}
+
+static return_vals_t
+connect_to_new_wlan(const struct wireless_lan *lanlist, int num,
+ const char *ifname)
+{
+ int i, rtn, j = 0;
+ struct interface *intf;
+ struct wireless_lan *reqlan;
+ char buf[2048];
+ char *endbuf = buf;
+ size_t buflen = 0;
+ char zenity_cmd[2048];
+ char *other_str = gettext("Other");
+ char *rescan_str = gettext("Rescan");
+ boolean_t autoconf = B_FALSE;
+
+ dprintf("connect_to_new_wlan(..., %d, %s)", num, ifname);
+
+ if (num == 0) {
+ display(gettext("No Wifi networks found; continuing in case "
+ "you know of any which do not broadcast."));
+ }
+
+ if ((intf = get_interface(ifname)) == NULL) {
+ dprintf("connect_to_new_wlan: cannot find wireless interface: "
+ "%s", ifname);
+ return (FAILURE);
+ }
+
+ /* build list for display */
+ buf[0] = '\0';
+ for (i = 0; i < num; i++) {
+ if ((lanlist[i].essid == NULL) || (lanlist[i].bssid == NULL)) {
+ syslog(LOG_WARNING, "wifi list entry %d broken: "
+ "essid %s, bssid %s; ignoring", i,
+ STRING(lanlist[i].essid), STRING(lanlist[i].bssid));
+ continue;
+ }
+ /*
+ * Only use the interface which finds the AP to connect to it.
+ */
+ if (strcmp(lanlist[i].wl_if_name, ifname) != 0) {
+ dprintf("connect_to_new_wlan: wrong interface (%s) for "
+ "%s (should be %s)", ifname, lanlist[i].essid,
+ lanlist[i].wl_if_name);
+ continue;
+ }
+
+ j++;
+ /*
+ * Zenity uses a space as its delimiter, so put the ESSID in
+ * quotes so it won't get confused if there is a space in the
+ * ESSID name.
+ */
+ if (sizeof (buf) - 1 > buflen) {
+ buflen += snprintf(endbuf, sizeof (buf) - buflen,
+ "%d '%s' %s %s '%s' ", j,
+ lanlist[i].essid, lanlist[i].bssid,
+ lanlist[i].need_wepkey ? "WEP" : "none",
+ lanlist[i].signal_strength);
+ endbuf = buf + buflen;
+ }
+ }
+ if (sizeof (buf) - 1 > buflen) {
+ /*
+ * All columns except the first are empty for the "Other"
+ * and "Rescan" rows.
+ */
+ (void) snprintf(endbuf, sizeof (buf) - buflen,
+ "\"%s\" \"\" \"\" \"\" \"\" \"%s\" \"\" \"\" \"\" \"\"",
+ other_str, rescan_str);
+ }
+
+ (void) snprintf(zenity_cmd, sizeof (zenity_cmd),
+ "%s --list --title=\"%s\""
+ " --height=%d --width=500 --column=\"#\" --column=\"%s\""
+ " --column=\"%s\" --column=\"%s\" --column=\"%s\""
+ " %s ", ZENITY, gettext("Choose WiFi network you wish to activate"),
+ zenity_height(j), "ESSID", "BSSID", gettext("Encryption"),
+ gettext("Signal"), buf);
+
+ /* present list to user and get selection */
+ rtn = get_user_preference(zenity_cmd, other_str, rescan_str, &reqlan,
+ lanlist);
+ switch (rtn) {
+ case 1:
+ /* user chose "other"; pop-up for specific essid */
+ reqlan = get_specific_lan();
+ break;
+ case 2:
+ /* user chose "Rescan" */
+ (void) scan_wireless_nets(intf);
+ return (TRY_AGAIN);
+ case -1:
+ reqlan = NULL;
+ break;
+ default:
+ /* common case: reqlan was set in get_user_preference() */
+ break;
+ }
+
+ if ((reqlan == NULL) || (reqlan->essid == NULL)) {
+ dprintf("did not get user preference; attempting autoconf");
+ return (wlan_autoconf(ifname) ? SUCCESS : FAILURE);
+ }
+ dprintf("get_user_preference() returned essid %s, bssid %s, encr %s",
+ reqlan->essid, STRING(reqlan->bssid),
+ reqlan->need_wepkey ? "WEP" : "none");
+
+ /* set wepkey before first time connection */
+ if (reqlan->need_wepkey && reqlan->raw_wepkey == NULL &&
+ reqlan->cooked_wepkey == NULL)
+ get_user_wepkey(reqlan);
+
+ /*
+ * now attempt to connect to selection, backing
+ * off to autoconf if the connect fails
+ */
+ if (connect_chosen_lan(reqlan, ifname)) {
+ /* succeeded, so add entry to known_essid_list_file */
+ update_known_wifi_nets_file(reqlan->essid, reqlan->bssid);
+ } else {
+ /* failed to connect; try auto-conf */
+ syslog(LOG_WARNING, "Could not connect to chosen WLAN "
+ "%s; going to auto-conf", reqlan->essid);
+ autoconf = B_TRUE;
+ }
+ free_wireless_lan(reqlan);
+
+ if (!autoconf)
+ return (SUCCESS);
+ return (wlan_autoconf(ifname) ? SUCCESS : FAILURE);
+}
+
+struct wireless_lan *
+prompt_for_visited(void)
+{
+ char buf[1024];
+ size_t buflen = 0;
+ char *endbuf = buf;
+ char zenity_cmd[1024];
+ char *select_str = gettext("Select from all available WiFi networks");
+ char *rescan_str = gettext("Rescan");
+ struct wireless_lan *req_conf = NULL, *list;
+ int i = 0;
+
+ /* Build zenity command string */
+ buf[0] = '\0';
+ if (visited_wlan_list->total > 0) {
+ struct visited_wlans *vlp;
+ struct wireless_lan *wlp;
+
+ list = calloc(visited_wlan_list->total,
+ sizeof (struct wireless_lan));
+ if (list == NULL) {
+ syslog(LOG_ERR, "prompt_for_visited: calloc failed");
+ return (NULL);
+ }
+ for (vlp = visited_wlan_list->head;
+ vlp != NULL; vlp = vlp->next) {
+
+ wlp = vlp->wifi_net;
+ if (wlp->essid == NULL || wlp->bssid == NULL) {
+ dprintf("Invalid essid/bssid values");
+ continue;
+ }
+ i++;
+ if (sizeof (buf) - 1 > buflen) {
+ buflen += snprintf(endbuf,
+ sizeof (buf) - buflen,
+ "%d '%s' %s %s '%s' ",
+ i, wlp->essid, wlp->bssid,
+ wlp->need_wepkey ? "WEP" : gettext("none"),
+ wlp->signal_strength);
+ endbuf = buf + buflen;
+ }
+ list[i-1].essid = wlp->essid;
+ list[i-1].bssid = wlp->bssid;
+ list[i-1].need_wepkey = wlp->need_wepkey;
+ list[i-1].raw_wepkey = wlp->raw_wepkey;
+ list[i-1].cooked_wepkey = wlp->cooked_wepkey;
+ list[i-1].signal_strength = wlp->signal_strength;
+ list[i-1].wl_if_name = wlp->wl_if_name;
+ }
+ }
+ if (sizeof (buf) - 1 > buflen) {
+ (void) snprintf(endbuf, sizeof (buf) - buflen,
+ "\"%s\"", select_str);
+ }
+
+ (void) snprintf(zenity_cmd, sizeof (zenity_cmd),
+ "%s --list --title=\"%s\""
+ " --height=%d --width=670 --column=\"#\" --column=\"%s\""
+ " --column=\"%s\" --column=\"%s\" --column=\"%s\""
+ " %s", ZENITY, gettext("Choose from pre-visited WiFi network"),
+ zenity_height(i), "ESSID", "BSSID", gettext("Encryption"),
+ gettext("Signal"), buf);
+
+ /*
+ * If the user doesn't make a choice or something goes wrong
+ * (get_user_preference() returned -1), or if the user chooses
+ * the "select from all available" string (get_user_preference()
+ * returned 1), we simply return NULL: there was no selection
+ * made from the visited list. If the user *did* make a choice
+ * (get_user_preference() returned 0), return the alloc'd struct.
+ */
+ if (get_user_preference(zenity_cmd, select_str, rescan_str,
+ &req_conf, list) != 0)
+ req_conf = NULL;
+
+ free(list);
+ return (req_conf);
+}
+
+static boolean_t
+wlan_autoconf(const char *ifname)
+{
+ dladm_status_t status;
+ boolean_t autoconf;
+
+ if (lookup_boolean_property(OUR_PG, "autoconf", &autoconf) == 0) {
+ if (!autoconf)
+ return (B_FALSE);
+ }
+
+ /* If the NIC is already associated with something, just return. */
+ if (check_wlan(ifname, NULL))
+ return (B_TRUE);
+
+ /*
+ * Do autoconf, relying on the heuristics used by dladm_wlan_connect()
+ * to cycle through WLANs detected in priority order, attempting
+ * to connect.
+ */
+ status = dladm_wlan_connect(ifname, NULL,
+ DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, NULL, 0, 0);
+ if (status != DLADM_STATUS_OK) {
+ char errmsg[DLADM_STRSIZE];
+
+ syslog(LOG_ERR,
+ "wlan_autoconf: dladm_wlan_connect failed for '%s': %s",
+ ifname, dladm_status2str(status, errmsg));
+ return (B_FALSE);
+ }
+ return (B_TRUE);
+}
+
+/*
+ * Returns:
+ * B_TRUE if this info is already in visited_wlan_list
+ * B_FALSE if not
+ */
+static boolean_t
+already_in_visited_wlan_list(const struct wireless_lan *new_wlan)
+{
+ struct visited_wlans *vwlp;
+
+ vwlp = visited_wlan_list->head;
+ while (vwlp != NULL && vwlp->wifi_net != NULL) {
+ if (strcmp(vwlp->wifi_net->essid, new_wlan->essid) == 0) {
+ dprintf("%s already in visited_wlan_list",
+ vwlp->wifi_net->essid);
+ return (B_TRUE);
+ } else {
+ vwlp = vwlp->next;
+ }
+ }
+ return (B_FALSE);
+}
+
+static char *
+get_zenity_response(const char *cmd)
+{
+ char buf[1024];
+ size_t buf_len;
+ char *rtnp;
+ FILE *cptr;
+ int ret;
+
+ if (!valid_graphical_user(B_TRUE))
+ return (NULL);
+
+ cptr = popen(cmd, "r");
+ if (cptr == NULL) {
+ syslog(LOG_ERR, "Could not run %s: %m", ZENITY);
+ return (NULL);
+ }
+ if (fgets(buf, sizeof (buf), cptr) != NULL) {
+ buf_len = strlen(buf);
+ if (buf_len > 0 && buf[buf_len - 1] == '\n')
+ buf[buf_len - 1] = '\0';
+ dprintf("get_zenity_resp: zenity returned '%s'", buf);
+ } else {
+ buf[0] = '\0';
+ dprintf("get_zenity_resp: zenity returned nothing");
+ }
+ ret = pclose(cptr);
+ /*
+ * We should probably make sure that those ZENITY_* exit
+ * environment variables are not set first...
+ */
+ if (ret == -1 || !WIFEXITED(ret) || (WEXITSTATUS(ret) == 255)) {
+ dprintf("get_zenity_resp: %s did not exit normally", ZENITY);
+ return (NULL);
+ }
+ if (WEXITSTATUS(ret) == 1) {
+ dprintf("get_zenity_resp: user cancelled");
+ return (NULL);
+ }
+ if ((rtnp = strdup(buf)) == NULL)
+ syslog(LOG_ERR, "get_zenity_response: strdup failed");
+
+ return (rtnp);
+}
+
+/*
+ * get_user_preference(): Present a list of essid/bssid pairs to the
+ * user via zenity (passed in in a pre-formatted zenity string in the
+ * param cmd). If there's a final list item ("Other", "Select from
+ * full list", etc.) that may be selected and should be differentiated,
+ * that item should be passed in in compare param.
+ *
+ * Four possible return values:
+ * -1: No response from user, or other error. *req_lan is undefined.
+ * 0: essid/bssid pair was selected. *req_lan has these values in
+ * a malloc'd buffer, which the caller is responsible for freeing.
+ * 1: a compare string ("Other") was given, and the user response matched
+ * that string. *req_lan is undefined.
+ * 2: a compare string ("Rescan") was given, and the user response matched
+ * that string. *req_lan is undefined.
+ */
+int
+get_user_preference(const char *cmd, const char *compare_other,
+ const char *compare_rescan, struct wireless_lan **req_lan,
+ const struct wireless_lan *list)
+{
+ char *response;
+ struct wireless_lan *wlp;
+ const struct wireless_lan *sel;
+ int answer;
+
+ assert(req_lan != NULL);
+ wlp = calloc(1, sizeof (struct wireless_lan));
+ if (wlp == NULL) {
+ syslog(LOG_ERR, "malloc failed");
+ return (-1);
+ }
+
+ response = get_zenity_response(cmd);
+ if (response == NULL) {
+ free(wlp);
+ return (-1);
+ }
+ if (strcmp(response, compare_other) == 0) {
+ free(response);
+ free(wlp);
+ return (1);
+ }
+ if (strcmp(response, compare_rescan) == 0) {
+ free(response);
+ free(wlp);
+ return (2);
+ }
+ answer = atoi(response);
+ if (answer <= 0) {
+ dprintf("%s returned invalid string", ZENITY);
+ free(response);
+ free(wlp);
+ return (-1);
+ }
+ sel = &list[answer - 1];
+
+ if ((wlp->essid = strdup(sel->essid)) == NULL)
+ goto dup_error;
+
+ if ((sel->bssid != NULL) && ((wlp->bssid = strdup(sel->bssid)) == NULL))
+ goto dup_error;
+
+ wlp->need_wepkey = sel->need_wepkey;
+
+ if ((sel->raw_wepkey != NULL) &&
+ ((wlp->raw_wepkey = strdup(sel->raw_wepkey)) == NULL))
+ goto dup_error;
+
+ if (sel->cooked_wepkey != NULL) {
+ wlp->cooked_wepkey = malloc(sizeof (dladm_wlan_wepkey_t));
+ if (wlp->cooked_wepkey == NULL)
+ goto dup_error;
+ *(wlp->cooked_wepkey) = *(sel->cooked_wepkey);
+ }
+
+ if ((sel->signal_strength != NULL) &&
+ ((wlp->signal_strength = strdup(sel->signal_strength)) == NULL))
+ goto dup_error;
+
+ if ((sel->wl_if_name != NULL) &&
+ ((wlp->wl_if_name = strdup(sel->wl_if_name)) == NULL))
+ goto dup_error;
+
+ dprintf("selected: %s, %s, %s, '%s', %s", wlp->essid,
+ STRING(wlp->bssid), wlp->need_wepkey ? "WEP" : "none",
+ STRING(wlp->signal_strength), STRING(wlp->wl_if_name));
+
+ free(response);
+
+ *req_lan = wlp;
+ return (0);
+
+dup_error:
+ syslog(LOG_ERR, "get_user_preference: strdup failed");
+ free_wireless_lan(wlp);
+ free(wlp);
+ free(response);
+ return (-1);
+}
+
+/*
+ * Returns a pointer to an alloc'd struct wireless lan if a response
+ * is received from the user; only the essid will be valid in this
+ * case. If no response received, or other failure, returns NULL.
+ */
+static struct wireless_lan *
+get_specific_lan(void)
+{
+ char specify_str[1024];
+ char *response;
+ struct wireless_lan *wlp;
+
+ /*
+ * TRANSLATION_NOTE: the token "ESSID" should not be translated
+ * in the phrase below.
+ */
+ (void) snprintf(specify_str, sizeof (specify_str), ZENITY
+ " --entry --title=\"%s\" --text=\"%s\"",
+ gettext("Specify WiFi network"), gettext("Enter ESSID"));
+
+ response = get_zenity_response(specify_str);
+ if (response == NULL)
+ return (NULL);
+
+ wlp = calloc(1, sizeof (struct wireless_lan));
+ if (wlp == NULL) {
+ syslog(LOG_ERR, "malloc failed: %m");
+ free(response);
+ return (NULL);
+ }
+ wlp->essid = response;
+
+ (void) snprintf(specify_str, sizeof (specify_str), ZENITY
+ " --list --title=\"%s\" --text=\"%s\" --column=\"%s\" none wep",
+ gettext("Security"), gettext("Enter security"),
+ gettext("Type"));
+
+ response = get_zenity_response(specify_str);
+ if (response != NULL && strcmp(response, "wep") == 0)
+ wlp->need_wepkey = B_TRUE;
+
+ free(response);
+ return (wlp);
+}
+
+/*
+ * Returns:
+ * B_TRUE if things go well
+ * B_FALSE if we were unable to connect to anything
+ */
+boolean_t
+handle_wireless_lan(const char *ifname)
+{
+ const struct wireless_lan *cur_wlans;
+ int i, num_wlans;
+ struct wireless_lan *req_conf = NULL;
+ boolean_t result;
+ dladm_wlan_strength_t strongest = DLADM_WLAN_STRENGTH_VERY_WEAK;
+ dladm_wlan_strength_t strength;
+ return_vals_t connect_result;
+
+start_over:
+ if (visited_wlan_list == NULL) {
+ if ((visited_wlan_list = calloc(1,
+ sizeof (struct visited_wlans_list))) == NULL) {
+ syslog(LOG_ERR, "handle_wireless_lan: calloc failed");
+ return (B_FALSE);
+ }
+ }
+
+ /*
+ * We wait while a scan is in progress. Since we allow a user
+ * to initiate a re-scan, we can proceed even when no scan
+ * has been done to fill in the AP list.
+ */
+ (void) pthread_mutex_lock(&wifi_init_mutex);
+ while (wifi_scan_intf != NULL)
+ (void) pthread_cond_wait(&wifi_init_cond, &wifi_init_mutex);
+ (void) pthread_mutex_unlock(&wifi_init_mutex);
+
+ (void) pthread_mutex_lock(&wifi_mutex);
+ num_wlans = wireless_lan_used;
+ cur_wlans = wlans;
+
+ /*
+ * Try to see if any of the wifi nets currently available
+ * has been used previously. If more than one available
+ * nets has been used before, then prompt user with
+ * all the applicable previously wifi nets, and ask which
+ * one to connect to.
+ */
+ for (i = 0; i < num_wlans; i++) {
+ struct visited_wlans *new_wlan;
+
+ /* Find the AP with the highest signal. */
+ if (dladm_wlan_str2strength(cur_wlans[i].signal_strength,
+ &strength) != DLADM_STATUS_OK) {
+ continue;
+ }
+ if (strength > strongest)
+ strongest = strength;
+
+ if (!known_wifi_nets_lookup(cur_wlans[i].essid,
+ cur_wlans[i].bssid))
+ continue;
+
+ if (already_in_visited_wlan_list(&cur_wlans[i])) {
+ /* don't have to add it again */
+ continue;
+ }
+
+ /* add this to the visited_wlan_list */
+ dprintf("adding essid %s, bssid %s to visited list",
+ cur_wlans[i].essid, STRING(cur_wlans[i].bssid));
+
+ new_wlan = calloc(1, sizeof (struct visited_wlans));
+ if (new_wlan == NULL) {
+ syslog(LOG_ERR, "handle_wireless_lan: calloc failed");
+ result = B_FALSE;
+ connect_result = FAILURE;
+ goto all_done;
+ }
+ new_wlan->wifi_net = calloc(1, sizeof (struct wireless_lan));
+ if (new_wlan->wifi_net == NULL) {
+ free(new_wlan);
+ syslog(LOG_ERR, "handle_wireless_lan: calloc failed");
+ result = B_FALSE;
+ connect_result = FAILURE;
+ goto all_done;
+ }
+ new_wlan->wifi_net->essid = strdup(cur_wlans[i].essid);
+ new_wlan->wifi_net->bssid = strdup(cur_wlans[i].bssid);
+ new_wlan->wifi_net->raw_wepkey = NULL;
+ new_wlan->wifi_net->cooked_wepkey = NULL;
+ new_wlan->wifi_net->need_wepkey = cur_wlans[i].need_wepkey;
+ new_wlan->wifi_net->signal_strength =
+ strdup(cur_wlans[i].signal_strength);
+ new_wlan->wifi_net->wl_if_name =
+ strdup(cur_wlans[i].wl_if_name);
+ if (new_wlan->wifi_net->essid == NULL ||
+ new_wlan->wifi_net->bssid == NULL ||
+ new_wlan->wifi_net->signal_strength == NULL ||
+ new_wlan->wifi_net->wl_if_name == NULL) {
+ syslog(LOG_ERR, "handle_wireless_lan: strdup failed");
+ free_wireless_lan(new_wlan->wifi_net);
+ free(new_wlan->wifi_net);
+ free(new_wlan);
+ result = B_FALSE;
+ connect_result = FAILURE;
+ goto all_done;
+ }
+
+ new_wlan->next = visited_wlan_list->head;
+ visited_wlan_list->head = new_wlan;
+ visited_wlan_list->total++;
+ }
+
+ if (visited_wlan_list->total == 1) {
+ struct wireless_lan *target = visited_wlan_list->head->wifi_net;
+
+ /*
+ * only one previously visited wifi net, connect to it
+ * (falling back to autoconf if the connect fails) if there
+ * is no AP with a better signal strength.
+ */
+ if (dladm_wlan_str2strength(target->signal_strength,
+ &strength) == DLADM_STATUS_OK) {
+ if (strength < strongest)
+ goto connect_any;
+ }
+ result = connect_or_autoconf(target, ifname);
+ connect_result = result ? SUCCESS : FAILURE;
+
+ } else if (visited_wlan_list->total > 1) {
+ /*
+ * more than one previously visited wifi nets seen.
+ * prompt user for which one should we connect to
+ */
+ if ((req_conf = prompt_for_visited()) != NULL) {
+ result = connect_or_autoconf(req_conf, ifname);
+ connect_result = result ? SUCCESS : FAILURE;
+ } else {
+ /*
+ * The user didn't make a choice; offer the full list.
+ */
+ connect_result = connect_to_new_wlan(cur_wlans,
+ num_wlans, ifname);
+ result = (connect_result == SUCCESS);
+ }
+ } else {
+connect_any:
+ /* last case, no previously visited wlan found */
+ connect_result = connect_to_new_wlan(cur_wlans, num_wlans,
+ ifname);
+ result = (connect_result == SUCCESS);
+ }
+
+all_done:
+ /*
+ * We locked down the list above; free it now that we're done.
+ */
+ (void) pthread_mutex_unlock(&wifi_mutex);
+ if (visited_wlan_list != NULL) {
+ struct visited_wlans *vwlp = visited_wlan_list->head, *next;
+
+ for (; vwlp != NULL; vwlp = next) {
+ if (vwlp->wifi_net != NULL) {
+ free_wireless_lan(vwlp->wifi_net);
+ free(vwlp->wifi_net);
+ vwlp->wifi_net = NULL;
+ }
+ next = vwlp->next;
+ free(vwlp);
+ }
+ free(visited_wlan_list);
+ visited_wlan_list = NULL;
+ }
+ if (req_conf != NULL) {
+ free_wireless_lan(req_conf);
+ free(req_conf);
+ req_conf = NULL;
+ }
+ if (connect_result == TRY_AGAIN) {
+ dprintf("end of handle_wireless_lan() TRY_AGAIN");
+ goto start_over;
+ }
+ return (result);
+}