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