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