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