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