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