mirror of
https://github.com/zerotier/edge.git
synced 2026-05-22 16:25:05 -07:00
953 lines
24 KiB
Perl
Executable File
953 lines
24 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
# Copyright 2004-2013 SPARTA, Inc. All rights reserved.
|
|
# See the COPYING file included with the DNSSEC-Tools package for details.
|
|
|
|
#
|
|
# If we're executing from a packed environment, make sure we've got the
|
|
# library path for the packed modules.
|
|
#
|
|
BEGIN {
|
|
if ($ENV{'PAR_TEMP'}) {
|
|
unshift @INC, ("$ENV{'PAR_TEMP'}/inc/lib");
|
|
}
|
|
}
|
|
|
|
use Net::DNS::SEC::Tools::QWPrimitives;
|
|
use Net::DNS::SEC::Tools::BootStrap;
|
|
use Net::DNS::SEC::Tools::conf;
|
|
use Net::DNS::SEC::Tools::Donuts::Rule;
|
|
use Net::DNS::SEC::Tools::dnssectools;
|
|
use Date::Parse;
|
|
|
|
######################################################################
|
|
# detect needed perl module requirements
|
|
#
|
|
dnssec_tools_load_mods('Date::Parse' => "");
|
|
use Data::Dumper;
|
|
|
|
my $have_qw =
|
|
eval {
|
|
require QWizard;
|
|
};
|
|
my $qw;
|
|
|
|
use strict;
|
|
|
|
use Config;
|
|
use Net::DNS::SEC::Tools::Donuts;
|
|
|
|
our @guiargs;
|
|
|
|
my %opts = (l => 5,
|
|
c => $ENV{'HOME'} . "/.donuts.conf",
|
|
T => 'port 53 || ip[6:2] & 0x1fff != 0',
|
|
o => '%d.%t.pcap',
|
|
O => "wrapped",
|
|
L => "stdout",
|
|
r => "/usr/share/dnssec-tools/donuts/rules/*.txt," .
|
|
$ENV{'HOME'} . "/.dnssec-tools/donuts/rules/*.txt");
|
|
|
|
my $TCPDUMP = "tcpdump";
|
|
|
|
# override some defaults if we're a self-extracting perl archive with
|
|
# the files contained within the archive.
|
|
if (runpacked()) {
|
|
$opts{'r'} = $ENV{'PAR_TEMP'} . "/inc/rules/" . "*.txt,";
|
|
|
|
setconffile("$ENV{'PAR_TEMP'}/inc/dnssec-tools.conf");
|
|
|
|
$TCPDUMP = "./tcpdump";
|
|
}
|
|
|
|
our ($rf, $current_zone_file,
|
|
@ignorelist, @onlylist,
|
|
$netdns, $netdns_error, %outstructure, $current_domain,
|
|
@filestested);
|
|
|
|
|
|
my %primaries =
|
|
(display_errors =>
|
|
{
|
|
title => 'Zone Errors',
|
|
introduction => 'Below are the errors found when analyzing the zones',
|
|
leftside =>
|
|
["Browse Results",
|
|
{ type => 'tree',
|
|
name => 'showthis',
|
|
refresh_on_change => 1,
|
|
expand_all => 2,
|
|
root => 'Errors',
|
|
parent => \&get_gui_parent,
|
|
children => \&get_gui_children },
|
|
],
|
|
questions =>
|
|
[{ type => 'table',
|
|
name => 'results',
|
|
text => sub { ((qwparam('showthis') eq 'Errors' ||
|
|
qwparam('showthis') eq '') ?
|
|
'Summary:' : 'Results:') },
|
|
values => sub {
|
|
if (qwparam('showthis') =~ /::/) {
|
|
my $tab;
|
|
my ($spot, $value) = (qwparam('showthis') =~ /(.*)::(.*)/);
|
|
foreach my $data (@{$outstructure{$spot}{$value}}) {
|
|
push @$tab, $data;
|
|
}
|
|
return [$tab];
|
|
} elsif (qwparam('showthis') eq 'Errors' ||
|
|
qwparam('showthis') eq '') {
|
|
my $tab;
|
|
|
|
push @$tab, ['results on testing:', join(', ',@filestested)];
|
|
# push @$tab, ['rules considered:', $#{$donuts->rules()}];
|
|
# push @$tab, ['rules tested:', "FIX ME: only rules run"];
|
|
# push @$tab, ['records analyzed:', $#{$donuts->records()}];
|
|
# push @$tab, ['names analyzed:', "FIX ME"];
|
|
# push @$tab, ['errors found:', $globalerrcount];
|
|
return [$tab];
|
|
}
|
|
return [[[""]]];
|
|
}
|
|
}]
|
|
});
|
|
|
|
DTGetOptions(\%opts,
|
|
['GUI:VERSION',"DNSSEC-Tools Version: 2.2.3"],
|
|
|
|
['GUI:screen',"Rule Set Configuration:"],
|
|
["l|level=i", "The maximum rule level to run (default = 5)",
|
|
helpdesc => '(higher number = include more nit-picking tests)',
|
|
question => { type => 'menu', values => [1..9] }],
|
|
|
|
['GUI:separator','Output Format Options:'],
|
|
["show-gui", "Display the results in a browsable window.",
|
|
nocgi => 1,
|
|
question => { type => 'checkbox', default => 1, indent => 1}],
|
|
["O|output-format=s", "Output format (wrapped, or text)",
|
|
nocgi => 1,
|
|
question => { type => 'menu', default => 'wrapped', indent => 1,
|
|
values => [qw(wrapped text)]}],
|
|
["L|output-location=s", "Output location (stdout, stderr, file:/path)",
|
|
nocgi => 1,
|
|
question => { type => 'menu', default => 'stdout', indent => 1,
|
|
values => [qw(stdout stderr)]}],
|
|
["s|sorted", "Sort the loaded records so the output is consistent"],
|
|
["v|verbose+",
|
|
"Verbose output (show extra processing information). Use multiple times for increasing amounts of output.",
|
|
question => { type => 'checkbox', default => 1, indent => 1 }],
|
|
["V|show-records", "Display the contents of the records read or queried"],
|
|
|
|
["q|quiet", "Quiet output (Do not print summary information)",
|
|
indent => 1],
|
|
|
|
['GUI:separator','Advanced Options:'],
|
|
['GUI:guionly',{type => 'checkbox',
|
|
values => [1,0],
|
|
default => 0,
|
|
indent => 1,
|
|
text => 'Show Advanced Options',
|
|
name => 'advanced'}],
|
|
|
|
"",
|
|
['GUI:guionly',{type => 'button',
|
|
values => 'Help',
|
|
default => 1,
|
|
nocgi => 1,
|
|
text => 'Display Help Options',
|
|
name => 'displayhelp'},
|
|
],
|
|
|
|
['GUI:screen','Advanced Configuration:', doif => 'advanced'],
|
|
['GUI:separator','Rules Selection Configuration:'],
|
|
["r|rules=s", "glob pattern for rule files to load",
|
|
indent => 1,
|
|
doif =>
|
|
sub { ref($_[1]->{'generator'}) !~ /HTML/}, # not safe for web
|
|
],
|
|
["i|ignore=s", "Regular expression for rules to ignore",
|
|
indent => 1],
|
|
["only=s", "Regular expression for rules to include (default = all)",
|
|
indent => 1],
|
|
["f|features=s", "Extra features to turn on",
|
|
helpdesc => '(comma separated)',
|
|
indent => 1],
|
|
|
|
['GUI:separator',"Configuration Files:", nocgi => 1],
|
|
["C|no-config","Do not load personal configuration files",
|
|
indent => 1, nocgi => 1],
|
|
["c|config-file=s",
|
|
"Use an alternate personal configuration file",
|
|
indent => 1,
|
|
doif =>
|
|
sub { ref($_[1]->{'generator'}) !~ /HTML/}, # not safe for web
|
|
],
|
|
|
|
['GUI:otherargs_text',"FILE DOMAIN [FILE DOMAIN...]"],
|
|
['GUI:otherargs_required',1],
|
|
|
|
['GUI:screen',"Extra Live Query Options:",
|
|
doif => sub {
|
|
$_[1]->qwparam('live') && !$_[1]->qwparam('displayhelp') &&
|
|
ref($_[1]->{'generator'}) !~ /HTML/ # not safe for web
|
|
;
|
|
}
|
|
],
|
|
["t|tcpdump-capture=s",
|
|
"Start tcpdump on interface STRING during run"],
|
|
["T|tcpdump-filter=s",
|
|
"Use tcpdump filter (default: port 53 or fragments)"],
|
|
["o|tcpdump-output-file=s",
|
|
"Save tcpdump results to file STRING."],
|
|
|
|
['GUI:screen',"Help Options:",
|
|
doif => 'displayhelp'],
|
|
["R|help-rules", 'Show the rules that donuts checks'],
|
|
["F|help-features",
|
|
"Show available additional features of available rules."],
|
|
["H|help-config",
|
|
'Show configuration tokens supported by the rules'],
|
|
|
|
['GUI:nootherargs',1],
|
|
['GUI:submodules','getzonefiles','getzonenames'],
|
|
['GUI:otherprimaries',
|
|
dnssec_tools_get_qwprimitives(%primaries)],
|
|
) || exit;
|
|
|
|
push @main::ARGV, @guiargs;
|
|
|
|
if (!$opts{'R'} && !$opts{'F'} && !$opts{'H'} &&
|
|
($#ARGV == -1 || $#ARGV % 2 != 1)) {
|
|
print STDERR "\nUsage Error: $0 called with wrong number of arguments\n";
|
|
print STDERR " file and zone name arguments are both needed\n";
|
|
print STDERR " (EG: $0 FILE1 example.com FILE2 other.example.com)\n\n";
|
|
exit 1;
|
|
}
|
|
|
|
#
|
|
# Create the main Donuts object
|
|
#
|
|
my $donuts = new Net::DNS::SEC::Tools::Donuts();
|
|
$donuts->set_config('verbose', $opts{'v'});
|
|
$donuts->set_config('sorted', $opts{'s'});
|
|
$donuts->set_output_format($opts{'O'});
|
|
$donuts->set_output_location($opts{'L'});
|
|
|
|
#
|
|
# initialize ignore list
|
|
#
|
|
if ($opts{'i'}) {
|
|
$donuts->set_ignore_list(split(/,\s*/, $opts{'i'}));
|
|
}
|
|
|
|
if ($opts{'only'}) {
|
|
$donuts->set_only_list(split(/,\s*/, $opts{'only'}));
|
|
}
|
|
|
|
#
|
|
# create the feature set
|
|
#
|
|
my %features;
|
|
if ($opts{'f'}) {
|
|
$donuts->set_feature_list(split(/,\s*/, $opts{'f'}));
|
|
}
|
|
|
|
# start the output wrapper (eg, <?xml ...>)
|
|
$donuts->output()->StartOutput();
|
|
|
|
#
|
|
# load rule files
|
|
# (comma separated list)
|
|
#
|
|
$donuts->load_rule_files($opts{'r'});
|
|
|
|
#
|
|
# load optional user-config file
|
|
#
|
|
if ($opts{'c'} && !$opts{'C'} && -f $opts{'c'}) {
|
|
$donuts->parse_config_file($opts{'c'});
|
|
}
|
|
|
|
#
|
|
# display config file help
|
|
#
|
|
if ($opts{'H'}) {
|
|
maybe_output_to_web();
|
|
print STDERR "$0 configuration tokens for loaded rules:\n\n";
|
|
printf STDERR sprintf("%-20s %-15s%s\n",
|
|
"RULE NAME", "TOKEN", "DESCRIPTION");
|
|
printf STDERR sprintf("%-20s %-15s%s\n", "_" x 19, "_" x 13,
|
|
"_" x (80-20-15-2));
|
|
my @rules = $donuts->rules();
|
|
foreach my $rule (@rules) {
|
|
$rule->print_help();
|
|
}
|
|
exit;
|
|
}
|
|
|
|
#
|
|
# display a list of rules
|
|
#
|
|
if ($opts{'R'}) {
|
|
maybe_output_to_web();
|
|
print STDERR "\n$0 rules:\n\n";
|
|
printf STDERR "RULE NAME\n DESCRIPTION...\n";
|
|
printf STDERR "_" x 75 . "\n";
|
|
my @rules = $donuts->rules();
|
|
foreach my $rule (@rules) {
|
|
$rule->print_description() if (!$rule->{'internal'});
|
|
}
|
|
exit;
|
|
}
|
|
|
|
#
|
|
# display a list of rules
|
|
#
|
|
if ($opts{'F'}) {
|
|
maybe_output_to_web();
|
|
print STDERR "\n$0 feature list:\n";
|
|
print STDERR " (Turn these on using the --features flag)\n\n";
|
|
my %shown;
|
|
my @features = $donuts->available_features();
|
|
foreach my $feature (@features) {
|
|
print " $feature\n";
|
|
}
|
|
exit;
|
|
}
|
|
|
|
#
|
|
# must specify at least one zone file
|
|
#
|
|
exit() if ($opts{'h'} || $#ARGV == -1);
|
|
|
|
#
|
|
# load zone files
|
|
#
|
|
my $exitcode = 0;
|
|
my $parseerror;
|
|
my $errcount;
|
|
|
|
maybe_output_to_web();
|
|
while ($#ARGV > -1) {
|
|
$errcount = 0;
|
|
my $rulecount = 0;
|
|
my ($rulesrun, $errorsfound);
|
|
$current_zone_file = shift;
|
|
push @filestested, $current_zone_file;
|
|
$current_domain = shift;
|
|
$current_domain =~ s/\.$//; # remove potential trailing dot
|
|
|
|
#
|
|
# Start collecting TCPDUMP data if requested
|
|
#
|
|
my $tcpdumpproc;
|
|
if ($opts{'t'}) {
|
|
my $file = $opts{'o'};
|
|
$file =~ s/\%t/time()/eg; # replace %t with epoch
|
|
$file =~ s/\%d/$current_domain/g; # replace %d with domain
|
|
my @args = ("-i", $opts{t},
|
|
"-f", $opts{T},
|
|
"-s", 4096,
|
|
"-w", $file);
|
|
if ($tcpdumpproc = fork()) {
|
|
# parent
|
|
sleep(2); # wait for child to get going
|
|
print STDERR "--- Starting tcpdump\n" if ($opts{v});
|
|
} else {
|
|
# child
|
|
|
|
# close stderr/out since we don't want the output
|
|
close(STDOUT);
|
|
close(STDERR);
|
|
|
|
open(STDOUT,">/dev/null");
|
|
open(STDERR,">/dev/null");
|
|
|
|
# exec tcpdump
|
|
exec($TCPDUMP, @args);
|
|
}
|
|
}
|
|
|
|
#
|
|
# Parse the file into an array
|
|
#
|
|
my $parse_error = $donuts->load_zone($current_zone_file, $current_domain);
|
|
my $rrset = $donuts->zone_records();
|
|
|
|
if ($opts{'V'}) {
|
|
print "Records parsed:\n";
|
|
dump_records($rrset, " ");
|
|
}
|
|
|
|
next if ($parse_error);
|
|
if (!$rrset) {
|
|
print STDERR "WARNING: failed to read $current_zone_file for an unknown reason\n";
|
|
print STDERR "$@\n" if ($@);
|
|
next;
|
|
}
|
|
|
|
#
|
|
# call each rule on each record
|
|
#
|
|
$donuts->output()->Separator();
|
|
$donuts->output()->StartSection("Donuts Analysis", "$current_domain");
|
|
($rulecount, $errcount) = $donuts->analyze($opts{'l'});
|
|
|
|
$donuts->show_statuses() if (!$opts{'q'});
|
|
$donuts->summarize_results() if (!$opts{'q'});
|
|
|
|
|
|
# print "$errcount errors found in $current_zone_file\n";
|
|
|
|
if (scalar($donuts->rules()) == -1) {
|
|
$donuts->output()->Comment("WARNING: no rules found to be executed!!!");
|
|
$donuts->output()->Comment("WARNING: (maybe use the --rules switch to fix this?)");
|
|
}
|
|
if ($#$rrset == -1) {
|
|
$donuts->output()->Comment("WARNING: no records found to be analyzed in $current_domain!!!");
|
|
}
|
|
if ($errcount) {
|
|
$exitcode = 1;
|
|
}
|
|
|
|
$donuts->output()->EndSection();
|
|
|
|
#
|
|
# stop tcpdump if we had started it
|
|
#
|
|
|
|
if ($tcpdumpproc) {
|
|
print STDERR "--- Stopping tcpdump.\n" if ($opts{v});
|
|
kill(15, $tcpdumpproc);
|
|
sleep(1);
|
|
kill(9, $tcpdumpproc);
|
|
}
|
|
}
|
|
$donuts->output()->EndOutput();
|
|
|
|
if ($opts{'show-gui'}) {
|
|
setup_gui();
|
|
display_gui_results();
|
|
}
|
|
exit($exitcode);
|
|
|
|
######################################################################
|
|
#
|
|
# GUI support (requires the QWizard module)
|
|
#
|
|
|
|
#
|
|
# setup: creates the qwizard instance and needed primaries
|
|
#
|
|
sub setup_gui {
|
|
return if (!$have_qw);
|
|
import QWizard;
|
|
|
|
# the primaries
|
|
$qw = $Getopt::Long::GUI_qw || new QWizard();
|
|
$qw->merge_primaries(\%primaries);
|
|
}
|
|
|
|
#
|
|
# calls QWizard
|
|
#
|
|
sub display_gui_results {
|
|
return if (!$have_qw);
|
|
$qw->reset_qwizard();
|
|
$qw->{'generator'}{'noheaders'} = 1;
|
|
$qw->magic('display_errors');
|
|
}
|
|
|
|
#
|
|
# returns the parent of a given node
|
|
#
|
|
sub get_gui_parent {
|
|
my ($wiz, $name) = @_;
|
|
return if ($name eq 'Errors');
|
|
return 'Errors' if ($name eq 'By Record Name' || $name eq 'By Rule Type');
|
|
return 'By Record Name' if ($name =~ /^location::/);
|
|
return 'By Rule Type' if ($name =~ /^rulename::/);
|
|
}
|
|
|
|
#
|
|
# returns the children of a given node
|
|
#
|
|
sub get_gui_children {
|
|
my ($wiz, $name) = @_;
|
|
return ['By Record Name', 'By Rule Type'] if ($name eq 'Errors');
|
|
|
|
if ($name eq 'By Record Name') {
|
|
my @ret;
|
|
map { push @ret, { name => 'location::' . $_,
|
|
label => $_ }
|
|
} keys(%{$outstructure{'location'}});
|
|
return \@ret;
|
|
}
|
|
|
|
if ($name eq 'By Rule Type') {
|
|
my @ret;
|
|
map { push @ret, { name => 'rulename::' . $_,
|
|
label => $_ }
|
|
} keys(%{$outstructure{'rulename'}});
|
|
return \@ret;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
#######################################################################
|
|
|
|
#
|
|
# subroutines for doing live queries on running systems
|
|
#
|
|
|
|
sub get_query {
|
|
my ($name, $type, $resolver) = @_;
|
|
$resolver = $netdns if (!$resolver);
|
|
my $query = $resolver->query($name, $type);
|
|
if ($query) {
|
|
return $query;
|
|
} else {
|
|
# print STDERR "DNS error " . $resolver->errorstring . "\n";
|
|
$netdns_error = $resolver->errorstring;
|
|
return;
|
|
}
|
|
}
|
|
|
|
sub live_query {
|
|
my $query = get_query(@_);
|
|
if ($query) {
|
|
return $query->answer;
|
|
}
|
|
return ();
|
|
}
|
|
|
|
#
|
|
# returns 0 when arrays of records are identical.
|
|
# returns -1 if the arrays are non-equal length
|
|
# returns the index+1 where the arrays differ otherwise.
|
|
sub compare_arrays {
|
|
my ($a1, $b1, $sortfun) = @_;
|
|
$sortfun = sub { $a cmp $b } if (!$sortfun);
|
|
return -1 if ($#$a1 != $#$b1);
|
|
my @a = sort $sortfun @$a1;
|
|
my @b = sort $sortfun @$b1;
|
|
|
|
for (my $i = 0; $i <= $#a && $i <= $#b; $i++) {
|
|
if ($a[$i]->string() ne $b[$i]->string()) {
|
|
return $i+1;
|
|
}
|
|
}
|
|
return $#a+1 if ($#a < $#b);
|
|
return $#b+1 if ($#a < $#a);
|
|
return 0;
|
|
}
|
|
|
|
sub compare_RR_arrays {
|
|
my ($a1, $b1, $hashval) = @_;
|
|
return -1 if ($#$a1 != $#$b1);
|
|
my @a = sort @$a1;
|
|
my @b = sort @$b1;
|
|
|
|
for (my $i = 0; $i <= $#$a1; $i++) {
|
|
print STDERR "$a[$i]{$hashval} ne $b[$i]{$hashval}\n";
|
|
if ($a[$i]{$hashval} ne $b[$i]{$hashval}) {
|
|
return $i;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub print_parse_error {
|
|
my ($line, $err) = @_;
|
|
$errcount++;
|
|
print STDERR "$current_zone_file:$line $err\n";
|
|
}
|
|
|
|
#
|
|
# setup for printing some things to a web page instead
|
|
#
|
|
sub maybe_output_to_web {
|
|
#
|
|
# some stuff for web purposes should be redirected to the screen
|
|
#
|
|
if (defined($Getopt::GUI::Long::GUI_qw) && $Getopt::GUI::Long::GUI_qw->{'generator'} =~ /HTML/) {
|
|
$donuts->set_output_format('html');
|
|
$donuts->set_output_location('stdout');
|
|
}
|
|
}
|
|
|
|
sub debug_dump_data {
|
|
my ($datastorage) = @_;
|
|
print "data dump: \n";
|
|
foreach my $domain (keys(%$datastorage)) {
|
|
print " $domain\n";
|
|
foreach my $type (keys(%{$datastorage->{$domain}})) {
|
|
print " $type: ", 1 + $#{$datastorage->{$domain}{$type}}, "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
sub records_sorter {
|
|
if ($a->name eq $b->name) {
|
|
return $a->type cmp $b->type;
|
|
}
|
|
return $a->name cmp $b->type;
|
|
}
|
|
|
|
sub dump_records {
|
|
my ($records, $prefix, $short) = @_;
|
|
$prefix = "" if (!defined($prefix));
|
|
$short = 0 if (!defined($short));
|
|
|
|
my $formatstring = $prefix . ($short ? "%-75.75s\n" : "%s\n");
|
|
|
|
foreach my $record (sort records_sorter @$records) {
|
|
printf($formatstring, $record->string);
|
|
}
|
|
}
|
|
|
|
# this is merely a convenience function for rule authors to place into
|
|
# rules so a break point can be put on the function to stop in a
|
|
# particular location within a rule definition.
|
|
sub break_here {
|
|
my $x = 1;
|
|
}
|
|
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
donuts - analyze DNS zone files for errors and warnings
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
donuts [-v] [-l LEVEL] [-r RULEFILES] [-i IGNORELIST]
|
|
[-C] [-c configfile] [-h] [-H] ZONEFILE DOMAINNAME...
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
B<donuts> is a DNS lint application that examines DNS zone files
|
|
looking for particular problems. This is especially important for
|
|
zones making use of DNSSEC security records, since many subtle
|
|
problems can occur. The default mode of operation assumes you want to
|
|
check for DNSSEC-related issues; to turn off the invocation of the
|
|
DNSSEC-related rules run B<donuts> with "-i DNSSEC".
|
|
|
|
If the B<Text::Wrap> Perl module is installed, B<donuts> will give better
|
|
output formatting.
|
|
|
|
=head2 QUERYING LIVE ZONES
|
|
|
|
If the I<ZONEFILE> may be a live name prefixed with "live:" and the
|
|
records afterwards will be queried and analyized as if it was in a
|
|
zonefile. For example, running donuts as:
|
|
|
|
donuts -i NSEC live:badsign-a,good-a test.dnssec-tools.org
|
|
|
|
Will query the test.dnssec-tools.org zone for the I<badsign-a> and
|
|
I<good-a> records, collect the data and run them through the donuts
|
|
analysis. Because other data is needed as well for the analysis to be
|
|
useful, donuts will also automatically collect the DNSKEYs, SOAs, and
|
|
NS records for the zone. Because NSEC records aren't loaded, it is
|
|
advisable to add that rule exclusion for rules to be run.
|
|
|
|
Other data types may be queried by appending a ':' and the type name
|
|
to a record. For example:
|
|
|
|
donuts -i NSEC live:good-a,good-aaaa:aaaa test.dnssec-tools.org
|
|
|
|
=head2 QUERYING LIVE ZONES WITH AFXR
|
|
|
|
If your host is allowed to query for afxr transfers of a particular
|
|
zone, you may use the I<afxr:> filename token to indicate it should
|
|
pull the zone data using a AFXR transfer.
|
|
|
|
donuts -i NSEC afxr: dnssec-tools.org
|
|
|
|
=head1 OPTIONS
|
|
|
|
=head2 Rule Set Configuration:
|
|
|
|
=over
|
|
|
|
=item -l I<LEVEL>
|
|
|
|
=item --level=I<LEVEL>
|
|
|
|
Sets the level of errors to be displayed. The default is level 5.
|
|
The maximum value is level 9, which displays many debugging results.
|
|
You probably want to run no higher than level 8.
|
|
|
|
=item -r I<RULEFILES>
|
|
|
|
=item --rules=I<RULEFILES>
|
|
|
|
A comma-separated list of rule files to load. The strings will be
|
|
passed to I<glob()> so * wildcards can be used to specify multiple files.
|
|
|
|
Defaults to B</usr/share/dnssec-tools/donuts/rules/*.txt> and
|
|
B<$HOME/.dnssec-tools/donuts/rules/*.txt>.
|
|
|
|
=item -i I<IGNORELIST>
|
|
|
|
=item --ignore=I<IGNORELIST>
|
|
|
|
A comma-separated list of regex patterns which are checked against
|
|
rule names to determine if some should be ignored. Run with I<-v> to
|
|
figure out rule names if you're not sure which rule is generating
|
|
errors you don't wish to see.
|
|
|
|
=item -f LIST
|
|
|
|
=item --features=LIST
|
|
|
|
The I<--features> option specifies additional rule features that should
|
|
be executed. Some rules are turned off by default because they are
|
|
more intensive or require a live network connection, for instance.
|
|
Use the I<--features> flag to turn them on. The LIST argument should be
|
|
a comma-separated list. Example usage:
|
|
|
|
--features live,nsec_check
|
|
|
|
Features available in the default rule set distributed with B<donuts>:
|
|
|
|
=over
|
|
|
|
=item live
|
|
|
|
The I<live> feature allows rules that need to perform live DNS queries
|
|
to run. Most of these I<live> rules query parent and children of the
|
|
current zone, when appropriate, to see that the parent/child
|
|
relationships have been built properly. For example, if you have a
|
|
DS record which authenticates the key used in a child zone the I<live>
|
|
feature will let a rule run which checks to see if the child is
|
|
actually publishing the DNSKEY that corresponds to the test zone's DS
|
|
record.
|
|
|
|
=item nsec_check
|
|
|
|
This checks all the NSEC or NSEC3 records (as appropriate for the
|
|
zone) to ensure the chain is complete and that no-overlaps exist. It
|
|
is fairly memory- and cpu-intensive in large zones.
|
|
|
|
=back
|
|
|
|
=back
|
|
|
|
=head2 Configuration File Options:
|
|
|
|
=over
|
|
|
|
=item -c I<CONFIGFILE>
|
|
|
|
=item --config-file=I<CONFIGFILE>
|
|
|
|
Parse a configuration file to change constraints specified by rules.
|
|
This defaults to B<$HOME/.donuts.conf>.
|
|
|
|
=item -C
|
|
|
|
=item --no-config
|
|
|
|
Don't read user configuration files at all, such as those specified by
|
|
the I<-c> option or the B<$HOME/.donuts.conf> file.
|
|
|
|
=back
|
|
|
|
=head2 Extra Live Query Options:
|
|
|
|
Live Queries are enabled through the use of the I<-f live> arguments.
|
|
These options are only useful if that feature has been enabled.
|
|
|
|
=over
|
|
|
|
=item -t I<INTERFACE>
|
|
|
|
=item --tcpdump-capture=I<INTERFACE>
|
|
|
|
Specifies that B<tcpdump> should be started on I<INTERFACE> (e.g.,
|
|
"eth0") just before B<donuts> begins its run of rules for each domain
|
|
and will stop it just after it has processed the rules. This is
|
|
useful when you wish to capture the traffic generated by the I<live>
|
|
feature, described above.
|
|
|
|
=item -T I<FILTER>
|
|
|
|
=item --tcpdump-filter=I<FILTER>
|
|
|
|
When B<tcpdump> is run, this I<FILTER> is passed to it for purposes of
|
|
filtering traffic. By default, this is set to I<port 53 || ip[6:2] &
|
|
0x1fff != 0>, which limits the traffic to traffic destined to port 53
|
|
(DNS) or fragmented packets.
|
|
|
|
=item -o I<FILE>
|
|
|
|
=item --tcpdump-output-file=I<FILE>
|
|
|
|
Saves the B<tcpdump>-captured packets to I<FILE>. The following
|
|
special fields can be used to help generate unique file names:
|
|
|
|
=over
|
|
|
|
=item %d
|
|
|
|
This is replaced with the current domain name being analyzed (e.g.,
|
|
"example.com").
|
|
|
|
=item %t
|
|
|
|
This is replaced with the current epoch time (i.e., the number of
|
|
seconds since Jan 1, 1970).
|
|
|
|
=back
|
|
|
|
This field defaults to I<%d.%t.pcap>.
|
|
|
|
=item --show-gui
|
|
|
|
[alpha code]
|
|
|
|
Displays a browsable GUI screen showing the results of the B<donuts> tests.
|
|
|
|
The B<QWizard> and B<Gtk2> Perl modules must be installed for this to work.
|
|
|
|
=back
|
|
|
|
=head2 Help Options
|
|
|
|
=over
|
|
|
|
=item -H
|
|
|
|
Displays the personal configuration file rules and tokens that are
|
|
acceptable in a configuration file. The output will
|
|
consist of a rule name, a token, and a description of its meaning.
|
|
|
|
Your configuration file (e.g., B<$HOME/.donuts.conf>) may have lines in it
|
|
that look like this:
|
|
|
|
# change the default minimum number of legal NS records from 2 to 1
|
|
name: DNS_MULTIPLE_NS
|
|
minnsrecords: 1
|
|
|
|
# change the level of the following rule from 8 to 5
|
|
name: DNS_REASONABLE_TTLS
|
|
level: 5
|
|
|
|
This allows you to override certain aspects of how rules are executed.
|
|
|
|
=item -R
|
|
|
|
Displays a list of all known rules along with their description (if
|
|
available).
|
|
|
|
=item -h
|
|
|
|
Displays a help message.
|
|
|
|
=item --help
|
|
|
|
Displays a help message more tailored to people who prefer long-style
|
|
options.
|
|
|
|
=item -q
|
|
|
|
Turns on a quieter output mode where only the errors and warnings are
|
|
shown. IE, the summary line of "N errors found ..." is not shown.
|
|
|
|
-q is ignored if a -v argument is present; the -v argument requests a
|
|
longer output summary and thus it doesn't make sense to use them both
|
|
at the same time.
|
|
|
|
=item -v
|
|
|
|
Turns on more verbose output. Multiple I<-v>'s will turn on increasing
|
|
amounts of output. The number of -v's will dictate output:
|
|
|
|
=item -s
|
|
|
|
Sorts the resource records so that the order they're processed in is
|
|
always consistent. If the incoming zone is not always consistently
|
|
ordered, the output can vary unless the resource records are always in
|
|
the same order. When sorted, however, they're always evaluated in the
|
|
same order even if the zone file (or similar) order changes, and thus
|
|
the output is consistent, making it easier for tools like I<diff> to
|
|
detect where changes occur in the output. This comes at a higher CPU
|
|
cost, as it takes more time to sort the entries.
|
|
|
|
=over
|
|
|
|
=item 1
|
|
|
|
Describes which rules are being loaded and extra detail for rules that found errors (rule Level and extra text detail)
|
|
|
|
=item 2
|
|
|
|
Even more detail about rules that found errors: file name, file line
|
|
number, rule type.
|
|
|
|
=item 3
|
|
|
|
Shows extra detail on the record text being analyzed (the detail is
|
|
not always available, however).
|
|
|
|
=item 4
|
|
|
|
Even more detail about rules that found errors: dumps the rule code itself.
|
|
|
|
=item 5
|
|
|
|
Even more detail about rules that found errors: dumps the internal
|
|
rule structure.
|
|
|
|
=back
|
|
|
|
=back
|
|
|
|
=head2 Obsolete Options
|
|
|
|
=over
|
|
|
|
=item -L
|
|
|
|
Obsolete command line option. Please use I<--features live> instead.
|
|
|
|
=back
|
|
|
|
=head1 EXAMPLES
|
|
|
|
Run B<donuts> in its default mode on the I<example.com> zone which is
|
|
contained in the B<db.example.com> file:
|
|
|
|
% donuts db.example.com example.com
|
|
|
|
Run B<donuts> with significantly more output, both in terms of verbosity
|
|
and in terms of the number of rules that are run to analyze the file:
|
|
|
|
% donuts -v -v --level 9 db.example.com example.com
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright 2004-2013 SPARTA, Inc. All rights reserved.
|
|
See the COPYING file included with the DNSSEC-Tools package for details.
|
|
|
|
=head1 AUTHOR
|
|
|
|
Wes Hardaker <hardaker@users.sourceforge.net>
|
|
|
|
=head1 SEE ALSO
|
|
|
|
For more information on the dnssec-tools project:
|
|
|
|
http://www.dnssec-tools.org/
|
|
|
|
For writing rules that can be loaded by B<donuts>:
|
|
|
|
B<Net::DNS::SEC::Tools::Donuts::Rule>,
|
|
|
|
General DNS and DNSSEC usage:
|
|
|
|
B<Net::DNS>, B<Net::DNS::SEC>
|
|
|
|
=cut
|