/* * 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/WPA 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "defines.h" #include "structures.h" #include "functions.h" #include "variables.h" #define WLAN_ENC(sec) \ ((sec == DLADM_WLAN_SECMODE_WPA ? "WPA" : \ (sec == DLADM_WLAN_SECMODE_WEP ? "WEP" : "none"))) #define NEED_ENC(sec) \ (sec == DLADM_WLAN_SECMODE_WPA || sec == DLADM_WLAN_SECMODE_WEP) 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 key_string_to_secobj_value(char *, uint8_t *, uint_t *, dladm_secobj_class_t); static int store_key(struct wireless_lan *); static dladm_wlan_key_t *retrieve_key(const char *, const char *, dladm_secobj_class_t); static boolean_t add_wlan_entry(struct interface *, char *, char *, char *, dladm_wlan_secmode_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_key(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_key(struct wireless_lan *wlan) { char zenity_cmd[1024]; char buf[1024]; FILE *zcptr; dladm_secobj_class_t class; /* * First, test if we have key stored as secobj. If so, * no need to prompt for it. */ class = (wlan->sec_mode == DLADM_WLAN_SECMODE_WEP ? DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA); wlan->cooked_key = retrieve_key(wlan->essid, wlan->bssid, class); if (wlan->cooked_key != NULL) { dprintf("get_user_key: retrieve_key() returns non NULL"); return; } (void) snprintf(zenity_cmd, sizeof (zenity_cmd), "%s --entry --text=\"%s %s\"" " --title=\"%s\" --hide-text", ZENITY, gettext("Enter key for WiFi network"), wlan->essid, gettext("Enter 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_key = strdup(buf); if (wlan->raw_key != NULL) { /* Store key persistently */ if (store_key(wlan) != 0) { syslog(LOG_ERR, "get_user_key: failed to store" " user specified key"); } } else { syslog(LOG_ERR, "get_user_key: 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_key); wlp->raw_key = NULL; free(wlp->cooked_key); wlp->cooked_key = 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, dladm_wlan_secmode_t sec) { 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].sec_mode = sec; wlans[n].raw_key = NULL; wlans[n].cooked_key = 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) { dladm_wlan_secmode_t sec; 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); sec = attrp->wa_secmode; if (!find_wlan_entry(arg, essid_name, bssid_name) && add_wlan_entry(arg, essid_name, bssid_name, strength, sec)) { 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 keys * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj() * and dladm_get_secobj(). */ /* * Convert key 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 key_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp, dladm_secobj_class_t class) { size_t buf_len = strlen(buf); dprintf("before: key_string_to_secobj_value: buf_len = %d", buf_len); if (buf_len == 0) { syslog(LOG_ERR, "key_string_to_secobj_value: empty key"); return (-1); } if (buf[buf_len - 1] == '\n') buf[--buf_len] = '\0'; dprintf("after: key_string_to_secobj_value: buf_len = %d", buf_len); if (class == DLADM_SECOBJ_CLASS_WPA) { /* * Per IEEE802.11i spec, the Pre-shared key (PSK) length should * be between 8 and 63. */ if (buf_len < 8 || buf_len > 63) { syslog(LOG_ERR, "key_string_to_secobj_value:" " invalid WPA key length: buf_len = %d", buf_len); return (-1); } (void) memcpy(obj_val, buf, (uint_t)buf_len); *obj_lenp = buf_len; return (0); } 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, "key_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, "key_string_to_secobj_value: invalid WEP key"); return (-1); } break; default: syslog(LOG_ERR, "key_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, rtn, len; if (bssid == NULL) rtn = snprintf(name, nsz, "nwam-%s", essid); else rtn = snprintf(name, nsz, "nwam-%s-%s", essid, bssid); len = (rtn < nsz) ? rtn : nsz - 1; for (i = 0; i < len; i++) if (name[i] == ':') name[i] = '.'; } static int store_key(struct wireless_lan *wlan) { uint8_t obj_val[DLADM_SECOBJ_VAL_MAX]; uint_t obj_len = sizeof (obj_val); char obj_name[DLADM_SECOBJ_NAME_MAX]; dladm_status_t status; char errmsg[DLADM_STRSIZE]; dladm_secobj_class_t class; /* * Name key object for this WLAN so it can be later retrieved * (name is unique for each ESSID/BSSID combination). */ set_key_name(wlan->essid, wlan->bssid, obj_name, sizeof (obj_name)); dprintf("store_key: obj_name is %s", obj_name); class = (wlan->sec_mode == DLADM_WLAN_SECMODE_WEP ? DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA); if (key_string_to_secobj_value(wlan->raw_key, obj_val, &obj_len, class) != 0) { /* above function logs internally on failure */ return (-1); } status = dladm_set_secobj(obj_name, class, obj_val, obj_len, DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_TEMP); if (status != DLADM_STATUS_OK) { syslog(LOG_ERR, "store_key: could not create secure object " "'%s' for key: %s", obj_name, dladm_status2str(status, errmsg)); return (-1); } /* * We don't really need to retrieve the key we just stored, but * we do need to set the cooked key, and the function below takes * care of allocating memory and setting the length and slot ID * besides just copying the value, so it is simpler just to call * the retrieve function instead of doing it all here. * * Since we just stored the key, retrieve_key() "shouldn't" * fail. If it does fail, it's not the end of the world; a NULL * value for wlan->cooked_key simply means this particular * attempt to connect will fail, and alternative connection * options will be used. */ wlan->cooked_key = retrieve_key(wlan->essid, wlan->bssid, class); return (0); } /* * retrieve_key returns NULL if no key was recovered from libdladm */ static dladm_wlan_key_t * retrieve_key(const char *essid, const char *bssid, dladm_secobj_class_t req) { dladm_status_t status; char errmsg[DLADM_STRSIZE]; dladm_wlan_key_t *cooked_key; dladm_secobj_class_t class; /* * Newly-allocated key must be freed by caller, or by * subsequent call to retrieve_key(). */ if ((cooked_key = malloc(sizeof (dladm_wlan_key_t))) == NULL) { syslog(LOG_ERR, "retrieve_key: malloc failed"); return (NULL); } /* Set name appropriately to retrieve key for this WLAN */ set_key_name(essid, bssid, cooked_key->wk_name, DLADM_SECOBJ_NAME_MAX); dprintf("retrieve_key: len = %d, object = %s\n", strlen(cooked_key->wk_name), cooked_key->wk_name); cooked_key->wk_len = DLADM_SECOBJ_NAME_MAX; cooked_key->wk_idx = 1; /* Try the kernel first, then fall back to persistent storage. */ status = dladm_get_secobj(cooked_key->wk_name, &class, cooked_key->wk_val, &cooked_key->wk_len, DLADM_OPT_TEMP); if (status != DLADM_STATUS_OK) { dprintf("retrieve_key: dladm_get_secobj(TEMP) failed: %s", dladm_status2str(status, errmsg)); status = dladm_get_secobj(cooked_key->wk_name, &class, cooked_key->wk_val, &cooked_key->wk_len, DLADM_OPT_PERSIST); } switch (status) { case DLADM_STATUS_OK: dprintf("retrieve_key: dladm_get_secobj succeeded: len %d", cooked_key->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_key); return (NULL); default: syslog(LOG_ERR, "retrieve_key: could not get key " "from secure object '%s': %s", cooked_key->wk_name, dladm_status2str(status, errmsg)); free(cooked_key); return (NULL); } if (class != req) { /* the key mismatch */ syslog(LOG_ERR, "retrieve_key: key type mismatch" " from secure object '%s'", cooked_key->wk_name); free(cooked_key); return (NULL); } return (cooked_key); } /* 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_key_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 key */ if (NEED_ENC(reqlan->sec_mode)) { get_user_key(reqlan); if (reqlan->cooked_key == NULL) return (B_FALSE); attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE; attr.wa_secmode = reqlan->sec_mode; key = reqlan->cooked_key; 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, WLAN_ENC(lanlist[i].sec_mode), 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), WLAN_ENC(reqlan->sec_mode)); /* set key before first time connection */ if (NEED_ENC(reqlan->sec_mode) && reqlan->raw_key == NULL && reqlan->cooked_key == NULL) get_user_key(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, WLAN_ENC(wlp->sec_mode), wlp->signal_strength); endbuf = buf + buflen; } list[i-1].essid = wlp->essid; list[i-1].bssid = wlp->bssid; list[i-1].sec_mode = wlp->sec_mode; list[i-1].raw_key = wlp->raw_key; list[i-1].cooked_key = wlp->cooked_key; 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->sec_mode = sel->sec_mode; if ((sel->raw_key != NULL) && ((wlp->raw_key = strdup(sel->raw_key)) == NULL)) goto dup_error; if (sel->cooked_key != NULL) { wlp->cooked_key = malloc(sizeof (dladm_wlan_key_t)); if (wlp->cooked_key == NULL) goto dup_error; *(wlp->cooked_key) = *(sel->cooked_key); } 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), WLAN_ENC(wlp->sec_mode), 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 wpa", gettext("Security"), gettext("Enter security"), gettext("Type")); response = get_zenity_response(specify_str); wlp->sec_mode = DLADM_WLAN_SECMODE_NONE; if (response != NULL) { if (strcmp(response, "wep") == 0) wlp->sec_mode = DLADM_WLAN_SECMODE_WEP; else if (strcmp(response, "wpa") == 0) wlp->sec_mode = DLADM_WLAN_SECMODE_WPA; } 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_key = NULL; new_wlan->wifi_net->cooked_key = NULL; new_wlan->wifi_net->sec_mode = cur_wlans[i].sec_mode; 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); }