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 nwam_error_t 467 known_wlan_get_keyslot(const char *essid, uint_t *keyslotp) 468 { 469 nwam_known_wlan_handle_t kwh = NULL; 470 nwam_value_t keyslotval = NULL; 471 uint64_t slot; 472 nwam_error_t err; 473 474 if ((err = nwam_known_wlan_read(essid, 0, &kwh)) != NWAM_SUCCESS) 475 return (err); 476 if ((err = nwam_known_wlan_get_prop_value(kwh, 477 NWAM_KNOWN_WLAN_PROP_KEYSLOT, &keyslotval)) == NWAM_SUCCESS && 478 (err = nwam_value_get_uint64(keyslotval, &slot)) == NWAM_SUCCESS) { 479 *keyslotp = (uint_t)slot; 480 } else { 481 if (err == NWAM_ENTITY_NOT_FOUND) 482 err = NWAM_SUCCESS; 483 *keyslotp = 1; 484 } 485 if (keyslotval != NULL) 486 nwam_value_free(keyslotval); 487 if (kwh != NULL) 488 nwam_known_wlan_free(kwh); 489 return (err); 490 } 491 492 /* Performs a scan on a wifi link NCU */ 493 /* ARGSUSED */ 494 static int 495 nwamd_ncu_known_wlan_committed(nwamd_object_t object, void *data) 496 { 497 nwamd_ncu_t *ncu_data = object->nwamd_object_data; 498 499 if (ncu_data->ncu_type != NWAM_NCU_TYPE_LINK) 500 return (0); 501 502 /* network selection will be done only if possible */ 503 if (ncu_data->ncu_link.nwamd_link_media == DL_WIFI) 504 (void) nwamd_wlan_scan(ncu_data->ncu_name); 505 return (0); 506 } 507 508 /* Handle known WLAN initialization/refresh event */ 509 /* ARGSUSED */ 510 void 511 nwamd_known_wlan_handle_init_event(nwamd_event_t known_wlan_event) 512 { 513 /* 514 * Since the Known WLAN list has changed, do a rescan so that the 515 * best network is selected. 516 */ 517 (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, 518 nwamd_ncu_known_wlan_committed, NULL); 519 } 520 521 void 522 nwamd_known_wlan_handle_action_event(nwamd_event_t known_wlan_event) 523 { 524 switch (known_wlan_event->event_msg->nwe_data.nwe_object_action. 525 nwe_action) { 526 case NWAM_ACTION_ADD: 527 case NWAM_ACTION_REFRESH: 528 nwamd_known_wlan_handle_init_event(known_wlan_event); 529 break; 530 case NWAM_ACTION_DESTROY: 531 /* Nothing needs to be done for destroy */ 532 break; 533 /* all other events are invalid for known WLANs */ 534 case NWAM_ACTION_ENABLE: 535 case NWAM_ACTION_DISABLE: 536 default: 537 nlog(LOG_INFO, "nwam_known_wlan_handle_action_event: " 538 "unexpected action"); 539 break; 540 } 541 } 542 543 int 544 nwamd_known_wlan_action(const char *known_wlan, nwam_action_t action) 545 { 546 nwamd_event_t known_wlan_event = nwamd_event_init_object_action 547 (NWAM_OBJECT_TYPE_KNOWN_WLAN, known_wlan, NULL, action); 548 if (known_wlan_event == NULL) 549 return (1); 550 nwamd_event_enqueue(known_wlan_event); 551 return (0); 552 } 553