mirror of
https://github.com/zerotier/edge.git
synced 2026-05-22 16:25:05 -07:00
404 lines
11 KiB
Perl
Executable File
404 lines
11 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.
|
|
|
|
use Net::SMTP;
|
|
use strict;
|
|
use Net::DNS::SEC::Tools::conf;
|
|
use Net::DNS::SEC::Tools::QWPrimitives;
|
|
|
|
|
|
my $donuts = "donuts"; # Name for donuts script.
|
|
|
|
#
|
|
# If we're running packed, we'll use our local copy of donuts.
|
|
#
|
|
if (runpacked()) {
|
|
$donuts = "$ENV{'PAR_TEMP'}/inc/donuts";
|
|
}
|
|
|
|
my %opts = (z => 60*60*24,
|
|
t => '/tmp/donutsd',
|
|
f => $ENV{'USER'} || $ENV{'LOGNAME'},
|
|
s => 'localhost');
|
|
|
|
DTGetOptions(\%opts,
|
|
['a|donuts-arguments=s', 'Arguments to pass to donuts'],
|
|
['z|time-between-checks=s', 'Time between checks'],
|
|
['t|temporary-directory=s', 'Temporary directory to store results in'],
|
|
['i|input-list=s', 'File containing file/zonename/zonecontact pairings'],
|
|
['o|run-once', 'Run once and exit'],
|
|
['O|run-count=i', 'Number of times to run before exiting'],
|
|
'',
|
|
['GUI:separator', 'E-Mail options:'],
|
|
['s|smtp-server=s', 'SMTP server to mail mail through (localhost)'],
|
|
['f|from-address=s','From address to use when sending mail'],
|
|
['x|include-diff-output','Include diff output in mail'],
|
|
['e|email-summary-to=s', 'Email a result summary to ADDRESS'],
|
|
'',
|
|
['v|verbose','Turn on verbose output'],
|
|
) || exit();
|
|
|
|
my %zoneinfo;
|
|
|
|
if ($opts{'i'}) {
|
|
if (! -f $opts{'i'}) {
|
|
print STDERR "Can't find or read the file: $opts{i}\n";
|
|
exit 1;
|
|
}
|
|
|
|
if ($opts{i} =~ /.xml$/i) {
|
|
# xml configuration
|
|
require XML::Smart;
|
|
my $doc = XML::Smart->new($opts{i});
|
|
my $nodes = $doc->{'donutsd'}[0]{'zones'}[0]{'zone'};
|
|
foreach my $zone (@$nodes) {
|
|
push @ARGV, $zone->{'file'}, $zone->{'name'}, $zone->{'contact'};
|
|
if ($zone->{'donutsargs'}) {
|
|
$zoneinfo{$zone->{'name'}}{'donutsargs'} =
|
|
$zone->{'donutsargs'};
|
|
}
|
|
}
|
|
|
|
# parse command line arguments as well:
|
|
my $configs = $doc->{'donutsd'}[0]{'configs'}[0]{'config'};
|
|
foreach my $config (@$configs) {
|
|
if (!$opts{$config->{'flag'}}) {
|
|
$opts{$config->{'flag'}} = $config->{'value'};
|
|
}
|
|
}
|
|
} else {
|
|
# simple text file
|
|
open(I, $opts{'i'});
|
|
while (<I>) {
|
|
next if (/^\s*$/);
|
|
next if (/^\s*#/);
|
|
push @ARGV, split();
|
|
}
|
|
close(I);
|
|
}
|
|
}
|
|
|
|
if ($#ARGV == -1) {
|
|
print STDERR
|
|
"You must specify at least one ZONEFILE ZONENAME ZONECONTACT set\n";
|
|
exit 1;
|
|
}
|
|
|
|
if (($#ARGV+1)%3 != 0) {
|
|
print STDERR
|
|
"Arguments must be passed in triples of: ZONEFILE ZONENAME ZONECONTACT\n";
|
|
exit 1;
|
|
}
|
|
|
|
# create temporary directory
|
|
if (! -d $opts{'t'}) {
|
|
mkdir($opts{'t'});
|
|
}
|
|
|
|
my $loopcount = 0;
|
|
|
|
while (!$opts{o}) {
|
|
my @args = @ARGV;
|
|
while ($#args > -1) {
|
|
my $file = shift @args;
|
|
my $zonename = shift @args;
|
|
my $contactaddr = shift @args;
|
|
|
|
my $mailit;
|
|
|
|
verbose("running donuts on $file/$zonename");
|
|
System("$donuts $opts{a} $zoneinfo{$zonename}{'donutsargs'} $file $zonename > $opts{t}/$zonename.new 2>&1");
|
|
if (-f "$opts{t}/$zonename") {
|
|
verbose(" comparing results from last run");
|
|
system("diff -u $opts{t}/$zonename $opts{t}/$zonename.new > $opts{t}/$zonename.diff 2>&1");
|
|
$mailit = $?;
|
|
} else {
|
|
verbose(" there was no data from a previous run");
|
|
$mailit = 1;
|
|
}
|
|
if ($mailit) {
|
|
verbose(" output changed; mailing $contactaddr about $file");
|
|
mailcontact($contactaddr, "$opts{t}/$zonename.new", $zonename);
|
|
}
|
|
if (-f "$opts{t}/$zonename") {
|
|
unlink("$opts{t}/$zonename");
|
|
}
|
|
|
|
# extract last error line
|
|
if ($opts{a} =~ /(-v|--verbose)/ ||
|
|
$zoneinfo{$zonename}{'donutsargs'} =~ /(-v|--verbose)/) {
|
|
System("tail -6 $opts{t}/$zonename.new >> $opts{t}/donuts.summary.new");
|
|
} else {
|
|
System("tail -1 $opts{t}/$zonename.new >> $opts{t}/donuts.summary.new");
|
|
}
|
|
|
|
verbose(" $opts{t}/$zonename.new => $opts{t}/$zonename");
|
|
rename("$opts{t}/$zonename.new", "$opts{t}/$zonename");
|
|
}
|
|
|
|
# mail the global administrator a summary
|
|
if ($opts{'e'}) {
|
|
system("diff -u $opts{t}/donuts.summary $opts{t}/donuts.summary.new > $opts{t}/donuts.summary.diff 2>&1");
|
|
if ($?) {
|
|
mailcontact($opts{e}, "$opts{t}/donuts.summary.new", "all-zones");
|
|
}
|
|
}
|
|
verbose(" $opts{t}/donuts.summary.new => $opts{t}/donuts.summary");
|
|
rename("$opts{t}/donuts.summary.new", "$opts{t}/donuts.summary");
|
|
|
|
# check for limited looping
|
|
if (exists $opts{O}) {
|
|
if ($loopcount >= $opts{O}) { $opts{o} = 1; }
|
|
else { $loopcount++; }
|
|
}
|
|
# sleep and start again in a while
|
|
verbose("sleeping for $opts{z}\n");
|
|
sleep($opts{z});
|
|
}
|
|
|
|
######################################################################
|
|
# mailcontact()
|
|
# - emails a contact address with the donuts error output
|
|
sub mailcontact {
|
|
my ($contact, $file, $zonename) = @_;
|
|
|
|
if ($contact eq '') {
|
|
verbose(" Warning: invalid mail address: mail can not be sent");
|
|
return 1;
|
|
}
|
|
|
|
# set up the SMTP object and required data
|
|
my $message = Net::SMTP->new($opts{s});
|
|
if(! $message) {
|
|
print STDERR "unable to connect to mail host \"$opts{s}\"; not sending donuts results\n";
|
|
return;
|
|
}
|
|
|
|
$message->mail($opts{f});
|
|
$message->to(split(/,\s*/,$contact));
|
|
$message->data();
|
|
|
|
# create headers
|
|
$message->datasend("To: " . $contact . "\n");
|
|
$message->datasend("From: " . $opts{'f'} . "\n");
|
|
$message->datasend("Subject: donuts output for zone: $zonename\n\n");
|
|
|
|
# create the body of the message: the warning
|
|
$message->datasend("The donuts DNS zone-file syntax checker was run on \"$zonename\"\n");
|
|
$message->datasend("and there were resulting errors or errors that have changed since the last run.\n");
|
|
$message->datasend("The results of this run of donuts can be found below:\n\n");
|
|
$message->datasend("You will not receive another message until the output from donuts has changed.\n\n");
|
|
|
|
# create the body of the message: the donuts results
|
|
# Include the file
|
|
$message->datasend(("-" x 70) . "\n\n");
|
|
open(IF,"$file");
|
|
while (<IF>) {
|
|
$message->datasend($_);
|
|
}
|
|
close(IF);
|
|
|
|
# create the body of the message: the donuts results
|
|
# Include the file
|
|
if ($opts{'x'}) {
|
|
$message->datasend("\n" . ("-" x 70) . "\n\n");
|
|
$file =~ s/.new$/.diff/;
|
|
open(IF,"$file");
|
|
while (<IF>) {
|
|
$message->datasend($_);
|
|
}
|
|
close(IF);
|
|
}
|
|
|
|
# finish and send the message
|
|
$message->dataend();
|
|
$message->quit;
|
|
}
|
|
|
|
|
|
######################################################################
|
|
# verbose() routine
|
|
# - passes arguments to print iff $opts{v} is defined (ie, -v was specified)
|
|
#
|
|
sub verbose {
|
|
if ($opts{'v'}) {
|
|
print STDERR @_;
|
|
if ($_[$#_] !~ /\n$/) {
|
|
print STDERR "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
######################################################################
|
|
# System()
|
|
# - calls system but first calls verbose() on the args
|
|
sub System {
|
|
verbose(" running: ", @_);
|
|
system(@_);
|
|
}
|
|
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
B<donutsd> - Run the B<donuts> syntax checker periodically and report the
|
|
results to an administrator
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
donutsd [-z FREQ] [-t TMPDIR] [-f FROM] [-s SMTPSERVER] [-a DONUTSARGS]
|
|
[-x] [-v] [-i zonelistfile] [ZONEFILE ZONENAME ZONECONTACT]
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
B<donutsd> runs B<donuts> on a set of zone files every so often (the
|
|
frequency is specified by the I<-z> flag which defaults to 24 hours) and
|
|
watches for changes in the results. These changes may be due to the
|
|
time-sensitive nature of DNSSEC-related records (e.g., RRSIG validity
|
|
periods) or because parent/child relationships have changed. If any
|
|
changes have occurred in the output since the last run of B<donuts> on a
|
|
particular zone file, the results are emailed to the specified zone
|
|
administrator's email address.
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over
|
|
|
|
=item -v
|
|
|
|
Turns on more verbose output.
|
|
|
|
=item -o
|
|
|
|
Run once and quit, as opposed to sleeping or re-running forever.
|
|
|
|
=item -a ARGUMENTS
|
|
|
|
Specifies command line arguments to be passed to B<donuts> executions.
|
|
|
|
=item -z TIME
|
|
|
|
Sleeps TIME seconds between calls to B<donuts>. The DNSSEC-Tools
|
|
B<timetrans> program can be used to convert from large time units (e.g.,
|
|
weeks and days) to seconds.
|
|
|
|
=item -e ADDRESS
|
|
|
|
Mail ADDRESS with a summary of the results from all the files.
|
|
These are the last few lines of the B<donuts> output for each zone that
|
|
details the number of errors found.
|
|
|
|
=item -s SMTPSERVER
|
|
|
|
When sending mail, send it to the SMTPSERVER specified. The default
|
|
is I<localhost>.
|
|
|
|
=item -f FROMADDR
|
|
|
|
When sending mail, use FROMADDR for the From: address.
|
|
|
|
=item -x
|
|
|
|
Send the I<diff> output in the email message as well as the B<donuts> output.
|
|
|
|
=item -t TMPDIR
|
|
|
|
Store temporary files in TMPDIR.
|
|
|
|
=item -i INPUTZONES
|
|
|
|
See the next section details.
|
|
|
|
=back
|
|
|
|
=head1 ZONE ARGUMENTS
|
|
|
|
The rest of the arguments to B<donutsd> should be triplets of the
|
|
following information:
|
|
|
|
=over
|
|
|
|
=item ZONEFILE
|
|
|
|
The zone file to examine.
|
|
|
|
=item ZONENAME
|
|
|
|
The zonename that file is supposed to be defining.
|
|
|
|
=item ZONECONTACT
|
|
|
|
An email address of the zone administrator (or a comma-separated
|
|
list of addresses.) The results will be sent to this email address.
|
|
|
|
=back
|
|
|
|
Additionally, instead of listing all the zones you wish to monitor on
|
|
the command line, you can use the I<-i> flag which specifies a
|
|
file to be read listing the TRIPLES instead. Each line in this file
|
|
should contain one triple with white-space separating the arguments.
|
|
|
|
Example:
|
|
|
|
db.zonefile1.com zone1.com admin@zone1.com
|
|
db.zonefile2.com zone2.com admin@zone2.com,admin2@zone2.com
|
|
|
|
For even more control, you can specify an XML file (whose name must end in
|
|
B<.xml>) that describes the same information. This also allows for per-zone
|
|
customization of the B<donuts> arguments. The B<XML::Smart> Perl module must
|
|
be installed in order to use this feature.
|
|
|
|
<donutsd>
|
|
<zones>
|
|
<zone>
|
|
<file>db.example.com</file>
|
|
<name>example.com</name>
|
|
<contact>admin@example.com</contact>
|
|
<!-- this is not a signed zone therefore we'll
|
|
add these args so we don't display DNSSEC errors -->
|
|
<donutsargs>-i DNSSEC</donutsargs>
|
|
</zone>
|
|
</zones>
|
|
</donutsd>
|
|
|
|
The B<donutsd> tree may also contain a I<configs> section where
|
|
command-line flags can be specified:
|
|
|
|
<donutsd>
|
|
<configs>
|
|
<config><flag>a</flag><value>--features live --level 8</value></config>
|
|
<config><flag>e</flag><value>wes@example.com</value></config>
|
|
</configs>
|
|
<zones>
|
|
...
|
|
</zones>
|
|
</donutsd>
|
|
|
|
Real command line flags will be used in preference to those specified
|
|
in the B<.xml> file, however.
|
|
|
|
=head1 EXAMPLE
|
|
|
|
donutsd -a "--features live --level 8" -f root@example.com \
|
|
db.example.com example.com admin@example.com
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright 2005-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
|
|
|
|
B<donuts(8)>
|
|
|
|
B<timetrans(1)>
|
|
|
|
http://dnssec-tools.sourceforge.net
|
|
|
|
=cut
|