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