c4ca06b9b3
header is now in two parts: bsdxml.h and bsdxml_external.h, representing the expat.h and expat_external.h headers. Updated the info on the man page as well. Also, fixed a type-error in a printf in sbin/ifconfig/regdomain.c that would cause a compiler warning. Approved by: sam, phk
637 lines
15 KiB
C
637 lines
15 KiB
C
/*-
|
|
* Copyright (c) 2008 Sam Leffler, Errno Consulting
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
#ifndef lint
|
|
static const char rcsid[] = "$FreeBSD$";
|
|
#endif /* not lint */
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/errno.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/sbuf.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <fcntl.h>
|
|
#include <err.h>
|
|
#include <unistd.h>
|
|
|
|
#include <bsdxml.h>
|
|
|
|
#include "regdomain.h"
|
|
|
|
#include <net80211/_ieee80211.h>
|
|
|
|
#define MAXLEVEL 20
|
|
|
|
struct mystate {
|
|
struct regdata *rdp;
|
|
struct regdomain *rd; /* current domain */
|
|
struct netband *netband; /* current netband */
|
|
struct freqband *freqband; /* current freqband */
|
|
struct country *country; /* current country */
|
|
netband_head *curband; /* current netband list */
|
|
int level;
|
|
struct sbuf *sbuf[MAXLEVEL];
|
|
int nident;
|
|
};
|
|
|
|
struct ident {
|
|
const void *id;
|
|
void *p;
|
|
enum { DOMAIN, COUNTRY, FREQBAND } type;
|
|
};
|
|
|
|
static void
|
|
start_element(void *data, const char *name, const char **attr)
|
|
{
|
|
#define iseq(a,b) (strcasecmp(a,b) == 0)
|
|
struct mystate *mt;
|
|
const void *id, *ref, *mode;
|
|
int i;
|
|
|
|
mt = data;
|
|
if (++mt->level == MAXLEVEL) {
|
|
/* XXX force parser to abort */
|
|
return;
|
|
}
|
|
mt->sbuf[mt->level] = sbuf_new(NULL, NULL, 0, SBUF_AUTOEXTEND);
|
|
id = ref = mode = NULL;
|
|
for (i = 0; attr[i] != NULL; i += 2) {
|
|
if (iseq(attr[i], "id")) {
|
|
id = attr[i+1];
|
|
} else if (iseq(attr[i], "ref")) {
|
|
ref = attr[i+1];
|
|
} else if (iseq(attr[i], "mode")) {
|
|
mode = attr[i+1];
|
|
} else
|
|
printf("%*.*s[%s = %s]\n", mt->level + 1,
|
|
mt->level + 1, "", attr[i], attr[i+1]);
|
|
}
|
|
if (iseq(name, "rd") && mt->rd == NULL) {
|
|
if (mt->country == NULL) {
|
|
mt->rd = calloc(1, sizeof(struct regdomain));
|
|
mt->rd->name = strdup(id);
|
|
mt->nident++;
|
|
LIST_INSERT_HEAD(&mt->rdp->domains, mt->rd, next);
|
|
} else
|
|
mt->country->rd = (void *)strdup(ref);
|
|
return;
|
|
}
|
|
if (iseq(name, "defcc") && mt->rd != NULL) {
|
|
mt->rd->cc = (void *)strdup(ref);
|
|
return;
|
|
}
|
|
if (iseq(name, "netband") && mt->curband == NULL && mt->rd != NULL) {
|
|
if (mode == NULL) {
|
|
/* XXX complain */
|
|
return;
|
|
}
|
|
if (iseq(mode, "11b"))
|
|
mt->curband = &mt->rd->bands_11b;
|
|
else if (iseq(mode, "11g"))
|
|
mt->curband = &mt->rd->bands_11g;
|
|
else if (iseq(mode, "11a"))
|
|
mt->curband = &mt->rd->bands_11a;
|
|
else if (iseq(mode, "11ng"))
|
|
mt->curband = &mt->rd->bands_11ng;
|
|
else if (iseq(mode, "11na"))
|
|
mt->curband = &mt->rd->bands_11na;
|
|
/* XXX else complain */
|
|
return;
|
|
}
|
|
if (iseq(name, "band") && mt->netband == NULL) {
|
|
if (mt->curband == NULL) {
|
|
/* XXX complain */
|
|
return;
|
|
}
|
|
mt->netband = calloc(1, sizeof(struct netband));
|
|
LIST_INSERT_HEAD(mt->curband, mt->netband, next);
|
|
return;
|
|
}
|
|
if (iseq(name, "freqband") && mt->freqband == NULL && mt->netband != NULL) {
|
|
/* XXX handle inlines and merge into table? */
|
|
if (mt->netband->band != NULL) {
|
|
/* XXX complain */
|
|
} else
|
|
mt->netband->band = (void *)strdup(ref);
|
|
return;
|
|
}
|
|
|
|
if (iseq(name, "country") && mt->country == NULL) {
|
|
mt->country = calloc(1, sizeof(struct country));
|
|
mt->country->isoname = strdup(id);
|
|
mt->nident++;
|
|
LIST_INSERT_HEAD(&mt->rdp->countries, mt->country, next);
|
|
return;
|
|
}
|
|
|
|
if (iseq(name, "freqband") && mt->freqband == NULL) {
|
|
mt->freqband = calloc(1, sizeof(struct freqband));
|
|
mt->freqband->id = strdup(id);
|
|
mt->nident++;
|
|
LIST_INSERT_HEAD(&mt->rdp->freqbands, mt->freqband, next);
|
|
return;
|
|
}
|
|
#undef iseq
|
|
}
|
|
|
|
static uint32_t
|
|
decode_flag(const char *p, int len)
|
|
{
|
|
#define iseq(a,b) (strcasecmp(a,b) == 0)
|
|
static const struct {
|
|
const char *name;
|
|
int len;
|
|
uint32_t value;
|
|
} flags[] = {
|
|
#define FLAG(x) { #x, sizeof(#x), x }
|
|
FLAG(IEEE80211_CHAN_A),
|
|
FLAG(IEEE80211_CHAN_B),
|
|
FLAG(IEEE80211_CHAN_G),
|
|
FLAG(IEEE80211_CHAN_HT20),
|
|
FLAG(IEEE80211_CHAN_HT40),
|
|
FLAG(IEEE80211_CHAN_ST),
|
|
FLAG(IEEE80211_CHAN_TURBO),
|
|
FLAG(IEEE80211_CHAN_PASSIVE),
|
|
FLAG(IEEE80211_CHAN_DFS),
|
|
FLAG(IEEE80211_CHAN_CCK),
|
|
FLAG(IEEE80211_CHAN_OFDM),
|
|
FLAG(IEEE80211_CHAN_2GHZ),
|
|
FLAG(IEEE80211_CHAN_5GHZ),
|
|
FLAG(IEEE80211_CHAN_DYN),
|
|
FLAG(IEEE80211_CHAN_GFSK),
|
|
FLAG(IEEE80211_CHAN_GSM),
|
|
FLAG(IEEE80211_CHAN_STURBO),
|
|
FLAG(IEEE80211_CHAN_HALF),
|
|
FLAG(IEEE80211_CHAN_QUARTER),
|
|
FLAG(IEEE80211_CHAN_HT40U),
|
|
FLAG(IEEE80211_CHAN_HT40D),
|
|
FLAG(IEEE80211_CHAN_4MSXMIT),
|
|
FLAG(IEEE80211_CHAN_NOADHOC),
|
|
FLAG(IEEE80211_CHAN_NOHOSTAP),
|
|
FLAG(IEEE80211_CHAN_11D),
|
|
FLAG(IEEE80211_CHAN_FHSS),
|
|
FLAG(IEEE80211_CHAN_PUREG),
|
|
FLAG(IEEE80211_CHAN_108A),
|
|
FLAG(IEEE80211_CHAN_108G),
|
|
#undef FLAG
|
|
};
|
|
int i;
|
|
|
|
for (i = 0; i < sizeof(flags)/sizeof(flags[0]); i++)
|
|
if (len == flags[i].len && iseq(p, flags[i].name))
|
|
return flags[i].value;
|
|
return 0;
|
|
#undef iseq
|
|
}
|
|
|
|
static void
|
|
end_element(void *data, const char *name)
|
|
{
|
|
#define iseq(a,b) (strcasecmp(a,b) == 0)
|
|
struct mystate *mt;
|
|
int len;
|
|
char *p;
|
|
|
|
mt = data;
|
|
sbuf_finish(mt->sbuf[mt->level]);
|
|
p = sbuf_data(mt->sbuf[mt->level]);
|
|
len = sbuf_len(mt->sbuf[mt->level]);
|
|
|
|
/* <freqband>...</freqband> */
|
|
if (iseq(name, "freqstart") && mt->freqband != NULL) {
|
|
mt->freqband->freqStart = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "freqend") && mt->freqband != NULL) {
|
|
mt->freqband->freqEnd = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "chanwidth") && mt->freqband != NULL) {
|
|
mt->freqband->chanWidth = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "chansep") && mt->freqband != NULL) {
|
|
mt->freqband->chanSep = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "flags")) {
|
|
if (mt->freqband != NULL)
|
|
mt->freqband->flags |= decode_flag(p, len);
|
|
else if (mt->netband != NULL)
|
|
mt->netband->flags |= decode_flag(p, len);
|
|
else {
|
|
/* XXX complain */
|
|
}
|
|
goto done;
|
|
}
|
|
|
|
/* <rd> ... </rd> */
|
|
if (iseq(name, "name") && mt->rd != NULL) {
|
|
mt->rd->name = strdup(p);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "sku") && mt->rd != NULL) {
|
|
mt->rd->sku = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "netband") && mt->rd != NULL) {
|
|
mt->curband = NULL;
|
|
goto done;
|
|
}
|
|
|
|
/* <band> ... </band> */
|
|
if (iseq(name, "freqband") && mt->netband != NULL) {
|
|
/* XXX handle inline freqbands */
|
|
goto done;
|
|
}
|
|
if (iseq(name, "maxpower") && mt->netband != NULL) {
|
|
mt->netband->maxPower = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "maxpowerdfs") && mt->netband != NULL) {
|
|
mt->netband->maxPowerDFS = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
|
|
/* <country>...</country> */
|
|
if (iseq(name, "isocc") && mt->country != NULL) {
|
|
mt->country->code = strtoul(p, NULL, 0);
|
|
goto done;
|
|
}
|
|
if (iseq(name, "name") && mt->country != NULL) {
|
|
mt->country->name = strdup(p);
|
|
goto done;
|
|
}
|
|
|
|
if (len != 0) {
|
|
printf("Unexpected XML: name \"%s\" data \"%s\"\n", name, p);
|
|
/* XXX goto done? */
|
|
}
|
|
/* </freqband> */
|
|
if (iseq(name, "freqband") && mt->freqband != NULL) {
|
|
/* XXX must have start/end frequencies */
|
|
/* XXX must have channel width/sep */
|
|
mt->freqband = NULL;
|
|
goto done;
|
|
}
|
|
/* </rd> */
|
|
if (iseq(name, "rd") && mt->rd != NULL) {
|
|
mt->rd = NULL;
|
|
goto done;
|
|
}
|
|
/* </band> */
|
|
if (iseq(name, "band") && mt->netband != NULL) {
|
|
if (mt->netband->band == NULL) {
|
|
printf("No frequency band information at line %d\n",
|
|
#if 0
|
|
XML_GetCurrentLineNumber(parser));
|
|
#else
|
|
0);
|
|
#endif
|
|
}
|
|
if (mt->netband->maxPower == 0) {
|
|
/* XXX complain */
|
|
}
|
|
/* default max power w/ DFS to max power */
|
|
if (mt->netband->maxPowerDFS == 0)
|
|
mt->netband->maxPowerDFS = mt->netband->maxPower;
|
|
mt->netband = NULL;
|
|
goto done;
|
|
}
|
|
/* </netband> */
|
|
if (iseq(name, "netband") && mt->netband != NULL) {
|
|
mt->curband = NULL;
|
|
goto done;
|
|
}
|
|
/* </country> */
|
|
if (iseq(name, "country") && mt->country != NULL) {
|
|
if (mt->country->code == 0) {
|
|
/* XXX must have iso cc */
|
|
}
|
|
if (mt->country->name == NULL) {
|
|
/* XXX must have name */
|
|
}
|
|
if (mt->country->rd == NULL) {
|
|
/* XXX? rd ref? */
|
|
}
|
|
mt->country = NULL;
|
|
goto done;
|
|
}
|
|
done:
|
|
sbuf_delete(mt->sbuf[mt->level]);
|
|
mt->sbuf[mt->level--] = NULL;
|
|
#undef iseq
|
|
}
|
|
|
|
static void
|
|
char_data(void *data, const XML_Char *s, int len)
|
|
{
|
|
struct mystate *mt;
|
|
const char *b, *e;
|
|
|
|
mt = data;
|
|
|
|
b = s;
|
|
e = s + len-1;
|
|
for (; isspace(*b) && b < e; b++)
|
|
;
|
|
for (; isspace(*e) && e > b; e++)
|
|
;
|
|
if (e != b || (*b != '\0' && !isspace(*b)))
|
|
sbuf_bcat(mt->sbuf[mt->level], b, e-b+1);
|
|
}
|
|
|
|
static void *
|
|
findid(struct regdata *rdp, const void *id, int type)
|
|
{
|
|
struct ident *ip;
|
|
|
|
for (ip = rdp->ident; ip->id != NULL; ip++)
|
|
if (ip->type == type && strcasecmp(ip->id, id) == 0)
|
|
return ip->p;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Parse an regdomain XML configuration and build the internal representation.
|
|
*/
|
|
int
|
|
lib80211_regdomain_readconfig(struct regdata *rdp, const void *p, size_t len)
|
|
{
|
|
XML_Parser parser;
|
|
struct mystate *mt;
|
|
struct regdomain *dp;
|
|
struct country *cp;
|
|
struct freqband *fp;
|
|
struct netband *nb;
|
|
const void *id;
|
|
int i;
|
|
|
|
memset(rdp, 0, sizeof(struct regdata));
|
|
mt = calloc(1, sizeof(struct mystate));
|
|
if (mt == NULL)
|
|
return ENOMEM;
|
|
/* parse the XML input */
|
|
mt->rdp = rdp;
|
|
parser = XML_ParserCreate(NULL);
|
|
XML_SetUserData(parser, mt);
|
|
XML_SetElementHandler(parser, start_element, end_element);
|
|
XML_SetCharacterDataHandler(parser, char_data);
|
|
if (XML_Parse(parser, p, len, 1) != XML_STATUS_OK) {
|
|
warnx("%s: %s at line %ld", __func__,
|
|
XML_ErrorString(XML_GetErrorCode(parser)),
|
|
XML_GetCurrentLineNumber(parser));
|
|
return -1;
|
|
}
|
|
XML_ParserFree(parser);
|
|
|
|
/* setup the identifer table */
|
|
rdp->ident = calloc(sizeof(struct ident), mt->nident + 1);
|
|
if (rdp->ident == NULL)
|
|
return ENOMEM;
|
|
free(mt);
|
|
i = 0;
|
|
LIST_FOREACH(dp, &rdp->domains, next) {
|
|
rdp->ident[i].id = dp->name;
|
|
rdp->ident[i].p = dp;
|
|
rdp->ident[i].type = DOMAIN;
|
|
i++;
|
|
}
|
|
LIST_FOREACH(fp, &rdp->freqbands, next) {
|
|
rdp->ident[i].id = fp->id;
|
|
rdp->ident[i].p = fp;
|
|
rdp->ident[i].type = FREQBAND;
|
|
i++;
|
|
}
|
|
LIST_FOREACH(cp, &rdp->countries, next) {
|
|
rdp->ident[i].id = cp->isoname;
|
|
rdp->ident[i].p = cp;
|
|
rdp->ident[i].type = COUNTRY;
|
|
i++;
|
|
}
|
|
|
|
/* patch references */
|
|
LIST_FOREACH(dp, &rdp->domains, next) {
|
|
if (dp->cc != NULL) {
|
|
id = dp->cc;
|
|
dp->cc = findid(rdp, id, COUNTRY);
|
|
free(__DECONST(char *, id));
|
|
}
|
|
LIST_FOREACH(nb, &dp->bands_11b, next)
|
|
nb->band = findid(rdp, nb->band, FREQBAND);
|
|
LIST_FOREACH(nb, &dp->bands_11g, next)
|
|
nb->band = findid(rdp, nb->band, FREQBAND);
|
|
LIST_FOREACH(nb, &dp->bands_11a, next)
|
|
nb->band = findid(rdp, nb->band, FREQBAND);
|
|
LIST_FOREACH(nb, &dp->bands_11ng, next)
|
|
nb->band = findid(rdp, nb->band, FREQBAND);
|
|
LIST_FOREACH(nb, &dp->bands_11na, next)
|
|
nb->band = findid(rdp, nb->band, FREQBAND);
|
|
}
|
|
LIST_FOREACH(cp, &rdp->countries, next) {
|
|
id = cp->rd;
|
|
cp->rd = findid(rdp, id, DOMAIN);
|
|
free(__DECONST(char *, id));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
cleanup_bands(netband_head *head)
|
|
{
|
|
struct netband *nb;
|
|
|
|
for (;;) {
|
|
nb = LIST_FIRST(head);
|
|
if (nb == NULL)
|
|
break;
|
|
free(nb);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Cleanup state/resources for a previously parsed regdomain database.
|
|
*/
|
|
void
|
|
lib80211_regdomain_cleanup(struct regdata *rdp)
|
|
{
|
|
|
|
free(rdp->ident);
|
|
rdp->ident = NULL;
|
|
for (;;) {
|
|
struct regdomain *dp = LIST_FIRST(&rdp->domains);
|
|
if (dp == NULL)
|
|
break;
|
|
LIST_REMOVE(dp, next);
|
|
cleanup_bands(&dp->bands_11b);
|
|
cleanup_bands(&dp->bands_11g);
|
|
cleanup_bands(&dp->bands_11a);
|
|
cleanup_bands(&dp->bands_11ng);
|
|
cleanup_bands(&dp->bands_11na);
|
|
if (dp->name != NULL)
|
|
free(__DECONST(char *, dp->name));
|
|
}
|
|
for (;;) {
|
|
struct country *cp = LIST_FIRST(&rdp->countries);
|
|
if (cp == NULL)
|
|
break;
|
|
LIST_REMOVE(cp, next);
|
|
if (cp->name != NULL)
|
|
free(__DECONST(char *, cp->name));
|
|
free(cp);
|
|
}
|
|
for (;;) {
|
|
struct freqband *fp = LIST_FIRST(&rdp->freqbands);
|
|
if (fp == NULL)
|
|
break;
|
|
LIST_REMOVE(fp, next);
|
|
free(fp);
|
|
}
|
|
}
|
|
|
|
struct regdata *
|
|
lib80211_alloc_regdata(void)
|
|
{
|
|
struct regdata *rdp;
|
|
struct stat sb;
|
|
void *xml;
|
|
int fd;
|
|
|
|
rdp = calloc(1, sizeof(struct regdata));
|
|
|
|
fd = open(_PATH_REGDOMAIN, O_RDONLY);
|
|
if (fd < 0) {
|
|
#ifdef DEBUG
|
|
warn("%s: open(%s)", __func__, _PATH_REGDOMAIN);
|
|
#endif
|
|
free(rdp);
|
|
return NULL;
|
|
}
|
|
if (fstat(fd, &sb) < 0) {
|
|
#ifdef DEBUG
|
|
warn("%s: fstat(%s)", __func__, _PATH_REGDOMAIN);
|
|
#endif
|
|
close(fd);
|
|
free(rdp);
|
|
return NULL;
|
|
}
|
|
xml = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
|
if (xml == MAP_FAILED) {
|
|
#ifdef DEBUG
|
|
warn("%s: mmap", __func__);
|
|
#endif
|
|
close(fd);
|
|
free(rdp);
|
|
return NULL;
|
|
}
|
|
if (lib80211_regdomain_readconfig(rdp, xml, sb.st_size) != 0) {
|
|
#ifdef DEBUG
|
|
warn("%s: error reading regulatory database", __func__);
|
|
#endif
|
|
munmap(xml, sb.st_size);
|
|
close(fd);
|
|
free(rdp);
|
|
return NULL;
|
|
}
|
|
munmap(xml, sb.st_size);
|
|
close(fd);
|
|
|
|
return rdp;
|
|
}
|
|
|
|
void
|
|
lib80211_free_regdata(struct regdata *rdp)
|
|
{
|
|
lib80211_regdomain_cleanup(rdp);
|
|
free(rdp);
|
|
}
|
|
|
|
/*
|
|
* Lookup a regdomain by SKU.
|
|
*/
|
|
const struct regdomain *
|
|
lib80211_regdomain_findbysku(const struct regdata *rdp, enum RegdomainCode sku)
|
|
{
|
|
const struct regdomain *dp;
|
|
|
|
LIST_FOREACH(dp, &rdp->domains, next) {
|
|
if (dp->sku == sku)
|
|
return dp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Lookup a regdomain by name.
|
|
*/
|
|
const struct regdomain *
|
|
lib80211_regdomain_findbyname(const struct regdata *rdp, const char *name)
|
|
{
|
|
const struct regdomain *dp;
|
|
|
|
LIST_FOREACH(dp, &rdp->domains, next) {
|
|
if (strcasecmp(dp->name, name) == 0)
|
|
return dp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Lookup a country by ISO country code.
|
|
*/
|
|
const struct country *
|
|
lib80211_country_findbycc(const struct regdata *rdp, enum ISOCountryCode cc)
|
|
{
|
|
const struct country *cp;
|
|
|
|
LIST_FOREACH(cp, &rdp->countries, next) {
|
|
if (cp->code == cc)
|
|
return cp;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Lookup a country by ISO/long name.
|
|
*/
|
|
const struct country *
|
|
lib80211_country_findbyname(const struct regdata *rdp, const char *name)
|
|
{
|
|
const struct country *cp;
|
|
int len;
|
|
|
|
len = strlen(name);
|
|
LIST_FOREACH(cp, &rdp->countries, next) {
|
|
if (strcasecmp(cp->isoname, name) == 0 ||
|
|
strncasecmp(cp->name, name, len) == 0)
|
|
return cp;
|
|
}
|
|
return NULL;
|
|
}
|