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
26static 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
51struct 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
64struct ident {
65	const void *id;
66	void *p;
67	enum { DOMAIN, COUNTRY, FREQBAND } type;
68};
69
70static void
71start_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
176static int
177decode_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
239static void
240end_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	}
379done:
380	sbuf_delete(mt->sbuf[mt->level]);
381	mt->sbuf[mt->level--] = NULL;
382#undef iseq
383}
384
385static void
386char_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
403static void *
404findid(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 */
417int
418lib80211_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
563static void
564cleanup_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 */
580void
581lib80211_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
619struct regdata *
620lib80211_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
669void
670lib80211_free_regdata(struct regdata *rdp)
671{
672	lib80211_regdomain_cleanup(rdp);
673	free(rdp);
674}
675
676/*
677 * Lookup a regdomain by SKU.
678 */
679const struct regdomain *
680lib80211_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 */
694const struct regdomain *
695lib80211_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 */
709const struct country *
710lib80211_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 */
724const struct country *
725lib80211_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