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