1 /* $NetBSD: nslint.c,v 1.1.1.3 2014/12/10 03:34:34 christos Exp $ */
4 * Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009
5 * The Regents of the University of California. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that: (1) source code distributions
9 * retain the above copyright notice and this paragraph in its entirety, (2)
10 * distributions including binary code include the above copyright notice and
11 * this paragraph in its entirety in the documentation or other materials
12 * provided with the distribution, and (3) all advertising materials mentioning
13 * features or use of this software display the following acknowledgement:
14 * ``This product includes software developed by the University of California,
15 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
16 * the University nor the names of its contributors may be used to endorse
17 * or promote products derived from this software without specific prior
19 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
20 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
21 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
24 static const char copyright
[] =
25 "@(#) Copyright (c) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009\n\
26 The Regents of the University of California. All rights reserved.\n";
27 static const char rcsid
[] =
28 "@(#) Id: nslint.c 247 2009-10-14 17:54:05Z leres (LBL)";
31 * nslint - perform consistency checks on dns files
34 #include <sys/types.h>
36 #include <sys/socket.h>
38 #include <netinet/in.h>
40 #include <arpa/inet.h>
61 #ifdef HAVE_OS_PROTO_H
65 #define NSLINTBOOT "nslint.boot" /* default nslint.boot file */
66 #define NSLINTCONF "nslint.conf" /* default nslint.conf file */
68 /* Is the string just a dot by itself? */
69 #define CHECKDOT(p) (p[0] == '.' && p[1] == '\0')
71 /* Address (network order) */
75 struct in_addr _a_addr4
;
76 struct in6_addr _a_addr6
;
79 #define a_addr4 addr._a_addr4.s_addr
80 #define a_addr6 addr._a_addr6.s6_addr
86 struct in_addr _n_addr4
;
87 struct in6_addr _n_addr6
;
90 struct in_addr _n_mask4
;
91 struct in6_addr _n_mask6
;
94 #define n_addr4 addr._n_addr4.s_addr
95 #define n_mask4 mask._n_mask4.s_addr
96 #define n_addr6 addr._n_addr6.s6_addr
97 #define n_mask6 mask._n_mask6.s6_addr
101 char *host
; /* pointer to hostname */
102 struct addr addr
; /* ip address */
103 u_int ttl
; /* ttl of A records */
104 int records
; /* resource records seen */
105 int flags
; /* flags word */
108 /* Ignored zone struct */
110 char *zone
; /* zone name */
111 int len
; /* length of zone */
114 /* Resource records seen */
116 #define REC_AAAA 0x0002
117 #define REC_PTR 0x0004
118 #define REC_WKS 0x0008
119 #define REC_HINFO 0x0010
120 #define REC_MX 0x0020
121 #define REC_CNAME 0x0040
122 #define REC_NS 0x0080
123 #define REC_SOA 0x0100
124 #define REC_RP 0x0200
125 #define REC_TXT 0x0400
126 #define REC_SRV 0x0800
128 /* These aren't real records */
129 #define REC_OTHER 0x1000
130 #define REC_REF 0x2000
131 #define REC_UNKNOWN 0x4000
133 /* resource record types for parsing */
154 /* Test for records we want to map to REC_OTHER */
155 #define MASK_TEST_REC (REC_WKS | REC_HINFO | \
156 REC_MX | REC_SOA | REC_RP | REC_TXT | REC_SRV | REC_UNKNOWN)
158 /* Mask away records we don't care about in the final processing to REC_OTHER */
159 #define MASK_CHECK_REC \
160 (REC_A | REC_AAAA | REC_PTR | REC_CNAME | REC_REF | REC_OTHER)
162 /* Test for records we want to check for duplicate name detection */
163 #define MASK_TEST_DUP \
164 (REC_A | REC_AAAA | REC_HINFO | REC_CNAME)
167 #define FLG_SELFMX 0x001 /* mx record refers to self */
168 #define FLG_MXREF 0x002 /* this record referred to by a mx record */
169 #define FLG_SMTPWKS 0x004 /* saw wks with smtp/tcp */
170 #define FLG_ALLOWDUPA 0x008 /* allow duplicate a records */
172 /* doconf() and doboot() flags */
173 #define CONF_MUSTEXIST 0x001 /* fatal for files to not exist */
174 #define CONF_NOZONE 0x002 /* do not parse zone files */
176 /* Test for smtp problems */
177 #define MASK_TEST_SMTP \
178 (FLG_SELFMX | FLG_SMTPWKS)
180 #define ITEMSIZE (1 << 17) /* power of two */
182 struct item items
[ITEMSIZE
];
183 int itemcnt
; /* count of items */
185 /* Hostname string storage */
186 #define STRSIZE 8192; /* size to malloc when more space is needed */
187 char *strptr
; /* pointer to string pool */
188 int strsize
; /* size of space left in pool */
193 char *bootfile
= "/etc/namedb/named.boot";
194 char *conffile
= "/etc/namedb/named.conf";
196 char *bootfile
= "/etc/named.boot";
197 char *conffile
= "/etc/named.conf";
204 static struct network
*netlist
;
205 static u_int netlistsize
; /* size of array */
206 static u_int netlistcnt
; /* next free element */
208 char **protoserv
; /* valid protocol/service names */
213 static char inaddr
[] = ".in-addr.arpa.";
214 static char inaddr6
[] = ".ip6.arpa.";
216 /* XXX should be dynamic */
217 static struct ignoredzone ignoredzones
[10];
218 static int numignoredzones
= 0;
219 #define SIZEIGNOREDZONES (sizeof(ignoredzones) / sizeof(ignoredzones[0]))
223 #define SOA_REFRESH 1
226 #define SOA_MINIMUM 4
228 static u_int soaval
[5];
230 #define NSOAVAL (sizeof(soaval) / sizeof(soaval[0]))
233 void add_domain(char *, const char *);
234 const char *addr2str(struct addr
*);
235 int checkaddr(const char *);
236 int checkdots(const char *);
237 void checkdups(struct item
*, int);
238 int checkignoredzone(const char *);
239 int checkserv(const char *, char **p
);
240 int checkwks(FILE *, char *, int *, char **);
241 int cmpaddr(const void *, const void *);
242 int cmpitemaddr(const void *, const void *);
243 int cmpitemhost(const void *, const void *);
244 int cmpnetwork(const void *, const void *);
245 void doboot(const char *, int);
246 void doconf(const char *, int);
247 const char *extractaddr(const char *, struct addr
*);
248 const char *extractnetwork(const char *, struct network
*);
249 struct network
*findnetwork(struct addr
*);
250 void initprotoserv(void);
251 int main(int, char **);
252 int maskwidth(struct network
*);
253 const char *network2str(struct network
*);
255 const char *parsenetwork(const char *);
256 const char *parseptr(const char *, struct addr
*);
257 char *parsequoted(char *);
258 int parserrsig(const char *, char **);
259 int parsesoa(const char *, char **);
260 void process(const char *, const char *, const char *);
261 int rfc1034host(const char *, int);
262 enum rrtype
txt2rrtype(const char *);
263 int samesubnet(struct addr
*, struct addr
*, struct network
*);
264 void setmaskwidth(u_int w
, struct network
*);
265 int updateitem(const char *, struct addr
*, int, u_int
, int);
266 void usage(void) __attribute__((noreturn
));
269 extern int optind
, opterr
;
272 main(int argc
, char **argv
)
275 int op
, donamedboot
, donamedconf
;
277 if ((cp
= strrchr(argv
[0], '/')) != NULL
)
284 while ((op
= getopt(argc
, argv
, "b:c:B:C:d")) != -1)
314 if (optind
!= argc
|| (donamedboot
&& donamedconf
))
317 /* Find config file if not manually specified */
318 if (!donamedboot
&& !donamedconf
) {
319 if (access(conffile
, R_OK
) >= 0)
321 if (access(bootfile
, R_OK
) >= 0)
324 if (donamedboot
&& donamedconf
) {
326 "%s: nslint: both %s and %s exist; use -b or -c\n",
327 prog
, conffile
, bootfile
);
333 doboot(bootfile
, CONF_MUSTEXIST
| CONF_NOZONE
);
334 if (nslintboot
!= NULL
)
335 doboot(nslintboot
, CONF_MUSTEXIST
);
337 doboot(NSLINTBOOT
, 0);
338 doboot(bootfile
, CONF_MUSTEXIST
);
340 doconf(conffile
, CONF_MUSTEXIST
| CONF_NOZONE
);
341 if (nslintconf
!= NULL
)
342 doconf(nslintconf
, CONF_MUSTEXIST
);
344 doconf(NSLINTCONF
, 0);
345 doconf(conffile
, CONF_MUSTEXIST
);
348 /* Sort network list */
350 qsort(netlist
, netlistcnt
, sizeof(netlist
[0]), cmpnetwork
);
356 /* add domain if necessary */
358 add_domain(char *name
, const char *domain
)
362 /* Kill trailing white space and convert to lowercase */
363 for (cp
= name
; *cp
!= '\0' && !isspace(*cp
); ++cp
)
367 /* If necessary, append domain */
368 if (cp
>= name
&& *cp
++ != '.') {
371 (void)strcpy(cp
, domain
);
373 /* XXX should we insure a trailing dot? */
377 addr2str(struct addr
*ap
)
381 memset(&net
, 0, sizeof(net
));
382 net
.family
= ap
->family
;
383 switch (ap
->family
) {
386 net
.n_addr4
= ap
->a_addr4
;
387 setmaskwidth(32, &net
);
391 memmove(net
.n_addr6
, &ap
->a_addr6
, sizeof(ap
->a_addr6
));
392 setmaskwidth(128, &net
);
398 return (network2str(&net
));
402 * Returns true if name is really an ip address.
405 checkaddr(const char *name
)
409 return (inet_pton(AF_INET
, name
, (char *)&addr
));
413 * Returns true if name contains a dot but not a trailing dot.
414 * Special case: allow a single dot if the second part is not one
415 * of the 3 or 4 letter top level domains or is any 2 letter TLD
418 checkdots(const char *name
)
420 const char *cp
, *cp2
;
422 if ((cp
= strchr(name
, '.')) == NULL
)
424 cp2
= name
+ strlen(name
) - 1;
425 if (cp2
>= name
&& *cp2
== '.')
428 /* Return true of more than one dot*/
430 if (strchr(cp
, '.') != NULL
)
433 if (strlen(cp
) == 2 ||
434 strcasecmp(cp
, "gov") == 0 ||
435 strcasecmp(cp
, "edu") == 0 ||
436 strcasecmp(cp
, "com") == 0 ||
437 strcasecmp(cp
, "net") == 0 ||
438 strcasecmp(cp
, "org") == 0 ||
439 strcasecmp(cp
, "mil") == 0 ||
440 strcasecmp(cp
, "int") == 0 ||
441 strcasecmp(cp
, "nato") == 0 ||
442 strcasecmp(cp
, "arpa") == 0)
447 /* Records we use to detect duplicates */
448 static struct duprec
{
453 { REC_AAAA
, "aaaa" },
454 { REC_HINFO
, "hinfo" },
455 { REC_CNAME
, "cname" },
460 checkdups(struct item
*ip
, int records
)
464 records
&= (ip
->records
& MASK_TEST_DUP
);
467 for (dp
= duprec
; dp
->name
!= NULL
; ++dp
)
468 if ((records
& dp
->record
) != 0) {
470 fprintf(stderr
, "%s: multiple \"%s\" records for %s\n",
471 prog
, dp
->name
, ip
->host
);
472 records
&= ~dp
->record
;
475 fprintf(stderr
, "%s: checkdups: records not zero %s (0x%x)\n",
476 prog
, ip
->host
, records
);
479 /* Check for an "ignored zone" (usually dynamic dns) */
481 checkignoredzone(const char *name
)
486 if (len
> 1 && name
[len
- 1] == '.')
488 for (i
= 0; i
< numignoredzones
; ++i
) {
489 len2
= len
- ignoredzones
[i
].len
;
491 strncasecmp(name
+ len2
,
492 ignoredzones
[i
].zone
, len
- len2
) == 0)
499 checkserv(const char *serv
, char **p
)
501 for (; *p
!= NULL
; ++p
)
502 if (*serv
== **p
&& strcmp(serv
, *p
) == 0)
508 checkwks(FILE *f
, char *proto
, int *smtpp
, char **errstrp
)
511 char *cp
, *serv
, **p
;
512 static char errstr
[132];
516 if (!protoserv_init
) {
524 /* Terminate protocol */
526 while (!isspace(*cp
) && *cp
!= '\0')
544 if (fgets(buf
, sizeof(buf
), f
) == NULL
) {
545 *errstrp
= "mismatched parens";
553 /* Find end of service, converting to lowercase */
554 for (serv
= cp
; !isspace(*cp
) && *cp
!= '\0'; ++cp
)
559 if (sawparen
&& *cp
== ')') {
560 /* XXX should check for trailing junk */
564 (void)sprintf(psbuf
, "%s/%s", serv
, proto
);
566 if (*serv
== 's' && strcmp(psbuf
, "tcp/smtp") == 0)
569 for (p
= protoserv
; *p
!= NULL
; ++p
)
570 if (*psbuf
== **p
&& strcmp(psbuf
, *p
) == 0) {
574 sprintf(errstr
, "%s unknown", psbuf
);
584 cmpaddr(const void *arg1
, const void *arg2
)
587 const struct network
*n1
, *n2
;
589 n1
= (const struct network
*)arg1
;
590 n2
= (const struct network
*)arg2
;
592 /* IPv4 before IPv6 */
593 if (n1
->family
!= n2
->family
)
594 return ((n1
->family
== AF_INET
) ? -1 : 1);
596 switch (n1
->family
) {
600 if (ntohl(n1
->n_addr4
) < ntohl(n2
->n_addr4
))
602 else if (ntohl(n1
->n_addr4
) > ntohl(n2
->n_addr4
))
609 for (i
= 0; i
< 16; ++i
) {
610 if (ntohl(n1
->n_addr6
[i
]) < ntohl(n2
->n_addr6
[i
]))
612 if (ntohl(n1
->n_addr6
[i
]) > ntohl(n2
->n_addr6
[i
]))
623 cmpitemaddr(const void *arg1
, const void *arg2
)
625 struct item
*i1
, *i2
;
627 i1
= (struct item
*)arg1
;
628 i2
= (struct item
*)arg2
;
630 return (cmpaddr(&i1
->addr
, &i2
->addr
));
634 cmpitemhost(const void *arg1
, const void *arg2
)
636 struct item
*i1
, *i2
;
638 i1
= (struct item
*)arg1
;
639 i2
= (struct item
*)arg2
;
641 return (strcasecmp(i1
->host
, i1
->host
));
644 /* Sort by network number (use mask when networks are the same) */
646 cmpnetwork(const void *arg1
, const void *arg2
)
649 const struct network
*n1
, *n2
;
651 n1
= (const struct network
*)arg1
;
652 n2
= (const struct network
*)arg2
;
654 /* IPv4 before IPv6 */
655 if (n1
->family
!= n2
->family
)
656 return ((n1
->family
== AF_INET
) ? -1 : 1);
658 switch (n1
->family
) {
662 if (ntohl(n1
->n_addr4
) < ntohl(n2
->n_addr4
))
664 else if (ntohl(n1
->n_addr4
) > ntohl(n2
->n_addr4
))
668 if (ntohl(n1
->n_mask4
) < ntohl(n2
->n_mask4
))
670 else if (ntohl(n1
->n_mask4
) > ntohl(n2
->n_mask4
))
677 for (i
= 0; i
< 16; ++i
) {
678 if (ntohl(n1
->n_addr6
[i
]) < ntohl(n2
->n_addr6
[i
]))
680 if (ntohl(n1
->n_addr6
[i
]) > ntohl(n2
->n_addr6
[i
]))
686 for (i
= 0; i
< 16; ++i
) {
687 if (n1
->n_mask6
[i
] < n2
->n_mask6
[i
])
689 if (n1
->n_mask6
[i
] > n2
->n_mask6
[i
])
702 doboot(const char *file
, int flags
)
708 char buf
[1024], name
[128];
711 f
= fopen(file
, "r");
713 /* Not an error if it doesn't exist */
714 if ((flags
& CONF_MUSTEXIST
) == 0 && errno
== ENOENT
) {
717 "%s: doit: %s doesn't exist (ignoring)\n",
721 fprintf(stderr
, "%s: %s: %s\n", prog
, file
, strerror(errno
));
725 printf("%s: doit: opened %s\n", prog
, file
);
728 while (fgets(buf
, sizeof(buf
), f
) != NULL
) {
734 cp
= strchr(buf
, ';');
737 cp
= buf
+ strlen(buf
) - 1;
738 if (cp
>= buf
&& *cp
== '\n')
742 /* Eat leading whitespace */
746 /* Skip blank lines */
747 if (*cp
== '\n' || *cp
== '\0')
752 while (!isspace(*cp
) && *cp
!= '\0')
756 /* Find next keyword */
759 if (strcasecmp(cp2
, "directory") == 0) {
760 /* Terminate directory */
762 while (!isspace(*cp
) && *cp
!= '\0')
765 if (chdir(cp2
) < 0) {
767 fprintf(stderr
, "%s: can't chdir %s: %s\n",
768 prog
, cp2
, strerror(errno
));
774 if (strcasecmp(cp2
, "primary") == 0) {
775 /* Extract domain, converting to lowercase */
776 for (cp2
= name
; !isspace(*cp
) && *cp
!= '\0'; ++cp
)
778 *cp2
++ = tolower(*cp
);
781 /* Insure trailing dot */
782 if (cp2
> name
&& cp2
[-1] != '.')
790 /* Terminate directory */
792 while (!isspace(*cp
) && *cp
!= '\0')
796 /* Process it! (zone is the same as the domain) */
798 memset(soaval
, 0, sizeof(soaval
));
799 if ((flags
& CONF_NOZONE
) == 0)
800 process(cp2
, name
, name
);
803 if (strcasecmp(cp2
, "network") == 0) {
804 errstr
= parsenetwork(cp
);
805 if (errstr
!= NULL
) {
808 "%s: %s:%d: bad network: %s\n",
809 prog
, file
, n
, errstr
);
813 if (strcasecmp(cp2
, "include") == 0) {
814 /* Terminate include file */
816 while (!isspace(*cp
) && *cp
!= '\0')
822 /* Eat any other options */
828 doconf(const char *file
, int flags
)
830 int n
, fd
, cc
, i
, depth
;
831 char *cp
, *cp2
, *buf
;
833 char *name
, *zonename
, *filename
, *typename
;
834 int namelen
, zonenamelen
, filenamelen
, typenamelen
;
836 char zone
[128], includefile
[256];
839 fd
= open(file
, O_RDONLY
, 0);
841 /* Not an error if it doesn't exist */
842 if ((flags
& CONF_MUSTEXIST
) == 0 && errno
== ENOENT
) {
845 "%s: doconf: %s doesn't exist (ignoring)\n",
849 fprintf(stderr
, "%s: %s: %s\n", prog
, file
, strerror(errno
));
853 printf("%s: doconf: opened %s\n", prog
, file
);
855 if (fstat(fd
, &sbuf
) < 0) {
856 fprintf(stderr
, "%s: fstat(%s) %s\n",
857 prog
, file
, strerror(errno
));
860 buf
= (char *)malloc(sbuf
.st_size
+ 1);
862 fprintf(stderr
, "%s: malloc: %s\n", prog
, strerror(errno
));
866 /* Slurp entire config file */
870 cc
= read(fd
, cp
, n
);
872 fprintf(stderr
, "%s: read(%s) %s\n",
873 prog
, file
, strerror(errno
));
878 } while (cc
!= 0 && cc
< n
);
881 #define EATWHITESPACE \
882 while (isspace(*cp)) { \
888 /* Handle both to-end-of-line and C style comments */
889 #define EATCOMMENTS \
898 while (*cp != '\n' && *cp != '\0') \
901 else if (strncmp(cp, "//", 2) == 0) { \
904 while (*cp != '\n' && *cp != '\0') \
907 else if (strncmp(cp, "/*", 2) == 0) { \
909 for (cp += 2; *cp != '\0'; ++cp) { \
912 else if (strncmp(cp, "*/", 2) == 0) { \
918 } while (sawcomment); \
921 #define GETNAME(name, len) \
925 while (!isspace(*cp) && *cp != ';' && *cp != '\0') { \
931 #define GETQUOTEDNAME(name, len) \
935 fprintf(stderr, "%s: %s:%d missing left quote\n", \
941 while (*cp != '"' && *cp != '\n' && *cp != '\0') { \
947 fprintf(stderr, "%s: %s:%d missing right quote\n", \
953 /* Eat everything to the next semicolon, perhaps eating matching qbraces */
954 #define EATSEMICOLON \
957 while (*cp != '\0') { \
979 /* Eat everything to the next left qbrace */
980 #define EATSLEFTBRACE \
981 while (*cp != '\0') { \
993 while (*cp
!= '\0') {
997 GETNAME(name
, namelen
)
1000 fprintf(stderr
, "%s: %s:%d garbage char '%c' (1)\n",
1001 prog
, file
, n
, *cp
);
1006 if (strncasecmp(name
, "options", namelen
) == 0) {
1011 "%s: %s:%d missing left qbrace in options\n",
1016 while (*cp
!= '}' && *cp
!= '\0') {
1018 GETNAME(name
, namelen
)
1022 "%s: %s:%d garbage char '%c' (2)\n",
1023 prog
, file
, n
, *cp
);
1028 /* If not the "directory" option, just eat it */
1029 if (strncasecmp(name
, "directory",
1032 GETQUOTEDNAME(cp2
, i
)
1034 if (chdir(cp2
) < 0) {
1037 "%s: %s:.%d can't chdir %s: %s\n",
1052 "%s: %s:%d missing options semi\n",
1058 if (strncasecmp(name
, "zone", namelen
) == 0) {
1060 GETQUOTEDNAME(zonename
, zonenamelen
)
1066 if (strncasecmp(cp
, "in", 2) == 0) {
1069 } else if (strncasecmp(cp
, "chaos", 5) == 0) {
1073 if (*cp
!= '{') { /* } */
1076 "%s: %s:%d missing left qbrace in zone\n",
1082 while (*cp
!= '\0') {
1086 } else if (*cp
== '}') {
1092 GETNAME(name
, namelen
)
1096 "%s: %s:%d garbage char '%c' (3)\n",
1097 prog
, file
, n
, *cp
);
1101 if (strncasecmp(name
, "type",
1104 GETNAME(typename
, typenamelen
)
1108 "%s: %s:%d garbage char '%c' (4)\n",
1109 prog
, file
, n
, *cp
);
1113 } else if (strncasecmp(name
, "file",
1116 GETQUOTEDNAME(filename
, filenamelen
)
1118 /* Just ignore keywords we don't understand */
1126 "%s: %s:%d missing zone right qbrace\n",
1133 "%s: %s:%d missing zone semi\n",
1138 /* If we got something interesting, process it */
1139 if (typenamelen
== 0) {
1141 fprintf(stderr
, "%s: missing zone type!\n",
1145 if (strncasecmp(typename
, "master", typenamelen
) == 0) {
1146 if (filenamelen
== 0) {
1149 "%s: missing zone filename!\n",
1153 strncpy(zone
, zonename
, zonenamelen
);
1154 zone
[zonenamelen
] = '\0';
1155 for (cp2
= zone
; *cp2
!= '\0'; ++cp2
)
1157 *cp2
= tolower(*cp2
);
1158 /* Insure trailing dot */
1159 if (cp2
> zone
&& cp2
[-1] != '.') {
1163 filename
[filenamelen
] = '\0';
1165 memset(soaval
, 0, sizeof(soaval
));
1166 if ((flags
& CONF_NOZONE
) == 0)
1167 process(filename
, zone
, zone
);
1171 if (strncasecmp(name
, "nslint", namelen
) == 0) {
1176 "%s: %s:%d missing left qbrace in nslint\n",
1182 while (*cp
!= '}' && *cp
!= '\0') {
1184 GETNAME(name
, namelen
)
1185 if (strncasecmp(name
, "network",
1188 GETQUOTEDNAME(cp2
, i
)
1191 p
= parsenetwork(cp2
);
1195 "%s: %s:%d: bad network: %s\n",
1198 } else if (strncasecmp(name
, "ignorezone",
1201 GETQUOTEDNAME(cp2
, i
)
1203 if (numignoredzones
+ 1 <
1204 sizeof(ignoredzones
) /
1205 sizeof(ignoredzones
[0])) {
1206 ignoredzones
[numignoredzones
].zone
=
1208 if (ignoredzones
[numignoredzones
].zone
!= NULL
) {
1209 ignoredzones
[numignoredzones
].len
= strlen(cp2
);
1216 "%s: unknown nslint \"%.*s\"\n",
1217 prog
, namelen
, name
);
1227 "%s: %s:%d: missing nslint semi\n",
1233 if (strncasecmp(name
, "include", namelen
) == 0) {
1235 GETQUOTEDNAME(filename
, filenamelen
)
1236 strncpy(includefile
, filename
, filenamelen
);
1237 includefile
[filenamelen
] = '\0';
1238 doconf(includefile
, 1);
1242 if (strncasecmp(name
, "view", namelen
) == 0) {
1247 /* Skip over statements we don't understand */
1256 extractaddr(const char *str
, struct addr
*ap
)
1259 memset(ap
, 0, sizeof(*ap
));
1261 /* Let's see what we've got here */
1262 if (strchr(str
, '.') != NULL
) {
1263 ap
->family
= AF_INET
;
1264 } else if (strchr(str
, ':') != NULL
) {
1265 ap
->family
= AF_INET6
;
1267 return ("unrecognized address type");
1269 switch (ap
->family
) {
1272 if (!inet_pton(ap
->family
, str
, &ap
->a_addr4
))
1273 return ("cannot parse IPv4 address");
1278 if (!inet_pton(ap
->family
, str
, &ap
->a_addr6
))
1279 return ("cannot parse IPv6 address");
1290 extractnetwork(const char *str
, struct network
*np
)
1298 memset(np
, 0, sizeof(*np
));
1300 /* Let's see what we've got here */
1301 if (strchr(str
, '.') != NULL
) {
1302 np
->family
= AF_INET
;
1304 } else if (strchr(str
, ':') != NULL
) {
1305 np
->family
= AF_INET6
;
1308 return ("unrecognized address type");
1310 p
= strchr(str
, '/');
1312 /* Mask length was specified */
1313 strncpy(temp
, str
, sizeof(temp
));
1314 temp
[sizeof(temp
) - 1] = '\0';
1315 cp
= strchr(temp
, '/');
1320 w
= strtol(cp
, &ep
, 10);
1322 return ("garbage following mask width");
1326 switch (np
->family
) {
1329 if (!inet_pton(np
->family
, str
, &np
->n_addr4
))
1330 return ("cannot parse IPv4 address");
1333 return ("mask length must be <= 32");
1334 setmaskwidth(w
, np
);
1336 if ((np
->n_addr4
& ~np
->n_mask4
) != 0)
1337 return ("non-network bits set in addr");
1340 if ((ntohl(np
->n_addr4
) & 0xff000000) == 0)
1341 return ("high octet must be non-zero");
1346 if (!inet_pton(np
->family
, str
, &np
->n_addr6
))
1347 return ("cannot parse IPv6 address");
1349 return ("mask length must be <= 128");
1350 setmaskwidth(w
, np
);
1352 for (i
= 0; i
< 16; ++i
) {
1353 if ((np
->n_addr6
[i
] & ~np
->n_mask6
[i
]) != 0)
1354 return ("non-network bits set in addr");
1366 findnetwork(struct addr
*ap
)
1371 switch (ap
->family
) {
1374 for (i
= 0, np
= netlist
; i
< netlistcnt
; ++i
, ++np
)
1375 if ((ap
->a_addr4
& np
->n_mask4
) == np
->n_addr4
)
1380 for (i
= 0, np
= netlist
; i
< netlistcnt
; ++i
, ++np
) {
1381 for (j
= 0; j
< sizeof(ap
->a_addr6
); ++j
) {
1382 if ((ap
->a_addr6
[j
] & np
->n_mask6
[j
]) !=
1386 if (j
>= sizeof(ap
->a_addr6
))
1404 protoserv_len
= 256;
1405 protoserv
= (char **)malloc(protoserv_len
* sizeof(*protoserv
));
1406 if (protoserv
== NULL
) {
1407 fprintf(stderr
, "%s: nslint: malloc: %s\n",
1408 prog
, strerror(errno
));
1412 while ((sp
= getservent()) != NULL
) {
1413 (void)sprintf(psbuf
, "%s/%s", sp
->s_name
, sp
->s_proto
);
1415 /* Convert to lowercase */
1416 for (cp
= psbuf
; *cp
!= '\0'; ++cp
)
1420 if (protoserv_last
+ 1 >= protoserv_len
) {
1421 protoserv_len
<<= 1;
1422 protoserv
= realloc(protoserv
,
1423 protoserv_len
* sizeof(*protoserv
));
1424 if (protoserv
== NULL
) {
1425 fprintf(stderr
, "%s: nslint: realloc: %s\n",
1426 prog
, strerror(errno
));
1430 protoserv
[protoserv_last
] = savestr(psbuf
);
1433 protoserv
[protoserv_last
] = NULL
;
1437 maskwidth(struct network
*np
)
1443 /* Work backwards until we find a set bit */
1444 switch (np
->family
) {
1447 m
= ntohl(np
->n_mask4
);
1448 for (w
= 32; w
> 0; --w
) {
1449 tm
= 0xffffffff << (32 - w
);
1457 for (j
= 15; j
>= 0; --j
) {
1459 for (i
= 8; i
> 0; --w
, --i
) {
1460 tm
= (0xff << (8 - i
)) & 0xff;
1474 network2str(struct network
*np
)
1479 static char buf
[128];
1482 switch (np
->family
) {
1485 if (inet_ntop(np
->family
, &np
->n_addr4
,
1486 buf
, sizeof(buf
)) == NULL
) {
1487 fprintf(stderr
, "network2str: v4 botch");
1495 if (inet_ntop(np
->family
, &np
->n_addr6
,
1496 buf
, sizeof(buf
)) == NULL
) {
1497 fprintf(stderr
, "network2str: v6 botch");
1508 /* Append address mask width */
1512 size
= sizeof(buf
) - len
;
1513 (void)snprintf(cp
, size
, "/%d", w
);
1520 int n
, records
, flags
;
1521 struct item
*ip
, *lastaip
, **ipp
, **itemlist
;
1522 struct addr addr
, lastaddr
;
1525 itemlist
= (struct item
**)calloc(itemcnt
, sizeof(*ipp
));
1526 if (itemlist
== NULL
) {
1527 fprintf(stderr
, "%s: nslint: calloc: %s\n",
1528 prog
, strerror(errno
));
1532 for (n
= 0, ip
= items
; n
< ITEMSIZE
; ++n
, ++ip
) {
1533 if (ip
->host
== NULL
)
1535 /* Save entries with addresses for later check */
1536 if (ip
->addr
.family
!= 0)
1542 printf("%s\t%s\t0x%x\t0x%x\n",
1543 ip
->host
, addr2str(&ip
->addr
),
1544 ip
->records
, ip
->flags
);
1547 /* Check for illegal hostnames (rfc1034) */
1548 if (rfc1034host(ip
->host
, ip
->records
))
1551 /* Check for missing ptr records (ok if also an ns record) */
1552 records
= ip
->records
& MASK_CHECK_REC
;
1553 if ((ip
->records
& MASK_TEST_REC
) != 0)
1554 records
|= REC_OTHER
;
1557 case REC_A
| REC_OTHER
| REC_PTR
| REC_REF
:
1558 case REC_A
| REC_OTHER
| REC_PTR
:
1559 case REC_A
| REC_PTR
| REC_REF
:
1560 case REC_A
| REC_PTR
:
1561 case REC_AAAA
| REC_OTHER
| REC_PTR
| REC_REF
:
1562 case REC_AAAA
| REC_OTHER
| REC_PTR
:
1563 case REC_AAAA
| REC_PTR
| REC_REF
:
1564 case REC_AAAA
| REC_PTR
:
1566 /* These are O.K. */
1569 case REC_CNAME
| REC_REF
:
1571 fprintf(stderr
, "%s: \"cname\" referenced by other"
1572 " \"cname\" or \"mx\": %s\n", prog
, ip
->host
);
1575 case REC_OTHER
| REC_REF
:
1578 * This is only an error if there is an address
1579 * associated with the hostname; this means
1580 * there was a wks entry with bogus address.
1581 * Otherwise, we have an mx or hinfo.
1583 * XXX ignore localhost for now
1584 * (use flag to indicate loopback?)
1586 if (ip
->addr
.family
== AF_INET
&&
1587 ip
->addr
.a_addr4
!= htonl(INADDR_LOOPBACK
)) {
1590 "%s: \"wks\" without \"a\" and \"ptr\": %s -> %s\n",
1591 prog
, ip
->host
, addr2str(&ip
->addr
));
1596 if (!checkignoredzone(ip
->host
)) {
1598 fprintf(stderr
, "%s: Name referenced without"
1599 " other records: %s\n", prog
, ip
->host
);
1603 case REC_A
| REC_OTHER
| REC_REF
:
1604 case REC_A
| REC_OTHER
:
1605 case REC_A
| REC_REF
:
1607 case REC_AAAA
| REC_OTHER
| REC_REF
:
1608 case REC_AAAA
| REC_OTHER
:
1609 case REC_AAAA
| REC_REF
:
1612 fprintf(stderr
, "%s: Missing \"ptr\": %s -> %s\n",
1613 prog
, ip
->host
, addr2str(&ip
->addr
));
1616 case REC_OTHER
| REC_PTR
| REC_REF
:
1617 case REC_OTHER
| REC_PTR
:
1618 case REC_PTR
| REC_REF
:
1621 fprintf(stderr
, "%s: Missing \"a\": %s -> %s\n",
1622 prog
, ip
->host
, addr2str(&ip
->addr
));
1625 case REC_A
| REC_CNAME
| REC_OTHER
| REC_PTR
| REC_REF
:
1626 case REC_A
| REC_CNAME
| REC_OTHER
| REC_PTR
:
1627 case REC_A
| REC_CNAME
| REC_OTHER
| REC_REF
:
1628 case REC_A
| REC_CNAME
| REC_OTHER
:
1629 case REC_A
| REC_CNAME
| REC_PTR
| REC_REF
:
1630 case REC_A
| REC_CNAME
| REC_PTR
:
1631 case REC_A
| REC_CNAME
| REC_REF
:
1632 case REC_A
| REC_CNAME
:
1633 case REC_AAAA
| REC_CNAME
| REC_OTHER
| REC_PTR
| REC_REF
:
1634 case REC_AAAA
| REC_CNAME
| REC_OTHER
| REC_PTR
:
1635 case REC_AAAA
| REC_CNAME
| REC_OTHER
| REC_REF
:
1636 case REC_AAAA
| REC_CNAME
| REC_OTHER
:
1637 case REC_AAAA
| REC_CNAME
| REC_PTR
| REC_REF
:
1638 case REC_AAAA
| REC_CNAME
| REC_PTR
:
1639 case REC_AAAA
| REC_CNAME
| REC_REF
:
1640 case REC_AAAA
| REC_CNAME
:
1641 case REC_CNAME
| REC_OTHER
| REC_PTR
| REC_REF
:
1642 case REC_CNAME
| REC_OTHER
| REC_PTR
:
1643 case REC_CNAME
| REC_OTHER
| REC_REF
:
1644 case REC_CNAME
| REC_OTHER
:
1645 case REC_CNAME
| REC_PTR
| REC_REF
:
1646 case REC_CNAME
| REC_PTR
:
1648 fprintf(stderr
, "%s: \"cname\" %s has other records\n",
1653 /* Second level test */
1654 if ((ip
->records
& ~(REC_NS
| REC_TXT
)) == 0)
1656 /* Fall through... */
1661 "%s: records == 0x%x: can't happen (%s 0x%x)\n",
1662 prog
, records
, ip
->host
, ip
->records
);
1666 /* Check for smtp problems */
1667 flags
= ip
->flags
& MASK_TEST_SMTP
;
1669 if ((flags
& FLG_SELFMX
) != 0 &&
1670 (ip
->records
& (REC_A
| REC_AAAA
)) == 0) {
1673 "%s: Self \"mx\" for %s missing"
1674 " \"a\" or \"aaaa\" record\n",
1681 case FLG_SELFMX
| FLG_SMTPWKS
:
1682 /* These are O.K. */
1686 if ((ip
->records
& REC_WKS
) != 0) {
1689 "%s: smtp/tcp missing from \"wks\": %s\n",
1697 "%s: Saw smtp/tcp without self \"mx\": %s\n",
1704 "%s: flags == 0x%x: can't happen (%s)\n",
1705 prog
, flags
, ip
->host
);
1708 /* Check for chained MX records */
1709 if ((ip
->flags
& (FLG_SELFMX
| FLG_MXREF
)) == FLG_MXREF
&&
1710 (ip
->records
& REC_MX
) != 0) {
1712 fprintf(stderr
, "%s: \"mx\" referenced by other"
1713 " \"mx\" record: %s\n", prog
, ip
->host
);
1717 /* Check for doubly booked addresses */
1719 qsort(itemlist
, n
, sizeof(itemlist
[0]), cmpaddr
);
1720 memset(&lastaddr
, 0, sizeof(lastaddr
));
1722 for (ipp
= itemlist
; n
> 0; ++ipp
, --n
) {
1723 addr
= (*ipp
)->addr
;
1724 if (cmpaddr(&lastaddr
, &addr
) == 0 &&
1725 ((*ipp
)->flags
& FLG_ALLOWDUPA
) == 0 &&
1726 (ip
->flags
& FLG_ALLOWDUPA
) == 0) {
1728 fprintf(stderr
, "%s: %s in use by %s and %s\n",
1729 prog
, addr2str(&addr
), (*ipp
)->host
, ip
->host
);
1731 memmove(&lastaddr
, &addr
, sizeof(addr
));
1735 /* Check for hosts with multiple addresses on the same subnet */
1737 qsort(itemlist
, n
, sizeof(itemlist
[0]), cmpitemhost
);
1738 if (netlistcnt
> 0) {
1741 for (ipp
= itemlist
; n
> 0; ++ipp
, --n
) {
1743 if ((ip
->records
& (REC_A
| REC_AAAA
)) == 0 ||
1744 (ip
->flags
& FLG_ALLOWDUPA
) != 0)
1746 if (lastaip
!= NULL
&&
1747 strcasecmp(ip
->host
, lastaip
->host
) == 0) {
1748 np
= findnetwork(&ip
->addr
);
1752 "%s: Can't find subnet mask"
1755 addr2str(&ip
->addr
));
1756 } else if (samesubnet(&lastaip
->addr
,
1760 "%s: Multiple \"a\" records for %s on subnet %s",
1763 fprintf(stderr
, "\n\t(%s",
1764 addr2str(&lastaip
->addr
));
1765 fprintf(stderr
, " and %s)\n",
1766 addr2str(&ip
->addr
));
1774 printf("%s: %d/%d items used, %d error%s\n", prog
, itemcnt
,
1775 ITEMSIZE
, errors
, errors
== 1 ? "" : "s");
1779 parsenetwork(const char *cp
)
1784 while (isspace(*cp
))
1787 p
= extractnetwork(cp
, &net
);
1791 while (isspace(*cp
))
1794 /* Make sure there's room */
1795 if (netlistsize
<= netlistcnt
) {
1796 if (netlistsize
== 0) {
1798 netlist
= (struct network
*)
1799 malloc(netlistsize
* sizeof(*netlist
));
1802 netlist
= (struct network
*)
1803 realloc(netlist
, netlistsize
* sizeof(*netlist
));
1805 if (netlist
== NULL
) {
1807 "%s: parsenetwork: malloc/realloc: %s\n",
1808 prog
, strerror(errno
));
1814 memmove(netlist
+ netlistcnt
, &net
, sizeof(net
));
1821 parseptr(const char *str
, struct addr
*ap
)
1829 memset(ap
, 0, sizeof(*ap
));
1833 p
= str
+ strlen(str
) - sizeof(inaddr
) + 1;
1834 if (p
>= str
&& strcasecmp(p
, inaddr
) == 0) {
1835 ap
->family
= AF_INET
;
1840 p
= str
+ strlen(str
) - sizeof(inaddr6
) + 1;
1841 if (p
>= str
&& strcasecmp(p
, inaddr6
) == 0) {
1842 ap
->family
= AF_INET6
;
1849 return ("Not a IPv4 or IPv6 \"ptr\" record");
1851 up
= (u_char
*)&ap
->addr
;
1852 for (i
= 0; i
< n
; ++i
) {
1853 /* Back up to previous dot or beginning of string */
1854 while (p
> str
&& p
[-1] != '.')
1856 v
= strtoul(p
, &cp
, base
);
1860 return ("Octet larger than 8 bits");
1863 return ("Octet larger than 4 bits");
1865 return ("Junk in \"ptr\" record");
1867 /* Back up over dot */
1871 /* Back up to previous dot or beginning of string */
1872 while (p
> str
&& p
[-1] != '.')
1874 v2
= strtoul(p
, &cp
, base
);
1876 return ("Octet larger than 4 bits");
1878 return ("Junk in \"ptr\" record");
1882 return ("Junk in \"ptr\" record");
1886 /* Back up over dot */
1893 return ("Too many octets in \"ptr\" record");
1895 return ("Not enough octets in \"ptr\" record");
1900 /* Returns a pointer after the next token or quoted string, else NULL */
1902 parsequoted(char *cp
)
1907 while (*cp
!= '"' && *cp
!= '\0')
1913 while (!isspace(*cp
) && *cp
!= '\0')
1919 /* Return true when done */
1921 parserrsig(const char *str
, char **errstrp
)
1925 /* XXX just look for closing paren */
1926 cp
= str
+ strlen(str
) - 1;
1933 /* Return true when done */
1935 parsesoa(const char *cp
, char **errstrp
)
1938 static char errstr
[132];
1940 /* Eat leading whitespace */
1941 while (isspace(*cp
))
1944 /* Find opening paren */
1946 cp
= strchr(cp
, '(');
1950 while (isspace(*cp
))
1955 /* Grab any numbers we find */
1956 garbage
= "leading garbage";
1957 while (isdigit(*cp
) && nsoaval
< NSOAVAL
) {
1958 soaval
[nsoaval
] = atoi(cp
);
1961 } while (isdigit(*cp
));
1962 if (nsoaval
== SOA_SERIAL
&& *cp
== '.' && isdigit(cp
[1])) {
1965 } while (isdigit(*cp
));
1973 soaval
[nsoaval
] *= 7;
1977 soaval
[nsoaval
] *= 24;
1981 soaval
[nsoaval
] *= 60;
1985 soaval
[nsoaval
] *= 60;
1996 while (isspace(*cp
))
1998 garbage
= "trailing garbage";
2002 /* If we're done, do some sanity checks */
2003 if (nsoaval
>= NSOAVAL
&& *cp
== ')') {
2007 else if (soaval
[SOA_EXPIRE
] <
2008 soaval
[SOA_REFRESH
] + 10 * soaval
[SOA_RETRY
]) {
2009 (void)sprintf(errstr
,
2010 "expire less than refresh + 10 * retry (%u < %u + 10 * %u)",
2012 soaval
[SOA_REFRESH
],
2015 } else if (soaval
[SOA_REFRESH
] < 2 * soaval
[SOA_RETRY
]) {
2016 (void)sprintf(errstr
,
2017 "refresh less than 2 * retry (%u < 2 * %u)",
2018 soaval
[SOA_REFRESH
],
2034 process(const char *file
, const char *domain
, const char *zone
)
2037 char ch
, *cp
, *cp2
, *cp3
, *rtype
;
2039 int n
, sawsoa
, sawrrsig
, flags
, i
;
2044 // struct network *net;
2046 char buf
[2048], name
[256], lastname
[256], odomain
[256];
2048 const char *addrfmt
=
2049 "%s: %s/%s:%d \"%s\" target is an ip address: %s\n";
2050 const char *dotfmt
=
2051 "%s: %s/%s:%d \"%s\" target missing trailing dot: %s\n";
2053 /* Check for an "ignored zone" (usually dynamic dns) */
2054 if (checkignoredzone(zone
))
2057 f
= fopen(file
, "r");
2059 fprintf(stderr
, "%s: %s/%s: %s\n",
2060 prog
, cwd
, file
, strerror(errno
));
2065 printf("%s: process: opened %s/%s\n", prog
, cwd
, file
);
2075 while (fgets(buf
, sizeof(buf
), f
) != NULL
) {
2078 while (*cp
!= '\0') {
2079 /* Handle quoted strings (but don't report errors) */
2082 while (*cp
!= '"' && *cp
!= '\n' && *cp
!= '\0')
2086 if (*cp
== '\n' || *cp
== ';')
2092 /* Nuke trailing white space */
2093 while (cp
>= buf
&& isspace(*cp
))
2100 /* Handle multi-line soa records */
2103 if (parsesoa(cp
, &errstr
))
2105 if (errstr
!= NULL
) {
2108 "%s: %s/%s:%d Bad \"soa\" record (%s)\n",
2109 prog
, cwd
, file
, n
, errstr
);
2114 /* Handle multi-line rrsig records */
2117 if (parserrsig(cp
, &errstr
))
2119 if (errstr
!= NULL
) {
2122 "%s: %s/%s:%d Bad \"rrsig\" record (%s)\n",
2123 prog
, cwd
, file
, n
, errstr
);
2129 printf(">%s<\n", cp
);
2133 /* Same name as last record */
2134 if (lastname
[0] == '\0') {
2137 "%s: %s/%s:%d No default name\n",
2138 prog
, cwd
, file
, n
);
2141 (void)strcpy(name
, lastname
);
2143 /* Extract name, converting to lowercase */
2144 for (cp2
= name
; !isspace(*cp
) && *cp
!= '\0'; ++cp
)
2146 *cp2
++ = tolower(*cp
);
2151 /* Check for domain shorthand */
2152 if (name
[0] == '@' && name
[1] == '\0')
2153 (void)strcpy(name
, domain
);
2156 /* Find next token */
2157 while (isspace(*cp
))
2160 /* Handle includes (gag) */
2161 if (name
[0] == '$' && strcasecmp(name
, "$include") == 0) {
2162 /* Extract filename */
2164 while (!isspace(*cp
) && *cp
!= '\0')
2168 /* Look for optional domain */
2169 while (isspace(*cp
))
2172 process(name
, domain
, zone
);
2175 /* Convert optional domain to lowercase */
2176 for (; !isspace(*cp
) && *cp
!= '\0'; ++cp
)
2180 process(name
, cp2
, cp2
);
2185 /* Handle $origin */
2186 if (name
[0] == '$' && strcasecmp(name
, "$origin") == 0) {
2187 /* Extract domain, converting to lowercase */
2188 for (cp2
= odomain
; !isspace(*cp
) && *cp
!= '\0'; ++cp
)
2190 *cp2
++ = tolower(*cp
);
2200 if (name
[0] == '$' && strcasecmp(name
, "$ttl") == 0) {
2202 while (isdigit(*cp
))
2207 if (strchr("wdhms", ch
) != NULL
)
2209 while (isspace(*cp
))
2214 "%s: %s/%s:%d Bad $ttl \"%s\"\n",
2215 prog
, cwd
, file
, n
, cp2
);
2217 (void)strcpy(name
, lastname
);
2221 /* Parse ttl or use default */
2226 } while (isdigit(*cp
));
2257 if (!isspace(*cp
)) {
2259 fprintf(stderr
, "%s: %s/%s:%d Bad ttl\n",
2260 prog
, cwd
, file
, n
);
2264 /* Find next token */
2266 while (isspace(*cp
))
2269 ttl
= soaval
[SOA_MINIMUM
];
2271 /* Eat optional "in" */
2272 if ((cp
[0] == 'i' || cp
[0] == 'I') &&
2273 (cp
[1] == 'n' || cp
[1] == 'N') && isspace(cp
[2])) {
2274 /* Find next token */
2276 while (isspace(*cp
))
2278 } else if ((cp
[0] == 'c' || cp
[0] == 'C') &&
2279 isspace(cp
[5]) && strncasecmp(cp
, "chaos", 5) == 0) {
2280 /* Find next token */
2282 while (isspace(*cp
))
2286 /* Find end of record type, converting to lowercase */
2288 for (rtype
= cp
; !isspace(*cp
) && *cp
!= '\0'; ++cp
)
2293 /* Find "the rest" */
2294 while (isspace(*cp
))
2297 /* Check for non-ptr names with dots but no trailing dot */
2298 if (!isdigit(*name
) &&
2299 checkdots(name
) && strcmp(domain
, ".") != 0) {
2302 "%s: %s/%s:%d \"%s\" name missing trailing dot: %s\n",
2303 prog
, cwd
, file
, n
, rtype
, name
);
2306 /* Check for FQDNs outside the zone */
2307 cp2
= name
+ strlen(name
) - 1;
2308 if (cp2
>= name
&& *cp2
== '.' && strchr(name
, '.') != NULL
) {
2309 cp2
= name
+ strlen(name
) - strlen(zone
);
2310 if (cp2
>= name
&& strcasecmp(cp2
, zone
) != 0) {
2313 "%s: %s/%s:%d \"%s\" outside zone %s\n",
2314 prog
, cwd
, file
, n
, name
, zone
);
2318 rrtype
= txt2rrtype(rtype
);
2322 /* Handle "a" record */
2323 add_domain(name
, domain
);
2324 p
= extractaddr(cp
, ap
);
2327 cp2
= cp
+ strlen(cp
) - 1;
2328 if (cp2
>= cp
&& *cp2
== '\n')
2331 "%s: %s/%s:%d Bad \"a\" record ip addr \"%s\"\n",
2332 prog
, cwd
, file
, n
, cp
);
2335 if (ap
->family
!= AF_INET
) {
2337 cp2
= cp
+ strlen(cp
) - 1;
2338 if (cp2
>= cp
&& *cp2
== '\n')
2341 "%s: %s/%s:%d \"a\"record not AF_INET \"%s\"\n",
2342 prog
, cwd
, file
, n
, cp
);
2345 errors
+= updateitem(name
, ap
, REC_A
, ttl
, 0);
2349 /* Handle "aaaa" record */
2350 add_domain(name
, domain
);
2351 p
= extractaddr(cp
, ap
);
2354 cp2
= cp
+ strlen(cp
) - 1;
2355 if (cp2
>= cp
&& *cp2
== '\n')
2358 "%s: %s/%s:%d Bad \"aaaa\" record ip addr \"%s\"\n",
2359 prog
, cwd
, file
, n
, cp
);
2362 if (ap
->family
!= AF_INET6
) {
2364 cp2
= cp
+ strlen(cp
) - 1;
2365 if (cp2
>= cp
&& *cp2
== '\n')
2368 "%s: %s/%s:%d \"aaaa\"record not AF_INET6 \"%s\"\n",
2369 prog
, cwd
, file
, n
, cp
);
2372 errors
+= updateitem(name
, ap
, REC_AAAA
, ttl
, 0);
2376 /* Handle "ptr" record */
2377 add_domain(name
, domain
);
2378 if (strcmp(cp
, "@") == 0)
2379 (void)strcpy(cp
, zone
);
2380 if (checkdots(cp
)) {
2383 checkaddr(cp
) ? addrfmt
: dotfmt
,
2384 prog
, cwd
, file
, n
, rtype
, cp
);
2386 add_domain(cp
, domain
);
2387 p
= parseptr(name
, ap
);
2391 "%s: %s/%s:%d Bad \"ptr\" record (%s) ip addr \"%s\"\n",
2392 prog
, cwd
, file
, n
, p
, name
);
2395 errors
+= updateitem(cp
, ap
, REC_PTR
, 0, 0);
2399 /* Handle "soa" record */
2400 if (!CHECKDOT(name
)) {
2401 add_domain(name
, domain
);
2402 errors
+= updateitem(name
, NULL
, REC_SOA
, 0, 0);
2405 if (!parsesoa(cp
, &errstr
))
2407 if (errstr
!= NULL
) {
2410 "%s: %s/%s:%d Bad \"soa\" record (%s)\n",
2411 prog
, cwd
, file
, n
, errstr
);
2417 /* Handle "wks" record */
2418 p
= extractaddr(cp
, ap
);
2422 while (!isspace(*cp2
) && *cp2
!= '\0')
2426 "%s: %s/%s:%d Bad \"wks\" record ip addr \"%s\"\n",
2427 prog
, cwd
, file
, n
, cp
);
2430 /* Step over ip address */
2431 while (*cp
== '.' || isdigit(*cp
))
2433 while (isspace(*cp
))
2435 /* Make sure services are legit */
2437 n
+= checkwks(f
, cp
, &smtp
, &errstr
);
2438 if (errstr
!= NULL
) {
2441 "%s: %s/%s:%d Bad \"wks\" record (%s)\n",
2442 prog
, cwd
, file
, n
, errstr
);
2445 add_domain(name
, domain
);
2446 errors
+= updateitem(name
, ap
, REC_WKS
,
2447 0, smtp
? FLG_SMTPWKS
: 0);
2448 /* XXX check to see if ip address records exists? */
2452 /* Handle "hinfo" record */
2453 add_domain(name
, domain
);
2454 errors
+= updateitem(name
, NULL
, REC_HINFO
, 0, 0);
2456 cp
= parsequoted(cp
);
2460 "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
2461 prog
, cwd
, file
, n
, cp2
);
2464 if (!isspace(*cp
)) {
2467 "%s: %s/%s:%d \"hinfo\" missing white space: %s\n",
2468 prog
, cwd
, file
, n
, cp2
);
2472 while (isspace(*cp
))
2477 "%s: %s/%s:%d \"hinfo\" missing keyword: %s\n",
2478 prog
, cwd
, file
, n
, cp2
);
2481 cp
= parsequoted(cp
);
2485 "%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
2486 prog
, cwd
, file
, n
, cp2
);
2492 "%s: %s/%s:%d \"hinfo\" garbage after keywords: %s\n",
2493 prog
, cwd
, file
, n
, cp2
);
2499 /* Handle "mx" record */
2500 add_domain(name
, domain
);
2501 errors
+= updateitem(name
, NULL
, REC_MX
, ttl
, 0);
2503 /* Look for priority */
2504 if (!isdigit(*cp
)) {
2507 "%s: %s/%s:%d Bad \"mx\" priority: %s\n",
2508 prog
, cwd
, file
, n
, cp
);
2511 /* Skip over priority */
2513 while (isdigit(*cp
))
2515 while (isspace(*cp
))
2520 "%s: %s/%s:%d Missing \"mx\" hostname\n",
2521 prog
, cwd
, file
, n
);
2523 if (strcmp(cp
, "@") == 0)
2524 (void)strcpy(cp
, zone
);
2525 if (checkdots(cp
)) {
2528 checkaddr(cp
) ? addrfmt
: dotfmt
,
2529 prog
, cwd
, file
, n
, rtype
, cp
);
2532 /* Check to see if mx host exists */
2533 add_domain(cp
, domain
);
2535 if (*name
== *cp
&& strcmp(name
, cp
) == 0)
2536 flags
|= FLG_SELFMX
;
2537 errors
+= updateitem(cp
, NULL
, REC_REF
, 0, flags
);
2541 /* Handle "cname" record */
2542 add_domain(name
, domain
);
2543 errors
+= updateitem(name
, NULL
, REC_CNAME
, 0, 0);
2544 if (checkdots(cp
)) {
2547 checkaddr(cp
) ? addrfmt
: dotfmt
,
2548 prog
, cwd
, file
, n
, rtype
, cp
);
2551 /* Make sure cname points somewhere */
2552 if (strcmp(cp
, "@") == 0)
2553 (void)strcpy(cp
, zone
);
2554 add_domain(cp
, domain
);
2555 errors
+= updateitem(cp
, NULL
, REC_REF
, 0, 0);
2559 /* Handle "srv" record */
2560 add_domain(name
, domain
);
2561 errors
+= updateitem(name
, NULL
, REC_SRV
, 0, 0);
2564 /* Skip over three values */
2565 for (i
= 0; i
< 3; ++i
) {
2566 if (!isdigit(*cp
)) {
2568 fprintf(stderr
, "%s: %s/%s:%d"
2569 " Bad \"srv\" value: %s\n",
2570 prog
, cwd
, file
, n
, cp
);
2573 /* Skip over value */
2575 while (isdigit(*cp
))
2577 while (isspace(*cp
))
2581 /* Check to see if mx host exists */
2582 add_domain(cp
, domain
);
2583 errors
+= updateitem(cp
, NULL
, REC_REF
, 0, 0);
2587 /* Handle "txt" record */
2588 add_domain(name
, domain
);
2589 errors
+= updateitem(name
, NULL
, REC_TXT
, 0, 0);
2591 cp
= parsequoted(cp
);
2595 "%s: %s/%s:%d \"txt\" missing quote: %s\n",
2596 prog
, cwd
, file
, n
, cp2
);
2599 while (isspace(*cp
))
2604 "%s: %s/%s:%d \"txt\" garbage after text: %s\n",
2605 prog
, cwd
, file
, n
, cp2
);
2611 /* Handle "ns" record */
2612 errors
+= updateitem(zone
, NULL
, REC_NS
, 0, 0);
2613 if (strcmp(cp
, "@") == 0)
2614 (void)strcpy(cp
, zone
);
2615 if (checkdots(cp
)) {
2618 checkaddr(cp
) ? addrfmt
: dotfmt
,
2619 prog
, cwd
, file
, n
, rtype
, cp
);
2621 add_domain(cp
, domain
);
2622 errors
+= updateitem(cp
, NULL
, REC_REF
, 0, 0);
2626 /* Handle "rp" record */
2627 add_domain(name
, domain
);
2628 errors
+= updateitem(name
, NULL
, REC_RP
, 0, 0);
2631 /* Step over mailbox name */
2632 /* XXX could add_domain() and check further */
2633 while (!isspace(*cp
) && *cp
!= '\0')
2638 "%s: %s/%s:%d \"rp\" missing text name: %s\n",
2639 prog
, cwd
, file
, n
, cp2
);
2645 /* Step over text name */
2646 while (!isspace(*cp
) && *cp
!= '\0')
2652 "%s: %s/%s:%d \"rp\" garbage after text name: %s\n",
2653 prog
, cwd
, file
, n
, cp2
);
2657 /* Make sure text name points somewhere (if not ".") */
2658 if (!CHECKDOT(cp3
)) {
2659 add_domain(cp3
, domain
);
2660 errors
+= updateitem(cp3
, NULL
, REC_REF
, 0, 0);
2665 /* Handle "allow duplicate a" record */
2666 add_domain(name
, domain
);
2667 p
= extractaddr(cp
, ap
);
2670 cp2
= cp
+ strlen(cp
) - 1;
2671 if (cp2
>= cp
&& *cp2
== '\n')
2674 "%s: %s/%s:%d Bad \"allowdupa\" record ip addr \"%s\"\n",
2675 prog
, cwd
, file
, n
, cp
);
2678 errors
+= updateitem(name
, ap
, 0, 0, FLG_ALLOWDUPA
);
2682 /* Handle "dnskey" record */
2683 add_domain(name
, domain
);
2684 errors
+= updateitem(name
, NULL
, REC_CNAME
, 0, 0);
2685 if (checkdots(cp
)) {
2688 checkaddr(cp
) ? addrfmt
: dotfmt
,
2689 prog
, cwd
, file
, n
, rtype
, cp
);
2692 /* Make sure cname points somewhere */
2693 if (strcmp(cp
, "@") == 0)
2694 (void)strcpy(cp
, zone
);
2695 add_domain(cp
, domain
);
2696 errors
+= updateitem(cp
, NULL
, REC_REF
, 0, 0);
2701 if (!parserrsig(cp
, &errstr
))
2703 if (errstr
!= NULL
) {
2706 "%s: %s/%s:%d Bad \"rrsig\" record (%s)\n",
2707 prog
, cwd
, file
, n
, errstr
);
2717 /* Unknown record type */
2720 "%s: %s/%s:%d Unknown record type \"%s\"\n",
2721 prog
, cwd
, file
, n
, rtype
);
2722 add_domain(name
, domain
);
2723 errors
+= updateitem(name
, NULL
, REC_UNKNOWN
, 0, 0);
2726 (void)strcpy(lastname
, name
);
2732 static const char *microlist
[] = {
2741 rfc1034host(const char *host
, int recs
)
2743 const char *cp
, **p
;
2747 for (p
= microlist
; *p
!= NULL
;++p
)
2748 if ((cp
= strstr(host
, *p
)) != NULL
&&
2751 cp
[strlen(*p
)] == '.') {
2757 if (!(isalpha(*cp
) || isdigit(*cp
) || (*cp
== '_' && underok
))) {
2759 "%s: illegal hostname \"%s\" (starts with non-alpha/numeric)\n",
2763 for (++cp
; *cp
!= '.' && *cp
!= '\0'; ++cp
)
2764 if (!(isalpha(*cp
) || isdigit(*cp
) || *cp
== '-' ||
2765 (*cp
== '/' && (recs
& REC_SOA
) != 0))) {
2767 "%s: Illegal hostname \"%s\" ('%c' illegal character)\n",
2771 if (--cp
>= host
&& *cp
== '-') {
2772 fprintf(stderr
, "%s: Illegal hostname \"%s\" (ends with '-')\n",
2780 txt2rrtype(const char *str
)
2782 if (strcasecmp(str
, "aaaa") == 0)
2784 if (strcasecmp(str
, "a") == 0)
2786 if (strcasecmp(str
, "allowdupa") == 0)
2787 return (RR_ALLOWDUPA
);
2788 if (strcasecmp(str
, "cname") == 0)
2790 if (strcasecmp(str
, "dnskey") == 0)
2792 if (strcasecmp(str
, "hinfo") == 0)
2794 if (strcasecmp(str
, "mx") == 0)
2796 if (strcasecmp(str
, "ns") == 0)
2798 if (strcasecmp(str
, "ptr") == 0)
2800 if (strcasecmp(str
, "rp") == 0)
2802 if (strcasecmp(str
, "soa") == 0)
2804 if (strcasecmp(str
, "srv") == 0)
2806 if (strcasecmp(str
, "txt") == 0)
2808 if (strcasecmp(str
, "wks") == 0)
2810 if (strcasecmp(str
, "RRSIG") == 0)
2812 if (strcasecmp(str
, "NSEC") == 0)
2818 samesubnet(struct addr
*a1
, struct addr
*a2
, struct network
*np
)
2823 /* IPv4 before IPv6 */
2824 if (a1
->family
!= a2
->family
)
2827 switch (a1
->family
) {
2830 /* Apply the mask to both values */
2831 v1
= a1
->a_addr4
& np
->n_mask4
;
2832 v2
= a2
->a_addr4
& np
->n_mask4
;
2836 /* Apply the mask to both values */
2837 for (i
= 0; i
< 16; ++i
) {
2838 v1
= a1
->a_addr6
[i
] & np
->n_mask6
[i
];
2839 v2
= a2
->a_addr6
[i
] & np
->n_mask6
[i
];
2851 /* Set address mask in network order */
2853 setmaskwidth(u_int w
, struct network
*np
)
2857 switch (np
->family
) {
2863 np
->n_mask4
= htonl(0xffffffff << (32 - w
));
2867 /* XXX is this right? */
2868 memset(np
->n_mask6
, 0, sizeof(np
->n_mask6
));
2869 for (i
= 0; i
< w
/ 8; ++i
)
2870 np
->n_mask6
[i
] = 0xff;
2873 if (j
> 0 && i
< 16)
2874 np
->n_mask6
[i
] = 0xff << (8 - j
);
2883 updateitem(const char *host
, struct addr
*ap
, int records
, u_int ttl
, int flags
)
2895 /* Hash the host name */
2898 while (*ccp
!= '\0')
2899 i
= i
* 37 + *ccp
++;
2900 ip
= &items
[i
& (ITEMSIZE
- 1)];
2902 /* Look for a match or any empty slot */
2903 while (n
< ITEMSIZE
&& ip
->host
!= NULL
) {
2905 if ((ap
== NULL
|| ip
->addr
.family
== 0 ||
2906 cmpaddr(ap
, &ip
->addr
) == 0) &&
2907 *host
== *ip
->host
&& strcmp(host
, ip
->host
) == 0) {
2909 if (ip
->addr
.family
== 0 && ap
!= NULL
)
2910 memmove(&ip
->addr
, ap
, sizeof(*ap
));
2911 if ((records
& MASK_TEST_DUP
) != 0)
2912 checkdups(ip
, records
);
2913 ip
->records
|= records
;
2914 /* Only check differing ttl's for A and MX records */
2917 else if (ttl
!= 0 && ip
->ttl
!= ttl
) {
2919 "%s: Differing ttls for %s (%u != %u)\n",
2920 prog
, ip
->host
, ttl
, ip
->ttl
);
2924 /* Not done if we wildcard matched the name */
2930 if (ip
>= &items
[ITEMSIZE
])
2934 if (n
>= ITEMSIZE
) {
2935 fprintf(stderr
, "%s: Out of item slots (max %d)\n",
2940 /* Done if we were wildcarding the name (and found entries for it) */
2941 if (ap
== NULL
&& foundsome
) {
2945 /* Didn't find it, make new entry */
2948 fprintf(stderr
, "%s: Reusing bucket!\n", prog
);
2952 memmove(&ip
->addr
, ap
, sizeof(*ap
));
2953 ip
->host
= savestr(host
);
2954 if ((records
& MASK_TEST_DUP
) != 0)
2955 checkdups(ip
, records
);
2956 ip
->records
|= records
;
2967 fprintf(stderr
, "Version %s\n", version
);
2968 fprintf(stderr
, "usage: %s [-d] [-b named.boot] [-B nslint.boot]\n",
2970 fprintf(stderr
, " %s [-d] [-c named.conf] [-C nslint.conf]\n",