1171568Sscottl/*	$NetBSD$	*/
2211095Sdes
3171568Sscottl/*
4171568Sscottl * Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001
5171568Sscottl *	The Regents of the University of California.  All rights reserved.
6171568Sscottl *
7171568Sscottl * Redistribution and use in source and binary forms, with or without
8171568Sscottl * modification, are permitted provided that: (1) source code distributions
9171568Sscottl * retain the above copyright notice and this paragraph in its entirety, (2)
10171568Sscottl * distributions including binary code include the above copyright notice and
11171568Sscottl * this paragraph in its entirety in the documentation or other materials
12171568Sscottl * provided with the distribution, and (3) all advertising materials mentioning
13171568Sscottl * features or use of this software display the following acknowledgement:
14171568Sscottl * ``This product includes software developed by the University of California,
15171568Sscottl * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
16171568Sscottl * the University nor the names of its contributors may be used to endorse
17171568Sscottl * or promote products derived from this software without specific prior
18171568Sscottl * written permission.
19171568Sscottl * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
20171568Sscottl * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
21171568Sscottl * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
22171568Sscottl */
23171568Sscottl#ifndef lint
24171568Sscottlstatic const char copyright[] =
25171568Sscottl    "@(#) Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001\n\
26171568SscottlThe Regents of the University of California.  All rights reserved.\n";
27171568Sscottlstatic const char rcsid[] =
28171568Sscottl    "@(#) Id: nslint.c,v 1.2 2011/11/30 00:48:51 marka Exp  (LBL)";
29171568Sscottl#endif
30171568Sscottl/*
31171568Sscottl * nslint - perform consistency checks on dns files
32171568Sscottl */
33171568Sscottl
34171568Sscottl#include <sys/types.h>
35171568Sscottl#include <sys/stat.h>
36171568Sscottl
37171568Sscottl#include <netinet/in.h>
38171568Sscottl
39171568Sscottl#include <arpa/inet.h>
40171568Sscottl
41171568Sscottl#include <ctype.h>
42171568Sscottl#include <errno.h>
43171568Sscottl#ifdef HAVE_FCNTL_H
44171568Sscottl#include <fcntl.h>
45171568Sscottl#endif
46171568Sscottl#ifdef HAVE_MALLOC_H
47171568Sscottl#include <malloc.h>
48171568Sscottl#endif
49171568Sscottl#ifdef HAVE_MEMORY_H
50171568Sscottl#include <memory.h>
51171568Sscottl#endif
52211095Sdes#include <netdb.h>
53171568Sscottl#include <stdio.h>
54171568Sscottl#include <stdlib.h>
55171568Sscottl#include <string.h>
56171568Sscottl#include <time.h>
57171568Sscottl#include <unistd.h>
58171568Sscottl
59171568Sscottl#include "savestr.h"
60171568Sscottl
61171568Sscottl#include "gnuc.h"
62171568Sscottl#ifdef HAVE_OS_PROTO_H
63171568Sscottl#include "os-proto.h"
64171568Sscottl#endif
65171568Sscottl
66171568Sscottl#define NSLINTBOOT "nslint.boot"	/* default nslint.boot file */
67171568Sscottl#define NSLINTCONF "nslint.conf"	/* default nslint.conf file */
68171568Sscottl
69171568Sscottl/* item struct */
70171568Sscottlstruct item {
71171568Sscottl	char *host;		/* pointer to hostname */
72171568Sscottl	u_int32_t addr;		/* ip address */
73171568Sscottl	u_int ttl;		/* ttl of A records */
74171568Sscottl	int records;		/* resource records seen */
75171568Sscottl	int flags;		/* flags word */
76171568Sscottl};
77171568Sscottl
78171568Sscottl/* Resource records seen */
79171568Sscottl#define REC_A		0x0001
80171568Sscottl#define REC_PTR		0x0002
81171568Sscottl#define REC_WKS		0x0004
82171568Sscottl#define REC_HINFO	0x0008
83171568Sscottl#define REC_MX		0x0010
84171568Sscottl#define REC_CNAME	0x0020
85171568Sscottl#define REC_NS		0x0040
86171568Sscottl#define REC_SOA		0x0080
87171568Sscottl#define REC_RP		0x0100
88171568Sscottl#define REC_TXT		0x0200
89171568Sscottl#define REC_SRV		0x0400
90171568Sscottl
91171568Sscottl/* These aren't real records */
92171568Sscottl#define REC_OTHER	0x0800
93171568Sscottl#define REC_REF		0x1000
94171568Sscottl#define REC_UNKNOWN	0x2000
95171568Sscottl
96171568Sscottl/* Test for records we want to map to REC_OTHER */
97171568Sscottl#define MASK_TEST_REC (REC_WKS | REC_HINFO | \
98171568Sscottl    REC_MX | REC_SOA | REC_RP | REC_TXT | REC_SRV | REC_UNKNOWN)
99171568Sscottl
100171568Sscottl/* Mask away records we don't care about in the final processing to REC_OTHER */
101171568Sscottl#define MASK_CHECK_REC \
102171568Sscottl    (REC_A | REC_PTR | REC_CNAME | REC_REF | REC_OTHER)
103171568Sscottl
104171568Sscottl/* Test for records we want to check for duplicate name detection */
105171568Sscottl#define MASK_TEST_DUP \
106171568Sscottl    (REC_A | REC_HINFO)
107171568Sscottl
108171568Sscottl/* Flags */
109171568Sscottl#define FLG_SELFMX	0x001	/* mx record refers to self */
110171568Sscottl#define FLG_MXREF	0x002	/* this record referred to by a mx record */
111171568Sscottl#define FLG_SMTPWKS	0x004	/* saw wks with smtp/tcp */
112171568Sscottl#define FLG_ALLOWDUPA	0x008	/* allow duplicate a records */
113171568Sscottl
114171568Sscottl/* Test for smtp problems */
115171568Sscottl#define MASK_TEST_SMTP \
116171568Sscottl    (FLG_SELFMX | FLG_SMTPWKS)
117171568Sscottl
118171568Sscottl
119171568Sscottl#define ITEMSIZE (1 << 17)	/* power of two */
120171568Sscottl#define ITEMHASH(str, h, p) \
121171568Sscottl    for (p = str, h = 0; *p != '.' && *p != '\0';) h = (h << 5) - h + *p++
122171568Sscottl
123171568Sscottlstruct	item items[ITEMSIZE];
124171568Sscottlint	itemcnt;		/* count of items */
125171568Sscottl
126171568Sscottl/* Hostname string storage */
127171568Sscottl#define STRSIZE 8192;		/* size to malloc when more space is needed */
128171568Sscottlchar	*strptr;		/* pointer to string pool */
129171568Sscottlint	strsize;		/* size of space left in pool */
130171568Sscottl
131171568Sscottlint	debug;
132171568Sscottlint	errors;
133171568Sscottlchar	*bootfile = "/etc/named.boot";
134171568Sscottlchar	*conffile = "/etc/named.conf";
135171568Sscottlchar	*nslintboot;
136171568Sscottlchar	*nslintconf;
137171568Sscottlchar	*prog;
138171568Sscottlchar	*cwd = ".";
139171568Sscottl
140171568Sscottlchar **protoserv;		/* valid protocol/service names */
141171568Sscottlint protoserv_init;
142171568Sscottlint protoserv_last;
143171568Sscottlint protoserv_len;
144171568Sscottl
145171568Sscottlstatic char inaddr[] = ".in-addr.arpa.";
146171568Sscottl
147171568Sscottl/* SOA record */
148171568Sscottl#define SOA_SERIAL	0
149171568Sscottl#define SOA_REFRESH	1
150171568Sscottl#define SOA_RETRY	2
151171568Sscottl#define SOA_EXPIRE	3
152211095Sdes#define SOA_MINIMUM	4
153171568Sscottl
154171568Sscottlstatic u_int soaval[5];
155171568Sscottlstatic int nsoaval;
156171568Sscottl#define NSOAVAL (sizeof(soaval) / sizeof(soaval[0]))
157171568Sscottl
158171568Sscottl/* Forwards */
159171568Sscottlstatic	inline void add_domain(char *, const char *);
160171568Sscottlint	checkdots(const char *);
161171568Sscottlvoid	checkdups(struct item *, int);
162171568Sscottlint	checkserv(const char *, char **p);
163171568Sscottlint	checkwks(FILE *, char *, int *, char **);
164171568Sscottlint	cmpaddr(const void *, const void *);
165171568Sscottlint	cmphost(const void *, const void *);
166171568Sscottlint	doboot(const char *, int);
167171568Sscottlint	doconf(const char *, int);
168171568Sscottlvoid	initprotoserv(void);
169171568Sscottlchar	*intoa(u_int32_t);
170171568Sscottlint	main(int, char **);
171171568Sscottlint	nslint(void);
172171568Sscottlint	parseinaddr(const char *, u_int32_t *, u_int32_t *);
173171568Sscottlint	parsenetwork(const char *, char **);
174171568Sscottlu_int32_t parseptr(const char *, u_int32_t, u_int32_t, char **);
175171568Sscottlchar	*parsequoted(char *);
176171568Sscottlint	parsesoa(const char *, char **);
177171568Sscottlvoid	process(const char *, const char *, const char *);
178171568Sscottlint	rfc1034host(const char *, int);
179171568Sscottlint	updateitem(const char *, u_int32_t, int, u_int, int);
180171568Sscottl__dead	void usage(void) __attribute__((volatile));
181171568Sscottl
182171568Sscottlextern	char *optarg;
183171568Sscottlextern	int optind, opterr;
184171568Sscottl
185171568Sscottl/* add domain if necessary */
186171568Sscottlstatic inline void
187171568Sscottladd_domain(register char *name, register const char *domain)
188171568Sscottl{
189171568Sscottl	register char *cp;
190171568Sscottl
191171568Sscottl	/* Kill trailing white space and convert to lowercase */
192171568Sscottl	for (cp = name; *cp != '\0' && !isspace(*cp); ++cp)
193171568Sscottl		if (isupper(*cp))
194171568Sscottl			*cp = tolower(*cp);
195171568Sscottl	*cp-- = '\0';
196171568Sscottl	/* If necessary, append domain */
197171568Sscottl	if (cp >= name && *cp++ != '.') {
198171568Sscottl		if (*domain != '.')
199171568Sscottl			*cp++ = '.';
200171568Sscottl		(void)strcpy(cp, domain);
201171568Sscottl	}
202171568Sscottl	/* XXX should we insure a trailing dot? */
203171568Sscottl}
204171568Sscottl
205int
206main(int argc, char **argv)
207{
208	register char *cp;
209	register int op, status, i, donamedboot, donamedconf;
210
211	if ((cp = strrchr(argv[0], '/')) != NULL)
212		prog = cp + 1;
213	else
214		prog = argv[0];
215
216	donamedboot = 0;
217	donamedconf = 0;
218	while ((op = getopt(argc, argv, "b:c:B:C:d")) != -1)
219		switch (op) {
220
221		case 'b':
222			bootfile = optarg;
223			++donamedboot;
224			break;
225
226		case 'c':
227			conffile = optarg;
228			++donamedconf;
229			break;
230
231		case 'B':
232			nslintboot = optarg;
233			++donamedboot;
234			break;
235
236		case 'C':
237			nslintconf = optarg;
238			++donamedconf;
239			break;
240
241		case 'd':
242			++debug;
243			break;
244
245		default:
246			usage();
247		}
248	if (optind != argc || (donamedboot && donamedconf))
249		usage();
250
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;
262	}
263
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;
274	}
275	status |= nslint();
276	exit (status);
277}
278
279struct netlist {
280	u_int32_t net;
281	u_int32_t mask;
282};
283
284static struct netlist *netlist;
285static u_int netlistsize;	/* size of array */
286static u_int netlistcnt;	/* next free element */
287
288static u_int32_t
289findmask(u_int32_t addr)
290{
291	register int i;
292
293	for (i = 0; i < netlistcnt; ++i)
294		if ((addr & netlist[i].mask) == netlist[i].net)
295			return (netlist[i].mask);
296	return (0);
297}
298
299int
300parsenetwork(register const char *cp, register char **errstrp)
301{
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];
307
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;
323	}
324
325
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);
335		}
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;
343		}
344		if (i != 3) {
345			*errstrp = "wrong number of dots in mask";
346			return (0);
347		}
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);
357		}
358		mask = 0xffffffff << (32 - w);
359	} else {
360		*errstrp = "garbage after net";
361		return (0);
362	}
363
364	while (isspace(*cp))
365		++cp;
366
367	if (*cp != '\0') {
368		*errstrp = "trailing garbage";
369		return (0);
370	}
371
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);
378	}
379
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));
390		}
391		if (netlist == NULL) {
392			fprintf(stderr, "%s: nslint: malloc/realloc: %s\n",
393			    prog, strerror(errno));
394			exit(1);
395		}
396	}
397
398	/* Add to list */
399	netlist[netlistcnt].net = net;
400	netlist[netlistcnt].mask = mask;
401	++netlistcnt;
402
403	return (1);
404}
405
406int
407doboot(register const char *file, register int mustexist)
408{
409	register int n;
410	register char *cp, *cp2;
411	register FILE *f;
412	char *errstr;
413	char buf[1024], name[128];
414
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);
425		}
426		fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
427		exit(1);
428	}
429	if (debug > 1)
430		printf("%s: doit: opened %s\n", prog, file);
431
432	n = 0;
433	while (fgets(buf, sizeof(buf), f) != NULL) {
434		++n;
435
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;
446
447		/* Eat leading whitespace */
448		while (isspace(*cp))
449			++cp;
450
451		/* Skip blank lines */
452		if (*cp == '\n' || *cp == '\0')
453			continue;
454
455		/* Get name */
456		cp2 = cp;
457		while (!isspace(*cp) && *cp != '\0')
458			++cp;
459		*cp++ = '\0';
460
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);
475			}
476			cwd = savestr(cp2);
477			continue;
478		}
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';
490
491			/* Find file */
492			while (isspace(*cp))
493				++cp;
494
495			/* Terminate directory */
496			cp2 = cp;
497			while (!isspace(*cp) && *cp != '\0')
498				++cp;
499			*cp = '\0';
500
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;
506		}
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);
513			}
514			continue;
515		}
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;
524		}
525		/* Eat any other options */
526	}
527	(void)fclose(f);
528
529	return (errors != 0);
530}
531
532int
533doconf(register const char *file, register int mustexist)
534{
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];
542
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);
553		}
554		fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
555		exit(1);
556	}
557	if (debug > 1)
558		printf("%s: doconf: opened %s\n", prog, file);
559
560	if (fstat(fd, &sbuf) < 0) {
561		fprintf(stderr, "%s: fstat(%s) %s\n",
562		    prog, file, strerror(errno));
563		exit(1);
564	}
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);
569	}
570
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);
580		}
581		cp += cc;
582		n -= cc;
583	} while (cc != 0 && cc < n);
584	buf[cc] = '\0';
585
586#define EATWHITESPACE \
587	while (isspace(*cp)) { \
588		if (*cp == '\n') \
589			++n; \
590		++cp; \
591	}
592
593/* Handle both to-end-of-line and C style comments */
594#define EATCOMMENTS \
595	{ \
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; \
605		} \
606		else if (strncmp(cp, "//", 2) == 0) { \
607			sawcomment = 1; \
608			cp += 2; \
609			while (*cp != '\n' && *cp != '\0') \
610				++cp; \
611		} \
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; \
620				} \
621			} \
622		} \
623	} while (sawcomment); \
624	}
625
626#define GETNAME(name, len) \
627	{ \
628	(name) = cp; \
629	(len) = 0; \
630	while (!isspace(*cp) && *cp != ';' && *cp != '\0') { \
631		++(len); \
632		++cp; \
633	} \
634	}
635
636#define GETQUOTEDNAME(name, len) \
637	{ \
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; \
649	} \
650	if (*cp != '"') { \
651		++errors; \
652		fprintf(stderr, "%s: %s:%d missing right quote\n", \
653		    prog, file, n); \
654	} else \
655		++cp; \
656	}
657
658/* Eat everything to the next semicolon, perhaps eating matching qbraces */
659#define EATSEMICOLON \
660	{ \
661	register int depth = 0; \
662	while (*cp != '\0') { \
663		EATCOMMENTS \
664		if (*cp == ';') { \
665			++cp; \
666			if (depth == 0) \
667				break; \
668			continue; \
669		} \
670		if (*cp == '{') { \
671			++depth; \
672			++cp; \
673			continue; \
674		} \
675		if (*cp == '}') { \
676			--depth; \
677			++cp; \
678			continue; \
679		} \
680		++cp; \
681	} \
682	}
683
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;
698		}
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;
720				}
721
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);
735					}
736					cwd = savestr(cp2);
737				}
738				EATSEMICOLON
739				EATCOMMENTS
740			}
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;
751		}
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
766			}
767			if (*cp != '{')  {	/* } */
768				++errors;
769				fprintf(stderr,
770			    "%s: %s:%d missing left qbrace in zone\n",
771				    prog, file, n);
772				continue;
773			}
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;
784				}
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;
794				}
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;
806					}
807				} else if (strncasecmp(name, "file",
808				    namelen) == 0) {
809					EATCOMMENTS
810					GETQUOTEDNAME(filename, filenamelen)
811				}
812				/* Just ignore keywords we don't understand */
813				EATSEMICOLON
814				EATCOMMENTS
815			}
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;
838			}
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;
846				}
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';
856				}
857				filename[filenamelen] = '\0';
858				nsoaval = -1;
859				memset(soaval, 0, sizeof(soaval));
860				process(filename, zone, zone);
861			}
862			continue;
863		}
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)
882
883
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);
890					}
891				} else {
892					++errors;
893					fprintf(stderr,
894					    "%s: unknown nslint \"%.*s\"\n",
895					    prog, namelen, name);
896				}
897				EATSEMICOLON
898				EATCOMMENTS
899			}
900			++cp;
901			EATCOMMENTS
902			if (*cp != ';')  {
903				++errors;
904				fprintf(stderr, "missing options semi\n");
905			} else
906				++cp;
907			continue;
908		}
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;
917		}
918
919		/* Skip over statements we don't understand */
920		EATSEMICOLON
921	}
922
923	free(buf);
924	close(fd);
925	return (errors != 0);
926}
927
928/* Return true when done */
929int
930parsesoa(register const char *cp, register char **errstrp)
931{
932	register char ch, *garbage;
933	static char errstr[132];
934
935	/* Eat leading whitespace */
936	while (isspace(*cp))
937		++cp;
938
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;
948	}
949
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) {
966
967			case 'w':
968				soaval[nsoaval] *= 7;
969				/* fall through */
970
971			case 'd':
972				soaval[nsoaval] *= 24;
973				/* fall through */
974
975			case 'h':
976				soaval[nsoaval] *= 60;
977				/* fall through */
978
979			case 'm':
980				soaval[nsoaval] *= 60;
981				/* fall through */
982
983			case 's':
984				++cp;
985				break;
986
987			default:
988				;	/* none */
989			}
990		}
991		while (isspace(*cp))
992			++cp;
993		garbage = "trailing garbage";
994		++nsoaval;
995	}
996
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;
1016		}
1017		return (1);
1018	}
1019
1020	if (*cp != '\0') {
1021		*errstrp = garbage;
1022		return (1);
1023	}
1024
1025	return (0);
1026}
1027
1028void
1029process(register const char *file, register const char *domain,
1030    register const char *zone)
1031{
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";
1043
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;
1050	}
1051	if (debug > 1)
1052		printf("%s: process: opened %s/%s\n", prog, cwd, file);
1053
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		fclose(f);
1065		return;
1066	}
1067
1068	lastname[0] = '\0';
1069	sawsoa = 0;
1070	while (fgets(buf, sizeof(buf), f) != NULL) {
1071		++n;
1072		cp = buf;
1073		while (*cp != '\0') {
1074			/* Handle quoted strings (but don't report errors) */
1075			if (*cp == '"') {
1076				++cp;
1077				while (*cp != '"' && *cp != '\n' && *cp != '\0')
1078					++cp;
1079				continue;
1080			}
1081			if (*cp == '\n' || *cp == ';')
1082				break;
1083			++cp;
1084		}
1085		*cp-- = '\0';
1086
1087		/* Nuke trailing white space */
1088		while (cp >= buf && isspace(*cp))
1089			*cp-- = '\0';
1090
1091		cp = buf;
1092		if (*cp == '\0')
1093			continue;
1094
1095		/* Handle multi-line soa records */
1096		if (sawsoa) {
1097			errstr = NULL;
1098			if (parsesoa(cp, &errstr))
1099				sawsoa = 0;
1100			if (errstr != NULL) {
1101				++errors;
1102				fprintf(stderr,
1103				    "%s: %s/%s:%d bad \"soa\" record (%s)\n",
1104				    prog, cwd, file, n, errstr);
1105			}
1106			continue;
1107		}
1108		if (debug > 3)
1109			printf(">%s<\n", cp);
1110
1111		/* Look for name */
1112		if (isspace(*cp)) {
1113			/* Same name as last record */
1114			if (lastname[0] == '\0') {
1115				++errors;
1116				fprintf(stderr,
1117				    "%s: %s/%s:%d no default name\n",
1118				    prog, cwd, file, n);
1119				continue;
1120			}
1121			(void)strcpy(name, lastname);
1122		} else {
1123			/* Extract name, converting to lowercase */
1124			for (cp2 = name; !isspace(*cp) && *cp != '\0'; ++cp)
1125				if (isupper(*cp))
1126					*cp2++ = tolower(*cp);
1127				else
1128					*cp2++ = *cp;
1129			*cp2 = '\0';
1130
1131			/* Check for domain shorthand */
1132			if (name[0] == '@' && name[1] == '\0')
1133				(void)strcpy(name, domain);
1134		}
1135
1136		/* Find next token */
1137		while (isspace(*cp))
1138			++cp;
1139
1140		/* Handle includes (gag) */
1141		if (name[0] == '$' && strcasecmp(name, "$include") == 0) {
1142			/* Extract filename */
1143			cp2 = name;
1144			while (!isspace(*cp) && *cp != '\0')
1145				*cp2++ = *cp++;
1146			*cp2 = '\0';
1147
1148			/* Look for optional domain */
1149			while (isspace(*cp))
1150				++cp;
1151			if (*cp == '\0')
1152				process(name, domain, zone);
1153			else {
1154				cp2 = cp;
1155				/* Convert optional domain to lowercase */
1156				for (; !isspace(*cp) && *cp != '\0'; ++cp)
1157					if (isupper(*cp))
1158						*cp = tolower(*cp);
1159				*cp = '\0';
1160				process(name, cp2, cp2);
1161			}
1162			continue;
1163		}
1164
1165		/* Handle $origin */
1166		if (name[0] == '$' && strcasecmp(name, "$origin") == 0) {
1167			/* Extract domain, converting to lowercase */
1168			for (cp2 = odomain; !isspace(*cp) && *cp != '\0'; ++cp)
1169				if (isupper(*cp))
1170					*cp2++ = tolower(*cp);
1171				else
1172					*cp2++ = *cp;
1173			*cp2 = '\0';
1174			domain = odomain;
1175			lastname[0] = '\0';
1176
1177			/* Are we doing an in-addr.arpa domain? */
1178			net = 0;
1179			mask = 0;
1180			ccp = domain + strlen(domain) - (sizeof(inaddr) - 1);
1181			if (ccp >= domain && strcasecmp(ccp, inaddr) == 0 &&
1182			    !parseinaddr(domain, &net, &mask)) {
1183				++errors;
1184				fprintf(stderr,
1185				    "%s: %s/%s:%d bad in-addr.arpa domain\n",
1186				    prog, cwd, file, n);
1187				return;
1188			}
1189			continue;
1190		}
1191
1192		/* Handle ttl */
1193		if (name[0] == '$' && strcasecmp(name, "$ttl") == 0) {
1194			cp2 = cp;
1195			while (isdigit(*cp))
1196				++cp;
1197			ch = *cp;
1198			if (isupper(ch))
1199				ch = tolower(ch);
1200			if (strchr("wdhms", ch) != NULL)
1201				++cp;
1202			while (isspace(*cp))
1203				++cp;
1204			if (*cp != '\0') {
1205				++errors;
1206				fprintf(stderr,
1207				    "%s: %s/%s:%d bad $ttl \"%s\"\n",
1208				    prog, cwd, file, n, cp2);
1209			}
1210			(void)strcpy(name, lastname);
1211			continue;
1212		}
1213
1214		/* Parse ttl or use default  */
1215		if (isdigit(*cp)) {
1216			ttl = atoi(cp);
1217			do {
1218				++cp;
1219			} while (isdigit(*cp));
1220
1221			ch = *cp;
1222			if (isupper(ch))
1223				ch = tolower(ch);
1224			switch (ch) {
1225
1226			case 'w':
1227				ttl *= 7;
1228				/* fall through */
1229
1230			case 'd':
1231				ttl *= 24;
1232				/* fall through */
1233
1234			case 'h':
1235				ttl *= 60;
1236				/* fall through */
1237
1238			case 'm':
1239				ttl *= 60;
1240				/* fall through */
1241
1242			case 's':
1243				++cp;
1244				break;
1245
1246			default:
1247				;	/* none */
1248			}
1249
1250
1251			if (!isspace(*cp)) {
1252				++errors;
1253				fprintf(stderr, "%s: %s/%s:%d bad ttl\n",
1254				    prog, cwd, file, n);
1255				continue;
1256			}
1257
1258			/* Find next token */
1259			++cp;
1260			while (isspace(*cp))
1261				++cp;
1262		} else
1263			ttl = soaval[SOA_MINIMUM];
1264
1265		/* Eat optional "in" */
1266		if ((cp[0] == 'i' || cp[0] == 'I') &&
1267		    (cp[1] == 'n' || cp[1] == 'N') && isspace(cp[2])) {
1268			/* Find next token */
1269			cp += 3;
1270			while (isspace(*cp))
1271				++cp;
1272		} else if ((cp[0] == 'c' || cp[0] == 'C') &&
1273		    isspace(cp[5]) && strncasecmp(cp, "chaos", 5) == 0) {
1274			/* Find next token */
1275			cp += 5;
1276			while (isspace(*cp))
1277				++cp;
1278		}
1279
1280		/* Find end of record type, converting to lowercase */
1281		rtype = cp;
1282		for (rtype = cp; !isspace(*cp) && *cp != '\0'; ++cp)
1283			if (isupper(*cp))
1284				*cp = tolower(*cp);
1285		*cp++ = '\0';
1286
1287		/* Find "the rest" */
1288		while (isspace(*cp))
1289			++cp;
1290
1291		/* Check for non-ptr names with dots but no trailing dot */
1292		if (!isdigit(*name) &&
1293		    checkdots(name) && strcmp(domain, ".") != 0) {
1294			++errors;
1295			fprintf(stderr,
1296			  "%s: %s/%s:%d \"%s\" name missing trailing dot: %s\n",
1297			    prog, cwd, file, n, rtype, name);
1298		}
1299
1300		/* Check for FQDNs outside the zone */
1301		cp2 = name + strlen(name) - 1;
1302		if (cp2 >= name && *cp2 == '.' && strchr(name, '.') != NULL) {
1303			cp2 = name + strlen(name) - strlen(zone);
1304			if (cp2 >= name && strcasecmp(cp2, zone) != 0) {
1305				++errors;
1306				fprintf(stderr,
1307				    "%s: %s/%s:%d \"%s\" outside zone %s\n",
1308				    prog, cwd, file, n, name, zone);
1309			}
1310		}
1311
1312#define CHECK4(p, a, b, c, d) \
1313    (p[0] == (a) && p[1] == (b) && p[2] == (c) && p[3] == (d) && p[4] == '\0')
1314#define CHECK3(p, a, b, c) \
1315    (p[0] == (a) && p[1] == (b) && p[2] == (c) && p[3] == '\0')
1316#define CHECK2(p, a, b) \
1317    (p[0] == (a) && p[1] == (b) && p[2] == '\0')
1318#define CHECKDOT(p) \
1319    (p[0] == '.' && p[1] == '\0')
1320
1321		if (rtype[0] == 'a' && rtype[1] == '\0') {
1322			/* Handle "a" record */
1323			add_domain(name, domain);
1324			addr = htonl(inet_addr(cp));
1325			if ((int)addr == -1) {
1326				++errors;
1327				cp2 = cp + strlen(cp) - 1;
1328				if (cp2 >= cp && *cp2 == '\n')
1329					*cp2 = '\0';
1330				fprintf(stderr,
1331			    "%s: %s/%s:%d bad \"a\" record ip addr \"%s\"\n",
1332				    prog, cwd, file, n, cp);
1333				continue;
1334			}
1335			errors += updateitem(name, addr, REC_A, ttl, 0);
1336		} else if (CHECK4(rtype, 'a', 'a', 'a', 'a')) {
1337			/* Just eat for now */
1338			continue;
1339		} else if (CHECK3(rtype, 'p', 't', 'r')) {
1340			/* Handle "ptr" record */
1341			add_domain(name, domain);
1342			if (strcmp(cp, "@") == 0)
1343				(void)strcpy(cp, zone);
1344			if (checkdots(cp)) {
1345				++errors;
1346				fprintf(stderr, dotfmt,
1347				    prog, cwd, file, n, rtype, cp);
1348			}
1349			add_domain(cp, domain);
1350			errstr = NULL;
1351			addr = parseptr(name, net, mask, &errstr);
1352			if (errstr != NULL) {
1353				++errors;
1354				fprintf(stderr,
1355			"%s: %s/%s:%d bad \"ptr\" record (%s) ip addr \"%s\"\n",
1356				    prog, cwd, file, n, errstr, name);
1357				continue;
1358			}
1359			errors += updateitem(cp, addr, REC_PTR, 0, 0);
1360		} else if (CHECK3(rtype, 's', 'o', 'a')) {
1361			/* Handle "soa" record */
1362			if (!CHECKDOT(name)) {
1363				add_domain(name, domain);
1364				errors += updateitem(name, 0, REC_SOA, 0, 0);
1365			}
1366			errstr = NULL;
1367			if (!parsesoa(cp, &errstr))
1368				++sawsoa;
1369			if (errstr != NULL) {
1370				++errors;
1371				fprintf(stderr,
1372				    "%s: %s/%s:%d bad \"soa\" record (%s)\n",
1373				    prog, cwd, file, n, errstr);
1374				continue;
1375			}
1376		} else if (CHECK3(rtype, 'w', 'k', 's')) {
1377			/* Handle "wks" record */
1378			addr = htonl(inet_addr(cp));
1379			if ((int)addr == -1) {
1380				++errors;
1381				cp2 = cp;
1382				while (!isspace(*cp2) && *cp2 != '\0')
1383					++cp2;
1384				*cp2 = '\0';
1385				fprintf(stderr,
1386			    "%s: %s/%s:%d bad \"wks\" record ip addr \"%s\"\n",
1387				    prog, cwd, file, n, cp);
1388				continue;
1389			}
1390			/* Step over ip address */
1391			while (*cp == '.' || isdigit(*cp))
1392				++cp;
1393			while (isspace(*cp))
1394				*cp++ = '\0';
1395			/* Make sure services are legit */
1396			errstr = NULL;
1397			n += checkwks(f, cp, &smtp, &errstr);
1398			if (errstr != NULL) {
1399				++errors;
1400				fprintf(stderr,
1401				    "%s: %s/%s:%d bad \"wks\" record (%s)\n",
1402				    prog, cwd, file, n, errstr);
1403				continue;
1404			}
1405			add_domain(name, domain);
1406			errors += updateitem(name, addr, REC_WKS,
1407			    0, smtp ? FLG_SMTPWKS : 0);
1408			/* XXX check to see if ip address records exists? */
1409		} else if (rtype[0] == 'h' && strcmp(rtype, "hinfo") == 0) {
1410			/* Handle "hinfo" record */
1411			add_domain(name, domain);
1412			errors += updateitem(name, 0, REC_HINFO, 0, 0);
1413			cp2 = cp;
1414			cp = parsequoted(cp);
1415			if (cp == NULL) {
1416				++errors;
1417				fprintf(stderr,
1418			    "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
1419				    prog, cwd, file, n, cp2);
1420				continue;
1421			}
1422			if (!isspace(*cp)) {
1423				++errors;
1424				fprintf(stderr,
1425			    "%s: %s/%s:%d \"hinfo\" missing white space: %s\n",
1426				    prog, cwd, file, n, cp2);
1427				continue;
1428			}
1429			++cp;
1430			while (isspace(*cp))
1431				++cp;
1432			if (*cp == '\0') {
1433				++errors;
1434				fprintf(stderr,
1435			    "%s: %s/%s:%d \"hinfo\" missing keyword: %s\n",
1436				    prog, cwd, file, n, cp2);
1437				continue;
1438			}
1439			cp = parsequoted(cp);
1440			if (cp == NULL) {
1441				++errors;
1442				fprintf(stderr,
1443			    "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
1444				    prog, cwd, file, n, cp2);
1445				continue;
1446			}
1447			if (*cp != '\0') {
1448				++errors;
1449				fprintf(stderr,
1450			"%s: %s/%s:%d \"hinfo\" garbage after keywords: %s\n",
1451				    prog, cwd, file, n, cp2);
1452				continue;
1453			}
1454		} else if (CHECK2(rtype, 'm', 'x')) {
1455			/* Handle "mx" record */
1456			add_domain(name, domain);
1457			errors += updateitem(name, 0, REC_MX, ttl, 0);
1458
1459			/* Look for priority */
1460			if (!isdigit(*cp)) {
1461				++errors;
1462				fprintf(stderr,
1463				    "%s: %s/%s:%d bad \"mx\" priority: %s\n",
1464				    prog, cwd, file, n, cp);
1465			}
1466
1467			/* Skip over priority */
1468			++cp;
1469			while (isdigit(*cp))
1470				++cp;
1471			while (isspace(*cp))
1472				++cp;
1473			if (*cp == '\0') {
1474				++errors;
1475				fprintf(stderr,
1476				    "%s: %s/%s:%d missing \"mx\" hostname\n",
1477				    prog, cwd, file, n);
1478			}
1479			if (strcmp(cp, "@") == 0)
1480				(void)strcpy(cp, zone);
1481			if (checkdots(cp)) {
1482				++errors;
1483				fprintf(stderr, dotfmt,
1484				    prog, cwd, file, n, rtype, cp);
1485			}
1486
1487			/* Check to see if mx host exists */
1488			add_domain(cp, domain);
1489			flags = FLG_MXREF;
1490			if (*name == *cp && strcmp(name, cp) == 0)
1491				flags |= FLG_SELFMX;
1492			errors += updateitem(cp, 0, REC_REF, 0, flags);
1493		} else if (rtype[0] == 'c' && strcmp(rtype, "cname") == 0) {
1494			/* Handle "cname" record */
1495			add_domain(name, domain);
1496			errors += updateitem(name, 0, REC_CNAME, 0, 0);
1497			if (checkdots(cp)) {
1498				++errors;
1499				fprintf(stderr, dotfmt,
1500				    prog, cwd, file, n, rtype, cp);
1501			}
1502
1503			/* Make sure cname points somewhere */
1504			if (strcmp(cp, "@") == 0)
1505				(void)strcpy(cp, zone);
1506			add_domain(cp, domain);
1507			errors += updateitem(cp, 0, REC_REF, 0, 0);
1508		} else if (CHECK3(rtype, 's', 'r', 'v')) {
1509			/* Handle "srv" record */
1510			add_domain(name, domain);
1511			errors += updateitem(name, 0, REC_SRV, 0, 0);
1512			cp2 = cp;
1513
1514			/* Skip over three values */
1515			for (i = 0; i < 3; ++i) {
1516				if (!isdigit(*cp)) {
1517					++errors;
1518					fprintf(stderr, "%s: %s/%s:%d"
1519					    " bad \"srv\" value: %s\n",
1520					    prog, cwd, file, n, cp);
1521				}
1522
1523				/* Skip over value */
1524				++cp;
1525				while (isdigit(*cp))
1526					++cp;
1527				while (isspace(*cp))
1528					++cp;
1529			}
1530
1531			/* Check to see if mx host exists */
1532			add_domain(cp, domain);
1533			errors += updateitem(cp, 0, REC_REF, 0, 0);
1534		} else if (CHECK3(rtype, 't', 'x', 't')) {
1535			/* Handle "txt" record */
1536			add_domain(name, domain);
1537			errors += updateitem(name, 0, REC_TXT, 0, 0);
1538			cp2 = cp;
1539			cp = parsequoted(cp);
1540			if (cp == NULL) {
1541				++errors;
1542				fprintf(stderr,
1543				    "%s: %s/%s:%d \"txt\" missing quote: %s\n",
1544				    prog, cwd, file, n, cp2);
1545				continue;
1546			}
1547			while (isspace(*cp))
1548				++cp;
1549			if (*cp != '\0') {
1550				++errors;
1551				fprintf(stderr,
1552			    "%s: %s/%s:%d \"txt\" garbage after text: %s\n",
1553				    prog, cwd, file, n, cp2);
1554				continue;
1555			}
1556		} else if (CHECK2(rtype, 'n', 's')) {
1557			/* Handle "ns" record */
1558			errors += updateitem(zone, 0, REC_NS, 0, 0);
1559			if (strcmp(cp, "@") == 0)
1560				(void)strcpy(cp, zone);
1561			if (checkdots(cp)) {
1562				++errors;
1563				fprintf(stderr, dotfmt,
1564				    prog, cwd, file, n, rtype, cp);
1565			}
1566			add_domain(cp, domain);
1567			errors += updateitem(cp, 0, REC_REF, 0, 0);
1568		} else if (CHECK2(rtype, 'r', 'p')) {
1569			/* Handle "rp" record */
1570			add_domain(name, domain);
1571			errors += updateitem(name, 0, REC_RP, 0, 0);
1572			cp2 = cp;
1573
1574			/* Step over mailbox name */
1575			/* XXX could add_domain() and check further */
1576			while (!isspace(*cp) && *cp != '\0')
1577				++cp;
1578			if (*cp == '\0') {
1579				++errors;
1580				fprintf(stderr,
1581			    "%s: %s/%s:%d \"rp\" missing text name: %s\n",
1582				    prog, cwd, file, n, cp2);
1583				continue;
1584			}
1585			++cp;
1586			cp3 = cp;
1587
1588			/* Step over text name */
1589			while (!isspace(*cp) && *cp != '\0')
1590				++cp;
1591
1592			if (*cp != '\0') {
1593				++errors;
1594				fprintf(stderr,
1595			    "%s: %s/%s:%d \"rp\" garbage after text name: %s\n",
1596				    prog, cwd, file, n, cp2);
1597				continue;
1598			}
1599
1600			/* Make sure text name points somewhere (if not ".") */
1601			if (!CHECKDOT(cp3)) {
1602				add_domain(cp3, domain);
1603				errors += updateitem(cp3, 0, REC_REF, 0, 0);
1604			}
1605		} else if (rtype[0] == 'a' && strcmp(rtype, "allowdupa") == 0) {
1606			/* Handle "allow duplicate a" record */
1607			add_domain(name, domain);
1608			addr = htonl(inet_addr(cp));
1609			if ((int)addr == -1) {
1610				++errors;
1611				cp2 = cp + strlen(cp) - 1;
1612				if (cp2 >= cp && *cp2 == '\n')
1613					*cp2 = '\0';
1614				fprintf(stderr,
1615		    "%s: %s/%s:%d bad \"allowdupa\" record ip addr \"%s\"\n",
1616				    prog, cwd, file, n, cp);
1617				continue;
1618			}
1619			errors += updateitem(name, addr, 0, 0, FLG_ALLOWDUPA);
1620		} else {
1621			/* Unknown record type */
1622			++errors;
1623			fprintf(stderr,
1624			    "%s: %s/%s:%d unknown record type \"%s\"\n",
1625			    prog, cwd, file, n, rtype);
1626			add_domain(name, domain);
1627			errors += updateitem(name, 0, REC_UNKNOWN, 0, 0);
1628		}
1629		(void)strcpy(lastname, name);
1630	}
1631	(void)fclose(f);
1632	return;
1633}
1634
1635/* Records we use to detect duplicates */
1636static struct duprec {
1637	int record;
1638	char *name;
1639} duprec[] = {
1640	{ REC_A, "a" },
1641	{ REC_HINFO, "hinfo" },
1642	{ 0, NULL },
1643};
1644
1645void
1646checkdups(register struct item *ip, register int records)
1647{
1648	register struct duprec *dp;
1649
1650	records &= (ip->records & MASK_TEST_DUP);
1651	if (records == 0)
1652		return;
1653	for (dp = duprec; dp->name != NULL; ++dp)
1654		if ((records & dp->record) != 0) {
1655			++errors;
1656			fprintf(stderr, "%s: multiple \"%s\" records for %s\n",
1657			    prog, dp->name, ip->host);
1658			records &= ~dp->record;
1659		}
1660	if (records != 0)
1661		fprintf(stderr, "%s: checkdups: records not zero (%d)\n",
1662		    prog, records);
1663}
1664
1665int
1666updateitem(register const char *host, register u_int32_t addr,
1667    register int records, register u_int ttl, register int flags)
1668{
1669	register const char *ccp;
1670	register int n, errs;
1671	register u_int i;
1672	register struct item *ip;
1673	int foundsome;
1674
1675	n = 0;
1676	foundsome = 0;
1677	errs = 0;
1678	ITEMHASH(host, i, ccp);
1679	ip = &items[i & (ITEMSIZE - 1)];
1680	while (n < ITEMSIZE && ip->host) {
1681		if ((addr == 0 || addr == ip->addr || ip->addr == 0) &&
1682		    *host == *ip->host && strcmp(host, ip->host) == 0) {
1683			++foundsome;
1684			if (ip->addr == 0)
1685				ip->addr = addr;
1686			if ((records & MASK_TEST_DUP) != 0)
1687				checkdups(ip, records);
1688			ip->records |= records;
1689			/* Only check differing ttl's for A and MX records */
1690			if (ip->ttl == 0)
1691				ip->ttl = ttl;
1692			else if (ttl != 0 && ip->ttl != ttl) {
1693				fprintf(stderr,
1694				    "%s: differing ttls for %s (%u != %u)\n",
1695				    prog, ip->host, ttl, ip->ttl);
1696				++errs;
1697			}
1698			ip->flags |= flags;
1699			/* Not done if we wildcard matched the name */
1700			if (addr)
1701				return (errs);
1702		}
1703		++n;
1704		++ip;
1705		if (ip >= &items[ITEMSIZE])
1706			ip = items;
1707	}
1708
1709	if (n >= ITEMSIZE) {
1710		fprintf(stderr, "%s: out of item slots (max %d)\n",
1711		    prog, ITEMSIZE);
1712		exit(1);
1713	}
1714
1715	/* Done if we were wildcarding the name (and found entries for it) */
1716	if (addr == 0 && foundsome)
1717		return (errs);
1718
1719	/* Didn't find it, make new entry */
1720	++itemcnt;
1721	if (ip->host) {
1722		fprintf(stderr, "%s: reusing bucket!\n", prog);
1723		exit(1);
1724	}
1725	ip->addr = addr;
1726	ip->host = savestr(host);
1727	if ((records & MASK_TEST_DUP) != 0)
1728		checkdups(ip, records);
1729	ip->records |= records;
1730	if (ttl != 0)
1731		ip->ttl = ttl;
1732	ip->flags |= flags;
1733	return (errs);
1734}
1735
1736static const char *microlist[] = {
1737	"_tcp",
1738	"_udp",
1739	"_msdcs",
1740	"_sites",
1741	NULL
1742};
1743
1744int
1745rfc1034host(register const char *host, register int recs)
1746{
1747	register const char *cp, **p;
1748	register int underok;
1749
1750	underok = 0;
1751	for (p = microlist; *p != NULL ;++p)
1752		if ((cp = strstr(host, *p)) != NULL &&
1753		    cp > host &&
1754		    cp[-1] == '.' &&
1755		    cp[strlen(*p)] == '.') {
1756			++underok;
1757			break;
1758		}
1759
1760	cp = host;
1761	if (!(isalpha(*cp) || isdigit(*cp) || (*cp == '_' && underok))) {
1762		fprintf(stderr,
1763	    "%s: illegal hostname \"%s\" (starts with non-alpha/numeric)\n",
1764		    prog, host);
1765		return (1);
1766	}
1767	for (++cp; *cp != '.' && *cp != '\0'; ++cp)
1768		if (!(isalpha(*cp) || isdigit(*cp) || *cp == '-' ||
1769		    (*cp == '/' && (recs & REC_SOA) != 0))) {
1770			fprintf(stderr,
1771		    "%s: illegal hostname \"%s\" ('%c' illegal character)\n",
1772			    prog, host, *cp);
1773			return (1);
1774		}
1775	if (--cp >= host && *cp == '-') {
1776		fprintf(stderr, "%s: illegal hostname \"%s\" (ends with '-')\n",
1777		    prog, host);
1778		return (1);
1779	}
1780	return (0);
1781}
1782
1783int
1784nslint(void)
1785{
1786	register int n, records, flags;
1787	register struct item *ip, *lastaip, **ipp, **itemlist;
1788	register u_int32_t addr, lastaddr, mask;
1789
1790	itemlist = (struct item **)calloc(itemcnt, sizeof(*ipp));
1791	if (itemlist == NULL) {
1792		fprintf(stderr, "%s: nslint: calloc: %s\n",
1793		    prog, strerror(errno));
1794		exit(1);
1795	}
1796	ipp = itemlist;
1797	for (n = 0, ip = items; n < ITEMSIZE; ++n, ++ip) {
1798		if (ip->host == NULL)
1799			continue;
1800
1801		/* Save entries with addresses for later check */
1802		if (ip->addr != 0)
1803			*ipp++ = ip;
1804
1805		if (debug > 1) {
1806			if (debug > 2)
1807				printf("%d\t", n);
1808			printf("%s\t%s\t0x%x\t0x%x\n",
1809			    ip->host, intoa(ip->addr), ip->records, ip->flags);
1810		}
1811
1812		/* Check for illegal hostnames (rfc1034) */
1813		if (rfc1034host(ip->host, ip->records))
1814			++errors;
1815
1816		/* Check for missing ptr records (ok if also an ns record) */
1817		records = ip->records & MASK_CHECK_REC;
1818		if ((ip->records & MASK_TEST_REC) != 0)
1819			records |= REC_OTHER;
1820		switch (records) {
1821
1822		case REC_A | REC_OTHER | REC_PTR | REC_REF:
1823		case REC_A | REC_OTHER | REC_PTR:
1824		case REC_A | REC_PTR | REC_REF:
1825		case REC_A | REC_PTR:
1826		case REC_CNAME:
1827			/* These are O.K. */
1828			break;
1829
1830		case REC_CNAME | REC_REF:
1831			++errors;
1832			fprintf(stderr, "%s: \"cname\" referenced by other"
1833			    " \"cname\" or \"mx\": %s\n", prog, ip->host);
1834			break;
1835
1836		case REC_OTHER | REC_REF:
1837		case REC_OTHER:
1838			/*
1839			 * This is only an error if there is an address
1840			 * associated with the hostname; this means
1841			 * there was a wks entry with bogus address.
1842			 * Otherwise, we have an mx or hinfo.
1843			 */
1844			if (ip->addr != 0) {
1845				++errors;
1846				fprintf(stderr,
1847			    "%s: \"wks\" without \"a\" and \"ptr\": %s -> %s\n",
1848				    prog, ip->host, intoa(ip->addr));
1849			}
1850			break;
1851
1852		case REC_REF:
1853			++errors;
1854			fprintf(stderr,
1855			    "%s: name referenced without other records: %s\n",
1856			    prog, ip->host);
1857			break;
1858
1859		case REC_A | REC_OTHER | REC_REF:
1860		case REC_A | REC_OTHER:
1861		case REC_A | REC_REF:
1862		case REC_A:
1863			++errors;
1864			fprintf(stderr, "%s: missing \"ptr\": %s -> %s\n",
1865			    prog, ip->host, intoa(ip->addr));
1866			break;
1867
1868		case REC_OTHER | REC_PTR | REC_REF:
1869		case REC_OTHER | REC_PTR:
1870		case REC_PTR | REC_REF:
1871		case REC_PTR:
1872			++errors;
1873			fprintf(stderr, "%s: missing \"a\": %s -> %s\n",
1874			    prog, ip->host, intoa(ip->addr));
1875			break;
1876
1877		case REC_A | REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
1878		case REC_A | REC_CNAME | REC_OTHER | REC_PTR:
1879		case REC_A | REC_CNAME | REC_OTHER | REC_REF:
1880		case REC_A | REC_CNAME | REC_OTHER:
1881		case REC_A | REC_CNAME | REC_PTR | REC_REF:
1882		case REC_A | REC_CNAME | REC_PTR:
1883		case REC_A | REC_CNAME | REC_REF:
1884		case REC_A | REC_CNAME:
1885		case REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
1886		case REC_CNAME | REC_OTHER | REC_PTR:
1887		case REC_CNAME | REC_OTHER | REC_REF:
1888		case REC_CNAME | REC_OTHER:
1889		case REC_CNAME | REC_PTR | REC_REF:
1890		case REC_CNAME | REC_PTR:
1891			++errors;
1892			fprintf(stderr, "%s: \"cname\" %s has other records\n",
1893			    prog, ip->host);
1894			break;
1895
1896		case 0:
1897			/* Second level test */
1898			if ((ip->records & ~(REC_NS | REC_TXT)) == 0)
1899				break;
1900			/* Fall through... */
1901
1902		default:
1903			++errors;
1904			fprintf(stderr,
1905			    "%s: records == 0x%x: can't happen (%s 0x%x)\n",
1906			    prog, records, ip->host, ip->records);
1907			break;
1908		}
1909
1910		/* Check for smtp problems */
1911		flags = ip->flags & MASK_TEST_SMTP;
1912
1913		if ((flags & FLG_SELFMX) != 0 && (ip->records & REC_A) == 0) {
1914			++errors;
1915			fprintf(stderr,
1916			    "%s: self \"mx\" for %s missing \"a\" record\n",
1917			    prog, ip->host);
1918		}
1919
1920		switch (flags) {
1921
1922		case 0:
1923		case FLG_SELFMX | FLG_SMTPWKS:
1924			/* These are O.K. */
1925			break;
1926
1927		case FLG_SELFMX:
1928			if ((ip->records & REC_WKS) != 0) {
1929				++errors;
1930				fprintf(stderr,
1931				    "%s: smtp/tcp missing from \"wks\": %s\n",
1932				    prog, ip->host);
1933			}
1934			break;
1935
1936		case FLG_SMTPWKS:
1937			++errors;
1938			fprintf(stderr,
1939			    "%s: saw smtp/tcp without self \"mx\": %s\n",
1940			    prog, ip->host);
1941			break;
1942
1943		default:
1944			++errors;
1945			fprintf(stderr,
1946			    "%s: flags == 0x%x: can't happen (%s)\n",
1947			    prog, flags, ip->host);
1948		}
1949
1950		/* Check for chained MX records */
1951		if ((ip->flags & (FLG_SELFMX | FLG_MXREF)) == FLG_MXREF &&
1952		    (ip->records & REC_MX) != 0) {
1953			++errors;
1954			fprintf(stderr, "%s: \"mx\" referenced by other"
1955			    " \"mx\" record: %s\n", prog, ip->host);
1956		}
1957	}
1958
1959	/* Check for doubly booked addresses */
1960	n = ipp - itemlist;
1961	qsort(itemlist, n, sizeof(itemlist[0]), cmpaddr);
1962	lastaddr = 0;
1963	ip = NULL;
1964	for (ipp = itemlist; n > 0; ++ipp, --n) {
1965		addr = (*ipp)->addr;
1966		if (lastaddr == addr &&
1967		    ((*ipp)->flags & FLG_ALLOWDUPA) == 0 &&
1968		    (ip->flags & FLG_ALLOWDUPA) == 0) {
1969			++errors;
1970			fprintf(stderr, "%s: %s in use by %s and %s\n",
1971			    prog, intoa(addr), (*ipp)->host, ip->host);
1972		}
1973		lastaddr = addr;
1974		ip = *ipp;
1975	}
1976
1977	/* Check for hosts with multiple addresses on the same subnet */
1978	n = ipp - itemlist;
1979	qsort(itemlist, n, sizeof(itemlist[0]), cmphost);
1980	if (netlistcnt > 0) {
1981		n = ipp - itemlist;
1982		lastaip = NULL;
1983		for (ipp = itemlist; n > 0; ++ipp, --n) {
1984			ip = *ipp;
1985			if ((ip->records & REC_A) == 0 ||
1986			    (ip->flags & FLG_ALLOWDUPA) != 0)
1987				continue;
1988			if (lastaip != NULL &&
1989			    strcasecmp(ip->host, lastaip->host) == 0) {
1990				mask = findmask(ip->addr);
1991				if (mask == 0) {
1992					++errors;
1993					fprintf(stderr,
1994					    "%s: can't find mask for %s (%s)\n",
1995					    prog, ip->host, intoa(ip->addr));
1996				} else if ((lastaip->addr & mask) ==
1997				    (ip->addr & mask) ) {
1998					++errors;
1999					fprintf(stderr,
2000			    "%s: multiple \"a\" records for %s on subnet %s",
2001					    prog, ip->host,
2002					    intoa(ip->addr & mask));
2003					fprintf(stderr, "\n\t(%s",
2004					    intoa(lastaip->addr));
2005					fprintf(stderr, " and %s)\n",
2006					    intoa(ip->addr));
2007				}
2008			}
2009			lastaip = ip;
2010		}
2011	}
2012
2013	if (debug)
2014		printf("%s: %d/%d items used, %d error%s\n", prog, itemcnt,
2015		    ITEMSIZE, errors, errors == 1 ? "" : "s");
2016	return (errors != 0);
2017}
2018
2019/* Similar to inet_ntoa() */
2020char *
2021intoa(u_int32_t addr)
2022{
2023	register char *cp;
2024	register u_int byte;
2025	register int n;
2026	static char buf[sizeof(".xxx.xxx.xxx.xxx")];
2027
2028	cp = &buf[sizeof buf];
2029	*--cp = '\0';
2030
2031	n = 4;
2032	do {
2033		byte = addr & 0xff;
2034		*--cp = byte % 10 + '0';
2035		byte /= 10;
2036		if (byte > 0) {
2037			*--cp = byte % 10 + '0';
2038			byte /= 10;
2039			if (byte > 0)
2040				*--cp = byte + '0';
2041		}
2042		*--cp = '.';
2043		addr >>= 8;
2044	} while (--n > 0);
2045
2046	return cp + 1;
2047}
2048
2049int
2050parseinaddr(register const char *cp, register u_int32_t *netp,
2051    register u_int32_t *maskp)
2052{
2053	register int i, bits;
2054	register u_int32_t o, net, mask;
2055
2056	if (!isdigit(*cp))
2057		return (0);
2058	net = 0;
2059	mask = 0xff000000;
2060	bits = 0;
2061	o = 0;
2062	do {
2063		o = o * 10 + (*cp++ - '0');
2064	} while (isdigit(*cp));
2065	net = o << 24;
2066
2067	/* Check for classless delegation mask width */
2068	if (*cp == '/') {
2069		++cp;
2070		o = 0;
2071		do {
2072			o = o * 10 + (*cp++ - '0');
2073		} while (isdigit(*cp));
2074		bits = o;
2075		if (bits <= 0 || bits > 32)
2076			return (0);
2077	}
2078
2079	if (*cp == '.' && isdigit(cp[1])) {
2080		++cp;
2081		o = 0;
2082		do {
2083			o = o * 10 + (*cp++ - '0');
2084		} while (isdigit(*cp));
2085		net = (net >> 8) | (o << 24);
2086		mask = 0xffff0000;
2087		if (*cp == '.' && isdigit(cp[1])) {
2088			++cp;
2089			o = 0;
2090			do {
2091				o = o * 10 + (*cp++ - '0');
2092			} while (isdigit(*cp));
2093			net = (net >> 8) | (o << 24);
2094			mask = 0xffffff00;
2095			if (*cp == '.' && isdigit(cp[1])) {
2096				++cp;
2097				o = 0;
2098				do {
2099					o = o * 10 + (*cp++ - '0');
2100				} while (isdigit(*cp));
2101				net = (net >> 8) | (o << 24);
2102				mask = 0xffffffff;
2103			}
2104		}
2105	}
2106	if (strcasecmp(cp, inaddr) != 0)
2107		return (0);
2108
2109	/* Classless delegation */
2110	/* XXX check that calculated mask isn't smaller than octet mask? */
2111	if (bits != 0)
2112		for (mask = 0, i = 31; bits > 0; --i, --bits)
2113			mask |= (1 << i);
2114
2115	*netp = net;
2116	*maskp = mask;
2117	return (1);
2118}
2119
2120u_int32_t
2121parseptr(register const char *cp, u_int32_t net, u_int32_t mask,
2122    register char **errstrp)
2123{
2124	register u_int32_t o, addr;
2125	register int shift;
2126
2127	addr = 0;
2128	shift = 0;
2129	while (isdigit(*cp) && shift < 32) {
2130		o = 0;
2131		do {
2132			o = o * 10 + (*cp++ - '0');
2133		} while (isdigit(*cp));
2134		addr |= o << shift;
2135		shift += 8;
2136		if (*cp != '.') {
2137			if (*cp == '\0')
2138				break;
2139			*errstrp = "missing dot";
2140			return (0);
2141		}
2142		++cp;
2143	}
2144
2145	if (shift > 32) {
2146		*errstrp = "more than 4 octets";
2147		return (0);
2148	}
2149
2150	if (shift == 32 && strcasecmp(cp, inaddr + 1) == 0)
2151		return (addr);
2152
2153#ifdef notdef
2154	if (*cp != '\0') {
2155		*errstrp = "trailing junk";
2156		return (0);
2157	}
2158#endif
2159#ifdef notdef
2160	if ((~mask & net) != 0) {
2161		*errstrp = "too many octets for net";
2162		return (0);
2163	}
2164#endif
2165	return (net | addr);
2166}
2167
2168int
2169checkwks(register FILE *f, register char *proto, register int *smtpp,
2170    register char **errstrp)
2171{
2172	register int n, sawparen;
2173	register char *cp, *serv, **p;
2174	static char errstr[132];
2175	char buf[1024];
2176	char psbuf[512];
2177
2178	if (!protoserv_init) {
2179		initprotoserv();
2180		++protoserv_init;
2181	}
2182
2183	/* Line count */
2184	n = 0;
2185
2186	/* Terminate protocol */
2187	cp = proto;
2188	while (!isspace(*cp) && *cp != '\0')
2189		++cp;
2190	if (*cp != '\0')
2191		*cp++ = '\0';
2192
2193	/* Find services */
2194	*smtpp = 0;
2195	sawparen = 0;
2196	if (*cp == '(') {
2197		++sawparen;
2198		++cp;
2199		while (isspace(*cp))
2200			++cp;
2201	}
2202	for (;;) {
2203		if (*cp == '\0') {
2204			if (!sawparen)
2205				break;
2206			if (fgets(buf, sizeof(buf), f) == NULL) {
2207				*errstrp = "mismatched parens";
2208				return (n);
2209			}
2210			++n;
2211			cp = buf;
2212			while (isspace(*cp))
2213				++cp;
2214		}
2215		/* Find end of service, converting to lowercase */
2216		for (serv = cp; !isspace(*cp) && *cp != '\0'; ++cp)
2217			if (isupper(*cp))
2218				*cp = tolower(*cp);
2219		if (*cp != '\0')
2220			*cp++ = '\0';
2221		if (sawparen && *cp == ')') {
2222			/* XXX should check for trailing junk */
2223			break;
2224		}
2225
2226		(void)sprintf(psbuf, "%s/%s", serv, proto);
2227
2228		if (*serv == 's' && strcmp(psbuf, "tcp/smtp") == 0)
2229			++*smtpp;
2230
2231		for (p = protoserv; *p != NULL; ++p)
2232			if (*psbuf == **p && strcmp(psbuf, *p) == 0) {
2233				break;
2234			}
2235		if (*p == NULL) {
2236			sprintf(errstr, "%s unknown", psbuf);
2237			*errstrp = errstr;
2238			break;
2239		}
2240	}
2241
2242	return (n);
2243}
2244
2245int
2246checkserv(register const char *serv, register char **p)
2247{
2248	for (; *p != NULL; ++p)
2249		if (*serv == **p && strcmp(serv, *p) == 0)
2250			return (1);
2251	return (0);
2252}
2253
2254void
2255initprotoserv(void)
2256{
2257	register char *cp;
2258	register struct servent *sp;
2259	char psbuf[512];
2260
2261	protoserv_len = 256;
2262	protoserv = (char **)malloc(protoserv_len * sizeof(*protoserv));
2263	if (protoserv == NULL) {
2264		fprintf(stderr, "%s: nslint: malloc: %s\n",
2265		    prog, strerror(errno));
2266		exit(1);
2267	}
2268
2269	while ((sp = getservent()) != NULL) {
2270		(void)sprintf(psbuf, "%s/%s", sp->s_name, sp->s_proto);
2271
2272		/* Convert to lowercase */
2273		for (cp = psbuf; *cp != '\0'; ++cp)
2274			if (isupper(*cp))
2275				*cp = tolower(*cp);
2276
2277		if (protoserv_last + 1 >= protoserv_len) {
2278			protoserv_len <<= 1;
2279			protoserv = realloc(protoserv,
2280			    protoserv_len * sizeof(*protoserv));
2281			if (protoserv == NULL) {
2282				fprintf(stderr, "%s: nslint: realloc: %s\n",
2283				    prog, strerror(errno));
2284				exit(1);
2285			}
2286		}
2287		protoserv[protoserv_last] = savestr(psbuf);
2288		++protoserv_last;
2289	}
2290	protoserv[protoserv_last] = NULL;
2291}
2292
2293/*
2294 * Returns true if name contains a dot but not a trailing dot.
2295 * Special case: allow a single dot if the second part is not one
2296 * of the 3 or 4 letter top level domains or is any 2 letter TLD
2297 */
2298int
2299checkdots(register const char *name)
2300{
2301	register const char *cp, *cp2;
2302
2303	if ((cp = strchr(name, '.')) == NULL)
2304		return (0);
2305	cp2 = name + strlen(name) - 1;
2306	if (cp2 >= name && *cp2 == '.')
2307		return (0);
2308
2309	/* Return true of more than one dot*/
2310	++cp;
2311	if (strchr(cp, '.') != NULL)
2312		return (1);
2313
2314	if (strlen(cp) == 2 ||
2315	    strcasecmp(cp, "gov") == 0 ||
2316	    strcasecmp(cp, "edu") == 0 ||
2317	    strcasecmp(cp, "com") == 0 ||
2318	    strcasecmp(cp, "net") == 0 ||
2319	    strcasecmp(cp, "org") == 0 ||
2320	    strcasecmp(cp, "mil") == 0 ||
2321	    strcasecmp(cp, "int") == 0 ||
2322	    strcasecmp(cp, "nato") == 0 ||
2323	    strcasecmp(cp, "arpa") == 0)
2324		return (1);
2325	return (0);
2326}
2327
2328int
2329cmpaddr(register const void *ip1, register const void *ip2)
2330{
2331	register u_int32_t a1, a2;
2332
2333	a1 = (*(struct item **)ip1)->addr;
2334	a2 = (*(struct item **)ip2)->addr;
2335
2336	if (a1 < a2)
2337		return (-1);
2338	else if (a1 > a2)
2339		return (1);
2340	else
2341		return (0);
2342}
2343
2344int
2345cmphost(register const void *ip1, register const void *ip2)
2346{
2347	register const char *s1, *s2;
2348
2349	s1 = (*(struct item **)ip1)->host;
2350	s2 = (*(struct item **)ip2)->host;
2351
2352	return (strcasecmp(s1, s2));
2353}
2354
2355/* Returns a pointer after the next token or quoted string, else NULL */
2356char *
2357parsequoted(register char *cp)
2358{
2359
2360	if (*cp == '"') {
2361		++cp;
2362		while (*cp != '"' && *cp != '\0')
2363			++cp;
2364		if (*cp != '"')
2365			return (NULL);
2366		++cp;
2367	} else {
2368		while (!isspace(*cp) && *cp != '\0')
2369			++cp;
2370	}
2371	return (cp);
2372}
2373
2374__dead void
2375usage(void)
2376{
2377	extern char version[];
2378
2379	fprintf(stderr, "Version %s\n", version);
2380	fprintf(stderr, "usage: %s [-d] [-b named.boot] [-B nslint.boot]\n",
2381	    prog);
2382	fprintf(stderr, "       %s [-d] [-c named.conf] [-C nslint.conf]\n",
2383	    prog);
2384	exit(1);
2385}
2386