/*- * 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: head/sbin/ifconfig/regdomain.c 200587 2009-12-15 20:44:12Z gavin $"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include "regdomain.h" #include #define MAXLEVEL 20 struct mystate { XML_Parser parser; 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_auto(); 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) { warnx("no mode for netband at line %ld", XML_GetCurrentLineNumber(mt->parser)); 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; else warnx("unknown mode \"%s\" at line %ld", __DECONST(char *, mode), XML_GetCurrentLineNumber(mt->parser)); return; } if (iseq(name, "band") && mt->netband == NULL) { if (mt->curband == NULL) { warnx("band without enclosing netband at line %ld", XML_GetCurrentLineNumber(mt->parser)); 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) { warnx("duplicate freqband at line %ld ignored", XML_GetCurrentLineNumber(mt->parser)); /* 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->country->code = NO_COUNTRY; 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 int decode_flag(struct mystate *mt, 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)-1, 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 { "ECM", 3, REQ_ECM }, { "INDOOR", 6, REQ_INDOOR }, { "OUTDOOR", 7, REQ_OUTDOOR }, }; 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; warnx("unknown flag \"%.*s\" at line %ld ignored", len, p, XML_GetCurrentLineNumber(mt->parser)); 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]); /* ... */ 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(mt, p, len); else if (mt->netband != NULL) mt->netband->flags |= decode_flag(mt, p, len); else { warnx("flags without freqband or netband at line %ld ignored", XML_GetCurrentLineNumber(mt->parser)); } goto done; } /* ... */ 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; } /* ... */ 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; } if (iseq(name, "maxantgain") && mt->netband != NULL) { mt->netband->maxAntGain = strtoul(p, NULL, 0); goto done; } /* ... */ 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) { warnx("unexpected XML token \"%s\" data \"%s\" at line %ld", name, p, XML_GetCurrentLineNumber(mt->parser)); /* XXX goto done? */ } /* */ if (iseq(name, "freqband") && mt->freqband != NULL) { /* XXX must have start/end frequencies */ /* XXX must have channel width/sep */ mt->freqband = NULL; goto done; } /* */ if (iseq(name, "rd") && mt->rd != NULL) { mt->rd = NULL; goto done; } /* */ if (iseq(name, "band") && mt->netband != NULL) { if (mt->netband->band == NULL) { warnx("no freqbands for band at line %ld", XML_GetCurrentLineNumber(mt->parser)); } if (mt->netband->maxPower == 0) { warnx("no maxpower for band at line %ld", XML_GetCurrentLineNumber(mt->parser)); } /* default max power w/ DFS to max power */ if (mt->netband->maxPowerDFS == 0) mt->netband->maxPowerDFS = mt->netband->maxPower; mt->netband = NULL; goto done; } /* */ if (iseq(name, "netband") && mt->netband != NULL) { mt->curband = NULL; goto done; } /* */ if (iseq(name, "country") && mt->country != NULL) { if (mt->country->code == NO_COUNTRY) { warnx("no ISO cc for country at line %ld", XML_GetCurrentLineNumber(mt->parser)); } if (mt->country->name == NULL) { warnx("no name for country at line %ld", XML_GetCurrentLineNumber(mt->parser)); } if (mt->country->rd == NULL) { warnx("no regdomain reference for country at line %ld", XML_GetCurrentLineNumber(mt->parser)); } 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) { struct mystate *mt; struct regdomain *dp; struct country *cp; struct freqband *fp; struct netband *nb; const void *id; int i, errors; memset(rdp, 0, sizeof(struct regdata)); mt = calloc(1, sizeof(struct mystate)); if (mt == NULL) return ENOMEM; /* parse the XML input */ mt->rdp = rdp; mt->parser = XML_ParserCreate(NULL); XML_SetUserData(mt->parser, mt); XML_SetElementHandler(mt->parser, start_element, end_element); XML_SetCharacterDataHandler(mt->parser, char_data); if (XML_Parse(mt->parser, p, len, 1) != XML_STATUS_OK) { warnx("%s: %s at line %ld", __func__, XML_ErrorString(XML_GetErrorCode(mt->parser)), XML_GetCurrentLineNumber(mt->parser)); return -1; } XML_ParserFree(mt->parser); /* setup the identifer table */ rdp->ident = calloc(sizeof(struct ident), mt->nident + 1); if (rdp->ident == NULL) return ENOMEM; free(mt); errors = 0; 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); if (dp->cc == NULL) { warnx("undefined country \"%s\"", __DECONST(char *, id)); errors++; } free(__DECONST(char *, id)); } LIST_FOREACH(nb, &dp->bands_11b, next) { id = findid(rdp, nb->band, FREQBAND); if (id == NULL) { warnx("undefined 11b band \"%s\"", __DECONST(char *, nb->band)); errors++; } nb->band = id; } LIST_FOREACH(nb, &dp->bands_11g, next) { id = findid(rdp, nb->band, FREQBAND); if (id == NULL) { warnx("undefined 11g band \"%s\"", __DECONST(char *, nb->band)); errors++; } nb->band = id; } LIST_FOREACH(nb, &dp->bands_11a, next) { id = findid(rdp, nb->band, FREQBAND); if (id == NULL) { warnx("undefined 11a band \"%s\"", __DECONST(char *, nb->band)); errors++; } nb->band = id; } LIST_FOREACH(nb, &dp->bands_11ng, next) { id = findid(rdp, nb->band, FREQBAND); if (id == NULL) { warnx("undefined 11ng band \"%s\"", __DECONST(char *, nb->band)); errors++; } nb->band = id; } LIST_FOREACH(nb, &dp->bands_11na, next) { id = findid(rdp, nb->band, FREQBAND); if (id == NULL) { warnx("undefined 11na band \"%s\"", __DECONST(char *, nb->band)); errors++; } nb->band = id; } } LIST_FOREACH(cp, &rdp->countries, next) { id = cp->rd; cp->rd = findid(rdp, id, DOMAIN); if (cp->rd == NULL) { warnx("undefined country \"%s\"", __DECONST(char *, id)); errors++; } free(__DECONST(char *, id)); } return errors ? EINVAL : 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) return cp; } LIST_FOREACH(cp, &rdp->countries, next) { if (strncasecmp(cp->name, name, len) == 0) return cp; } return NULL; }