diff options
Diffstat (limited to 'contrib/nslint-2.1a3/nslint.c')
-rw-r--r-- | contrib/nslint-2.1a3/nslint.c | 2382 |
1 files changed, 2382 insertions, 0 deletions
diff --git a/contrib/nslint-2.1a3/nslint.c b/contrib/nslint-2.1a3/nslint.c new file mode 100644 index 0000000..8ce9505 --- /dev/null +++ b/contrib/nslint-2.1a3/nslint.c @@ -0,0 +1,2382 @@ +/* + * Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001 + * 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\n\ +The Regents of the University of California. All rights reserved.\n"; +static const char rcsid[] = + "@(#) $Id: nslint.c,v 1.1 2001/12/21 04:12:04 marka Exp $ (LBL)"; +#endif +/* + * nslint - perform consistency checks on dns files + */ + +#include <sys/types.h> +#include <sys/stat.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_MALLOC_H +#include <malloc.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 "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 */ + +/* item struct */ +struct item { + char *host; /* pointer to hostname */ + u_int32_t addr; /* ip address */ + u_int ttl; /* ttl of A records */ + int records; /* resource records seen */ + int flags; /* flags word */ +}; + +/* Resource records seen */ +#define REC_A 0x0001 +#define REC_PTR 0x0002 +#define REC_WKS 0x0004 +#define REC_HINFO 0x0008 +#define REC_MX 0x0010 +#define REC_CNAME 0x0020 +#define REC_NS 0x0040 +#define REC_SOA 0x0080 +#define REC_RP 0x0100 +#define REC_TXT 0x0200 +#define REC_SRV 0x0400 + +/* These aren't real records */ +#define REC_OTHER 0x0800 +#define REC_REF 0x1000 +#define REC_UNKNOWN 0x2000 + +/* 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_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_HINFO) + +/* 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 */ + +/* Test for smtp problems */ +#define MASK_TEST_SMTP \ + (FLG_SELFMX | FLG_SMTPWKS) + + +#define ITEMSIZE (1 << 17) /* power of two */ +#define ITEMHASH(str, h, p) \ + for (p = str, h = 0; *p != '.' && *p != '\0';) h = (h << 5) - h + *p++ + +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; +char *bootfile = "/etc/named.boot"; +char *conffile = "/etc/named.conf"; +char *nslintboot; +char *nslintconf; +char *prog; +char *cwd = "."; + +char **protoserv; /* valid protocol/service names */ +int protoserv_init; +int protoserv_last; +int protoserv_len; + +static char inaddr[] = ".in-addr.arpa."; + +/* 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 */ +static inline void add_domain(char *, const char *); +int checkdots(const char *); +void checkdups(struct item *, int); +int checkserv(const char *, char **p); +int checkwks(FILE *, char *, int *, char **); +int cmpaddr(const void *, const void *); +int cmphost(const void *, const void *); +int doboot(const char *, int); +int doconf(const char *, int); +void initprotoserv(void); +char *intoa(u_int32_t); +int main(int, char **); +int nslint(void); +int parseinaddr(const char *, u_int32_t *, u_int32_t *); +int parsenetwork(const char *, char **); +u_int32_t parseptr(const char *, u_int32_t, u_int32_t, char **); +char *parsequoted(char *); +int parsesoa(const char *, char **); +void process(const char *, const char *, const char *); +int rfc1034host(const char *, int); +int updateitem(const char *, u_int32_t, int, u_int, int); +__dead void usage(void) __attribute__((volatile)); + +extern char *optarg; +extern int optind, opterr; + +/* add domain if necessary */ +static inline void +add_domain(register char *name, register const char *domain) +{ + register 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? */ +} + +int +main(int argc, char **argv) +{ + register char *cp; + register int op, status, i, 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(); + + if (donamedboot) + status = doboot(bootfile, 1); + else if (donamedconf) + status = doconf(conffile, 1); + else { + status = doconf(conffile, 0); + if (status < 0) { + status = doboot(bootfile, 1); + ++donamedboot; + } else + ++donamedconf; + } + + if (donamedboot) { + if (nslintboot != NULL) + status |= doboot(nslintboot, 1); + else if ((i = doboot(NSLINTBOOT, 0)) > 0) + status |= i; + } else { + if (nslintconf != NULL) + status |= doconf(nslintconf, 1); + else if ((i = doconf(NSLINTCONF, 0)) > 0) + status |= i; + } + status |= nslint(); + exit (status); +} + +struct netlist { + u_int32_t net; + u_int32_t mask; +}; + +static struct netlist *netlist; +static u_int netlistsize; /* size of array */ +static u_int netlistcnt; /* next free element */ + +static u_int32_t +findmask(u_int32_t addr) +{ + register int i; + + for (i = 0; i < netlistcnt; ++i) + if ((addr & netlist[i].mask) == netlist[i].net) + return (netlist[i].mask); + return (0); +} + +int +parsenetwork(register const char *cp, register char **errstrp) +{ + register int i, w; + register u_int32_t net, mask; + register u_int32_t o; + register int shift; + static char errstr[132]; + + while (isspace(*cp)) + ++cp; + net = 0; + mask = 0; + shift = 24; + while (isdigit(*cp) && shift >= 0) { + o = 0; + do { + o = o * 10 + (*cp++ - '0'); + } while (isdigit(*cp)); + net |= o << shift; + shift -= 8; + if (*cp != '.') + break; + ++cp; + } + + + if (isspace(*cp)) { + ++cp; + while (isspace(*cp)) + ++cp; + mask = htonl(inet_addr(cp)); + if ((int)mask == -1) { + *errstrp = errstr; + (void)sprintf(errstr, "bad mask \"%s\"", cp); + return (0); + } + i = 0; + while (isdigit(*cp)) + ++cp; + for (i = 0; i < 3 && *cp == '.'; ++i) { + ++cp; + while (isdigit(*cp)) + ++cp; + } + if (i != 3) { + *errstrp = "wrong number of dots in mask"; + return (0); + } + } else if (*cp == '/') { + ++cp; + w = atoi(cp); + do { + ++cp; + } while (isdigit(*cp)); + if (w < 1 || w > 32) { + *errstrp = "bad mask width"; + return (0); + } + mask = 0xffffffff << (32 - w); + } else { + *errstrp = "garbage after net"; + return (0); + } + + while (isspace(*cp)) + ++cp; + + if (*cp != '\0') { + *errstrp = "trailing garbage"; + return (0); + } + + /* Finaly sanity checks */ + if ((net & ~ mask) != 0) { + *errstrp = errstr; + (void)sprintf(errstr, "host bits set in net \"%s\"", + intoa(net)); + return (0); + } + + /* Make sure there's room */ + if (netlistsize <= netlistcnt) { + if (netlistsize == 0) { + netlistsize = 32; + netlist = (struct netlist *) + malloc(netlistsize * sizeof(*netlist)); + } else { + netlistsize <<= 1; + netlist = (struct netlist *) + realloc(netlist, netlistsize * sizeof(*netlist)); + } + if (netlist == NULL) { + fprintf(stderr, "%s: nslint: malloc/realloc: %s\n", + prog, strerror(errno)); + exit(1); + } + } + + /* Add to list */ + netlist[netlistcnt].net = net; + netlist[netlistcnt].mask = mask; + ++netlistcnt; + + return (1); +} + +int +doboot(register const char *file, register int mustexist) +{ + register int n; + register char *cp, *cp2; + register FILE *f; + 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 (!mustexist && errno == ENOENT) { + if (debug > 1) + printf( + "%s: doit: %s doesn't exist (ignoring)\n", + prog, file); + return (-1); + } + 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)); + process(cp2, name, name); + continue; + } + if (strcasecmp(cp2, "network") == 0) { + if (!parsenetwork(cp, &errstr)) { + ++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'; + errors += doboot(cp2, 1); + continue; + } + /* Eat any other options */ + } + (void)fclose(f); + + return (errors != 0); +} + +int +doconf(register const char *file, register int mustexist) +{ + register int n, fd, cc, i, depth; + register char *cp, *cp2, *buf; + register char *name, *zonename, *filename, *typename; + register int namelen, zonenamelen, filenamelen, typenamelen; + char *errstr; + 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 (!mustexist && errno == ENOENT) { + if (debug > 1) + printf( + "%s: doconf: %s doesn't exist (ignoring)\n", + prog, file); + return (-1); + } + 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 \ + { \ + register 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; \ + } \ + } + + 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)); + 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'; + if (!parsenetwork(cp2, &errstr)) { + ++errors; + fprintf(stderr, + "%s: %s:%d: bad network: %s\n", + prog, file, n, errstr); + } + } else { + ++errors; + fprintf(stderr, + "%s: unknown nslint \"%.*s\"\n", + prog, namelen, name); + } + EATSEMICOLON + EATCOMMENTS + } + ++cp; + EATCOMMENTS + if (*cp != ';') { + ++errors; + fprintf(stderr, "missing options semi\n"); + } else + ++cp; + continue; + } + if (strncasecmp(name, "include", namelen) == 0) { + EATCOMMENTS + GETQUOTEDNAME(filename, filenamelen) + strncpy(includefile, filename, filenamelen); + includefile[filenamelen] = '\0'; + errors += doconf(includefile, 1); + EATSEMICOLON + continue; + } + + /* Skip over statements we don't understand */ + EATSEMICOLON + } + + free(buf); + close(fd); + return (errors != 0); +} + +/* Return true when done */ +int +parsesoa(register const char *cp, register char **errstrp) +{ + register 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(register const char *file, register const char *domain, + register const char *zone) +{ + register FILE *f; + register char ch, *cp, *cp2, *cp3, *rtype; + register const char *ccp; + register int n, sawsoa, flags, i; + register u_int ttl; + register u_int32_t addr; + u_int32_t net, mask; + int smtp; + char buf[1024], name[128], lastname[128], odomain[128]; + char *errstr; + char *dotfmt = "%s: %s/%s:%d \"%s\" target missing trailing dot: %s\n"; + + 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); + + /* Are we doing an in-addr.arpa domain? */ + n = 0; + net = 0; + mask = 0; + ccp = domain + strlen(domain) - sizeof(inaddr) + 1; + if (ccp >= domain && strcasecmp(ccp, inaddr) == 0 && + !parseinaddr(domain, &net, &mask)) { + ++errors; + fprintf(stderr, "%s: %s/%s:%d bad in-addr.arpa domain\n", + prog, cwd, file, n); + return; + } + + lastname[0] = '\0'; + sawsoa = 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; + } + 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'; + + /* Are we doing an in-addr.arpa domain? */ + net = 0; + mask = 0; + ccp = domain + strlen(domain) - (sizeof(inaddr) - 1); + if (ccp >= domain && strcasecmp(ccp, inaddr) == 0 && + !parseinaddr(domain, &net, &mask)) { + ++errors; + fprintf(stderr, + "%s: %s/%s:%d bad in-addr.arpa domain\n", + prog, cwd, file, n); + return; + } + 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); + } + } + +#define CHECK4(p, a, b, c, d) \ + (p[0] == (a) && p[1] == (b) && p[2] == (c) && p[3] == (d) && p[4] == '\0') +#define CHECK3(p, a, b, c) \ + (p[0] == (a) && p[1] == (b) && p[2] == (c) && p[3] == '\0') +#define CHECK2(p, a, b) \ + (p[0] == (a) && p[1] == (b) && p[2] == '\0') +#define CHECKDOT(p) \ + (p[0] == '.' && p[1] == '\0') + + if (rtype[0] == 'a' && rtype[1] == '\0') { + /* Handle "a" record */ + add_domain(name, domain); + addr = htonl(inet_addr(cp)); + if ((int)addr == -1) { + ++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; + } + errors += updateitem(name, addr, REC_A, ttl, 0); + } else if (CHECK4(rtype, 'a', 'a', 'a', 'a')) { + /* Just eat for now */ + continue; + } else if (CHECK3(rtype, 'p', 't', 'r')) { + /* Handle "ptr" record */ + add_domain(name, domain); + if (strcmp(cp, "@") == 0) + (void)strcpy(cp, zone); + if (checkdots(cp)) { + ++errors; + fprintf(stderr, dotfmt, + prog, cwd, file, n, rtype, cp); + } + add_domain(cp, domain); + errstr = NULL; + addr = parseptr(name, net, mask, &errstr); + if (errstr != NULL) { + ++errors; + fprintf(stderr, + "%s: %s/%s:%d bad \"ptr\" record (%s) ip addr \"%s\"\n", + prog, cwd, file, n, errstr, name); + continue; + } + errors += updateitem(cp, addr, REC_PTR, 0, 0); + } else if (CHECK3(rtype, 's', 'o', 'a')) { + /* Handle "soa" record */ + if (!CHECKDOT(name)) { + add_domain(name, domain); + errors += updateitem(name, 0, 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; + } + } else if (CHECK3(rtype, 'w', 'k', 's')) { + /* Handle "wks" record */ + addr = htonl(inet_addr(cp)); + if ((int)addr == -1) { + ++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, addr, REC_WKS, + 0, smtp ? FLG_SMTPWKS : 0); + /* XXX check to see if ip address records exists? */ + } else if (rtype[0] == 'h' && strcmp(rtype, "hinfo") == 0) { + /* Handle "hinfo" record */ + add_domain(name, domain); + errors += updateitem(name, 0, 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; + } + } else if (CHECK2(rtype, 'm', 'x')) { + /* Handle "mx" record */ + add_domain(name, domain); + errors += updateitem(name, 0, 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, 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, 0, REC_REF, 0, flags); + } else if (rtype[0] == 'c' && strcmp(rtype, "cname") == 0) { + /* Handle "cname" record */ + add_domain(name, domain); + errors += updateitem(name, 0, REC_CNAME, 0, 0); + if (checkdots(cp)) { + ++errors; + fprintf(stderr, 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, 0, REC_REF, 0, 0); + } else if (CHECK3(rtype, 's', 'r', 'v')) { + /* Handle "srv" record */ + add_domain(name, domain); + errors += updateitem(name, 0, 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, 0, REC_REF, 0, 0); + } else if (CHECK3(rtype, 't', 'x', 't')) { + /* Handle "txt" record */ + add_domain(name, domain); + errors += updateitem(name, 0, 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; + } + } else if (CHECK2(rtype, 'n', 's')) { + /* Handle "ns" record */ + errors += updateitem(zone, 0, REC_NS, 0, 0); + if (strcmp(cp, "@") == 0) + (void)strcpy(cp, zone); + if (checkdots(cp)) { + ++errors; + fprintf(stderr, dotfmt, + prog, cwd, file, n, rtype, cp); + } + add_domain(cp, domain); + errors += updateitem(cp, 0, REC_REF, 0, 0); + } else if (CHECK2(rtype, 'r', 'p')) { + /* Handle "rp" record */ + add_domain(name, domain); + errors += updateitem(name, 0, 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, 0, REC_REF, 0, 0); + } + } else if (rtype[0] == 'a' && strcmp(rtype, "allowdupa") == 0) { + /* Handle "allow duplicate a" record */ + add_domain(name, domain); + addr = htonl(inet_addr(cp)); + if ((int)addr == -1) { + ++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, addr, 0, 0, FLG_ALLOWDUPA); + } else { + /* 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, 0, REC_UNKNOWN, 0, 0); + } + (void)strcpy(lastname, name); + } + (void)fclose(f); + return; +} + +/* Records we use to detect duplicates */ +static struct duprec { + int record; + char *name; +} duprec[] = { + { REC_A, "a" }, + { REC_HINFO, "hinfo" }, + { 0, NULL }, +}; + +void +checkdups(register struct item *ip, register int records) +{ + register 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 (%d)\n", + prog, records); +} + +int +updateitem(register const char *host, register u_int32_t addr, + register int records, register u_int ttl, register int flags) +{ + register const char *ccp; + register int n, errs; + register u_int i; + register struct item *ip; + int foundsome; + + n = 0; + foundsome = 0; + errs = 0; + ITEMHASH(host, i, ccp); + ip = &items[i & (ITEMSIZE - 1)]; + while (n < ITEMSIZE && ip->host) { + if ((addr == 0 || addr == ip->addr || ip->addr == 0) && + *host == *ip->host && strcmp(host, ip->host) == 0) { + ++foundsome; + if (ip->addr == 0) + ip->addr = addr; + 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 (addr) + 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 (addr == 0 && foundsome) + return (errs); + + /* Didn't find it, make new entry */ + ++itemcnt; + if (ip->host) { + fprintf(stderr, "%s: reusing bucket!\n", prog); + exit(1); + } + ip->addr = addr; + 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); +} + +static const char *microlist[] = { + "_tcp", + "_udp", + "_msdcs", + "_sites", + NULL +}; + +int +rfc1034host(register const char *host, register int recs) +{ + register const char *cp, **p; + register 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); +} + +int +nslint(void) +{ + register int n, records, flags; + register struct item *ip, *lastaip, **ipp, **itemlist; + register u_int32_t addr, lastaddr, mask; + + 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 != 0) + *ipp++ = ip; + + if (debug > 1) { + if (debug > 2) + printf("%d\t", n); + printf("%s\t%s\t0x%x\t0x%x\n", + ip->host, intoa(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_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. + */ + if (ip->addr != 0) { + ++errors; + fprintf(stderr, + "%s: \"wks\" without \"a\" and \"ptr\": %s -> %s\n", + prog, ip->host, intoa(ip->addr)); + } + break; + + case REC_REF: + ++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: + ++errors; + fprintf(stderr, "%s: missing \"ptr\": %s -> %s\n", + prog, ip->host, intoa(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, intoa(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_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) == 0) { + ++errors; + fprintf(stderr, + "%s: self \"mx\" for %s missing \"a\" 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); + lastaddr = 0; + ip = NULL; + for (ipp = itemlist; n > 0; ++ipp, --n) { + addr = (*ipp)->addr; + if (lastaddr == addr && + ((*ipp)->flags & FLG_ALLOWDUPA) == 0 && + (ip->flags & FLG_ALLOWDUPA) == 0) { + ++errors; + fprintf(stderr, "%s: %s in use by %s and %s\n", + prog, intoa(addr), (*ipp)->host, ip->host); + } + lastaddr = addr; + ip = *ipp; + } + + /* Check for hosts with multiple addresses on the same subnet */ + n = ipp - itemlist; + qsort(itemlist, n, sizeof(itemlist[0]), cmphost); + if (netlistcnt > 0) { + n = ipp - itemlist; + lastaip = NULL; + for (ipp = itemlist; n > 0; ++ipp, --n) { + ip = *ipp; + if ((ip->records & REC_A) == 0 || + (ip->flags & FLG_ALLOWDUPA) != 0) + continue; + if (lastaip != NULL && + strcasecmp(ip->host, lastaip->host) == 0) { + mask = findmask(ip->addr); + if (mask == 0) { + ++errors; + fprintf(stderr, + "%s: can't find mask for %s (%s)\n", + prog, ip->host, intoa(ip->addr)); + } else if ((lastaip->addr & mask) == + (ip->addr & mask) ) { + ++errors; + fprintf(stderr, + "%s: multiple \"a\" records for %s on subnet %s", + prog, ip->host, + intoa(ip->addr & mask)); + fprintf(stderr, "\n\t(%s", + intoa(lastaip->addr)); + fprintf(stderr, " and %s)\n", + intoa(ip->addr)); + } + } + lastaip = ip; + } + } + + if (debug) + printf("%s: %d/%d items used, %d error%s\n", prog, itemcnt, + ITEMSIZE, errors, errors == 1 ? "" : "s"); + return (errors != 0); +} + +/* Similar to inet_ntoa() */ +char * +intoa(u_int32_t addr) +{ + register char *cp; + register u_int byte; + register int n; + static char buf[sizeof(".xxx.xxx.xxx.xxx")]; + + cp = &buf[sizeof buf]; + *--cp = '\0'; + + n = 4; + do { + byte = addr & 0xff; + *--cp = byte % 10 + '0'; + byte /= 10; + if (byte > 0) { + *--cp = byte % 10 + '0'; + byte /= 10; + if (byte > 0) + *--cp = byte + '0'; + } + *--cp = '.'; + addr >>= 8; + } while (--n > 0); + + return cp + 1; +} + +int +parseinaddr(register const char *cp, register u_int32_t *netp, + register u_int32_t *maskp) +{ + register int i, bits; + register u_int32_t o, net, mask; + + if (!isdigit(*cp)) + return (0); + net = 0; + mask = 0xff000000; + bits = 0; + o = 0; + do { + o = o * 10 + (*cp++ - '0'); + } while (isdigit(*cp)); + net = o << 24; + + /* Check for classless delegation mask width */ + if (*cp == '/') { + ++cp; + o = 0; + do { + o = o * 10 + (*cp++ - '0'); + } while (isdigit(*cp)); + bits = o; + if (bits <= 0 || bits > 32) + return (0); + } + + if (*cp == '.' && isdigit(cp[1])) { + ++cp; + o = 0; + do { + o = o * 10 + (*cp++ - '0'); + } while (isdigit(*cp)); + net = (net >> 8) | (o << 24); + mask = 0xffff0000; + if (*cp == '.' && isdigit(cp[1])) { + ++cp; + o = 0; + do { + o = o * 10 + (*cp++ - '0'); + } while (isdigit(*cp)); + net = (net >> 8) | (o << 24); + mask = 0xffffff00; + if (*cp == '.' && isdigit(cp[1])) { + ++cp; + o = 0; + do { + o = o * 10 + (*cp++ - '0'); + } while (isdigit(*cp)); + net = (net >> 8) | (o << 24); + mask = 0xffffffff; + } + } + } + if (strcasecmp(cp, inaddr) != 0) + return (0); + + /* Classless delegation */ + /* XXX check that calculated mask isn't smaller than octet mask? */ + if (bits != 0) + for (mask = 0, i = 31; bits > 0; --i, --bits) + mask |= (1 << i); + + *netp = net; + *maskp = mask; + return (1); +} + +u_int32_t +parseptr(register const char *cp, u_int32_t net, u_int32_t mask, + register char **errstrp) +{ + register u_int32_t o, addr; + register int shift; + + addr = 0; + shift = 0; + while (isdigit(*cp) && shift < 32) { + o = 0; + do { + o = o * 10 + (*cp++ - '0'); + } while (isdigit(*cp)); + addr |= o << shift; + shift += 8; + if (*cp != '.') { + if (*cp == '\0') + break; + *errstrp = "missing dot"; + return (0); + } + ++cp; + } + + if (shift > 32) { + *errstrp = "more than 4 octets"; + return (0); + } + + if (shift == 32 && strcasecmp(cp, inaddr + 1) == 0) + return (addr); + +#ifdef notdef + if (*cp != '\0') { + *errstrp = "trailing junk"; + return (0); + } +#endif +#ifdef notdef + if ((~mask & net) != 0) { + *errstrp = "too many octets for net"; + return (0); + } +#endif + return (net | addr); +} + +int +checkwks(register FILE *f, register char *proto, register int *smtpp, + register char **errstrp) +{ + register int n, sawparen; + register 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 +checkserv(register const char *serv, register char **p) +{ + for (; *p != NULL; ++p) + if (*serv == **p && strcmp(serv, *p) == 0) + return (1); + return (0); +} + +void +initprotoserv(void) +{ + register char *cp; + register 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; +} + +/* + * 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(register const char *name) +{ + register 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); +} + +int +cmpaddr(register const void *ip1, register const void *ip2) +{ + register u_int32_t a1, a2; + + a1 = (*(struct item **)ip1)->addr; + a2 = (*(struct item **)ip2)->addr; + + if (a1 < a2) + return (-1); + else if (a1 > a2) + return (1); + else + return (0); +} + +int +cmphost(register const void *ip1, register const void *ip2) +{ + register const char *s1, *s2; + + s1 = (*(struct item **)ip1)->host; + s2 = (*(struct item **)ip2)->host; + + return (strcasecmp(s1, s2)); +} + +/* Returns a pointer after the next token or quoted string, else NULL */ +char * +parsequoted(register char *cp) +{ + + if (*cp == '"') { + ++cp; + while (*cp != '"' && *cp != '\0') + ++cp; + if (*cp != '"') + return (NULL); + ++cp; + } else { + while (!isspace(*cp) && *cp != '\0') + ++cp; + } + return (cp); +} + +__dead void +usage(void) +{ + extern char version[]; + + 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); +} |