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