mirror of
https://github.com/zerotier/edge.git
synced 2026-05-22 16:25:05 -07:00
2875 lines
64 KiB
Perl
Executable File
2875 lines
64 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
#
|
|
# Copyright 2006-2014 SPARTA, Inc. All rights reserved. See the COPYING
|
|
# file distributed with this software for details.
|
|
#
|
|
#
|
|
# trustman
|
|
#
|
|
|
|
#
|
|
# 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 strict;
|
|
|
|
use Net::DNS;
|
|
use Net::DNS::SEC::Tools::conf;
|
|
use Net::DNS::SEC::Validator;
|
|
use Net::DNS::SEC::Tools::timetrans;
|
|
use Net::DNS::Packet;
|
|
use Net::SMTP;
|
|
use Getopt::Long qw(:config no_ignore_case_always);
|
|
use Sys::Syslog;
|
|
use IO::File;
|
|
use POSIX;
|
|
use Data::Dumper;
|
|
use File::Temp qw(tempfile);
|
|
$Data::Dumper::Purity = 1;
|
|
|
|
#
|
|
# Version information.
|
|
#
|
|
my $NAME = "trustman";
|
|
my $VERS = "$NAME version: 2.0.0";
|
|
my $DTVERS = "DNSSEC-Tools Version: 2.2.3";
|
|
|
|
#
|
|
# Detect required Perl modules.
|
|
#
|
|
use Net::DNS::SEC::Tools::BootStrap;
|
|
dnssec_tools_load_mods( 'Date::Parse' => "",
|
|
'Net::DNS::SEC' => "");
|
|
|
|
########################################################
|
|
#
|
|
# Time constants.
|
|
#
|
|
|
|
my $DAYS30 = 2592000;
|
|
my $DAYS15 = 129600;
|
|
my $HOUR = 3600;
|
|
my $DAY = 86400;
|
|
|
|
########################################################
|
|
#
|
|
# Option data.
|
|
#
|
|
my %opts = (
|
|
t => $HOUR, # default to one hour
|
|
v => 0, # verbose on
|
|
c => 0 # don't configure files
|
|
);
|
|
|
|
my @opts = (
|
|
'a|anchor_data_file=s',
|
|
'c|config=s',
|
|
'd|dtconfig=s',
|
|
'f|foreground|fg',
|
|
'k|dnsval_conf_file=s',
|
|
'h|help',
|
|
'L|syslog',
|
|
'm|mail_contact_addr=s',
|
|
'M|monitor',
|
|
'n|named_conf_file=s',
|
|
'N|no_error',
|
|
'o|root_hints_file=s',
|
|
'p|print',
|
|
'r|resolv_conf_file=s',
|
|
's|smtp_server=s',
|
|
'nomail',
|
|
'S|single_run',
|
|
't|sleeptime=i',
|
|
'T|tmp_dir=s',
|
|
'v|verbose',
|
|
'V|Version',
|
|
'w|hold_time=s',
|
|
'z|zone=s',
|
|
'norevoke',
|
|
);
|
|
|
|
#
|
|
# Option variables.
|
|
#
|
|
my $contactaddr;
|
|
my $dvfile;
|
|
my $holdtime;
|
|
my $monitor = ''; # Monitor flag.
|
|
my $ncfile;
|
|
my $newconf;
|
|
my $newkeyfile; # Holds data about newly detected keys,
|
|
my $resfile;
|
|
my $rhfile;
|
|
my $sleeptime;
|
|
my $smtpserver;
|
|
my $tmpdir;
|
|
|
|
my $errs = 0; # Option error count.
|
|
|
|
########################################################
|
|
#
|
|
# Monitoring data.
|
|
#
|
|
|
|
my $monstr = ''; # Monitor string.
|
|
|
|
my $cnt_dvkeys = 0; # Count of keys added to dnsval.conf.
|
|
my $cnt_nckeys = 0; # Count of keys added to named.conf.
|
|
my $cnt_hdtimedout = 0; # Count of hold-down timers reached.
|
|
my $cnt_hdtimerson = 0; # Count of hold-down timers in future.
|
|
my $cnt_newkeys = 0; # Count of new keys received.
|
|
my $cnt_rmpkeys = 0; # Count of removed pending new keys.
|
|
my $cnt_rmkeys = 0; # Count of keys removed from zone.
|
|
my $cnt_revkeys = 0; # Count of keys revoked from zone.
|
|
|
|
########################################################
|
|
#
|
|
# Data.
|
|
#
|
|
|
|
my %dtconf; # Contents of dnssec-tools.conf file.
|
|
|
|
my @zones = ();
|
|
my %revzones = ();
|
|
my %keystorage;
|
|
my %newkeys;
|
|
|
|
my %remkeys;
|
|
my %sleeptimes;
|
|
my %active_refresh_times;
|
|
my %zone_configfile_map;
|
|
my %zone_retry_times;
|
|
|
|
my $once;
|
|
my $norevoke = 0;
|
|
|
|
my $initrun = 1;
|
|
|
|
my ($conffileh, $tmpfileh, $tmpfile, $currenttmpdir, $oldsep);
|
|
|
|
###############################################################################
|
|
|
|
main();
|
|
exit(0);
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: main()
|
|
#
|
|
sub main
|
|
{
|
|
#
|
|
# Get our options and arguments.
|
|
#
|
|
optsandargs();
|
|
|
|
#
|
|
# Determine zones to be managed.
|
|
#
|
|
push @zones, split(/,/,$opts{'z'}) if($opts{'z'});
|
|
for(my $i = 0; $i <=$#zones; $i++)
|
|
{
|
|
$revzones{$zones[$i]} = $i;
|
|
}
|
|
|
|
#
|
|
# Loads the newkeys info.
|
|
#
|
|
load_newkeys();
|
|
|
|
#
|
|
# Build a new configuration file.
|
|
#
|
|
mknewconf();
|
|
|
|
#
|
|
# Retrieve zones to be monitored and their configured trust anchors.
|
|
#
|
|
get_zones_keys(\%keystorage);
|
|
|
|
#
|
|
# Start the daemon, if that's what we should do.
|
|
#
|
|
&daemonize if(!$opts{'f'});
|
|
|
|
#
|
|
# Check the keys.
|
|
do
|
|
{
|
|
my $newsleeptime;
|
|
|
|
checkkeys();
|
|
|
|
$newsleeptime = getsleeptime($sleeptime);
|
|
|
|
Verbose("sleeping for " . timetrans($newsleeptime) . "\n") if(! $once);
|
|
sleep($newsleeptime);
|
|
|
|
} while(! $once);
|
|
|
|
#
|
|
# If we're monitoring, print the contents of the monitor string.
|
|
#
|
|
monreport() if($monitor);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: optsandargs()
|
|
#
|
|
sub optsandargs
|
|
{
|
|
#
|
|
# Parse command-line options.
|
|
#
|
|
GetOptions(\%opts,@opts) || usage(1);
|
|
|
|
#
|
|
# Handle some immediate options.
|
|
#
|
|
usage(0) if($opts{'h'});
|
|
show_version() if($opts{'V'});
|
|
|
|
#
|
|
# Parse the DNSESC-Tools configuration file.
|
|
#
|
|
getdtconf();
|
|
|
|
#
|
|
# newkeyfile will hold data about new keys detected,
|
|
# but not yet added to config files (waiting for add_holddown_time
|
|
# to expire). Read this file if it exists, and write to it
|
|
# any time the %newkeys structure is modified.
|
|
#
|
|
$newkeyfile = $opts{'a'} ? $opts{'a'} : $dtconf{'taanchorfile'};
|
|
$newconf = $opts{'c'};
|
|
$resfile = $opts{'r'} ? $opts{'r'} : $dtconf{'taresolvconffile'};
|
|
$ncfile = $opts{'n'} ? $opts{'n'} : $dtconf{'tanamedconffile'};
|
|
$dvfile = $opts{'k'} ? $opts{'k'} : $dtconf{'tadnsvalconffile'};
|
|
$contactaddr = $opts{'m'} ? $opts{'m'} : $dtconf{'tacontact'};
|
|
$smtpserver = $opts{'s'} ? $opts{'s'} : $dtconf{'tasmtpserver'};
|
|
$tmpdir = $opts{'T'} ? $opts{'T'} : $dtconf{'tatmpdir'};
|
|
$sleeptime = $opts{'t'} ? $opts{'t'} : $dtconf{'tasleeptime'};
|
|
$holdtime = $opts{'w'} ? $opts{'w'} : $dtconf{'taholdtime'};
|
|
$norevoke = 1 if($opts{'norevoke'});
|
|
$rhfile = $opts{'o'} ? $opts{'o'} : '';
|
|
$monitor = $opts{'M'};
|
|
$once = $opts{'S'};
|
|
|
|
if(!$dvfile && !$ncfile)
|
|
{
|
|
print STDERR "Error: a dnsval.conf (-k) file or named.conf (-n) file must be specified.\n";
|
|
$errs++;
|
|
}
|
|
|
|
#
|
|
# Use a local root.hints file if we're running in a packed environment.
|
|
#
|
|
if(runpacked())
|
|
{
|
|
$rhfile = "$ENV{'PAR_TEMP'}/inc/root.hints" if($rhfile eq '');
|
|
$resfile = "$ENV{'PAR_TEMP'}/inc/resolv.conf" if($resfile eq '');
|
|
}
|
|
|
|
#
|
|
# Validate the files we've been given.
|
|
#
|
|
chkfile(1,0,$newkeyfile,"taanchorfile");
|
|
chkfile(1,0,$dvfile,"tadnsvalconffile");
|
|
chkfile(1,0,$ncfile,"tanamedconffile");
|
|
chkfile(1,0,$resfile,"taresolvconffile");
|
|
chkfile(1,0,$rhfile,"taroothintsfile");
|
|
chkfile(0,0,$tmpdir,"tatmpdir");
|
|
|
|
#
|
|
# Ensure we have an SMTP server.
|
|
#
|
|
if(!$smtpserver)
|
|
{
|
|
print STDERR "Error: tasmtpserver is undefined in configuration file and -s not given\n";
|
|
$errs++;
|
|
}
|
|
|
|
#
|
|
# Ensure we have a reporting method.
|
|
#
|
|
if(!$contactaddr && !$opts{'L'} && !$opts{'p'})
|
|
{
|
|
print STDERR "Error: No reporting method chosen; please select -m, -L, or -p.\n";
|
|
$errs++;
|
|
}
|
|
|
|
#
|
|
# If we're monitoring, adjust some settings so we're *only*
|
|
# monitoring and files won't be rebuilt.
|
|
#
|
|
if($monitor)
|
|
{
|
|
delete($opts{'v'});
|
|
$newconf = 0;
|
|
$opts{'f'} = 1;
|
|
$once = 1;
|
|
}
|
|
|
|
#
|
|
# Give a usage message if there were any errors.
|
|
#
|
|
if($errs)
|
|
{
|
|
print "\n";
|
|
usage(1);
|
|
}
|
|
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: getdtconf()
|
|
#
|
|
# Purpose: Parse the DNSESC-Tools configuration file. If the -dtconfig
|
|
# option was given, we'll use that instead of the default.
|
|
#
|
|
sub getdtconf
|
|
{
|
|
#
|
|
# Use a local config file if we're running as part of a packed
|
|
# configuration.
|
|
#
|
|
if(runpacked())
|
|
{
|
|
setconffile("$ENV{'PAR_TEMP'}/inc/dnssec-tools.conf");
|
|
}
|
|
|
|
#
|
|
# If there's a -dtconfig command line option, we'll use that.
|
|
#
|
|
setconffile($opts{'d'}) if(exists($opts{'d'}));
|
|
|
|
#
|
|
# Parse the dnssec-tools.conf file.
|
|
#
|
|
%dtconf = parseconfig();
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: load_newkeys()
|
|
#
|
|
# Purpose: Loads the newkeys info from a file.
|
|
#
|
|
sub load_newkeys
|
|
{
|
|
my $undefval = $/;
|
|
|
|
#
|
|
# If we're only monitoring, we won't worry about the newkey file.
|
|
#
|
|
return if($monitor);
|
|
|
|
#
|
|
# Load in the newkeys info from file if available.
|
|
#
|
|
if($newkeyfile eq '')
|
|
{
|
|
print STDERR "no newkey file; use -a option\n";
|
|
exit(2);
|
|
}
|
|
|
|
#
|
|
# Load in the newkeys info from file if available.
|
|
#
|
|
if(open(FILE, "< $newkeyfile") == 0)
|
|
{
|
|
print STDERR "can't open newkey file \'$newkeyfile\': $!\n";
|
|
exit(3);
|
|
}
|
|
|
|
undef $/;
|
|
|
|
eval <FILE>;
|
|
|
|
warn "can't recreate newkey data from file: $@" if $@;
|
|
|
|
close FILE;
|
|
$/ = $undefval;
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: mknewconf()
|
|
#
|
|
# Purpose: Create a new configuration file based on our options.
|
|
#
|
|
sub mknewconf
|
|
{
|
|
my $conffile;
|
|
|
|
#
|
|
# Only make a new config file if one was specified.
|
|
#
|
|
return if(! $newconf);
|
|
|
|
#
|
|
# Open the current config file.
|
|
#
|
|
$conffile = getconffile();
|
|
open(CONF,$conffile) or die "unable to open \"$conffile\".";
|
|
usage(1) unless $newconf;
|
|
|
|
#
|
|
# Open the new config file and copy some lines from old to new.
|
|
#
|
|
open(OUT,">$newconf") or die "unable to open \"$newconf\" for writing.";
|
|
while(<CONF>)
|
|
{
|
|
next if(/^tasleeptime/ && ($opts{'t'}));
|
|
next if(/^taholdtime/ && ($opts{'w'}));
|
|
next if(/^tasmtpserver/ && ($opts{'s'}));
|
|
next if(/^tacontact/ && ($opts{'m'}));
|
|
next if(/^taresolvconffile/ && ($opts{'r'}));
|
|
next if(/^tanamedconffile/ && ($opts{'n'}));
|
|
next if(/^tadnsvalconffile/ && ($opts{'k'}));
|
|
next if(/^taroothintsfile/ && ($opts{'o'}));
|
|
print OUT $_;
|
|
}
|
|
|
|
#
|
|
# Copy the option values to the new file.
|
|
#
|
|
print OUT "tasleeptime\t" . $sleeptime . "\n" if($opts{'t'});
|
|
print OUT "taholdtime\t" . $holdtime . "\n" if($opts{'w'});
|
|
print OUT "tasmtpserver\t" . $smtpserver . "\n" if($opts{'s'});
|
|
print OUT "tacontact\t" . $contactaddr . "\n" if($opts{'m'});
|
|
print OUT "taresolvconffile\t" . $resfile . "\n" if($opts{'r'});
|
|
print OUT "tanamedconffile\t" . $ncfile . "\n" if($opts{'n'});
|
|
print OUT "tadnsvalconffile\t" . $dvfile . "\n" if($opts{'k'});
|
|
print OUT "taroothintsfile\t" . $rhfile . "\n" if($opts{'o'});
|
|
|
|
#
|
|
# Clean up a little.
|
|
#
|
|
close (OUT);
|
|
close (CONF);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: get_zones_keys()
|
|
#
|
|
# Purpose: Retrieve zones to be monitored, and their configured trust
|
|
# anchors (keys), from config files (named.conf, dnsval.conf).
|
|
# Create the revzones structure for later use.
|
|
#
|
|
sub get_zones_keys
|
|
{
|
|
#
|
|
# If zones are specified on the command line, we will only check
|
|
# those zones. Otherwise, check all zones found in config files.
|
|
#
|
|
read_conf_file(\%keystorage, $ncfile, \%zone_configfile_map) if($ncfile);
|
|
read_dnsval_file(\%keystorage, $dvfile, \%zone_configfile_map) if($dvfile);
|
|
|
|
#
|
|
# If @zones exists now, we used only zones from the cmd line,
|
|
# so we're done. If not, we got zones from config files, and
|
|
# need to populate both @zones and %revzones.
|
|
#
|
|
if(!exists($zones[0]))
|
|
{
|
|
foreach my $z (keys(%keystorage))
|
|
{
|
|
$zones[$#zones + 1] = $z;
|
|
if(!(exists($revzones{$z})))
|
|
{
|
|
$revzones{$z} = $#zones +1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!@zones)
|
|
{
|
|
print STDERR "No zones to check, exiting....\n";
|
|
exit(4);
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: read_conf_file()
|
|
#
|
|
# Purpose: Reads in a named.conf style config file pointed to by $file.
|
|
# Looks for trust anchors using $pat and stores key information
|
|
# in $storage.
|
|
#
|
|
sub read_conf_file
|
|
{
|
|
my ($storage, $file, $configmap) = @_;
|
|
my $pat = "trusted-keys";
|
|
|
|
#
|
|
# This pattern is looking for a full name. regexp pulled from Fast.pm.
|
|
#
|
|
my $mfn = qr{[-\w\$\d*]+(?:\.[-\w\$\d]+)*\.?};
|
|
|
|
Verbose("Reading and parsing trust keys from $file\n");
|
|
|
|
open(FILE, "< $file") or die "can't open config file: $!\n";
|
|
|
|
while(<FILE>)
|
|
{
|
|
next if(! /$pat/);
|
|
|
|
while(<FILE>)
|
|
{
|
|
last if(/^\s*\};/);
|
|
|
|
next if(! /\s*($mfn)\s+(257)\s+(\d+)\s+(\d+)\s+\"(.+)\"\s*;/);
|
|
|
|
my $zonename = $1;
|
|
my ($flags, $protocol, $algorithm) = ($2, $3, $4);
|
|
my $key = $5;
|
|
|
|
#
|
|
# Lop off trailing dots, but make sure the root
|
|
# isn't an empty string.
|
|
#
|
|
$zonename =~ s/\.$//;
|
|
$zonename = '.' if($zonename eq '');
|
|
|
|
if(keys(%revzones))
|
|
{
|
|
#
|
|
# Only store key data from zones we're actually
|
|
# checking (@zones) if zones were supplied on
|
|
# the command line (-z).
|
|
#
|
|
next if(! exists($revzones{$zonename}));
|
|
|
|
$key =~ s/[\n\r\s]//g;
|
|
|
|
# Need to remember where these keys came from.
|
|
$configmap->{$zonename} = $file;
|
|
|
|
my $newstorageobj =
|
|
{
|
|
flags => $flags,
|
|
protocol => $protocol,
|
|
algorithm => $algorithm,
|
|
key => $key,
|
|
};
|
|
|
|
Verbose(" Found a key for zone \"$zonename\"\n");
|
|
push (@{$storage->{$zonename}}, $newstorageobj);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
close(FILE);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: read_dnsval_file()
|
|
#
|
|
# Purpose: Reads in a dnsval.conf style config file pointed to by $file.
|
|
# Looks for trust anchors using $pat and stores key
|
|
# information in $storage.
|
|
#
|
|
sub read_dnsval_file
|
|
{
|
|
my ($storage, $file, $configmap) = @_;
|
|
my $pat = "trust-anchor";
|
|
|
|
Verbose("Reading and parsing trust keys from $file\n");
|
|
|
|
start_read_config($file);
|
|
|
|
my $fh = $conffileh;
|
|
|
|
while(read_next_ta_chunk())
|
|
{
|
|
s/\s;\s*$//;
|
|
|
|
next if(! s/^\s*(\S*)\s*$pat\s*//);
|
|
|
|
my $trustanchor_type = $1;
|
|
|
|
while($_ ne '')
|
|
{
|
|
next if(s/^[\n\r]\s*//);
|
|
next if(s/^\s*#[^\n\r]*[\n\r]*//);
|
|
|
|
# last if(! s/^\s*(\S+)\s+("*[^"]+"|\S+)\s*//);
|
|
last if(! s/^\s*(\S+)\s+("+[^"]+"|DS[^\n\r]+)\s*//);
|
|
|
|
my ($zonename, $value) = ($1, $2);
|
|
$value =~ s/[\n\r]//g;
|
|
my ($flags, $proto, $algo, $key) = $value =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S[^"]+)/;
|
|
|
|
#
|
|
# Lop off trailing dots, but make sure the root
|
|
# isn't an empty string.
|
|
#
|
|
$zonename =~ s/\.$//;
|
|
$zonename = '.' if($zonename eq '');
|
|
|
|
if(keys(%revzones))
|
|
{
|
|
#
|
|
# Only store key data from zones we are
|
|
# actually checking (@zones) if zones
|
|
# were supplied on the command line (-z).
|
|
#
|
|
next if(! exists($revzones{$zonename}));
|
|
|
|
$configmap->{$zonename} = $file;
|
|
|
|
push @{$storage->{$zonename}},
|
|
{
|
|
flags => $flags,
|
|
protocol => $proto,
|
|
algorithm => $algo,
|
|
key => $key,
|
|
};
|
|
|
|
Verbose(" Found a key for zone \"$zonename\"\n");
|
|
$storage->{$zonename}[$#{$storage->{$zonename}}]{key} =~ s/\s+//g;
|
|
}
|
|
else
|
|
{
|
|
$configmap->{$zonename} = $file;
|
|
|
|
push @{$storage->{$zonename}},
|
|
{
|
|
flags => $flags,
|
|
protocol => $proto,
|
|
algorithm => $algo,
|
|
key => $key,
|
|
};
|
|
|
|
Verbose(" Found a key for zone \"$zonename\"\n");
|
|
$storage->{$zonename}[$#{$storage->{$zonename}}]{key} =~ s/\s+//g;
|
|
}
|
|
}
|
|
}
|
|
|
|
$fh->close;
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: monreport()
|
|
#
|
|
# Purpose: Gve a monitoring report. This is only called if
|
|
# the -monitor option was given.
|
|
#
|
|
sub monreport
|
|
{
|
|
$monstr = '';
|
|
|
|
monvalue($cnt_dvkeys,
|
|
"$cnt_dvkeys key added to dnsval.conf; ",
|
|
"$cnt_dvkeys keys added to dnsval.conf; ");
|
|
|
|
monvalue($cnt_nckeys,
|
|
"$cnt_nckeys key added to named.conf; ",
|
|
"$cnt_nckeys keys added to named.conf; ");
|
|
|
|
monvalue($cnt_hdtimedout,
|
|
"$cnt_hdtimedout hold-down timer reached; ",
|
|
"$cnt_hdtimedout hold-down timers reached; ");
|
|
|
|
monvalue($cnt_hdtimerson,
|
|
"$cnt_hdtimerson hold-down timer in future; ",
|
|
"$cnt_hdtimerson hold-down timers in future; ");
|
|
|
|
monvalue($cnt_newkeys,
|
|
"$cnt_newkeys new key received; ",
|
|
"$cnt_newkeys new keys received; ");
|
|
|
|
monvalue($cnt_rmpkeys,
|
|
"$cnt_rmpkeys removed pending key; ",
|
|
"$cnt_rmpkeys removed pending keys; ");
|
|
|
|
monvalue($cnt_rmkeys,
|
|
"$cnt_rmkeys key removed; ",
|
|
"$cnt_rmkeys keys removed; ");
|
|
|
|
monvalue($cnt_revkeys,
|
|
"$cnt_revkeys key revoked; ",
|
|
"$cnt_revkeys keys revoked; ");
|
|
|
|
|
|
$monstr =~ s/; $//;
|
|
|
|
$monstr = "nothing to report" if($monstr eq '');
|
|
|
|
print "$monstr\n";
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: save_newkeys()
|
|
#
|
|
# Purpose: Save any available new-key information.
|
|
#
|
|
sub save_newkeys
|
|
{
|
|
#
|
|
# If we're only monitoring, we won't worry about the newkey file.
|
|
#
|
|
return if($monitor);
|
|
|
|
#
|
|
# Load in the newkeys info from file if available.
|
|
#
|
|
if($newkeyfile eq '')
|
|
{
|
|
print STDERR "no newkey file; use -a option\n";
|
|
exit(2);
|
|
}
|
|
|
|
#
|
|
# Write the newkeys file whenever it's modified.
|
|
#
|
|
Verbose("Writing new keys to $newkeyfile\n");
|
|
|
|
if(open(FILE, "> $newkeyfile") == 0)
|
|
{
|
|
warn "can't open newkeys file \"$newkeyfile\": $!";
|
|
exit(3);
|
|
}
|
|
|
|
print FILE Data::Dumper->Dump([\%newkeys], [qw(*newkeys)]);
|
|
|
|
close FILE or warn "can't close newkeys file: $!";
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: chkfile()
|
|
#
|
|
# Purpose: Verify the given file exists and is a file or directory.
|
|
#
|
|
sub chkfile
|
|
{
|
|
my $fflag = shift; # File/directory flag.
|
|
my $reqd = shift; # Required flag.
|
|
my $name = shift; # Node name to check.
|
|
my $field = shift; # Node's description.
|
|
|
|
if($reqd && ($name eq ''))
|
|
{
|
|
print STDERR "Error: $field is not set in configuration file\n";
|
|
$errs++;
|
|
return;
|
|
}
|
|
|
|
return if(! $name);
|
|
|
|
if(! -e $name)
|
|
{
|
|
print STDERR "Error: \"$name\" does not exist\n";
|
|
$errs++;
|
|
return;
|
|
}
|
|
|
|
if($fflag)
|
|
{
|
|
if(! -f $name)
|
|
{
|
|
print STDERR "Error: \"$name\" is not a regular file\n";
|
|
$errs++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(! -d $name)
|
|
{
|
|
print STDERR "Error: \"$name\" is not a directory\n";
|
|
$errs++;
|
|
}
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: checkkeys()
|
|
#
|
|
# Purpose: Does most of the work for all of trustman.
|
|
#
|
|
sub checkkeys
|
|
{
|
|
my %keys_to_verify;
|
|
my @zones_to_check;
|
|
my $nowsecs = getnowsecs();
|
|
|
|
#
|
|
# Copy the keys from the key storage to the list of keys to verify.
|
|
#
|
|
foreach my $k (keys(%keystorage))
|
|
{
|
|
@{$keys_to_verify{$k}} = @{$keystorage{$k}};
|
|
}
|
|
|
|
#
|
|
# Check all zones to see if $active_refresh_times{$z} has been reached.
|
|
# We'll also add the zone if this is our first pass.
|
|
#
|
|
foreach my $z (@zones)
|
|
{
|
|
if(($nowsecs >= $active_refresh_times{$z}) || $initrun)
|
|
{
|
|
push @zones_to_check, $z;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Build a list of keys to verify. (I think...)
|
|
#
|
|
checkzones(\@zones_to_check,\%keys_to_verify);
|
|
|
|
#
|
|
# All zones have been queried, and queries have been processed.
|
|
#
|
|
|
|
#
|
|
# Check timing of new keys.
|
|
#
|
|
timing_check() if(%newkeys);
|
|
|
|
#
|
|
# If any keys have reached their hold-down time, remove them from
|
|
# the dnsval.conf and named.conf files.
|
|
#
|
|
removecheck() if(keys(%remkeys));
|
|
|
|
#
|
|
# Schedule the remaining trust anchors for hold-down checking.
|
|
#
|
|
sched_remove(\%keys_to_verify);
|
|
|
|
$initrun = 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: checkzones()
|
|
#
|
|
# Purpose: Check zones.
|
|
#
|
|
sub checkzones
|
|
{
|
|
my $ref0 = shift;
|
|
my $ref1 = shift;
|
|
my @zones_to_check = @$ref0;
|
|
my %keys_to_verify = %$ref1;
|
|
|
|
Verbose(" Checking zone keys for validity\n");
|
|
foreach my $z (@zones_to_check)
|
|
{
|
|
my $query;
|
|
my %pending; # Pending new keys.
|
|
|
|
#
|
|
# Get the zone's validated DNSKEY record.
|
|
#
|
|
$query = resolve_and_check_dnskey($z,$dvfile);
|
|
if(! $query)
|
|
{
|
|
vnotify("query for keys failed for zone $z\n");
|
|
|
|
compute_queryfail_sleepsecs(
|
|
$z,
|
|
$zone_retry_times{$z}{'ottl'},
|
|
$zone_retry_times{$z}{'sigexp'}
|
|
);
|
|
|
|
next;
|
|
}
|
|
|
|
#
|
|
# Add any new keys to this zone's list of pending new keys.
|
|
#
|
|
if(keys(%newkeys))
|
|
{
|
|
for(my $i = 0; $i <= $#{$newkeys{$z}}; $i++)
|
|
{
|
|
my $keyobj =
|
|
{
|
|
flags => $newkeys{$z}[$i]{flags},
|
|
protocol => $newkeys{$z}[$i]{protocol},
|
|
algorithm => $newkeys{$z}[$i]{algorithm},
|
|
key => $newkeys{$z}[$i]{key},
|
|
found => 0,
|
|
};
|
|
Verbose(" pending key for zone \"$z\"\n");
|
|
push (@{$pending{$z}}, $keyobj);
|
|
}
|
|
}
|
|
|
|
# check the RRSIG over the DNSKEY
|
|
|
|
#
|
|
# Calculate the sleep-time and refresh time for this DNSKEY
|
|
# based on one of its RRSIGs.
|
|
#
|
|
my $origttl;
|
|
foreach my $rrsigrec (grep { $_->type eq 'RRSIG' } $query->answer)
|
|
{
|
|
#
|
|
# This assumes that the orig TTLs are always the same
|
|
# (which they should be).
|
|
# XXX: Turn this into a warning if not?
|
|
#
|
|
$origttl = $rrsigrec->orgttl;
|
|
|
|
my $sigexp = $rrsigrec->sigexpiration;
|
|
my $retryobj = {
|
|
ottl => $origttl,
|
|
sigexp => $sigexp
|
|
};
|
|
|
|
$zone_retry_times{$z} = $retryobj;
|
|
|
|
my ($rsecs,$rtm) = compute_sleepsecs($origttl,$sigexp);
|
|
|
|
$sleeptimes{$z} = $rsecs;
|
|
$active_refresh_times{$z} = $rtm;
|
|
|
|
# Verbose(" $z ... refresh_secs=$rsecs, refresh_time=$rtm\n");
|
|
|
|
#
|
|
# Only need one sleep time per zone so we'll drop out.
|
|
#
|
|
# XXX: But should we have the shortest or the
|
|
# longest? (In theory they *should* be the same...)
|
|
#
|
|
last;
|
|
}
|
|
|
|
if(! $origttl)
|
|
{
|
|
Verbose("No original TTL found for \"$z\"???");
|
|
}
|
|
|
|
#
|
|
# If an RRSET is received which does NOT contain a pending
|
|
# new key, remove that new key from the %newkeys.
|
|
#
|
|
foreach my $keyrec (grep { $_->type eq 'DNSKEY' } $query->answer)
|
|
{
|
|
my $ttl = $keyrec->ttl;
|
|
my $key = $keyrec->key;
|
|
|
|
next if(! ($keyrec->flags & 1));
|
|
$key =~ s/\s+//g; # remove all spaces
|
|
|
|
#
|
|
# We don't care if a DNSKEY record is found with the
|
|
# revoke bit set unless it's a key we have stored.
|
|
# So, check for a match first.
|
|
#
|
|
if(compare_keys(\%keystorage, $z, $keyrec, $key) != 0)
|
|
{
|
|
my $notnewkey = 0;
|
|
|
|
# May be a new key, remember it.
|
|
# Check if this key is already in %newkeys.
|
|
#
|
|
# Also need to find any keys in %newkeys which
|
|
# do NOT appear in a subsequent RRSET.
|
|
|
|
if(keys(%newkeys))
|
|
{
|
|
for(my $i = 0; $i <= $#{$newkeys{$z}}; $i++)
|
|
{
|
|
next if(keycmp($newkeys{$z}[$i], $keyrec, $key) == 0);
|
|
|
|
$notnewkey = 1;
|
|
next if(!keys(%pending));
|
|
|
|
for(my $i = 0; $i <= $#{$pending{$z}}; $i++)
|
|
{
|
|
if(keycmp($pending{$z}[$i], $keyrec, $key))
|
|
{
|
|
$pending{$z}[$i]{found} = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!$notnewkey)
|
|
{
|
|
my $add_holddown_time = compute_add_holddown($origttl, $holdtime);
|
|
my $newkeyobj =
|
|
{
|
|
flags => $keyrec->flags,
|
|
protocol => $keyrec->protocol,
|
|
algorithm => $keyrec->algorithm,
|
|
key => $key,
|
|
holdtime => $add_holddown_time,
|
|
};
|
|
|
|
Verbose(" adding holddown for new key in $z ($add_holddown_time seconds from now)\n");
|
|
|
|
push(@{$newkeys{$z}},$newkeyobj);
|
|
|
|
my $notif = "A new key has been received for zone " . $z . ".\n It will be added when the add holddown time is reached.\n";
|
|
vnotify($notif);
|
|
save_newkeys();
|
|
$cnt_newkeys++;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Check if it has the revoke bit set or we aren't
|
|
# doing a revoke.
|
|
#
|
|
elsif(($keyrec->{flags} & 128) && ($norevoke == 0))
|
|
{
|
|
# this key is being revoked
|
|
# print "key being revoked:\n";
|
|
# print_keyrec($keyrec);
|
|
|
|
if($dvfile)
|
|
{
|
|
revoke_ta_dnsvalconf($z,$keyrec);
|
|
}
|
|
if($ncfile)
|
|
{
|
|
revoke_ta_namedconf($z,$keyrec);
|
|
}
|
|
|
|
#
|
|
# Verify that ALL keys in %keystorage (now
|
|
# %keys_to_verify) were matched. If a known
|
|
# key disappears, set its remove_holddown timer
|
|
# for removal if it doesn't reappear in time.
|
|
}
|
|
else
|
|
{
|
|
#
|
|
# If this is neither a new key, nor a revoked
|
|
# key if it is a configured trust anchor, delete
|
|
# it from the keys_to_verify structure so we
|
|
# know it is not "removed".
|
|
#
|
|
for(my $i = 0; $i <= $#{$keys_to_verify{$z}}; $i++)
|
|
{
|
|
if(keycmp($keys_to_verify{$z}[$i], $keyrec, $key))
|
|
{
|
|
splice @{$keys_to_verify{$z}},$i,1;
|
|
}
|
|
}
|
|
|
|
#
|
|
# If it appears in the %remkeys struct, since
|
|
# it has now reappeared, remove it from remkeys.
|
|
#
|
|
if(keys(%remkeys))
|
|
{
|
|
for(my $i = 0; $i <= $#{$remkeys{$z}}; $i++)
|
|
{
|
|
if(keycmp($remkeys{$z}[$i], $keyrec, $key))
|
|
{
|
|
splice @{$remkeys{$z}},$i,1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#
|
|
# Only want to remove pending keys which do not appear in
|
|
# this RRSET if the query was successful. Will deal with
|
|
# the unsuccessful query below.
|
|
#
|
|
for(my $k = 0; $k <= $#{$pending{$z}}; $k++)
|
|
{
|
|
#
|
|
# Any pending key still not marked found should be
|
|
# removed from %newkeys.
|
|
#
|
|
next if($pending{$z}[$k]{found});
|
|
|
|
for(my $j = 0; $j <= $#{$newkeys{$z}}; $j++)
|
|
{
|
|
#
|
|
# Find the entry in newkeys that corresponds
|
|
# to the pending key not found.
|
|
#
|
|
if(keycmp($newkeys{$z}[$j], $pending{$z}[$k]))
|
|
{
|
|
splice @{$newkeys{$z}},$j,1;
|
|
|
|
my $notif = "Pending new key for zone " . $z . " has been removed.\n";
|
|
vnotify($notif);
|
|
save_newkeys();
|
|
$cnt_rmpkeys++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: timing_check()
|
|
#
|
|
# Purpose: Check timing of new keys.
|
|
#
|
|
sub timing_check
|
|
{
|
|
my @newkeyzones;
|
|
|
|
Verbose("checking new keys for timing\n");
|
|
|
|
#
|
|
# If add_holddown_time has been reached, notify.
|
|
#
|
|
my $nowsecs = getnowsecs();
|
|
|
|
foreach my $z (keys(%newkeys))
|
|
{
|
|
for(my $i = 0; $i <= $#{$newkeys{$z}}; $i++)
|
|
{
|
|
if($nowsecs >= $newkeys{$z}[$i]{holdtime})
|
|
{
|
|
# notify about this key
|
|
Verbose(" hold-down timer for zone \"$z\" reached (now = $nowsecs > $newkeys{$z}[$i]{holdtime})\n");
|
|
mon(" hold-down timer for zone \"$z\" reached (now = $nowsecs > $newkeys{$z}[$i]{holdtime})\n");
|
|
push @newkeyzones, $z;
|
|
$cnt_hdtimedout++;
|
|
}
|
|
else
|
|
{
|
|
Verbose(" hold-down timer for key \"$z\" still in the future (" . timetrans($newkeys{$z}[$i]{holdtime}- $nowsecs) . ")\n");
|
|
mon(" hold-down timer for zone \"$z\" reached (now = $nowsecs > $newkeys{$z}[$i]{holdtime})\n");
|
|
$cnt_hdtimerson++;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach my $z (@newkeyzones)
|
|
{
|
|
#
|
|
# These are all zones for which new keys have reached
|
|
# their add hold-down time. Add these keys as new trust
|
|
# anchors to the appropriate config files.
|
|
#
|
|
if($ncfile && ($zone_configfile_map{$z} eq $ncfile))
|
|
{
|
|
add_ta_namedconf($z);
|
|
}
|
|
|
|
if($dvfile && ($zone_configfile_map{$z} eq $dvfile))
|
|
{
|
|
add_ta_dnsvalconf($z);
|
|
}
|
|
|
|
#
|
|
# Now that this key has been added to the appropriate config
|
|
# file(s), put it in keystorage and remove it from newkeys.
|
|
#
|
|
for(my $i =0; $i <= $#{$newkeys{$z}}; $i++)
|
|
{
|
|
my $newstorageobj =
|
|
{
|
|
flags => $newkeys{$z}[$i]{flags},
|
|
protocol => $newkeys{$z}[$i]{protocol},
|
|
algorithm => $newkeys{$z}[$i]{algorithm},
|
|
key => $newkeys{$z}[$i]{key},
|
|
};
|
|
push (@{$keystorage{$z}}, $newstorageobj);
|
|
|
|
splice @{$newkeys{$z}},$i,1;
|
|
save_newkeys();
|
|
}
|
|
}
|
|
|
|
#
|
|
# Mail it.
|
|
#
|
|
# if(($contactaddr) && (@newkeyzones))
|
|
# {
|
|
# mailcontact(0,$smtpserver,$contactaddr,@newkeyzones);
|
|
# }
|
|
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: removecheck()
|
|
#
|
|
# Purpose: See if any remkeys have reached their holdtimes. If so,
|
|
# remove them from the dnsval.conf file and the named.conf file.
|
|
#
|
|
sub removecheck
|
|
{
|
|
my $nowsecs = getnowsecs();
|
|
|
|
foreach my $z (keys(%remkeys))
|
|
{
|
|
for(my $i = 0; $i <= $#{$remkeys{$z}}; $i++)
|
|
{
|
|
next if($nowsecs < $remkeys{$z}[$i]{holdtime});
|
|
|
|
#
|
|
# Mark this for deletion.
|
|
#
|
|
if($zone_configfile_map{$z} eq $ncfile)
|
|
{
|
|
remove_ta_namedconf($z, $remkeys{$z}[$i]);
|
|
}
|
|
|
|
if($zone_configfile_map{$z} eq $dvfile)
|
|
{
|
|
remove_ta_dnsvalconf($z, $remkeys{$z}[$i]);
|
|
}
|
|
|
|
#
|
|
# Remove this key from remkeys now, it's been removed.
|
|
#
|
|
splice @{$remkeys{$z}},$i,1;
|
|
}
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: sched_remove()
|
|
#
|
|
# Purpose: Any zones or keys still in %keys_to_verify did not appear in
|
|
# a query, but are configured trust anchors. Set the remove
|
|
# hold-down time (30 days) for these keys and add to remkeys
|
|
# for processing on next go.
|
|
#
|
|
sub sched_remove
|
|
{
|
|
my $hr = shift;
|
|
my %keys_to_verify = %$hr;
|
|
|
|
foreach my $z (keys(%keys_to_verify))
|
|
{
|
|
my $remove_holddown_time = compute_remove_holddown();
|
|
|
|
for(my $i = 0; $i <= $#{$keys_to_verify{$z}}; $i++)
|
|
{
|
|
my $remkeyobj =
|
|
{
|
|
flags => $keys_to_verify{$z}[$i]{flags},
|
|
protocol => $keys_to_verify{$z}[$i]{protocol},
|
|
algorithm => $keys_to_verify{$z}[$i]{algorithm},
|
|
key => $keys_to_verify{$z}[$i]{key},
|
|
holdtime => $remove_holddown_time,
|
|
};
|
|
|
|
#
|
|
# Only add this key if it isn't already there.
|
|
#
|
|
my $addit = 1;
|
|
if(keys(%remkeys))
|
|
{
|
|
for(my $i = 0; $i <= $#{$remkeys{$z}}; $i++)
|
|
{
|
|
if(keycmp($remkeys{$z}[$i], $remkeyobj))
|
|
{
|
|
$addit = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if($addit)
|
|
{
|
|
push (@{$remkeys{$z}},$remkeyobj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: start_read_config()
|
|
#
|
|
sub start_read_config
|
|
{
|
|
my ($currentfile) = @_;
|
|
|
|
$oldsep = $/;
|
|
$/ = ";";
|
|
|
|
$conffileh = new IO::File;
|
|
|
|
Die("Failed to create a file handle for opening $currentfile")
|
|
if(!$conffileh);
|
|
|
|
if(!$conffileh->open("$currentfile"))
|
|
{
|
|
Die("Failed to open the file handle for reading $currentfile")
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: end_read_config()
|
|
#
|
|
sub end_read_config
|
|
{
|
|
$conffileh->close();
|
|
$/ = $oldsep;
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: start_tmpfile()
|
|
#
|
|
sub start_tmpfile
|
|
{
|
|
my ($currentfile) = @_;
|
|
my ($base, $suffix);
|
|
|
|
my $newfile = $currentfile;
|
|
|
|
$newfile =~ s/(.*)\/([^\/]*)(\.[^\/]*)/$2-XXXXXX/; # contains dir path
|
|
|
|
($currenttmpdir, $base, $suffix) = ($1, $2, $3);
|
|
|
|
#
|
|
# Doesn't contain directory path.
|
|
#
|
|
if(!$suffix)
|
|
{
|
|
$newfile =~ s/^([^\/]*)(\.[^\/]*)/$1-XXXXXX/;
|
|
($currenttmpdir, $base, $suffix) = ("/tmp",$1, $2);
|
|
}
|
|
|
|
if(!$suffix)
|
|
{
|
|
$newfile = "tmpfile-XXXXXX";
|
|
$currenttmpdir = "/tmp";
|
|
$suffix = ".conf";
|
|
}
|
|
|
|
$currenttmpdir = $tmpdir if($tmpdir);
|
|
($tmpfileh, $tmpfile) = tempfile($newfile, DIR => $currenttmpdir, SUFFIX => $suffix);
|
|
|
|
Die("Failed to open $currenttmpdir/$newfile.$suffix") if(!$tmpfileh);
|
|
|
|
start_read_config($currentfile);
|
|
|
|
Verbose("Opened $currenttmpdir/$tmpfile to create a replacement for $currentfile\n");
|
|
|
|
return($tmpfileh, $tmpfile);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: end_tmpfile()
|
|
#
|
|
sub end_tmpfile
|
|
{
|
|
my ($currentfile) = @_;
|
|
|
|
#
|
|
# If we're only monitoring, we won't worry about the tmp file.
|
|
#
|
|
return if($monitor);
|
|
|
|
end_read_config($currentfile);
|
|
$tmpfileh->close();
|
|
|
|
#
|
|
# Rename TMP to $ncfile.
|
|
#
|
|
my $origname = $currentfile . ".orig";
|
|
|
|
if(!rename($currentfile,$origname))
|
|
{
|
|
Die("Fatal Error: Failed to rename $currentfile to $origname");
|
|
}
|
|
|
|
if(!rename($tmpfile,$currentfile))
|
|
{
|
|
Die("Fatal Error: Failed to rename newly created $tmpfile to $currentfile;\nAn appropriate -T flag or tatmpdir setting may correct this problem.");
|
|
}
|
|
|
|
Verbose("Closed $tmpfile and renamed back to $currentfile\n");
|
|
$tmpfileh = undef;
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: read_next_ta_chunk()
|
|
#
|
|
# Purpose: This parses the dnsval.conf file into pieces. Specifically:
|
|
# - Reads in a section
|
|
# - Assures the bounding ';' was not within a comment
|
|
# - strips off any leading comments so the first section
|
|
# should be of the "name type" clause it's looking for
|
|
# (eg: ": trust-anchor").
|
|
# - leaves the text in $_
|
|
# - *iff* $tmpfileh is defined, prints out the stripped parts
|
|
# to ensure that they're saved back to the tmp file being
|
|
# created.
|
|
#
|
|
sub read_next_ta_chunk
|
|
{
|
|
$_ = <$conffileh>;
|
|
|
|
#
|
|
# The ; separator may be in the middle of a comment unfortunately.
|
|
# read in more lines if so.
|
|
#
|
|
while(/[\n\r]\s*#[^\n\r]*;\s*$/ || /^\s*#[^\n\r]*;\s*$/)
|
|
{
|
|
my $nextline = <$conffileh>;
|
|
last if($nextline eq '');
|
|
$_ .= $nextline;
|
|
}
|
|
|
|
#
|
|
# Weed out any comments that occur before the starting line we're
|
|
# looking for.
|
|
#
|
|
while(s/^(\s*#[^\n\r]*[\n\r])//)
|
|
{
|
|
print $tmpfileh $1 if(defined($tmpfileh));
|
|
}
|
|
|
|
return($_);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: find_next_ta_chunk_type()
|
|
#
|
|
# Purpose: Looks for a particular section of the dnsval.conf file.
|
|
# (Pretty much always "trust-anchor" is likely to be looked for.)
|
|
#
|
|
sub find_next_ta_chunk_type
|
|
{
|
|
my ($type) = @_;
|
|
|
|
while(read_next_ta_chunk())
|
|
{
|
|
|
|
my $zonefound = 0;
|
|
if(s/^(\s*\S+\s+$type\s*)//)
|
|
{
|
|
# Reprint the segment we just read and smashed.
|
|
print $tmpfileh "$1";
|
|
|
|
# Strip the trailing semi.
|
|
s/\s*;\s*$//;
|
|
|
|
# Return the rest for processing.
|
|
return($_);
|
|
}
|
|
else
|
|
{
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: add_ta_namedconf()
|
|
#
|
|
# Purpose: Add keys to a named.conf file which have been detected from
|
|
# a validated source, and have passed their add_holddown_time.
|
|
#
|
|
# Implements Section 2.4.1 of RFC 5011.
|
|
#
|
|
sub add_ta_namedconf
|
|
{
|
|
my $zone = @_;
|
|
|
|
return if(!$ncfile);
|
|
|
|
start_tmpfile($ncfile);
|
|
|
|
while(<$conffileh>)
|
|
{
|
|
print $tmpfileh $_;
|
|
|
|
if(/^trusted-keys/)
|
|
{
|
|
print $tmpfileh "\n\n";
|
|
for(my $i =0; $i <= $#{$newkeys{$zone}}; $i++)
|
|
{
|
|
my $newkey = $zone . " " .
|
|
$newkeys{$zone}[$i]{flags} . " " .
|
|
$newkeys{$zone}[$i]{protocol} . " " .
|
|
$newkeys{$zone}[$i]{algorithm} . " " .
|
|
$newkeys{$zone}[$i]{key} . "\";\n";
|
|
|
|
print $tmpfileh $newkey;
|
|
|
|
notify("New key added to " . $ncfile . " for zone " . $zone . "\n");
|
|
$cnt_nckeys++;
|
|
}
|
|
}
|
|
}
|
|
|
|
end_tmpfile($ncfile);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: add_ta_dnsvalconf()
|
|
#
|
|
# Purpose: Add keys to a dnsval.conf file which have been detected from
|
|
# a validated source, and have passed their add_holddown_time.
|
|
#
|
|
# Implements Section 2.4.1 of RFC 5011.
|
|
#
|
|
sub add_ta_dnsvalconf
|
|
{
|
|
my ($zone) = @_;
|
|
my $pat = "trust-anchor";
|
|
|
|
next if(!$dvfile);
|
|
|
|
start_tmpfile($dvfile);
|
|
|
|
while(find_next_ta_chunk_type($pat))
|
|
{
|
|
my $zonefound = 0;
|
|
|
|
#
|
|
# This is just looking to see if the zone we're adding a
|
|
# key for is already in the file (if it's not they've
|
|
# likely set a security expectation that allowed the key to
|
|
# be auto-added even though it's never been secured).
|
|
#
|
|
my $lookingfor = $_;
|
|
while($lookingfor)
|
|
{
|
|
# Skip comments and blank lines.
|
|
next if($lookingfor =~ s/^\s*#[^\n]*\n+//);
|
|
next if($lookingfor =~ s/^\s*\n//);
|
|
|
|
#
|
|
# Spot the actual zone-name/data combo.
|
|
#
|
|
$lookingfor =~ s/^\s*(\S+)\s+("*[^"]+"|\S+)\s*//;
|
|
my ($z, $val) = ($1, $2);
|
|
|
|
#
|
|
# Strip off the trailing dot from the zone name.
|
|
$z =~ s/\.$//;
|
|
|
|
if($z eq $zone)
|
|
{
|
|
$zonefound = 1;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Dump the original contents back out; this should preserve
|
|
# lines.
|
|
#
|
|
print $tmpfileh $_,"\n";
|
|
|
|
# Print the new keys.
|
|
for(my $i =0; $i <= $#{$newkeys{$zone}}; $i++)
|
|
{
|
|
my $newkeyentry = $zone . ". \"" .
|
|
$newkeys{$zone}[$i]{flags} . " " .
|
|
$newkeys{$zone}[$i]{protocol} . " " .
|
|
$newkeys{$zone}[$i]{algorithm} . " " .
|
|
$newkeys{$zone}[$i]{key} . "\"";
|
|
|
|
if($zonefound)
|
|
{
|
|
print $tmpfileh $newkeyentry . $2;
|
|
Verbose("Adding the following key to $dvfile:\n");
|
|
Verbose($newkeyentry . "\n");
|
|
notify("New key added to " . $dvfile . " for zone " . $zone . "\n");
|
|
$cnt_dvkeys++;
|
|
}
|
|
else
|
|
{
|
|
Verbose("Failed to find original zone key in $ncfile!\n");
|
|
}
|
|
}
|
|
|
|
print $tmpfileh "\n;";
|
|
}
|
|
|
|
end_tmpfile($dvfile);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: remove_ta_dnsvalconf()
|
|
#
|
|
# Purpose: Remove keys from a dnsval.conf file.
|
|
# This usually is required when a known key configured as a trust
|
|
# anchor disappears from the query results from a validated
|
|
# response, and remains missing for the required hold time.
|
|
#
|
|
sub remove_ta_dnsvalconf
|
|
{
|
|
my ($zone, $keyrec) = @_;
|
|
|
|
my $k = $keyrec->{key};
|
|
my $f = $keyrec->{flags};
|
|
my $p = $keyrec->{protocol};
|
|
my $a = $keyrec->{algorithm};
|
|
my $pat = "trust-anchor";
|
|
|
|
next if(!$dvfile);
|
|
|
|
start_tmpfile($dvfile);
|
|
|
|
while(find_next_ta_chunk_type($pat))
|
|
{
|
|
while($_ ne '' && s/^\s*(\S+)\s+("*[^"]+"|\S+)\s*//)
|
|
{
|
|
my ($z, $val) = ($1, $2);
|
|
|
|
#
|
|
# Strip off the trailing dot from the zone name.
|
|
#
|
|
$z =~ s/\.$//;
|
|
$val =~ s/[\n\r]//g;
|
|
|
|
if($z eq $zone)
|
|
{
|
|
my ($flags, $protocol, $algorithm, $key) = $val =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S[^"]+)/;
|
|
$key =~ s/\s+//g;
|
|
$k =~ s/[\n\r]//g;
|
|
|
|
if(($k eq $key) &&
|
|
($f eq $flags) &&
|
|
($p eq $protocol) &&
|
|
($a eq $algorithm))
|
|
{
|
|
# It's a match, comment it out.
|
|
print $tmpfileh "# The following key has been removed.\n";
|
|
|
|
my $remkeyrec = $z . ". " . $val;
|
|
print $tmpfileh "# " . $remkeyrec . "\n\n";
|
|
|
|
notify("The following key has been removed from zone " . $zone . ": " . $remkeyrec . "\n");
|
|
$cnt_rmkeys++;
|
|
}
|
|
else
|
|
{
|
|
#
|
|
# Add the trailing dot when printing
|
|
# the zone name.
|
|
#
|
|
print $tmpfileh $z . ". " . $val . "\n\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#
|
|
# Add the trailing dot when printing zone name.
|
|
#
|
|
print $tmpfileh $z . ". " . $val . "\n\n";
|
|
}
|
|
}
|
|
|
|
print $tmpfileh "\n;\n";
|
|
}
|
|
|
|
end_tmpfile($dvfile);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: remove_ta_namedconf()
|
|
#
|
|
# Purpose: Remove keys from a named.conf file.
|
|
# This usually is required when a known key configured as a trust
|
|
# anchor disappears from the query results from a validated
|
|
# response, and remains missing for the required hold time.
|
|
#
|
|
|
|
sub remove_ta_namedconf
|
|
{
|
|
my ($zone, $keyrec) = @_;
|
|
|
|
my $key = $keyrec->{key};
|
|
my $flags = $keyrec->{flags};
|
|
my $proto = $keyrec->{protocol};
|
|
my $algo = $keyrec->{algorithm};
|
|
my $pat = "^trusted-keys";
|
|
|
|
next if(!$ncfile);
|
|
|
|
my $trustsection = 0;
|
|
|
|
start_tmpfile($ncfile);
|
|
|
|
while(<$conffileh>)
|
|
{
|
|
if(s/^\s*$pat\s*//)
|
|
{
|
|
print $tmpfileh "trusted-keys {";
|
|
$trustsection = 1;
|
|
s/\s*\{//;
|
|
if($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
|
|
{
|
|
my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);
|
|
# strip off the trailing dot from the zone name
|
|
$z =~ s/\.$//;
|
|
$k =~ s/\s+//g;
|
|
$k =~ s/\"//g;
|
|
if($z eq $zone)
|
|
{
|
|
$key =~ s/[\n\r]//g;
|
|
$key =~ s/\"//g;
|
|
if(($key eq $k) &&
|
|
($flags eq $f) &&
|
|
($proto eq $p) &&
|
|
($algo eq $a))
|
|
{
|
|
# It's a match, comment it out.
|
|
print $tmpfileh $space; # attempting to preserve spacing
|
|
print $tmpfileh "# The following key has been removed.\n";
|
|
my $remkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
|
|
print $tmpfileh "# " . $remkeyrec . "\n";
|
|
notify("The following key has been removed from zone " . $zone . ": " . $remkeyrec . "\n");
|
|
$cnt_rmkeys++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Just print it, it's not the key we're looking for.
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
}
|
|
elsif($trustsection)
|
|
{
|
|
if(/\s*\};/)
|
|
{
|
|
$trustsection = 0;
|
|
print $tmpfileh "\n};\n";
|
|
}
|
|
elsif($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
|
|
{
|
|
my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);
|
|
|
|
#
|
|
# Strip off the trailing dot from the zone name.
|
|
#
|
|
$z =~ s/\.$//;
|
|
$k =~ s/\s+//g;
|
|
$k =~ s/\"//g;
|
|
|
|
if($z eq $zone)
|
|
{
|
|
$key =~ s/[\n\r]//g;
|
|
$key =~ s/\"//g;
|
|
|
|
if(($key eq $k) &&
|
|
($flags eq $f) &&
|
|
($proto eq $p) &&
|
|
($algo eq $a))
|
|
{
|
|
# It's a match, comment it out.
|
|
print $tmpfileh $space; # attempting to preserve spacing
|
|
print $tmpfileh "# The following key has been removed.\n";
|
|
my $remkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
|
|
print $tmpfileh "# " . $remkeyrec . "\n";
|
|
|
|
notify("The following key has been removed from zone " . $zone . ": " . $remkeyrec . "\n");
|
|
$cnt_rmkeys++;
|
|
}
|
|
else
|
|
{
|
|
# Just print it, it's not the key we're looking for.
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Just print it, it's not the zone we're looking for.
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
|
|
end_tmpfile($ncfile);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: revoke_ta_dnsvalconf()
|
|
#
|
|
# Purpose: Revoke keys marked for revocation in a query response
|
|
# from a validated zone.
|
|
#
|
|
# Implements Section 2.1 Revocation from RFC 5011.
|
|
#
|
|
sub revoke_ta_dnsvalconf
|
|
{
|
|
my ($zone,$keyrec) = @_;
|
|
my $pat = "trust-anchor";
|
|
|
|
next if(!$dvfile);
|
|
|
|
start_tmpfile($dvfile);
|
|
|
|
while(find_next_ta_chunk_type($pat))
|
|
{
|
|
while($_ ne '' && s/^\s*(\S+)\s+("[^"]+"|[^\n\r]+)\s*//)
|
|
{ #"
|
|
my ($z, $val) = ($1, $2);
|
|
|
|
#
|
|
# Strip off the trailing dot from the zone name.
|
|
#
|
|
$z =~ s/\.$//;
|
|
$val =~ s/[\n\r]//g;
|
|
|
|
if($z ne $zone)
|
|
{
|
|
# Not the revoked zone, put info back in file.
|
|
print $tmpfileh $z . ". " . $val . "\n\n";
|
|
next;
|
|
}
|
|
|
|
my ($flags, $protocol, $algorithm, $key) =
|
|
$val =~ /(\d+)\s+(\d+)\s+(\d+)\s+(\S[^"]+)/;
|
|
|
|
$key =~ s/\s+//g;
|
|
|
|
my $keyin = $keyrec->{key};
|
|
|
|
$keyin =~ s/[\n\r]//g;
|
|
|
|
#
|
|
# Flag will not match and prob doesn't matter, proto
|
|
# and algo probably do not matter either but I am
|
|
# leaving them in for now.
|
|
# $keyrec->{flags} eq $flags &&
|
|
if(($keyin eq $key) &&
|
|
($keyrec->{protocol} eq $protocol) &&
|
|
($keyrec->{algorithm} eq $algorithm))
|
|
{
|
|
# It's a match, comment it out.
|
|
print $tmpfileh "# The following key has been revoked.\n";
|
|
|
|
# Give the key new flag values.
|
|
my $revkeyrec = $z . ". \"" . $keyrec->{flags} . " " . $protocol . " " . $algorithm . " " . $key . "\"";
|
|
print $tmpfileh "# " . $revkeyrec . "\n\n";
|
|
|
|
notify("The following key has been revoked from zone " . $z . ":\n" . $revkeyrec . "\n");
|
|
$cnt_revkeys++;
|
|
}
|
|
else
|
|
{
|
|
# Not the revoked key, put info back in file.
|
|
print $tmpfileh $z . ". " . $val . "\n\n";
|
|
}
|
|
|
|
}
|
|
|
|
print $tmpfileh "\n;\n";
|
|
}
|
|
|
|
end_tmpfile($dvfile);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: revoke_ta_namedconf()
|
|
#
|
|
# Purpose: Revoke keys marked for revocation in a query response
|
|
# from a validated zone.
|
|
#
|
|
# Implements Section 2.1 Revocation from RFC 5011.
|
|
#
|
|
sub revoke_ta_namedconf
|
|
{
|
|
my ($zone,$keyrec) = @_;
|
|
my $pat = "^trusted-keys";
|
|
my $trustsection = 0;
|
|
|
|
next if(!$ncfile);
|
|
|
|
start_tmpfile($ncfile);
|
|
|
|
while(<$conffileh>)
|
|
{
|
|
if(s/^\s*$pat\s*//)
|
|
{
|
|
print $tmpfileh "trusted-keys {";
|
|
$trustsection = 1;
|
|
s/\s*\{//;
|
|
|
|
if($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
|
|
{
|
|
my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);
|
|
|
|
# Strip off the trailing dot from the zone name.
|
|
$z =~ s/\.$//;
|
|
$k =~ s/\s+//g;
|
|
$k =~ s/\"//g;
|
|
|
|
if($z eq $zone)
|
|
{
|
|
my $keyin = $keyrec->{key};
|
|
$keyin =~ s/[\n\r]//g;
|
|
if(($keyin eq $k) &&
|
|
($keyrec->{flags} eq $f) &&
|
|
($keyrec->{protocol} eq $p) &&
|
|
($keyrec->{algorithm} eq $a))
|
|
{
|
|
# It's a match, comment it out.
|
|
print $tmpfileh $space; # attempting to preserve spacing
|
|
print $tmpfileh "# The following key has been revoked.\n";
|
|
|
|
my $revkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
|
|
print $tmpfileh "# " . $revkeyrec . "\n";
|
|
|
|
notify("The following key has been revoked from zone " . $z . ":\n" . $revkeyrec . "\n");
|
|
$cnt_revkeys++;
|
|
}
|
|
else
|
|
{
|
|
# Just print it, it's not the
|
|
# zone we're looking for.
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Just print it, it's not the zone
|
|
# we're looking for.
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
}
|
|
elsif($trustsection)
|
|
{
|
|
|
|
if(/\s*\};/)
|
|
{
|
|
$trustsection = 0;
|
|
print $tmpfileh "\n};\n";
|
|
}
|
|
elsif($_ ne '' && /^(\s*\n*)(\S+)\s+(\d+)\s+(\d+)\s+(\d)+\s+(\"*[^"]+"|\S+)\s*/)
|
|
{
|
|
my ($space, $z, $f, $p, $a, $k) = ($1, $2, $3, $4, $5, $6);
|
|
# strip off the trailing dot from the zone name
|
|
$z =~ s/\.$//;
|
|
$k =~ s/\s+//g;
|
|
$k =~ s/\"//g;
|
|
if($z eq $zone)
|
|
{
|
|
my $keyin = $keyrec->{key};
|
|
$keyin =~ s/[\n\r]//g;
|
|
|
|
if(($keyin eq $k) &&
|
|
($keyrec->{flags} eq $f) &&
|
|
($keyrec->{protocol} eq $p) &&
|
|
($keyrec->{algorithm} eq $a))
|
|
{
|
|
# It's a match, comment it out.
|
|
print $tmpfileh $space; # attempting to preserve spacing
|
|
print $tmpfileh "# The following key has been revoked.\n";
|
|
my $revkeyrec = $z . ". " . $f . " " . $p . " " . $a . " " . "\"" . $k . "\";";
|
|
print $tmpfileh "# " . $revkeyrec . "\n";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Just print it, it's not the key
|
|
# we're looking for.
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
print $tmpfileh $_;
|
|
}
|
|
}
|
|
|
|
end_tmpfile($ncfile);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: resolve_and_check_dnskey()
|
|
#
|
|
# Purpose: Queries a zone to get the DNSKEY record; returns an answer
|
|
# only if it was validated. Called by checkzones().
|
|
#
|
|
sub resolve_and_check_dnskey
|
|
{
|
|
my ($z,$file) = @_;
|
|
my $validator;
|
|
my $rq;
|
|
my $pkt = undef;
|
|
|
|
Verbose(" Checking the live \"$z\" key\n");
|
|
|
|
$validator = new Net::DNS::SEC::Validator(resolv_conf => $resfile,
|
|
#log_target => "7:stderr",
|
|
dnsval_conf => $file,
|
|
root_hints => $rhfile);
|
|
if(! $validator)
|
|
{
|
|
Verbose("Help! Failed to create validator object using:\n resolv_conf: \'$resfile\', dnsval_conf: \'$file\', root_hints: \'$rhfile\'\n");
|
|
return(undef);
|
|
}
|
|
|
|
$rq = $validator->res_query($z, "IN", "DNSKEY");
|
|
if(! $rq)
|
|
{
|
|
Verbose("Help! resolving failed\n");
|
|
}
|
|
|
|
if($validator->isvalidated)
|
|
{
|
|
$pkt = new Net::DNS::Packet(\$rq);
|
|
}
|
|
else
|
|
{
|
|
Verbose("Help! Failed to validate keys for \"$z\"\n");
|
|
}
|
|
|
|
return($pkt);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: compute_add_holddown()
|
|
#
|
|
# Purpose: Used in implementation of Section 2.4.1 of RFC 5011.
|
|
#
|
|
sub compute_add_holddown
|
|
{
|
|
my ($ttl, $default) = @_;
|
|
my $holddown;
|
|
my $nowsecs = getnowsecs();
|
|
|
|
#
|
|
# Default to 30 days.
|
|
#
|
|
$default = $DAYS30 if(!$default);
|
|
|
|
#
|
|
# Return secs since the epoch as the time to release this holddown.
|
|
#
|
|
|
|
#
|
|
# Allow 5 seconds from now; unsafe undocumented debugging feature.
|
|
#
|
|
if($default == -42)
|
|
{
|
|
return($nowsecs + 5);
|
|
}
|
|
|
|
#
|
|
# Take the maximum of now+TTL or now+specified-default.
|
|
#
|
|
if($ttl > $default)
|
|
{
|
|
$holddown = $nowsecs + $ttl;
|
|
}
|
|
else
|
|
{
|
|
$holddown = $nowsecs + $default;
|
|
}
|
|
|
|
return($holddown);
|
|
}
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: compute_remove_holddown()
|
|
#
|
|
# Purpose: Used in implementation of Section 2.4.2 of RFC 5011.
|
|
#
|
|
# 30 days from "now"
|
|
#
|
|
sub compute_remove_holddown
|
|
{
|
|
my $holddown;
|
|
my $default = $DAYS30;
|
|
my $nowsecs = getnowsecs();
|
|
|
|
#
|
|
# Return secs since the epoch as the time to release this holddown.
|
|
#
|
|
$holddown = $nowsecs + $default;
|
|
return($holddown);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: compute_sleepsecs()
|
|
#
|
|
# Purpose: Compute the sleep time in seconds.
|
|
# min(expiration interval [sigexpiration - now],1/2 * ottl,
|
|
# 15 days).
|
|
#
|
|
# Implements Section 2.3 of RFC 5011.
|
|
#
|
|
sub compute_sleepsecs
|
|
{
|
|
my ($ottl,$sexp) = @_;
|
|
|
|
$sexp =~ s/(....)(..)(..)(..)(..)(..)/$1-$2-$3T$4:$5:$6/;
|
|
my $sigexp = str2time($sexp);
|
|
|
|
my $halfottl = $ottl / 2;
|
|
my $nowsecs = getnowsecs();
|
|
my $expinterval = $sigexp - $nowsecs;
|
|
my $actrefsecs;
|
|
|
|
if($halfottl < $expinterval)
|
|
{
|
|
if($halfottl < $DAYS15)
|
|
{
|
|
$actrefsecs = $halfottl;
|
|
}
|
|
else
|
|
{
|
|
$actrefsecs = $DAYS15;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if($expinterval < $DAYS15)
|
|
{
|
|
$actrefsecs = $expinterval;
|
|
}
|
|
else
|
|
{
|
|
$actrefsecs = $DAYS15;
|
|
}
|
|
}
|
|
|
|
return($actrefsecs,$actrefsecs+$nowsecs);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: compute_queryfail_sleepsecs()
|
|
#
|
|
# Purpose: Compute the number of seconds to sleep in case of a query
|
|
# failure. The sleep-time is then saved for the specified zone.
|
|
#
|
|
# MAX(1 hour, MIN(1 day, 0.1 * ottl, 0.1 *
|
|
# expiration interval[sigexpiration - now])
|
|
#
|
|
# Implements Section 2.3 of RFC 5011.
|
|
#
|
|
sub compute_queryfail_sleepsecs
|
|
{
|
|
my ($z,$ottl,$sexp) = @_;
|
|
$sexp =~ s/(....)(..)(..)(..)(..)(..)/$1-$2-$3T$4:$5:$6/;
|
|
my $sigexp = str2time($sexp);
|
|
my $tenth_ottl = $ottl / 10;
|
|
my $nowsecs = getnowsecs();
|
|
my $tenth_expinterval = ($sigexp - $nowsecs) / 10;
|
|
my $refreshsecs;
|
|
|
|
if($tenth_ottl < $tenth_expinterval)
|
|
{
|
|
if($tenth_ottl < $DAY)
|
|
{
|
|
$refreshsecs = $tenth_ottl;
|
|
}
|
|
else
|
|
{
|
|
$refreshsecs = $DAY;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if($tenth_expinterval < $DAY)
|
|
{
|
|
$refreshsecs = $tenth_expinterval;
|
|
}
|
|
else
|
|
{
|
|
$refreshsecs = $DAY;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Save the appropriate sleep-time, but with an hour as the minimum.
|
|
#
|
|
if($refreshsecs >= $HOUR)
|
|
{
|
|
$sleeptimes{$z} = $refreshsecs;
|
|
}
|
|
else
|
|
{
|
|
$sleeptimes{$z} = $HOUR;
|
|
}
|
|
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: compare_keys()
|
|
#
|
|
# Purpose: Compares the contents of two keys to see if the new one ($zone,
|
|
# $rec, and $keyin) matches a cached one previously stored (in
|
|
# $storage->{$zone}).
|
|
#
|
|
sub compare_keys
|
|
{
|
|
my ($storage, $zone, $rec, $keyin) = @_;
|
|
my $newkey = 1;
|
|
|
|
if(!exists($storage->{$zone}))
|
|
{
|
|
# What would nonexistence of this really mean?
|
|
}
|
|
|
|
for(my $i = 0; $i <= $#{$storage->{$zone}}; $i++)
|
|
{
|
|
# not testing: $storage->{$zone}[$i]{flags} eq $rec->flags &&
|
|
|
|
if(($storage->{$zone}[$i]{key} eq $keyin) &&
|
|
($storage->{$zone}[$i]{protocol} eq $rec->protocol) &&
|
|
($storage->{$zone}[$i]{algorithm} eq $rec->algorithm))
|
|
{
|
|
$newkey = 0;
|
|
|
|
# Any match is good enough, get out now.
|
|
$i = $#{$storage->{$zone}} + 1;
|
|
}
|
|
else
|
|
{
|
|
$newkey = 1;
|
|
}
|
|
}
|
|
|
|
return($newkey);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: keys_equal()
|
|
#
|
|
# Purpose: Computes key equality of two key references.
|
|
# Equality is based on: key, protocol, algorithm (not flags!)
|
|
#
|
|
# Returns: 1 : equal
|
|
# 0 : not equal
|
|
#
|
|
sub keys_equal
|
|
{
|
|
my ($key1, $key2) = @_;
|
|
|
|
if(($key1->{key} eq $key2->{key}) &&
|
|
($key1->{protocol} eq $key2->{protocol}) &&
|
|
($key1->{algorithm} eq $key2->{algorithm}))
|
|
{
|
|
return(1);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: keycmp()
|
|
#
|
|
# Purpose: Computes key equality of to key references.
|
|
# Equality is based on: key, protocol, algorithm, and flags.
|
|
#
|
|
# If the key argument (the third argument) wasn't given,
|
|
# we'll use the 'key' value from the second hash.
|
|
#
|
|
# (Might should consider combining keycmp() with keys_equal().)
|
|
#
|
|
# Returns: 1 : equal
|
|
# 0 : not equal
|
|
#
|
|
sub keycmp
|
|
{
|
|
my $key1 = shift;
|
|
my $key2 = shift;
|
|
my $key;
|
|
|
|
if(@_ > 0) { $key = shift; }
|
|
else { $key = $key2->{key}; }
|
|
|
|
if(($key1->{key} eq $key) &&
|
|
($key1->{flags} eq $key2->{flags}) &&
|
|
($key1->{protocol} eq $key2->{protocol}) &&
|
|
($key1->{algorithm} eq $key2->{algorithm}))
|
|
{
|
|
return(1);
|
|
}
|
|
|
|
return(0);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: key_in_keyarr()
|
|
#
|
|
# Purpose: Checks if a key is in a key array, returns first matching
|
|
# location.
|
|
# Based on: keys_equal()
|
|
#
|
|
# Returns: < 0 : key not in key array
|
|
# >= 0 : location of matching key in key array
|
|
#
|
|
sub key_in_keyarr
|
|
{
|
|
my ($key, $keyarr) = @_;
|
|
|
|
for(my $i=0; $i<$#{$keyarr}; $i++)
|
|
{
|
|
if(keys_equal($key, $keyarr->[$i]))
|
|
{
|
|
return($i);
|
|
}
|
|
}
|
|
|
|
return(-1);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: getsleeptime()
|
|
#
|
|
# Purpose: Find the shortest sleep time of all the zones.
|
|
# The sleep time is only used if we're doing more than
|
|
# one pass over the keys.
|
|
#
|
|
sub getsleeptime
|
|
{
|
|
my $slp = shift;
|
|
|
|
return(0) if($once);
|
|
|
|
foreach my $z (keys(%sleeptimes))
|
|
{
|
|
if(($slp > $sleeptimes{$z}) && ($sleeptimes{$z} > 0))
|
|
{
|
|
$slp = $sleeptimes{$z};
|
|
}
|
|
}
|
|
|
|
return($slp);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: getnowsecs()
|
|
#
|
|
# Purpose: Get the seconds for the current local time.
|
|
#
|
|
sub getnowsecs
|
|
{
|
|
my $now = localtime();
|
|
my $nowsecs = str2time($now);
|
|
|
|
return($nowsecs);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: print_keyrec()
|
|
#
|
|
# Purpose: Print a keyrec.
|
|
#
|
|
sub print_keyrec
|
|
{
|
|
my $rec = @_[0];
|
|
|
|
printf "flags: %s protocol: %s algo: %s\nkey:%s\n", $rec->{flags},
|
|
$rec->{protocol},$rec->{algorithm}, $rec->{key};
|
|
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: daemonize()
|
|
#
|
|
# Purpose: Run as a daemon.
|
|
#
|
|
sub daemonize
|
|
{
|
|
chdir '/' or die "Can't chdir to /: $!";
|
|
|
|
open STDIN, '/dev/null' or die "Can't read /dev/null: $!";
|
|
open STDERR, '>/dev/null' or die "Can't write to /dev/null: $!";
|
|
|
|
defined(my $pid = fork()) or die "Can't fork: $!";
|
|
exit(0) if $pid;
|
|
|
|
POSIX::setsid() or die "Can't start a new session: $!";
|
|
umask 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: mon()
|
|
#
|
|
# Purpose: Add a line to the monitor string.
|
|
#
|
|
sub mon
|
|
{
|
|
my ($msg) = @_;
|
|
|
|
$monstr .= "$msg\n" if($monitor);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: monvalue()
|
|
#
|
|
# Purpose: Add a line to the monitor string iff it has a positive value.
|
|
# If a plural form is provided, we'll check to see if the value
|
|
# is one or more than one. The appropriate form will be used.
|
|
#
|
|
sub monvalue
|
|
{
|
|
my $val = shift; # Value we're monitoring.
|
|
my $msgsing = shift; # Singular form of message.
|
|
my $msgplural = shift; # Plural form of message.
|
|
|
|
return if($val == 0);
|
|
|
|
if(($val == 1) || (! defined($msgplural)))
|
|
{
|
|
$monstr .= $msgsing;
|
|
}
|
|
else
|
|
{
|
|
$monstr .= $msgplural;
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: vnotify()
|
|
#
|
|
# Purpose: Send a notification, but only if the -v option was given.
|
|
#
|
|
sub vnotify
|
|
{
|
|
my ($msg) = @_;
|
|
|
|
notify($msg) if($opts{'v'});
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: notify()
|
|
#
|
|
# Purpose: Mails or logs notifications, depending on configuration.
|
|
#
|
|
sub notify
|
|
{
|
|
my ($message) = @_;
|
|
|
|
if($opts{'L'})
|
|
{
|
|
openlog('trustman','pid','user') || warn "could not open syslog";
|
|
syslog('warning',"%s", $message);
|
|
closelog();
|
|
}
|
|
|
|
if($opts{'p'})
|
|
{
|
|
$| = 1;
|
|
|
|
#
|
|
# If in verbose mode, make sure messages are easily detectable
|
|
# within the verbose output.
|
|
#
|
|
Verbose("v" x 70 . "\n");
|
|
print $message;
|
|
Verbose("^" x 70 . "\n");
|
|
}
|
|
|
|
if($smtpserver && $contactaddr && !$opts{'nomail'})
|
|
{
|
|
Verbose(" mailing $contactaddr\n");
|
|
mailcontact(0,$smtpserver,$contactaddr,$message);
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: mailcontact()
|
|
#
|
|
# Purpose: Emails a contact address with the error output.
|
|
#
|
|
sub mailcontact
|
|
{
|
|
my ($ok,$smtp,$contact,$msg) = @_;
|
|
my $fromaddr = 'trustman@localhost';
|
|
|
|
Verbose("sending mail to $contact\n");
|
|
|
|
#
|
|
# Set up the SMTP object and required data.
|
|
#
|
|
my $message = Net::SMTP->new($smtp) || die "failed to create smtp message";
|
|
$message->mail($fromaddr);
|
|
$message->to(split(/,\s*/,$contact));
|
|
$message->data();
|
|
|
|
# Create headers.
|
|
$message->datasend("To: " . $contact . "\n");
|
|
$message->datasend("From: " . $fromaddr . "\n");
|
|
|
|
# Create the body of the message: the warning.
|
|
$message->datasend("Subject: trustman notification\n\n");
|
|
$message->datasend($msg);
|
|
$message->datasend("\n\n");
|
|
|
|
# Finish and send the message.
|
|
$message->dataend();
|
|
$message->quit;
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: Verbose()
|
|
#
|
|
# Purpose: Prints something(s) to STDERR only if -v was specified.
|
|
#
|
|
sub Verbose
|
|
{
|
|
print STDERR @_ if($opts{'v'});
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: Die()
|
|
#
|
|
# Purpose: Prints a fatal error message to STDERR and exits.
|
|
#
|
|
sub Die
|
|
{
|
|
notify(join("",@_, "\n", "This is a fatal error. EXITING!\n"));
|
|
print STDERR @_,"\n";
|
|
print STDERR "This is a fatal error. EXITING!\n";
|
|
exit(5);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: usage()
|
|
#
|
|
# Purpose: Prints a command-usage message, with an optional error
|
|
# message, and exits.
|
|
#
|
|
sub usage
|
|
{
|
|
my $rc = shift; # Exit code.
|
|
my ($extratext) = @_;
|
|
|
|
print STDERR "\nError:\n $extratext\n\n" if($extratext);
|
|
|
|
print STDERR "trustman [-k /PATH/TO/DNSVAL.CONF] [-n /PATH/TO/NAMED.CONF] [-z ZONE] [-L] [-f]\n [-S] [-c OUTCONFIGFILE] [-d DNSSECCONFIGFILE] [-v] [-V]
|
|
|
|
File Options:
|
|
-anchor_data_file FILE (-a)
|
|
-config FILE (-c)
|
|
-dtconfig DTCONFFILE (-d)
|
|
-dnsval_conf_file /PATH/TO/DNSVAL.CONF (-k)
|
|
-named_conf_file /PATH/TO/NAMED.CONF (-n)
|
|
-resolv_conf_file CONFFILE (-r)
|
|
-root_hints_file /PATH/TO/root.hints (-o)
|
|
-tmp_dir TMPDIR (-T)
|
|
|
|
Logging and Output Options:
|
|
-mail_contact_addr EMAIL_ADDRESS (-m)
|
|
-smtp_server SMTPSERVERNAME (-s)
|
|
-no_error (-N)
|
|
-print (-p)
|
|
-syslog (-L)
|
|
-nomail
|
|
|
|
Operational Options:
|
|
-zone ZONE (-z)
|
|
-hold_time SECONDS (-w)
|
|
-single_run (-S)
|
|
-foreground (-f)
|
|
-sleeptime SECONDS (-t)
|
|
-monitor (-M)
|
|
|
|
Testing Options:
|
|
-norevoke
|
|
|
|
Help Options:
|
|
-help (-h)
|
|
-verbose (-v)
|
|
-version (-V)
|
|
|
|
Extra Notes:
|
|
- If a zone is not specified, all zones in the
|
|
key_containing_files will be checked.
|
|
|
|
- If missing options are not specified on the command
|
|
line, some values will be read from the dnssec-tools.conf.
|
|
Run with the -c flag to generate suitable dnssec-tools.conf
|
|
configuration lines.
|
|
";
|
|
|
|
exit($rc);
|
|
}
|
|
|
|
#----------------------------------------------------------------------
|
|
# Routine: show_version()
|
|
#
|
|
# Purpose: Prints a version message and exits.
|
|
#
|
|
sub show_version
|
|
{
|
|
print STDERR "$VERS\n";
|
|
print STDERR "$DTVERS\n";
|
|
exit(0);
|
|
}
|
|
|
|
##############################################################################
|
|
#
|
|
|
|
=pod
|
|
|
|
=head1 NAME
|
|
|
|
trustman - Manage keys used as trust anchors
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
trustman [options]
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
B<trustman> manages keys used by DNSSEC as trust anchors in compliance with
|
|
RFC5011. It may be used as a daemon for ongoing key verification or manually
|
|
for initialization and one-time key verification.
|
|
|
|
By default, B<trustman> runs as a daemon to ensure that keys stored locally in
|
|
configuration files still match the same keys fetched from the zone where they
|
|
are defined. In addition, these checks can be run once manually (B<-S>) and
|
|
in the foreground (B<-f>).
|
|
|
|
For each key mismatch check, if key mismatches are detected then B<trustman>
|
|
performs the following operations:
|
|
|
|
- sets an add hold-down timer for new keys;
|
|
- sets a remove hold-down timer for missing keys;
|
|
- removes revoked keys from the configuration file.
|
|
|
|
On subsequent runs, the timers are checked. If the timers have expired, keys
|
|
are added to or removed from the configuration file, as appropriate.
|
|
|
|
B<named.conf> and B<dnsval.conf> are the usual configuration files. These
|
|
files must be specified in the DNSSEC-Tools configuration file or in command
|
|
line options.
|
|
|
|
=head1 OPTIONS
|
|
|
|
B<trustman> takes a number of options, each of which is described in this
|
|
section. Each option name may be shortened to the minimum number of unique
|
|
characters, but some options also have an alias (as noted.) The single-letter
|
|
form of each option is denoted in parentheses, e.g.: B<-anchor_data_file>
|
|
(B<-a>).
|
|
|
|
=over 4
|
|
|
|
=item B<-anchor_data_file file (-a)>
|
|
|
|
A persistent data file for storing new keys waiting to be added.
|
|
|
|
=item B<-config file (-c) >
|
|
|
|
Create a configuration file for B<trustman> from the command line options
|
|
given. The existing DNSSEC-Tools configuration file is copied to the
|
|
specified configuration file, and new configuration entries are appended
|
|
corresponding to the command line options. B<trustman>-specific entries
|
|
already in the existing configuration file will be replaced with new entries
|
|
from the command line. This will allow fewer command line options to be
|
|
specified in the future.
|
|
|
|
=item B<-dnsval_conf_file /path/to/dnsval.conf (-k)>
|
|
|
|
A B<dnsval.conf> file to read, and possibly be updated.
|
|
|
|
=item B<-dtconfig config_file (-d)>
|
|
|
|
Name of an alternate DNSSEC-Tools configuration file to be processed.
|
|
If specified, this configuration file is used I<in place> of the normal
|
|
DNSSEC-Tools configuration file, B<not> in addition to it. Also, it will be
|
|
handled prior to I<keyrec> files, I<rollrec> files, and command-line options.
|
|
|
|
=item B<-foreground (-f)>
|
|
|
|
Run in the foreground. B<trustman> will still run in a loop.
|
|
To run once, use the B<-single_run> option instead.
|
|
|
|
=item B<-hold_time seconds (-w)>
|
|
|
|
The value of the hold-down timer. This is the number of seconds from the time
|
|
that a new key is found. Generally, the default and recommended value of 30
|
|
days should be used.
|
|
|
|
=item B<-mail_contact_addr email_address (-m)>
|
|
|
|
Mail address for the contact person to whom reports should be sent.
|
|
|
|
=item B<-monitor (-M)>
|
|
|
|
Indicates that B<trustman> was run from a monitoring system, and a summary
|
|
of events will be printed. Specifying this option automatically turns on
|
|
the B<-single_run> option and turns off the B<-verbose> option.
|
|
|
|
This was developed for use with the Nagios monitoring system, but it can
|
|
be adapted for other monitors.
|
|
|
|
=item B<-named_conf_file /path/to/named.conf (-n)>
|
|
|
|
A B<named.conf> file to read, and possibly update.
|
|
|
|
=item B<-nomail>
|
|
|
|
Prevents mail from being sent, even if an SMTP server was specified in the
|
|
configuration file. This is useful for only sending notifications via
|
|
B<stdout> (B<-p>) or B<syslog> (B<-L>).
|
|
|
|
=item B<-norevoke>
|
|
|
|
This option turns off checks for the REVOKE bit.
|
|
|
|
=item B<-no_error (-N)>
|
|
|
|
Send report even when there are no errors.
|
|
|
|
=item B<-print (-p)>
|
|
|
|
Log messages to B<stdout>.
|
|
|
|
=item B<-resolv_conf_file conffile (-r)>
|
|
|
|
A B<resolv.conf> file to read. B</dev/null> can be specified to force
|
|
I<libval> to recursively answer the query rather than asking other name
|
|
servers.)
|
|
|
|
=item B<-root_hints_file /path/to/root.hints (-o)>
|
|
|
|
A B<root.hints> file to read.
|
|
|
|
=item B<-single_run (-S)>
|
|
|
|
Do not loop, but run only once.
|
|
|
|
=item B<-sleeptime seconds (-t)>
|
|
|
|
The number of seconds to sleep between checks. Default is 3600 (one hour.)
|
|
|
|
=item B<-smtp_server smtpservername (-s)>
|
|
|
|
The SMTP server that B<trustman> should use to send reports by mail.
|
|
|
|
=item B<-syslog (-L)>
|
|
|
|
Log messages to B<syslog>.
|
|
|
|
=item B<-tmp_dir directory (-T)>
|
|
|
|
Specifies where temporary files should be created. This is used when
|
|
creating new versions of the B<dnsval.conf> and B<named.conf> files before
|
|
they are moved into place.
|
|
|
|
Files created in this directory will be B<renamed> to their final location.
|
|
You should ensure that this directory, the final B<dnsval.conf> location,
|
|
and the final B<named.conf> location are on the same disk partition.
|
|
Most operating systems will only rename files within a partition and will
|
|
give an error if told to rename a file from one partition to another.
|
|
|
|
=item B<-zone zone (-z)>
|
|
|
|
The zone to check. Specifying this option supersedes the default
|
|
configuration file.
|
|
|
|
=item B<-help (-h)>
|
|
|
|
Display a help message.
|
|
|
|
=item B<-verbose (-v)>
|
|
|
|
Gives verbose output.
|
|
|
|
=item B<-Version (-V)>
|
|
|
|
Displays the version information for B<trustman> and the DNSSEC-Tools package.
|
|
|
|
=back
|
|
|
|
=head1 CONFIGURATION
|
|
|
|
In addition to the command line arguments, the B<dnssec-tools.conf> file can
|
|
be configured with the following values to remove the need to use some of
|
|
the command-line options. The command-line options always override the
|
|
settings in the B<dnssec-tools.conf> file.
|
|
|
|
=over 4
|
|
|
|
=item B<taanchorfile file>
|
|
|
|
This specifies the file where B<trustman> state information will be kept.
|
|
This is equivalent to the B<-anchor_data_file> flag.
|
|
|
|
=item B<tacontact contact_email>
|
|
|
|
This is equivalent to the B<-mail_contact_addr> flag for specifying to whom
|
|
email notices will be sent.
|
|
|
|
=item B<tadnsvalconffile file>
|
|
|
|
This specifies the B<dnsval.conf> file to read and write.
|
|
This is equivalent to the B<-dnsval_conf_file> flag.
|
|
|
|
=item B<tanamedconffile file>
|
|
|
|
This specifies the B<named.conf> file to read and write.
|
|
This is equivalent to the B<-named_conf_file> flag.
|
|
|
|
=item B<taresolvconffile file>
|
|
|
|
This specifies the B<resolv.conf> file to use.
|
|
This is equivalent to the B<-resolv_conf_file> flag.
|
|
|
|
=item B<taroothintsfile file>
|
|
|
|
This specifies the B<root.hints> file to read.
|
|
This is equivalent to the B<-root_hints_file> flag.
|
|
|
|
=item B<tasmtpserver servername>
|
|
|
|
This is equivalent to the B<-smtp_server> flag for specifying the SMTP server
|
|
to which email notices will be sent.
|
|
|
|
=item B<tatmpdir directory>
|
|
|
|
This specifies where temporary files should be created. This is used when
|
|
creating new versions of the B<dnsval.conf> and B<named.conf> files before
|
|
they're moved into place.
|
|
|
|
See the note about renaming in the description of the B<-tmp_dir> option.
|
|
|
|
=back
|
|
|
|
=head1 EXIT CODES
|
|
|
|
B<trustman> may exit for the following reasons:
|
|
|
|
0 - Successful execution. In daemon mode, this may just mean
|
|
that the daemon was successfully started. The daemon itself
|
|
may exit with some other error.
|
|
|
|
1 - Invalid options were specified.
|
|
|
|
2 - No new-key file was specified.
|
|
|
|
3 - Unable to open the new-key file.
|
|
|
|
4 - Unable to determine a set of zones to check.
|
|
|
|
5 - Some form of file-management error was encountered.
|
|
|
|
=head1 COPYRIGHT
|
|
|
|
Copyright 2006-2014 SPARTA, Inc. All rights reserved.
|
|
See the COPYING file included with the DNSSEC-Tools package for details.
|
|
|
|
=head1 Author
|
|
|
|
Lindy Foster
|
|
|
|
(Current contact for B<trustman> is Wayne Morrison, tewok@tislabs.com.)
|
|
|
|
=head1 SEE ALSO
|
|
|
|
B<Net::DNS::SEC::Tools::conf.pm(3)>,
|
|
B<Net::DNS::SEC::Tools::defaults.pm(3)>,
|
|
|
|
B<dnssec-tools.conf(5)>
|
|
|
|
=cut
|