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