David van Moolenbroek 00b67f09dd Import NetBSD named(8)
Also known as ISC bind.  This import adds utilities such as host(1),
dig(1), and nslookup(1), as well as many other tools and libraries.

Change-Id: I035ca46e64f1965d57019e773f4ff0ef035e4aa3
2017-03-21 22:00:06 +00:00

2974 lines
63 KiB
C

/* $NetBSD: nslint.c,v 1.1.1.3 2014/12/10 03:34:34 christos Exp $ */
/*
* Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef lint
static const char copyright[] =
"@(#) Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009\n\
The Regents of the University of California. All rights reserved.\n";
static const char rcsid[] =
"@(#) Id: nslint.c 247 2009-10-14 17:54:05Z leres (LBL)";
#endif
/*
* nslint - perform consistency checks on dns files
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "savestr.h"
#include "version.h"
#include "gnuc.h"
#ifdef HAVE_OS_PROTO_H
#include "os-proto.h"
#endif
#define NSLINTBOOT "nslint.boot" /* default nslint.boot file */
#define NSLINTCONF "nslint.conf" /* default nslint.conf file */
/* Is the string just a dot by itself? */
#define CHECKDOT(p) (p[0] == '.' && p[1] == '\0')
/* Address (network order) */
struct addr {
u_int family;
union {
struct in_addr _a_addr4;
struct in6_addr _a_addr6;
} addr;
};
#define a_addr4 addr._a_addr4.s_addr
#define a_addr6 addr._a_addr6.s6_addr
/* Network */
struct network {
u_int family;
union {
struct in_addr _n_addr4;
struct in6_addr _n_addr6;
} addr;
union {
struct in_addr _n_mask4;
struct in6_addr _n_mask6;
} mask;
};
#define n_addr4 addr._n_addr4.s_addr
#define n_mask4 mask._n_mask4.s_addr
#define n_addr6 addr._n_addr6.s6_addr
#define n_mask6 mask._n_mask6.s6_addr
/* Item struct */
struct item {
char *host; /* pointer to hostname */
struct addr addr; /* ip address */
u_int ttl; /* ttl of A records */
int records; /* resource records seen */
int flags; /* flags word */
};
/* Ignored zone struct */
struct ignoredzone {
char *zone; /* zone name */
int len; /* length of zone */
};
/* Resource records seen */
#define REC_A 0x0001
#define REC_AAAA 0x0002
#define REC_PTR 0x0004
#define REC_WKS 0x0008
#define REC_HINFO 0x0010
#define REC_MX 0x0020
#define REC_CNAME 0x0040
#define REC_NS 0x0080
#define REC_SOA 0x0100
#define REC_RP 0x0200
#define REC_TXT 0x0400
#define REC_SRV 0x0800
/* These aren't real records */
#define REC_OTHER 0x1000
#define REC_REF 0x2000
#define REC_UNKNOWN 0x4000
/* resource record types for parsing */
enum rrtype {
RR_UNDEF = 0,
RR_A,
RR_AAAA,
RR_ALLOWDUPA,
RR_CNAME,
RR_DNSKEY,
RR_HINFO,
RR_MX,
RR_NS,
RR_PTR,
RR_RP,
RR_SOA,
RR_SRV,
RR_TXT,
RR_WKS,
RR_RRSIG,
RR_NSEC,
};
/* Test for records we want to map to REC_OTHER */
#define MASK_TEST_REC (REC_WKS | REC_HINFO | \
REC_MX | REC_SOA | REC_RP | REC_TXT | REC_SRV | REC_UNKNOWN)
/* Mask away records we don't care about in the final processing to REC_OTHER */
#define MASK_CHECK_REC \
(REC_A | REC_AAAA | REC_PTR | REC_CNAME | REC_REF | REC_OTHER)
/* Test for records we want to check for duplicate name detection */
#define MASK_TEST_DUP \
(REC_A | REC_AAAA | REC_HINFO | REC_CNAME)
/* Flags */
#define FLG_SELFMX 0x001 /* mx record refers to self */
#define FLG_MXREF 0x002 /* this record referred to by a mx record */
#define FLG_SMTPWKS 0x004 /* saw wks with smtp/tcp */
#define FLG_ALLOWDUPA 0x008 /* allow duplicate a records */
/* doconf() and doboot() flags */
#define CONF_MUSTEXIST 0x001 /* fatal for files to not exist */
#define CONF_NOZONE 0x002 /* do not parse zone files */
/* Test for smtp problems */
#define MASK_TEST_SMTP \
(FLG_SELFMX | FLG_SMTPWKS)
#define ITEMSIZE (1 << 17) /* power of two */
struct item items[ITEMSIZE];
int itemcnt; /* count of items */
/* Hostname string storage */
#define STRSIZE 8192; /* size to malloc when more space is needed */
char *strptr; /* pointer to string pool */
int strsize; /* size of space left in pool */
int debug;
int errors;
#ifdef __FreeBSD__
char *bootfile = "/etc/namedb/named.boot";
char *conffile = "/etc/namedb/named.conf";
#else
char *bootfile = "/etc/named.boot";
char *conffile = "/etc/named.conf";
#endif
char *nslintboot;
char *nslintconf;
char *prog;
char *cwd = ".";
static struct network *netlist;
static u_int netlistsize; /* size of array */
static u_int netlistcnt; /* next free element */
char **protoserv; /* valid protocol/service names */
int protoserv_init;
int protoserv_last;
int protoserv_len;
static char inaddr[] = ".in-addr.arpa.";
static char inaddr6[] = ".ip6.arpa.";
/* XXX should be dynamic */
static struct ignoredzone ignoredzones[10];
static int numignoredzones = 0;
#define SIZEIGNOREDZONES (sizeof(ignoredzones) / sizeof(ignoredzones[0]))
/* SOA record */
#define SOA_SERIAL 0
#define SOA_REFRESH 1
#define SOA_RETRY 2
#define SOA_EXPIRE 3
#define SOA_MINIMUM 4
static u_int soaval[5];
static int nsoaval;
#define NSOAVAL (sizeof(soaval) / sizeof(soaval[0]))
/* Forwards */
void add_domain(char *, const char *);
const char *addr2str(struct addr *);
int checkaddr(const char *);
int checkdots(const char *);
void checkdups(struct item *, int);
int checkignoredzone(const char *);
int checkserv(const char *, char **p);
int checkwks(FILE *, char *, int *, char **);
int cmpaddr(const void *, const void *);
int cmpitemaddr(const void *, const void *);
int cmpitemhost(const void *, const void *);
int cmpnetwork(const void *, const void *);
void doboot(const char *, int);
void doconf(const char *, int);
const char *extractaddr(const char *, struct addr *);
const char *extractnetwork(const char *, struct network *);
struct network *findnetwork(struct addr *);
void initprotoserv(void);
int main(int, char **);
int maskwidth(struct network *);
const char *network2str(struct network *);
void nslint(void);
const char *parsenetwork(const char *);
const char *parseptr(const char *, struct addr *);
char *parsequoted(char *);
int parserrsig(const char *, char **);
int parsesoa(const char *, char **);
void process(const char *, const char *, const char *);
int rfc1034host(const char *, int);
enum rrtype txt2rrtype(const char *);
int samesubnet(struct addr *, struct addr *, struct network *);
void setmaskwidth(u_int w, struct network *);
int updateitem(const char *, struct addr *, int, u_int, int);
void usage(void) __attribute__((noreturn));
extern char *optarg;
extern int optind, opterr;
int
main(int argc, char **argv)
{
char *cp;
int op, donamedboot, donamedconf;
if ((cp = strrchr(argv[0], '/')) != NULL)
prog = cp + 1;
else
prog = argv[0];
donamedboot = 0;
donamedconf = 0;
while ((op = getopt(argc, argv, "b:c:B:C:d")) != -1)
switch (op) {
case 'b':
bootfile = optarg;
++donamedboot;
break;
case 'c':
conffile = optarg;
++donamedconf;
break;
case 'B':
nslintboot = optarg;
++donamedboot;
break;
case 'C':
nslintconf = optarg;
++donamedconf;
break;
case 'd':
++debug;
break;
default:
usage();
}
if (optind != argc || (donamedboot && donamedconf))
usage();
/* Find config file if not manually specified */
if (!donamedboot && !donamedconf) {
if (access(conffile, R_OK) >= 0)
++donamedconf;
if (access(bootfile, R_OK) >= 0)
++donamedboot;
if (donamedboot && donamedconf) {
fprintf(stderr,
"%s: nslint: both %s and %s exist; use -b or -c\n",
prog, conffile, bootfile);
exit(1);
}
}
if (donamedboot) {
doboot(bootfile, CONF_MUSTEXIST | CONF_NOZONE);
if (nslintboot != NULL)
doboot(nslintboot, CONF_MUSTEXIST);
else
doboot(NSLINTBOOT, 0);
doboot(bootfile, CONF_MUSTEXIST);
} else {
doconf(conffile, CONF_MUSTEXIST | CONF_NOZONE);
if (nslintconf != NULL)
doconf(nslintconf, CONF_MUSTEXIST);
else
doconf(NSLINTCONF, 0);
doconf(conffile, CONF_MUSTEXIST);
}
/* Sort network list */
if (netlistcnt > 0)
qsort(netlist, netlistcnt, sizeof(netlist[0]), cmpnetwork);
nslint();
exit (errors != 0);
}
/* add domain if necessary */
void
add_domain(char *name, const char *domain)
{
char *cp;
/* Kill trailing white space and convert to lowercase */
for (cp = name; *cp != '\0' && !isspace(*cp); ++cp)
if (isupper(*cp))
*cp = tolower(*cp);
*cp-- = '\0';
/* If necessary, append domain */
if (cp >= name && *cp++ != '.') {
if (*domain != '.')
*cp++ = '.';
(void)strcpy(cp, domain);
}
/* XXX should we insure a trailing dot? */
}
const char *
addr2str(struct addr *ap)
{
struct network net;
memset(&net, 0, sizeof(net));
net.family = ap->family;
switch (ap->family) {
case AF_INET:
net.n_addr4 = ap->a_addr4;
setmaskwidth(32, &net);
break;
case AF_INET6:
memmove(net.n_addr6, &ap->a_addr6, sizeof(ap->a_addr6));
setmaskwidth(128, &net);
break;
default:
return ("<nil>");
}
return (network2str(&net));
}
/*
* Returns true if name is really an ip address.
*/
int
checkaddr(const char *name)
{
struct in_addr addr;
return (inet_pton(AF_INET, name, (char *)&addr));
}
/*
* Returns true if name contains a dot but not a trailing dot.
* Special case: allow a single dot if the second part is not one
* of the 3 or 4 letter top level domains or is any 2 letter TLD
*/
int
checkdots(const char *name)
{
const char *cp, *cp2;
if ((cp = strchr(name, '.')) == NULL)
return (0);
cp2 = name + strlen(name) - 1;
if (cp2 >= name && *cp2 == '.')
return (0);
/* Return true of more than one dot*/
++cp;
if (strchr(cp, '.') != NULL)
return (1);
if (strlen(cp) == 2 ||
strcasecmp(cp, "gov") == 0 ||
strcasecmp(cp, "edu") == 0 ||
strcasecmp(cp, "com") == 0 ||
strcasecmp(cp, "net") == 0 ||
strcasecmp(cp, "org") == 0 ||
strcasecmp(cp, "mil") == 0 ||
strcasecmp(cp, "int") == 0 ||
strcasecmp(cp, "nato") == 0 ||
strcasecmp(cp, "arpa") == 0)
return (1);
return (0);
}
/* Records we use to detect duplicates */
static struct duprec {
int record;
char *name;
} duprec[] = {
{ REC_A, "a" },
{ REC_AAAA, "aaaa" },
{ REC_HINFO, "hinfo" },
{ REC_CNAME, "cname" },
{ 0, NULL },
};
void
checkdups(struct item *ip, int records)
{
struct duprec *dp;
records &= (ip->records & MASK_TEST_DUP);
if (records == 0)
return;
for (dp = duprec; dp->name != NULL; ++dp)
if ((records & dp->record) != 0) {
++errors;
fprintf(stderr, "%s: multiple \"%s\" records for %s\n",
prog, dp->name, ip->host);
records &= ~dp->record;
}
if (records != 0)
fprintf(stderr, "%s: checkdups: records not zero %s (0x%x)\n",
prog, ip->host, records);
}
/* Check for an "ignored zone" (usually dynamic dns) */
int
checkignoredzone(const char *name)
{
int i, len, len2;
len = strlen(name);
if (len > 1 && name[len - 1] == '.')
--len;
for (i = 0; i < numignoredzones; ++i) {
len2 = len - ignoredzones[i].len;
if (len2 >= 0 &&
strncasecmp(name + len2,
ignoredzones[i].zone, len - len2) == 0)
return (1);
}
return (0);
}
int
checkserv(const char *serv, char **p)
{
for (; *p != NULL; ++p)
if (*serv == **p && strcmp(serv, *p) == 0)
return (1);
return (0);
}
int
checkwks(FILE *f, char *proto, int *smtpp, char **errstrp)
{
int n, sawparen;
char *cp, *serv, **p;
static char errstr[132];
char buf[1024];
char psbuf[512];
if (!protoserv_init) {
initprotoserv();
++protoserv_init;
}
/* Line count */
n = 0;
/* Terminate protocol */
cp = proto;
while (!isspace(*cp) && *cp != '\0')
++cp;
if (*cp != '\0')
*cp++ = '\0';
/* Find services */
*smtpp = 0;
sawparen = 0;
if (*cp == '(') {
++sawparen;
++cp;
while (isspace(*cp))
++cp;
}
for (;;) {
if (*cp == '\0') {
if (!sawparen)
break;
if (fgets(buf, sizeof(buf), f) == NULL) {
*errstrp = "mismatched parens";
return (n);
}
++n;
cp = buf;
while (isspace(*cp))
++cp;
}
/* Find end of service, converting to lowercase */
for (serv = cp; !isspace(*cp) && *cp != '\0'; ++cp)
if (isupper(*cp))
*cp = tolower(*cp);
if (*cp != '\0')
*cp++ = '\0';
if (sawparen && *cp == ')') {
/* XXX should check for trailing junk */
break;
}
(void)sprintf(psbuf, "%s/%s", serv, proto);
if (*serv == 's' && strcmp(psbuf, "tcp/smtp") == 0)
++*smtpp;
for (p = protoserv; *p != NULL; ++p)
if (*psbuf == **p && strcmp(psbuf, *p) == 0) {
break;
}
if (*p == NULL) {
sprintf(errstr, "%s unknown", psbuf);
*errstrp = errstr;
break;
}
}
return (n);
}
int
cmpaddr(const void *arg1, const void *arg2)
{
int i, r1;
const struct network *n1, *n2;
n1 = (const struct network *)arg1;
n2 = (const struct network *)arg2;
/* IPv4 before IPv6 */
if (n1->family != n2->family)
return ((n1->family == AF_INET) ? -1 : 1);
switch (n1->family) {
case AF_INET:
/* Address */
if (ntohl(n1->n_addr4) < ntohl(n2->n_addr4))
return (-1);
else if (ntohl(n1->n_addr4) > ntohl(n2->n_addr4))
return (1);
return (0);
case AF_INET6:
/* Address */
r1 = 0;
for (i = 0; i < 16; ++i) {
if (ntohl(n1->n_addr6[i]) < ntohl(n2->n_addr6[i]))
return (-1);
if (ntohl(n1->n_addr6[i]) > ntohl(n2->n_addr6[i]))
return (1);
}
return (0);
default:
abort();
}
}
int
cmpitemaddr(const void *arg1, const void *arg2)
{
struct item *i1, *i2;
i1 = (struct item *)arg1;
i2 = (struct item *)arg2;
return (cmpaddr(&i1->addr, &i2->addr));
}
int
cmpitemhost(const void *arg1, const void *arg2)
{
struct item *i1, *i2;
i1 = (struct item *)arg1;
i2 = (struct item *)arg2;
return (strcasecmp(i1->host, i1->host));
}
/* Sort by network number (use mask when networks are the same) */
int
cmpnetwork(const void *arg1, const void *arg2)
{
int i, r1, r2;
const struct network *n1, *n2;
n1 = (const struct network *)arg1;
n2 = (const struct network *)arg2;
/* IPv4 before IPv6 */
if (n1->family != n2->family)
return ((n1->family == AF_INET) ? -1 : 1);
switch (n1->family) {
case AF_INET:
/* Address */
if (ntohl(n1->n_addr4) < ntohl(n2->n_addr4))
return (-1);
else if (ntohl(n1->n_addr4) > ntohl(n2->n_addr4))
return (1);
/* Mask */
if (ntohl(n1->n_mask4) < ntohl(n2->n_mask4))
return (1);
else if (ntohl(n1->n_mask4) > ntohl(n2->n_mask4))
return (-1);
return (0);
case AF_INET6:
/* Address */
r1 = 0;
for (i = 0; i < 16; ++i) {
if (ntohl(n1->n_addr6[i]) < ntohl(n2->n_addr6[i]))
return (-1);
if (ntohl(n1->n_addr6[i]) > ntohl(n2->n_addr6[i]))
return (1);
}
/* Mask */
r2 = 0;
for (i = 0; i < 16; ++i) {
if (n1->n_mask6[i] < n2->n_mask6[i])
return (1);
if (n1->n_mask6[i] > n2->n_mask6[i])
return (-1);
}
return (0);
break;
default:
abort();
}
abort();
}
void
doboot(const char *file, int flags)
{
int n;
char *cp, *cp2;
FILE *f;
const char *errstr;
char buf[1024], name[128];
errno = 0;
f = fopen(file, "r");
if (f == NULL) {
/* Not an error if it doesn't exist */
if ((flags & CONF_MUSTEXIST) == 0 && errno == ENOENT) {
if (debug > 1)
printf(
"%s: doit: %s doesn't exist (ignoring)\n",
prog, file);
return;
}
fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
exit(1);
}
if (debug > 1)
printf("%s: doit: opened %s\n", prog, file);
n = 0;
while (fgets(buf, sizeof(buf), f) != NULL) {
++n;
/* Skip comments */
if (buf[0] == ';')
continue;
cp = strchr(buf, ';');
if (cp)
*cp = '\0';
cp = buf + strlen(buf) - 1;
if (cp >= buf && *cp == '\n')
*cp = '\0';
cp = buf;
/* Eat leading whitespace */
while (isspace(*cp))
++cp;
/* Skip blank lines */
if (*cp == '\n' || *cp == '\0')
continue;
/* Get name */
cp2 = cp;
while (!isspace(*cp) && *cp != '\0')
++cp;
*cp++ = '\0';
/* Find next keyword */
while (isspace(*cp))
++cp;
if (strcasecmp(cp2, "directory") == 0) {
/* Terminate directory */
cp2 = cp;
while (!isspace(*cp) && *cp != '\0')
++cp;
*cp = '\0';
if (chdir(cp2) < 0) {
++errors;
fprintf(stderr, "%s: can't chdir %s: %s\n",
prog, cp2, strerror(errno));
exit(1);
}
cwd = savestr(cp2);
continue;
}
if (strcasecmp(cp2, "primary") == 0) {
/* Extract domain, converting to lowercase */
for (cp2 = name; !isspace(*cp) && *cp != '\0'; ++cp)
if (isupper(*cp))
*cp2++ = tolower(*cp);
else
*cp2++ = *cp;
/* Insure trailing dot */
if (cp2 > name && cp2[-1] != '.')
*cp2++ = '.';
*cp2 = '\0';
/* Find file */
while (isspace(*cp))
++cp;
/* Terminate directory */
cp2 = cp;
while (!isspace(*cp) && *cp != '\0')
++cp;
*cp = '\0';
/* Process it! (zone is the same as the domain) */
nsoaval = -1;
memset(soaval, 0, sizeof(soaval));
if ((flags & CONF_NOZONE) == 0)
process(cp2, name, name);
continue;
}
if (strcasecmp(cp2, "network") == 0) {
errstr = parsenetwork(cp);
if (errstr != NULL) {
++errors;
fprintf(stderr,
"%s: %s:%d: bad network: %s\n",
prog, file, n, errstr);
}
continue;
}
if (strcasecmp(cp2, "include") == 0) {
/* Terminate include file */
cp2 = cp;
while (!isspace(*cp) && *cp != '\0')
++cp;
*cp = '\0';
doboot(cp2, 1);
continue;
}
/* Eat any other options */
}
(void)fclose(f);
}
void
doconf(const char *file, int flags)
{
int n, fd, cc, i, depth;
char *cp, *cp2, *buf;
const char *p;
char *name, *zonename, *filename, *typename;
int namelen, zonenamelen, filenamelen, typenamelen;
struct stat sbuf;
char zone[128], includefile[256];
errno = 0;
fd = open(file, O_RDONLY, 0);
if (fd < 0) {
/* Not an error if it doesn't exist */
if ((flags & CONF_MUSTEXIST) == 0 && errno == ENOENT) {
if (debug > 1)
printf(
"%s: doconf: %s doesn't exist (ignoring)\n",
prog, file);
return;
}
fprintf(stderr, "%s: %s: %s\n", prog, file, strerror(errno));
exit(1);
}
if (debug > 1)
printf("%s: doconf: opened %s\n", prog, file);
if (fstat(fd, &sbuf) < 0) {
fprintf(stderr, "%s: fstat(%s) %s\n",
prog, file, strerror(errno));
exit(1);
}
buf = (char *)malloc(sbuf.st_size + 1);
if (buf == NULL) {
fprintf(stderr, "%s: malloc: %s\n", prog, strerror(errno));
exit(1);
}
/* Slurp entire config file */
n = sbuf.st_size;
cp = buf;
do {
cc = read(fd, cp, n);
if (cc < 0) {
fprintf(stderr, "%s: read(%s) %s\n",
prog, file, strerror(errno));
exit(1);
}
cp += cc;
n -= cc;
} while (cc != 0 && cc < n);
buf[cc] = '\0';
#define EATWHITESPACE \
while (isspace(*cp)) { \
if (*cp == '\n') \
++n; \
++cp; \
}
/* Handle both to-end-of-line and C style comments */
#define EATCOMMENTS \
{ \
int sawcomment; \
do { \
EATWHITESPACE \
sawcomment = 0; \
if (*cp == '#') { \
sawcomment = 1; \
++cp; \
while (*cp != '\n' && *cp != '\0') \
++cp; \
} \
else if (strncmp(cp, "//", 2) == 0) { \
sawcomment = 1; \
cp += 2; \
while (*cp != '\n' && *cp != '\0') \
++cp; \
} \
else if (strncmp(cp, "/*", 2) == 0) { \
sawcomment = 1; \
for (cp += 2; *cp != '\0'; ++cp) { \
if (*cp == '\n') \
++n; \
else if (strncmp(cp, "*/", 2) == 0) { \
cp += 2; \
break; \
} \
} \
} \
} while (sawcomment); \
}
#define GETNAME(name, len) \
{ \
(name) = cp; \
(len) = 0; \
while (!isspace(*cp) && *cp != ';' && *cp != '\0') { \
++(len); \
++cp; \
} \
}
#define GETQUOTEDNAME(name, len) \
{ \
if (*cp != '"') { \
++errors; \
fprintf(stderr, "%s: %s:%d missing left quote\n", \
prog, file, n); \
} else \
++cp; \
(name) = cp; \
(len) = 0; \
while (*cp != '"' && *cp != '\n' && *cp != '\0') { \
++(len); \
++cp; \
} \
if (*cp != '"') { \
++errors; \
fprintf(stderr, "%s: %s:%d missing right quote\n", \
prog, file, n); \
} else \
++cp; \
}
/* Eat everything to the next semicolon, perhaps eating matching qbraces */
#define EATSEMICOLON \
{ \
int depth = 0; \
while (*cp != '\0') { \
EATCOMMENTS \
if (*cp == ';') { \
++cp; \
if (depth == 0) \
break; \
continue; \
} \
if (*cp == '{') { \
++depth; \
++cp; \
continue; \
} \
if (*cp == '}') { \
--depth; \
++cp; \
continue; \
} \
++cp; \
} \
}
/* Eat everything to the next left qbrace */
#define EATSLEFTBRACE \
while (*cp != '\0') { \
EATCOMMENTS \
if (*cp == '{') { \
++cp; \
break; \
} \
++cp; \
}
n = 1;
zone[0] = '\0';
cp = buf;
while (*cp != '\0') {
EATCOMMENTS
if (*cp == '\0')
break;
GETNAME(name, namelen)
if (namelen == 0) {
++errors;
fprintf(stderr, "%s: %s:%d garbage char '%c' (1)\n",
prog, file, n, *cp);
++cp;
continue;
}
EATCOMMENTS
if (strncasecmp(name, "options", namelen) == 0) {
EATCOMMENTS
if (*cp != '{') {
++errors;
fprintf(stderr,
"%s: %s:%d missing left qbrace in options\n",
prog, file, n);
} else
++cp;
EATCOMMENTS
while (*cp != '}' && *cp != '\0') {
EATCOMMENTS
GETNAME(name, namelen)
if (namelen == 0) {
++errors;
fprintf(stderr,
"%s: %s:%d garbage char '%c' (2)\n",
prog, file, n, *cp);
++cp;
break;
}
/* If not the "directory" option, just eat it */
if (strncasecmp(name, "directory",
namelen) == 0) {
EATCOMMENTS
GETQUOTEDNAME(cp2, i)
cp2[i] = '\0';
if (chdir(cp2) < 0) {
++errors;
fprintf(stderr,
"%s: %s:.%d can't chdir %s: %s\n",
prog, file, n, cp2,
strerror(errno));
exit(1);
}
cwd = savestr(cp2);
}
EATSEMICOLON
EATCOMMENTS
}
++cp;
EATCOMMENTS
if (*cp != ';') {
++errors;
fprintf(stderr,
"%s: %s:%d missing options semi\n",
prog, file, n);
} else
++cp;
continue;
}
if (strncasecmp(name, "zone", namelen) == 0) {
EATCOMMENTS
GETQUOTEDNAME(zonename, zonenamelen)
typename = NULL;
filename = NULL;
typenamelen = 0;
filenamelen = 0;
EATCOMMENTS
if (strncasecmp(cp, "in", 2) == 0) {
cp += 2;
EATWHITESPACE
} else if (strncasecmp(cp, "chaos", 5) == 0) {
cp += 5;
EATWHITESPACE
}
if (*cp != '{') { /* } */
++errors;
fprintf(stderr,
"%s: %s:%d missing left qbrace in zone\n",
prog, file, n);
continue;
}
depth = 0;
EATCOMMENTS
while (*cp != '\0') {
if (*cp == '{') {
++cp;
++depth;
} else if (*cp == '}') {
if (--depth <= 1)
break;
++cp;
}
EATCOMMENTS
GETNAME(name, namelen)
if (namelen == 0) {
++errors;
fprintf(stderr,
"%s: %s:%d garbage char '%c' (3)\n",
prog, file, n, *cp);
++cp;
break;
}
if (strncasecmp(name, "type",
namelen) == 0) {
EATCOMMENTS
GETNAME(typename, typenamelen)
if (namelen == 0) {
++errors;
fprintf(stderr,
"%s: %s:%d garbage char '%c' (4)\n",
prog, file, n, *cp);
++cp;
break;
}
} else if (strncasecmp(name, "file",
namelen) == 0) {
EATCOMMENTS
GETQUOTEDNAME(filename, filenamelen)
}
/* Just ignore keywords we don't understand */
EATSEMICOLON
EATCOMMENTS
}
/* { */
if (*cp != '}') {
++errors;
fprintf(stderr,
"%s: %s:%d missing zone right qbrace\n",
prog, file, n);
} else
++cp;
if (*cp != ';') {
++errors;
fprintf(stderr,
"%s: %s:%d missing zone semi\n",
prog, file, n);
} else
++cp;
EATCOMMENTS
/* If we got something interesting, process it */
if (typenamelen == 0) {
++errors;
fprintf(stderr, "%s: missing zone type!\n",
prog);
continue;
}
if (strncasecmp(typename, "master", typenamelen) == 0) {
if (filenamelen == 0) {
++errors;
fprintf(stderr,
"%s: missing zone filename!\n",
prog);
continue;
}
strncpy(zone, zonename, zonenamelen);
zone[zonenamelen] = '\0';
for (cp2 = zone; *cp2 != '\0'; ++cp2)
if (isupper(*cp2))
*cp2 = tolower(*cp2);
/* Insure trailing dot */
if (cp2 > zone && cp2[-1] != '.') {
*cp2++ = '.';
*cp2 = '\0';
}
filename[filenamelen] = '\0';
nsoaval = -1;
memset(soaval, 0, sizeof(soaval));
if ((flags & CONF_NOZONE) == 0)
process(filename, zone, zone);
}
continue;
}
if (strncasecmp(name, "nslint", namelen) == 0) {
EATCOMMENTS
if (*cp != '{') {
++errors;
fprintf(stderr,
"%s: %s:%d missing left qbrace in nslint\n",
prog, file, n);
} else
++cp;
++cp;
EATCOMMENTS
while (*cp != '}' && *cp != '\0') {
EATCOMMENTS
GETNAME(name, namelen)
if (strncasecmp(name, "network",
namelen) == 0) {
EATCOMMENTS
GETQUOTEDNAME(cp2, i)
cp2[i] = '\0';
p = parsenetwork(cp2);
if (p != NULL) {
++errors;
fprintf(stderr,
"%s: %s:%d: bad network: %s\n",
prog, file, n, p);
}
} else if (strncasecmp(name, "ignorezone",
namelen) == 0) {
EATCOMMENTS
GETQUOTEDNAME(cp2, i)
cp2[i] = '\0';
if (numignoredzones + 1 <
sizeof(ignoredzones) /
sizeof(ignoredzones[0])) {
ignoredzones[numignoredzones].zone =
savestr(cp2);
if (ignoredzones[numignoredzones].zone != NULL) {
ignoredzones[numignoredzones].len = strlen(cp2);
++numignoredzones;
}
}
} else {
++errors;
fprintf(stderr,
"%s: unknown nslint \"%.*s\"\n",
prog, namelen, name);
}
EATSEMICOLON
EATCOMMENTS
}
++cp;
EATCOMMENTS
if (*cp != ';') {
++errors;
fprintf(stderr,
"%s: %s:%d: missing nslint semi\n",
prog, file, n);
} else
++cp;
continue;
}
if (strncasecmp(name, "include", namelen) == 0) {
EATCOMMENTS
GETQUOTEDNAME(filename, filenamelen)
strncpy(includefile, filename, filenamelen);
includefile[filenamelen] = '\0';
doconf(includefile, 1);
EATSEMICOLON
continue;
}
if (strncasecmp(name, "view", namelen) == 0) {
EATSLEFTBRACE
continue;
}
/* Skip over statements we don't understand */
EATSEMICOLON
}
free(buf);
close(fd);
}
const char *
extractaddr(const char *str, struct addr *ap)
{
memset(ap, 0, sizeof(*ap));
/* Let's see what we've got here */
if (strchr(str, '.') != NULL) {
ap->family = AF_INET;
} else if (strchr(str, ':') != NULL) {
ap->family = AF_INET6;
} else
return ("unrecognized address type");
switch (ap->family) {
case AF_INET:
if (!inet_pton(ap->family, str, &ap->a_addr4))
return ("cannot parse IPv4 address");
break;
case AF_INET6:
if (!inet_pton(ap->family, str, &ap->a_addr6))
return ("cannot parse IPv6 address");
break;
default:
abort();
}
return (NULL);
}
const char *
extractnetwork(const char *str, struct network *np)
{
int i;
long w;
char *cp, *ep;
const char *p;
char temp[64];
memset(np, 0, sizeof(*np));
/* Let's see what we've got here */
if (strchr(str, '.') != NULL) {
np->family = AF_INET;
w = 32;
} else if (strchr(str, ':') != NULL) {
np->family = AF_INET6;
w = 128;
} else
return ("unrecognized address type");
p = strchr(str, '/');
if (p != NULL) {
/* Mask length was specified */
strncpy(temp, str, sizeof(temp));
temp[sizeof(temp) - 1] = '\0';
cp = strchr(temp, '/');
if (cp == NULL)
abort();
*cp++ = '\0';
ep = NULL;
w = strtol(cp, &ep, 10);
if (*ep != '\0')
return ("garbage following mask width");
str = temp;
}
switch (np->family) {
case AF_INET:
if (!inet_pton(np->family, str, &np->n_addr4))
return ("cannot parse IPv4 address");
if (w > 32)
return ("mask length must be <= 32");
setmaskwidth(w, np);
if ((np->n_addr4 & ~np->n_mask4) != 0)
return ("non-network bits set in addr");
#ifdef notdef
if ((ntohl(np->n_addr4) & 0xff000000) == 0)
return ("high octet must be non-zero");
#endif
break;
case AF_INET6:
if (!inet_pton(np->family, str, &np->n_addr6))
return ("cannot parse IPv6 address");
if (w > 128)
return ("mask length must be <= 128");
setmaskwidth(w, np);
for (i = 0; i < 16; ++i) {
if ((np->n_addr6[i] & ~np->n_mask6[i]) != 0)
return ("non-network bits set in addr");
}
break;
default:
abort();
}
return (NULL);
}
struct network *
findnetwork(struct addr *ap)
{
int i, j;
struct network *np;
switch (ap->family) {
case AF_INET:
for (i = 0, np = netlist; i < netlistcnt; ++i, ++np)
if ((ap->a_addr4 & np->n_mask4) == np->n_addr4)
return (np);
break;
case AF_INET6:
for (i = 0, np = netlist; i < netlistcnt; ++i, ++np) {
for (j = 0; j < sizeof(ap->a_addr6); ++j) {
if ((ap->a_addr6[j] & np->n_mask6[j]) !=
np->n_addr6[j])
break;
}
if (j >= sizeof(ap->a_addr6))
return (np);
}
break;
default:
abort();
}
return (NULL);
}
void
initprotoserv(void)
{
char *cp;
struct servent *sp;
char psbuf[512];
protoserv_len = 256;
protoserv = (char **)malloc(protoserv_len * sizeof(*protoserv));
if (protoserv == NULL) {
fprintf(stderr, "%s: nslint: malloc: %s\n",
prog, strerror(errno));
exit(1);
}
while ((sp = getservent()) != NULL) {
(void)sprintf(psbuf, "%s/%s", sp->s_name, sp->s_proto);
/* Convert to lowercase */
for (cp = psbuf; *cp != '\0'; ++cp)
if (isupper(*cp))
*cp = tolower(*cp);
if (protoserv_last + 1 >= protoserv_len) {
protoserv_len <<= 1;
protoserv = realloc(protoserv,
protoserv_len * sizeof(*protoserv));
if (protoserv == NULL) {
fprintf(stderr, "%s: nslint: realloc: %s\n",
prog, strerror(errno));
exit(1);
}
}
protoserv[protoserv_last] = savestr(psbuf);
++protoserv_last;
}
protoserv[protoserv_last] = NULL;
}
int
maskwidth(struct network *np)
{
int w;
int i, j;
u_int32_t m, tm;
/* Work backwards until we find a set bit */
switch (np->family) {
case AF_INET:
m = ntohl(np->n_mask4);
for (w = 32; w > 0; --w) {
tm = 0xffffffff << (32 - w);
if (tm == m)
break;
}
break;
case AF_INET6:
w = 128;
for (j = 15; j >= 0; --j) {
m = np->n_mask6[j];
for (i = 8; i > 0; --w, --i) {
tm = (0xff << (8 - i)) & 0xff;
if (tm == m)
return (w);
}
}
break;
default:
abort();
}
return (w);
}
const char *
network2str(struct network *np)
{
int w;
size_t len, size;
char *cp;
static char buf[128];
w = maskwidth(np);
switch (np->family) {
case AF_INET:
if (inet_ntop(np->family, &np->n_addr4,
buf, sizeof(buf)) == NULL) {
fprintf(stderr, "network2str: v4 botch");
abort();
}
if (w == 32)
return (buf);
break;
case AF_INET6:
if (inet_ntop(np->family, &np->n_addr6,
buf, sizeof(buf)) == NULL) {
fprintf(stderr, "network2str: v6 botch");
abort();
}
if (w == 128)
return (buf);
break;
default:
return ("<nil>");
}
/* Append address mask width */
cp = buf;
len = strlen(cp);
cp += len;
size = sizeof(buf) - len;
(void)snprintf(cp, size, "/%d", w);
return (buf);
}
void
nslint(void)
{
int n, records, flags;
struct item *ip, *lastaip, **ipp, **itemlist;
struct addr addr, lastaddr;
struct network *np;
itemlist = (struct item **)calloc(itemcnt, sizeof(*ipp));
if (itemlist == NULL) {
fprintf(stderr, "%s: nslint: calloc: %s\n",
prog, strerror(errno));
exit(1);
}
ipp = itemlist;
for (n = 0, ip = items; n < ITEMSIZE; ++n, ++ip) {
if (ip->host == NULL)
continue;
/* Save entries with addresses for later check */
if (ip->addr.family != 0)
*ipp++ = ip;
if (debug > 1) {
if (debug > 2)
printf("%d\t", n);
printf("%s\t%s\t0x%x\t0x%x\n",
ip->host, addr2str(&ip->addr),
ip->records, ip->flags);
}
/* Check for illegal hostnames (rfc1034) */
if (rfc1034host(ip->host, ip->records))
++errors;
/* Check for missing ptr records (ok if also an ns record) */
records = ip->records & MASK_CHECK_REC;
if ((ip->records & MASK_TEST_REC) != 0)
records |= REC_OTHER;
switch (records) {
case REC_A | REC_OTHER | REC_PTR | REC_REF:
case REC_A | REC_OTHER | REC_PTR:
case REC_A | REC_PTR | REC_REF:
case REC_A | REC_PTR:
case REC_AAAA | REC_OTHER | REC_PTR | REC_REF:
case REC_AAAA | REC_OTHER | REC_PTR:
case REC_AAAA | REC_PTR | REC_REF:
case REC_AAAA | REC_PTR:
case REC_CNAME:
/* These are O.K. */
break;
case REC_CNAME | REC_REF:
++errors;
fprintf(stderr, "%s: \"cname\" referenced by other"
" \"cname\" or \"mx\": %s\n", prog, ip->host);
break;
case REC_OTHER | REC_REF:
case REC_OTHER:
/*
* This is only an error if there is an address
* associated with the hostname; this means
* there was a wks entry with bogus address.
* Otherwise, we have an mx or hinfo.
*
* XXX ignore localhost for now
* (use flag to indicate loopback?)
*/
if (ip->addr.family == AF_INET &&
ip->addr.a_addr4 != htonl(INADDR_LOOPBACK)) {
++errors;
fprintf(stderr,
"%s: \"wks\" without \"a\" and \"ptr\": %s -> %s\n",
prog, ip->host, addr2str(&ip->addr));
}
break;
case REC_REF:
if (!checkignoredzone(ip->host)) {
++errors;
fprintf(stderr, "%s: Name referenced without"
" other records: %s\n", prog, ip->host);
}
break;
case REC_A | REC_OTHER | REC_REF:
case REC_A | REC_OTHER:
case REC_A | REC_REF:
case REC_A:
case REC_AAAA | REC_OTHER | REC_REF:
case REC_AAAA | REC_OTHER:
case REC_AAAA | REC_REF:
case REC_AAAA:
++errors;
fprintf(stderr, "%s: Missing \"ptr\": %s -> %s\n",
prog, ip->host, addr2str(&ip->addr));
break;
case REC_OTHER | REC_PTR | REC_REF:
case REC_OTHER | REC_PTR:
case REC_PTR | REC_REF:
case REC_PTR:
++errors;
fprintf(stderr, "%s: Missing \"a\": %s -> %s\n",
prog, ip->host, addr2str(&ip->addr));
break;
case REC_A | REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
case REC_A | REC_CNAME | REC_OTHER | REC_PTR:
case REC_A | REC_CNAME | REC_OTHER | REC_REF:
case REC_A | REC_CNAME | REC_OTHER:
case REC_A | REC_CNAME | REC_PTR | REC_REF:
case REC_A | REC_CNAME | REC_PTR:
case REC_A | REC_CNAME | REC_REF:
case REC_A | REC_CNAME:
case REC_AAAA | REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
case REC_AAAA | REC_CNAME | REC_OTHER | REC_PTR:
case REC_AAAA | REC_CNAME | REC_OTHER | REC_REF:
case REC_AAAA | REC_CNAME | REC_OTHER:
case REC_AAAA | REC_CNAME | REC_PTR | REC_REF:
case REC_AAAA | REC_CNAME | REC_PTR:
case REC_AAAA | REC_CNAME | REC_REF:
case REC_AAAA | REC_CNAME:
case REC_CNAME | REC_OTHER | REC_PTR | REC_REF:
case REC_CNAME | REC_OTHER | REC_PTR:
case REC_CNAME | REC_OTHER | REC_REF:
case REC_CNAME | REC_OTHER:
case REC_CNAME | REC_PTR | REC_REF:
case REC_CNAME | REC_PTR:
++errors;
fprintf(stderr, "%s: \"cname\" %s has other records\n",
prog, ip->host);
break;
case 0:
/* Second level test */
if ((ip->records & ~(REC_NS | REC_TXT)) == 0)
break;
/* Fall through... */
default:
++errors;
fprintf(stderr,
"%s: records == 0x%x: can't happen (%s 0x%x)\n",
prog, records, ip->host, ip->records);
break;
}
/* Check for smtp problems */
flags = ip->flags & MASK_TEST_SMTP;
if ((flags & FLG_SELFMX) != 0 &&
(ip->records & (REC_A | REC_AAAA)) == 0) {
++errors;
fprintf(stderr,
"%s: Self \"mx\" for %s missing"
" \"a\" or \"aaaa\" record\n",
prog, ip->host);
}
switch (flags) {
case 0:
case FLG_SELFMX | FLG_SMTPWKS:
/* These are O.K. */
break;
case FLG_SELFMX:
if ((ip->records & REC_WKS) != 0) {
++errors;
fprintf(stderr,
"%s: smtp/tcp missing from \"wks\": %s\n",
prog, ip->host);
}
break;
case FLG_SMTPWKS:
++errors;
fprintf(stderr,
"%s: Saw smtp/tcp without self \"mx\": %s\n",
prog, ip->host);
break;
default:
++errors;
fprintf(stderr,
"%s: flags == 0x%x: can't happen (%s)\n",
prog, flags, ip->host);
}
/* Check for chained MX records */
if ((ip->flags & (FLG_SELFMX | FLG_MXREF)) == FLG_MXREF &&
(ip->records & REC_MX) != 0) {
++errors;
fprintf(stderr, "%s: \"mx\" referenced by other"
" \"mx\" record: %s\n", prog, ip->host);
}
}
/* Check for doubly booked addresses */
n = ipp - itemlist;
qsort(itemlist, n, sizeof(itemlist[0]), cmpaddr);
memset(&lastaddr, 0, sizeof(lastaddr));
ip = NULL;
for (ipp = itemlist; n > 0; ++ipp, --n) {
addr = (*ipp)->addr;
if (cmpaddr(&lastaddr, &addr) == 0 &&
((*ipp)->flags & FLG_ALLOWDUPA) == 0 &&
(ip->flags & FLG_ALLOWDUPA) == 0) {
++errors;
fprintf(stderr, "%s: %s in use by %s and %s\n",
prog, addr2str(&addr), (*ipp)->host, ip->host);
}
memmove(&lastaddr, &addr, sizeof(addr));
ip = *ipp;
}
/* Check for hosts with multiple addresses on the same subnet */
n = ipp - itemlist;
qsort(itemlist, n, sizeof(itemlist[0]), cmpitemhost);
if (netlistcnt > 0) {
n = ipp - itemlist;
lastaip = NULL;
for (ipp = itemlist; n > 0; ++ipp, --n) {
ip = *ipp;
if ((ip->records & (REC_A | REC_AAAA)) == 0 ||
(ip->flags & FLG_ALLOWDUPA) != 0)
continue;
if (lastaip != NULL &&
strcasecmp(ip->host, lastaip->host) == 0) {
np = findnetwork(&ip->addr);
if (np == NULL) {
++errors;
fprintf(stderr,
"%s: Can't find subnet mask"
" for %s (%s)\n",
prog, ip->host,
addr2str(&ip->addr));
} else if (samesubnet(&lastaip->addr,
&ip->addr, np)) {
++errors;
fprintf(stderr,
"%s: Multiple \"a\" records for %s on subnet %s",
prog, ip->host,
network2str(np));
fprintf(stderr, "\n\t(%s",
addr2str(&lastaip->addr));
fprintf(stderr, " and %s)\n",
addr2str(&ip->addr));
}
}
lastaip = ip;
}
}
if (debug)
printf("%s: %d/%d items used, %d error%s\n", prog, itemcnt,
ITEMSIZE, errors, errors == 1 ? "" : "s");
}
const char *
parsenetwork(const char *cp)
{
const char *p;
struct network net;
while (isspace(*cp))
++cp;
p = extractnetwork(cp, &net);
if (p != NULL)
return (p);
while (isspace(*cp))
++cp;
/* Make sure there's room */
if (netlistsize <= netlistcnt) {
if (netlistsize == 0) {
netlistsize = 32;
netlist = (struct network *)
malloc(netlistsize * sizeof(*netlist));
} else {
netlistsize <<= 1;
netlist = (struct network *)
realloc(netlist, netlistsize * sizeof(*netlist));
}
if (netlist == NULL) {
fprintf(stderr,
"%s: parsenetwork: malloc/realloc: %s\n",
prog, strerror(errno));
exit(1);
}
}
/* Add to list */
memmove(netlist + netlistcnt, &net, sizeof(net));
++netlistcnt;
return (NULL);
}
const char *
parseptr(const char *str, struct addr *ap)
{
int i, n, base;
u_long v, v2;
char *cp;
const char *p;
u_char *up;
memset(ap, 0, sizeof(*ap));
base = -1;
/* IPv4 */
p = str + strlen(str) - sizeof(inaddr) + 1;
if (p >= str && strcasecmp(p, inaddr) == 0) {
ap->family = AF_INET;
n = 4;
base = 10;
} else {
/* IPv6 */
p = str + strlen(str) - sizeof(inaddr6) + 1;
if (p >= str && strcasecmp(p, inaddr6) == 0) {
ap->family = AF_INET6;
n = 16;
base = 16;
}
}
if (base < 0)
return ("Not a IPv4 or IPv6 \"ptr\" record");
up = (u_char *)&ap->addr;
for (i = 0; i < n; ++i) {
/* Back up to previous dot or beginning of string */
while (p > str && p[-1] != '.')
--p;
v = strtoul(p, &cp, base);
if (base == 10) {
if (v > 0xff)
return ("Octet larger than 8 bits");
} else {
if (v > 0xf)
return ("Octet larger than 4 bits");
if (*cp != '.')
return ("Junk in \"ptr\" record");
/* Back up over dot */
if (p > str)
--p;
/* Back up to previous dot or beginning of string */
while (p > str && p[-1] != '.')
--p;
v2 = strtoul(p, &cp, base);
if (v2 > 0xf)
return ("Octet larger than 4 bits");
if (*cp != '.')
return ("Junk in \"ptr\" record");
v = (v << 4) | v2;
}
if (*cp != '.')
return ("Junk in \"ptr\" record");
*up++ = v & 0xff;
/* Back up over dot */
if (p > str)
--p;
else if (p == str)
break;
}
if (i < n - 1)
return ("Too many octets in \"ptr\" record");
if (p != str)
return ("Not enough octets in \"ptr\" record");
return (NULL);
}
/* Returns a pointer after the next token or quoted string, else NULL */
char *
parsequoted(char *cp)
{
if (*cp == '"') {
++cp;
while (*cp != '"' && *cp != '\0')
++cp;
if (*cp != '"')
return (NULL);
++cp;
} else {
while (!isspace(*cp) && *cp != '\0')
++cp;
}
return (cp);
}
/* Return true when done */
int
parserrsig(const char *str, char **errstrp)
{
const char *cp;
/* XXX just look for closing paren */
cp = str + strlen(str) - 1;
while (cp >= str)
if (*cp-- == ')')
return (1);
return (0);
}
/* Return true when done */
int
parsesoa(const char *cp, char **errstrp)
{
char ch, *garbage;
static char errstr[132];
/* Eat leading whitespace */
while (isspace(*cp))
++cp;
/* Find opening paren */
if (nsoaval < 0) {
cp = strchr(cp, '(');
if (cp == NULL)
return (0);
++cp;
while (isspace(*cp))
++cp;
nsoaval = 0;
}
/* Grab any numbers we find */
garbage = "leading garbage";
while (isdigit(*cp) && nsoaval < NSOAVAL) {
soaval[nsoaval] = atoi(cp);
do {
++cp;
} while (isdigit(*cp));
if (nsoaval == SOA_SERIAL && *cp == '.' && isdigit(cp[1])) {
do {
++cp;
} while (isdigit(*cp));
} else {
ch = *cp;
if (isupper(ch))
ch = tolower(ch);
switch (ch) {
case 'w':
soaval[nsoaval] *= 7;
/* fall through */
case 'd':
soaval[nsoaval] *= 24;
/* fall through */
case 'h':
soaval[nsoaval] *= 60;
/* fall through */
case 'm':
soaval[nsoaval] *= 60;
/* fall through */
case 's':
++cp;
break;
default:
; /* none */
}
}
while (isspace(*cp))
++cp;
garbage = "trailing garbage";
++nsoaval;
}
/* If we're done, do some sanity checks */
if (nsoaval >= NSOAVAL && *cp == ')') {
++cp;
if (*cp != '\0')
*errstrp = garbage;
else if (soaval[SOA_EXPIRE] <
soaval[SOA_REFRESH] + 10 * soaval[SOA_RETRY]) {
(void)sprintf(errstr,
"expire less than refresh + 10 * retry (%u < %u + 10 * %u)",
soaval[SOA_EXPIRE],
soaval[SOA_REFRESH],
soaval[SOA_RETRY]);
*errstrp = errstr;
} else if (soaval[SOA_REFRESH] < 2 * soaval[SOA_RETRY]) {
(void)sprintf(errstr,
"refresh less than 2 * retry (%u < 2 * %u)",
soaval[SOA_REFRESH],
soaval[SOA_RETRY]);
*errstrp = errstr;
}
return (1);
}
if (*cp != '\0') {
*errstrp = garbage;
return (1);
}
return (0);
}
void
process(const char *file, const char *domain, const char *zone)
{
FILE *f;
char ch, *cp, *cp2, *cp3, *rtype;
const char *p;
int n, sawsoa, sawrrsig, flags, i;
u_int ttl;
enum rrtype rrtype;
struct addr *ap;
struct addr addr;
// struct network *net;
int smtp;
char buf[2048], name[256], lastname[256], odomain[256];
char *errstr;
const char *addrfmt =
"%s: %s/%s:%d \"%s\" target is an ip address: %s\n";
const char *dotfmt =
"%s: %s/%s:%d \"%s\" target missing trailing dot: %s\n";
/* Check for an "ignored zone" (usually dynamic dns) */
if (checkignoredzone(zone))
return;
f = fopen(file, "r");
if (f == NULL) {
fprintf(stderr, "%s: %s/%s: %s\n",
prog, cwd, file, strerror(errno));
++errors;
return;
}
if (debug > 1)
printf("%s: process: opened %s/%s\n", prog, cwd, file);
/* Line number */
n = 0;
ap = &addr;
lastname[0] = '\0';
sawsoa = 0;
sawrrsig = 0;
while (fgets(buf, sizeof(buf), f) != NULL) {
++n;
cp = buf;
while (*cp != '\0') {
/* Handle quoted strings (but don't report errors) */
if (*cp == '"') {
++cp;
while (*cp != '"' && *cp != '\n' && *cp != '\0')
++cp;
continue;
}
if (*cp == '\n' || *cp == ';')
break;
++cp;
}
*cp-- = '\0';
/* Nuke trailing white space */
while (cp >= buf && isspace(*cp))
*cp-- = '\0';
cp = buf;
if (*cp == '\0')
continue;
/* Handle multi-line soa records */
if (sawsoa) {
errstr = NULL;
if (parsesoa(cp, &errstr))
sawsoa = 0;
if (errstr != NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad \"soa\" record (%s)\n",
prog, cwd, file, n, errstr);
}
continue;
}
/* Handle multi-line rrsig records */
if (sawrrsig) {
errstr = NULL;
if (parserrsig(cp, &errstr))
sawsoa = 0;
if (errstr != NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad \"rrsig\" record (%s)\n",
prog, cwd, file, n, errstr);
}
continue;
}
if (debug > 3)
printf(">%s<\n", cp);
/* Look for name */
if (isspace(*cp)) {
/* Same name as last record */
if (lastname[0] == '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d No default name\n",
prog, cwd, file, n);
continue;
}
(void)strcpy(name, lastname);
} else {
/* Extract name, converting to lowercase */
for (cp2 = name; !isspace(*cp) && *cp != '\0'; ++cp)
if (isupper(*cp))
*cp2++ = tolower(*cp);
else
*cp2++ = *cp;
*cp2 = '\0';
/* Check for domain shorthand */
if (name[0] == '@' && name[1] == '\0')
(void)strcpy(name, domain);
}
/* Find next token */
while (isspace(*cp))
++cp;
/* Handle includes (gag) */
if (name[0] == '$' && strcasecmp(name, "$include") == 0) {
/* Extract filename */
cp2 = name;
while (!isspace(*cp) && *cp != '\0')
*cp2++ = *cp++;
*cp2 = '\0';
/* Look for optional domain */
while (isspace(*cp))
++cp;
if (*cp == '\0')
process(name, domain, zone);
else {
cp2 = cp;
/* Convert optional domain to lowercase */
for (; !isspace(*cp) && *cp != '\0'; ++cp)
if (isupper(*cp))
*cp = tolower(*cp);
*cp = '\0';
process(name, cp2, cp2);
}
continue;
}
/* Handle $origin */
if (name[0] == '$' && strcasecmp(name, "$origin") == 0) {
/* Extract domain, converting to lowercase */
for (cp2 = odomain; !isspace(*cp) && *cp != '\0'; ++cp)
if (isupper(*cp))
*cp2++ = tolower(*cp);
else
*cp2++ = *cp;
*cp2 = '\0';
domain = odomain;
lastname[0] = '\0';
continue;
}
/* Handle ttl */
if (name[0] == '$' && strcasecmp(name, "$ttl") == 0) {
cp2 = cp;
while (isdigit(*cp))
++cp;
ch = *cp;
if (isupper(ch))
ch = tolower(ch);
if (strchr("wdhms", ch) != NULL)
++cp;
while (isspace(*cp))
++cp;
if (*cp != '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad $ttl \"%s\"\n",
prog, cwd, file, n, cp2);
}
(void)strcpy(name, lastname);
continue;
}
/* Parse ttl or use default */
if (isdigit(*cp)) {
ttl = atoi(cp);
do {
++cp;
} while (isdigit(*cp));
ch = *cp;
if (isupper(ch))
ch = tolower(ch);
switch (ch) {
case 'w':
ttl *= 7;
/* fall through */
case 'd':
ttl *= 24;
/* fall through */
case 'h':
ttl *= 60;
/* fall through */
case 'm':
ttl *= 60;
/* fall through */
case 's':
++cp;
break;
default:
; /* none */
}
if (!isspace(*cp)) {
++errors;
fprintf(stderr, "%s: %s/%s:%d Bad ttl\n",
prog, cwd, file, n);
continue;
}
/* Find next token */
++cp;
while (isspace(*cp))
++cp;
} else
ttl = soaval[SOA_MINIMUM];
/* Eat optional "in" */
if ((cp[0] == 'i' || cp[0] == 'I') &&
(cp[1] == 'n' || cp[1] == 'N') && isspace(cp[2])) {
/* Find next token */
cp += 3;
while (isspace(*cp))
++cp;
} else if ((cp[0] == 'c' || cp[0] == 'C') &&
isspace(cp[5]) && strncasecmp(cp, "chaos", 5) == 0) {
/* Find next token */
cp += 5;
while (isspace(*cp))
++cp;
}
/* Find end of record type, converting to lowercase */
rtype = cp;
for (rtype = cp; !isspace(*cp) && *cp != '\0'; ++cp)
if (isupper(*cp))
*cp = tolower(*cp);
*cp++ = '\0';
/* Find "the rest" */
while (isspace(*cp))
++cp;
/* Check for non-ptr names with dots but no trailing dot */
if (!isdigit(*name) &&
checkdots(name) && strcmp(domain, ".") != 0) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"%s\" name missing trailing dot: %s\n",
prog, cwd, file, n, rtype, name);
}
/* Check for FQDNs outside the zone */
cp2 = name + strlen(name) - 1;
if (cp2 >= name && *cp2 == '.' && strchr(name, '.') != NULL) {
cp2 = name + strlen(name) - strlen(zone);
if (cp2 >= name && strcasecmp(cp2, zone) != 0) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"%s\" outside zone %s\n",
prog, cwd, file, n, name, zone);
}
}
rrtype = txt2rrtype(rtype);
switch (rrtype) {
case RR_A:
/* Handle "a" record */
add_domain(name, domain);
p = extractaddr(cp, ap);
if (p != NULL) {
++errors;
cp2 = cp + strlen(cp) - 1;
if (cp2 >= cp && *cp2 == '\n')
*cp2 = '\0';
fprintf(stderr,
"%s: %s/%s:%d Bad \"a\" record ip addr \"%s\"\n",
prog, cwd, file, n, cp);
continue;
}
if (ap->family != AF_INET) {
++errors;
cp2 = cp + strlen(cp) - 1;
if (cp2 >= cp && *cp2 == '\n')
*cp2 = '\0';
fprintf(stderr,
"%s: %s/%s:%d \"a\"record not AF_INET \"%s\"\n",
prog, cwd, file, n, cp);
continue;
}
errors += updateitem(name, ap, REC_A, ttl, 0);
break;
case RR_AAAA:
/* Handle "aaaa" record */
add_domain(name, domain);
p = extractaddr(cp, ap);
if (p != NULL) {
++errors;
cp2 = cp + strlen(cp) - 1;
if (cp2 >= cp && *cp2 == '\n')
*cp2 = '\0';
fprintf(stderr,
"%s: %s/%s:%d Bad \"aaaa\" record ip addr \"%s\"\n",
prog, cwd, file, n, cp);
continue;
}
if (ap->family != AF_INET6) {
++errors;
cp2 = cp + strlen(cp) - 1;
if (cp2 >= cp && *cp2 == '\n')
*cp2 = '\0';
fprintf(stderr,
"%s: %s/%s:%d \"aaaa\"record not AF_INET6 \"%s\"\n",
prog, cwd, file, n, cp);
continue;
}
errors += updateitem(name, ap, REC_AAAA, ttl, 0);
break;
case RR_PTR:
/* Handle "ptr" record */
add_domain(name, domain);
if (strcmp(cp, "@") == 0)
(void)strcpy(cp, zone);
if (checkdots(cp)) {
++errors;
fprintf(stderr,
checkaddr(cp) ? addrfmt : dotfmt,
prog, cwd, file, n, rtype, cp);
}
add_domain(cp, domain);
p = parseptr(name, ap);
if (p != NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad \"ptr\" record (%s) ip addr \"%s\"\n",
prog, cwd, file, n, p, name);
continue;
}
errors += updateitem(cp, ap, REC_PTR, 0, 0);
break;
case RR_SOA:
/* Handle "soa" record */
if (!CHECKDOT(name)) {
add_domain(name, domain);
errors += updateitem(name, NULL, REC_SOA, 0, 0);
}
errstr = NULL;
if (!parsesoa(cp, &errstr))
++sawsoa;
if (errstr != NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad \"soa\" record (%s)\n",
prog, cwd, file, n, errstr);
continue;
}
break;
case RR_WKS:
/* Handle "wks" record */
p = extractaddr(cp, ap);
if (p != NULL) {
++errors;
cp2 = cp;
while (!isspace(*cp2) && *cp2 != '\0')
++cp2;
*cp2 = '\0';
fprintf(stderr,
"%s: %s/%s:%d Bad \"wks\" record ip addr \"%s\"\n",
prog, cwd, file, n, cp);
continue;
}
/* Step over ip address */
while (*cp == '.' || isdigit(*cp))
++cp;
while (isspace(*cp))
*cp++ = '\0';
/* Make sure services are legit */
errstr = NULL;
n += checkwks(f, cp, &smtp, &errstr);
if (errstr != NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad \"wks\" record (%s)\n",
prog, cwd, file, n, errstr);
continue;
}
add_domain(name, domain);
errors += updateitem(name, ap, REC_WKS,
0, smtp ? FLG_SMTPWKS : 0);
/* XXX check to see if ip address records exists? */
break;
case RR_HINFO:
/* Handle "hinfo" record */
add_domain(name, domain);
errors += updateitem(name, NULL, REC_HINFO, 0, 0);
cp2 = cp;
cp = parsequoted(cp);
if (cp == NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
prog, cwd, file, n, cp2);
continue;
}
if (!isspace(*cp)) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"hinfo\" missing white space: %s\n",
prog, cwd, file, n, cp2);
continue;
}
++cp;
while (isspace(*cp))
++cp;
if (*cp == '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"hinfo\" missing keyword: %s\n",
prog, cwd, file, n, cp2);
continue;
}
cp = parsequoted(cp);
if (cp == NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
prog, cwd, file, n, cp2);
continue;
}
if (*cp != '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"hinfo\" garbage after keywords: %s\n",
prog, cwd, file, n, cp2);
continue;
}
break;
case RR_MX:
/* Handle "mx" record */
add_domain(name, domain);
errors += updateitem(name, NULL, REC_MX, ttl, 0);
/* Look for priority */
if (!isdigit(*cp)) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad \"mx\" priority: %s\n",
prog, cwd, file, n, cp);
}
/* Skip over priority */
++cp;
while (isdigit(*cp))
++cp;
while (isspace(*cp))
++cp;
if (*cp == '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Missing \"mx\" hostname\n",
prog, cwd, file, n);
}
if (strcmp(cp, "@") == 0)
(void)strcpy(cp, zone);
if (checkdots(cp)) {
++errors;
fprintf(stderr,
checkaddr(cp) ? addrfmt : dotfmt,
prog, cwd, file, n, rtype, cp);
}
/* Check to see if mx host exists */
add_domain(cp, domain);
flags = FLG_MXREF;
if (*name == *cp && strcmp(name, cp) == 0)
flags |= FLG_SELFMX;
errors += updateitem(cp, NULL, REC_REF, 0, flags);
break;
case RR_CNAME:
/* Handle "cname" record */
add_domain(name, domain);
errors += updateitem(name, NULL, REC_CNAME, 0, 0);
if (checkdots(cp)) {
++errors;
fprintf(stderr,
checkaddr(cp) ? addrfmt : dotfmt,
prog, cwd, file, n, rtype, cp);
}
/* Make sure cname points somewhere */
if (strcmp(cp, "@") == 0)
(void)strcpy(cp, zone);
add_domain(cp, domain);
errors += updateitem(cp, NULL, REC_REF, 0, 0);
break;
case RR_SRV:
/* Handle "srv" record */
add_domain(name, domain);
errors += updateitem(name, NULL, REC_SRV, 0, 0);
cp2 = cp;
/* Skip over three values */
for (i = 0; i < 3; ++i) {
if (!isdigit(*cp)) {
++errors;
fprintf(stderr, "%s: %s/%s:%d"
" Bad \"srv\" value: %s\n",
prog, cwd, file, n, cp);
}
/* Skip over value */
++cp;
while (isdigit(*cp))
++cp;
while (isspace(*cp))
++cp;
}
/* Check to see if mx host exists */
add_domain(cp, domain);
errors += updateitem(cp, NULL, REC_REF, 0, 0);
break;
case RR_TXT:
/* Handle "txt" record */
add_domain(name, domain);
errors += updateitem(name, NULL, REC_TXT, 0, 0);
cp2 = cp;
cp = parsequoted(cp);
if (cp == NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"txt\" missing quote: %s\n",
prog, cwd, file, n, cp2);
continue;
}
while (isspace(*cp))
++cp;
if (*cp != '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"txt\" garbage after text: %s\n",
prog, cwd, file, n, cp2);
continue;
}
break;
case RR_NS:
/* Handle "ns" record */
errors += updateitem(zone, NULL, REC_NS, 0, 0);
if (strcmp(cp, "@") == 0)
(void)strcpy(cp, zone);
if (checkdots(cp)) {
++errors;
fprintf(stderr,
checkaddr(cp) ? addrfmt : dotfmt,
prog, cwd, file, n, rtype, cp);
}
add_domain(cp, domain);
errors += updateitem(cp, NULL, REC_REF, 0, 0);
break;
case RR_RP:
/* Handle "rp" record */
add_domain(name, domain);
errors += updateitem(name, NULL, REC_RP, 0, 0);
cp2 = cp;
/* Step over mailbox name */
/* XXX could add_domain() and check further */
while (!isspace(*cp) && *cp != '\0')
++cp;
if (*cp == '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"rp\" missing text name: %s\n",
prog, cwd, file, n, cp2);
continue;
}
++cp;
cp3 = cp;
/* Step over text name */
while (!isspace(*cp) && *cp != '\0')
++cp;
if (*cp != '\0') {
++errors;
fprintf(stderr,
"%s: %s/%s:%d \"rp\" garbage after text name: %s\n",
prog, cwd, file, n, cp2);
continue;
}
/* Make sure text name points somewhere (if not ".") */
if (!CHECKDOT(cp3)) {
add_domain(cp3, domain);
errors += updateitem(cp3, NULL, REC_REF, 0, 0);
}
break;
case RR_ALLOWDUPA:
/* Handle "allow duplicate a" record */
add_domain(name, domain);
p = extractaddr(cp, ap);
if (p != NULL) {
++errors;
cp2 = cp + strlen(cp) - 1;
if (cp2 >= cp && *cp2 == '\n')
*cp2 = '\0';
fprintf(stderr,
"%s: %s/%s:%d Bad \"allowdupa\" record ip addr \"%s\"\n",
prog, cwd, file, n, cp);
continue;
}
errors += updateitem(name, ap, 0, 0, FLG_ALLOWDUPA);
break;
case RR_DNSKEY:
/* Handle "dnskey" record */
add_domain(name, domain);
errors += updateitem(name, NULL, REC_CNAME, 0, 0);
if (checkdots(cp)) {
++errors;
fprintf(stderr,
checkaddr(cp) ? addrfmt : dotfmt,
prog, cwd, file, n, rtype, cp);
}
/* Make sure cname points somewhere */
if (strcmp(cp, "@") == 0)
(void)strcpy(cp, zone);
add_domain(cp, domain);
errors += updateitem(cp, NULL, REC_REF, 0, 0);
break;
case RR_RRSIG:
errstr = NULL;
if (!parserrsig(cp, &errstr))
++sawrrsig;
if (errstr != NULL) {
++errors;
fprintf(stderr,
"%s: %s/%s:%d Bad \"rrsig\" record (%s)\n",
prog, cwd, file, n, errstr);
continue;
}
break;
case RR_NSEC:
/* XXX */
continue;
default:
/* Unknown record type */
++errors;
fprintf(stderr,
"%s: %s/%s:%d Unknown record type \"%s\"\n",
prog, cwd, file, n, rtype);
add_domain(name, domain);
errors += updateitem(name, NULL, REC_UNKNOWN, 0, 0);
break;
}
(void)strcpy(lastname, name);
}
(void)fclose(f);
return;
}
static const char *microlist[] = {
"_tcp",
"_udp",
"_msdcs",
"_sites",
NULL
};
int
rfc1034host(const char *host, int recs)
{
const char *cp, **p;
int underok;
underok = 0;
for (p = microlist; *p != NULL ;++p)
if ((cp = strstr(host, *p)) != NULL &&
cp > host &&
cp[-1] == '.' &&
cp[strlen(*p)] == '.') {
++underok;
break;
}
cp = host;
if (!(isalpha(*cp) || isdigit(*cp) || (*cp == '_' && underok))) {
fprintf(stderr,
"%s: illegal hostname \"%s\" (starts with non-alpha/numeric)\n",
prog, host);
return (1);
}
for (++cp; *cp != '.' && *cp != '\0'; ++cp)
if (!(isalpha(*cp) || isdigit(*cp) || *cp == '-' ||
(*cp == '/' && (recs & REC_SOA) != 0))) {
fprintf(stderr,
"%s: Illegal hostname \"%s\" ('%c' illegal character)\n",
prog, host, *cp);
return (1);
}
if (--cp >= host && *cp == '-') {
fprintf(stderr, "%s: Illegal hostname \"%s\" (ends with '-')\n",
prog, host);
return (1);
}
return (0);
}
enum rrtype
txt2rrtype(const char *str)
{
if (strcasecmp(str, "aaaa") == 0)
return (RR_AAAA);
if (strcasecmp(str, "a") == 0)
return (RR_A);
if (strcasecmp(str, "allowdupa") == 0)
return (RR_ALLOWDUPA);
if (strcasecmp(str, "cname") == 0)
return (RR_CNAME);
if (strcasecmp(str, "dnskey") == 0)
return (RR_DNSKEY);
if (strcasecmp(str, "hinfo") == 0)
return (RR_HINFO);
if (strcasecmp(str, "mx") == 0)
return (RR_MX);
if (strcasecmp(str, "ns") == 0)
return (RR_NS);
if (strcasecmp(str, "ptr") == 0)
return (RR_PTR);
if (strcasecmp(str, "rp") == 0)
return (RR_RP);
if (strcasecmp(str, "soa") == 0)
return (RR_SOA);
if (strcasecmp(str, "srv") == 0)
return (RR_SRV);
if (strcasecmp(str, "txt") == 0)
return (RR_TXT);
if (strcasecmp(str, "wks") == 0)
return (RR_WKS);
if (strcasecmp(str, "RRSIG") == 0)
return (RR_RRSIG);
if (strcasecmp(str, "NSEC") == 0)
return (RR_NSEC);
return (RR_UNDEF);
}
int
samesubnet(struct addr *a1, struct addr *a2, struct network *np)
{
int i;
u_int32_t v1, v2;
/* IPv4 before IPv6 */
if (a1->family != a2->family)
return (0);
switch (a1->family) {
case AF_INET:
/* Apply the mask to both values */
v1 = a1->a_addr4 & np->n_mask4;
v2 = a2->a_addr4 & np->n_mask4;
return (v1 == v2);
case AF_INET6:
/* Apply the mask to both values */
for (i = 0; i < 16; ++i) {
v1 = a1->a_addr6[i] & np->n_mask6[i];
v2 = a2->a_addr6[i] & np->n_mask6[i];
if (v1 != v2)
return (0);
}
break;
default:
abort();
}
return (1);
}
/* Set address mask in network order */
void
setmaskwidth(u_int w, struct network *np)
{
int i, j;
switch (np->family) {
case AF_INET:
if (w <= 0)
np->n_mask4 = 0;
else
np->n_mask4 = htonl(0xffffffff << (32 - w));
break;
case AF_INET6:
/* XXX is this right? */
memset(np->n_mask6, 0, sizeof(np->n_mask6));
for (i = 0; i < w / 8; ++i)
np->n_mask6[i] = 0xff;
i = w / 8;
j = w % 8;
if (j > 0 && i < 16)
np->n_mask6[i] = 0xff << (8 - j);
break;
default:
abort();
}
}
int
updateitem(const char *host, struct addr *ap, int records, u_int ttl, int flags)
{
const char *ccp;
int n, errs;
u_int i;
struct item *ip;
int foundsome;
n = 0;
foundsome = 0;
errs = 0;
/* Hash the host name */
i = 0;
ccp = host;
while (*ccp != '\0')
i = i * 37 + *ccp++;
ip = &items[i & (ITEMSIZE - 1)];
/* Look for a match or any empty slot */
while (n < ITEMSIZE && ip->host != NULL) {
if ((ap == NULL || ip->addr.family == 0 ||
cmpaddr(ap, &ip->addr) == 0) &&
*host == *ip->host && strcmp(host, ip->host) == 0) {
++foundsome;
if (ip->addr.family == 0 && ap != NULL)
memmove(&ip->addr, ap, sizeof(*ap));
if ((records & MASK_TEST_DUP) != 0)
checkdups(ip, records);
ip->records |= records;
/* Only check differing ttl's for A and MX records */
if (ip->ttl == 0)
ip->ttl = ttl;
else if (ttl != 0 && ip->ttl != ttl) {
fprintf(stderr,
"%s: Differing ttls for %s (%u != %u)\n",
prog, ip->host, ttl, ip->ttl);
++errs;
}
ip->flags |= flags;
/* Not done if we wildcard matched the name */
if (ap != NULL)
return (errs);
}
++n;
++ip;
if (ip >= &items[ITEMSIZE])
ip = items;
}
if (n >= ITEMSIZE) {
fprintf(stderr, "%s: Out of item slots (max %d)\n",
prog, ITEMSIZE);
exit(1);
}
/* Done if we were wildcarding the name (and found entries for it) */
if (ap == NULL && foundsome) {
return (errs);
}
/* Didn't find it, make new entry */
++itemcnt;
if (ip->host) {
fprintf(stderr, "%s: Reusing bucket!\n", prog);
exit(1);
}
if (ap != NULL)
memmove(&ip->addr, ap, sizeof(*ap));
ip->host = savestr(host);
if ((records & MASK_TEST_DUP) != 0)
checkdups(ip, records);
ip->records |= records;
if (ttl != 0)
ip->ttl = ttl;
ip->flags |= flags;
return (errs);
}
void
usage(void)
{
fprintf(stderr, "Version %s\n", version);
fprintf(stderr, "usage: %s [-d] [-b named.boot] [-B nslint.boot]\n",
prog);
fprintf(stderr, " %s [-d] [-c named.conf] [-C nslint.conf]\n",
prog);
exit(1);
}