1 /*- 2 * Copyright (c) 2008 Sam Leffler, Errno Consulting 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include <sys/types.h> 27 #include <sys/errno.h> 28 #include <sys/param.h> 29 #include <sys/mman.h> 30 #include <sys/sbuf.h> 31 #include <sys/stat.h> 32 33 #include <inttypes.h> 34 #include <stdio.h> 35 #include <string.h> 36 #include <ctype.h> 37 #include <fcntl.h> 38 #include <err.h> 39 #include <unistd.h> 40 41 #include <bsdxml.h> 42 43 #include "lib80211_regdomain.h" 44 45 #include <net80211/_ieee80211.h> 46 47 #define MAXLEVEL 20 48 49 struct mystate { 50 XML_Parser parser; 51 struct regdata *rdp; 52 struct regdomain *rd; /* current domain */ 53 struct netband *netband; /* current netband */ 54 struct freqband *freqband; /* current freqband */ 55 struct country *country; /* current country */ 56 netband_head *curband; /* current netband list */ 57 int level; 58 struct sbuf *sbuf[MAXLEVEL]; 59 int nident; 60 }; 61 62 struct ident { 63 const void *id; 64 void *p; 65 enum { DOMAIN, COUNTRY, FREQBAND } type; 66 }; 67 68 static void 69 start_element(void *data, const char *name, const char **attr) 70 { 71 #define iseq(a,b) (strcasecmp(a,b) == 0) 72 struct mystate *mt; 73 const void *id, *ref, *mode; 74 int i; 75 76 mt = data; 77 if (++mt->level == MAXLEVEL) { 78 /* XXX force parser to abort */ 79 return; 80 } 81 mt->sbuf[mt->level] = sbuf_new_auto(); 82 id = ref = mode = NULL; 83 for (i = 0; attr[i] != NULL; i += 2) { 84 if (iseq(attr[i], "id")) { 85 id = attr[i+1]; 86 } else if (iseq(attr[i], "ref")) { 87 ref = attr[i+1]; 88 } else if (iseq(attr[i], "mode")) { 89 mode = attr[i+1]; 90 } else 91 printf("%*.*s[%s = %s]\n", mt->level + 1, 92 mt->level + 1, "", attr[i], attr[i+1]); 93 } 94 if (iseq(name, "rd") && mt->rd == NULL) { 95 if (mt->country == NULL) { 96 mt->rd = calloc(1, sizeof(struct regdomain)); 97 mt->rd->name = strdup(id); 98 mt->nident++; 99 LIST_INSERT_HEAD(&mt->rdp->domains, mt->rd, next); 100 } else 101 mt->country->rd = (void *)strdup(ref); 102 return; 103 } 104 if (iseq(name, "defcc") && mt->rd != NULL) { 105 mt->rd->cc = (void *)strdup(ref); 106 return; 107 } 108 if (iseq(name, "netband") && mt->curband == NULL && mt->rd != NULL) { 109 if (mode == NULL) { 110 warnx("no mode for netband at line %" PRIu64, 111 XML_GetCurrentLineNumber64(mt->parser)); 112 return; 113 } 114 if (iseq(mode, "11b")) 115 mt->curband = &mt->rd->bands_11b; 116 else if (iseq(mode, "11g")) 117 mt->curband = &mt->rd->bands_11g; 118 else if (iseq(mode, "11a")) 119 mt->curband = &mt->rd->bands_11a; 120 else if (iseq(mode, "11ng")) 121 mt->curband = &mt->rd->bands_11ng; 122 else if (iseq(mode, "11na")) 123 mt->curband = &mt->rd->bands_11na; 124 else if (iseq(mode, "11ac")) 125 mt->curband = &mt->rd->bands_11ac; 126 else if (iseq(mode, "11acg")) 127 mt->curband = &mt->rd->bands_11acg; 128 else 129 warnx("unknown mode \"%s\" at line %" PRIu64, 130 __DECONST(char *, mode), 131 XML_GetCurrentLineNumber64(mt->parser)); 132 return; 133 } 134 if (iseq(name, "band") && mt->netband == NULL) { 135 if (mt->curband == NULL) { 136 warnx("band without enclosing netband at line %" PRIu64, 137 XML_GetCurrentLineNumber64(mt->parser)); 138 return; 139 } 140 mt->netband = calloc(1, sizeof(struct netband)); 141 LIST_INSERT_HEAD(mt->curband, mt->netband, next); 142 return; 143 } 144 if (iseq(name, "freqband") && mt->freqband == NULL && mt->netband != NULL) { 145 /* XXX handle inlines and merge into table? */ 146 if (mt->netband->band != NULL) { 147 warnx("duplicate freqband at line %" PRIu64 " ignored", 148 XML_GetCurrentLineNumber64(mt->parser)); 149 /* XXX complain */ 150 } else 151 mt->netband->band = (void *)strdup(ref); 152 return; 153 } 154 155 if (iseq(name, "country") && mt->country == NULL) { 156 mt->country = calloc(1, sizeof(struct country)); 157 mt->country->isoname = strdup(id); 158 mt->country->code = NO_COUNTRY; 159 mt->nident++; 160 LIST_INSERT_HEAD(&mt->rdp->countries, mt->country, next); 161 return; 162 } 163 164 if (iseq(name, "freqband") && mt->freqband == NULL) { 165 mt->freqband = calloc(1, sizeof(struct freqband)); 166 mt->freqband->id = strdup(id); 167 mt->nident++; 168 LIST_INSERT_HEAD(&mt->rdp->freqbands, mt->freqband, next); 169 return; 170 } 171 #undef iseq 172 } 173 174 static int 175 decode_flag(struct mystate *mt, const char *p, int len) 176 { 177 #define iseq(a,b) (strcasecmp(a,b) == 0) 178 static const struct { 179 const char *name; 180 int len; 181 uint32_t value; 182 } flags[] = { 183 #define FLAG(x) { #x, sizeof(#x)-1, x } 184 FLAG(IEEE80211_CHAN_A), 185 FLAG(IEEE80211_CHAN_B), 186 FLAG(IEEE80211_CHAN_G), 187 FLAG(IEEE80211_CHAN_HT20), 188 FLAG(IEEE80211_CHAN_HT40), 189 FLAG(IEEE80211_CHAN_VHT20), 190 FLAG(IEEE80211_CHAN_VHT40), 191 FLAG(IEEE80211_CHAN_VHT80), 192 FLAG(IEEE80211_CHAN_VHT160), 193 /* 194 * XXX VHT80P80? This likely should be done by 195 * 80MHz chan logic in net80211 / ifconfig. 196 */ 197 FLAG(IEEE80211_CHAN_ST), 198 FLAG(IEEE80211_CHAN_TURBO), 199 FLAG(IEEE80211_CHAN_PASSIVE), 200 FLAG(IEEE80211_CHAN_DFS), 201 FLAG(IEEE80211_CHAN_CCK), 202 FLAG(IEEE80211_CHAN_OFDM), 203 FLAG(IEEE80211_CHAN_2GHZ), 204 FLAG(IEEE80211_CHAN_5GHZ), 205 FLAG(IEEE80211_CHAN_DYN), 206 FLAG(IEEE80211_CHAN_GFSK), 207 FLAG(IEEE80211_CHAN_GSM), 208 FLAG(IEEE80211_CHAN_STURBO), 209 FLAG(IEEE80211_CHAN_HALF), 210 FLAG(IEEE80211_CHAN_QUARTER), 211 FLAG(IEEE80211_CHAN_HT40U), 212 FLAG(IEEE80211_CHAN_HT40D), 213 FLAG(IEEE80211_CHAN_4MSXMIT), 214 FLAG(IEEE80211_CHAN_NOADHOC), 215 FLAG(IEEE80211_CHAN_NOHOSTAP), 216 FLAG(IEEE80211_CHAN_11D), 217 FLAG(IEEE80211_CHAN_FHSS), 218 FLAG(IEEE80211_CHAN_PUREG), 219 FLAG(IEEE80211_CHAN_108A), 220 FLAG(IEEE80211_CHAN_108G), 221 #undef FLAG 222 { "ECM", 3, REQ_ECM }, 223 { "INDOOR", 6, REQ_INDOOR }, 224 { "OUTDOOR", 7, REQ_OUTDOOR }, 225 }; 226 unsigned int i; 227 228 for (i = 0; i < nitems(flags); i++) 229 if (len == flags[i].len && iseq(p, flags[i].name)) 230 return flags[i].value; 231 warnx("unknown flag \"%.*s\" at line %" PRIu64 " ignored", 232 len, p, XML_GetCurrentLineNumber64(mt->parser)); 233 return 0; 234 #undef iseq 235 } 236 237 static void 238 end_element(void *data, const char *name) 239 { 240 #define iseq(a,b) (strcasecmp(a,b) == 0) 241 struct mystate *mt; 242 int len; 243 char *p; 244 245 mt = data; 246 sbuf_finish(mt->sbuf[mt->level]); 247 p = sbuf_data(mt->sbuf[mt->level]); 248 len = sbuf_len(mt->sbuf[mt->level]); 249 250 /* <freqband>...</freqband> */ 251 if (iseq(name, "freqstart") && mt->freqband != NULL) { 252 mt->freqband->freqStart = strtoul(p, NULL, 0); 253 goto done; 254 } 255 if (iseq(name, "freqend") && mt->freqband != NULL) { 256 mt->freqband->freqEnd = strtoul(p, NULL, 0); 257 goto done; 258 } 259 if (iseq(name, "chanwidth") && mt->freqband != NULL) { 260 mt->freqband->chanWidth = strtoul(p, NULL, 0); 261 goto done; 262 } 263 if (iseq(name, "chansep") && mt->freqband != NULL) { 264 mt->freqband->chanSep = strtoul(p, NULL, 0); 265 goto done; 266 } 267 if (iseq(name, "flags")) { 268 if (mt->freqband != NULL) 269 mt->freqband->flags |= decode_flag(mt, p, len); 270 else if (mt->netband != NULL) 271 mt->netband->flags |= decode_flag(mt, p, len); 272 else { 273 warnx("flags without freqband or netband at line %" PRIu64 " ignored", 274 XML_GetCurrentLineNumber64(mt->parser)); 275 } 276 goto done; 277 } 278 279 /* <rd> ... </rd> */ 280 if (iseq(name, "name") && mt->rd != NULL) { 281 mt->rd->name = strdup(p); 282 goto done; 283 } 284 if (iseq(name, "sku") && mt->rd != NULL) { 285 mt->rd->sku = strtoul(p, NULL, 0); 286 goto done; 287 } 288 if (iseq(name, "netband") && mt->rd != NULL) { 289 mt->curband = NULL; 290 goto done; 291 } 292 293 /* <band> ... </band> */ 294 if (iseq(name, "freqband") && mt->netband != NULL) { 295 /* XXX handle inline freqbands */ 296 goto done; 297 } 298 if (iseq(name, "maxpower") && mt->netband != NULL) { 299 mt->netband->maxPower = strtoul(p, NULL, 0); 300 goto done; 301 } 302 if (iseq(name, "maxpowerdfs") && mt->netband != NULL) { 303 mt->netband->maxPowerDFS = strtoul(p, NULL, 0); 304 goto done; 305 } 306 if (iseq(name, "maxantgain") && mt->netband != NULL) { 307 mt->netband->maxAntGain = strtoul(p, NULL, 0); 308 goto done; 309 } 310 311 /* <country>...</country> */ 312 if (iseq(name, "isocc") && mt->country != NULL) { 313 mt->country->code = strtoul(p, NULL, 0); 314 goto done; 315 } 316 if (iseq(name, "name") && mt->country != NULL) { 317 mt->country->name = strdup(p); 318 goto done; 319 } 320 321 if (len != 0) { 322 warnx("unexpected XML token \"%s\" data \"%s\" at line %" PRIu64, 323 name, p, XML_GetCurrentLineNumber64(mt->parser)); 324 /* XXX goto done? */ 325 } 326 /* </freqband> */ 327 if (iseq(name, "freqband") && mt->freqband != NULL) { 328 /* XXX must have start/end frequencies */ 329 /* XXX must have channel width/sep */ 330 mt->freqband = NULL; 331 goto done; 332 } 333 /* </rd> */ 334 if (iseq(name, "rd") && mt->rd != NULL) { 335 mt->rd = NULL; 336 goto done; 337 } 338 /* </band> */ 339 if (iseq(name, "band") && mt->netband != NULL) { 340 if (mt->netband->band == NULL) { 341 warnx("no freqbands for band at line %" PRIu64, 342 XML_GetCurrentLineNumber64(mt->parser)); 343 } 344 if (mt->netband->maxPower == 0) { 345 warnx("no maxpower for band at line %" PRIu64, 346 XML_GetCurrentLineNumber64(mt->parser)); 347 } 348 /* default max power w/ DFS to max power */ 349 if (mt->netband->maxPowerDFS == 0) 350 mt->netband->maxPowerDFS = mt->netband->maxPower; 351 mt->netband = NULL; 352 goto done; 353 } 354 /* </netband> */ 355 if (iseq(name, "netband") && mt->netband != NULL) { 356 mt->curband = NULL; 357 goto done; 358 } 359 /* </country> */ 360 if (iseq(name, "country") && mt->country != NULL) { 361 /* XXX NO_COUNTRY should be in the net80211 country enum */ 362 if ((int) mt->country->code == NO_COUNTRY) { 363 warnx("no ISO cc for country at line %" PRIu64, 364 XML_GetCurrentLineNumber64(mt->parser)); 365 } 366 if (mt->country->name == NULL) { 367 warnx("no name for country at line %" PRIu64, 368 XML_GetCurrentLineNumber64(mt->parser)); 369 } 370 if (mt->country->rd == NULL) { 371 warnx("no regdomain reference for country at line %" PRIu64, 372 XML_GetCurrentLineNumber64(mt->parser)); 373 } 374 mt->country = NULL; 375 goto done; 376 } 377 done: 378 sbuf_delete(mt->sbuf[mt->level]); 379 mt->sbuf[mt->level--] = NULL; 380 #undef iseq 381 } 382 383 static void 384 char_data(void *data, const XML_Char *s, int len) 385 { 386 struct mystate *mt; 387 const char *b, *e; 388 389 mt = data; 390 391 b = s; 392 e = s + len-1; 393 for (; isspace(*b) && b < e; b++) 394 ; 395 for (; isspace(*e) && e > b; e++) 396 ; 397 if (e != b || (*b != '\0' && !isspace(*b))) 398 sbuf_bcat(mt->sbuf[mt->level], b, e-b+1); 399 } 400 401 static void * 402 findid(struct regdata *rdp, const void *id, int type) 403 { 404 struct ident *ip; 405 406 for (ip = rdp->ident; ip->id != NULL; ip++) 407 if ((int) ip->type == type && strcasecmp(ip->id, id) == 0) 408 return ip->p; 409 return NULL; 410 } 411 412 /* 413 * Parse an regdomain XML configuration and build the internal representation. 414 */ 415 int 416 lib80211_regdomain_readconfig(struct regdata *rdp, const void *p, size_t len) 417 { 418 struct mystate *mt; 419 struct regdomain *dp; 420 struct country *cp; 421 struct freqband *fp; 422 struct netband *nb; 423 const void *id; 424 int i, errors; 425 426 memset(rdp, 0, sizeof(struct regdata)); 427 mt = calloc(1, sizeof(struct mystate)); 428 if (mt == NULL) 429 return ENOMEM; 430 /* parse the XML input */ 431 mt->rdp = rdp; 432 mt->parser = XML_ParserCreate(NULL); 433 XML_SetUserData(mt->parser, mt); 434 XML_SetElementHandler(mt->parser, start_element, end_element); 435 XML_SetCharacterDataHandler(mt->parser, char_data); 436 if (XML_Parse(mt->parser, p, len, 1) != XML_STATUS_OK) { 437 warnx("%s: %s at line %" PRIu64, __func__, 438 XML_ErrorString(XML_GetErrorCode(mt->parser)), 439 XML_GetCurrentLineNumber64(mt->parser)); 440 return -1; 441 } 442 XML_ParserFree(mt->parser); 443 444 /* setup the identifer table */ 445 rdp->ident = calloc(mt->nident + 1, sizeof(struct ident)); 446 if (rdp->ident == NULL) 447 return ENOMEM; 448 free(mt); 449 450 errors = 0; 451 i = 0; 452 LIST_FOREACH(dp, &rdp->domains, next) { 453 rdp->ident[i].id = dp->name; 454 rdp->ident[i].p = dp; 455 rdp->ident[i].type = DOMAIN; 456 i++; 457 } 458 LIST_FOREACH(fp, &rdp->freqbands, next) { 459 rdp->ident[i].id = fp->id; 460 rdp->ident[i].p = fp; 461 rdp->ident[i].type = FREQBAND; 462 i++; 463 } 464 LIST_FOREACH(cp, &rdp->countries, next) { 465 rdp->ident[i].id = cp->isoname; 466 rdp->ident[i].p = cp; 467 rdp->ident[i].type = COUNTRY; 468 i++; 469 } 470 471 /* patch references */ 472 LIST_FOREACH(dp, &rdp->domains, next) { 473 if (dp->cc != NULL) { 474 id = dp->cc; 475 dp->cc = findid(rdp, id, COUNTRY); 476 if (dp->cc == NULL) { 477 warnx("undefined country \"%s\"", 478 __DECONST(char *, id)); 479 errors++; 480 } 481 free(__DECONST(char *, id)); 482 } 483 LIST_FOREACH(nb, &dp->bands_11b, next) { 484 id = findid(rdp, nb->band, FREQBAND); 485 if (id == NULL) { 486 warnx("undefined 11b band \"%s\"", 487 __DECONST(char *, nb->band)); 488 errors++; 489 } 490 nb->band = id; 491 } 492 LIST_FOREACH(nb, &dp->bands_11g, next) { 493 id = findid(rdp, nb->band, FREQBAND); 494 if (id == NULL) { 495 warnx("undefined 11g band \"%s\"", 496 __DECONST(char *, nb->band)); 497 errors++; 498 } 499 nb->band = id; 500 } 501 LIST_FOREACH(nb, &dp->bands_11a, next) { 502 id = findid(rdp, nb->band, FREQBAND); 503 if (id == NULL) { 504 warnx("undefined 11a band \"%s\"", 505 __DECONST(char *, nb->band)); 506 errors++; 507 } 508 nb->band = id; 509 } 510 LIST_FOREACH(nb, &dp->bands_11ng, next) { 511 id = findid(rdp, nb->band, FREQBAND); 512 if (id == NULL) { 513 warnx("undefined 11ng band \"%s\"", 514 __DECONST(char *, nb->band)); 515 errors++; 516 } 517 nb->band = id; 518 } 519 LIST_FOREACH(nb, &dp->bands_11na, next) { 520 id = findid(rdp, nb->band, FREQBAND); 521 if (id == NULL) { 522 warnx("undefined 11na band \"%s\"", 523 __DECONST(char *, nb->band)); 524 errors++; 525 } 526 nb->band = id; 527 } 528 LIST_FOREACH(nb, &dp->bands_11ac, next) { 529 id = findid(rdp, nb->band, FREQBAND); 530 if (id == NULL) { 531 warnx("undefined 11ac band \"%s\"", 532 __DECONST(char *, nb->band)); 533 errors++; 534 } 535 nb->band = id; 536 } 537 LIST_FOREACH(nb, &dp->bands_11acg, next) { 538 id = findid(rdp, nb->band, FREQBAND); 539 if (id == NULL) { 540 warnx("undefined 11acg band \"%s\"", 541 __DECONST(char *, nb->band)); 542 errors++; 543 } 544 nb->band = id; 545 } 546 } 547 LIST_FOREACH(cp, &rdp->countries, next) { 548 id = cp->rd; 549 cp->rd = findid(rdp, id, DOMAIN); 550 if (cp->rd == NULL) { 551 warnx("undefined country \"%s\"", 552 __DECONST(char *, id)); 553 errors++; 554 } 555 free(__DECONST(char *, id)); 556 } 557 558 return errors ? EINVAL : 0; 559 } 560 561 static void 562 cleanup_bands(netband_head *head) 563 { 564 struct netband *nb; 565 566 for (;;) { 567 nb = LIST_FIRST(head); 568 if (nb == NULL) 569 break; 570 LIST_REMOVE(nb, next); 571 free(nb); 572 } 573 } 574 575 /* 576 * Cleanup state/resources for a previously parsed regdomain database. 577 */ 578 void 579 lib80211_regdomain_cleanup(struct regdata *rdp) 580 { 581 582 free(rdp->ident); 583 rdp->ident = NULL; 584 for (;;) { 585 struct regdomain *dp = LIST_FIRST(&rdp->domains); 586 if (dp == NULL) 587 break; 588 LIST_REMOVE(dp, next); 589 cleanup_bands(&dp->bands_11b); 590 cleanup_bands(&dp->bands_11g); 591 cleanup_bands(&dp->bands_11a); 592 cleanup_bands(&dp->bands_11ng); 593 cleanup_bands(&dp->bands_11na); 594 cleanup_bands(&dp->bands_11ac); 595 cleanup_bands(&dp->bands_11acg); 596 if (dp->name != NULL) 597 free(__DECONST(char *, dp->name)); 598 } 599 for (;;) { 600 struct country *cp = LIST_FIRST(&rdp->countries); 601 if (cp == NULL) 602 break; 603 LIST_REMOVE(cp, next); 604 if (cp->name != NULL) 605 free(__DECONST(char *, cp->name)); 606 free(cp); 607 } 608 for (;;) { 609 struct freqband *fp = LIST_FIRST(&rdp->freqbands); 610 if (fp == NULL) 611 break; 612 LIST_REMOVE(fp, next); 613 free(fp); 614 } 615 } 616 617 struct regdata * 618 lib80211_alloc_regdata(void) 619 { 620 struct regdata *rdp; 621 struct stat sb; 622 void *xml; 623 int fd; 624 625 rdp = calloc(1, sizeof(struct regdata)); 626 627 fd = open(_PATH_REGDOMAIN, O_RDONLY); 628 if (fd < 0) { 629 #ifdef DEBUG 630 warn("%s: open(%s)", __func__, _PATH_REGDOMAIN); 631 #endif 632 free(rdp); 633 return NULL; 634 } 635 if (fstat(fd, &sb) < 0) { 636 #ifdef DEBUG 637 warn("%s: fstat(%s)", __func__, _PATH_REGDOMAIN); 638 #endif 639 close(fd); 640 free(rdp); 641 return NULL; 642 } 643 xml = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); 644 if (xml == MAP_FAILED) { 645 #ifdef DEBUG 646 warn("%s: mmap", __func__); 647 #endif 648 close(fd); 649 free(rdp); 650 return NULL; 651 } 652 if (lib80211_regdomain_readconfig(rdp, xml, sb.st_size) != 0) { 653 #ifdef DEBUG 654 warn("%s: error reading regulatory database", __func__); 655 #endif 656 munmap(xml, sb.st_size); 657 close(fd); 658 free(rdp); 659 return NULL; 660 } 661 munmap(xml, sb.st_size); 662 close(fd); 663 664 return rdp; 665 } 666 667 void 668 lib80211_free_regdata(struct regdata *rdp) 669 { 670 lib80211_regdomain_cleanup(rdp); 671 free(rdp); 672 } 673 674 /* 675 * Lookup a regdomain by SKU. 676 */ 677 const struct regdomain * 678 lib80211_regdomain_findbysku(const struct regdata *rdp, enum RegdomainCode sku) 679 { 680 const struct regdomain *dp; 681 682 LIST_FOREACH(dp, &rdp->domains, next) { 683 if (dp->sku == sku) 684 return dp; 685 } 686 return NULL; 687 } 688 689 /* 690 * Lookup a regdomain by name. 691 */ 692 const struct regdomain * 693 lib80211_regdomain_findbyname(const struct regdata *rdp, const char *name) 694 { 695 const struct regdomain *dp; 696 697 LIST_FOREACH(dp, &rdp->domains, next) { 698 if (strcasecmp(dp->name, name) == 0) 699 return dp; 700 } 701 return NULL; 702 } 703 704 /* 705 * Lookup a country by ISO country code. 706 */ 707 const struct country * 708 lib80211_country_findbycc(const struct regdata *rdp, enum ISOCountryCode cc) 709 { 710 const struct country *cp; 711 712 LIST_FOREACH(cp, &rdp->countries, next) { 713 if (cp->code == cc) 714 return cp; 715 } 716 return NULL; 717 } 718 719 /* 720 * Lookup a country by ISO/long name. 721 */ 722 const struct country * 723 lib80211_country_findbyname(const struct regdata *rdp, const char *name) 724 { 725 const struct country *cp; 726 int len; 727 728 len = strlen(name); 729 LIST_FOREACH(cp, &rdp->countries, next) { 730 if (strcasecmp(cp->isoname, name) == 0) 731 return cp; 732 } 733 LIST_FOREACH(cp, &rdp->countries, next) { 734 if (strncasecmp(cp->name, name, len) == 0) 735 return cp; 736 } 737 return NULL; 738 } 739