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