/*
* 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$ (LBL)";
#endif
/*
* nslint - perform consistency checks on dns files
*/
#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
/* item struct */
struct item {
};
/* Resource records seen */
/* These aren't real records */
/* Test for records we want to map to REC_OTHER */
/* Mask away records we don't care about in the final processing to REC_OTHER */
#define MASK_CHECK_REC \
/* Test for records we want to check for duplicate name detection */
#define MASK_TEST_DUP \
/* Flags */
/* Test for smtp problems */
#define MASK_TEST_SMTP \
/* Hostname string storage */
int debug;
int errors;
char *nslintboot;
char *nslintconf;
char *prog;
int protoserv_init;
int protoserv_last;
int protoserv_len;
/* SOA record */
#define SOA_SERIAL 0
static int nsoaval;
/* Forwards */
static inline void add_domain(char *, const char *);
int checkdots(const char *);
int checkserv(const char *, char **p);
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);
int main(int, char **);
int nslint(void);
int parsenetwork(const char *, char **);
char *parsequoted(char *);
int parsesoa(const char *, char **);
void process(const char *, const char *, const char *);
int rfc1034host(const char *, int);
extern char *optarg;
/* add domain if necessary */
static inline void
{
register char *cp;
/* Kill trailing white space and convert to lowercase */
*cp-- = '\0';
/* If necessary, append domain */
if (*domain != '.')
*cp++ = '.';
}
/* XXX should we insure a trailing dot? */
}
int
{
register char *cp;
else
donamedboot = 0;
donamedconf = 0;
switch (op) {
case 'b':
++donamedboot;
break;
case 'c':
++donamedconf;
break;
case 'B':
nslintboot = optarg;
++donamedboot;
break;
case 'C':
nslintconf = optarg;
++donamedconf;
break;
case 'd':
++debug;
break;
default:
usage();
}
usage();
if (donamedboot)
else if (donamedconf)
else {
if (status < 0) {
++donamedboot;
} else
++donamedconf;
}
if (donamedboot) {
if (nslintboot != NULL)
else if ((i = doboot(NSLINTBOOT, 0)) > 0)
status |= i;
} else {
if (nslintconf != NULL)
else if ((i = doconf(NSLINTCONF, 0)) > 0)
status |= i;
}
}
struct netlist {
};
static u_int32_t
{
register int i;
for (i = 0; i < netlistcnt; ++i)
return (0);
}
int
{
register int i, w;
register u_int32_t o;
register int shift;
++cp;
net = 0;
mask = 0;
shift = 24;
o = 0;
do {
shift -= 8;
if (*cp != '.')
break;
++cp;
}
++cp;
++cp;
if ((int)mask == -1) {
return (0);
}
i = 0;
++cp;
++cp;
++cp;
}
if (i != 3) {
*errstrp = "wrong number of dots in mask";
return (0);
}
} else if (*cp == '/') {
++cp;
do {
++cp;
if (w < 1 || w > 32) {
*errstrp = "bad mask width";
return (0);
}
} else {
*errstrp = "garbage after net";
return (0);
}
++cp;
if (*cp != '\0') {
*errstrp = "trailing garbage";
return (0);
}
/* Finaly sanity checks */
return (0);
}
/* Make sure there's room */
if (netlistsize <= netlistcnt) {
if (netlistsize == 0) {
netlistsize = 32;
} else {
netlistsize <<= 1;
}
exit(1);
}
}
/* Add to list */
++netlistcnt;
return (1);
}
int
{
register int n;
register FILE *f;
char *errstr;
errno = 0;
if (f == NULL) {
/* Not an error if it doesn't exist */
if (debug > 1)
"%s: doit: %s doesn't exist (ignoring)\n",
return (-1);
}
exit(1);
}
if (debug > 1)
n = 0;
++n;
/* Skip comments */
if (buf[0] == ';')
continue;
if (cp)
*cp = '\0';
*cp = '\0';
/* Eat leading whitespace */
++cp;
/* Skip blank lines */
continue;
/* Get name */
++cp;
*cp++ = '\0';
/* Find next keyword */
++cp;
/* Terminate directory */
++cp;
*cp = '\0';
++errors;
exit(1);
}
continue;
}
/* Extract domain, converting to lowercase */
else
/* Insure trailing dot */
*cp2++ = '.';
*cp2 = '\0';
/* Find file */
++cp;
/* Terminate directory */
++cp;
*cp = '\0';
/* Process it! (zone is the same as the domain) */
nsoaval = -1;
continue;
}
++errors;
"%s: %s:%d: bad network: %s\n",
}
continue;
}
/* Terminate include file */
++cp;
*cp = '\0';
continue;
}
/* Eat any other options */
}
(void)fclose(f);
return (errors != 0);
}
int
{
char *errstr;
errno = 0;
if (fd < 0) {
/* Not an error if it doesn't exist */
if (debug > 1)
"%s: doconf: %s doesn't exist (ignoring)\n",
return (-1);
}
exit(1);
}
if (debug > 1)
exit(1);
}
exit(1);
}
/* Slurp entire config file */
do {
if (cc < 0) {
exit(1);
}
n -= cc;
#define EATWHITESPACE \
if (*cp == '\n') \
++n; \
++cp; \
}
/* Handle both to-end-of-line and C style comments */
#define EATCOMMENTS \
{ \
int sawcomment; \
do { \
sawcomment = 0; \
if (*cp == '#') { \
sawcomment = 1; \
++cp; \
++cp; \
} \
sawcomment = 1; \
cp += 2; \
++cp; \
} \
sawcomment = 1; \
if (*cp == '\n') \
++n; \
cp += 2; \
break; \
} \
} \
} \
} while (sawcomment); \
}
{ \
(len) = 0; \
++(len); \
++cp; \
} \
}
{ \
if (*cp != '"') { \
++errors; \
} else \
++cp; \
(len) = 0; \
++(len); \
++cp; \
} \
if (*cp != '"') { \
++errors; \
} else \
++cp; \
}
/* Eat everything to the next semicolon, perhaps eating matching qbraces */
#define EATSEMICOLON \
{ \
register int depth = 0; \
while (*cp != '\0') { \
if (*cp == ';') { \
++cp; \
if (depth == 0) \
break; \
continue; \
} \
if (*cp == '{') { \
++depth; \
++cp; \
continue; \
} \
if (*cp == '}') { \
--depth; \
++cp; \
continue; \
} \
++cp; \
} \
}
n = 1;
zone[0] = '\0';
while (*cp != '\0') {
if (*cp == '\0')
break;
if (namelen == 0) {
++errors;
++cp;
continue;
}
if (*cp != '{') {
++errors;
"%s: %s:%d missing left qbrace in options\n",
} else
++cp;
if (namelen == 0) {
++errors;
"%s: %s:%d garbage char '%c' (2)\n",
++cp;
break;
}
/* If not the "directory" option, just eat it */
namelen) == 0) {
GETQUOTEDNAME(cp2, i)
cp2[i] = '\0';
++errors;
"%s: %s:.%d can't chdir %s: %s\n",
exit(1);
}
}
}
++cp;
if (*cp != ';') {
++errors;
"%s: %s:%d missing options semi\n",
} else
++cp;
continue;
}
typenamelen = 0;
filenamelen = 0;
cp += 2;
cp += 5;
}
++errors;
"%s: %s:%d missing left qbrace in zone\n",
continue;
}
depth = 0;
while (*cp != '\0') {
if (*cp == '{') {
++cp;
++depth;
} else if (*cp == '}') {
if (--depth <= 1)
break;
++cp;
}
if (namelen == 0) {
++errors;
"%s: %s:%d garbage char '%c' (3)\n",
++cp;
break;
}
namelen) == 0) {
if (namelen == 0) {
++errors;
"%s: %s:%d garbage char '%c' (4)\n",
++cp;
break;
}
namelen) == 0) {
}
/* Just ignore keywords we don't understand */
}
/* { */
if (*cp != '}') {
++errors;
"%s: %s:%d missing zone right qbrace\n",
} else
++cp;
if (*cp != ';') {
++errors;
"%s: %s:%d missing zone semi\n",
} else
++cp;
/* If we got something interesting, process it */
if (typenamelen == 0) {
++errors;
prog);
continue;
}
if (filenamelen == 0) {
++errors;
"%s: missing zone filename!\n",
prog);
continue;
}
/* Insure trailing dot */
*cp2++ = '.';
*cp2 = '\0';
}
nsoaval = -1;
}
continue;
}
if (*cp != '{') {
++errors;
"%s: %s:%d missing left qbrace in nslint\n",
} else
++cp;
++cp;
namelen) == 0) {
GETQUOTEDNAME(cp2, i)
cp2[i] = '\0';
++errors;
"%s: %s:%d: bad network: %s\n",
}
} else {
++errors;
"%s: unknown nslint \"%.*s\"\n",
}
}
++cp;
if (*cp != ';') {
++errors;
} else
++cp;
continue;
}
continue;
}
/* Skip over statements we don't understand */
}
return (errors != 0);
}
/* Return true when done */
int
{
/* Eat leading whitespace */
++cp;
/* Find opening paren */
if (nsoaval < 0) {
return (0);
++cp;
++cp;
nsoaval = 0;
}
/* Grab any numbers we find */
garbage = "leading garbage";
do {
++cp;
do {
++cp;
} else {
switch (ch) {
case 'w':
/* fall through */
case 'd':
/* fall through */
case 'h':
/* fall through */
case 'm':
/* fall through */
case 's':
++cp;
break;
default:
; /* none */
}
}
++cp;
garbage = "trailing garbage";
++nsoaval;
}
/* If we're done, do some sanity checks */
++cp;
if (*cp != '\0')
else if (soaval[SOA_EXPIRE] <
"expire less than refresh + 10 * retry (%u < %u + 10 * %u)",
"refresh less than 2 * retry (%u < 2 * %u)",
}
return (1);
}
if (*cp != '\0') {
return (1);
}
return (0);
}
void
register const char *zone)
{
register FILE *f;
register const char *ccp;
int smtp;
char *errstr;
if (f == NULL) {
++errors;
return;
}
if (debug > 1)
/* Are we doing an in-addr.arpa domain? */
n = 0;
net = 0;
mask = 0;
++errors;
fclose(f);
return;
}
lastname[0] = '\0';
sawsoa = 0;
++n;
while (*cp != '\0') {
/* Handle quoted strings (but don't report errors) */
if (*cp == '"') {
++cp;
++cp;
continue;
}
break;
++cp;
}
*cp-- = '\0';
/* Nuke trailing white space */
*cp-- = '\0';
if (*cp == '\0')
continue;
/* Handle multi-line soa records */
if (sawsoa) {
sawsoa = 0;
++errors;
"%s: %s/%s:%d bad \"soa\" record (%s)\n",
}
continue;
}
if (debug > 3)
/* Look for name */
/* Same name as last record */
if (lastname[0] == '\0') {
++errors;
"%s: %s/%s:%d no default name\n",
continue;
}
} else {
/* Extract name, converting to lowercase */
else
*cp2 = '\0';
/* Check for domain shorthand */
}
/* Find next token */
++cp;
/* Handle includes (gag) */
/* Extract filename */
*cp2 = '\0';
/* Look for optional domain */
++cp;
if (*cp == '\0')
else {
/* Convert optional domain to lowercase */
*cp = '\0';
}
continue;
}
/* Handle $origin */
/* Extract domain, converting to lowercase */
else
*cp2 = '\0';
lastname[0] = '\0';
/* Are we doing an in-addr.arpa domain? */
net = 0;
mask = 0;
++errors;
"%s: %s/%s:%d bad in-addr.arpa domain\n",
return;
}
continue;
}
/* Handle ttl */
++cp;
++cp;
++cp;
if (*cp != '\0') {
++errors;
"%s: %s/%s:%d bad $ttl \"%s\"\n",
}
continue;
}
/* Parse ttl or use default */
do {
++cp;
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 */
}
++errors;
continue;
}
/* Find next token */
++cp;
++cp;
} else
/* Eat optional "in" */
/* Find next token */
cp += 3;
++cp;
/* Find next token */
cp += 5;
++cp;
}
/* Find end of record type, converting to lowercase */
*cp++ = '\0';
/* Find "the rest" */
++cp;
/* Check for non-ptr names with dots but no trailing dot */
++errors;
"%s: %s/%s:%d \"%s\" name missing trailing dot: %s\n",
}
/* Check for FQDNs outside the zone */
++errors;
"%s: %s/%s:%d \"%s\" outside zone %s\n",
}
}
#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')
/* Handle "a" record */
if ((int)addr == -1) {
++errors;
*cp2 = '\0';
"%s: %s/%s:%d bad \"a\" record ip addr \"%s\"\n",
continue;
}
/* Just eat for now */
continue;
/* Handle "ptr" record */
++errors;
}
++errors;
"%s: %s/%s:%d bad \"ptr\" record (%s) ip addr \"%s\"\n",
continue;
}
/* Handle "soa" record */
}
++sawsoa;
++errors;
"%s: %s/%s:%d bad \"soa\" record (%s)\n",
continue;
}
/* Handle "wks" record */
if ((int)addr == -1) {
++errors;
++cp2;
*cp2 = '\0';
"%s: %s/%s:%d bad \"wks\" record ip addr \"%s\"\n",
continue;
}
/* Step over ip address */
++cp;
*cp++ = '\0';
/* Make sure services are legit */
++errors;
"%s: %s/%s:%d bad \"wks\" record (%s)\n",
continue;
}
0, smtp ? FLG_SMTPWKS : 0);
/* XXX check to see if ip address records exists? */
/* Handle "hinfo" record */
++errors;
"%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
continue;
}
++errors;
"%s: %s/%s:%d \"hinfo\" missing white space: %s\n",
continue;
}
++cp;
++cp;
if (*cp == '\0') {
++errors;
"%s: %s/%s:%d \"hinfo\" missing keyword: %s\n",
continue;
}
++errors;
"%s: %s/%s:%d \"hinfo\" missing quote: %s\n",
continue;
}
if (*cp != '\0') {
++errors;
"%s: %s/%s:%d \"hinfo\" garbage after keywords: %s\n",
continue;
}
/* Handle "mx" record */
/* Look for priority */
++errors;
"%s: %s/%s:%d bad \"mx\" priority: %s\n",
}
/* Skip over priority */
++cp;
++cp;
++cp;
if (*cp == '\0') {
++errors;
"%s: %s/%s:%d missing \"mx\" hostname\n",
}
++errors;
}
/* Check to see if mx host exists */
flags |= FLG_SELFMX;
/* Handle "cname" record */
++errors;
}
/* Make sure cname points somewhere */
/* Handle "srv" record */
/* Skip over three values */
for (i = 0; i < 3; ++i) {
++errors;
" bad \"srv\" value: %s\n",
}
/* Skip over value */
++cp;
++cp;
++cp;
}
/* Check to see if mx host exists */
/* Handle "txt" record */
++errors;
"%s: %s/%s:%d \"txt\" missing quote: %s\n",
continue;
}
++cp;
if (*cp != '\0') {
++errors;
"%s: %s/%s:%d \"txt\" garbage after text: %s\n",
continue;
}
/* Handle "ns" record */
++errors;
}
/* Handle "rp" record */
/* Step over mailbox name */
/* XXX could add_domain() and check further */
++cp;
if (*cp == '\0') {
++errors;
"%s: %s/%s:%d \"rp\" missing text name: %s\n",
continue;
}
++cp;
/* Step over text name */
++cp;
if (*cp != '\0') {
++errors;
"%s: %s/%s:%d \"rp\" garbage after text name: %s\n",
continue;
}
/* Make sure text name points somewhere (if not ".") */
}
/* Handle "allow duplicate a" record */
if ((int)addr == -1) {
++errors;
*cp2 = '\0';
"%s: %s/%s:%d bad \"allowdupa\" record ip addr \"%s\"\n",
continue;
}
} else {
/* Unknown record type */
++errors;
"%s: %s/%s:%d unknown record type \"%s\"\n",
}
}
(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
{
if (records == 0)
return;
++errors;
}
if (records != 0)
}
int
{
register const char *ccp;
register int n, errs;
register u_int i;
int foundsome;
n = 0;
foundsome = 0;
errs = 0;
++foundsome;
if ((records & MASK_TEST_DUP) != 0)
/* Only check differing ttl's for A and MX records */
"%s: differing ttls for %s (%u != %u)\n",
++errs;
}
/* Not done if we wildcard matched the name */
if (addr)
return (errs);
}
++n;
++ip;
}
if (n >= ITEMSIZE) {
exit(1);
}
/* Done if we were wildcarding the name (and found entries for it) */
return (errs);
/* Didn't find it, make new entry */
++itemcnt;
exit(1);
}
if ((records & MASK_TEST_DUP) != 0)
if (ttl != 0)
return (errs);
}
static const char *microlist[] = {
"_tcp",
"_udp",
"_msdcs",
"_sites",
};
int
{
register const char *cp, **p;
register int underok;
underok = 0;
++underok;
break;
}
return (1);
}
"%s: illegal hostname \"%s\" ('%c' illegal character)\n",
return (1);
}
return (1);
}
return (0);
}
int
nslint(void)
{
exit(1);
}
continue;
/* Save entries with addresses for later check */
if (debug > 1) {
if (debug > 2)
printf("%d\t", n);
printf("%s\t%s\t0x%x\t0x%x\n",
}
/* Check for illegal hostnames (rfc1034) */
++errors;
/* Check for missing ptr records (ok if also an ns record) */
switch (records) {
case REC_CNAME:
/* These are O.K. */
break;
++errors;
break;
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.
*/
++errors;
"%s: \"wks\" without \"a\" and \"ptr\": %s -> %s\n",
}
break;
case REC_REF:
++errors;
"%s: name referenced without other records: %s\n",
break;
case REC_A:
++errors;
break;
case REC_PTR:
++errors;
break;
++errors;
break;
case 0:
/* Second level test */
break;
/* Fall through... */
default:
++errors;
"%s: records == 0x%x: can't happen (%s 0x%x)\n",
break;
}
/* Check for smtp problems */
++errors;
"%s: self \"mx\" for %s missing \"a\" record\n",
}
switch (flags) {
case 0:
case FLG_SELFMX | FLG_SMTPWKS:
/* These are O.K. */
break;
case FLG_SELFMX:
++errors;
}
break;
case FLG_SMTPWKS:
++errors;
break;
default:
++errors;
"%s: flags == 0x%x: can't happen (%s)\n",
}
/* Check for chained MX records */
++errors;
}
}
/* Check for doubly booked addresses */
lastaddr = 0;
++errors;
}
}
/* Check for hosts with multiple addresses on the same subnet */
if (netlistcnt > 0) {
continue;
if (mask == 0) {
++errors;
"%s: can't find mask for %s (%s)\n",
++errors;
"%s: multiple \"a\" records for %s on subnet %s",
}
}
}
}
if (debug)
return (errors != 0);
}
/* Similar to inet_ntoa() */
char *
{
register char *cp;
register int n;
*--cp = '\0';
n = 4;
do {
byte /= 10;
if (byte > 0) {
byte /= 10;
if (byte > 0)
}
*--cp = '.';
addr >>= 8;
} while (--n > 0);
return cp + 1;
}
int
{
register int i, bits;
return (0);
net = 0;
mask = 0xff000000;
bits = 0;
o = 0;
do {
net = o << 24;
/* Check for classless delegation mask width */
if (*cp == '/') {
++cp;
o = 0;
do {
bits = o;
return (0);
}
++cp;
o = 0;
do {
mask = 0xffff0000;
++cp;
o = 0;
do {
mask = 0xffffff00;
++cp;
o = 0;
do {
mask = 0xffffffff;
}
}
}
return (0);
/* Classless delegation */
/* XXX check that calculated mask isn't smaller than octet mask? */
if (bits != 0)
mask |= (1 << i);
return (1);
}
register char **errstrp)
{
register int shift;
addr = 0;
shift = 0;
o = 0;
do {
shift += 8;
if (*cp != '.') {
if (*cp == '\0')
break;
*errstrp = "missing dot";
return (0);
}
++cp;
}
if (shift > 32) {
*errstrp = "more than 4 octets";
return (0);
}
return (addr);
#ifdef notdef
if (*cp != '\0') {
*errstrp = "trailing junk";
return (0);
}
#endif
#ifdef notdef
*errstrp = "too many octets for net";
return (0);
}
#endif
}
int
register char **errstrp)
{
register int n, sawparen;
if (!protoserv_init) {
}
/* Line count */
n = 0;
/* Terminate protocol */
++cp;
if (*cp != '\0')
*cp++ = '\0';
/* Find services */
*smtpp = 0;
sawparen = 0;
if (*cp == '(') {
++sawparen;
++cp;
++cp;
}
for (;;) {
if (*cp == '\0') {
if (!sawparen)
break;
*errstrp = "mismatched parens";
return (n);
}
++n;
++cp;
}
/* Find end of service, converting to lowercase */
if (*cp != '\0')
*cp++ = '\0';
/* XXX should check for trailing junk */
break;
}
++*smtpp;
break;
}
if (*p == NULL) {
break;
}
}
return (n);
}
int
{
for (; *p != NULL; ++p)
return (1);
return (0);
}
void
initprotoserv(void)
{
register char *cp;
protoserv_len = 256;
exit(1);
}
/* Convert to lowercase */
protoserv_len <<= 1;
protoserv_len * sizeof(*protoserv));
exit(1);
}
}
}
}
/*
* 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
{
return (0);
return (0);
/* Return true of more than one dot*/
++cp;
return (1);
return (1);
return (0);
}
int
{
return (-1);
return (1);
else
return (0);
}
int
{
}
/* Returns a pointer after the next token or quoted string, else NULL */
char *
{
if (*cp == '"') {
++cp;
++cp;
if (*cp != '"')
return (NULL);
++cp;
} else {
++cp;
}
return (cp);
}
__dead void
usage(void)
{
extern char version[];
prog);
prog);
exit(1);
}