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