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