
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
2974 lines
63 KiB
C
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);
|
|
}
|