1 /*
2 * Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009, 2010
3 * The Regents of the University of California. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that: (1) source code distributions
7 * retain the above copyright notice and this paragraph in its entirety, (2)
8 * distributions including binary code include the above copyright notice and
9 * this paragraph in its entirety in the documentation or other materials
10 * provided with the distribution, and (3) all advertising materials mentioning
11 * features or use of this software display the following acknowledgement:
12 * ``This product includes software developed by the University of California,
13 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
14 * the University nor the names of its contributors may be used to endorse
15 * or promote products derived from this software without specific prior
16 * written permission.
17 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
18 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
20 */
21 #ifndef lint
22 static const char copyright[] =
23 "@(#) Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009, 2010\n\
24 The Regents of the University of California. All rights reserved.\n";
25 static const char rcsid[] =
26 "@(#) $Id: nslint.c 256 2010-03-06 04:14:09Z leres $ (LBL)";
27 #endif
28 /*
29 * nslint - perform consistency checks on dns files
30 */
31
32 #include <sys/types.h>
33 #include <sys/stat.h>
34 #include <sys/socket.h>
35
36 #include <netinet/in.h>
37
38 #include <arpa/inet.h>
39
40 #include <ctype.h>
41 #include <errno.h>
42 #ifdef HAVE_FCNTL_H
43 #include <fcntl.h>
44 #endif
45 #ifdef HAVE_MEMORY_H
46 #include <memory.h>
47 #endif
48 #include <netdb.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <time.h>
53 #include <unistd.h>
54
55 #include "savestr.h"
56 #include "version.h"
57
58 #include "gnuc.h"
59 #ifdef HAVE_OS_PROTO_H
60 #include "os-proto.h"
61 #endif
62
63 #define NSLINTBOOT "nslint.boot" /* default nslint.boot file */
64 #define NSLINTCONF "nslint.conf" /* default nslint.conf file */
65
66 /* Is the string just a dot by itself? */
67 #define CHECKDOT(p) (p[0] == '.' && p[1] == '\0')
68
69 /* Address (network order) */
70 struct addr {
71 u_int family;
72 union {
73 struct in_addr _a_addr4;
74 struct in6_addr _a_addr6;
75 } addr;
76 };
77 #define a_addr4 addr._a_addr4.s_addr
78 #define a_addr6 addr._a_addr6.s6_addr
79
80 /* Network */
81 struct network {
82 u_int family;
83 union {
84 struct in_addr _n_addr4;
85 struct in6_addr _n_addr6;
86 } addr;
87 union {
88 struct in_addr _n_mask4;
89 struct in6_addr _n_mask6;
90 } mask;
91 };
92 #define n_addr4 addr._n_addr4.s_addr
93 #define n_mask4 mask._n_mask4.s_addr
94 #define n_addr6 addr._n_addr6.s6_addr
95 #define n_mask6 mask._n_mask6.s6_addr
96
97 /* Item struct */
98 struct item {
99 char *host; /* pointer to hostname */
100 struct addr addr; /* ip address */
101 u_int ttl; /* ttl of A records */
102 int records; /* resource records seen */
103 int flags; /* flags word */
104 };
105
106 /* Ignored zone struct */
107 struct ignoredzone {
108 char *zone; /* zone name */
109 int len; /* length of zone */
110 };
111
112 /* Resource records seen */
113 #define REC_A 0x0001
114 #define REC_AAAA 0x0002
115 #define REC_PTR 0x0004
116 #define REC_WKS 0x0008
117 #define REC_HINFO 0x0010
118 #define REC_MX 0x0020
119 #define REC_CNAME 0x0040
120 #define REC_NS 0x0080
121 #define REC_SOA 0x0100
122 #define REC_RP 0x0200
123 #define REC_TXT 0x0400
124 #define REC_SRV 0x0800
125
126 /* These aren't real records */
127 #define REC_OTHER 0x1000
128 #define REC_REF 0x2000
129 #define REC_UNKNOWN 0x4000
130
131 /* resource record types for parsing */
132 enum rrtype {
133 RR_UNDEF = 0,
134 RR_A,
135 RR_AAAA,
136 RR_ALLOWDUPA,
137 RR_CNAME,
138 RR_DNSKEY,
139 RR_HINFO,
140 RR_MX,
141 RR_NS,
142 RR_PTR,
143 RR_RP,
144 RR_SOA,
145 RR_SRV,
146 RR_TXT,
147 RR_WKS,
148 RR_RRSIG,
149 RR_NSEC,
150 };
151
152 /* Test for records we want to map to REC_OTHER */
153 #define MASK_TEST_REC (REC_WKS | REC_HINFO | \
154 REC_MX | REC_SOA | REC_RP | REC_TXT | REC_SRV | REC_UNKNOWN)
155
156 /* Mask away records we don't care about in the final processing to REC_OTHER */
157 #define MASK_CHECK_REC \
158 (REC_A | REC_AAAA | REC_PTR | REC_CNAME | REC_REF | REC_OTHER)
159
160 /* Test for records we want to check for duplicate name detection */
161 #define MASK_TEST_DUP \
162 (REC_A | REC_AAAA | REC_HINFO | REC_CNAME)
163
164 /* Flags */
165 #define FLG_SELFMX 0x001 /* mx record refers to self */
166 #define FLG_MXREF 0x002 /* this record referred to by a mx record */
167 #define FLG_SMTPWKS 0x004 /* saw wks with smtp/tcp */
168 #define FLG_ALLOWDUPA 0x008 /* allow duplicate a records */
169
170 /* doconf() and doboot() flags */
171 #define CONF_MUSTEXIST 0x001 /* fatal for files to not exist */
172 #define CONF_NOZONE 0x002 /* do not parse zone files */
173
174 /* Test for smtp problems */
175 #define MASK_TEST_SMTP \
176 (FLG_SELFMX | FLG_SMTPWKS)
177
178 #define ITEMSIZE (1 << 17) /* power of two */
179
180 struct item items[ITEMSIZE];
181 int itemcnt; /* count of items */
182
183 /* Hostname string storage */
184 #define STRSIZE 8192; /* size to malloc when more space is needed */
185 char *strptr; /* pointer to string pool */
186 int strsize; /* size of space left in pool */
187
188 int debug;
189 int errors;
190 #ifdef __FreeBSD__
191 char *bootfile = "/etc/namedb/named.boot";
192 char *conffile = "/etc/namedb/named.conf";
193 #else
194 char *bootfile = "/etc/namedb/named.boot";
195 char *conffile = "/etc/namedb/named.conf";
196 #endif
197 char *nslintboot;
198 char *nslintconf;
199 char *prog;
200 char *cwd = ".";
201
202 static struct network *netlist;
203 static u_int netlistsize; /* size of array */
204 static u_int netlistcnt; /* next free element */
205
206 char **protoserv; /* valid protocol/service names */
207 int protoserv_init;
208 int protoserv_last;
209 int protoserv_len;
210
211 static char inaddr[] = ".in-addr.arpa.";
212 static char inaddr6[] = ".ip6.arpa.";
213
214 /* XXX should be dynamic */
215 static struct ignoredzone ignoredzones[10];
216 static int numignoredzones = 0;
217 #define SIZEIGNOREDZONES (sizeof(ignoredzones) / sizeof(ignoredzones[0]))
218
219 /* SOA record */
220 #define SOA_SERIAL 0
221 #define SOA_REFRESH 1
222 #define SOA_RETRY 2
223 #define SOA_EXPIRE 3
224 #define SOA_MINIMUM 4
225
226 static u_int soaval[5];
227 static int nsoaval;
228 #define NSOAVAL (sizeof(soaval) / sizeof(soaval[0]))
229
230 /* Forwards */
231 void add_domain(char *, const char *);
232 const char *addr2str(struct addr *);
233 int checkaddr(const char *);
234 int checkdots(const char *);
235 void checkdups(struct item *, int);
236 int checkignoredzone(const char *);
237 int checkserv(const char *, char **p);
238 int checkwks(FILE *, char *, int *, char **);
239 int cmpaddr(const void *, const void *);
240 int cmpitemaddr(const void *, const void *);
241 int cmpitemhost(const void *, const void *);
242 int cmpnetwork(const void *, const void *);
243 void doboot(const char *, int);
244 void doconf(const char *, int);
245 const char *extractaddr(const char *, struct addr *);
246 const char *extractnetwork(const char *, struct network *);
247 struct network *findnetwork(struct addr *);
248 void initprotoserv(void);
249 int isip6arpa(const char *);
250 int main(int, char **);
251 int maskwidth(struct network *);
252 const char *network2str(struct network *);
253 void nslint(void);
254 const char *parsenetwork(const char *);
255 const char *parseptr(const char *, struct addr *);
256 char *parsequoted(char *);
257 int parserrsig(const char *, char **);
258 int parsesoa(const char *, char **);
259 void process(const char *, const char *, const char *);
260 int rfc1034host(const char *, int);
261 enum rrtype txt2rrtype(const char *);
262 int samesubnet(struct addr *, struct addr *, struct network *);
263 void setmaskwidth(u_int w, struct network *);
264 int updateitem(const char *, struct addr *, int, u_int, int);
265 void usage(void) __attribute__((noreturn));
266
267 extern char *optarg;
268 extern int optind, opterr;
269
270 int
main(int argc,char ** argv)271 main(int argc, char **argv)
272 {
273 char *cp;
274 int op, donamedboot, donamedconf;
275
276 if ((cp = strrchr(argv[0], '/')) != NULL)
277 prog = cp + 1;
278 else
279 prog = argv[0];
280
281 donamedboot = 0;
282 donamedconf = 0;
283 while ((op = getopt(argc, argv, "b:c:B:C:d")) != -1)
284 switch (op) {
285
286 case 'b':
287 bootfile = optarg;
288 ++donamedboot;
289 break;
290
291 case 'c':
292 conffile = optarg;
293 ++donamedconf;
294 break;
295
296 case 'B':
297 nslintboot = optarg;
298 ++donamedboot;
299 break;
300
301 case 'C':
302 nslintconf = optarg;
303 ++donamedconf;
304 break;
305
306 case 'd':
307 ++debug;
308 break;
309
310 default:
311 usage();
312 }
313 if (optind != argc || (donamedboot && donamedconf))
314 usage();
315
316 /* Find config file if not manually specified */
317 if (!donamedboot && !donamedconf) {
318 if (access(conffile, R_OK) >= 0)
319 ++donamedconf;
320 if (access(bootfile, R_OK) >= 0)
321 ++donamedboot;
322
323 if (donamedboot && donamedconf) {
324 fprintf(stderr,
325 "%s: nslint: both %s and %s exist; use -b or -c\n",
326 prog, conffile, bootfile);
327 exit(1);
328 }
329 }
330
331 if (donamedboot) {
332 doboot(bootfile, CONF_MUSTEXIST | CONF_NOZONE);
333 if (nslintboot != NULL)
334 doboot(nslintboot, CONF_MUSTEXIST);
335 else
336 doboot(NSLINTBOOT, 0);
337 doboot(bootfile, CONF_MUSTEXIST);
338 } else {
339 doconf(conffile, CONF_MUSTEXIST | CONF_NOZONE);
340 if (nslintconf != NULL)
341 doconf(nslintconf, CONF_MUSTEXIST);
342 else
343 doconf(NSLINTCONF, 0);
344 doconf(conffile, CONF_MUSTEXIST);
345 }
346
347 /* Sort network list */
348 if (netlistcnt > 0)
349 qsort(netlist, netlistcnt, sizeof(netlist[0]), cmpnetwork);
350
351 nslint();
352 exit (errors != 0);
353 }
354
355 /* add domain if necessary */
356 void
add_domain(char * name,const char * domain)357 add_domain(char *name, const char *domain)
358 {
359 char *cp;
360
361 /* Kill trailing white space and convert to lowercase */
362 for (cp = name; *cp != '\0' && !isspace(*cp); ++cp)
363 if (isupper(*cp))
364 *cp = tolower(*cp);
365 *cp-- = '\0';
366 /* If necessary, append domain */
367 if (cp >= name && *cp++ != '.') {
368 if (*domain != '.')
369 *cp++ = '.';
370 (void)strcpy(cp, domain);
371 }
372 /* XXX should we insure a trailing dot? */
373 }
374
375 const char *
addr2str(struct addr * ap)376 addr2str(struct addr *ap)
377 {
378 struct network net;
379
380 memset(&net, 0, sizeof(net));
381 net.family = ap->family;
382 switch (ap->family) {
383
384 case AF_INET:
385 net.n_addr4 = ap->a_addr4;
386 setmaskwidth(32, &net);
387 break;
388
389 case AF_INET6:
390 memmove(net.n_addr6, &ap->a_addr6, sizeof(ap->a_addr6));
391 setmaskwidth(128, &net);
392 break;
393
394 default:
395 return ("<nil>");
396 }
397 return (network2str(&net));
398 }
399
400 /*
401 * Returns true if name is really an ip address.
402 */
403 int
checkaddr(const char * name)404 checkaddr(const char *name)
405 {
406 struct in_addr addr;
407
408 return (inet_pton(AF_INET, name, (char *)&addr));
409 }
410
411 /*
412 * Returns true if name contains a dot but not a trailing dot.
413 * Special case: allow a single dot if the second part is not one
414 * of the 3 or 4 letter top level domains or is any 2 letter TLD
415 */
416 int
checkdots(const char * name)417 checkdots(const char *name)
418 {
419 const char *cp, *cp2;
420
421 if ((cp = strchr(name, '.')) == NULL)
422 return (0);
423 cp2 = name + strlen(name) - 1;
424 if (cp2 >= name && *cp2 == '.')
425 return (0);
426
427 /* Return true of more than one dot*/
428 ++cp;
429 if (strchr(cp, '.') != NULL)
430 return (1);
431
432 if (strlen(cp) == 2 ||
433 strcasecmp(cp, "gov") == 0 ||
434 strcasecmp(cp, "edu") == 0 ||
435 strcasecmp(cp, "com") == 0 ||
436 strcasecmp(cp, "net") == 0 ||
437 strcasecmp(cp, "org") == 0 ||
438 strcasecmp(cp, "mil") == 0 ||
439 strcasecmp(cp, "int") == 0 ||
440 strcasecmp(cp, "nato") == 0 ||
441 strcasecmp(cp, "arpa") == 0)
442 return (1);
443 return (0);
444 }
445
446 /* Records we use to detect duplicates */
447 static struct duprec {
448 int record;
449 char *name;
450 } duprec[] = {
451 { REC_A, "a" },
452 { REC_AAAA, "aaaa" },
453 { REC_HINFO, "hinfo" },
454 { REC_CNAME, "cname" },
455 { 0, NULL },
456 };
457
458 void
checkdups(struct item * ip,int records)459 checkdups(struct item *ip, int records)
460 {
461 struct duprec *dp;
462
463 records &= (ip->records & MASK_TEST_DUP);
464 if (records == 0)
465 return;
466 for (dp = duprec; dp->name != NULL; ++dp)
467 if ((records & dp->record) != 0) {
468 ++errors;
469 fprintf(stderr, "%s: multiple \"%s\" records for %s\n",
470 prog, dp->name, ip->host);
471 records &= ~dp->record;
472 }
473 if (records != 0)
474 fprintf(stderr, "%s: checkdups: records not zero %s (0x%x)\n",
475 prog, ip->host, records);
476 }
477
478 /* Check for an "ignored zone" (usually dynamic dns) */
479 int
checkignoredzone(const char * name)480 checkignoredzone(const char *name)
481 {
482 int i, len, len2;
483
484 len = strlen(name);
485 if (len > 1 && name[len - 1] == '.')
486 --len;
487 for (i = 0; i < numignoredzones; ++i) {
488 len2 = len - ignoredzones[i].len;
489 if (len2 >= 0 &&
490 strncasecmp(name + len2,
491 ignoredzones[i].zone, len - len2) == 0)
492 return (1);
493 }
494 return (0);
495 }
496
497 int
checkserv(const char * serv,char ** p)498 checkserv(const char *serv, char **p)
499 {
500 for (; *p != NULL; ++p)
501 if (*serv == **p && strcmp(serv, *p) == 0)
502 return (1);
503 return (0);
504 }
505
506 int
checkwks(FILE * f,char * proto,int * smtpp,char ** errstrp)507 checkwks(FILE *f, char *proto, int *smtpp, char **errstrp)
508 {
509 int n, sawparen;
510 char *cp, *serv, **p;
511 static char errstr[132];
512 char buf[1024];
513 char psbuf[512];
514
515 if (!protoserv_init) {
516 initprotoserv();
517 ++protoserv_init;
518 }
519
520 /* Line count */
521 n = 0;
522
523 /* Terminate protocol */
524 cp = proto;
525 while (!isspace(*cp) && *cp != '\0')
526 ++cp;
527 if (*cp != '\0')
528 *cp++ = '\0';
529
530 /* Find services */
531 *smtpp = 0;
532 sawparen = 0;
533 if (*cp == '(') {
534 ++sawparen;
535 ++cp;
536 while (isspace(*cp))
537 ++cp;
538 }
539 for (;;) {
540 if (*cp == '\0') {
541 if (!sawparen)
542 break;
543 if (fgets(buf, sizeof(buf), f) == NULL) {
544 *errstrp = "mismatched parens";
545 return (n);
546 }
547 ++n;
548 cp = buf;
549 while (isspace(*cp))
550 ++cp;
551 }
552 /* Find end of service, converting to lowercase */
553 for (serv = cp; !isspace(*cp) && *cp != '\0'; ++cp)
554 if (isupper(*cp))
555 *cp = tolower(*cp);
556 if (*cp != '\0')
557 *cp++ = '\0';
558 if (sawparen && *cp == ')') {
559 /* XXX should check for trailing junk */
560 break;
561 }
562
563 (void)sprintf(psbuf, "%s/%s", serv, proto);
564
565 if (*serv == 's' && strcmp(psbuf, "tcp/smtp") == 0)
566 ++*smtpp;
567
568 for (p = protoserv; *p != NULL; ++p)
569 if (*psbuf == **p && strcmp(psbuf, *p) == 0) {
570 break;
571 }
572 if (*p == NULL) {
573 sprintf(errstr, "%s unknown", psbuf);
574 *errstrp = errstr;
575 break;
576 }
577 }
578
579 return (n);
580 }
581
582 int
cmpaddr(const void * arg1,const void * arg2)583 cmpaddr(const void *arg1, const void *arg2)
584 {
585 int i, r1;
586 const struct network *n1, *n2;
587
588 n1 = (const struct network *)arg1;
589 n2 = (const struct network *)arg2;
590
591 /* IPv4 before IPv6 */
592 if (n1->family != n2->family)
593 return ((n1->family == AF_INET) ? -1 : 1);
594
595 switch (n1->family) {
596
597 case AF_INET:
598 /* Address */
599 if (ntohl(n1->n_addr4) < ntohl(n2->n_addr4))
600 return (-1);
601 else if (ntohl(n1->n_addr4) > ntohl(n2->n_addr4))
602 return (1);
603 return (0);
604
605 case AF_INET6:
606 /* Address */
607 r1 = 0;
608 for (i = 0; i < 16; ++i) {
609 if (ntohl(n1->n_addr6[i]) < ntohl(n2->n_addr6[i]))
610 return (-1);
611 if (ntohl(n1->n_addr6[i]) > ntohl(n2->n_addr6[i]))
612 return (1);
613 }
614 return (0);
615
616 default:
617 abort();
618 }
619 }
620
621 int
cmpitemaddr(const void * arg1,const void * arg2)622 cmpitemaddr(const void *arg1, const void *arg2)
623 {
624 struct item *i1, *i2;
625
626 i1 = (struct item *)arg1;
627 i2 = (struct item *)arg2;
628
629 return (cmpaddr(&i1->addr, &i2->addr));
630 }
631
632 int
cmpitemhost(const void * arg1,const void * arg2)633 cmpitemhost(const void *arg1, const void *arg2)
634 {
635 struct item *i1, *i2;
636
637 i1 = (struct item *)arg1;
638 i2 = (struct item *)arg2;
639
640 return (strcasecmp(i1->host, i1->host));
641 }
642
643 /* Sort by network number (use mask when networks are the same) */
644 int
cmpnetwork(const void * arg1,const void * arg2)645 cmpnetwork(const void *arg1, const void *arg2)
646 {
647 int i, r1, r2;
648 const struct network *n1, *n2;
649
650 n1 = (const struct network *)arg1;
651 n2 = (const struct network *)arg2;
652
653 /* IPv4 before IPv6 */
654 if (n1->family != n2->family)
655 return ((n1->family == AF_INET) ? -1 : 1);
656
657 switch (n1->family) {
658
659 case AF_INET:
660 /* Address */
661 if (ntohl(n1->n_addr4) < ntohl(n2->n_addr4))
662 return (-1);
663 else if (ntohl(n1->n_addr4) > ntohl(n2->n_addr4))
664 return (1);
665
666 /* Mask */
667 if (ntohl(n1->n_mask4) < ntohl(n2->n_mask4))
668 return (1);
669 else if (ntohl(n1->n_mask4) > ntohl(n2->n_mask4))
670 return (-1);
671 return (0);
672
673 case AF_INET6:
674 /* Address */
675 r1 = 0;
676 for (i = 0; i < 16; ++i) {
677 if (ntohl(n1->n_addr6[i]) < ntohl(n2->n_addr6[i]))
678 return (-1);
679 if (ntohl(n1->n_addr6[i]) > ntohl(n2->n_addr6[i]))
680 return (1);
681 }
682
683 /* Mask */
684 r2 = 0;
685 for (i = 0; i < 16; ++i) {
686 if (n1->n_mask6[i] < n2->n_mask6[i])
687 return (1);
688 if (n1->n_mask6[i] > n2->n_mask6[i])
689 return (-1);
690 }
691 return (0);
692 break;
693
694 default:
695 abort();
696 }
697 abort();
698 }
699
700 void
doboot(const char * file,int flags)701 doboot(const char *file, int flags)
702 {
703 int n;
704 char *cp, *cp2;
705 FILE *f;
706 const char *errstr;
707 char buf[1024], name[128];
708
709 errno = 0;
710 f = fopen(file, "r");
711 if (f == NULL) {
712 /* Not an error if it doesn't exist */
713 if ((flags & CONF_MUSTEXIST) == 0 && errno == ENOENT) {
714 if (debug > 1)
715 printf(
716 "%s: doit: %s doesn't exist (ignoring)\n",
717 prog, file);
718 return;
719 }
720 fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
721 exit(1);
722 }
723 if (debug > 1)
724 printf("%s: doit: opened %s\n", prog, file);
725
726 n = 0;
727 while (fgets(buf, sizeof(buf), f) != NULL) {
728 ++n;
729
730 /* Skip comments */
731 if (buf[0] == ';')
732 continue;
733 cp = strchr(buf, ';');
734 if (cp)
735 *cp = '\0';
736 cp = buf + strlen(buf) - 1;
737 if (cp >= buf && *cp == '\n')
738 *cp = '\0';
739 cp = buf;
740
741 /* Eat leading whitespace */
742 while (isspace(*cp))
743 ++cp;
744
745 /* Skip blank lines */
746 if (*cp == '\n' || *cp == '\0')
747 continue;
748
749 /* Get name */
750 cp2 = cp;
751 while (!isspace(*cp) && *cp != '\0')
752 ++cp;
753 *cp++ = '\0';
754
755 /* Find next keyword */
756 while (isspace(*cp))
757 ++cp;
758 if (strcasecmp(cp2, "directory") == 0) {
759 /* Terminate directory */
760 cp2 = cp;
761 while (!isspace(*cp) && *cp != '\0')
762 ++cp;
763 *cp = '\0';
764 if (chdir(cp2) < 0) {
765 ++errors;
766 fprintf(stderr, "%s: can't chdir %s: %s\n",
767 prog, cp2, strerror(errno));
768 exit(1);
769 }
770 cwd = savestr(cp2);
771 continue;
772 }
773 if (strcasecmp(cp2, "primary") == 0) {
774 /* Extract domain, converting to lowercase */
775 for (cp2 = name; !isspace(*cp) && *cp != '\0'; ++cp)
776 if (isupper(*cp))
777 *cp2++ = tolower(*cp);
778 else
779 *cp2++ = *cp;
780 /* Insure trailing dot */
781 if (cp2 > name && cp2[-1] != '.')
782 *cp2++ = '.';
783 *cp2 = '\0';
784
785 /* Find file */
786 while (isspace(*cp))
787 ++cp;
788
789 /* Terminate directory */
790 cp2 = cp;
791 while (!isspace(*cp) && *cp != '\0')
792 ++cp;
793 *cp = '\0';
794
795 /* Process it! (zone is the same as the domain) */
796 nsoaval = -1;
797 memset(soaval, 0, sizeof(soaval));
798 if ((flags & CONF_NOZONE) == 0)
799 process(cp2, name, name);
800 continue;
801 }
802 if (strcasecmp(cp2, "network") == 0) {
803 errstr = parsenetwork(cp);
804 if (errstr != NULL) {
805 ++errors;
806 fprintf(stderr,
807 "%s: %s:%d: bad network: %s\n",
808 prog, file, n, errstr);
809 }
810 continue;
811 }
812 if (strcasecmp(cp2, "include") == 0) {
813 /* Terminate include file */
814 cp2 = cp;
815 while (!isspace(*cp) && *cp != '\0')
816 ++cp;
817 *cp = '\0';
818 doboot(cp2, 1);
819 continue;
820 }
821 /* Eat any other options */
822 }
823 (void)fclose(f);
824 }
825
826 void
doconf(const char * file,int flags)827 doconf(const char *file, int flags)
828 {
829 int n, fd, cc, i, depth;
830 char *cp, *cp2, *buf;
831 const char *p;
832 char *name, *zonename, *filename, *typename;
833 int namelen, zonenamelen, filenamelen, typenamelen;
834 struct stat sbuf;
835 char zone[128], includefile[256];
836
837 errno = 0;
838 fd = open(file, O_RDONLY, 0);
839 if (fd < 0) {
840 /* Not an error if it doesn't exist */
841 if ((flags & CONF_MUSTEXIST) == 0 && errno == ENOENT) {
842 if (debug > 1)
843 printf(
844 "%s: doconf: %s doesn't exist (ignoring)\n",
845 prog, file);
846 return;
847 }
848 fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
849 exit(1);
850 }
851 if (debug > 1)
852 printf("%s: doconf: opened %s\n", prog, file);
853
854 if (fstat(fd, &sbuf) < 0) {
855 fprintf(stderr, "%s: fstat(%s) %s\n",
856 prog, file, strerror(errno));
857 exit(1);
858 }
859 buf = (char *)malloc(sbuf.st_size + 1);
860 if (buf == NULL) {
861 fprintf(stderr, "%s: malloc: %s\n", prog, strerror(errno));
862 exit(1);
863 }
864
865 /* Slurp entire config file */
866 n = sbuf.st_size;
867 cp = buf;
868 do {
869 cc = read(fd, cp, n);
870 if (cc < 0) {
871 fprintf(stderr, "%s: read(%s) %s\n",
872 prog, file, strerror(errno));
873 exit(1);
874 }
875 cp += cc;
876 n -= cc;
877 } while (cc != 0 && cc < n);
878 buf[cc] = '\0';
879
880 #define EATWHITESPACE \
881 while (isspace(*cp)) { \
882 if (*cp == '\n') \
883 ++n; \
884 ++cp; \
885 }
886
887 /* Handle both to-end-of-line and C style comments */
888 #define EATCOMMENTS \
889 { \
890 int sawcomment; \
891 do { \
892 EATWHITESPACE \
893 sawcomment = 0; \
894 if (*cp == '#') { \
895 sawcomment = 1; \
896 ++cp; \
897 while (*cp != '\n' && *cp != '\0') \
898 ++cp; \
899 } \
900 else if (strncmp(cp, "//", 2) == 0) { \
901 sawcomment = 1; \
902 cp += 2; \
903 while (*cp != '\n' && *cp != '\0') \
904 ++cp; \
905 } \
906 else if (strncmp(cp, "/*", 2) == 0) { \
907 sawcomment = 1; \
908 for (cp += 2; *cp != '\0'; ++cp) { \
909 if (*cp == '\n') \
910 ++n; \
911 else if (strncmp(cp, "*/", 2) == 0) { \
912 cp += 2; \
913 break; \
914 } \
915 } \
916 } \
917 } while (sawcomment); \
918 }
919
920 #define GETNAME(name, len) \
921 { \
922 (name) = cp; \
923 (len) = 0; \
924 while (!isspace(*cp) && *cp != ';' && *cp != '\0') { \
925 ++(len); \
926 ++cp; \
927 } \
928 }
929
930 #define GETQUOTEDNAME(name, len) \
931 { \
932 if (*cp != '"') { \
933 ++errors; \
934 fprintf(stderr, "%s: %s:%d missing left quote\n", \
935 prog, file, n); \
936 } else \
937 ++cp; \
938 (name) = cp; \
939 (len) = 0; \
940 while (*cp != '"' && *cp != '\n' && *cp != '\0') { \
941 ++(len); \
942 ++cp; \
943 } \
944 if (*cp != '"') { \
945 ++errors; \
946 fprintf(stderr, "%s: %s:%d missing right quote\n", \
947 prog, file, n); \
948 } else \
949 ++cp; \
950 }
951
952 /* Eat everything to the next semicolon, perhaps eating matching qbraces */
953 #define EATSEMICOLON \
954 { \
955 int depth = 0; \
956 while (*cp != '\0') { \
957 EATCOMMENTS \
958 if (*cp == ';') { \
959 ++cp; \
960 if (depth == 0) \
961 break; \
962 continue; \
963 } \
964 if (*cp == '{') { \
965 ++depth; \
966 ++cp; \
967 continue; \
968 } \
969 if (*cp == '}') { \
970 --depth; \
971 ++cp; \
972 continue; \
973 } \
974 ++cp; \
975 } \
976 }
977
978 /* Eat everything to the next left qbrace */
979 #define EATSLEFTBRACE \
980 while (*cp != '\0') { \
981 EATCOMMENTS \
982 if (*cp == '{') { \
983 ++cp; \
984 break; \
985 } \
986 ++cp; \
987 }
988
989 n = 1;
990 zone[0] = '\0';
991 cp = buf;
992 while (*cp != '\0') {
993 EATCOMMENTS
994 if (*cp == '\0')
995 break;
996 GETNAME(name, namelen)
997 if (namelen == 0) {
998 ++errors;
999 fprintf(stderr, "%s: %s:%d garbage char '%c' (1)\n",
1000 prog, file, n, *cp);
1001 ++cp;
1002 continue;
1003 }
1004 EATCOMMENTS
1005 if (strncasecmp(name, "options", namelen) == 0) {
1006 EATCOMMENTS
1007 if (*cp != '{') {
1008 ++errors;
1009 fprintf(stderr,
1010 "%s: %s:%d missing left qbrace in options\n",
1011 prog, file, n);
1012 } else
1013 ++cp;
1014 EATCOMMENTS
1015 while (*cp != '}' && *cp != '\0') {
1016 EATCOMMENTS
1017 GETNAME(name, namelen)
1018 if (namelen == 0) {
1019 ++errors;
1020 fprintf(stderr,
1021 "%s: %s:%d garbage char '%c' (2)\n",
1022 prog, file, n, *cp);
1023 ++cp;
1024 break;
1025 }
1026
1027 /* If not the "directory" option, just eat it */
1028 if (strncasecmp(name, "directory",
1029 namelen) == 0) {
1030 EATCOMMENTS
1031 GETQUOTEDNAME(cp2, i)
1032 cp2[i] = '\0';
1033 if (chdir(cp2) < 0) {
1034 ++errors;
1035 fprintf(stderr,
1036 "%s: %s:.%d can't chdir %s: %s\n",
1037 prog, file, n, cp2,
1038 strerror(errno));
1039 exit(1);
1040 }
1041 cwd = savestr(cp2);
1042 }
1043 EATSEMICOLON
1044 EATCOMMENTS
1045 }
1046 ++cp;
1047 EATCOMMENTS
1048 if (*cp != ';') {
1049 ++errors;
1050 fprintf(stderr,
1051 "%s: %s:%d missing options semi\n",
1052 prog, file, n);
1053 } else
1054 ++cp;
1055 continue;
1056 }
1057 if (strncasecmp(name, "zone", namelen) == 0) {
1058 EATCOMMENTS
1059 GETQUOTEDNAME(zonename, zonenamelen)
1060 typename = NULL;
1061 filename = NULL;
1062 typenamelen = 0;
1063 filenamelen = 0;
1064 EATCOMMENTS
1065 if (strncasecmp(cp, "in", 2) == 0) {
1066 cp += 2;
1067 EATWHITESPACE
1068 } else if (strncasecmp(cp, "chaos", 5) == 0) {
1069 cp += 5;
1070 EATWHITESPACE
1071 }
1072 if (*cp != '{') { /* } */
1073 ++errors;
1074 fprintf(stderr,
1075 "%s: %s:%d missing left qbrace in zone\n",
1076 prog, file, n);
1077 continue;
1078 }
1079 depth = 0;
1080 EATCOMMENTS
1081 while (*cp != '\0') {
1082 if (*cp == '{') {
1083 ++cp;
1084 ++depth;
1085 } else if (*cp == '}') {
1086 if (--depth <= 1)
1087 break;
1088 ++cp;
1089 }
1090 EATCOMMENTS
1091 GETNAME(name, namelen)
1092 if (namelen == 0) {
1093 ++errors;
1094 fprintf(stderr,
1095 "%s: %s:%d garbage char '%c' (3)\n",
1096 prog, file, n, *cp);
1097 ++cp;
1098 break;
1099 }
1100 if (strncasecmp(name, "type",
1101 namelen) == 0) {
1102 EATCOMMENTS
1103 GETNAME(typename, typenamelen)
1104 if (namelen == 0) {
1105 ++errors;
1106 fprintf(stderr,
1107 "%s: %s:%d garbage char '%c' (4)\n",
1108 prog, file, n, *cp);
1109 ++cp;
1110 break;
1111 }
1112 } else if (strncasecmp(name, "file",
1113 namelen) == 0) {
1114 EATCOMMENTS
1115 GETQUOTEDNAME(filename, filenamelen)
1116 }
1117 /* Just ignore keywords we don't understand */
1118 EATSEMICOLON
1119 EATCOMMENTS
1120 }
1121 /* { */
1122 if (*cp != '}') {
1123 ++errors;
1124 fprintf(stderr,
1125 "%s: %s:%d missing zone right qbrace\n",
1126 prog, file, n);
1127 } else
1128 ++cp;
1129 if (*cp != ';') {
1130 ++errors;
1131 fprintf(stderr,
1132 "%s: %s:%d missing zone semi\n",
1133 prog, file, n);
1134 } else
1135 ++cp;
1136 EATCOMMENTS
1137 /* If we got something interesting, process it */
1138 if (typenamelen == 0) {
1139 ++errors;
1140 fprintf(stderr, "%s: missing zone type!\n",
1141 prog);
1142 continue;
1143 }
1144 if (strncasecmp(typename, "master", typenamelen) == 0) {
1145 if (filenamelen == 0) {
1146 ++errors;
1147 fprintf(stderr,
1148 "%s: missing zone filename!\n",
1149 prog);
1150 continue;
1151 }
1152 strncpy(zone, zonename, zonenamelen);
1153 zone[zonenamelen] = '\0';
1154 for (cp2 = zone; *cp2 != '\0'; ++cp2)
1155 if (isupper(*cp2))
1156 *cp2 = tolower(*cp2);
1157 /* Insure trailing dot */
1158 if (cp2 > zone && cp2[-1] != '.') {
1159 *cp2++ = '.';
1160 *cp2 = '\0';
1161 }
1162 filename[filenamelen] = '\0';
1163 nsoaval = -1;
1164 memset(soaval, 0, sizeof(soaval));
1165 if ((flags & CONF_NOZONE) == 0)
1166 process(filename, zone, zone);
1167 }
1168 continue;
1169 }
1170 if (strncasecmp(name, "nslint", namelen) == 0) {
1171 EATCOMMENTS
1172 if (*cp != '{') {
1173 ++errors;
1174 fprintf(stderr,
1175 "%s: %s:%d missing left qbrace in nslint\n",
1176 prog, file, n);
1177 } else
1178 ++cp;
1179 ++cp;
1180 EATCOMMENTS
1181 while (*cp != '}' && *cp != '\0') {
1182 EATCOMMENTS
1183 GETNAME(name, namelen)
1184 if (strncasecmp(name, "network",
1185 namelen) == 0) {
1186 EATCOMMENTS
1187 GETQUOTEDNAME(cp2, i)
1188
1189 cp2[i] = '\0';
1190 p = parsenetwork(cp2);
1191 if (p != NULL) {
1192 ++errors;
1193 fprintf(stderr,
1194 "%s: %s:%d: bad network: %s\n",
1195 prog, file, n, p);
1196 }
1197 } else if (strncasecmp(name, "ignorezone",
1198 namelen) == 0) {
1199 EATCOMMENTS
1200 GETQUOTEDNAME(cp2, i)
1201 cp2[i] = '\0';
1202 if (numignoredzones + 1 <
1203 sizeof(ignoredzones) /
1204 sizeof(ignoredzones[0])) {
1205 ignoredzones[numignoredzones].zone =
1206 savestr(cp2);
1207 if (ignoredzones[numignoredzones].zone != NULL) {
1208 ignoredzones[numignoredzones].len = strlen(cp2);
1209 ++numignoredzones;
1210 }
1211 }
1212 } else {
1213 ++errors;
1214 fprintf(stderr,
1215 "%s: unknown nslint \"%.*s\"\n",
1216 prog, namelen, name);
1217 }
1218 EATSEMICOLON
1219 EATCOMMENTS
1220 }
1221 ++cp;
1222 EATCOMMENTS
1223 if (*cp != ';') {
1224 ++errors;
1225 fprintf(stderr,
1226 "%s: %s:%d: missing nslint semi\n",
1227 prog, file, n);
1228 } else
1229 ++cp;
1230 continue;
1231 }
1232 if (strncasecmp(name, "include", namelen) == 0) {
1233 EATCOMMENTS
1234 GETQUOTEDNAME(filename, filenamelen)
1235 strncpy(includefile, filename, filenamelen);
1236 includefile[filenamelen] = '\0';
1237 doconf(includefile, 1);
1238 EATSEMICOLON
1239 continue;
1240 }
1241 if (strncasecmp(name, "view", namelen) == 0) {
1242 EATSLEFTBRACE
1243 continue;
1244 }
1245
1246 /* Skip over statements we don't understand */
1247 EATSEMICOLON
1248 }
1249
1250 free(buf);
1251 close(fd);
1252 }
1253
1254 const char *
extractaddr(const char * str,struct addr * ap)1255 extractaddr(const char *str, struct addr *ap)
1256 {
1257
1258 memset(ap, 0, sizeof(*ap));
1259
1260 /* Let's see what we've got here */
1261 if (strchr(str, '.') != NULL) {
1262 ap->family = AF_INET;
1263 } else if (strchr(str, ':') != NULL) {
1264 ap->family = AF_INET6;
1265 } else
1266 return ("unrecognized address type");
1267
1268 switch (ap->family) {
1269
1270 case AF_INET:
1271 if (!inet_pton(ap->family, str, &ap->a_addr4))
1272 return ("cannot parse IPv4 address");
1273
1274 break;
1275
1276 case AF_INET6:
1277 if (!inet_pton(ap->family, str, &ap->a_addr6))
1278 return ("cannot parse IPv6 address");
1279 break;
1280
1281 default:
1282 abort();
1283 }
1284
1285 return (NULL);
1286 }
1287
1288 const char *
extractnetwork(const char * str,struct network * np)1289 extractnetwork(const char *str, struct network *np)
1290 {
1291 int i;
1292 long w;
1293 char *cp, *ep;
1294 const char *p;
1295 char temp[64];
1296
1297 memset(np, 0, sizeof(*np));
1298
1299 /* Let's see what we've got here */
1300 if (strchr(str, '.') != NULL) {
1301 np->family = AF_INET;
1302 w = 32;
1303 } else if (strchr(str, ':') != NULL) {
1304 np->family = AF_INET6;
1305 w = 128;
1306 } else
1307 return ("unrecognized address type");
1308
1309 p = strchr(str, '/');
1310 if (p != NULL) {
1311 /* Mask length was specified */
1312 strncpy(temp, str, sizeof(temp));
1313 temp[sizeof(temp) - 1] = '\0';
1314 cp = strchr(temp, '/');
1315 if (cp == NULL)
1316 abort();
1317 *cp++ = '\0';
1318 ep = NULL;
1319 w = strtol(cp, &ep, 10);
1320 if (*ep != '\0')
1321 return ("garbage following mask width");
1322 str = temp;
1323 }
1324
1325 switch (np->family) {
1326
1327 case AF_INET:
1328 if (!inet_pton(np->family, str, &np->n_addr4))
1329 return ("cannot parse IPv4 address");
1330
1331 if (w > 32)
1332 return ("mask length must be <= 32");
1333 setmaskwidth(w, np);
1334
1335 if ((np->n_addr4 & ~np->n_mask4) != 0)
1336 return ("non-network bits set in addr");
1337
1338 #ifdef notdef
1339 if ((ntohl(np->n_addr4) & 0xff000000) == 0)
1340 return ("high octet must be non-zero");
1341 #endif
1342 break;
1343
1344 case AF_INET6:
1345 if (!inet_pton(np->family, str, &np->n_addr6))
1346 return ("cannot parse IPv6 address");
1347 if (w > 128)
1348 return ("mask length must be <= 128");
1349 setmaskwidth(w, np);
1350
1351 for (i = 0; i < 16; ++i) {
1352 if ((np->n_addr6[i] & ~np->n_mask6[i]) != 0)
1353 return ("non-network bits set in addr");
1354 }
1355 break;
1356
1357 default:
1358 abort();
1359 }
1360
1361 return (NULL);
1362 }
1363
1364 struct network *
findnetwork(struct addr * ap)1365 findnetwork(struct addr *ap)
1366 {
1367 int i, j;
1368 struct network *np;
1369
1370 switch (ap->family) {
1371
1372 case AF_INET:
1373 for (i = 0, np = netlist; i < netlistcnt; ++i, ++np)
1374 if ((ap->a_addr4 & np->n_mask4) == np->n_addr4)
1375 return (np);
1376 break;
1377
1378 case AF_INET6:
1379 for (i = 0, np = netlist; i < netlistcnt; ++i, ++np) {
1380 for (j = 0; j < sizeof(ap->a_addr6); ++j) {
1381 if ((ap->a_addr6[j] & np->n_mask6[j]) !=
1382 np->n_addr6[j])
1383 break;
1384 }
1385 if (j >= sizeof(ap->a_addr6))
1386 return (np);
1387 }
1388 break;
1389
1390 default:
1391 abort();
1392 }
1393 return (NULL);
1394 }
1395
1396 void
initprotoserv(void)1397 initprotoserv(void)
1398 {
1399 char *cp;
1400 struct servent *sp;
1401 char psbuf[512];
1402
1403 protoserv_len = 256;
1404 protoserv = (char **)malloc(protoserv_len * sizeof(*protoserv));
1405 if (protoserv == NULL) {
1406 fprintf(stderr, "%s: nslint: malloc: %s\n",
1407 prog, strerror(errno));
1408 exit(1);
1409 }
1410
1411 while ((sp = getservent()) != NULL) {
1412 (void)sprintf(psbuf, "%s/%s", sp->s_name, sp->s_proto);
1413
1414 /* Convert to lowercase */
1415 for (cp = psbuf; *cp != '\0'; ++cp)
1416 if (isupper(*cp))
1417 *cp = tolower(*cp);
1418
1419 if (protoserv_last + 1 >= protoserv_len) {
1420 protoserv_len <<= 1;
1421 protoserv = realloc(protoserv,
1422 protoserv_len * sizeof(*protoserv));
1423 if (protoserv == NULL) {
1424 fprintf(stderr, "%s: nslint: realloc: %s\n",
1425 prog, strerror(errno));
1426 exit(1);
1427 }
1428 }
1429 protoserv[protoserv_last] = savestr(psbuf);
1430 ++protoserv_last;
1431 }
1432 protoserv[protoserv_last] = NULL;
1433 }
1434
1435 int
isip6arpa(const char * name)1436 isip6arpa(const char *name)
1437 {
1438 const char *p;
1439 const char ip6arpa[] = ".ip6.arpa.";
1440 p = name + strlen(name) - (sizeof(ip6arpa) - 1);
1441 return (strcmp(p, ip6arpa) == 0);
1442 }
1443
1444 int
maskwidth(struct network * np)1445 maskwidth(struct network *np)
1446 {
1447 int w;
1448 int i, j;
1449 u_int32_t m, tm;
1450
1451 /* Work backwards until we find a set bit */
1452 switch (np->family) {
1453
1454 case AF_INET:
1455 m = ntohl(np->n_mask4);
1456 for (w = 32; w > 0; --w) {
1457 tm = 0xffffffff << (32 - w);
1458 if (tm == m)
1459 break;
1460 }
1461 break;
1462
1463 case AF_INET6:
1464 w = 128;
1465 for (j = 15; j >= 0; --j) {
1466 m = np->n_mask6[j];
1467 for (i = 8; i > 0; --w, --i) {
1468 tm = (0xff << (8 - i)) & 0xff;
1469 if (tm == m)
1470 return (w);
1471 }
1472 }
1473 break;
1474
1475 default:
1476 abort();
1477 }
1478 return (w);
1479 }
1480
1481 const char *
network2str(struct network * np)1482 network2str(struct network *np)
1483 {
1484 int w;
1485 size_t len, size;
1486 char *cp;
1487 static char buf[128];
1488
1489 w = maskwidth(np);
1490 switch (np->family) {
1491
1492 case AF_INET:
1493 if (inet_ntop(np->family, &np->n_addr4,
1494 buf, sizeof(buf)) == NULL) {
1495 fprintf(stderr, "network2str: v4 botch");
1496 abort();
1497 }
1498 if (w == 32)
1499 return (buf);
1500 break;
1501
1502 case AF_INET6:
1503 if (inet_ntop(np->family, &np->n_addr6,
1504 buf, sizeof(buf)) == NULL) {
1505 fprintf(stderr, "network2str: v6 botch");
1506 abort();
1507 }
1508 if (w == 128)
1509 return (buf);
1510 break;
1511
1512 default:
1513 return ("<nil>");
1514 }
1515
1516 /* Append address mask width */
1517 cp = buf;
1518 len = strlen(cp);
1519 cp += len;
1520 size = sizeof(buf) - len;
1521 (void)snprintf(cp, size, "/%d", w);
1522 return (buf);
1523 }
1524
1525 void
nslint(void)1526 nslint(void)
1527 {
1528 int n, records, flags;
1529 struct item *ip, *lastaip, **ipp, **itemlist;
1530 struct addr addr, lastaddr;
1531 struct network *np;
1532
1533 itemlist = (struct item **)calloc(itemcnt, sizeof(*ipp));
1534 if (itemlist == NULL) {
1535 fprintf(stderr, "%s: nslint: calloc: %s\n",
1536 prog, strerror(errno));
1537 exit(1);
1538 }
1539 ipp = itemlist;
1540 for (n = 0, ip = items; n < ITEMSIZE; ++n, ++ip) {
1541 if (ip->host == NULL)
1542 continue;
1543 /* Save entries with addresses for later check */
1544 if (ip->addr.family != 0)
1545 *ipp++ = ip;
1546
1547 if (debug > 1) {
1548 if (debug > 2)
1549 printf("%d\t", n);
1550 printf("%s\t%s\t0x%x\t0x%x\n",
1551 ip->host, addr2str(&ip->addr),
1552 ip->records, ip->flags);
1553 }
1554
1555 /* Check for illegal hostnames (rfc1034) */
1556 if (rfc1034host(ip->host, ip->records))
1557 ++errors;
1558
1559 /* Check for missing ptr records (ok if also an ns record) */
1560 records = ip->records & MASK_CHECK_REC;
1561 if ((ip->records & MASK_TEST_REC) != 0)
1562 records |= REC_OTHER;
1563 switch (records) {
1564
1565 case REC_A | REC_OTHER | REC_PTR | REC_REF:
1566 case REC_A | REC_OTHER | REC_PTR:
1567 case REC_A | REC_PTR | REC_REF:
1568 case REC_A | REC_PTR:
1569 case REC_AAAA | REC_OTHER | REC_PTR | REC_REF:
1570 case REC_AAAA | REC_OTHER | REC_PTR:
1571 case REC_AAAA | REC_PTR | REC_REF:
1572 case REC_AAAA | REC_PTR:
1573 case REC_CNAME:
1574 /* These are O.K. */
1575 break;
1576
1577 case REC_CNAME | REC_REF:
1578 ++errors;
1579 fprintf(stderr, "%s: \"cname\" referenced by other"
1580 " \"cname\" or \"mx\": %s\n", prog, ip->host);
1581 break;
1582
1583 case REC_OTHER | REC_REF:
1584 case REC_OTHER:
1585 /*
1586 * This is only an error if there is an address
1587 * associated with the hostname; this means
1588 * there was a wks entry with bogus address.
1589 * Otherwise, we have an mx or hinfo.
1590 *
1591 * XXX ignore localhost for now
1592 * (use flag to indicate loopback?)
1593 */
1594 if (ip->addr.family == AF_INET &&
1595 ip->addr.a_addr4 != htonl(INADDR_LOOPBACK)) {
1596 ++errors;
1597 fprintf(stderr,
1598 "%s: \"wks\" without \"a\" and \"ptr\": %s -> %s\n",
1599 prog, ip->host, addr2str(&ip->addr));
1600 }
1601 break;
1602
1603 case REC_REF:
1604 if (!checkignoredzone(ip->host)) {
1605 ++errors;
1606 fprintf(stderr, "%s: Name referenced without"
1607 " other records: %s\n", prog, ip->host);
1608 }
1609 break;
1610
1611 case REC_A | REC_OTHER | REC_REF:
1612 case REC_A | REC_OTHER:
1613 case REC_A | REC_REF:
1614 case REC_A:
1615 case REC_AAAA | REC_OTHER | REC_REF:
1616 case REC_AAAA | REC_OTHER:
1617 case REC_AAAA | REC_REF:
1618 case REC_AAAA:
1619 ++errors;
1620 fprintf(stderr, "%s: Missing \"ptr\": %s -> %s\n",
1621 prog, ip->host, addr2str(&ip->addr));
1622 break;
1623
1624 case REC_OTHER | REC_PTR | REC_REF:
1625 case REC_OTHER | REC_PTR:
1626 case REC_PTR | REC_REF:
1627 case REC_PTR:
1628 ++errors;
1629 fprintf(stderr, "%s: Missing \"a\": %s -> %s\n",
1630 prog, ip->host, addr2str(&ip->addr));
1631 break;
1632
1633 case REC_A | REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
1634 case REC_A | REC_CNAME | REC_OTHER | REC_PTR:
1635 case REC_A | REC_CNAME | REC_OTHER | REC_REF:
1636 case REC_A | REC_CNAME | REC_OTHER:
1637 case REC_A | REC_CNAME | REC_PTR | REC_REF:
1638 case REC_A | REC_CNAME | REC_PTR:
1639 case REC_A | REC_CNAME | REC_REF:
1640 case REC_A | REC_CNAME:
1641 case REC_AAAA | REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
1642 case REC_AAAA | REC_CNAME | REC_OTHER | REC_PTR:
1643 case REC_AAAA | REC_CNAME | REC_OTHER | REC_REF:
1644 case REC_AAAA | REC_CNAME | REC_OTHER:
1645 case REC_AAAA | REC_CNAME | REC_PTR | REC_REF:
1646 case REC_AAAA | REC_CNAME | REC_PTR:
1647 case REC_AAAA | REC_CNAME | REC_REF:
1648 case REC_AAAA | REC_CNAME:
1649 case REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
1650 case REC_CNAME | REC_OTHER | REC_PTR:
1651 case REC_CNAME | REC_OTHER | REC_REF:
1652 case REC_CNAME | REC_OTHER:
1653 case REC_CNAME | REC_PTR | REC_REF:
1654 case REC_CNAME | REC_PTR:
1655 ++errors;
1656 fprintf(stderr, "%s: \"cname\" %s has other records\n",
1657 prog, ip->host);
1658 break;
1659
1660 case 0:
1661 /* Second level test */
1662 if ((ip->records & ~(REC_NS | REC_TXT)) == 0)
1663 break;
1664 /* Fall through... */
1665
1666 default:
1667 ++errors;
1668 fprintf(stderr,
1669 "%s: records == 0x%x: can't happen (%s 0x%x)\n",
1670 prog, records, ip->host, ip->records);
1671 break;
1672 }
1673
1674 /* Check for smtp problems */
1675 flags = ip->flags & MASK_TEST_SMTP;
1676
1677 if ((flags & FLG_SELFMX) != 0 &&
1678 (ip->records & (REC_A | REC_AAAA)) == 0) {
1679 ++errors;
1680 fprintf(stderr,
1681 "%s: Self \"mx\" for %s missing"
1682 " \"a\" or \"aaaa\" record\n",
1683 prog, ip->host);
1684 }
1685
1686 switch (flags) {
1687
1688 case 0:
1689 case FLG_SELFMX | FLG_SMTPWKS:
1690 /* These are O.K. */
1691 break;
1692
1693 case FLG_SELFMX:
1694 if ((ip->records & REC_WKS) != 0) {
1695 ++errors;
1696 fprintf(stderr,
1697 "%s: smtp/tcp missing from \"wks\": %s\n",
1698 prog, ip->host);
1699 }
1700 break;
1701
1702 case FLG_SMTPWKS:
1703 ++errors;
1704 fprintf(stderr,
1705 "%s: Saw smtp/tcp without self \"mx\": %s\n",
1706 prog, ip->host);
1707 break;
1708
1709 default:
1710 ++errors;
1711 fprintf(stderr,
1712 "%s: flags == 0x%x: can't happen (%s)\n",
1713 prog, flags, ip->host);
1714 }
1715
1716 /* Check for chained MX records */
1717 if ((ip->flags & (FLG_SELFMX | FLG_MXREF)) == FLG_MXREF &&
1718 (ip->records & REC_MX) != 0) {
1719 ++errors;
1720 fprintf(stderr, "%s: \"mx\" referenced by other"
1721 " \"mx\" record: %s\n", prog, ip->host);
1722 }
1723 }
1724
1725 /* Check for doubly booked addresses */
1726 n = ipp - itemlist;
1727 qsort(itemlist, n, sizeof(itemlist[0]), cmpaddr);
1728 memset(&lastaddr, 0, sizeof(lastaddr));
1729 ip = NULL;
1730 for (ipp = itemlist; n > 0; ++ipp, --n) {
1731 addr = (*ipp)->addr;
1732 if (cmpaddr(&lastaddr, &addr) == 0 &&
1733 ((*ipp)->flags & FLG_ALLOWDUPA) == 0 &&
1734 (ip->flags & FLG_ALLOWDUPA) == 0) {
1735 ++errors;
1736 fprintf(stderr, "%s: %s in use by %s and %s\n",
1737 prog, addr2str(&addr), (*ipp)->host, ip->host);
1738 }
1739 memmove(&lastaddr, &addr, sizeof(addr));
1740 ip = *ipp;
1741 }
1742
1743 /* Check for hosts with multiple addresses on the same subnet */
1744 n = ipp - itemlist;
1745 qsort(itemlist, n, sizeof(itemlist[0]), cmpitemhost);
1746 if (netlistcnt > 0) {
1747 n = ipp - itemlist;
1748 lastaip = NULL;
1749 for (ipp = itemlist; n > 0; ++ipp, --n) {
1750 ip = *ipp;
1751 if ((ip->records & (REC_A | REC_AAAA)) == 0 ||
1752 (ip->flags & FLG_ALLOWDUPA) != 0)
1753 continue;
1754 if (lastaip != NULL &&
1755 strcasecmp(ip->host, lastaip->host) == 0) {
1756 np = findnetwork(&ip->addr);
1757 if (np == NULL) {
1758 ++errors;
1759 fprintf(stderr,
1760 "%s: Can't find subnet mask"
1761 " for %s (%s)\n",
1762 prog, ip->host,
1763 addr2str(&ip->addr));
1764 } else if (samesubnet(&lastaip->addr,
1765 &ip->addr, np)) {
1766 ++errors;
1767 fprintf(stderr,
1768 "%s: Multiple \"a\" records for %s on subnet %s",
1769 prog, ip->host,
1770 network2str(np));
1771 fprintf(stderr, "\n\t(%s",
1772 addr2str(&lastaip->addr));
1773 fprintf(stderr, " and %s)\n",
1774 addr2str(&ip->addr));
1775 }
1776 }
1777 lastaip = ip;
1778 }
1779 }
1780
1781 if (debug)
1782 printf("%s: %d/%d items used, %d error%s\n", prog, itemcnt,
1783 ITEMSIZE, errors, errors == 1 ? "" : "s");
1784 }
1785
1786 const char *
parsenetwork(const char * cp)1787 parsenetwork(const char *cp)
1788 {
1789 const char *p;
1790 struct network net;
1791
1792 while (isspace(*cp))
1793 ++cp;
1794
1795 p = extractnetwork(cp, &net);
1796 if (p != NULL)
1797 return (p);
1798
1799 while (isspace(*cp))
1800 ++cp;
1801
1802 /* Make sure there's room */
1803 if (netlistsize <= netlistcnt) {
1804 if (netlistsize == 0) {
1805 netlistsize = 32;
1806 netlist = (struct network *)
1807 malloc(netlistsize * sizeof(*netlist));
1808 } else {
1809 netlistsize <<= 1;
1810 netlist = (struct network *)
1811 realloc(netlist, netlistsize * sizeof(*netlist));
1812 }
1813 if (netlist == NULL) {
1814 fprintf(stderr,
1815 "%s: parsenetwork: malloc/realloc: %s\n",
1816 prog, strerror(errno));
1817 exit(1);
1818 }
1819 }
1820
1821 /* Add to list */
1822 memmove(netlist + netlistcnt, &net, sizeof(net));
1823 ++netlistcnt;
1824
1825 return (NULL);
1826 }
1827
1828 const char *
parseptr(const char * str,struct addr * ap)1829 parseptr(const char *str, struct addr *ap)
1830 {
1831 int i, n, base;
1832 u_long v, v2;
1833 char *cp;
1834 const char *p;
1835 u_char *up;
1836
1837 memset(ap, 0, sizeof(*ap));
1838 base = -1;
1839
1840 /* IPv4 */
1841 p = str + strlen(str) - sizeof(inaddr) + 1;
1842 if (p >= str && strcasecmp(p, inaddr) == 0) {
1843 ap->family = AF_INET;
1844 n = 4;
1845 base = 10;
1846 } else {
1847 /* IPv6 */
1848 p = str + strlen(str) - sizeof(inaddr6) + 1;
1849 if (p >= str && strcasecmp(p, inaddr6) == 0) {
1850 ap->family = AF_INET6;
1851 n = 16;
1852 base = 16;
1853 }
1854 }
1855
1856 if (base < 0)
1857 return ("Not a IPv4 or IPv6 \"ptr\" record");
1858
1859 up = (u_char *)&ap->addr;
1860 for (i = 0; i < n; ++i) {
1861 /* Back up to previous dot or beginning of string */
1862 while (p > str && p[-1] != '.')
1863 --p;
1864 v = strtoul(p, &cp, base);
1865
1866 if (base == 10) {
1867 if (v > 0xff)
1868 return ("Octet larger than 8 bits");
1869 } else {
1870 if (v > 0xf)
1871 return ("Octet larger than 4 bits");
1872 if (*cp != '.')
1873 return ("Junk in \"ptr\" record");
1874
1875 /* Back up over dot */
1876 if (p > str)
1877 --p;
1878
1879 /* Back up to previous dot or beginning of string */
1880 while (p > str && p[-1] != '.')
1881 --p;
1882 v2 = strtoul(p, &cp, base);
1883 if (v2 > 0xf)
1884 return ("Octet larger than 4 bits");
1885 if (*cp != '.')
1886 return ("Junk in \"ptr\" record");
1887 v = (v << 4) | v2;
1888 }
1889 if (*cp != '.')
1890 return ("Junk in \"ptr\" record");
1891
1892 *up++ = v & 0xff;
1893
1894 /* Back up over dot */
1895 if (p > str)
1896 --p;
1897 else if (p == str)
1898 break;
1899 }
1900 if (i < n - 1)
1901 return ("Too many octets in \"ptr\" record");
1902 if (p != str)
1903 return ("Not enough octets in \"ptr\" record");
1904
1905 return (NULL);
1906 }
1907
1908 /* Returns a pointer after the next token or quoted string, else NULL */
1909 char *
parsequoted(char * cp)1910 parsequoted(char *cp)
1911 {
1912
1913 if (*cp == '"') {
1914 ++cp;
1915 while (*cp != '"' && *cp != '\0')
1916 ++cp;
1917 if (*cp != '"')
1918 return (NULL);
1919 ++cp;
1920 } else {
1921 while (!isspace(*cp) && *cp != '\0')
1922 ++cp;
1923 }
1924 return (cp);
1925 }
1926
1927 /* Return true when done */
1928 int
parserrsig(const char * str,char ** errstrp)1929 parserrsig(const char *str, char **errstrp)
1930 {
1931 const char *cp;
1932
1933 /* XXX just look for closing paren */
1934 cp = str + strlen(str) - 1;
1935 while (cp >= str)
1936 if (*cp-- == ')')
1937 return (1);
1938 return (0);
1939 }
1940
1941 /* Return true when done */
1942 int
parsesoa(const char * cp,char ** errstrp)1943 parsesoa(const char *cp, char **errstrp)
1944 {
1945 char ch, *garbage;
1946 static char errstr[132];
1947
1948 /* Eat leading whitespace */
1949 while (isspace(*cp))
1950 ++cp;
1951
1952 /* Find opening paren */
1953 if (nsoaval < 0) {
1954 cp = strchr(cp, '(');
1955 if (cp == NULL)
1956 return (0);
1957 ++cp;
1958 while (isspace(*cp))
1959 ++cp;
1960 nsoaval = 0;
1961 }
1962
1963 /* Grab any numbers we find */
1964 garbage = "leading garbage";
1965 while (isdigit(*cp) && nsoaval < NSOAVAL) {
1966 soaval[nsoaval] = atoi(cp);
1967 do {
1968 ++cp;
1969 } while (isdigit(*cp));
1970 if (nsoaval == SOA_SERIAL && *cp == '.' && isdigit(cp[1])) {
1971 do {
1972 ++cp;
1973 } while (isdigit(*cp));
1974 } else {
1975 ch = *cp;
1976 if (isupper(ch))
1977 ch = tolower(ch);
1978 switch (ch) {
1979
1980 case 'w':
1981 soaval[nsoaval] *= 7;
1982 /* fall through */
1983
1984 case 'd':
1985 soaval[nsoaval] *= 24;
1986 /* fall through */
1987
1988 case 'h':
1989 soaval[nsoaval] *= 60;
1990 /* fall through */
1991
1992 case 'm':
1993 soaval[nsoaval] *= 60;
1994 /* fall through */
1995
1996 case 's':
1997 ++cp;
1998 break;
1999
2000 default:
2001 ; /* none */
2002 }
2003 }
2004 while (isspace(*cp))
2005 ++cp;
2006 garbage = "trailing garbage";
2007 ++nsoaval;
2008 }
2009
2010 /* If we're done, do some sanity checks */
2011 if (nsoaval >= NSOAVAL && *cp == ')') {
2012 ++cp;
2013 if (*cp != '\0')
2014 *errstrp = garbage;
2015 else if (soaval[SOA_EXPIRE] <
2016 soaval[SOA_REFRESH] + 10 * soaval[SOA_RETRY]) {
2017 (void)sprintf(errstr,
2018 "expire less than refresh + 10 * retry (%u < %u + 10 * %u)",
2019 soaval[SOA_EXPIRE],
2020 soaval[SOA_REFRESH],
2021 soaval[SOA_RETRY]);
2022 *errstrp = errstr;
2023 } else if (soaval[SOA_REFRESH] < 2 * soaval[SOA_RETRY]) {
2024 (void)sprintf(errstr,
2025 "refresh less than 2 * retry (%u < 2 * %u)",
2026 soaval[SOA_REFRESH],
2027 soaval[SOA_RETRY]);
2028 *errstrp = errstr;
2029 }
2030 return (1);
2031 }
2032
2033 if (*cp != '\0') {
2034 *errstrp = garbage;
2035 return (1);
2036 }
2037
2038 return (0);
2039 }
2040
2041 void
process(const char * file,const char * domain,const char * zone)2042 process(const char *file, const char *domain, const char *zone)
2043 {
2044 FILE *f;
2045 char ch, *cp, *cp2, *cp3, *rtype;
2046 const char *p;
2047 int n, sawsoa, sawrrsig, flags, i;
2048 u_int ttl;
2049 enum rrtype rrtype;
2050 struct addr *ap;
2051 struct addr addr;
2052 // struct network *net;
2053 int smtp;
2054 char buf[2048], name[256], lastname[256], odomain[256];
2055 char *errstr;
2056 const char *addrfmt =
2057 "%s: %s/%s:%d \"%s\" target is an ip address: %s\n";
2058 const char *dotfmt =
2059 "%s: %s/%s:%d \"%s\" target missing trailing dot: %s\n";
2060
2061 /* Check for an "ignored zone" (usually dynamic dns) */
2062 if (checkignoredzone(zone))
2063 return;
2064
2065 f = fopen(file, "r");
2066 if (f == NULL) {
2067 fprintf(stderr, "%s: %s/%s: %s\n",
2068 prog, cwd, file, strerror(errno));
2069 ++errors;
2070 return;
2071 }
2072 if (debug > 1)
2073 printf("%s: process: opened %s/%s\n", prog, cwd, file);
2074
2075 /* Line number */
2076 n = 0;
2077
2078 ap = &addr;
2079
2080 lastname[0] = '\0';
2081 sawsoa = 0;
2082 sawrrsig = 0;
2083 while (fgets(buf, sizeof(buf), f) != NULL) {
2084 ++n;
2085 cp = buf;
2086 while (*cp != '\0') {
2087 /* Handle quoted strings (but don't report errors) */
2088 if (*cp == '"') {
2089 ++cp;
2090 while (*cp != '"' && *cp != '\n' && *cp != '\0')
2091 ++cp;
2092 continue;
2093 }
2094 if (*cp == '\n' || *cp == ';')
2095 break;
2096 ++cp;
2097 }
2098 *cp-- = '\0';
2099
2100 /* Nuke trailing white space */
2101 while (cp >= buf && isspace(*cp))
2102 *cp-- = '\0';
2103
2104 cp = buf;
2105 if (*cp == '\0')
2106 continue;
2107
2108 /* Handle multi-line soa records */
2109 if (sawsoa) {
2110 errstr = NULL;
2111 if (parsesoa(cp, &errstr))
2112 sawsoa = 0;
2113 if (errstr != NULL) {
2114 ++errors;
2115 fprintf(stderr,
2116 "%s: %s/%s:%d Bad \"soa\" record (%s)\n",
2117 prog, cwd, file, n, errstr);
2118 }
2119 continue;
2120 }
2121
2122 /* Handle multi-line rrsig records */
2123 if (sawrrsig) {
2124 errstr = NULL;
2125 if (parserrsig(cp, &errstr))
2126 sawsoa = 0;
2127 if (errstr != NULL) {
2128 ++errors;
2129 fprintf(stderr,
2130 "%s: %s/%s:%d Bad \"rrsig\" record (%s)\n",
2131 prog, cwd, file, n, errstr);
2132 }
2133 continue;
2134 }
2135
2136 if (debug > 3)
2137 printf(">%s<\n", cp);
2138
2139 /* Look for name */
2140 if (isspace(*cp)) {
2141 /* Same name as last record */
2142 if (lastname[0] == '\0') {
2143 ++errors;
2144 fprintf(stderr,
2145 "%s: %s/%s:%d No default name\n",
2146 prog, cwd, file, n);
2147 continue;
2148 }
2149 (void)strcpy(name, lastname);
2150 } else {
2151 /* Extract name, converting to lowercase */
2152 for (cp2 = name; !isspace(*cp) && *cp != '\0'; ++cp)
2153 if (isupper(*cp))
2154 *cp2++ = tolower(*cp);
2155 else
2156 *cp2++ = *cp;
2157 *cp2 = '\0';
2158
2159 /* Check for domain shorthand */
2160 if (name[0] == '@' && name[1] == '\0')
2161 (void)strcpy(name, domain);
2162 }
2163
2164 /* Find next token */
2165 while (isspace(*cp))
2166 ++cp;
2167
2168 /* Handle includes (gag) */
2169 if (name[0] == '$' && strcasecmp(name, "$include") == 0) {
2170 /* Extract filename */
2171 cp2 = name;
2172 while (!isspace(*cp) && *cp != '\0')
2173 *cp2++ = *cp++;
2174 *cp2 = '\0';
2175
2176 /* Look for optional domain */
2177 while (isspace(*cp))
2178 ++cp;
2179 if (*cp == '\0')
2180 process(name, domain, zone);
2181 else {
2182 cp2 = cp;
2183 /* Convert optional domain to lowercase */
2184 for (; !isspace(*cp) && *cp != '\0'; ++cp)
2185 if (isupper(*cp))
2186 *cp = tolower(*cp);
2187 *cp = '\0';
2188 process(name, cp2, cp2);
2189 }
2190 continue;
2191 }
2192
2193 /* Handle $origin */
2194 if (name[0] == '$' && strcasecmp(name, "$origin") == 0) {
2195 /* Extract domain, converting to lowercase */
2196 for (cp2 = odomain; !isspace(*cp) && *cp != '\0'; ++cp)
2197 if (isupper(*cp))
2198 *cp2++ = tolower(*cp);
2199 else
2200 *cp2++ = *cp;
2201 *cp2 = '\0';
2202 domain = odomain;
2203 lastname[0] = '\0';
2204 continue;
2205 }
2206
2207 /* Handle ttl */
2208 if (name[0] == '$' && strcasecmp(name, "$ttl") == 0) {
2209 cp2 = cp;
2210 while (isdigit(*cp))
2211 ++cp;
2212 ch = *cp;
2213 if (isupper(ch))
2214 ch = tolower(ch);
2215 if (strchr("wdhms", ch) != NULL)
2216 ++cp;
2217 while (isspace(*cp))
2218 ++cp;
2219 if (*cp != '\0') {
2220 ++errors;
2221 fprintf(stderr,
2222 "%s: %s/%s:%d Bad $ttl \"%s\"\n",
2223 prog, cwd, file, n, cp2);
2224 }
2225 (void)strcpy(name, lastname);
2226 continue;
2227 }
2228
2229 /* Parse ttl or use default */
2230 if (isdigit(*cp)) {
2231 ttl = atoi(cp);
2232 do {
2233 ++cp;
2234 } while (isdigit(*cp));
2235
2236 ch = *cp;
2237 if (isupper(ch))
2238 ch = tolower(ch);
2239 switch (ch) {
2240
2241 case 'w':
2242 ttl *= 7;
2243 /* fall through */
2244
2245 case 'd':
2246 ttl *= 24;
2247 /* fall through */
2248
2249 case 'h':
2250 ttl *= 60;
2251 /* fall through */
2252
2253 case 'm':
2254 ttl *= 60;
2255 /* fall through */
2256
2257 case 's':
2258 ++cp;
2259 break;
2260
2261 default:
2262 ; /* none */
2263 }
2264
2265 if (!isspace(*cp)) {
2266 ++errors;
2267 fprintf(stderr, "%s: %s/%s:%d Bad ttl\n",
2268 prog, cwd, file, n);
2269 continue;
2270 }
2271
2272 /* Find next token */
2273 ++cp;
2274 while (isspace(*cp))
2275 ++cp;
2276 } else
2277 ttl = soaval[SOA_MINIMUM];
2278
2279 /* Eat optional "in" */
2280 if ((cp[0] == 'i' || cp[0] == 'I') &&
2281 (cp[1] == 'n' || cp[1] == 'N') && isspace(cp[2])) {
2282 /* Find next token */
2283 cp += 3;
2284 while (isspace(*cp))
2285 ++cp;
2286 } else if ((cp[0] == 'c' || cp[0] == 'C') &&
2287 isspace(cp[5]) && strncasecmp(cp, "chaos", 5) == 0) {
2288 /* Find next token */
2289 cp += 5;
2290 while (isspace(*cp))
2291 ++cp;
2292 }
2293
2294 /* Find end of record type, converting to lowercase */
2295 rtype = cp;
2296 for (rtype = cp; !isspace(*cp) && *cp != '\0'; ++cp)
2297 if (isupper(*cp))
2298 *cp = tolower(*cp);
2299 *cp++ = '\0';
2300
2301 /* Find "the rest" */
2302 while (isspace(*cp))
2303 ++cp;
2304
2305 /* Check for non-ptr names with dots but no trailing dot */
2306 if (!isdigit(*name) &&
2307 checkdots(name) &&
2308 strcmp(domain, ".") != 0 &&
2309 !isip6arpa(domain)) {
2310 ++errors;
2311 fprintf(stderr,
2312 "%s: %s/%s:%d \"%s\" name missing trailing dot: %s\n",
2313 prog, cwd, file, n, rtype, name);
2314 }
2315
2316 /* Check for FQDNs outside the zone */
2317 cp2 = name + strlen(name) - 1;
2318 if (cp2 >= name && *cp2 == '.' && strchr(name, '.') != NULL) {
2319 cp2 = name + strlen(name) - strlen(zone);
2320 if (cp2 >= name && strcasecmp(cp2, zone) != 0) {
2321 ++errors;
2322 fprintf(stderr,
2323 "%s: %s/%s:%d \"%s\" outside zone %s\n",
2324 prog, cwd, file, n, name, zone);
2325 }
2326 }
2327
2328 rrtype = txt2rrtype(rtype);
2329 switch (rrtype) {
2330
2331 case RR_A:
2332 /* Handle "a" record */
2333 add_domain(name, domain);
2334 p = extractaddr(cp, ap);
2335 if (p != NULL) {
2336 ++errors;
2337 cp2 = cp + strlen(cp) - 1;
2338 if (cp2 >= cp && *cp2 == '\n')
2339 *cp2 = '\0';
2340 fprintf(stderr,
2341 "%s: %s/%s:%d Bad \"a\" record ip addr \"%s\"\n",
2342 prog, cwd, file, n, cp);
2343 continue;
2344 }
2345 if (ap->family != AF_INET) {
2346 ++errors;
2347 cp2 = cp + strlen(cp) - 1;
2348 if (cp2 >= cp && *cp2 == '\n')
2349 *cp2 = '\0';
2350 fprintf(stderr,
2351 "%s: %s/%s:%d \"a\"record not AF_INET \"%s\"\n",
2352 prog, cwd, file, n, cp);
2353 continue;
2354 }
2355 errors += updateitem(name, ap, REC_A, ttl, 0);
2356 break;
2357
2358 case RR_AAAA:
2359 /* Handle "aaaa" record */
2360 add_domain(name, domain);
2361 p = extractaddr(cp, ap);
2362 if (p != NULL) {
2363 ++errors;
2364 cp2 = cp + strlen(cp) - 1;
2365 if (cp2 >= cp && *cp2 == '\n')
2366 *cp2 = '\0';
2367 fprintf(stderr,
2368 "%s: %s/%s:%d Bad \"aaaa\" record ip addr \"%s\"\n",
2369 prog, cwd, file, n, cp);
2370 continue;
2371 }
2372 if (ap->family != AF_INET6) {
2373 ++errors;
2374 cp2 = cp + strlen(cp) - 1;
2375 if (cp2 >= cp && *cp2 == '\n')
2376 *cp2 = '\0';
2377 fprintf(stderr,
2378 "%s: %s/%s:%d \"aaaa\"record not AF_INET6 \"%s\"\n",
2379 prog, cwd, file, n, cp);
2380 continue;
2381 }
2382 errors += updateitem(name, ap, REC_AAAA, ttl, 0);
2383 break;
2384
2385 case RR_PTR:
2386 /* Handle "ptr" record */
2387 add_domain(name, domain);
2388 if (strcmp(cp, "@") == 0)
2389 (void)strcpy(cp, zone);
2390 if (checkdots(cp)) {
2391 ++errors;
2392 fprintf(stderr,
2393 checkaddr(cp) ? addrfmt : dotfmt,
2394 prog, cwd, file, n, rtype, cp);
2395 }
2396 add_domain(cp, domain);
2397 p = parseptr(name, ap);
2398 if (p != NULL) {
2399 ++errors;
2400 fprintf(stderr,
2401 "%s: %s/%s:%d Bad \"ptr\" record (%s) ip addr \"%s\"\n",
2402 prog, cwd, file, n, p, name);
2403 continue;
2404 }
2405 errors += updateitem(cp, ap, REC_PTR, 0, 0);
2406 break;
2407
2408 case RR_SOA:
2409 /* Handle "soa" record */
2410 if (!CHECKDOT(name)) {
2411 add_domain(name, domain);
2412 errors += updateitem(name, NULL, REC_SOA, 0, 0);
2413 }
2414 errstr = NULL;
2415 if (!parsesoa(cp, &errstr))
2416 ++sawsoa;
2417 if (errstr != NULL) {
2418 ++errors;
2419 fprintf(stderr,
2420 "%s: %s/%s:%d Bad \"soa\" record (%s)\n",
2421 prog, cwd, file, n, errstr);
2422 continue;
2423 }
2424 break;
2425
2426 case RR_WKS:
2427 /* Handle "wks" record */
2428 p = extractaddr(cp, ap);
2429 if (p != NULL) {
2430 ++errors;
2431 cp2 = cp;
2432 while (!isspace(*cp2) && *cp2 != '\0')
2433 ++cp2;
2434 *cp2 = '\0';
2435 fprintf(stderr,
2436 "%s: %s/%s:%d Bad \"wks\" record ip addr \"%s\"\n",
2437 prog, cwd, file, n, cp);
2438 continue;
2439 }
2440 /* Step over ip address */
2441 while (*cp == '.' || isdigit(*cp))
2442 ++cp;
2443 while (isspace(*cp))
2444 *cp++ = '\0';
2445 /* Make sure services are legit */
2446 errstr = NULL;
2447 n += checkwks(f, cp, &smtp, &errstr);
2448 if (errstr != NULL) {
2449 ++errors;
2450 fprintf(stderr,
2451 "%s: %s/%s:%d Bad \"wks\" record (%s)\n",
2452 prog, cwd, file, n, errstr);
2453 continue;
2454 }
2455 add_domain(name, domain);
2456 errors += updateitem(name, ap, REC_WKS,
2457 0, smtp ? FLG_SMTPWKS : 0);
2458 /* XXX check to see if ip address records exists? */
2459 break;
2460
2461 case RR_HINFO:
2462 /* Handle "hinfo" record */
2463 add_domain(name, domain);
2464 errors += updateitem(name, NULL, REC_HINFO, 0, 0);
2465 cp2 = cp;
2466 cp = parsequoted(cp);
2467 if (cp == NULL) {
2468 ++errors;
2469 fprintf(stderr,
2470 "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
2471 prog, cwd, file, n, cp2);
2472 continue;
2473 }
2474 if (!isspace(*cp)) {
2475 ++errors;
2476 fprintf(stderr,
2477 "%s: %s/%s:%d \"hinfo\" missing white space: %s\n",
2478 prog, cwd, file, n, cp2);
2479 continue;
2480 }
2481 ++cp;
2482 while (isspace(*cp))
2483 ++cp;
2484 if (*cp == '\0') {
2485 ++errors;
2486 fprintf(stderr,
2487 "%s: %s/%s:%d \"hinfo\" missing keyword: %s\n",
2488 prog, cwd, file, n, cp2);
2489 continue;
2490 }
2491 cp = parsequoted(cp);
2492 if (cp == NULL) {
2493 ++errors;
2494 fprintf(stderr,
2495 "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
2496 prog, cwd, file, n, cp2);
2497 continue;
2498 }
2499 if (*cp != '\0') {
2500 ++errors;
2501 fprintf(stderr,
2502 "%s: %s/%s:%d \"hinfo\" garbage after keywords: %s\n",
2503 prog, cwd, file, n, cp2);
2504 continue;
2505 }
2506 break;
2507
2508 case RR_MX:
2509 /* Handle "mx" record */
2510 add_domain(name, domain);
2511 errors += updateitem(name, NULL, REC_MX, ttl, 0);
2512
2513 /* Look for priority */
2514 if (!isdigit(*cp)) {
2515 ++errors;
2516 fprintf(stderr,
2517 "%s: %s/%s:%d Bad \"mx\" priority: %s\n",
2518 prog, cwd, file, n, cp);
2519 }
2520
2521 /* Skip over priority */
2522 ++cp;
2523 while (isdigit(*cp))
2524 ++cp;
2525 while (isspace(*cp))
2526 ++cp;
2527 if (*cp == '\0') {
2528 ++errors;
2529 fprintf(stderr,
2530 "%s: %s/%s:%d Missing \"mx\" hostname\n",
2531 prog, cwd, file, n);
2532 }
2533 if (strcmp(cp, "@") == 0)
2534 (void)strcpy(cp, zone);
2535 if (checkdots(cp)) {
2536 ++errors;
2537 fprintf(stderr,
2538 checkaddr(cp) ? addrfmt : dotfmt,
2539 prog, cwd, file, n, rtype, cp);
2540 }
2541
2542 /* Check to see if mx host exists */
2543 add_domain(cp, domain);
2544 flags = FLG_MXREF;
2545 if (*name == *cp && strcmp(name, cp) == 0)
2546 flags |= FLG_SELFMX;
2547 errors += updateitem(cp, NULL, REC_REF, 0, flags);
2548 break;
2549
2550 case RR_CNAME:
2551 /* Handle "cname" record */
2552 add_domain(name, domain);
2553 errors += updateitem(name, NULL, REC_CNAME, 0, 0);
2554 if (checkdots(cp)) {
2555 ++errors;
2556 fprintf(stderr,
2557 checkaddr(cp) ? addrfmt : dotfmt,
2558 prog, cwd, file, n, rtype, cp);
2559 }
2560
2561 /* Make sure cname points somewhere */
2562 if (strcmp(cp, "@") == 0)
2563 (void)strcpy(cp, zone);
2564 add_domain(cp, domain);
2565 errors += updateitem(cp, NULL, REC_REF, 0, 0);
2566 break;
2567
2568 case RR_SRV:
2569 /* Handle "srv" record */
2570 add_domain(name, domain);
2571 errors += updateitem(name, NULL, REC_SRV, 0, 0);
2572 cp2 = cp;
2573
2574 /* Skip over three values */
2575 for (i = 0; i < 3; ++i) {
2576 if (!isdigit(*cp)) {
2577 ++errors;
2578 fprintf(stderr, "%s: %s/%s:%d"
2579 " Bad \"srv\" value: %s\n",
2580 prog, cwd, file, n, cp);
2581 }
2582
2583 /* Skip over value */
2584 ++cp;
2585 while (isdigit(*cp))
2586 ++cp;
2587 while (isspace(*cp))
2588 ++cp;
2589 }
2590
2591 /* Check to see if mx host exists */
2592 add_domain(cp, domain);
2593 errors += updateitem(cp, NULL, REC_REF, 0, 0);
2594 break;
2595
2596 case RR_TXT:
2597 /* Handle "txt" record */
2598 add_domain(name, domain);
2599 errors += updateitem(name, NULL, REC_TXT, 0, 0);
2600 cp2 = cp;
2601 cp = parsequoted(cp);
2602 if (cp == NULL) {
2603 ++errors;
2604 fprintf(stderr,
2605 "%s: %s/%s:%d \"txt\" missing quote: %s\n",
2606 prog, cwd, file, n, cp2);
2607 continue;
2608 }
2609 while (isspace(*cp))
2610 ++cp;
2611 if (*cp != '\0') {
2612 ++errors;
2613 fprintf(stderr,
2614 "%s: %s/%s:%d \"txt\" garbage after text: %s\n",
2615 prog, cwd, file, n, cp2);
2616 continue;
2617 }
2618 break;
2619
2620 case RR_NS:
2621 /* Handle "ns" record */
2622 errors += updateitem(zone, NULL, REC_NS, 0, 0);
2623 if (strcmp(cp, "@") == 0)
2624 (void)strcpy(cp, zone);
2625 if (checkdots(cp)) {
2626 ++errors;
2627 fprintf(stderr,
2628 checkaddr(cp) ? addrfmt : dotfmt,
2629 prog, cwd, file, n, rtype, cp);
2630 }
2631 add_domain(cp, domain);
2632 errors += updateitem(cp, NULL, REC_REF, 0, 0);
2633 break;
2634
2635 case RR_RP:
2636 /* Handle "rp" record */
2637 add_domain(name, domain);
2638 errors += updateitem(name, NULL, REC_RP, 0, 0);
2639 cp2 = cp;
2640
2641 /* Step over mailbox name */
2642 /* XXX could add_domain() and check further */
2643 while (!isspace(*cp) && *cp != '\0')
2644 ++cp;
2645 if (*cp == '\0') {
2646 ++errors;
2647 fprintf(stderr,
2648 "%s: %s/%s:%d \"rp\" missing text name: %s\n",
2649 prog, cwd, file, n, cp2);
2650 continue;
2651 }
2652 ++cp;
2653 cp3 = cp;
2654
2655 /* Step over text name */
2656 while (!isspace(*cp) && *cp != '\0')
2657 ++cp;
2658
2659 if (*cp != '\0') {
2660 ++errors;
2661 fprintf(stderr,
2662 "%s: %s/%s:%d \"rp\" garbage after text name: %s\n",
2663 prog, cwd, file, n, cp2);
2664 continue;
2665 }
2666
2667 /* Make sure text name points somewhere (if not ".") */
2668 if (!CHECKDOT(cp3)) {
2669 add_domain(cp3, domain);
2670 errors += updateitem(cp3, NULL, REC_REF, 0, 0);
2671 }
2672 break;
2673
2674 case RR_ALLOWDUPA:
2675 /* Handle "allow duplicate a" record */
2676 add_domain(name, domain);
2677 p = extractaddr(cp, ap);
2678 if (p != NULL) {
2679 ++errors;
2680 cp2 = cp + strlen(cp) - 1;
2681 if (cp2 >= cp && *cp2 == '\n')
2682 *cp2 = '\0';
2683 fprintf(stderr,
2684 "%s: %s/%s:%d Bad \"allowdupa\" record ip addr \"%s\"\n",
2685 prog, cwd, file, n, cp);
2686 continue;
2687 }
2688 errors += updateitem(name, ap, 0, 0, FLG_ALLOWDUPA);
2689 break;
2690
2691 case RR_DNSKEY:
2692 /* Handle "dnskey" record */
2693 add_domain(name, domain);
2694 errors += updateitem(name, NULL, REC_CNAME, 0, 0);
2695 if (checkdots(cp)) {
2696 ++errors;
2697 fprintf(stderr,
2698 checkaddr(cp) ? addrfmt : dotfmt,
2699 prog, cwd, file, n, rtype, cp);
2700 }
2701
2702 /* Make sure cname points somewhere */
2703 if (strcmp(cp, "@") == 0)
2704 (void)strcpy(cp, zone);
2705 add_domain(cp, domain);
2706 errors += updateitem(cp, NULL, REC_REF, 0, 0);
2707 break;
2708
2709 case RR_RRSIG:
2710 errstr = NULL;
2711 if (!parserrsig(cp, &errstr))
2712 ++sawrrsig;
2713 if (errstr != NULL) {
2714 ++errors;
2715 fprintf(stderr,
2716 "%s: %s/%s:%d Bad \"rrsig\" record (%s)\n",
2717 prog, cwd, file, n, errstr);
2718 continue;
2719 }
2720 break;
2721
2722 case RR_NSEC:
2723 /* XXX */
2724 continue;
2725
2726 default:
2727 /* Unknown record type */
2728 ++errors;
2729 fprintf(stderr,
2730 "%s: %s/%s:%d Unknown record type \"%s\"\n",
2731 prog, cwd, file, n, rtype);
2732 add_domain(name, domain);
2733 errors += updateitem(name, NULL, REC_UNKNOWN, 0, 0);
2734 break;
2735 }
2736 (void)strcpy(lastname, name);
2737 }
2738 (void)fclose(f);
2739 return;
2740 }
2741
2742 static const char *microlist[] = {
2743 "_tcp",
2744 "_udp",
2745 "_msdcs",
2746 "_sites",
2747 NULL
2748 };
2749
2750 int
rfc1034host(const char * host,int recs)2751 rfc1034host(const char *host, int recs)
2752 {
2753 const char *cp, **p;
2754 int underok;
2755
2756 underok = 0;
2757 for (p = microlist; *p != NULL ;++p)
2758 if ((cp = strstr(host, *p)) != NULL &&
2759 cp > host &&
2760 cp[-1] == '.' &&
2761 cp[strlen(*p)] == '.') {
2762 ++underok;
2763 break;
2764 }
2765
2766 cp = host;
2767 if (!(isalpha(*cp) || isdigit(*cp) || (*cp == '_' && underok))) {
2768 fprintf(stderr,
2769 "%s: illegal hostname \"%s\" (starts with non-alpha/numeric)\n",
2770 prog, host);
2771 return (1);
2772 }
2773 for (++cp; *cp != '.' && *cp != '\0'; ++cp)
2774 if (!(isalpha(*cp) || isdigit(*cp) || *cp == '-' ||
2775 (*cp == '/' && (recs & REC_SOA) != 0))) {
2776 fprintf(stderr,
2777 "%s: Illegal hostname \"%s\" ('%c' illegal character)\n",
2778 prog, host, *cp);
2779 return (1);
2780 }
2781 if (--cp >= host && *cp == '-') {
2782 fprintf(stderr, "%s: Illegal hostname \"%s\" (ends with '-')\n",
2783 prog, host);
2784 return (1);
2785 }
2786 return (0);
2787 }
2788
2789 enum rrtype
txt2rrtype(const char * str)2790 txt2rrtype(const char *str)
2791 {
2792 if (strcasecmp(str, "aaaa") == 0)
2793 return (RR_AAAA);
2794 if (strcasecmp(str, "a") == 0)
2795 return (RR_A);
2796 if (strcasecmp(str, "allowdupa") == 0)
2797 return (RR_ALLOWDUPA);
2798 if (strcasecmp(str, "cname") == 0)
2799 return (RR_CNAME);
2800 if (strcasecmp(str, "dnskey") == 0)
2801 return (RR_DNSKEY);
2802 if (strcasecmp(str, "hinfo") == 0)
2803 return (RR_HINFO);
2804 if (strcasecmp(str, "mx") == 0)
2805 return (RR_MX);
2806 if (strcasecmp(str, "ns") == 0)
2807 return (RR_NS);
2808 if (strcasecmp(str, "ptr") == 0)
2809 return (RR_PTR);
2810 if (strcasecmp(str, "rp") == 0)
2811 return (RR_RP);
2812 if (strcasecmp(str, "soa") == 0)
2813 return (RR_SOA);
2814 if (strcasecmp(str, "srv") == 0)
2815 return (RR_SRV);
2816 if (strcasecmp(str, "txt") == 0)
2817 return (RR_TXT);
2818 if (strcasecmp(str, "wks") == 0)
2819 return (RR_WKS);
2820 if (strcasecmp(str, "RRSIG") == 0)
2821 return (RR_RRSIG);
2822 if (strcasecmp(str, "NSEC") == 0)
2823 return (RR_NSEC);
2824 return (RR_UNDEF);
2825 }
2826
2827 int
samesubnet(struct addr * a1,struct addr * a2,struct network * np)2828 samesubnet(struct addr *a1, struct addr *a2, struct network *np)
2829 {
2830 int i;
2831 u_int32_t v1, v2;
2832
2833 /* IPv4 before IPv6 */
2834 if (a1->family != a2->family)
2835 return (0);
2836
2837 switch (a1->family) {
2838
2839 case AF_INET:
2840 /* Apply the mask to both values */
2841 v1 = a1->a_addr4 & np->n_mask4;
2842 v2 = a2->a_addr4 & np->n_mask4;
2843 return (v1 == v2);
2844
2845 case AF_INET6:
2846 /* Apply the mask to both values */
2847 for (i = 0; i < 16; ++i) {
2848 v1 = a1->a_addr6[i] & np->n_mask6[i];
2849 v2 = a2->a_addr6[i] & np->n_mask6[i];
2850 if (v1 != v2)
2851 return (0);
2852 }
2853 break;
2854
2855 default:
2856 abort();
2857 }
2858 return (1);
2859 }
2860
2861 /* Set address mask in network order */
2862 void
setmaskwidth(u_int w,struct network * np)2863 setmaskwidth(u_int w, struct network *np)
2864 {
2865 int i, j;
2866
2867 switch (np->family) {
2868
2869 case AF_INET:
2870 if (w <= 0)
2871 np->n_mask4 = 0;
2872 else
2873 np->n_mask4 = htonl(0xffffffff << (32 - w));
2874 break;
2875
2876 case AF_INET6:
2877 /* XXX is this right? */
2878 memset(np->n_mask6, 0, sizeof(np->n_mask6));
2879 for (i = 0; i < w / 8; ++i)
2880 np->n_mask6[i] = 0xff;
2881 i = w / 8;
2882 j = w % 8;
2883 if (j > 0 && i < 16)
2884 np->n_mask6[i] = 0xff << (8 - j);
2885 break;
2886
2887 default:
2888 abort();
2889 }
2890 }
2891
2892 int
updateitem(const char * host,struct addr * ap,int records,u_int ttl,int flags)2893 updateitem(const char *host, struct addr *ap, int records, u_int ttl, int flags)
2894 {
2895 const char *ccp;
2896 int n, errs;
2897 u_int i;
2898 struct item *ip;
2899 int foundsome;
2900
2901 n = 0;
2902 foundsome = 0;
2903 errs = 0;
2904
2905 /* Hash the host name */
2906 i = 0;
2907 ccp = host;
2908 while (*ccp != '\0')
2909 i = i * 37 + *ccp++;
2910 ip = &items[i & (ITEMSIZE - 1)];
2911
2912 /* Look for a match or any empty slot */
2913 while (n < ITEMSIZE && ip->host != NULL) {
2914
2915 if ((ap == NULL || ip->addr.family == 0 ||
2916 cmpaddr(ap, &ip->addr) == 0) &&
2917 *host == *ip->host && strcmp(host, ip->host) == 0) {
2918 ++foundsome;
2919 if (ip->addr.family == 0 && ap != NULL)
2920 memmove(&ip->addr, ap, sizeof(*ap));
2921 if ((records & MASK_TEST_DUP) != 0)
2922 checkdups(ip, records);
2923 ip->records |= records;
2924 /* Only check differing ttl's for A and MX records */
2925 if (ip->ttl == 0)
2926 ip->ttl = ttl;
2927 else if (ttl != 0 && ip->ttl != ttl) {
2928 fprintf(stderr,
2929 "%s: Differing ttls for %s (%u != %u)\n",
2930 prog, ip->host, ttl, ip->ttl);
2931 ++errs;
2932 }
2933 ip->flags |= flags;
2934 /* Not done if we wildcard matched the name */
2935 if (ap != NULL)
2936 return (errs);
2937 }
2938 ++n;
2939 ++ip;
2940 if (ip >= &items[ITEMSIZE])
2941 ip = items;
2942 }
2943
2944 if (n >= ITEMSIZE) {
2945 fprintf(stderr, "%s: Out of item slots (max %d)\n",
2946 prog, ITEMSIZE);
2947 exit(1);
2948 }
2949
2950 /* Done if we were wildcarding the name (and found entries for it) */
2951 if (ap == NULL && foundsome) {
2952 return (errs);
2953 }
2954
2955 /* Didn't find it, make new entry */
2956 ++itemcnt;
2957 if (ip->host) {
2958 fprintf(stderr, "%s: Reusing bucket!\n", prog);
2959 exit(1);
2960 }
2961 if (ap != NULL)
2962 memmove(&ip->addr, ap, sizeof(*ap));
2963 ip->host = savestr(host);
2964 if ((records & MASK_TEST_DUP) != 0)
2965 checkdups(ip, records);
2966 ip->records |= records;
2967 if (ttl != 0)
2968 ip->ttl = ttl;
2969 ip->flags |= flags;
2970 return (errs);
2971 }
2972
2973 void
usage(void)2974 usage(void)
2975 {
2976
2977 fprintf(stderr, "Version %s\n", version);
2978 fprintf(stderr, "usage: %s [-d] [-b named.boot] [-B nslint.boot]\n",
2979 prog);
2980 fprintf(stderr, " %s [-d] [-c named.conf] [-C nslint.conf]\n",
2981 prog);
2982 exit(1);
2983 }
2984