/* * 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 * . */ #include #include #include #include #include #include #include #include #include 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); }