/*
* dnsutl - utilities to make DNS easier to configure
* Copyright (C) 1999-2001, 2003, 2006, 2007 Peter Miller
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see
* <http://www.gnu.org/licenses/>.
*/
#include <ac/ctype.h>
#include <ac/string.h>
#include <check.h>
#include <error.h>
#include <gram.h>
#include <lex.h>
#include <srrf/reader.h>
#include <srrf/origin.h>
#include <symtab.h>
static symtab_ty *in_a_stp;
static symtab_ty *in_cname_stp;
static symtab_ty *rev_ns_stp;
static symtab_ty *in_ptr_stp;
static string_ty *dir;
static int num_cross_errors;
static void
check_domain_name(string_ty *s)
{
const char *cp;
int num_empty = !s->str_length;
int num_upper = 0;
if (s->str_length == 1 && s->str_text[0] == '.')
return;
if (s->str_length >= 1 && s->str_text[s->str_length - 1] == '.')
{
gram_error("domain must not end in dot");
return;
}
cp = s->str_text;
for (;;)
{
while (*cp == '.')
{
++cp;
++num_empty;
}
if (*cp == 0)
break;
for (;;)
{
int c = (unsigned char)*cp;
if (c == 0 || c == '.')
break;
if (isupper(c))
++num_upper;
++cp;
}
if (*cp == 0)
break;
++cp;
}
if (num_empty)
{
gram_error
(
"domain \"%s\" contains %d empty component%s",
s->str_text,
num_empty,
(num_empty == 1 ? "" : "s")
);
}
if (num_upper)
{
gram_error
(
"domain \"%s\" contains %d upper case charcater%s, please use lower case",
s->str_text,
num_upper,
(num_upper == 1 ? "" : "s")
);
}
}
static void
check_in_a_records(symtab_ty *stp, string_ty *key, void *data, void *aux)
{
srrf_t *rp = data;
int a1, a2, a3, a4;
string_ty *s;
(void)stp;
(void)key;
(void)aux;
sscanf(rp->arg.string[0]->str_text, "%d.%d.%d.%d", &a1, &a2, &a3, &a4);
s = str_format("%d.%d.%d.%d.in-addr.arpa.", a4, a3, a2, a1);
if (!symtab_query_downcase(in_ptr_stp, s))
{
/* Unless it's an external name server. */
if (!symtab_query_downcase(rev_ns_stp, rp->name))
{
error
(
"%s: %d: no matching IN A record for \"%s IN PTR %s\"",
rp->file_name->str_text,
rp->line_number,
rp->name->str_text,
rp->arg.string[0]->str_text
);
++num_cross_errors;
}
}
str_free(s);
}
static void
check_in_ptr_records(symtab_ty *stp, string_ty *key, void *data, void *aux)
{
srrf_t *rp = data;
int a1, a2, a3, a4;
(void)stp;
(void)key;
(void)aux;
if (sscanf(rp->name->str_text, "%d.%d.%d.%d.in-addr.arpa.",
&a1, &a2, &a3, &a4) != 4)
{
error("%s: %d: malformed \"%s IN PTR\" name",
rp->file_name->str_text, rp->line_number,
rp->name->str_text);
++num_cross_errors;
}
else if (!symtab_query_downcase(in_a_stp, rp->arg.string[0]))
{
error("%s: %d: no matching IN A record for \"%s IN PTR %s\"",
rp->file_name->str_text, rp->line_number,
rp->name->str_text, rp->arg.string[0]->str_text);
++num_cross_errors;
}
}
static void
check_rev_ns_records(symtab_ty *stp, string_ty *key, void *data, void *aux)
{
srrf_t *rp = data;
(void)stp;
(void)key;
(void)aux;
if
(
!symtab_query_downcase(in_a_stp, key)
&&
!symtab_query_downcase(in_cname_stp, key)
)
{
error
(
"%s: %d: no address for \"%s\" name server",
rp->file_name->str_text,
rp->line_number,
rp->arg.string[0]->str_text
);
++num_cross_errors;
}
}
void
check(const char *filename)
{
in_a_stp = symtab_alloc(5);
in_cname_stp = symtab_alloc(5);
in_ptr_stp = symtab_alloc(5);
rev_ns_stp = symtab_alloc(5);
grammar(filename);
/*
* Make sure the IN A records and IN PTR records all match.
*/
num_cross_errors = 0;
symtab_walk(in_a_stp, check_in_a_records, 0);
symtab_walk(in_ptr_stp, check_in_ptr_records, 0);
symtab_walk(rev_ns_stp, check_rev_ns_records, 0);
if (num_cross_errors)
{
fatal
(
"found %d cross validation error%s",
num_cross_errors,
(num_cross_errors == 1 ? "" : "s")
);
}
}
void
check_cache(string_ty *domain, string_ty *filename)
{
static string_ty *dot;
string_ty *fn1;
string_ty *fn2;
srrf_list_ty *data;
size_t j;
srrf_class_ty *in;
srrf_type_ty *in_a;
srrf_type_ty *in_ns;
int number_of_errors = 0;
/*
* Make sure we have seen a directory directive before this.
*/
if (!dir)
{
gram_error("Missing ``directory'' line above this one.");
return;
}
/*
* make sure the domain is dot
*/
if (!dot)
dot = str_from_c(".");
if (!str_equal(dot, domain))
gram_error("domain must be ``.''");
srrf_origin_set(domain);
/*
* read the cache file
*/
fn1 = str_format("%s/%s", dir->str_text, filename->str_text);
fn2 = srrf_find(fn1);
data = srrf_reader(fn2->str_text, 0, 0);
str_free(fn2);
str_free(fn1);
/*
* the only entries should be ``in ns'' or ``in a''
*/
in = srrf_class_by_name("in");
in_a = srrf_type_by_name(in, "a");
in_ns = srrf_type_by_name(in, "ns");
for (j = 0; j < data->nrecords; ++j)
{
srrf_t *rp;
rp = data->record[j];
if (rp->type == in_ns)
{
if (!str_equal(rp->name, dot))
{
/*
* The name server records must all be
* for root (".").
*/
error
(
"%s: %d: the NS records should only be for ``.''",
rp->file_name->str_text,
rp->line_number
);
++number_of_errors;
}
symtab_assign_downcase(rev_ns_stp, rp->arg.string[0], rp);
}
else if (rp->type == in_a)
{
symtab_assign_downcase(in_a_stp, rp->name, rp);
}
else
{
/*
* Actually, you can cache anything, but once
* you have the root server addresses, you can
* find out the rest.
*/
error
(
"%s: %d: cache file must only contain ``in a'' and ``in ns'' records",
rp->file_name->str_text,
rp->line_number
);
++number_of_errors;
}
if (rp->ttl < 30*24*60*60)
{
error
(
"%s: %d: time-to-live needs to be very long",
rp->file_name->str_text,
rp->line_number
);
++number_of_errors;
}
}
/*
* For each NS record, there must be an A record.
* For each A record, there must be an NS record.
*/
for (j = 0; j < data->nrecords; ++j)
{
srrf_t *rp;
rp = data->record[j];
if (rp->type == in_a)
{
if (!symtab_query_downcase(rev_ns_stp, rp->name))
{
error
(
"%s: %d: no name server for %s",
rp->file_name->str_text,
rp->line_number,
rp->name->str_text
);
++number_of_errors;
}
}
}
/*
* Mention the problem if anything bad was found,
* but don't stop yet, there's probably heaps more.
*/
if (number_of_errors)
{
gram_error
(
"the cache file \"%s\" contains %d fatal error%s",
filename->str_text,
number_of_errors,
(number_of_errors == 1 ? "" : "s")
);
}
}
void
check_directory(string_ty *name)
{
char *cp;
/*
* Make sure they specified an absolute path name.
*
* This a bit of a pest when we want to test it on something
* other than live data, or when we want to test data *before*
* making it live.
*
* This is where the include search paths come into it. If *any*
* include path options (-I) appeared on the command line,
* we are going to treat them as relative paths.
*/
if (name->str_text[0] != '/')
gram_error("directory path must be absolute");
for (cp = name->str_text; *cp == '/'; ++cp)
;
if (*cp == 0)
gram_error("directory path must not be root");
/*
* Stash it away.
*/
if (dir)
str_free(dir);
if (srrf_include_path_specified())
dir = str_from_c(cp);
else
dir = str_copy(name);
}
void
check_domain(string_ty *name)
{
(void)name;
}
void
check_forwarder(string_ty *address)
{
(void)address;
}
static string_ty *
append_dot_if_needed(string_ty *s)
{
if (s->str_length < 1 || s->str_text[s->str_length - 1] != '.')
return str_format("%s.", s->str_text);
return str_copy(s);
}
void
check_primary(string_ty *domain, string_ty *filename)
{
string_ty *fn1;
string_ty *fn2;
srrf_list_ty *data;
int number_of_errors = 0;
size_t j;
srrf_class_ty *in;
srrf_type_ty *in_a;
srrf_type_ty *in_cname;
srrf_type_ty *in_ns;
srrf_type_ty *in_ptr;
/*
* Make sure we have seen a directory directive before this.
*/
if (!dir)
{
gram_error("Missing ``directory'' line above this one.");
return;
}
/*
* Set the origin before we read the file.
*/
check_domain_name(domain);
fn2 = append_dot_if_needed(domain);
srrf_origin_set(fn2);
str_free(fn2);
/*
* read the file
*/
fn1 = str_format("%s/%s", dir->str_text, filename->str_text);
fn2 = srrf_find(fn1);
data = srrf_reader(fn2->str_text, 0, 0);
str_free(fn2);
str_free(fn1);
/*
* Stash the records so we can cross check once everything has
* been read in.
*/
in = srrf_class_by_name("in");
in_a = srrf_type_by_name(in, "a");
in_cname = srrf_type_by_name(in, "cname");
in_ns = srrf_type_by_name(in, "ns");
in_ptr = srrf_type_by_name(in, "ptr");
for (j = 0; j < data->nrecords; ++j)
{
srrf_t *rp;
rp = data->record[j];
if (rp->type == in_ns)
symtab_assign_downcase(rev_ns_stp, rp->arg.string[0], rp);
if (rp->type == in_a)
{
srrf_t *other = symtab_query_downcase(in_a_stp, rp->name);
if (other)
{
error
(
"%s: %d: duplicate %s IN A record",
rp->file_name->str_text,
rp->line_number,
rp->name->str_text
);
error
(
"%s: %d: ...here is the first one",
other->file_name->str_text,
other->line_number
);
++number_of_errors;
}
else
symtab_assign_downcase(in_a_stp, rp->name, rp);
}
if (rp->type == in_cname)
{
srrf_t *other = symtab_query_downcase(in_cname_stp, rp->name);
if (other)
{
error
(
"%s: %d: duplicate %s IN CNAME record",
rp->file_name->str_text,
rp->line_number,
rp->name->str_text
);
error
(
"%s: %d: ...here is the first one",
other->file_name->str_text,
other->line_number
);
++number_of_errors;
}
else
symtab_assign_downcase(in_cname_stp, rp->name, rp);
}
if (rp->type == in_ptr)
{
srrf_t *other = symtab_query_downcase(in_ptr_stp, rp->name);
if (other)
{
error
(
"%s: %d: duplicate %s IN PTR record",
rp->file_name->str_text,
rp->line_number,
rp->name->str_text
);
error
(
"%s: %d: ...here is the first one",
other->file_name->str_text,
other->line_number
);
++number_of_errors;
}
else
symtab_assign_downcase(in_ptr_stp, rp->name, rp);
}
}
/*
* Mention the problem if anything bad was found,
* but don't stop yet, there's probably heaps more.
*/
if (number_of_errors)
{
gram_error
(
"the cache file \"%s\" contains %d fatal error%s",
filename->str_text,
number_of_errors,
(number_of_errors == 1 ? "" : "s")
);
}
}
void
check_secondary(string_ty *domain, string_ty *filename)
{
(void)filename;
/*
* Make sure we have seen a directory directive before this.
*/
if (!dir)
{
gram_error("Missing ``directory'' line above this one.");
return;
}
/*
* Set the origin before we read the file.
*/
check_domain_name(domain);
}
void
check_option(string_ty *name)
{
static const char *table[] =
{
"query-log",
"no-recursion",
"forward-only",
"fake-iquery",
"no-fetch-glue",
};
const char **cpp;
for (cpp = table; cpp < ENDOF(table); ++cpp)
if (0 == strcmp(*cpp, name->str_text))
return;
gram_error("unknown ``%s'' option", name->str_text);
}
syntax highlighted by Code2HTML, v. 0.9.1