No empty .Rs/.Re
[netbsd-mini2440.git] / external / bsd / bind / dist / contrib / nslint-2.1a3 / nslint.c
blob6738ff859a18cffa7acd9ea4ae7c24dee1f07a42
1 /* $NetBSD$ */
3 /*
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
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\n\
26 The Regents of the University of California. All rights reserved.\n";
27 static const char rcsid[] =
28 "@(#) Id: nslint.c,v 1.1 2001/12/21 04:12:04 marka Exp (LBL)";
29 #endif
31 * nslint - perform consistency checks on dns files
34 #include <sys/types.h>
35 #include <sys/stat.h>
37 #include <netinet/in.h>
39 #include <arpa/inet.h>
41 #include <ctype.h>
42 #include <errno.h>
43 #ifdef HAVE_FCNTL_H
44 #include <fcntl.h>
45 #endif
46 #ifdef HAVE_MALLOC_H
47 #include <malloc.h>
48 #endif
49 #ifdef HAVE_MEMORY_H
50 #include <memory.h>
51 #endif
52 #include <netdb.h>
53 #include <stdio.h>
54 #include <stdlib.h>
55 #include <string.h>
56 #include <time.h>
57 #include <unistd.h>
59 #include "savestr.h"
61 #include "gnuc.h"
62 #ifdef HAVE_OS_PROTO_H
63 #include "os-proto.h"
64 #endif
66 #define NSLINTBOOT "nslint.boot" /* default nslint.boot file */
67 #define NSLINTCONF "nslint.conf" /* default nslint.conf file */
69 /* item struct */
70 struct item {
71 char *host; /* pointer to hostname */
72 u_int32_t addr; /* ip address */
73 u_int ttl; /* ttl of A records */
74 int records; /* resource records seen */
75 int flags; /* flags word */
78 /* Resource records seen */
79 #define REC_A 0x0001
80 #define REC_PTR 0x0002
81 #define REC_WKS 0x0004
82 #define REC_HINFO 0x0008
83 #define REC_MX 0x0010
84 #define REC_CNAME 0x0020
85 #define REC_NS 0x0040
86 #define REC_SOA 0x0080
87 #define REC_RP 0x0100
88 #define REC_TXT 0x0200
89 #define REC_SRV 0x0400
91 /* These aren't real records */
92 #define REC_OTHER 0x0800
93 #define REC_REF 0x1000
94 #define REC_UNKNOWN 0x2000
96 /* Test for records we want to map to REC_OTHER */
97 #define MASK_TEST_REC (REC_WKS | REC_HINFO | \
98 REC_MX | REC_SOA | REC_RP | REC_TXT | REC_SRV | REC_UNKNOWN)
100 /* Mask away records we don't care about in the final processing to REC_OTHER */
101 #define MASK_CHECK_REC \
102 (REC_A | REC_PTR | REC_CNAME | REC_REF | REC_OTHER)
104 /* Test for records we want to check for duplicate name detection */
105 #define MASK_TEST_DUP \
106 (REC_A | REC_HINFO)
108 /* Flags */
109 #define FLG_SELFMX 0x001 /* mx record refers to self */
110 #define FLG_MXREF 0x002 /* this record referred to by a mx record */
111 #define FLG_SMTPWKS 0x004 /* saw wks with smtp/tcp */
112 #define FLG_ALLOWDUPA 0x008 /* allow duplicate a records */
114 /* Test for smtp problems */
115 #define MASK_TEST_SMTP \
116 (FLG_SELFMX | FLG_SMTPWKS)
119 #define ITEMSIZE (1 << 17) /* power of two */
120 #define ITEMHASH(str, h, p) \
121 for (p = str, h = 0; *p != '.' && *p != '\0';) h = (h << 5) - h + *p++
123 struct item items[ITEMSIZE];
124 int itemcnt; /* count of items */
126 /* Hostname string storage */
127 #define STRSIZE 8192; /* size to malloc when more space is needed */
128 char *strptr; /* pointer to string pool */
129 int strsize; /* size of space left in pool */
131 int debug;
132 int errors;
133 char *bootfile = "/etc/named.boot";
134 char *conffile = "/etc/named.conf";
135 char *nslintboot;
136 char *nslintconf;
137 char *prog;
138 char *cwd = ".";
140 char **protoserv; /* valid protocol/service names */
141 int protoserv_init;
142 int protoserv_last;
143 int protoserv_len;
145 static char inaddr[] = ".in-addr.arpa.";
147 /* SOA record */
148 #define SOA_SERIAL 0
149 #define SOA_REFRESH 1
150 #define SOA_RETRY 2
151 #define SOA_EXPIRE 3
152 #define SOA_MINIMUM 4
154 static u_int soaval[5];
155 static int nsoaval;
156 #define NSOAVAL (sizeof(soaval) / sizeof(soaval[0]))
158 /* Forwards */
159 static inline void add_domain(char *, const char *);
160 int checkdots(const char *);
161 void checkdups(struct item *, int);
162 int checkserv(const char *, char **p);
163 int checkwks(FILE *, char *, int *, char **);
164 int cmpaddr(const void *, const void *);
165 int cmphost(const void *, const void *);
166 int doboot(const char *, int);
167 int doconf(const char *, int);
168 void initprotoserv(void);
169 char *intoa(u_int32_t);
170 int main(int, char **);
171 int nslint(void);
172 int parseinaddr(const char *, u_int32_t *, u_int32_t *);
173 int parsenetwork(const char *, char **);
174 u_int32_t parseptr(const char *, u_int32_t, u_int32_t, char **);
175 char *parsequoted(char *);
176 int parsesoa(const char *, char **);
177 void process(const char *, const char *, const char *);
178 int rfc1034host(const char *, int);
179 int updateitem(const char *, u_int32_t, int, u_int, int);
180 __dead void usage(void) __attribute__((volatile));
182 extern char *optarg;
183 extern int optind, opterr;
185 /* add domain if necessary */
186 static inline void
187 add_domain(register char *name, register const char *domain)
189 register char *cp;
191 /* Kill trailing white space and convert to lowercase */
192 for (cp = name; *cp != '\0' && !isspace(*cp); ++cp)
193 if (isupper(*cp))
194 *cp = tolower(*cp);
195 *cp-- = '\0';
196 /* If necessary, append domain */
197 if (cp >= name && *cp++ != '.') {
198 if (*domain != '.')
199 *cp++ = '.';
200 (void)strcpy(cp, domain);
202 /* XXX should we insure a trailing dot? */
206 main(int argc, char **argv)
208 register char *cp;
209 register int op, status, i, donamedboot, donamedconf;
211 if ((cp = strrchr(argv[0], '/')) != NULL)
212 prog = cp + 1;
213 else
214 prog = argv[0];
216 donamedboot = 0;
217 donamedconf = 0;
218 while ((op = getopt(argc, argv, "b:c:B:C:d")) != -1)
219 switch (op) {
221 case 'b':
222 bootfile = optarg;
223 ++donamedboot;
224 break;
226 case 'c':
227 conffile = optarg;
228 ++donamedconf;
229 break;
231 case 'B':
232 nslintboot = optarg;
233 ++donamedboot;
234 break;
236 case 'C':
237 nslintconf = optarg;
238 ++donamedconf;
239 break;
241 case 'd':
242 ++debug;
243 break;
245 default:
246 usage();
248 if (optind != argc || (donamedboot && donamedconf))
249 usage();
251 if (donamedboot)
252 status = doboot(bootfile, 1);
253 else if (donamedconf)
254 status = doconf(conffile, 1);
255 else {
256 status = doconf(conffile, 0);
257 if (status < 0) {
258 status = doboot(bootfile, 1);
259 ++donamedboot;
260 } else
261 ++donamedconf;
264 if (donamedboot) {
265 if (nslintboot != NULL)
266 status |= doboot(nslintboot, 1);
267 else if ((i = doboot(NSLINTBOOT, 0)) > 0)
268 status |= i;
269 } else {
270 if (nslintconf != NULL)
271 status |= doconf(nslintconf, 1);
272 else if ((i = doconf(NSLINTCONF, 0)) > 0)
273 status |= i;
275 status |= nslint();
276 exit (status);
279 struct netlist {
280 u_int32_t net;
281 u_int32_t mask;
284 static struct netlist *netlist;
285 static u_int netlistsize; /* size of array */
286 static u_int netlistcnt; /* next free element */
288 static u_int32_t
289 findmask(u_int32_t addr)
291 register int i;
293 for (i = 0; i < netlistcnt; ++i)
294 if ((addr & netlist[i].mask) == netlist[i].net)
295 return (netlist[i].mask);
296 return (0);
300 parsenetwork(register const char *cp, register char **errstrp)
302 register int i, w;
303 register u_int32_t net, mask;
304 register u_int32_t o;
305 register int shift;
306 static char errstr[132];
308 while (isspace(*cp))
309 ++cp;
310 net = 0;
311 mask = 0;
312 shift = 24;
313 while (isdigit(*cp) && shift >= 0) {
314 o = 0;
315 do {
316 o = o * 10 + (*cp++ - '0');
317 } while (isdigit(*cp));
318 net |= o << shift;
319 shift -= 8;
320 if (*cp != '.')
321 break;
322 ++cp;
326 if (isspace(*cp)) {
327 ++cp;
328 while (isspace(*cp))
329 ++cp;
330 mask = htonl(inet_addr(cp));
331 if ((int)mask == -1) {
332 *errstrp = errstr;
333 (void)sprintf(errstr, "bad mask \"%s\"", cp);
334 return (0);
336 i = 0;
337 while (isdigit(*cp))
338 ++cp;
339 for (i = 0; i < 3 && *cp == '.'; ++i) {
340 ++cp;
341 while (isdigit(*cp))
342 ++cp;
344 if (i != 3) {
345 *errstrp = "wrong number of dots in mask";
346 return (0);
348 } else if (*cp == '/') {
349 ++cp;
350 w = atoi(cp);
351 do {
352 ++cp;
353 } while (isdigit(*cp));
354 if (w < 1 || w > 32) {
355 *errstrp = "bad mask width";
356 return (0);
358 mask = 0xffffffff << (32 - w);
359 } else {
360 *errstrp = "garbage after net";
361 return (0);
364 while (isspace(*cp))
365 ++cp;
367 if (*cp != '\0') {
368 *errstrp = "trailing garbage";
369 return (0);
372 /* Finaly sanity checks */
373 if ((net & ~ mask) != 0) {
374 *errstrp = errstr;
375 (void)sprintf(errstr, "host bits set in net \"%s\"",
376 intoa(net));
377 return (0);
380 /* Make sure there's room */
381 if (netlistsize <= netlistcnt) {
382 if (netlistsize == 0) {
383 netlistsize = 32;
384 netlist = (struct netlist *)
385 malloc(netlistsize * sizeof(*netlist));
386 } else {
387 netlistsize <<= 1;
388 netlist = (struct netlist *)
389 realloc(netlist, netlistsize * sizeof(*netlist));
391 if (netlist == NULL) {
392 fprintf(stderr, "%s: nslint: malloc/realloc: %s\n",
393 prog, strerror(errno));
394 exit(1);
398 /* Add to list */
399 netlist[netlistcnt].net = net;
400 netlist[netlistcnt].mask = mask;
401 ++netlistcnt;
403 return (1);
407 doboot(register const char *file, register int mustexist)
409 register int n;
410 register char *cp, *cp2;
411 register FILE *f;
412 char *errstr;
413 char buf[1024], name[128];
415 errno = 0;
416 f = fopen(file, "r");
417 if (f == NULL) {
418 /* Not an error if it doesn't exist */
419 if (!mustexist && errno == ENOENT) {
420 if (debug > 1)
421 printf(
422 "%s: doit: %s doesn't exist (ignoring)\n",
423 prog, file);
424 return (-1);
426 fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
427 exit(1);
429 if (debug > 1)
430 printf("%s: doit: opened %s\n", prog, file);
432 n = 0;
433 while (fgets(buf, sizeof(buf), f) != NULL) {
434 ++n;
436 /* Skip comments */
437 if (buf[0] == ';')
438 continue;
439 cp = strchr(buf, ';');
440 if (cp)
441 *cp = '\0';
442 cp = buf + strlen(buf) - 1;
443 if (cp >= buf && *cp == '\n')
444 *cp = '\0';
445 cp = buf;
447 /* Eat leading whitespace */
448 while (isspace(*cp))
449 ++cp;
451 /* Skip blank lines */
452 if (*cp == '\n' || *cp == '\0')
453 continue;
455 /* Get name */
456 cp2 = cp;
457 while (!isspace(*cp) && *cp != '\0')
458 ++cp;
459 *cp++ = '\0';
461 /* Find next keyword */
462 while (isspace(*cp))
463 ++cp;
464 if (strcasecmp(cp2, "directory") == 0) {
465 /* Terminate directory */
466 cp2 = cp;
467 while (!isspace(*cp) && *cp != '\0')
468 ++cp;
469 *cp = '\0';
470 if (chdir(cp2) < 0) {
471 ++errors;
472 fprintf(stderr, "%s: can't chdir %s: %s\n",
473 prog, cp2, strerror(errno));
474 exit(1);
476 cwd = savestr(cp2);
477 continue;
479 if (strcasecmp(cp2, "primary") == 0) {
480 /* Extract domain, converting to lowercase */
481 for (cp2 = name; !isspace(*cp) && *cp != '\0'; ++cp)
482 if (isupper(*cp))
483 *cp2++ = tolower(*cp);
484 else
485 *cp2++ = *cp;
486 /* Insure trailing dot */
487 if (cp2 > name && cp2[-1] != '.')
488 *cp2++ = '.';
489 *cp2 = '\0';
491 /* Find file */
492 while (isspace(*cp))
493 ++cp;
495 /* Terminate directory */
496 cp2 = cp;
497 while (!isspace(*cp) && *cp != '\0')
498 ++cp;
499 *cp = '\0';
501 /* Process it! (zone is the same as the domain) */
502 nsoaval = -1;
503 memset(soaval, 0, sizeof(soaval));
504 process(cp2, name, name);
505 continue;
507 if (strcasecmp(cp2, "network") == 0) {
508 if (!parsenetwork(cp, &errstr)) {
509 ++errors;
510 fprintf(stderr,
511 "%s: %s:%d: bad network: %s\n",
512 prog, file, n, errstr);
514 continue;
516 if (strcasecmp(cp2, "include") == 0) {
517 /* Terminate include file */
518 cp2 = cp;
519 while (!isspace(*cp) && *cp != '\0')
520 ++cp;
521 *cp = '\0';
522 errors += doboot(cp2, 1);
523 continue;
525 /* Eat any other options */
527 (void)fclose(f);
529 return (errors != 0);
533 doconf(register const char *file, register int mustexist)
535 register int n, fd, cc, i, depth;
536 register char *cp, *cp2, *buf;
537 register char *name, *zonename, *filename, *typename;
538 register int namelen, zonenamelen, filenamelen, typenamelen;
539 char *errstr;
540 struct stat sbuf;
541 char zone[128], includefile[256];
543 errno = 0;
544 fd = open(file, O_RDONLY, 0);
545 if (fd < 0) {
546 /* Not an error if it doesn't exist */
547 if (!mustexist && errno == ENOENT) {
548 if (debug > 1)
549 printf(
550 "%s: doconf: %s doesn't exist (ignoring)\n",
551 prog, file);
552 return (-1);
554 fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
555 exit(1);
557 if (debug > 1)
558 printf("%s: doconf: opened %s\n", prog, file);
560 if (fstat(fd, &sbuf) < 0) {
561 fprintf(stderr, "%s: fstat(%s) %s\n",
562 prog, file, strerror(errno));
563 exit(1);
565 buf = (char *)malloc(sbuf.st_size + 1);
566 if (buf == NULL) {
567 fprintf(stderr, "%s: malloc: %s\n", prog, strerror(errno));
568 exit(1);
571 /* Slurp entire config file */
572 n = sbuf.st_size;
573 cp = buf;
574 do {
575 cc = read(fd, cp, n);
576 if (cc < 0) {
577 fprintf(stderr, "%s: read(%s) %s\n",
578 prog, file, strerror(errno));
579 exit(1);
581 cp += cc;
582 n -= cc;
583 } while (cc != 0 && cc < n);
584 buf[cc] = '\0';
586 #define EATWHITESPACE \
587 while (isspace(*cp)) { \
588 if (*cp == '\n') \
589 ++n; \
590 ++cp; \
593 /* Handle both to-end-of-line and C style comments */
594 #define EATCOMMENTS \
596 int sawcomment; \
597 do { \
598 EATWHITESPACE \
599 sawcomment = 0; \
600 if (*cp == '#') { \
601 sawcomment = 1; \
602 ++cp; \
603 while (*cp != '\n' && *cp != '\0') \
604 ++cp; \
606 else if (strncmp(cp, "//", 2) == 0) { \
607 sawcomment = 1; \
608 cp += 2; \
609 while (*cp != '\n' && *cp != '\0') \
610 ++cp; \
612 else if (strncmp(cp, "/*", 2) == 0) { \
613 sawcomment = 1; \
614 for (cp += 2; *cp != '\0'; ++cp) { \
615 if (*cp == '\n') \
616 ++n; \
617 else if (strncmp(cp, "*/", 2) == 0) { \
618 cp += 2; \
619 break; \
623 } while (sawcomment); \
626 #define GETNAME(name, len) \
628 (name) = cp; \
629 (len) = 0; \
630 while (!isspace(*cp) && *cp != ';' && *cp != '\0') { \
631 ++(len); \
632 ++cp; \
636 #define GETQUOTEDNAME(name, len) \
638 if (*cp != '"') { \
639 ++errors; \
640 fprintf(stderr, "%s: %s:%d missing left quote\n", \
641 prog, file, n); \
642 } else \
643 ++cp; \
644 (name) = cp; \
645 (len) = 0; \
646 while (*cp != '"' && *cp != '\n' && *cp != '\0') { \
647 ++(len); \
648 ++cp; \
650 if (*cp != '"') { \
651 ++errors; \
652 fprintf(stderr, "%s: %s:%d missing right quote\n", \
653 prog, file, n); \
654 } else \
655 ++cp; \
658 /* Eat everything to the next semicolon, perhaps eating matching qbraces */
659 #define EATSEMICOLON \
661 register int depth = 0; \
662 while (*cp != '\0') { \
663 EATCOMMENTS \
664 if (*cp == ';') { \
665 ++cp; \
666 if (depth == 0) \
667 break; \
668 continue; \
670 if (*cp == '{') { \
671 ++depth; \
672 ++cp; \
673 continue; \
675 if (*cp == '}') { \
676 --depth; \
677 ++cp; \
678 continue; \
680 ++cp; \
684 n = 1;
685 zone[0] = '\0';
686 cp = buf;
687 while (*cp != '\0') {
688 EATCOMMENTS
689 if (*cp == '\0')
690 break;
691 GETNAME(name, namelen)
692 if (namelen == 0) {
693 ++errors;
694 fprintf(stderr, "%s: %s:%d garbage char '%c' (1)\n",
695 prog, file, n, *cp);
696 ++cp;
697 continue;
699 EATCOMMENTS
700 if (strncasecmp(name, "options", namelen) == 0) {
701 EATCOMMENTS
702 if (*cp != '{') {
703 ++errors;
704 fprintf(stderr,
705 "%s: %s:%d missing left qbrace in options\n",
706 prog, file, n);
707 } else
708 ++cp;
709 EATCOMMENTS
710 while (*cp != '}' && *cp != '\0') {
711 EATCOMMENTS
712 GETNAME(name, namelen)
713 if (namelen == 0) {
714 ++errors;
715 fprintf(stderr,
716 "%s: %s:%d garbage char '%c' (2)\n",
717 prog, file, n, *cp);
718 ++cp;
719 break;
722 /* If not the "directory" option, just eat it */
723 if (strncasecmp(name, "directory",
724 namelen) == 0) {
725 EATCOMMENTS
726 GETQUOTEDNAME(cp2, i)
727 cp2[i] = '\0';
728 if (chdir(cp2) < 0) {
729 ++errors;
730 fprintf(stderr,
731 "%s: %s:.%d can't chdir %s: %s\n",
732 prog, file, n, cp2,
733 strerror(errno));
734 exit(1);
736 cwd = savestr(cp2);
738 EATSEMICOLON
739 EATCOMMENTS
741 ++cp;
742 EATCOMMENTS
743 if (*cp != ';') {
744 ++errors;
745 fprintf(stderr,
746 "%s: %s:%d missing options semi\n",
747 prog, file, n);
748 } else
749 ++cp;
750 continue;
752 if (strncasecmp(name, "zone", namelen) == 0) {
753 EATCOMMENTS
754 GETQUOTEDNAME(zonename, zonenamelen)
755 typename = NULL;
756 filename = NULL;
757 typenamelen = 0;
758 filenamelen = 0;
759 EATCOMMENTS
760 if (strncasecmp(cp, "in", 2) == 0) {
761 cp += 2;
762 EATWHITESPACE
763 } else if (strncasecmp(cp, "chaos", 5) == 0) {
764 cp += 5;
765 EATWHITESPACE
767 if (*cp != '{') { /* } */
768 ++errors;
769 fprintf(stderr,
770 "%s: %s:%d missing left qbrace in zone\n",
771 prog, file, n);
772 continue;
774 depth = 0;
775 EATCOMMENTS
776 while (*cp != '\0') {
777 if (*cp == '{') {
778 ++cp;
779 ++depth;
780 } else if (*cp == '}') {
781 if (--depth <= 1)
782 break;
783 ++cp;
785 EATCOMMENTS
786 GETNAME(name, namelen)
787 if (namelen == 0) {
788 ++errors;
789 fprintf(stderr,
790 "%s: %s:%d garbage char '%c' (3)\n",
791 prog, file, n, *cp);
792 ++cp;
793 break;
795 if (strncasecmp(name, "type",
796 namelen) == 0) {
797 EATCOMMENTS
798 GETNAME(typename, typenamelen)
799 if (namelen == 0) {
800 ++errors;
801 fprintf(stderr,
802 "%s: %s:%d garbage char '%c' (4)\n",
803 prog, file, n, *cp);
804 ++cp;
805 break;
807 } else if (strncasecmp(name, "file",
808 namelen) == 0) {
809 EATCOMMENTS
810 GETQUOTEDNAME(filename, filenamelen)
812 /* Just ignore keywords we don't understand */
813 EATSEMICOLON
814 EATCOMMENTS
816 /* { */
817 if (*cp != '}') {
818 ++errors;
819 fprintf(stderr,
820 "%s: %s:%d missing zone right qbrace\n",
821 prog, file, n);
822 } else
823 ++cp;
824 if (*cp != ';') {
825 ++errors;
826 fprintf(stderr,
827 "%s: %s:%d missing zone semi\n",
828 prog, file, n);
829 } else
830 ++cp;
831 EATCOMMENTS
832 /* If we got something interesting, process it */
833 if (typenamelen == 0) {
834 ++errors;
835 fprintf(stderr, "%s: missing zone type!\n",
836 prog);
837 continue;
839 if (strncasecmp(typename, "master", typenamelen) == 0) {
840 if (filenamelen == 0) {
841 ++errors;
842 fprintf(stderr,
843 "%s: missing zone filename!\n",
844 prog);
845 continue;
847 strncpy(zone, zonename, zonenamelen);
848 zone[zonenamelen] = '\0';
849 for (cp2 = zone; *cp2 != '\0'; ++cp2)
850 if (isupper(*cp2))
851 *cp2 = tolower(*cp2);
852 /* Insure trailing dot */
853 if (cp2 > zone && cp2[-1] != '.') {
854 *cp2++ = '.';
855 *cp2 = '\0';
857 filename[filenamelen] = '\0';
858 nsoaval = -1;
859 memset(soaval, 0, sizeof(soaval));
860 process(filename, zone, zone);
862 continue;
864 if (strncasecmp(name, "nslint", namelen) == 0) {
865 EATCOMMENTS
866 if (*cp != '{') {
867 ++errors;
868 fprintf(stderr,
869 "%s: %s:%d missing left qbrace in nslint\n",
870 prog, file, n);
871 } else
872 ++cp;
873 ++cp;
874 EATCOMMENTS
875 while (*cp != '}' && *cp != '\0') {
876 EATCOMMENTS
877 GETNAME(name, namelen)
878 if (strncasecmp(name, "network",
879 namelen) == 0) {
880 EATCOMMENTS
881 GETQUOTEDNAME(cp2, i)
884 cp2[i] = '\0';
885 if (!parsenetwork(cp2, &errstr)) {
886 ++errors;
887 fprintf(stderr,
888 "%s: %s:%d: bad network: %s\n",
889 prog, file, n, errstr);
891 } else {
892 ++errors;
893 fprintf(stderr,
894 "%s: unknown nslint \"%.*s\"\n",
895 prog, namelen, name);
897 EATSEMICOLON
898 EATCOMMENTS
900 ++cp;
901 EATCOMMENTS
902 if (*cp != ';') {
903 ++errors;
904 fprintf(stderr, "missing options semi\n");
905 } else
906 ++cp;
907 continue;
909 if (strncasecmp(name, "include", namelen) == 0) {
910 EATCOMMENTS
911 GETQUOTEDNAME(filename, filenamelen)
912 strncpy(includefile, filename, filenamelen);
913 includefile[filenamelen] = '\0';
914 errors += doconf(includefile, 1);
915 EATSEMICOLON
916 continue;
919 /* Skip over statements we don't understand */
920 EATSEMICOLON
923 free(buf);
924 close(fd);
925 return (errors != 0);
928 /* Return true when done */
930 parsesoa(register const char *cp, register char **errstrp)
932 register char ch, *garbage;
933 static char errstr[132];
935 /* Eat leading whitespace */
936 while (isspace(*cp))
937 ++cp;
939 /* Find opening paren */
940 if (nsoaval < 0) {
941 cp = strchr(cp, '(');
942 if (cp == NULL)
943 return (0);
944 ++cp;
945 while (isspace(*cp))
946 ++cp;
947 nsoaval = 0;
950 /* Grab any numbers we find */
951 garbage = "leading garbage";
952 while (isdigit(*cp) && nsoaval < NSOAVAL) {
953 soaval[nsoaval] = atoi(cp);
954 do {
955 ++cp;
956 } while (isdigit(*cp));
957 if (nsoaval == SOA_SERIAL && *cp == '.' && isdigit(cp[1])) {
958 do {
959 ++cp;
960 } while (isdigit(*cp));
961 } else {
962 ch = *cp;
963 if (isupper(ch))
964 ch = tolower(ch);
965 switch (ch) {
967 case 'w':
968 soaval[nsoaval] *= 7;
969 /* fall through */
971 case 'd':
972 soaval[nsoaval] *= 24;
973 /* fall through */
975 case 'h':
976 soaval[nsoaval] *= 60;
977 /* fall through */
979 case 'm':
980 soaval[nsoaval] *= 60;
981 /* fall through */
983 case 's':
984 ++cp;
985 break;
987 default:
988 ; /* none */
991 while (isspace(*cp))
992 ++cp;
993 garbage = "trailing garbage";
994 ++nsoaval;
997 /* If we're done, do some sanity checks */
998 if (nsoaval >= NSOAVAL && *cp == ')') {
999 ++cp;
1000 if (*cp != '\0')
1001 *errstrp = garbage;
1002 else if (soaval[SOA_EXPIRE] <
1003 soaval[SOA_REFRESH] + 10 * soaval[SOA_RETRY]) {
1004 (void)sprintf(errstr,
1005 "expire less than refresh + 10 * retry (%u < %u + 10 * %u)",
1006 soaval[SOA_EXPIRE],
1007 soaval[SOA_REFRESH],
1008 soaval[SOA_RETRY]);
1009 *errstrp = errstr;
1010 } else if (soaval[SOA_REFRESH] < 2 * soaval[SOA_RETRY]) {
1011 (void)sprintf(errstr,
1012 "refresh less than 2 * retry (%u < 2 * %u)",
1013 soaval[SOA_REFRESH],
1014 soaval[SOA_RETRY]);
1015 *errstrp = errstr;
1017 return (1);
1020 if (*cp != '\0') {
1021 *errstrp = garbage;
1022 return (1);
1025 return (0);
1028 void
1029 process(register const char *file, register const char *domain,
1030 register const char *zone)
1032 register FILE *f;
1033 register char ch, *cp, *cp2, *cp3, *rtype;
1034 register const char *ccp;
1035 register int n, sawsoa, flags, i;
1036 register u_int ttl;
1037 register u_int32_t addr;
1038 u_int32_t net, mask;
1039 int smtp;
1040 char buf[1024], name[128], lastname[128], odomain[128];
1041 char *errstr;
1042 char *dotfmt = "%s: %s/%s:%d \"%s\" target missing trailing dot: %s\n";
1044 f = fopen(file, "r");
1045 if (f == NULL) {
1046 fprintf(stderr, "%s: %s/%s: %s\n",
1047 prog, cwd, file, strerror(errno));
1048 ++errors;
1049 return;
1051 if (debug > 1)
1052 printf("%s: process: opened %s/%s\n", prog, cwd, file);
1054 /* Are we doing an in-addr.arpa domain? */
1055 n = 0;
1056 net = 0;
1057 mask = 0;
1058 ccp = domain + strlen(domain) - sizeof(inaddr) + 1;
1059 if (ccp >= domain && strcasecmp(ccp, inaddr) == 0 &&
1060 !parseinaddr(domain, &net, &mask)) {
1061 ++errors;
1062 fprintf(stderr, "%s: %s/%s:%d bad in-addr.arpa domain\n",
1063 prog, cwd, file, n);
1064 return;
1067 lastname[0] = '\0';
1068 sawsoa = 0;
1069 while (fgets(buf, sizeof(buf), f) != NULL) {
1070 ++n;
1071 cp = buf;
1072 while (*cp != '\0') {
1073 /* Handle quoted strings (but don't report errors) */
1074 if (*cp == '"') {
1075 ++cp;
1076 while (*cp != '"' && *cp != '\n' && *cp != '\0')
1077 ++cp;
1078 continue;
1080 if (*cp == '\n' || *cp == ';')
1081 break;
1082 ++cp;
1084 *cp-- = '\0';
1086 /* Nuke trailing white space */
1087 while (cp >= buf && isspace(*cp))
1088 *cp-- = '\0';
1090 cp = buf;
1091 if (*cp == '\0')
1092 continue;
1094 /* Handle multi-line soa records */
1095 if (sawsoa) {
1096 errstr = NULL;
1097 if (parsesoa(cp, &errstr))
1098 sawsoa = 0;
1099 if (errstr != NULL) {
1100 ++errors;
1101 fprintf(stderr,
1102 "%s: %s/%s:%d bad \"soa\" record (%s)\n",
1103 prog, cwd, file, n, errstr);
1105 continue;
1107 if (debug > 3)
1108 printf(">%s<\n", cp);
1110 /* Look for name */
1111 if (isspace(*cp)) {
1112 /* Same name as last record */
1113 if (lastname[0] == '\0') {
1114 ++errors;
1115 fprintf(stderr,
1116 "%s: %s/%s:%d no default name\n",
1117 prog, cwd, file, n);
1118 continue;
1120 (void)strcpy(name, lastname);
1121 } else {
1122 /* Extract name, converting to lowercase */
1123 for (cp2 = name; !isspace(*cp) && *cp != '\0'; ++cp)
1124 if (isupper(*cp))
1125 *cp2++ = tolower(*cp);
1126 else
1127 *cp2++ = *cp;
1128 *cp2 = '\0';
1130 /* Check for domain shorthand */
1131 if (name[0] == '@' && name[1] == '\0')
1132 (void)strcpy(name, domain);
1135 /* Find next token */
1136 while (isspace(*cp))
1137 ++cp;
1139 /* Handle includes (gag) */
1140 if (name[0] == '$' && strcasecmp(name, "$include") == 0) {
1141 /* Extract filename */
1142 cp2 = name;
1143 while (!isspace(*cp) && *cp != '\0')
1144 *cp2++ = *cp++;
1145 *cp2 = '\0';
1147 /* Look for optional domain */
1148 while (isspace(*cp))
1149 ++cp;
1150 if (*cp == '\0')
1151 process(name, domain, zone);
1152 else {
1153 cp2 = cp;
1154 /* Convert optional domain to lowercase */
1155 for (; !isspace(*cp) && *cp != '\0'; ++cp)
1156 if (isupper(*cp))
1157 *cp = tolower(*cp);
1158 *cp = '\0';
1159 process(name, cp2, cp2);
1161 continue;
1164 /* Handle $origin */
1165 if (name[0] == '$' && strcasecmp(name, "$origin") == 0) {
1166 /* Extract domain, converting to lowercase */
1167 for (cp2 = odomain; !isspace(*cp) && *cp != '\0'; ++cp)
1168 if (isupper(*cp))
1169 *cp2++ = tolower(*cp);
1170 else
1171 *cp2++ = *cp;
1172 *cp2 = '\0';
1173 domain = odomain;
1174 lastname[0] = '\0';
1176 /* Are we doing an in-addr.arpa domain? */
1177 net = 0;
1178 mask = 0;
1179 ccp = domain + strlen(domain) - (sizeof(inaddr) - 1);
1180 if (ccp >= domain && strcasecmp(ccp, inaddr) == 0 &&
1181 !parseinaddr(domain, &net, &mask)) {
1182 ++errors;
1183 fprintf(stderr,
1184 "%s: %s/%s:%d bad in-addr.arpa domain\n",
1185 prog, cwd, file, n);
1186 return;
1188 continue;
1191 /* Handle ttl */
1192 if (name[0] == '$' && strcasecmp(name, "$ttl") == 0) {
1193 cp2 = cp;
1194 while (isdigit(*cp))
1195 ++cp;
1196 ch = *cp;
1197 if (isupper(ch))
1198 ch = tolower(ch);
1199 if (strchr("wdhms", ch) != NULL)
1200 ++cp;
1201 while (isspace(*cp))
1202 ++cp;
1203 if (*cp != '\0') {
1204 ++errors;
1205 fprintf(stderr,
1206 "%s: %s/%s:%d bad $ttl \"%s\"\n",
1207 prog, cwd, file, n, cp2);
1209 (void)strcpy(name, lastname);
1210 continue;
1213 /* Parse ttl or use default */
1214 if (isdigit(*cp)) {
1215 ttl = atoi(cp);
1216 do {
1217 ++cp;
1218 } while (isdigit(*cp));
1220 ch = *cp;
1221 if (isupper(ch))
1222 ch = tolower(ch);
1223 switch (ch) {
1225 case 'w':
1226 ttl *= 7;
1227 /* fall through */
1229 case 'd':
1230 ttl *= 24;
1231 /* fall through */
1233 case 'h':
1234 ttl *= 60;
1235 /* fall through */
1237 case 'm':
1238 ttl *= 60;
1239 /* fall through */
1241 case 's':
1242 ++cp;
1243 break;
1245 default:
1246 ; /* none */
1250 if (!isspace(*cp)) {
1251 ++errors;
1252 fprintf(stderr, "%s: %s/%s:%d bad ttl\n",
1253 prog, cwd, file, n);
1254 continue;
1257 /* Find next token */
1258 ++cp;
1259 while (isspace(*cp))
1260 ++cp;
1261 } else
1262 ttl = soaval[SOA_MINIMUM];
1264 /* Eat optional "in" */
1265 if ((cp[0] == 'i' || cp[0] == 'I') &&
1266 (cp[1] == 'n' || cp[1] == 'N') && isspace(cp[2])) {
1267 /* Find next token */
1268 cp += 3;
1269 while (isspace(*cp))
1270 ++cp;
1271 } else if ((cp[0] == 'c' || cp[0] == 'C') &&
1272 isspace(cp[5]) && strncasecmp(cp, "chaos", 5) == 0) {
1273 /* Find next token */
1274 cp += 5;
1275 while (isspace(*cp))
1276 ++cp;
1279 /* Find end of record type, converting to lowercase */
1280 rtype = cp;
1281 for (rtype = cp; !isspace(*cp) && *cp != '\0'; ++cp)
1282 if (isupper(*cp))
1283 *cp = tolower(*cp);
1284 *cp++ = '\0';
1286 /* Find "the rest" */
1287 while (isspace(*cp))
1288 ++cp;
1290 /* Check for non-ptr names with dots but no trailing dot */
1291 if (!isdigit(*name) &&
1292 checkdots(name) && strcmp(domain, ".") != 0) {
1293 ++errors;
1294 fprintf(stderr,
1295 "%s: %s/%s:%d \"%s\" name missing trailing dot: %s\n",
1296 prog, cwd, file, n, rtype, name);
1299 /* Check for FQDNs outside the zone */
1300 cp2 = name + strlen(name) - 1;
1301 if (cp2 >= name && *cp2 == '.' && strchr(name, '.') != NULL) {
1302 cp2 = name + strlen(name) - strlen(zone);
1303 if (cp2 >= name && strcasecmp(cp2, zone) != 0) {
1304 ++errors;
1305 fprintf(stderr,
1306 "%s: %s/%s:%d \"%s\" outside zone %s\n",
1307 prog, cwd, file, n, name, zone);
1311 #define CHECK4(p, a, b, c, d) \
1312 (p[0] == (a) && p[1] == (b) && p[2] == (c) && p[3] == (d) && p[4] == '\0')
1313 #define CHECK3(p, a, b, c) \
1314 (p[0] == (a) && p[1] == (b) && p[2] == (c) && p[3] == '\0')
1315 #define CHECK2(p, a, b) \
1316 (p[0] == (a) && p[1] == (b) && p[2] == '\0')
1317 #define CHECKDOT(p) \
1318 (p[0] == '.' && p[1] == '\0')
1320 if (rtype[0] == 'a' && rtype[1] == '\0') {
1321 /* Handle "a" record */
1322 add_domain(name, domain);
1323 addr = htonl(inet_addr(cp));
1324 if ((int)addr == -1) {
1325 ++errors;
1326 cp2 = cp + strlen(cp) - 1;
1327 if (cp2 >= cp && *cp2 == '\n')
1328 *cp2 = '\0';
1329 fprintf(stderr,
1330 "%s: %s/%s:%d bad \"a\" record ip addr \"%s\"\n",
1331 prog, cwd, file, n, cp);
1332 continue;
1334 errors += updateitem(name, addr, REC_A, ttl, 0);
1335 } else if (CHECK4(rtype, 'a', 'a', 'a', 'a')) {
1336 /* Just eat for now */
1337 continue;
1338 } else if (CHECK3(rtype, 'p', 't', 'r')) {
1339 /* Handle "ptr" record */
1340 add_domain(name, domain);
1341 if (strcmp(cp, "@") == 0)
1342 (void)strcpy(cp, zone);
1343 if (checkdots(cp)) {
1344 ++errors;
1345 fprintf(stderr, dotfmt,
1346 prog, cwd, file, n, rtype, cp);
1348 add_domain(cp, domain);
1349 errstr = NULL;
1350 addr = parseptr(name, net, mask, &errstr);
1351 if (errstr != NULL) {
1352 ++errors;
1353 fprintf(stderr,
1354 "%s: %s/%s:%d bad \"ptr\" record (%s) ip addr \"%s\"\n",
1355 prog, cwd, file, n, errstr, name);
1356 continue;
1358 errors += updateitem(cp, addr, REC_PTR, 0, 0);
1359 } else if (CHECK3(rtype, 's', 'o', 'a')) {
1360 /* Handle "soa" record */
1361 if (!CHECKDOT(name)) {
1362 add_domain(name, domain);
1363 errors += updateitem(name, 0, REC_SOA, 0, 0);
1365 errstr = NULL;
1366 if (!parsesoa(cp, &errstr))
1367 ++sawsoa;
1368 if (errstr != NULL) {
1369 ++errors;
1370 fprintf(stderr,
1371 "%s: %s/%s:%d bad \"soa\" record (%s)\n",
1372 prog, cwd, file, n, errstr);
1373 continue;
1375 } else if (CHECK3(rtype, 'w', 'k', 's')) {
1376 /* Handle "wks" record */
1377 addr = htonl(inet_addr(cp));
1378 if ((int)addr == -1) {
1379 ++errors;
1380 cp2 = cp;
1381 while (!isspace(*cp2) && *cp2 != '\0')
1382 ++cp2;
1383 *cp2 = '\0';
1384 fprintf(stderr,
1385 "%s: %s/%s:%d bad \"wks\" record ip addr \"%s\"\n",
1386 prog, cwd, file, n, cp);
1387 continue;
1389 /* Step over ip address */
1390 while (*cp == '.' || isdigit(*cp))
1391 ++cp;
1392 while (isspace(*cp))
1393 *cp++ = '\0';
1394 /* Make sure services are legit */
1395 errstr = NULL;
1396 n += checkwks(f, cp, &smtp, &errstr);
1397 if (errstr != NULL) {
1398 ++errors;
1399 fprintf(stderr,
1400 "%s: %s/%s:%d bad \"wks\" record (%s)\n",
1401 prog, cwd, file, n, errstr);
1402 continue;
1404 add_domain(name, domain);
1405 errors += updateitem(name, addr, REC_WKS,
1406 0, smtp ? FLG_SMTPWKS : 0);
1407 /* XXX check to see if ip address records exists? */
1408 } else if (rtype[0] == 'h' && strcmp(rtype, "hinfo") == 0) {
1409 /* Handle "hinfo" record */
1410 add_domain(name, domain);
1411 errors += updateitem(name, 0, REC_HINFO, 0, 0);
1412 cp2 = cp;
1413 cp = parsequoted(cp);
1414 if (cp == NULL) {
1415 ++errors;
1416 fprintf(stderr,
1417 "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
1418 prog, cwd, file, n, cp2);
1419 continue;
1421 if (!isspace(*cp)) {
1422 ++errors;
1423 fprintf(stderr,
1424 "%s: %s/%s:%d \"hinfo\" missing white space: %s\n",
1425 prog, cwd, file, n, cp2);
1426 continue;
1428 ++cp;
1429 while (isspace(*cp))
1430 ++cp;
1431 if (*cp == '\0') {
1432 ++errors;
1433 fprintf(stderr,
1434 "%s: %s/%s:%d \"hinfo\" missing keyword: %s\n",
1435 prog, cwd, file, n, cp2);
1436 continue;
1438 cp = parsequoted(cp);
1439 if (cp == NULL) {
1440 ++errors;
1441 fprintf(stderr,
1442 "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
1443 prog, cwd, file, n, cp2);
1444 continue;
1446 if (*cp != '\0') {
1447 ++errors;
1448 fprintf(stderr,
1449 "%s: %s/%s:%d \"hinfo\" garbage after keywords: %s\n",
1450 prog, cwd, file, n, cp2);
1451 continue;
1453 } else if (CHECK2(rtype, 'm', 'x')) {
1454 /* Handle "mx" record */
1455 add_domain(name, domain);
1456 errors += updateitem(name, 0, REC_MX, ttl, 0);
1458 /* Look for priority */
1459 if (!isdigit(*cp)) {
1460 ++errors;
1461 fprintf(stderr,
1462 "%s: %s/%s:%d bad \"mx\" priority: %s\n",
1463 prog, cwd, file, n, cp);
1466 /* Skip over priority */
1467 ++cp;
1468 while (isdigit(*cp))
1469 ++cp;
1470 while (isspace(*cp))
1471 ++cp;
1472 if (*cp == '\0') {
1473 ++errors;
1474 fprintf(stderr,
1475 "%s: %s/%s:%d missing \"mx\" hostname\n",
1476 prog, cwd, file, n);
1478 if (strcmp(cp, "@") == 0)
1479 (void)strcpy(cp, zone);
1480 if (checkdots(cp)) {
1481 ++errors;
1482 fprintf(stderr, dotfmt,
1483 prog, cwd, file, n, rtype, cp);
1486 /* Check to see if mx host exists */
1487 add_domain(cp, domain);
1488 flags = FLG_MXREF;
1489 if (*name == *cp && strcmp(name, cp) == 0)
1490 flags |= FLG_SELFMX;
1491 errors += updateitem(cp, 0, REC_REF, 0, flags);
1492 } else if (rtype[0] == 'c' && strcmp(rtype, "cname") == 0) {
1493 /* Handle "cname" record */
1494 add_domain(name, domain);
1495 errors += updateitem(name, 0, REC_CNAME, 0, 0);
1496 if (checkdots(cp)) {
1497 ++errors;
1498 fprintf(stderr, dotfmt,
1499 prog, cwd, file, n, rtype, cp);
1502 /* Make sure cname points somewhere */
1503 if (strcmp(cp, "@") == 0)
1504 (void)strcpy(cp, zone);
1505 add_domain(cp, domain);
1506 errors += updateitem(cp, 0, REC_REF, 0, 0);
1507 } else if (CHECK3(rtype, 's', 'r', 'v')) {
1508 /* Handle "srv" record */
1509 add_domain(name, domain);
1510 errors += updateitem(name, 0, REC_SRV, 0, 0);
1511 cp2 = cp;
1513 /* Skip over three values */
1514 for (i = 0; i < 3; ++i) {
1515 if (!isdigit(*cp)) {
1516 ++errors;
1517 fprintf(stderr, "%s: %s/%s:%d"
1518 " bad \"srv\" value: %s\n",
1519 prog, cwd, file, n, cp);
1522 /* Skip over value */
1523 ++cp;
1524 while (isdigit(*cp))
1525 ++cp;
1526 while (isspace(*cp))
1527 ++cp;
1530 /* Check to see if mx host exists */
1531 add_domain(cp, domain);
1532 errors += updateitem(cp, 0, REC_REF, 0, 0);
1533 } else if (CHECK3(rtype, 't', 'x', 't')) {
1534 /* Handle "txt" record */
1535 add_domain(name, domain);
1536 errors += updateitem(name, 0, REC_TXT, 0, 0);
1537 cp2 = cp;
1538 cp = parsequoted(cp);
1539 if (cp == NULL) {
1540 ++errors;
1541 fprintf(stderr,
1542 "%s: %s/%s:%d \"txt\" missing quote: %s\n",
1543 prog, cwd, file, n, cp2);
1544 continue;
1546 while (isspace(*cp))
1547 ++cp;
1548 if (*cp != '\0') {
1549 ++errors;
1550 fprintf(stderr,
1551 "%s: %s/%s:%d \"txt\" garbage after text: %s\n",
1552 prog, cwd, file, n, cp2);
1553 continue;
1555 } else if (CHECK2(rtype, 'n', 's')) {
1556 /* Handle "ns" record */
1557 errors += updateitem(zone, 0, REC_NS, 0, 0);
1558 if (strcmp(cp, "@") == 0)
1559 (void)strcpy(cp, zone);
1560 if (checkdots(cp)) {
1561 ++errors;
1562 fprintf(stderr, dotfmt,
1563 prog, cwd, file, n, rtype, cp);
1565 add_domain(cp, domain);
1566 errors += updateitem(cp, 0, REC_REF, 0, 0);
1567 } else if (CHECK2(rtype, 'r', 'p')) {
1568 /* Handle "rp" record */
1569 add_domain(name, domain);
1570 errors += updateitem(name, 0, REC_RP, 0, 0);
1571 cp2 = cp;
1573 /* Step over mailbox name */
1574 /* XXX could add_domain() and check further */
1575 while (!isspace(*cp) && *cp != '\0')
1576 ++cp;
1577 if (*cp == '\0') {
1578 ++errors;
1579 fprintf(stderr,
1580 "%s: %s/%s:%d \"rp\" missing text name: %s\n",
1581 prog, cwd, file, n, cp2);
1582 continue;
1584 ++cp;
1585 cp3 = cp;
1587 /* Step over text name */
1588 while (!isspace(*cp) && *cp != '\0')
1589 ++cp;
1591 if (*cp != '\0') {
1592 ++errors;
1593 fprintf(stderr,
1594 "%s: %s/%s:%d \"rp\" garbage after text name: %s\n",
1595 prog, cwd, file, n, cp2);
1596 continue;
1599 /* Make sure text name points somewhere (if not ".") */
1600 if (!CHECKDOT(cp3)) {
1601 add_domain(cp3, domain);
1602 errors += updateitem(cp3, 0, REC_REF, 0, 0);
1604 } else if (rtype[0] == 'a' && strcmp(rtype, "allowdupa") == 0) {
1605 /* Handle "allow duplicate a" record */
1606 add_domain(name, domain);
1607 addr = htonl(inet_addr(cp));
1608 if ((int)addr == -1) {
1609 ++errors;
1610 cp2 = cp + strlen(cp) - 1;
1611 if (cp2 >= cp && *cp2 == '\n')
1612 *cp2 = '\0';
1613 fprintf(stderr,
1614 "%s: %s/%s:%d bad \"allowdupa\" record ip addr \"%s\"\n",
1615 prog, cwd, file, n, cp);
1616 continue;
1618 errors += updateitem(name, addr, 0, 0, FLG_ALLOWDUPA);
1619 } else {
1620 /* Unknown record type */
1621 ++errors;
1622 fprintf(stderr,
1623 "%s: %s/%s:%d unknown record type \"%s\"\n",
1624 prog, cwd, file, n, rtype);
1625 add_domain(name, domain);
1626 errors += updateitem(name, 0, REC_UNKNOWN, 0, 0);
1628 (void)strcpy(lastname, name);
1630 (void)fclose(f);
1631 return;
1634 /* Records we use to detect duplicates */
1635 static struct duprec {
1636 int record;
1637 char *name;
1638 } duprec[] = {
1639 { REC_A, "a" },
1640 { REC_HINFO, "hinfo" },
1641 { 0, NULL },
1644 void
1645 checkdups(register struct item *ip, register int records)
1647 register struct duprec *dp;
1649 records &= (ip->records & MASK_TEST_DUP);
1650 if (records == 0)
1651 return;
1652 for (dp = duprec; dp->name != NULL; ++dp)
1653 if ((records & dp->record) != 0) {
1654 ++errors;
1655 fprintf(stderr, "%s: multiple \"%s\" records for %s\n",
1656 prog, dp->name, ip->host);
1657 records &= ~dp->record;
1659 if (records != 0)
1660 fprintf(stderr, "%s: checkdups: records not zero (%d)\n",
1661 prog, records);
1665 updateitem(register const char *host, register u_int32_t addr,
1666 register int records, register u_int ttl, register int flags)
1668 register const char *ccp;
1669 register int n, errs;
1670 register u_int i;
1671 register struct item *ip;
1672 int foundsome;
1674 n = 0;
1675 foundsome = 0;
1676 errs = 0;
1677 ITEMHASH(host, i, ccp);
1678 ip = &items[i & (ITEMSIZE - 1)];
1679 while (n < ITEMSIZE && ip->host) {
1680 if ((addr == 0 || addr == ip->addr || ip->addr == 0) &&
1681 *host == *ip->host && strcmp(host, ip->host) == 0) {
1682 ++foundsome;
1683 if (ip->addr == 0)
1684 ip->addr = addr;
1685 if ((records & MASK_TEST_DUP) != 0)
1686 checkdups(ip, records);
1687 ip->records |= records;
1688 /* Only check differing ttl's for A and MX records */
1689 if (ip->ttl == 0)
1690 ip->ttl = ttl;
1691 else if (ttl != 0 && ip->ttl != ttl) {
1692 fprintf(stderr,
1693 "%s: differing ttls for %s (%u != %u)\n",
1694 prog, ip->host, ttl, ip->ttl);
1695 ++errs;
1697 ip->flags |= flags;
1698 /* Not done if we wildcard matched the name */
1699 if (addr)
1700 return (errs);
1702 ++n;
1703 ++ip;
1704 if (ip >= &items[ITEMSIZE])
1705 ip = items;
1708 if (n >= ITEMSIZE) {
1709 fprintf(stderr, "%s: out of item slots (max %d)\n",
1710 prog, ITEMSIZE);
1711 exit(1);
1714 /* Done if we were wildcarding the name (and found entries for it) */
1715 if (addr == 0 && foundsome)
1716 return (errs);
1718 /* Didn't find it, make new entry */
1719 ++itemcnt;
1720 if (ip->host) {
1721 fprintf(stderr, "%s: reusing bucket!\n", prog);
1722 exit(1);
1724 ip->addr = addr;
1725 ip->host = savestr(host);
1726 if ((records & MASK_TEST_DUP) != 0)
1727 checkdups(ip, records);
1728 ip->records |= records;
1729 if (ttl != 0)
1730 ip->ttl = ttl;
1731 ip->flags |= flags;
1732 return (errs);
1735 static const char *microlist[] = {
1736 "_tcp",
1737 "_udp",
1738 "_msdcs",
1739 "_sites",
1740 NULL
1744 rfc1034host(register const char *host, register int recs)
1746 register const char *cp, **p;
1747 register int underok;
1749 underok = 0;
1750 for (p = microlist; *p != NULL ;++p)
1751 if ((cp = strstr(host, *p)) != NULL &&
1752 cp > host &&
1753 cp[-1] == '.' &&
1754 cp[strlen(*p)] == '.') {
1755 ++underok;
1756 break;
1759 cp = host;
1760 if (!(isalpha(*cp) || isdigit(*cp) || (*cp == '_' && underok))) {
1761 fprintf(stderr,
1762 "%s: illegal hostname \"%s\" (starts with non-alpha/numeric)\n",
1763 prog, host);
1764 return (1);
1766 for (++cp; *cp != '.' && *cp != '\0'; ++cp)
1767 if (!(isalpha(*cp) || isdigit(*cp) || *cp == '-' ||
1768 (*cp == '/' && (recs & REC_SOA) != 0))) {
1769 fprintf(stderr,
1770 "%s: illegal hostname \"%s\" ('%c' illegal character)\n",
1771 prog, host, *cp);
1772 return (1);
1774 if (--cp >= host && *cp == '-') {
1775 fprintf(stderr, "%s: illegal hostname \"%s\" (ends with '-')\n",
1776 prog, host);
1777 return (1);
1779 return (0);
1783 nslint(void)
1785 register int n, records, flags;
1786 register struct item *ip, *lastaip, **ipp, **itemlist;
1787 register u_int32_t addr, lastaddr, mask;
1789 itemlist = (struct item **)calloc(itemcnt, sizeof(*ipp));
1790 if (itemlist == NULL) {
1791 fprintf(stderr, "%s: nslint: calloc: %s\n",
1792 prog, strerror(errno));
1793 exit(1);
1795 ipp = itemlist;
1796 for (n = 0, ip = items; n < ITEMSIZE; ++n, ++ip) {
1797 if (ip->host == NULL)
1798 continue;
1800 /* Save entries with addresses for later check */
1801 if (ip->addr != 0)
1802 *ipp++ = ip;
1804 if (debug > 1) {
1805 if (debug > 2)
1806 printf("%d\t", n);
1807 printf("%s\t%s\t0x%x\t0x%x\n",
1808 ip->host, intoa(ip->addr), ip->records, ip->flags);
1811 /* Check for illegal hostnames (rfc1034) */
1812 if (rfc1034host(ip->host, ip->records))
1813 ++errors;
1815 /* Check for missing ptr records (ok if also an ns record) */
1816 records = ip->records & MASK_CHECK_REC;
1817 if ((ip->records & MASK_TEST_REC) != 0)
1818 records |= REC_OTHER;
1819 switch (records) {
1821 case REC_A | REC_OTHER | REC_PTR | REC_REF:
1822 case REC_A | REC_OTHER | REC_PTR:
1823 case REC_A | REC_PTR | REC_REF:
1824 case REC_A | REC_PTR:
1825 case REC_CNAME:
1826 /* These are O.K. */
1827 break;
1829 case REC_CNAME | REC_REF:
1830 ++errors;
1831 fprintf(stderr, "%s: \"cname\" referenced by other"
1832 " \"cname\" or \"mx\": %s\n", prog, ip->host);
1833 break;
1835 case REC_OTHER | REC_REF:
1836 case REC_OTHER:
1838 * This is only an error if there is an address
1839 * associated with the hostname; this means
1840 * there was a wks entry with bogus address.
1841 * Otherwise, we have an mx or hinfo.
1843 if (ip->addr != 0) {
1844 ++errors;
1845 fprintf(stderr,
1846 "%s: \"wks\" without \"a\" and \"ptr\": %s -> %s\n",
1847 prog, ip->host, intoa(ip->addr));
1849 break;
1851 case REC_REF:
1852 ++errors;
1853 fprintf(stderr,
1854 "%s: name referenced without other records: %s\n",
1855 prog, ip->host);
1856 break;
1858 case REC_A | REC_OTHER | REC_REF:
1859 case REC_A | REC_OTHER:
1860 case REC_A | REC_REF:
1861 case REC_A:
1862 ++errors;
1863 fprintf(stderr, "%s: missing \"ptr\": %s -> %s\n",
1864 prog, ip->host, intoa(ip->addr));
1865 break;
1867 case REC_OTHER | REC_PTR | REC_REF:
1868 case REC_OTHER | REC_PTR:
1869 case REC_PTR | REC_REF:
1870 case REC_PTR:
1871 ++errors;
1872 fprintf(stderr, "%s: missing \"a\": %s -> %s\n",
1873 prog, ip->host, intoa(ip->addr));
1874 break;
1876 case REC_A | REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
1877 case REC_A | REC_CNAME | REC_OTHER | REC_PTR:
1878 case REC_A | REC_CNAME | REC_OTHER | REC_REF:
1879 case REC_A | REC_CNAME | REC_OTHER:
1880 case REC_A | REC_CNAME | REC_PTR | REC_REF:
1881 case REC_A | REC_CNAME | REC_PTR:
1882 case REC_A | REC_CNAME | REC_REF:
1883 case REC_A | REC_CNAME:
1884 case REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
1885 case REC_CNAME | REC_OTHER | REC_PTR:
1886 case REC_CNAME | REC_OTHER | REC_REF:
1887 case REC_CNAME | REC_OTHER:
1888 case REC_CNAME | REC_PTR | REC_REF:
1889 case REC_CNAME | REC_PTR:
1890 ++errors;
1891 fprintf(stderr, "%s: \"cname\" %s has other records\n",
1892 prog, ip->host);
1893 break;
1895 case 0:
1896 /* Second level test */
1897 if ((ip->records & ~(REC_NS | REC_TXT)) == 0)
1898 break;
1899 /* Fall through... */
1901 default:
1902 ++errors;
1903 fprintf(stderr,
1904 "%s: records == 0x%x: can't happen (%s 0x%x)\n",
1905 prog, records, ip->host, ip->records);
1906 break;
1909 /* Check for smtp problems */
1910 flags = ip->flags & MASK_TEST_SMTP;
1912 if ((flags & FLG_SELFMX) != 0 && (ip->records & REC_A) == 0) {
1913 ++errors;
1914 fprintf(stderr,
1915 "%s: self \"mx\" for %s missing \"a\" record\n",
1916 prog, ip->host);
1919 switch (flags) {
1921 case 0:
1922 case FLG_SELFMX | FLG_SMTPWKS:
1923 /* These are O.K. */
1924 break;
1926 case FLG_SELFMX:
1927 if ((ip->records & REC_WKS) != 0) {
1928 ++errors;
1929 fprintf(stderr,
1930 "%s: smtp/tcp missing from \"wks\": %s\n",
1931 prog, ip->host);
1933 break;
1935 case FLG_SMTPWKS:
1936 ++errors;
1937 fprintf(stderr,
1938 "%s: saw smtp/tcp without self \"mx\": %s\n",
1939 prog, ip->host);
1940 break;
1942 default:
1943 ++errors;
1944 fprintf(stderr,
1945 "%s: flags == 0x%x: can't happen (%s)\n",
1946 prog, flags, ip->host);
1949 /* Check for chained MX records */
1950 if ((ip->flags & (FLG_SELFMX | FLG_MXREF)) == FLG_MXREF &&
1951 (ip->records & REC_MX) != 0) {
1952 ++errors;
1953 fprintf(stderr, "%s: \"mx\" referenced by other"
1954 " \"mx\" record: %s\n", prog, ip->host);
1958 /* Check for doubly booked addresses */
1959 n = ipp - itemlist;
1960 qsort(itemlist, n, sizeof(itemlist[0]), cmpaddr);
1961 lastaddr = 0;
1962 ip = NULL;
1963 for (ipp = itemlist; n > 0; ++ipp, --n) {
1964 addr = (*ipp)->addr;
1965 if (lastaddr == addr &&
1966 ((*ipp)->flags & FLG_ALLOWDUPA) == 0 &&
1967 (ip->flags & FLG_ALLOWDUPA) == 0) {
1968 ++errors;
1969 fprintf(stderr, "%s: %s in use by %s and %s\n",
1970 prog, intoa(addr), (*ipp)->host, ip->host);
1972 lastaddr = addr;
1973 ip = *ipp;
1976 /* Check for hosts with multiple addresses on the same subnet */
1977 n = ipp - itemlist;
1978 qsort(itemlist, n, sizeof(itemlist[0]), cmphost);
1979 if (netlistcnt > 0) {
1980 n = ipp - itemlist;
1981 lastaip = NULL;
1982 for (ipp = itemlist; n > 0; ++ipp, --n) {
1983 ip = *ipp;
1984 if ((ip->records & REC_A) == 0 ||
1985 (ip->flags & FLG_ALLOWDUPA) != 0)
1986 continue;
1987 if (lastaip != NULL &&
1988 strcasecmp(ip->host, lastaip->host) == 0) {
1989 mask = findmask(ip->addr);
1990 if (mask == 0) {
1991 ++errors;
1992 fprintf(stderr,
1993 "%s: can't find mask for %s (%s)\n",
1994 prog, ip->host, intoa(ip->addr));
1995 } else if ((lastaip->addr & mask) ==
1996 (ip->addr & mask) ) {
1997 ++errors;
1998 fprintf(stderr,
1999 "%s: multiple \"a\" records for %s on subnet %s",
2000 prog, ip->host,
2001 intoa(ip->addr & mask));
2002 fprintf(stderr, "\n\t(%s",
2003 intoa(lastaip->addr));
2004 fprintf(stderr, " and %s)\n",
2005 intoa(ip->addr));
2008 lastaip = ip;
2012 if (debug)
2013 printf("%s: %d/%d items used, %d error%s\n", prog, itemcnt,
2014 ITEMSIZE, errors, errors == 1 ? "" : "s");
2015 return (errors != 0);
2018 /* Similar to inet_ntoa() */
2019 char *
2020 intoa(u_int32_t addr)
2022 register char *cp;
2023 register u_int byte;
2024 register int n;
2025 static char buf[sizeof(".xxx.xxx.xxx.xxx")];
2027 cp = &buf[sizeof buf];
2028 *--cp = '\0';
2030 n = 4;
2031 do {
2032 byte = addr & 0xff;
2033 *--cp = byte % 10 + '0';
2034 byte /= 10;
2035 if (byte > 0) {
2036 *--cp = byte % 10 + '0';
2037 byte /= 10;
2038 if (byte > 0)
2039 *--cp = byte + '0';
2041 *--cp = '.';
2042 addr >>= 8;
2043 } while (--n > 0);
2045 return cp + 1;
2049 parseinaddr(register const char *cp, register u_int32_t *netp,
2050 register u_int32_t *maskp)
2052 register int i, bits;
2053 register u_int32_t o, net, mask;
2055 if (!isdigit(*cp))
2056 return (0);
2057 net = 0;
2058 mask = 0xff000000;
2059 bits = 0;
2060 o = 0;
2061 do {
2062 o = o * 10 + (*cp++ - '0');
2063 } while (isdigit(*cp));
2064 net = o << 24;
2066 /* Check for classless delegation mask width */
2067 if (*cp == '/') {
2068 ++cp;
2069 o = 0;
2070 do {
2071 o = o * 10 + (*cp++ - '0');
2072 } while (isdigit(*cp));
2073 bits = o;
2074 if (bits <= 0 || bits > 32)
2075 return (0);
2078 if (*cp == '.' && isdigit(cp[1])) {
2079 ++cp;
2080 o = 0;
2081 do {
2082 o = o * 10 + (*cp++ - '0');
2083 } while (isdigit(*cp));
2084 net = (net >> 8) | (o << 24);
2085 mask = 0xffff0000;
2086 if (*cp == '.' && isdigit(cp[1])) {
2087 ++cp;
2088 o = 0;
2089 do {
2090 o = o * 10 + (*cp++ - '0');
2091 } while (isdigit(*cp));
2092 net = (net >> 8) | (o << 24);
2093 mask = 0xffffff00;
2094 if (*cp == '.' && isdigit(cp[1])) {
2095 ++cp;
2096 o = 0;
2097 do {
2098 o = o * 10 + (*cp++ - '0');
2099 } while (isdigit(*cp));
2100 net = (net >> 8) | (o << 24);
2101 mask = 0xffffffff;
2105 if (strcasecmp(cp, inaddr) != 0)
2106 return (0);
2108 /* Classless delegation */
2109 /* XXX check that calculated mask isn't smaller than octet mask? */
2110 if (bits != 0)
2111 for (mask = 0, i = 31; bits > 0; --i, --bits)
2112 mask |= (1 << i);
2114 *netp = net;
2115 *maskp = mask;
2116 return (1);
2119 u_int32_t
2120 parseptr(register const char *cp, u_int32_t net, u_int32_t mask,
2121 register char **errstrp)
2123 register u_int32_t o, addr;
2124 register int shift;
2126 addr = 0;
2127 shift = 0;
2128 while (isdigit(*cp) && shift < 32) {
2129 o = 0;
2130 do {
2131 o = o * 10 + (*cp++ - '0');
2132 } while (isdigit(*cp));
2133 addr |= o << shift;
2134 shift += 8;
2135 if (*cp != '.') {
2136 if (*cp == '\0')
2137 break;
2138 *errstrp = "missing dot";
2139 return (0);
2141 ++cp;
2144 if (shift > 32) {
2145 *errstrp = "more than 4 octets";
2146 return (0);
2149 if (shift == 32 && strcasecmp(cp, inaddr + 1) == 0)
2150 return (addr);
2152 #ifdef notdef
2153 if (*cp != '\0') {
2154 *errstrp = "trailing junk";
2155 return (0);
2157 #endif
2158 #ifdef notdef
2159 if ((~mask & net) != 0) {
2160 *errstrp = "too many octets for net";
2161 return (0);
2163 #endif
2164 return (net | addr);
2168 checkwks(register FILE *f, register char *proto, register int *smtpp,
2169 register char **errstrp)
2171 register int n, sawparen;
2172 register char *cp, *serv, **p;
2173 static char errstr[132];
2174 char buf[1024];
2175 char psbuf[512];
2177 if (!protoserv_init) {
2178 initprotoserv();
2179 ++protoserv_init;
2182 /* Line count */
2183 n = 0;
2185 /* Terminate protocol */
2186 cp = proto;
2187 while (!isspace(*cp) && *cp != '\0')
2188 ++cp;
2189 if (*cp != '\0')
2190 *cp++ = '\0';
2192 /* Find services */
2193 *smtpp = 0;
2194 sawparen = 0;
2195 if (*cp == '(') {
2196 ++sawparen;
2197 ++cp;
2198 while (isspace(*cp))
2199 ++cp;
2201 for (;;) {
2202 if (*cp == '\0') {
2203 if (!sawparen)
2204 break;
2205 if (fgets(buf, sizeof(buf), f) == NULL) {
2206 *errstrp = "mismatched parens";
2207 return (n);
2209 ++n;
2210 cp = buf;
2211 while (isspace(*cp))
2212 ++cp;
2214 /* Find end of service, converting to lowercase */
2215 for (serv = cp; !isspace(*cp) && *cp != '\0'; ++cp)
2216 if (isupper(*cp))
2217 *cp = tolower(*cp);
2218 if (*cp != '\0')
2219 *cp++ = '\0';
2220 if (sawparen && *cp == ')') {
2221 /* XXX should check for trailing junk */
2222 break;
2225 (void)sprintf(psbuf, "%s/%s", serv, proto);
2227 if (*serv == 's' && strcmp(psbuf, "tcp/smtp") == 0)
2228 ++*smtpp;
2230 for (p = protoserv; *p != NULL; ++p)
2231 if (*psbuf == **p && strcmp(psbuf, *p) == 0) {
2232 break;
2234 if (*p == NULL) {
2235 sprintf(errstr, "%s unknown", psbuf);
2236 *errstrp = errstr;
2237 break;
2241 return (n);
2245 checkserv(register const char *serv, register char **p)
2247 for (; *p != NULL; ++p)
2248 if (*serv == **p && strcmp(serv, *p) == 0)
2249 return (1);
2250 return (0);
2253 void
2254 initprotoserv(void)
2256 register char *cp;
2257 register struct servent *sp;
2258 char psbuf[512];
2260 protoserv_len = 256;
2261 protoserv = (char **)malloc(protoserv_len * sizeof(*protoserv));
2262 if (protoserv == NULL) {
2263 fprintf(stderr, "%s: nslint: malloc: %s\n",
2264 prog, strerror(errno));
2265 exit(1);
2268 while ((sp = getservent()) != NULL) {
2269 (void)sprintf(psbuf, "%s/%s", sp->s_name, sp->s_proto);
2271 /* Convert to lowercase */
2272 for (cp = psbuf; *cp != '\0'; ++cp)
2273 if (isupper(*cp))
2274 *cp = tolower(*cp);
2276 if (protoserv_last + 1 >= protoserv_len) {
2277 protoserv_len <<= 1;
2278 protoserv = realloc(protoserv,
2279 protoserv_len * sizeof(*protoserv));
2280 if (protoserv == NULL) {
2281 fprintf(stderr, "%s: nslint: realloc: %s\n",
2282 prog, strerror(errno));
2283 exit(1);
2286 protoserv[protoserv_last] = savestr(psbuf);
2287 ++protoserv_last;
2289 protoserv[protoserv_last] = NULL;
2293 * Returns true if name contains a dot but not a trailing dot.
2294 * Special case: allow a single dot if the second part is not one
2295 * of the 3 or 4 letter top level domains or is any 2 letter TLD
2298 checkdots(register const char *name)
2300 register const char *cp, *cp2;
2302 if ((cp = strchr(name, '.')) == NULL)
2303 return (0);
2304 cp2 = name + strlen(name) - 1;
2305 if (cp2 >= name && *cp2 == '.')
2306 return (0);
2308 /* Return true of more than one dot*/
2309 ++cp;
2310 if (strchr(cp, '.') != NULL)
2311 return (1);
2313 if (strlen(cp) == 2 ||
2314 strcasecmp(cp, "gov") == 0 ||
2315 strcasecmp(cp, "edu") == 0 ||
2316 strcasecmp(cp, "com") == 0 ||
2317 strcasecmp(cp, "net") == 0 ||
2318 strcasecmp(cp, "org") == 0 ||
2319 strcasecmp(cp, "mil") == 0 ||
2320 strcasecmp(cp, "int") == 0 ||
2321 strcasecmp(cp, "nato") == 0 ||
2322 strcasecmp(cp, "arpa") == 0)
2323 return (1);
2324 return (0);
2328 cmpaddr(register const void *ip1, register const void *ip2)
2330 register u_int32_t a1, a2;
2332 a1 = (*(struct item **)ip1)->addr;
2333 a2 = (*(struct item **)ip2)->addr;
2335 if (a1 < a2)
2336 return (-1);
2337 else if (a1 > a2)
2338 return (1);
2339 else
2340 return (0);
2344 cmphost(register const void *ip1, register const void *ip2)
2346 register const char *s1, *s2;
2348 s1 = (*(struct item **)ip1)->host;
2349 s2 = (*(struct item **)ip2)->host;
2351 return (strcasecmp(s1, s2));
2354 /* Returns a pointer after the next token or quoted string, else NULL */
2355 char *
2356 parsequoted(register char *cp)
2359 if (*cp == '"') {
2360 ++cp;
2361 while (*cp != '"' && *cp != '\0')
2362 ++cp;
2363 if (*cp != '"')
2364 return (NULL);
2365 ++cp;
2366 } else {
2367 while (!isspace(*cp) && *cp != '\0')
2368 ++cp;
2370 return (cp);
2373 __dead void
2374 usage(void)
2376 extern char version[];
2378 fprintf(stderr, "Version %s\n", version);
2379 fprintf(stderr, "usage: %s [-d] [-b named.boot] [-B nslint.boot]\n",
2380 prog);
2381 fprintf(stderr, " %s [-d] [-c named.conf] [-C nslint.conf]\n",
2382 prog);
2383 exit(1);