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