fetch-crl-3.0.22/0000755001343500074740000000000014147645373012447 5ustar davidgeminfetch-crl-3.0.22/fetch-crl0000755001343500074740000023574414147645373014263 0ustar davidgemin#! /usr/bin/perl -w # # @(#)$Id: fetch-crl3.pl.cin 3335 2021-11-25 08:34:59Z davidg $ # build version 3.0.22, release 1 # # Copyright 2010-2021 David Groep, Nationaal instituut voor # subatomaire fysica NIKHEF # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # package main; use strict; use Getopt::Long qw(:config no_ignore_case bundling); use POSIX; eval { require LWP or die; }; $@ and die "Please install libwww-perl (LWP)\n"; my $sccsid = '@(#)fetch-crl3 version 3.0.22'; # import modules that are needed but still external # (the installed version may have these packages embedded in-line) # require ConfigTiny and import ConfigTiny unless defined &ConfigTiny::new; require TrustAnchor and import TrustAnchor unless defined &TrustAnchor::new; require CRLWriter and import CRLWriter unless defined &CRLWriter::new; require FCLog and import FCLog unless defined &FCLog::new; require OSSL and import OSSL unless defined &OSSL::new; require CRL and import CRL unless defined &CRL::new; my $use_DataDumper = eval { require Data::Dumper; }; my $use_IOSelect = eval { require IO::Select; }; use vars qw/ $log $cnf /; # ########################################################################### # # ($cnf,$log) = &init_configuration(); # use Net::INET6Glue if so requested (is not a default module) if ( $cnf->{_}->{inet6glue} ) { eval { require Net::INET6Glue::INET_is_INET6 or die; }; $@ and die "Please install Net::INET6Glue before enabling inet6glue config\n"; } # verify local installation sanity for loaded modules $::log->getverbose > 6 and ! $use_DataDumper and $::log->err("Cannot set verbosity higher than 6 without Data::Dumper") and exit(1); $::cnf->{_}->{parallelism} and ! $use_IOSelect and $::log->err("Cannot use parallel retrieval without IO::Select") and exit(1); $use_DataDumper and $::log->verb(7,Data::Dumper::Dumper($cnf)); # set safe path if so requested $cnf->{_}->{path} and $ENV{"PATH"} = $cnf->{_}->{path} and $::log->verb(5,"Set PATH to",$ENV{"PATH"}); # set rcmode if present in config defined $cnf->{_}->{rcmode} and do { $::log->verb(4,"Setting exit status mode to ".$cnf->{_}->{rcmode}); $::log->setrcmode($cnf->{_}->{rcmode}) or exit($log->exitstatus); $::log->verb(2,"Exit status mode is set to ".$cnf->{_}->{rcmode}); }; # wait up to randomwait seconds to spread download load $cnf->{_}->{randomwait} and do { my $wtime = int(rand($cnf->{_}->{randomwait})); $::log->verb(2,"Sleeping $wtime seconds before continuing"); sleep($wtime); }; # the list of trust anchors to process comes from the command line and # all files in the infodir that are metadata or crl urls # in the next phase, the suffix will be stripped and the info file # when present preferred over the crlurl # my @metafiles = @ARGV; $::cnf->{_}->{"infodir"} and do { foreach my $fn ( map { glob ( $::cnf->{_}->{"infodir"} . "/$_" ); } "*.info", "*.crl_url" ) { next if $::cnf->{_}->{nosymlinks} and -l $fn; $fn =~ /.*\/([^\/]+)(\.crl_url|\.info)$/; push @metafiles, $1 unless grep /^$1$/,@metafiles or not defined $1; } }; @metafiles or $log->warn("No trust anchors to process") and exit($log->exitstatus); if ( $::cnf->{_}->{parallelism} ) { ¶llel_metafiles($::cnf->{_}->{parallelism}, @metafiles); } else { &process_metafiles( @metafiles ); } # run any post-processing if ( $::cnf->{_}->{"postexec"} ) { my @args = ( $::cnf->{_}->{"postexec"}, "v1", "global", $::cnf->{_}->{"infodir"}, $::cnf->{_}->{"cadir"}, $::cnf->{_}->{"output"} ); $::log->verb(2,"Executing global postscript @args"); my $postrc = system(@args); if ( $postrc == -1 ) { $::log->err("Cannot execute global postexec program: $!"); } elsif ( $postrc > 0 ) { $::log->err("Global postexec program returned error code ".($? >> 8)); } } $log->flush; exit($log->exitstatus); # ########################################################################### # # sub init_configuration() { my ($cnf,$log); my ($configfile,$agingtolerance,$infodir,$statedir,$cadir,$httptimeout); my ($output); my @formats; my $verbosity; my $quiet=0; my $help=0; my $showversion=0; my $debuglevel; my $parallelism=0; my $randomwait; my $nosymlinks; my $cfgdir; my $inet6glue=0; my %directives; $log = FCLog->new("qualified"); &GetOptions( "c|config=s" => \$configfile, "l|infodir=s" => \$infodir, "cadir=s" => \$cadir, "s|statedir=s" => \$statedir, "cfgdir=s" => \$cfgdir, "T|httptimeout=i" => \$httptimeout, "o|output=s" => \$output, "format=s@" => \@formats, "define=s" => \%directives, "v|verbose+" => \$verbosity, "h|help+" => \$help, "V|version+" => \$showversion, "q|quiet+" => \$quiet, "d|debug+" => \$debuglevel, "p|parallelism=i" => \$parallelism, "nosymlinks+" => \$nosymlinks, "a|agingtolerance=i" => \$agingtolerance, "r|randomwait=i" => \$randomwait, "inet6glue+" => \$inet6glue, ) or &help and exit(1); $help and &help and exit(0); $showversion and &showversion and exit(0); $configfile ||= ( -e "/etc/fetch-crl.conf" and "/etc/fetch-crl.conf" ); $configfile ||= ( -e "/etc/fetch-crl.cnf" and "/etc/fetch-crl.cnf" ); $cnf = ConfigTiny->new(); $configfile and $cnf->read($configfile) || die "Invalid config file $configfile:\n " . $cnf->errstr . "\n"; ( defined $cnf->{_}->{cfgdir} and $cfgdir = $cnf->{_}->{cfgdir} ) unless defined $cfgdir; $cfgdir ||= "/etc/fetch-crl.d"; if ( defined $cfgdir and -d $cfgdir and opendir(my $dh,$cfgdir) ) { while ( my $fn = readdir $dh ) { -f "$cfgdir/$fn" and -r "$cfgdir/$fn" and $cnf->read("$cfgdir/$fn"); } close $dh; } # add defined from the command line to the configuration, to the # main section _ thereof unless there is a colon in the key foreach my $k ( keys %directives ) { my $section ="_"; my $dvalue = $directives{$k}; if ( $k =~ m/(\w+):(.*)/ ) { $section = $1; $k=$2; } $cnf->{$section}->{$k} = $dvalue; } # command-line option overrides $cnf->{_}->{agingtolerance} = $agingtolerance if defined $agingtolerance; $cnf->{_}->{infodir} = $infodir if defined $infodir; $cnf->{_}->{cadir} = $cadir if defined $cadir; $cnf->{_}->{statedir} = $statedir if defined $statedir; $cnf->{_}->{httptimeout} = $httptimeout if defined $httptimeout; $cnf->{_}->{verbosity} = $verbosity if defined $verbosity; $cnf->{_}->{debuglevel} = $debuglevel if defined $debuglevel; $cnf->{_}->{output} = $output if defined $output; $cnf->{_}->{formats} = join "\001",@formats if @formats; $cnf->{_}->{parallelism} = $parallelism if $parallelism; $cnf->{_}->{randomwait} = $randomwait if defined $randomwait; $cnf->{_}->{nosymlinks} = $nosymlinks if defined $nosymlinks; $cnf->{_}->{inet6glue} = $inet6glue if $inet6glue; # deal with interaction of verbosity in logfile and quiet option # since a noquiet config option can cancel it if ( not defined $cnf->{_}->{noquiet} ) { if ( $quiet == 1) { $cnf->{_}->{verbosity} = -1; } } else { if ( $quiet >= 2) { $cnf->{_}->{verbosity} = -1; } } # key default values defined $cnf->{_}->{version} or $cnf->{_}->{version} = "3+"; defined $cnf->{_}->{packager} or $cnf->{_}->{packager} = "EUGridPMA"; defined $cnf->{_}->{openssl} or $cnf->{_}->{openssl} = "openssl"; defined $cnf->{_}->{agingtolerance} or $cnf->{_}->{agingtolerance} ||= 24; defined $cnf->{_}->{infodir} or $cnf->{_}->{infodir} = '/etc/grid-security/certificates'; defined $cnf->{_}->{output} or $cnf->{_}->{output} = $cnf->{_}->{infodir}; defined $cnf->{_}->{cadir} or $cnf->{_}->{cadir} = $cnf->{_}->{infodir}; defined $cnf->{_}->{statedir} or $cnf->{_}->{statedir} = "/var/cache/fetch-crl" if -d "/var/cache/fetch-crl" and -w "/var/cache/fetch-crl"; defined $cnf->{_}->{formats} or $cnf->{_}->{formats} = "openssl"; defined $cnf->{_}->{opensslmode} or $cnf->{_}->{opensslmode} = "dual"; defined $cnf->{_}->{httptimeout} or $cnf->{_}->{httptimeout} = 120; defined $cnf->{_}->{expirestolerance} or $cnf->{_}->{expirestolerance} = (7*60*60); # at least 7 hrs should nextUpdate be beyond the cache FreshUntil defined $cnf->{_}->{maxcachetime} or $cnf->{_}->{maxcachetime} = (4*24*60*60); # arbitrarily set it at 4 days defined $cnf->{_}->{nametemplate_der} or $cnf->{_}->{nametemplate_der} = "\@ANCHORNAME\@.\@R\@.crl"; defined $cnf->{_}->{nametemplate_pem} or $cnf->{_}->{nametemplate_pem} = "\@ANCHORNAME\@.\@R\@.crl.pem"; defined $cnf->{_}->{catemplate} or $cnf->{_}->{catemplate} = "\@ALIAS\@.pem\001". "\@ALIAS\@.\@R\@\001\@ANCHORNAME\@.\@R\@"; $cnf->{_}->{nonssverify} ||= 0; $cnf->{_}->{nocache} ||= 0; $cnf->{_}->{nosymlinks} ||= 0; $cnf->{_}->{verbosity} ||= 0; $cnf->{_}->{debuglevel} ||= 0; $cnf->{_}->{inet6glue} ||= 0; $cnf->{_}->{stateless} and delete $cnf->{_}->{statedir}; # expand array keys in config defined $cnf->{_}->{formats} and @{$cnf->{_}->{formats_}} = split(/[\001;,\s]+/,$cnf->{_}->{formats}); # sanity check on configuration $cnf->{_}->{statedir} and ! -d $cnf->{_}->{statedir} and die "Invalid state directory " . $cnf->{_}->{statedir} . "\n"; $cnf->{_}->{infodir} and ! -d $cnf->{_}->{infodir} and die "Invalid meta-data directory ".$cnf->{_}->{infodir}."\n"; # initialize logging $log->flush; $cnf->{_}->{logmode} and $log->destremove("qualified") and do { foreach ( split(/[,\001]+/,$cnf->{_}->{logmode}) ) { if ( /^syslog$/ ) { $log->destadd($_,$cnf->{_}->{syslogfacility}); } elsif ( /^(direct|qualified|cache)$/ ) { $log->destadd($_); } else { die "Invalid log destination $_, exiting.\n"; } } }; $log->setverbose($cnf->{_}->{verbosity}); $log->setdebug($cnf->{_}->{debuglevel}); return ($cnf,$log); } # ########################################################################### # # sub showversion() { (my $name = $0) =~ s/.*\///; print "$name version 3.0.22\n"; return 1; } sub help() { (my $name = $0) =~ s/.*\///; print <new(); $cnf->{_}->{"infodir"} and $ta->setInfodir($cnf->{_}->{"infodir"}); $ta->loadAnchor($f) or next; $ta->saveLogMode() and $ta->setLogMode(); $ta->loadState() or next; # using the HASH in the CA filename templates requires the CRL # is retrieved first to determinte the hash if ( $cnf->{_}->{"catemplate"} =~ /\@HASH\@/ ) { $ta->retrieve or next; $ta->loadCAfiles() or next; } else { $ta->loadCAfiles() or next; $ta->retrieve or next; } $ta->verifyAndConvertCRLs or next; my $writer = CRLWriter->new($ta); $writer->writeall() or next; $ta->saveState() or next; if ( $::cnf->{$ta->{"alias"}}->{"postexec"} ) { my @args = ( $::cnf->{$ta->{"alias"}}->{"postexec"}, "v1", "ta", $ta->{"alias"}, $ta->{"filename"}, $::cnf->{_}->{"cadir"}, $::cnf->{_}->{"output"} ); $::log->verb(2,"Executing postscript for ".$ta->{"alias"}.": @args"); my $postrc = system(@args); if ( $postrc == -1 ) { $::log->err("Cannot execute postexec program for".$ta->{"alias"}.": $!"); } elsif ( $postrc > 0 ) { $::log->err("postexec program for ".$ta->{"alias"}." returned error code ".($? >> 8)); } } $ta->restoreLogMode(); } return 1; } sub parallel_metafiles($@) { my $parallelism = shift; my @metafiles = @_; my %pids = (); # file handle by processID my %metafile_by_fh = (); # reverse map my $readset = new IO::Select(); my %logoutput = (); $| = 1; $::log->verb(2,"starting up to $parallelism worker processes"); while ( @metafiles or scalar keys %pids ) { # loop until we have started all possible retrievals AND have # collected all possible output ( @metafiles and (scalar keys %pids < $parallelism) ) and do { # we have metafiles left, and have spare process slots my $metafile = shift @metafiles; $logoutput{$metafile} = ""; my $cout; my $cpid = open $cout, "-|"; defined $cpid and defined $cout or $::log->err("Cannot fork ($metafile): $!") and next; $::log->verb(5,"LOOP: starting process $cpid for $metafile"); if ( $cpid == 0 ) { # I'm the child that should care for $metafile $0 = "fetch-crl worker $metafile"; $::log->cleanse(); $::log->destadd("qualified"); &process_metafiles($metafile); $::log->flush; exit($::log->exitstatus); } else { # parent $pids{$cpid} = $cout; $readset->add($cout); $metafile_by_fh{$cout} = $metafile; } }; # do a select loop over the outstanding requests to collect messages # if we are in the process of starting more processes, we just # briefly poll out pending output so as not to have blocking # children, but if we have started as many children as we ought to # we put in a longer timeout -- any output on a handle will # get us out of the select and into flushing mode again my $timeout = (@metafiles && (scalar keys %pids < $parallelism) ? 0.1:1); $::log->verb(6,"PLOOP: select with timeout $timeout"); my ( $rh_set ) = IO::Select->select($readset, undef, undef, $timeout); foreach my $fh ( @$rh_set ) { my $metafile = $metafile_by_fh{$fh}; # we know there is at least one byte to read, but also that # any client sends complete while (1) { my $char; my $length = sysread $fh, $char, 1; if ( $length ) { $logoutput{$metafile} .= $char; $char eq "\n" and last; } else { #expected a char but got eof $readset->remove($fh); close($fh); map { $pids{$_} == $fh and waitpid($_,WNOHANG) and delete $pids{$_} and $::log->verb(5,"Collected pid $_ (rc=$?),", length($logoutput{$metafile}),"bytes log output"); } keys %pids; last; } } } } # log out all collected log data from our children foreach my $metafile ( sort keys %logoutput ) { foreach my $line ( split(/\n/,$logoutput{$metafile}) ) { $line =~ /^ERROR\s+(.*)$/ and $::log->err($1); $line =~ /^WARN\s+(.*)$/ and $::log->warn($1); $line =~ /^VERBOSE\((\d+)\)\s+(.*)$/ and $::log->verb($1,$2); $line =~ /^DEBUG\((\d+)\)\s+(.*)$/ and $::log->debug($1,$2); } } return 1; } # # @(#)$Id: CRL.pm 2760 2014-12-13 08:15:43Z davidg $ # # package CRL; use strict; require OSSL and import OSSL unless defined &OSSL::new; use vars qw/ $log $cnf /; # Syntax: # CRL->new( [name [,data]] ); # CRL->setName( name); # CRL->setData( datablob ); # load a CRL in PEM format or bails out # CRL->verify( cafilelist ); # returns path to CA or undef if verify failed # # sub new { my $obref = {}; bless $obref; my $self = shift; $self = $obref; my $name = shift; my $data = shift; $self->{"name"} = "unknown"; $self->setName($name) if $name; $self->setData($data) if $data; return $self; } sub setName($$) { my $self = shift or die "Invalid invocation of CRL::setName\n"; my $name = shift; return 0 unless $name; $self->{"name"} = $name; return 1; } sub setData($$) { my $self = shift or die "Invalid invocation of CRL::setData\n"; my $data = shift; my $pemdata = undef; my $errormsg; my $openssl = OSSL->new() or $::log->err("OpenSSL not found") and return 0; # try to recognise data type and normalise to PEM string # but extract only the first blob of PEM (so max one CRL per data object) # if ( $data =~ /(^-----BEGIN X509 CRL-----\n[^-]+\n-----END X509 CRL-----$)/sm ) { $pemdata = $1; } elsif ( substr($data,0,1) eq "0" ) { # looks a bit like an ASN.1 SEQ ($pemdata,$errormsg) = $openssl->Exec3($data, qw/ crl -inform DER -outform PEM / ); $pemdata or $::log->warn("Apparent DER data for",$self->{"name"},"not recognised") and return 0; } else { $::log->warn("CRL data for",$self->{"name"},"not recognised"); return 0; } # extract other data from the pem blob with openssl (my $statusdata,$errormsg) = $openssl->Exec3($pemdata, qw/ crl -noout -issuer -sha1 -fingerprint -lastupdate -nextupdate -hash/); defined $statusdata or do { ( my $eline = $errormsg ) =~ s/\n.*//sgm; $::log->warn("Unable to extract CRL data for",$self->{"name"},$eline); return 0; }; $statusdata =~ /(?:^|\n)SHA1 Fingerprint=([^\n]+)\n/ and $self->{"sha1fp"} = $1; $statusdata =~ /(?:^|\n)issuer=([^\n]+)\n/ and $self->{"issuer"} = $1; $statusdata =~ /(?:^|\n)lastUpdate=([^\n]+)\n/ and $self->{"lastupdatestr"} = $1; $statusdata =~ /(?:^|\n)nextUpdate=([^\n]+)\n/ and $self->{"nextupdatestr"} = $1; $statusdata =~ /(?:^|\n)([0-9a-f]{8})\n/ and $self->{"hash"} = $1; $self->{"nextupdatestr"} and $self->{"nextupdate"} = $openssl->gms2t($self->{"nextupdatestr"}); $self->{"lastupdatestr"} and $self->{"lastupdate"} = $openssl->gms2t($self->{"lastupdatestr"}); #$self->{"nextupdate"} = time - 200; #$self->{"lastupdate"} = time + 200; $self->{"data"} = $data; $self->{"pemdata"} = $pemdata; return 1; } sub getLastUpdate($) { my $self = shift or die "Invalid invocation of CRL::getLastUpdate\n"; return $self->{"lastupdate"} || undef; } sub getNextUpdate($) { my $self = shift or die "Invalid invocation of CRL::getNextUpdate\n"; return $self->{"nextupdate"} || undef; } sub getAttribute($$) { my $self = shift or die "Invalid invocation of CRL::getAttribute\n"; my $key = shift; return $self->{$key} || undef; } sub getPEMdata($) { my $self = shift or die "Invalid invocation of CRL::getPEMdata\n"; $self->{"pemdata"} or $::log->err("Attempt to extract PEM data from bad CRL object", ($self->{"name"}||"unknown")) and return undef; return $self->{"pemdata"}; } sub verify($@) { my $self = shift or die "Invalid invocation of CRL::verify\n"; my $openssl = OSSL->new() or $::log->err("OpenSSL not found") and return 0; $self->{"pemdata"} or $::log->err("verify called on empty data blob") and return 0; my @verifyStatus = (); # openssl crl verify works against a single CA and does not need a # full chain to be present. That suits us file (checked with OpenSSL # 0.9.5a and 1.0.0a) my $verifyOK; foreach my $cafile ( @_ ) { -e $cafile or $::log->err("CRL::verify called with nonexistent CA file $cafile") and next; my ($dataout,$dataerr) = $openssl->Exec3($self->{"pemdata"}, qw/crl -noout -CAfile/,$cafile); $dataerr and $dataout .= $dataerr; $dataout =~ /verify OK/ and $verifyOK = $cafile and last; } $verifyOK or push @verifyStatus, "CRL signature failed"; $verifyOK and $::log->verb(4,"Verified CRL",$self->{"name"},"against $verifyOK"); $self->{"nextupdate"} or push @verifyStatus, "CRL nextUpdate determination failed"; $self->{"lastupdate"} or push @verifyStatus, "CRL lastUpdate determination failed"; if ( $self->{"nextupdate"} and $self->{"nextupdate"} < time ) { push @verifyStatus, "CRL has nextUpdate time in the past"; } if ( $self->{"lastupdate"} and $self->{"lastupdate"} > time ) { push @verifyStatus, "CRL has lastUpdate time in the future"; } return @verifyStatus; } 1; # # @(#)$Id: CRLWriter.pm 3334 2021-11-25 08:11:38Z davidg $ # # ########################################################################### # # # Syntax: # CRLWriter->new( [name [,index]] ); # CRLWriter->setTA( trustanchor ); # CRLWriter->setIndex( index ); # package CRLWriter; use strict; use File::Basename; use File::Temp qw/ tempfile /; require OSSL and import OSSL unless defined &OSSL::new; require base64 and import base64 unless defined &base64::b64encode; use vars qw/ $log $cnf /; sub new { my $obref = {}; bless $obref; my $self = shift; $self = $obref; my $name = shift; my $index = shift; $self->setTA($name) if defined $name; $self->setIndex($name) if defined $index; return $self; } sub getName($) { my $self = shift; return 0 unless defined $self; return $self->{"ta"}->getAnchorName; } sub setTA($$) { my $self = shift; my ($ta) = shift; return 0 unless defined $ta and defined $self; $ta->{"anchorname"} or $::log->err("CRLWriter::setTA called without uninitialised trust anchor") and return 0; $self->{"ta"} = $ta; return 1; } sub setIndex($$) { my $self = shift; my ($index) = shift; return 0 unless defined $self; $self->{"ta"} or $::log->err("CRLWriter::setIndex called without a loaded TA") and return 0; my $ta = $self->{"ta"}; $ta->{"crlurls"} or $::log->err("CRLWriter::setIndex called with uninitialised TA") and return 0; ! defined $index and delete $self->{"index"} and return 1; $index < 0 and $::log->err("CRLWriter::setIndex called with invalid index $index") and return 0; $index > $#{$ta->{"crlurls"}} and $::log->err("CRLWriter::setIndex index $index too large") and return 0; $self->{"index"} = $index; return 1; } sub updatefile($$%) { my $file = shift; my $content = shift; my %flags = @_; $content or return undef; $file or $::log->err("Cannot write content to undefined path") and return undef; my ( $basename, $path, $suffix ) = fileparse($file); # get content and do a comparison. If data identical, touch only # to update mtime (other tools like NGC Nagios use this mtime semantics) # my $olddata; my $mytime; -f $file and do { $mytime = (stat(_))[9]; { open OLDFILE,'<',$file or $::log->err("Cannot make backup of $file: $!") and return undef; binmode OLDFILE; local $/; $olddata = ; close OLDFILE; } }; if ( $flags{"BACKUP"} and $olddata ) { if ( -w $path ) { -e "$file~" and ( unlink "$file~" or $::log->warn("Cannot remove old backup $file~: $!") and return undef); if (open BCKFILE,'>',"$file~" ) { print BCKFILE $olddata; close BCKFILE; utime $mytime,$mytime, "$file~"; } else { $::log->warn("Cannot reate backup $file~: $!"); } } else { $::log->warn("Cannot make backup, $path not writable"); } } defined $olddata and $olddata eq $content and do { $::log->verb(4,"$file unchanged - touch only"); utime time,time,$file and return 1; $::log->warn("Touch of $file failed, CRL unmodified"); return 0; }; # write new CRL to file ($file in $path) - attempting to do # an atomic action to prevent a reace condition with clients # but do not insist if the $path is not writable for new files my $tmpcrlmode=((stat $file)[2] || 0644) & 07777; $::log->verb(5,"TMP file for $file mode $tmpcrlmode"); my $tmpcrl = File::Temp->new(DIR => $path, SUFFIX => '.tmp', PERMS => $tmpcrlmode, UNLINK => 1); if ( defined $tmpcrl ) { # we could create a tempfile next to current print $tmpcrl $content or $::log->err("Write to $tmpcrl: $!") and return undef; # atomic move, but no need to restore from backup on failure # and the unlink on destroy is implicit chmod $tmpcrlmode,$tmpcrl or $::log->err("chmod on $tmpcrl (to $tmpcrlmode): $!") and return undef; rename($tmpcrl, $file) or $::log->err("rename $tmpcrl to $file: $!") and return undef; # file was successfully renamed, so nothing left to unlink $tmpcrl->unlink_on_destroy( 0 ); } elsif ( open FH,'>',$file ) { # no adjecent write possible, fall back to rewrite print FH $content or $::log->err("Write to $file: $!") and return undef; close FH or $::log->err("Close on write of $file: $!") and return undef; } else { # something went wrong in opening the file for write, # so try and restore backup if that was selected $::log->err("Open for write of $file: $!"); $flags{"BACKUP"} and ! -s "$file" and -s "$file~" and do { #file has been clobbed, but backup OK unlink "$file" and link "$file~","$file" and unlink "$file~" or $::log->err("Restore of backup $file failed: $!"); }; return undef; } return 1; } sub writePEM($$$$) { my $self = shift; my $idx = shift; my $data = shift; my $ta = shift; defined $idx and $data and $ta or $::log->err("CRLWriter::writePEM: missing index or data") and return 0; my $output = $::cnf->{_}->{"output"}; $output = $::cnf->{_}->{"output_pem"} if defined $::cnf->{_}->{"output_pem"}; $output and -d $output or $::log->err("PEM target directory $output invalid") and return 0; my $filename = "$output/".$ta->{"nametemplate_pem"}; $filename =~ s/\@R\@/$idx/g; my %flags = (); $::cnf->{_}->{"backups"} and $flags{"BACKUP"} = 1; if ($data !~ /\n$/sm) { $::log->verb(5,"Appending newline to short PEM file",$filename); $data="$data\n"; } $::log->verb(3,"Writing PEM file",$filename); &updatefile($filename,$data,%flags) or return 0; return 1; } sub writeDER($$$$) { my $self = shift; my $idx = shift; my $data = shift; my $ta = shift; defined $idx and $data and $ta or $::log->err("CRLWriter::writeDER: missing index or data") and return 0; my $output = $::cnf->{_}->{"output"}; $output = $::cnf->{_}->{"output_der"} if defined $::cnf->{_}->{"output_der"}; $output and -d $output or $::log->err("DER target directory $output invalid") and return 0; my $filename = "$output/".$ta->{"nametemplate_der"}; $filename =~ s/\@R\@/$idx/g; my %flags = (); $::cnf->{_}->{"backups"} and $flags{"BACKUP"} = 1; my $openssl=OSSL->new(); my ($der,$errors) = $openssl->Exec3($data,qw/crl -inform PEM -outform DER/); $errors or not $der and $::log->err("Data count not be converted to DER: $errors") and return 0; $::log->verb(3,"Writing DER file",$filename); &updatefile($filename,$der,%flags) or return 0; return 1; } sub writeOpenSSL($$$$) { my $self = shift; my $idx = shift; my $data = shift; my $ta = shift; defined $idx and $data and $ta or $::log->err("CRLWriter::writeOpenSSL: missing index, data or ta") and return 0; my $output = $::cnf->{_}->{"output"}; $output = $::cnf->{_}->{"output_openssl"} if defined $::cnf->{_}->{"output_openssl"}; $output and -d $output or $::log->err("OpenSSL target directory $output invalid") and return 0; my $openssl=OSSL->new(); # guess the hash name or names from OpenSSL # if mode is dual (and OpenSSL1 installed) write two files my $opensslversion = $openssl->getVersion() or return 0; my ($cmddata,$errors); my @hashes = (); if ( $opensslversion ge "1" and $::cnf->{_}->{"opensslmode"} eq "dual" ) { $::log->verb(5,"OpenSSL version 1 dual-mode enabled"); # this mode needs the ta cafile to get both hashes, since these # can only be extracted by the x509 subcommand from a CA ... ($cmddata,$errors) = $openssl->Exec3(undef, qw/x509 -noout -subject_hash -subject_hash_old -in/, $ta->{"cafile"}[0]); $cmddata or $::log->err("OpenSSL cannot extract hashes from",$ta->{"cafile"}[0]) and return 0; @hashes = split(/[\s\n]+/,$cmddata); } else { $::log->verb(5,"OpenSSL version 1 single-mode or pre-1.0 style"); ($cmddata,$errors) = $openssl->Exec3($data,qw/crl -noout -hash/); $cmddata or $::log->err("OpenSSL cannot extract hashes from CRL for", $ta->{"alias"}.'/'.$idx ) and return 0; @hashes = split(/[\s\n]+/,$cmddata); } my %flags = (); $::cnf->{_}->{"backups"} and $flags{"BACKUP"} = 1; foreach my $hash ( @hashes ) { my $filename = "$output/$hash.r$idx"; $::log->verb(3,"Writing OpenSSL file",$filename); &updatefile($filename,$data,%flags) or return 0; } return 1; } sub writeNSS($$$$) { my $self = shift; my $idx = shift; my $data = shift; my $ta = shift; defined $idx and $data and $ta or $::log->err("CRLWriter::writeNSS: missing index, data or ta") and return 0; my $output = $::cnf->{_}->{"output"}; $output = $::cnf->{_}->{"output_nss"} if defined $::cnf->{_}->{"output_nss"}; $output and -d $output or $::log->err("NSS target directory $output invalid") and return 0; my $dbprefix=""; $dbprefix = $::cnf->{_}->{"nssdbprefix"} if defined $::cnf->{_}->{"nssdbprefix"}; my $filename = "$output/$dbprefix"; # the crlutil tool requires the DER formatted cert in a file my $tmpdir = $::cnf->{_}->{exec3tmpdir} || $ENV{"TMPDIR"} || '/tmp'; my ($derfh,$dername) = tempfile("fetchcrl3der.XXXXXX", DIR=>$tmpdir, UNLINK=>1); (my $b64data = $data) =~ s/-[^\n]+//gm; $b64data =~ s/\s+//gm; print $derfh base64::b64decode($b64data); # der is decoded PEM :-) my $cmd = "crlutil -I -d \"$output\" -P \"$dbprefix\" "; $::cnf->{_}->{nonssverify} and $cmd .= "-B "; $cmd .= "-n ".$ta->{"alias"}.'.'.$idx." "; $cmd .= "-i \"$dername\""; my $result = `$cmd 2>&1`; unlink $dername; if ( $? != 0 ) { $::log->err("Cannot update NSSDB filename: $result"); } else { $::log->verb(3,"WriteNSS: ".$ta->{"alias"}.'.'.$idx." added to $filename"); } return 1; } sub writeall($) { my $self = shift; return 0 unless defined $self; $self->{"ta"} or $::log->err("CRLWriter::setIndex called without a loaded TA") and return 0; my $ta = $self->{"ta"}; $ta->{"crlurls"} or $::log->err("CRLWriter::setIndex called with uninitialised TA") and return 0; $::log->verb(2,"Writing CRLs for",$ta->{"anchorname"}); my $completesuccess = 1; for ( my $idx = 0 ; $idx <= $#{$ta->{"crl"}} ; $idx++ ) { $ta->{"crl"}[$idx]{"pemdata"} or $::log->verb(3,"Ignored CRL $idx skipped") and next; # ignore empty crls, leave these in place my $writeAttempt = 0; my $writeSuccess = 0; ( grep /^pem$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and $writeSuccess += $self->writePEM($idx,$ta->{"crl"}[$idx]{"pemdata"},$ta); ( grep /^der$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and $writeSuccess += $self->writeDER($idx,$ta->{"crl"}[$idx]{"pemdata"},$ta); ( grep /^openssl$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and $writeSuccess += $self->writeOpenSSL($idx, $ta->{"crl"}[$idx]{"pemdata"},$ta); ( grep /^nss$/, @{$::cnf->{_}->{formats_}} ) and ++$writeAttempt and $writeSuccess += $self->writeNSS($idx,$ta->{"crl"}[$idx]{"pemdata"},$ta); if ( $writeSuccess == $writeAttempt ) { $::log->verb(4,"LastWrite time (mtime) set to current time"); $ta->{"crl"}[$idx]{"state"}{"mtime"} = time; } else { $::log->warn("Partial updating ($writeSuccess of $writeAttempt) for", $ta->{"anchorname"}, "CRL $idx: mtime not updated"); } $completesuccess &&= ($writeSuccess == $writeAttempt); } return $completesuccess; } 1; package ConfigTiny; # derived from Config::Tiny 2.12, but with some local mods and # some new syntax possibilities # If you thought Config::Simple was small... use strict; BEGIN { require 5.004; $ConfigTiny::VERSION = '2.12'; $ConfigTiny::errstr = ''; } # Create an empty object sub new { bless {}, shift } # Create an object from a file sub read { my $class = ref $_[0] ? shift : ref shift; # Check the file my $file = shift or return $class->_error( 'You did not specify a file name' ); return $class->_error( "File '$file' does not exist" ) unless -e $file; return $class->_error( "'$file' is a directory, not a file" ) unless -f _; return $class->_error( "Insufficient permissions to read '$file'" ) unless -r _; # Slurp in the file local $/ = undef; open CFG, $file or return $class->_error( "Failed to open file '$file': $!" ); my $contents = ; close CFG; return $class->read_string( $contents ); } # Create an object from a string sub read_string { my $class = ref $_[0] ? shift : ref shift; my $self = $class; #my $self = bless {}, $class; #my $self = shift; return undef unless defined $_[0]; # Parse the file my $ns = '_'; my $counter = 0; my $content = shift; $content =~ s/\\(?:\015{1,2}\012|\015|\012)\s*//gm; foreach ( split /(?:\015{1,2}\012|\015|\012)/, $content ) { $counter++; # Skip comments and empty lines next if /^\s*(?:\#|\;|$)/; # Remove inline comments s/\s\;\s.+$//g; # Handle section headers if ( /^\s*\[\s*(.+?)\s*\]\s*$/ ) { # Create the sub-hash if it doesn't exist. # Without this sections without keys will not # appear at all in the completed struct. $self->{$ns = $1} ||= {}; next; } # Handle properties if ( /^\s*([^=]+?)\s*=\s*(.*?)\s*$/ ) { $self->{$ns}->{$1} = $2; next; } # Handle settings if ( /^\s*([^=]+?)\s*$/ ) { $self->{$ns}->{$1} = 1; next; } return $self->_error( "Syntax error at line $counter: '$_'" ); } return $self; } # Save an object to a file sub write { my $self = shift; my $file = shift or return $self->_error( 'No file name provided' ); # Write it to the file open( CFG, '>' . $file ) or return $self->_error( "Failed to open file '$file' for writing: $!" ); print CFG $self->write_string; close CFG; } # Save an object to a string sub write_string { my $self = shift; my $contents = ''; foreach my $section ( sort { (($b eq '_') <=> ($a eq '_')) || ($a cmp $b) } keys %$self ) { my $block = $self->{$section}; $contents .= "\n" if length $contents; $contents .= "[$section]\n" unless $section eq '_'; foreach my $property ( sort keys %$block ) { $contents .= "$property=$block->{$property}\n"; } } $contents; } # Error handling sub errstr { $ConfigTiny::errstr } sub _error { $ConfigTiny::errstr = $_[1]; undef } 1; # # @(#)$Id: FCLog.pm 2692 2014-03-05 19:43:36Z davidg $ # # ########################################################################### # # Fetch-CRL3 logging support package FCLog; use Sys::Syslog; # Syntax: # $log = CL->new( [outputmode=qualified,cache,direct,syslog] ) # $log->destadd( destination [,facility] ) # $log->destremove ( destination ) # $log->setverbose( level ) # $log->setdebug( level ) # $log->setwarnings( 0|1 ) # $log->debug( level, message ...) # $log->verb( level, message ...) # $log->warn( level, message ...) # $log->err( level, message ...) # $log->clear( ) # $log->flush( ) # $log->exitstatus( ) # sub new { my $self = shift; my $obref = {}; bless $obref; $obref->{"debug"} = 0; $obref->{"verbose"} = 0; $obref->{"messagecache"} = (); $obref->{"warnings"} = 1; $obref->{"errors"} = 1; $obref->{"rcmode"} = "normal"; $obref->{"warncount"} = 0; $obref->{"errorcount"} = 0; $obref->{"retrerrorcount"} = 0; $obref->{"syslogfacility"} = "daemon"; while ( my $mode = shift ) { $obref->destadd($mode); } return $obref; } sub destadd { my $self = shift; my $mode = shift; my $facility = (shift or $self->{"syslogfacility"}); return 0 unless defined $mode; $self->{"logmode"}{$mode} = 1; if ( $mode eq "syslog" ) { my $progname = $0; $progname =~ s/^.*\///; $self->{"syslogfacility"} = $facility; openlog($progname,"nowait,pid", $facility); } return 1; } sub destremove { my $self = shift; my $ok = 1; my $mode = shift; $self->{"logmode"} = {} and return 1 if (defined $mode and $mode eq "all"); unshift @_,$mode; while ( my $mode = shift ) { if ( defined $self->{"logmode"}{$mode} ) { closelog() if $mode eq "syslog"; delete $self->{"logmode"}{$mode}; } else { $ok=0; } } return $ok; } sub setverbose { my ($self,$level) = @_; my $oldlevel = $self->{"verbose"}; $self->{"verbose"} = 0+$level; return $oldlevel; } sub getverbose { my ($self) = @_; return $self->{"verbose"}; } sub setdebug { my ($self,$level) = @_; my $oldlevel = $self->{"debug"}; $self->{"debug"} = $level; return $oldlevel; } sub getdebug { my ($self) = @_; return $self->{"debug"}; } sub setwarnings { my ($self,$level) = @_; my $oldlevel = $self->{"warnings"}; $self->{"warnings"} = $level; return $oldlevel; } sub getwarnings { my ($self) = @_; return $self->{"warnings"}; } sub geterrors { my ($self) = @_; return $self->{"errors"}; } sub seterrors { my ($self,$level) = @_; my $oldlevel = $self->{"errors"}; $self->{"errors"} = $level; return $oldlevel; } sub getrcmode { my ($self) = @_; return $self->{"rcmode"}; } sub setrcmode { my ($self,$level) = @_; if ( $level !~ /^(normal|differentiated|noretrievalerrors)$/ ) { $self->err("Attempt to set rcmode to invalid value of $level"); return undef; } my $oldlevel = $self->{"rcmode"}; $self->{"rcmode"} = $level; return $oldlevel; } sub verb($$$) { my $self = shift; my $level = shift; return 1 unless ( $level <= $self->{"verbose"} ); my $message = "@_"; $self->output("VERBOSE($level)",$message); return 1; } sub debug($$$) { my $self = shift; my $level = shift; return 1 unless ( $level <= $self->{"debug"} ); my $message = "@_"; $self->output("DEBUG($level)",$message); return 1; } sub warn($@) { my $self = shift; return 1 unless ( $self->{"warnings"} ); $self->{"warningcount"}++; my $message = "@_"; $self->output("WARN",$message); return 1; } sub err($@) { my $self = shift; my $message = "@_"; return 1 unless ( $self->{"errors"} ); $self->output("ERROR",$message); $self->{"errorcount"}++; return 1; } sub retr_err($@) { my $self = shift; my $message = "@_"; return 1 unless ( $self->{"errors"} ); $self->output("ERROR",$message); $self->{"retrerrorcount"}++; return 1; } sub output($$@) { my ($self,$label,@message) = @_; return 0 unless defined $label and @message; my $message = join " ",@message; print "" . ($label?"$label ":"") . "$message\n" if ( defined $self->{"logmode"}{"qualified"} ); push @{$self->{"messagecache"}},"" . ($label?"$label ":"") . "$message\n" if ( defined $self->{"logmode"}{"cache"} ); print "$message\n" if ( defined $self->{"logmode"}{"direct"} ); if ( defined $self->{"logmode"}{"syslog"} ) { my $severity = "LOG_INFO"; $severity = "LOG_NOTICE" if $label eq "WARN"; $severity = "LOG_ERR" if $label eq "ERROR"; $severity = "LOG_DEBUG" if $label =~ /^VERBOSE/; $severity = "LOG_DEBUG" if $label =~ /^DEBUG/; syslog($severity, "%s", $message); } return 1; } sub clear($) { my $self = shift; $self->{"messagecache"} = (); return 1; } sub flush($) { my $self = shift; foreach my $s ( @{$self->{"messagecache"}} ) { print $s; } $self->{"messagecache"} = (); ($self->{"errorcount"} + $self->{"retrerrorcount"}) and $self->{"errors"} and return 0; $self->{"warningcount"} and $self->{"warnings"} and return 1; return 1; } sub cleanse($) { my $self = shift; $self->{"messagecache"} = (); $self->{"errorcount"} = 0; $self->{"retrerrorcount"} = 0; $self->{"warningcount"} = 0; $self->{"logmode"} = {}; return 1; } sub exitstatus($) { my $self = shift; if ( $self->{"rcmode"} eq "normal" ) { $self->{"errorcount"} and $self->{"errors"} and return 1; $self->{"retrerrorcount"} and $self->{"errors"} and return 1; } elsif ( $self->{"rcmode"} eq "differentiated" ) { $self->{"errorcount"} and $self->{"errors"} and return 1; $self->{"retrerrorcount"} and $self->{"errors"} and return 2; } elsif ( $self->{"rcmode"} eq "noretrievalerrors" ) { $self->{"errorcount"} and $self->{"errors"} and return 1; } else { return 1; } return 0; } 1; # # @(#)$Id: OSSL.pm 2652 2013-07-02 19:16:38Z davidg $ # # package OSSL; use strict; use POSIX; use File::Temp qw/ tempfile /; use IPC::Open3; use IO::Select; use Time::Local; use vars qw/ $log $cnf $opensslversion /; # Syntax: # OSSL->new( [path] ); # OSSL->setName( name); # sub new { my $obref = {}; bless $obref; my $self = shift; $self = $obref; my $openssl = shift; $self->{"openssl"} = "openssl"; $self->{"openssl"} = $::cnf->{_}->{"openssl"} if $::cnf->{_}->{"openssl"}; $self->setOpenSSL($openssl) if $openssl; $self->{"version"} = undef; return $self; } sub setOpenSSL($$) { my $self = shift or die "Invalid invocation of CRL::setOpenSSL\n"; my $openssl = shift; return 0 unless $openssl; $openssl =~ /\// and ! -x "$openssl" or $::log->err("OpenSSL binary $openssl is not executable or does not exist") and return 0; $::log->verb(4,"Using OpenSSL at $openssl"); $self->{"openssl"} = $openssl; $self->{"version"} = undef; return 1; } sub getVersion($) { my $self = shift or die "Invalid invocation of CRL::getVersion\n"; #$self->{"version"} and return $self->{"version"}; $opensslversion and return $opensslversion; my ($data,$errors) = $self->Exec3(undef,qw/version/); if ( defined $data ) { $data =~ /^OpenSSL\s+([\d\.]+\w)/ or $::log->err("Cannot get OpenSSL version from command: invalid format in $data".($errors?" ($errors)":"")) and return undef; $self->{"version"} = $1; $opensslversion = $self->{"version"}; return $1; } else { $::log->err("Cannot get OpenSSL version from command: $errors"); return undef; } } sub Exec3select($$@) { my $self = shift or die "Invalid invocation of CRL::OpenSSL\n"; my $datain = shift; my ($dataout, $dataerr) = ("",undef); my $rc = 0; local(*CMD_IN, *CMD_OUT, *CMD_ERR); $::log->verb(6,"Executing openssl",@_); my $pid = open3(*CMD_IN, *CMD_OUT, *CMD_ERR, $self->{"openssl"}, @_ ); $SIG{CHLD} = sub { $rc = $? >> 8 if waitpid($pid, 0) > 0 }; $datain and print CMD_IN $datain; close(CMD_IN); print STDERR "Printed " . length($datain). " bytes of data\n"; my $selector = IO::Select->new(); $selector->add(*CMD_ERR); $selector->add(*CMD_OUT); my ($char,$cnt); while ($selector->count) { my @ready = $selector->can_read(1); #my @ready = IO::Select->select($selector,undef,undef,1); foreach my $fh (@ready) { if (fileno($fh) == fileno(CMD_ERR)) { $cnt = sysread CMD_ERR, $char, 1; if ( $cnt ) { $dataerr .= $char; } else { $selector->remove($fh); $dataerr and print STDERR "$dataerr\n";} } else { $cnt = sysread CMD_OUT, $char, 1; if ( $cnt ) { $dataout .= $char; } else { $selector->remove($fh); $dataout and print STDERR "$dataout\n"; } } $selector->remove($fh) if eof($fh); } } close(CMD_OUT); close(CMD_ERR); if ( $rc >> 8 ) { $::log->warn("Execute openssl " . $ARGV[0] . " failed: $rc"); (my $errmsg = $dataerr) =~ s/\n.*//sgm; $::log->verb(6,"STDERR:",$errmsg); return undef unless wantarray; return (undef,$dataerr); } return $dataout unless wantarray; return ($dataout,$dataerr); } sub Exec3pipe($$@) { my $self = shift or die "Invalid invocation of CRL::OpenSSL\n"; my $datain = shift; my ($dataout, $dataerr) = ("",undef); my $rc = 0; local(*CMD_IN, *CMD_OUT, *CMD_ERR); $::log->verb(6,"Executing openssl",@_); my ($tmpfh,$tmpname); $datain and do { ($tmpfh,$tmpname) = tempfile("fetchcrl3.XXXXXX", DIR=>'/tmp'); $|=1; print $tmpfh $datain; close $tmpfh; push @_, "-in", $tmpname; select undef,undef,undef,0.01; }; $|=1; my $pid = open3( *CMD_IN, *CMD_OUT, *CMD_ERR, $self->{"openssl"}, @_ ); # allow delay for child to startup - but will hang on many older platforms select undef,undef,undef,0.15; $SIG{CHLD} = sub { $rc = $? >> 8 if waitpid($pid, 0) > 0 }; #close(CMD_IN); CMD_OUT->autoflush; CMD_ERR->autoflush; my $selector = IO::Select->new(); $selector->add(*CMD_ERR, *CMD_OUT); while (my @ready = $selector->can_read(0.01)) { foreach my $fh (@ready) { if (fileno($fh) == fileno(CMD_ERR)) {$dataerr .= scalar } else {$dataout .= scalar } $selector->remove($fh) if eof($fh); } } close(CMD_OUT); close(CMD_ERR); $tmpname and unlink $tmpname; if ( $rc >> 8 ) { $::log->warn("Execute openssl " . $ARGV[0] . " failed: $rc"); (my $errmsg = $dataerr) =~ s/\n.*//sgm; $::log->verb(6,"STDERR:",$errmsg); return undef unless wantarray; return (undef,$dataerr); } return $dataout unless wantarray; return ($dataout,$dataerr); } sub Exec3file($$@) { my $self = shift or die "Invalid invocation of CRL::OpenSSL\n"; my $datain = shift; my ($dataout, $dataerr) = ("",undef); my $rc = 0; local(*CMD_IN, *CMD_OUT, *CMD_ERR); $::log->verb(6,"Executing openssl",@_); my ($tmpin,$tmpinname); my ($tmpout,$tmpoutname); my ($tmperr,$tmperrname); my $tmpdir = $::cnf->{_}->{exec3tmpdir} || $ENV{"TMPDIR"} || '/tmp'; $|=1; $datain and do { ($tmpin,$tmpinname) = tempfile("fetchcrl3in.XXXXXX", DIR=>$tmpdir); print $tmpin $datain; close $tmpin; }; ($tmpout,$tmpoutname) = tempfile("fetchcrl3out.XXXXXX", DIR=>$tmpdir); ($tmperr,$tmperrname) = tempfile("fetchcrl3out.XXXXXX", DIR=>$tmpdir); my $pid = fork(); defined $pid or $::log->warn("Internal error, fork for openssl failed: $!") and return undef; if ( $pid == 0 ) { # I'm a kid close STDIN; if ( $tmpinname ) { open STDIN, "<", $tmpinname or die "Cannot open tempfile $tmpinname again $!\n"; } else { open STDIN, "<", "/dev/null" or die "Cannot open /dev/null ??? $!\n"; } close STDOUT; if ( $tmpoutname ) { open STDOUT, ">", $tmpoutname or die "Cannot open tempfile $tmpoutname again $!\n"; } else { open STDOUT, ">", "/dev/null" or die "Cannot open /dev/null ??? $!\n"; } close STDERR; if ( $tmpoutname ) { open STDERR, ">", $tmperrname or die "Cannot open tempfile $tmperrname again $!\n"; } else { open STDERR, ">", "/dev/null" or die "Cannot open /dev/null ??? $!\n"; } exec $self->{"openssl"}, @_; } $rc = $? >> 8 if waitpid($pid, 0) > 0; { local $/; $dataout = <$tmpout>; }; { local $/; $dataerr = <$tmperr>; }; $tmpinname and unlink $tmpinname; $tmpoutname and unlink $tmpoutname; $tmperrname and unlink $tmperrname; if ( $rc >> 8 ) { $::log->warn("Execute openssl " . $ARGV[0] . " failed: $rc"); (my $errmsg = $dataerr) =~ s/\n.*//sgm; $::log->verb(6,"STDERR:",$errmsg); return undef unless wantarray; return (undef,$dataerr); } return $dataout unless wantarray; return ($dataout,$dataerr); } sub Exec3($@) { my $self = shift; grep /^pipe$/, $::cnf->{_}->{exec3mode}||"" and return $self->Exec3pipe(@_); grep /^select$/, $::cnf->{_}->{exec3mode}||"" and return $self->Exec3select(@_); return $self->Exec3file(@_); # default } sub gms2t($$) { my $self = shift; my ( $month, $mday, $htm, $year, $tz ) = split(/\s+/,$_[0]); die "OSSL::gms2t: cannot hangle non GMT output from OpenSSL\n" unless $tz eq "GMT"; my %mon=("Jan"=>0,"Feb"=>1,"Mar"=>2,"Apr"=>3,"May"=>4,"Jun"=>5, "Jul"=>6,"Aug"=>7,"Sep"=>8,"Oct"=>9,"Nov"=>10,"Dec"=>11); my ( $hrs,$min,$sec ) = split(/:/,$htm); my $gmt = timegm($sec,$min,$hrs,$mday,$mon{$month},$year); #print STDERR ">>> converted $_[0] to $gmt\n"; return $gmt; } 1; # # @(#)$Id: TrustAnchor.pm 3275 2020-01-16 20:33:09Z davidg $ # # ########################################################################### # # package TrustAnchor; use strict; use File::Basename; use LWP; require ConfigTiny and import ConfigTiny unless defined &ConfigTiny::new; require CRL and import CRL unless defined &CRL::new; require base64 and import base64 unless defined &base64::b64encode; use vars qw/ $log $cnf /; sub new { my $obref = {}; bless $obref; my $self = shift; $self = $obref; my $name = shift; $self->{"infodir"} = $cnf->{_}->{infodir}; $self->{"suffix"} = "info"; $self->loadAnchor($name) if defined $name; return $self; } sub saveLogMode($) { my $self = shift; return 0 unless defined $self; $self->{"preserve_warnings"} = $::log->getwarnings; $self->{"preserve_errors"} = $::log->geterrors; return 1; } sub setLogMode($) { my $self = shift; return 0 unless defined $self; $self->{"nowarnings"} and $::log->setwarnings(0); $self->{"noerrors"} and $::log->seterrors(0); return 1; } sub restoreLogMode($) { my $self = shift; return 0 unless defined $self; (defined $self->{"preserve_warnings"} and defined $self->{"preserve_errors"}) or die "Internal error: restoreLogMode called without previous save\n"; $::log->setwarnings($self->{"preserve_warnings"}); $::log->seterrors($self->{"preserve_errors"}); return 1; } sub getInfodir($$) { my $self = shift; my ($path) = shift; return 0 unless defined $self; return $self->{"infodir"}; } sub setInfodir($$) { my $self = shift; my ($path) = shift; return 0 unless defined $path and defined $self; -e $path or $::log->err("setInfodir: path $path does not exist") and return 0; -d $path or $::log->err("setInfodir: path $path is not a directory") and return 0; $self->{"infodir"} = $path; return 1; } sub loadAnchor($$) { my $self = shift; my ($name) = @_; return 0 unless defined $name; $::log->verb(1,"Initializing trust anchor $name"); my ( $basename, $path, $suffix) = fileparse($name,('.info','.crl_url')); $path = "" if $path eq "./" and substr($name,0,length($path)) ne $path ; $::log->err("Invalid name of trust anchor $name") and return 0 unless $basename; $self->{"infodir"} = $path if $path ne ""; $path = $self->{"infodir"} || ""; $path and $path .= "/" unless $path =~ /\/$/; if ( $suffix ) { -e $name or $::log->err("Trust anchor data $name not found") and return 0; } else { # try and guess which suffix should be used ($suffix eq "" and -e $path.$basename.".info" ) and $suffix = ".info"; ($suffix eq "" and -e $path.$basename.".crl_url" ) and $suffix = ".crl_url"; $suffix or $::log->err("No trust anchor metadata for $basename in '$path'") and return 0; } if ( $suffix eq ".crl_url" ) { $self->{"alias"} = $basename; @{$self->{"crlurls"}} = (); open CRLURL,"$path$basename$suffix" or $::log->err("Error reading crl_url $path$basename$suffix: $!") and return 0; $self->{"filename"} = "$path$basename$suffix"; my $urllist; while () { /^\s*([^#\n]+).*$/ and my $url = $1 or next; $url =~ s/\s*$//; # trailing whitespace is ignored $url =~ /^\w+:\/\/.*$/ or $::log->err("File $path$basename$suffix contains a non-URL entry") and close CRLURL and return 0; $urllist and $urllist .= "\001"; $urllist .= $url; } close CRLURL; push @{$self->{"crlurls"}}, $urllist; $self->{"status"} ||= "unknown"; } else { my $info = ConfigTiny->new(); $info->read( $path . $basename . $suffix ) or $::log->err("Error reading info $path$basename$suffix", $info->errstr) and return 0; $self->{"filename"} = "$path$basename$suffix"; $info->{_}->{"crl_url"} and $info->{_}->{"crl_url.0"} and $::log->err("Invalid info for $basename: crl_url and .0 duplicate") and return 0; $info->{_}->{"crl_url"} and $info->{_}->{"crl_url.0"} = $info->{_}->{"crl_url"}; # only do something when there is actually a CRL to process $info->{_}->{"crl_url.0"} or $::log->verb(1,"Trust anchor $basename does not have a CRL") and return 0; $info->{_}->{"alias"} or $::log->err("Invalid info for $basename: no alias") and return 0; $self->{"alias"} = $info->{_}->{"alias"}; @{$self->{"crlurls"}} = (); for ( my $i=0 ; defined $info->{_}{"crl_url.".$i} ; $i++ ) { $info->{_}{"crl_url.".$i} =~ s/[;\s]+/\001/g; $info->{_}{"crl_url.".$i} =~ s/^\s*([^\s]*)\s*$/$1/; $info->{_}{"crl_url.".$i} =~ /^\w+:\/\// or $::log->err("File $path$basename$suffix contains a non-URL entry", $info->{_}{"crl_url.".$i}) and close CRLURL and return 0; push @{$self->{"crlurls"}} , $info->{_}{"crl_url.".$i}; } foreach my $field ( qw/email ca_url status/ ) { $self->{$field} = $info->{_}->{$field} if $info->{_}->{$field}; } # status of CA is only knwon for info-file based CAs $self->{"status"} ||= "local"; } # preserve basename of file for config and diagnostics $self->{"anchorname"} = $basename; # # set defaults for common values foreach my $key ( qw / prepend_url postpend_url agingtolerance httptimeout proctimeout nowarnings noerrors nocache http_proxy nametemplate_der nametemplate_pem cadir catemplate statedir / ) { $self->{$key} = $self->{$key} || $::cnf->{$self->{"alias"}}->{$key} || $::cnf->{$self->{"anchorname"}}->{$key} || $::cnf->{_}->{$key} or delete $self->{$key}; defined $self->{$key} and do { $self->{$key} =~ s/\@ANCHORNAME\@/$self->{"anchorname"}/g; $self->{$key} =~ s/\@STATUS\@/$self->{"status"}/g; $self->{$key} =~ s/\@ALIAS\@/$self->{"alias"}/g; }; } # reversible toggle options foreach my $key ( qw / warnings errors cache / ) { delete $self->{"no$key"} if $::cnf->{$self->{"alias"}}->{$key} or $::cnf->{$self->{"anchorname"}}->{$key} or $::cnf->{_}->{$key}; } foreach my $key ( qw / nohttp_proxy noprepend_url nopostpend_url nostatedir / ) { (my $nokey = $key) =~ s/^no//; delete $self->{"$nokey"} if $::cnf->{$self->{"alias"}}->{$key} or $::cnf->{$self->{"anchorname"}}->{$key} or $::cnf->{_}->{$key}; } # overriding of the URLs (alias takes precedence over anchorname foreach my $section ( qw / anchorname alias / ) { my $i = 0; while ( defined ($::cnf->{$self->{$section}}->{"crl_url.".$i}) ) { my $urls; ($urls=$::cnf->{$self->{$section}}->{"crl_url.".$i} )=~s/[;\s]+/\001/g; ${$self->{"crlurls"}}[$i] = $urls; $i++; } } # templates to construct a CA name may still have other separators $self->{"catemplate"} =~ s/[;\s]+/\001/g; # select only http/https/ftp/file URLs # also transform the URLs using the base patterns and prepend any # local URL patterns (@ANCHORNAME@, @ALIAS@, and @R@) for ( my $i=0; $i <= $#{$self->{"crlurls"}} ; $i++ ) { my $urlstring = @{$self->{"crlurls"}}[$i]; my @urls = split(/\001/,$urlstring); $urlstring=""; foreach my $url ( @urls ) { if ( $url =~ /^(http:|https:|ftp:|file:)/ ) { $urlstring.="\001" if $urlstring; $urlstring.=$url; } else { $::log->verb(0,"URL $url in $basename$suffix unsupported, ignored"); } } if ( my $purl = $self->{"prepend_url"} ) { $purl =~ s/\@R\@/$i/g; $urlstring = join "\001" , $purl , $urlstring; } if ( my $purl = $self->{"postpend_url"} ) { $purl =~ s/\@R\@/$i/g; $urlstring = join "\001" , $urlstring, $purl; } if ( ! $urlstring ) { $::log->err("No usable CRL URLs for",$self->getAnchorName); $self->{"crlurls"}[$i] = ""; } else { $self->{"crlurls"}[$i] = $urlstring; } } return 1; } sub getAnchorName($) { my $self = shift; return ($self->{"anchorname"} || undef); } sub printAnchorName($) { my $self = shift; print "" . ($self->{"anchorname"} || "undefined") ."\n"; } sub displayAnchorName($) { my $self = shift; return ($self->{"anchorname"} || "undefined"); } sub loadCAfiles($) { my $self = shift; my $idx = 0; # try to find a CA dir, whatever it takes, almost my $cadir = $self->{"cadir"} || $self->{"infodir"}; -d $cadir or $::log->err("CA directory",$cadir,"does not exist") and return 0; # add @HASH@ support, inducing a file read and fork, only if really needed my $crlhash; if ( $self->{"catemplate"} =~ /\@HASH\@/ ) { $self->{"crl"}[0]{"data"} ne "" or $::log->err("CA name template contains HASH, but no CRL ". "could be loaded in time for ".$self->displayAnchorName) and return 0; my $probecrl = CRL->new(undef,$self->{"crl"}[0]{"data"}); $crlhash = $probecrl->getAttribute("hash"); $::log->verb(3,"Inferred CA template HASH ".($crlhash?$crlhash:"failed"). " for ".$self->displayAnchorName); } @{$self->{"cafile"}} = (); do { my $cafile; foreach my $catpl ( split /\001/, $self->{"catemplate"} ) { $catpl =~ s/\@R\@/$idx/g; $catpl =~ s/\@HASH\@/$crlhash/g; -e $cadir.'/'.$catpl and $cafile = $cadir.'/'.$catpl and last; } defined $cafile or do { $idx or do $::log->err("Cannot find any CA for", $self->{"alias"},"in",$cadir); return $idx?1:0; }; # is the new one any different from the previous (i.e. is the CA indexed?) $#{$self->{"cafile"}} >= 0 and $cafile eq $self->{"cafile"}[$#{$self->{"cafile"}}] and return 1; push @{$self->{"cafile"}}, $cafile; $::log->verb(3,"Added CA file $idx: $cafile"); } while(++$idx); return 0; # you never should come here } sub loadState($$) { my $self = shift; my $fallbackmode = shift; $self->{"crlurls"} or $::log->err("loading state for uninitialised list of CRLs") and return 0; $self->{"alias"} or $::log->err("loading state for uninitialised trust anchor") and return 0; for ( my $i = 0; $i <= $#{$self->{"crlurls"}} ; $i++ ) { # all indices if ( $self->{"statedir"} and -e $self->{"statedir"}.'/'.$self->{"alias"}.'.'.$i.'.state' ) { my $state = ConfigTiny->new(); $state->read($self->{"statedir"}.'/'.$self->{"alias"}.'.'.$i.'.state') or $::log->err("Cannot read existing state file", $self->{"statedir"}.'/'.$self->{"alias"}.'.$i.state', " - ",$state->errstr) and return 0; foreach my $key ( keys %{$state->{$self->{"alias"}}} ) { $self->{"crl"}[$i]{"state"}{$key} = $state->{$self->{"alias"}}->{$key}; } } # fine, but we should find at least an mtime if at all possible # make sure it is there: # try to retrieve state from installed files in @output_ # where the first look-alike CRL will win. NSS databases # are NOT supported for this heuristic if ( ! defined $self->{"crl"}[$i]{"state"}{"mtime"} ) { my $mtime; STATEHUNT: foreach my $output ( ( $::cnf->{_}->{"output"}, $::cnf->{_}->{"output_der"}, $::cnf->{_}->{"output_pem"}, $::cnf->{_}->{"output_nss"}, $::cnf->{_}->{"output_openssl"}) ) { defined $output and $output or next; foreach my $ref ( $self->{"nametemplate_der"}, $self->{"nametemplate_pem"}, $self->{"alias"}.".r\@R\@", $self->{"anchorname"}.".r\@R\@", ) { next unless $ref; my $file = $ref; # copy, not to change original $file =~ s/\@R\@/$i/g; $file = join "/", $output, $file; next if ! -e $file; $mtime = (stat(_))[9]; last STATEHUNT; } } $::log->verb(3,"Inferred mtime for",$self->{"alias"},"is",$mtime) if $mtime; $self->{"crl"}[$i]{"state"}{"mtime"} = $mtime if $mtime; } # as a last resort, set mtime to curren time $self->{"crl"}[$i]{"state"}{"mtime"} ||= time; } return 1; } sub saveState($$) { my $self = shift; my $fallbackmode = shift; $self->{"statedir"} and -d $self->{"statedir"} and -w $self->{"statedir"} or return 0; $self->{"crlurls"} or $::log->err("loading state for uninitialised list of CRLs") and return 0; $self->{"alias"} or $::log->err("loading state for uninitialised trust anchor") and return 0; # of state, mtime is set based on CRL write in $output and filled there for ( my $i = 0; $i <= $#{$self->{"crlurls"}} ; $i++ ) { # all indices if ( defined $self->{"statedir"} and -d $self->{"statedir"} ) { my $state = ConfigTiny->new; foreach my $key ( keys %{$self->{"crl"}[$i]{"state"}} ) { $state->{$self->{"alias"}}->{$key} = $self->{"crl"}[$i]{"state"}{$key}; } $state->write( $self->{"statedir"}.'/'.$self->{"alias"}.'.'.$i.'.state' ); $::log->verb(5,"State saved in", $self->{"statedir"}.'/'.$self->{"alias"}.'.'.$i.'.state'); } } return 1; } sub retrieveHTTP($$) { my $self = shift; my $idx = shift; my $url = shift; my %metadata; my $data; $url =~ /^(http:|https:|ftp:)/ or die "retrieveHTTP: non-http URL $url\n"; $::log->verb(3,"Downloading data from $url"); my $ua = LWP::UserAgent->new; $ua->agent('fetch-crl/'.$::cnf->{_}->{version} . ' ('. $ua->agent . '; '.$::cnf->{_}->{packager} . ')' ); # allow overriding of userAgent string to bypass Fortigates and like filters if ( defined $::cnf->{$self->{"alias"}}->{user_agent} ) { $ua->agent($::cnf->{$self->{"alias"}}->{user_agent}); $::log->verb(5,"Setting user agent for " . $self->{"alias"} . " to \"" . $::cnf->{$self->{"alias"}}->{user_agent} . "\"" ); } elsif ( defined $::cnf->{_}->{user_agent} ) { $ua->agent($::cnf->{_}->{user_agent}); $::log->verb(5,"Setting user agent to global value \"" . $::cnf->{_}->{user_agent} . "\"" ); } $ua->timeout($self->{"httptimeout"}); $ua->use_eval(0); if ( $self->{"http_proxy"} ) { if ( $self->{"http_proxy"} =~ /^ENV/i ) { $ua->env_proxy(); } else { $ua->proxy("http", $self->{"http_proxy"}); } } # set request cache control if specified as valid in config if ( defined $::cnf->{_}->{cache_control_request} ) { $::log->verb(5,"Setting request cache-control to ". $::cnf->{_}->{cache_control_request}); if ( $::cnf->{_}->{cache_control_request} =~ /^\d+$/ ) { $ua->default_header('Cache-control' => "max-age=".$::cnf->{_}->{cache_control_request} ); } else { die "Request cache control is invalid (not a number)\n"; } } # see with a HEAD request if we can get by with old data # but to assess that we need Last-Modified from the previous request # (so if the CA did not send that: too bad) if ( $self->{"crl"}[$idx]{"state"}{"lastmod"} and $self->{"crl"}[$idx]{"state"}{"b64data"} ) { $::log->verb(4,"Lastmod set to",$self->{"crl"}[$idx]{"state"}{"lastmod"}); $::log->verb(4,"Attemping HEAD retrieval of $url"); my $response; eval { local $SIG{ALRM}=sub{die "timed out after ".$self->{"httptimeout"}."s\n";}; alarm $self->{"httptimeout"}; $response = $ua->head($url); alarm 0; }; alarm 0; # make sure the alarm stops ticking, regardless of the eval if ( $@ ) { # died, alarm hit: server bad, so try next URL chomp($@); my $shorterror = $@; $shorterror =~ s/\n.*$//gs; $::log->verb(2,"HEAD error $url:", $shorterror); # underlying socket library may be verybose - filter and qualify messages if ( $shorterror ne $@ ) { foreach my $errorline ( split(/\n/,$@) ) { chomp($errorline); $errorline eq $shorterror and next; # nodups $errorline and $::log->verb(4,"HEAD error detail:", $errorline); } } return undef; } # try using cached data if it is fresh if ( ( ! $@ ) and $response->is_success and $response->header("Last-Modified") ) { my $lastmod = HTTP::Date::str2time($response->header("Last-Modified")); if ( $lastmod == $self->{"crl"}[$idx]{"state"}{"lastmod"}) { $::log->verb(4,"HEAD lastmod unchanged, using cache"); $data = base64::b64decode($self->{"crl"}[$idx]{"state"}{"b64data"}); %metadata = ( "freshuntil" => $response->fresh_until(heuristic_expiry=>0)||time, "lastmod" => $self->{"crl"}[$idx]{"state"}{"lastmod"} || time, "sourceurl" => $self->{"crl"}[$idx]{"state"}{"sourceurl"} || $url ); return ($data,%metadata) if wantarray; return $data; } elsif ( $lastmod < $self->{"crl"}[$idx]{"state"}{"lastmod"} ) { # retrieve again, but print warning abount this wierd behaviour $::log->warn("Retrieved HEAD Last-Modified is older than cache: ". "cache invalidated, GET issued"); } } } # try get if head fails, there was no cache, cache disabled or invalidated my $response; eval { local $SIG{ALRM}=sub{die "timed out after ".$self->{"httptimeout"}."s\n";}; alarm $self->{"httptimeout"}; $ua->parse_head(0); $response = $ua->get($url); alarm 0; }; alarm 0; # make sure the alarm stops ticking, regardless of the eval if ( $@ ) { chomp($@); my $shorterror = $@; $shorterror =~ s/\n.*$//gs; $::log->verb(0,"Download error $url:", $shorterror); # underlying socket library may be verybose - filter and qualify messages if ( $shorterror ne $@ ) { foreach my $errorline ( split(/\n/,$@) ) { chomp($errorline); $errorline eq $shorterror and next; # nodups $errorline and $::log->verb(4,"Download error detail:", $errorline); } } return undef; } if ( ! $response->is_success ) { $::log->verb(0,"Download error $url:",$response->status_line); return undef; } $data = $response->content; $metadata{"freshuntil"}=$response->fresh_until(heuristic_expiry=>0)||time; if ( my $lastmod = $response->header("Last-Modified") ) { $metadata{"lastmod"} = HTTP::Date::str2time($lastmod); } $metadata{"sourceurl"} = $url; return ($data,%metadata) if wantarray; return $data; } sub retrieveFile($$) { my $self = shift; my $idx = shift; my $url = shift; $url =~ /^file:\/*(\/.*)$/ or die "retrieveFile: non-file URL $url\n"; $::log->verb(4,"Retrieving data from $url"); # for files the previous state does not matter, we retrieve it # anyway my $data; { open CRLFILE,$1 or do { $! = "Cannot open $1: $!"; return undef; }; binmode CRLFILE; local $/; $data = ; close CRLFILE; } my %metadata; $metadata{"lastmod"} = (stat($1))[9]; $metadata{"freshuntil"} = time; $metadata{"sourceurl"} = $url; return ($data,%metadata) if wantarray; return $data; } sub retrieve($) { my $self = shift; $self->{"crlurls"} or $::log->err("Retrieving uninitialised list of CRL URLs") and return 0; $::log->verb(2,"Retrieving CRLs for",$self->{"alias"}); for ( my $i = 0; $i <= $#{$self->{"crlurls"}} ; $i++ ) { # all indices my ($result,%response); $::log->verb(3,"Retrieving CRL for",$self->{"alias"},"index $i"); # within the list of CRL URLs for a specific index, all entries # are considered equivalent. I.e., if we get one, the metadata will # be used for all (like Last-Modified, and cache control data) # if we have a cached piece of fresh data, return that one # and make sure the nextupdate in the CRL itself outlives claimed freshness if ( !$self->{"nocache"} and ($self->{"crl"}[$i]{"state"}{"freshuntil"} || 0) > time and ($self->{"crl"}[$i]{"state"}{"nextupdate"} || time) >= time and ($self->{"crl"}[$i]{"state"}{"nextupdate"} || 0) >= ($self->{"crl"}[$i]{"state"}{"freshuntil"} || 0) and $self->{"crl"}[$i]{"state"}{"b64data"} ) { $::log->verb(3,"Using cached content for",$self->{"alias"},"index",$i); $::log->verb(4,"Content dated", scalar gmtime($self->{"crl"}[$i]{"state"}{"lastmod"}), "valid until", scalar gmtime($self->{"crl"}[$i]{"state"}{"freshuntil"}), "UTC"); $result = base64::b64decode($self->{"crl"}[$i]{"state"}{"b64data"}); %response = ( "freshuntil" => $self->{"crl"}[$i]{"state"}{"freshuntil"} || time, "lastmod" => $self->{"crl"}[$i]{"state"}{"lastmod"} || time, "sourceurl" => $self->{"crl"}[$i]{"state"}{"sourceurl"} || "null:" ); } else { foreach my $url ( split(/\001/,$self->{"crlurls"}[$i]) ) { # of these, the first one wins $url =~ /^(http:|https:|ftp:)/ and ($result,%response) = $self->retrieveHTTP($i,$url); $url =~ /^(file:)/ and ($result,%response) = $self->retrieveFile($i,$url); last if $result; } } # check if result is there, otherwise invoke agingtolerance clause # before actually raising this as an error # note that agingtolerance stats counting only AFTER the freshness # of the cache control directives has passed ... if ( ! $result ) { $::log->verb(1,"CRL retrieval for", $self->{"alias"},($i?"[$i] ":"")."failed from all URLs"); if ( $self->{"agingtolerance"} && $self->{"crl"}[$i]{"state"}{"mtime"} ) { if ( ( time - $self->{"crl"}[$i]{"state"}{"mtime"} ) < 3600*$self->{"agingtolerance"}) { $::log->warn("CRL retrieval for", $self->{"alias"},($i?"[$i] ":"")."failed,", int((3600*$self->{"agingtolerance"}+ $self->{"crl"}[$i]{"state"}{"mtime"}- time )/3600). " left of ".$self->{"agingtolerance"}."h, retry later."); } else { $::log->retr_err("CRL retrieval for", $self->{"alias"},($i?"[$i] ":"")."failed.", $self->{"agingtolerance"}."h grace expired.", "CRL not updated"); } } else { # direct errors, no tolerance anymore $::log->retr_err("CRL retrieval for", $self->{"alias"},($i?"[$i] ":"")."failed,", "CRL not updated"); } next; # next subindex CRL for same CA, no further action on this one } # now data for $i is loaded in $result; # for freshness checks, take a sum (SysV style) my $sum = unpack("%32C*",$result) % 65535; $::log->verb(4,"Got",length($result),"bytes of data (sum=$sum)"); $self->{"crl"}[$i]{"data"} = $result; $self->{"crl"}[$i]{"state"}{"alias"} = $self->{"alias"}; $self->{"crl"}[$i]{"state"}{"index"} = $i; $self->{"crl"}[$i]{"state"}{"sum"} = $sum; ($self->{"crl"}[$i]{"state"}{"b64data"} = base64::b64encode($result)) =~ s/\s+//gm; $self->{"crl"}[$i]{"state"}{"retrievaltime"} = time; $self->{"crl"}[$i]{"state"}{"sourceurl"} = $response{"sourceurl"}||"null:"; $self->{"crl"}[$i]{"state"}{"freshuntil"} = $response{"freshuntil"}||time; $self->{"crl"}[$i]{"state"}{"lastmod"} = $response{"lastmod"}||time; } return 1; } sub verifyAndConvertCRLs($) { my $self = shift; $self->{"crlurls"} or $::log->err("Verifying uninitialised list of CRLs impossible") and return 0; # all CRLs must be valid in order to proceed # or we would end up shifting the relative ordering around and # possibly creatiing holes (or overwriting good local copies of # CRLs that have gone bad on the remote end for ( my $i = 0; $i <= $#{$self->{"crlurls"}} ; $i++ ) { # all indices $self->{"crlurls"}[$i] or $::log->verb(3,"CRL",$self->getAnchorName."/".$i,"ignored (no valid URL)") and next; $self->{"crl"}[$i]{"data"} or $::log->verb(3,"CRL",$self->getAnchorName."/".$i,"ignored (no new data)") and next; $::log->verb(4,"Verifying CRL $i for",$self->getAnchorName); my $crl = CRL->new($self->getAnchorName."/$i",$self->{"crl"}[$i]{"data"}); my @verifyMessages= $crl->verify(@{$self->{"cafile"}}); # do additional checks on correlation between download and current # lastUpdate of current file? have to guess the current file # unless we are stateful! my $oldlastupdate = $self->{"crl"}[$i]{"state"}{"lastupdate"} || undef; $oldlastupdate or do { $::log->verb(6,"Attempting to extract lastUpdate of previous D/L"); CRLSTATEHUNT: foreach my $output ( @{$::cnf->{_}->{"output_"}} , $self->{"infodir"} ) { foreach my $file ( $self->{"nametemplate_der"}, $self->{"nametemplate_pem"}, $self->{"alias"}.".r\@R\@", $self->{"anchorname"}.".r\@R\@", ) { next unless $file; (my $thisfile = $file ) =~ s/\@R\@/$i/g; $thisfile = join "/", $output, $thisfile; $::log->verb(6,"Trying guess $file for old CRL"); next if ! -e $thisfile; my $oldcrldata; { open OCF,$thisfile and do { binmode OCF; local $/; $oldcrldata = ; close OCF; } } my $oldcrl = CRL->new($thisfile,$oldcrldata); $oldlastupdate = $oldcrl->getLastUpdate; last CRLSTATEHUNT; } } $::log->verb(3,"Inferred lastupdate for",$self->{"alias"},"is", $oldlastupdate) if $oldlastupdate; }; if ( ! $crl->getLastUpdate ) { push @verifyMessages,"downloaded CRL lastUpdate could not be derived"; } elsif ( $oldlastupdate and ($crl->getLastUpdate < $oldlastupdate) and ($self->{"crl"}[$i]{"state"}{"mtime"} <= time) ) { push @verifyMessages,"downloaded CRL lastUpdate predates installed CRL,", "and current version has sane timestamp"; } elsif ( defined $oldlastupdate and $oldlastupdate > time ) { $::log->warn($self->{"anchorname"}."/$i:","replaced with downloaded CRL", "since current one has lastUpdate in the future"); } $#verifyMessages >= 0 and do { $::log->retr_err("CRL verification failed for",$self->{"anchorname"}."/$i", "(".$self->{"alias"}.")"); foreach my $m ( @verifyMessages ) { $::log->verb(0,$self->{"anchorname"}."/$i:",$m); } return 0; }; $self->{"crl"}[$i]{"pemdata"} = $crl->getPEMdata(); foreach my $key ( qw/ lastupdate nextupdate sha1fp issuer / ) { $self->{"crl"}[$i]{"state"}{$key} = $crl->getAttribute($key) || ""; } # issue a low-level warning in case the cache control headers from # the CA (or its CDN) are bugus, i.e. the CRL wille expire before the # cache does. Don't log at warning, since the site cannot fix this if ( defined ($self->{"crl"}[$i]{"state"}{"freshuntil"}) and ( $self->{"crl"}[$i]{"state"}{"freshuntil"} > ( $self->{"crl"}[$i]{"state"}{"nextupdate"} + $::cnf->{_}->{expirestolerance} ) ) ) { $::log->verb(1,"Cache control headers for CA ".$self->{"alias"}." at ". "URL ".$self->{"crl"}[$i]{"state"}{"sourceurl"}." have apparent ". "freshness ".sprintf("%.1f",($self->{"crl"}[$i]{"state"}{"freshuntil"}- $self->{"crl"}[$i]{"state"}{"nextupdate"})/3600). "hrs beyond CRL expiration nextUpdate. Reset freshness from ". gmtime($self->{"crl"}[$i]{"state"}{"freshuntil"})." UTC to ". $::cnf->{_}->{expirestolerance}." second before nextUpdate at ". gmtime($self->{"crl"}[$i]{"state"}{"nextupdate"})." UTC."); $self->{"crl"}[$i]{"state"}{"freshuntil"} = $self->{"crl"}[$i]{"state"}{"nextupdate"} - $::cnf->{_}->{expirestolerance}; } # limit maximum freshness period to compensate for CAs that overdo it if ( defined ($self->{"crl"}[$i]{"state"}{"freshuntil"}) and $self->{"crl"}[$i]{"state"}{"freshuntil"} > (time + $::cnf->{_}->{maxcachetime}) ) { $self->{"crl"}[$i]{"state"}{"freshuntil"} = time+$::cnf->{_}->{maxcachetime}; $::log->verb(1,"Cache state freshness expiry for CA ".$self->{"alias"}. " reset to at most ". sprintf("%.1f",$::cnf->{_}->{maxcachetime}/3600.). "hrs beyond current time (". gmtime($self->{"crl"}[$i]{"state"}{"freshuntil"})." UTC)"); } } return 1; } 1; # # Library inspired by the Perl 4 code from base64.pl by A. P. Barrett # , October 1993, and subsequent changes by # Earl Hood to use MIME::Base64 if available. # package base64; my $use_MIMEBase64 = eval { require MIME::Base64; }; sub b64decode { return &MIME::Base64::decode_base64 if $use_MIMEBase64; local($^W) = 0; # unpack("u",...) gives bogus warning in 5.00[123] use integer; my $str = shift; $str =~ tr|A-Za-z0-9+=/||cd; # remove non-base64 chars length($str) % 4 and die "Internal error in state: length of base64 data not a multiple of 4"; $str =~ s/=+$//; # remove padding $str =~ tr|A-Za-z0-9+/| -_|; # convert to uuencoded format return "" unless length $str; unpack("u", join('', map( chr(32 + length($_)*3/4) . $_, $str =~ /(.{1,60})/gs) ) ); } sub b64encode { return &MIME::Base64::encode_base64 if $use_MIMEBase64; local ($_) = shift; local($^W) = 0; use integer; # should be faster and more accurate my $result = pack("u", $_); $result =~ s/^.//mg; $result =~ s/\n//g; $result =~ tr|\` -_|AA-Za-z0-9+/|; my $padding = (3 - length($_) % 3) % 3; $result =~ s/.{$padding}$/'=' x $padding/e if $padding; $result =~ s/(.{1,76})/$1\n/g; $result; } 1; fetch-crl-3.0.22/fetch-crl.80000644001343500074740000001664414147645373014422 0ustar davidgemin.\" "@(#)$Id: fetch-crl.8,v 1.6 2009/09/21 20:22:32 pmacvsdg Exp $" .\" .\" .TH FETCH-CRL 8 local "Trust Anchor Utilities" .SH NAME fetch-crl \- retrieve certificate revocation lists .SH SYNOPSIS .ll +8 .B fetch-crl .RB [ \-c\ config ] .RB [ \-v [ v .. ] ] .RB [ \-q ] .RB [ \-h ] .RB [ \-\-inet6glue ] .RB [ \-l\ infopath ] .RB [ \-o\ outputpath ] .RB [ \-s\ statepath ] .RB [ \-a\ agingtolerance ] .RB [ \-T\ httptimeout ] .RB [ \-r\ randomwait ] .RB [ \-p\ parallelism ] .RB [ \-\-formats\ openssl | pem | der | nss ]\ .. .RB [ \-\-define\ key = value ]\ .. .RB [ \-\-cfgdir\ dirname ] .ll -8 .SH DESCRIPTION The .I fetch-crl utility will retrieve certificate revocation lists (CRLs) for a set of installed trust anchors, based on crl_url files or IGTF-style info files. It will install these for use with OpenSSL, NSS or third-party tools. It works based on a list of trust anchors, for each of which one or more CRLs should be installed in a CRL store. And for each of these CRLs, one or more URLs can be specified from which the specific CRL can be retrieved. There are several supported formats for CRL stores: .IP openssl has a directory in which .I hash. .I i files are stored, one CRL per file, and all CRLs for the trust anchors whose subject distinguished name hashes to .I hash are read and evaluated for each certificate issues by the CAs whose subject name hash matches .I hash OpenSSL in version 1 changes its subject name hashing algorithm, though, so that for one trust anchor .B two hashes could be used, depending on the specific OpenSSL version at hand. If OpenSSL version 1 or higher is used by .I fetch-crl and the default mode is used, each CRL is written out twice, once for each possible hash value. This mode in controlled by the .I opensslmode = { .I dual | .I single } configuration option in the configuration file. .IP pem writes out the CRL in PEM (RFC 1421) format. .IP der writes out the CRL in binary under distinguished encoding rules .IP nss will use the crlutil from the Mozilla NSS tools to add or replace a CRL in the NSS cert8.db database. .P Each CRLs can be retrieved from one of several URLs. These URLs are listed by default in the trust anchor meta-data: the .I .info file or the .I .crl_url file, as shipped with the trust anchor. In the crl_url file, there is one URL per line; in the .info file, the .I crl_url attribute is a semi-colon separated list of URLs. These URLs are then tried in order to retrieve a fresh CRL. Once data has been successfully retrieved, this data is used as the CRL if it passes verification, signature checking and expiration checks. Http, https, ftp and file URLs are supported. If data for a CRL has been downloaded but this data fails any of the subsequent checks (signature validation, freshness), the CRL data is discarded and NO further URLs are tried for this CRL! URLs can be pre-pended or post-pended to the default list via the configuration file. This can be used to prefer a local mirror repository over any URLs shipped by the trust anchor provider, without the need to modify the trust anchor metadata. By post-pending a URL, a 'last-resort' download location can be added in case the CA provided URLs cannot be used. The pre- and post-pended URLS are subject to token expansion of the tokens .IR @ALIAS@ ", " @ANCHORNAME@ ", and " @R@ , where .I R is the sequence number of the CRL on a per-trust anchor basis. Retrieved CRLs may be PEM (RFC1421) or DER encoded. They are automatically converted as needed by fetch-crl, using the OpenSSL command-line tool. Retrieving a CRL without having an accompanying CA root certificate in an OpenSSL-accessible form (like .I @ALIAS@.0 or .I @ANCHORNAME@.@R@ will result in a verification failures. The CA lookup directory and patterns can be configured via the configuration file .SH TOKEN EXPANSION In paths and name templates, tokens are expanded to allow a single pattern to be used for all trust anchors. The .IR nametemplate_* , .IR catemplate , .IR prepend_url , and .I postpend_url configuration settings are subject to token expansion. The following tokens are recognised .IP @ALIAS@ The alias name of the trust anchor as defined in the .I info file. If there is no info file and the meta-data is retrieved from .I crl_url files, then the alias is set to the basename (excluding the .crl_url suffix) of the filename of the trust anchor. .IP @ANCHORNAME@ The file name of the trust anchor, without any .info or .url_crl suffix. .IP @R@ The CRL sequence number, counting from 0. Note that most trust anchors only have a single CRL, with sequence number "0". .SH OPTIONS .TP .B \-h --help Show help text. .TP .B \-l --infodir metadata-directory The script will search this directory for files with the suffix '.info' or '.crl_url'. Note: the CRL files to download must be in either PEM or DER format. .TP .B \-o --out outputDirectory Directory where to put the downloaded and processed CRLs. The directory to be used as argument for this option is typically /etc/grid-security/certificates Default: infodir (meta-data directory) .TP .B \-a --agingtolerance hours The maximum age of the locally downloaded CRL before download failures trigger actual error messages. This error message suppression mechanism only works if the CRL has been downloaded at least once and either the crl_url files are named after the hash of the CRL issuer name, or a state directory is used to preserve state across invocations. Default: 24 hour aging tolerance .TP .B \-q --quiet Quiet mode (do not print information messages) .TP .B \-r --randomwait s Wait up to .I s seconds before starting the retrieval process(es). .TP .B \-p --parallelism n Do the retrieval for several trust anchors in parallel, with up to .I n processes doing retrievals. At most .I n downloads will be active at any one time. Multiple CRLs for the same trust anchor are still downloaded sequentially. .TP .B \-\-inet6glue Load the Net::INET6Glue module to enable IPv6 support in LWP. .TP .BI \-\-define\ key = value Add definitions to the configuration at runtime. The key=value pair is appended to the main section of the configuration, unless a colon is used in the key: then the part before the colon is the config file section name, and the part thereafter the key inside that section. To merely set a valueless option, set to to the null-string "". .SH CONFIGURATION See .B http://wiki.nikhef.nl/grid/FetchCRL3 or the included example file for a description of the configuration options. The default location of the configuration file is .IR /etc/fetch-crl.conf . Supplementary configuration is read from all files located in .IR /etc/fetch-crl.d/ , or the directory designated by the .I cfgdir directive, whose collated contents are added to the existing configuration data. .SH NOTES Defaults can be set in the fetch-crl system configuration file /etc/fetch-crl.conf. .SH "SEE ALSO" openssl(1), http://wiki.nikhef.nl/grid/FetchCRL3 .SH "DIAGNOSTICS" Exit status is normally 0; if an error occurs, exit status is 1 and diagnostics will be written to standard error. .SH LICENSE Licensed under the Apache License, Version 2.0 (the "License"); .B http://www.apache.org/licenses/LICENSE-2.0 .SH BUGS Although fetch-crl3 will install multiple CRLs in the CRL stores (called '.r0', '.r1', or labelled appropriately in an NSS store), if the number of CRLs decreases the left-overs are not automatically removed. So if the number of CRLs for a particular CA does down from .IR n " to " n-1 , the file .RI '.r n ' must be removed manually. fetch-crl-3.0.22/fetch-crl-cron.cron0000644001343500074740000000113014147645373016133 0ustar davidgemin# Cron job running by default every 6 hours, at 45 minutes +/- 3 minutes # The lock file can be enabled or disabled via a # service fetch-crl-cron start # chkconfig fetch-crl-cron on # Note the lock file not existing is success (and over-all success is needed # in order to prevent error messages from cron. "-q" makes it really # quiet, but beware that the "-q" overrides any verbosity settings 42 */6 * * * root [ ! -f /var/lock/subsys/fetch-crl-cron ] || ( [ -f /etc/sysconfig/fetch-crl ] && . /etc/sysconfig/fetch-crl ; /usr/sbin/fetch-crl -q -r 360 $FETCHCRL_OPTIONS $FETCHCRL_CRON_OPTIONS ) fetch-crl-3.0.22/fetch-crl-cron.init0000644001343500074740000000235614147645373016150 0ustar davidgemin#!/bin/sh # # fetch-crl-cron This shell script enables periodic CRL retrieval via cron # # chkconfig: - 65 01 # # description: Run the certificate revocation lists update periodically via cron # processname: fetch-crl-cron # config: /etc/fetch-crl.conf # ### BEGIN INIT INFO # Provides: fetch-crl-cron # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 3 5 # Default-Stop: 0 2 1 6 # Description: Run the certificate revocation lists update periodically via cron ### END INIT INFO # source function library . /etc/rc.d/init.d/functions lockfile=/var/lock/subsys/fetch-crl-cron RETVAL=0 start() { action $"Enabling periodic fetch-crl: " touch "$lockfile" RETVAL=$? } stop() { action $"Disabling periodic fetch-crl: " rm -f "$lockfile" RETVAL=$? } case "$1" in start) start ;; stop) stop ;; restart|force-reload) $0 stop $0 start ;; reload) ;; condrestart) [ -f "$lockfile" ] && { $0 stop $0 start } ;; status) if [ -f $lockfile ]; then echo $"Periodic fetch-crl is enabled." RETVAL=0 else echo $"Periodic fetch-crl is disabled." RETVAL=3 fi ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}" exit 1 esac exit $RETVAL fetch-crl-3.0.22/fetch-crl-boot.init0000644001343500074740000000334714147645373016153 0ustar davidgemin#!/bin/sh # # fetch-crl-boot This shell script trigger a run of fetch-crl at boot time # # chkconfig: - 65 01 # # description: Run of fetch-crl, a crl updater, on boot up # processname: fetch-crl # config: /etc/fetch-crl.conf # config: /etc/sysconfig/fetch-crl ### BEGIN INIT INFO # Provides: fetch-crl # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 3 5 # Default-Stop: 0 2 1 6 # Description: Run of fetch-crl, a crl updater, on boot up ### END INIT INFO # fetch-crl-boot must run after the start of xinetd and afs, since the URLs # may point to files on an AFS file system or to xinetd-based service URLs # source function library . /etc/rc.d/init.d/functions # source any environment settings, e.g. for HTTP proxies [ -f /etc/sysconfig/fetch-crl ] && . /etc/sysconfig/fetch-crl lockfile=/var/lock/subsys/fetch-crl-boot RETVAL=0 start() { if [ ! -f $lockfile ]; then action $"Running fetch-crl on boot: " /usr/sbin/fetch-crl -q $FETCHCRL_OPTIONS $FETCHCRL_BOOT_OPTIONS RETVAL=$? [ "$RETVAL" = 0 -o "$RETVAL" = 2 ] && touch $lockfile else RETVAL=0 fi } stop() { RETVAL=0 [ "$RETVAL" = 0 ] && rm -f $lockfile } case "$1" in start) start ;; stop) stop ;; restart|force-reload) $0 stop $0 start ;; condrestart) [ -f $lockfile ] && { $0 stop $0 start } ;; reload) ;; status) if [ -f $lockfile ] ; then echo -n $"fetch-crl-boot lockfile present" && success RETVAL=0 else echo -n $"fetch-crl-boot lockfile missing" && failure RETVAL=1 fi echo ;; *) echo $"Usage: $0 {start|stop|status|restart|reload|force-reload|condrestart}" exit 1 esac exit $RETVAL fetch-crl-3.0.22/fetch-crl.cnf0000644001343500074740000000021714147645373015006 0ustar davidgemin# # Default configuration file for fetch-crl3 # @(#)$Id$ # infodir = /etc/grid-security/certificates agingtolerance = 24 nosymlinks nowarnings fetch-crl-3.0.22/fetch-crl.cnf.example0000644001343500074740000004021614147645373016443 0ustar davidgemin# # EXAMPLE configuration file for Fetch-crl3 # @(#)$Id$ # # configuration file fetch-crl3 # use SEMICOLON (;) or \001 (^A) as list separators in values # # --------------------------------------------------------------------------- # cfgdir sets the directory where subordinate configuration files are # found. These files are read in addition to the main config file. # The default directory is /etc/fetch-crl.d/ and is used by default, so # to suppress this behaviour set this to the empty value "" # # cfgdir = /etc/fetch-crl.d # # --------------------------------------------------------------------------- # infoset set the location where the meta-data files (.info or .crl_url) # are help by default. All trust anchors listed there are processes, so # to suppress this behaviour set this to the empty value "" # # infodir = /etc/grid-security/certificates # # --------------------------------------------------------------------------- # cadir sets the location where the trust anchors themselves are found, as # PEM files, to be used in the CRL verification by openssl. They are usually # names after the trust anchor proper name ("alias.0"), or after the filename # of the trust anchor, the basename of the meta-data file name ("hash.0"). # It defaults to infodir # # cadir = /etc/grid-security/certificates # # --------------------------------------------------------------------------- # output sets the location where the retrieved CRLs are written by default. # It can be overridden on a per-output-format basis by setting the # "output_" options. It should point to a directory (even for the # NSS output format. It defaults to infodir # # output = /etc/grid-security/certificates # # --------------------------------------------------------------------------- # statedir points to the directory where per-CRL state files are kept. These # state files record the retrieval time, last-retrieved (modification) time, # best-before date and the (cached) content of the CRL. For the purposes of # the CRL state, all CRL URLs for a particular trust anchor index are # considered equal. # If it is unset, no state is preserved, but the last-retrieved time is # guessed from the modification time. If statedir does not exist, or is # not writable, it is not used but silently ignored. Writeability is # determined by perl's "-w" test. # It defaults to /var/cache/fetch-crl # # statedir = /var/cache/fetch-crl # # --------------------------------------------------------------------------- # formats lists one or more ways to write out the CRL to the output # directories. It can be one or more of "openssl", "der", "pem", or "nss" # in a comma-separated list. # * the "openssl" format writes out "hash.rX" files, with being the # first 4 bytes of the digest of the subject DN, and "X" a sequence number # of the CRL starting at 0 (".r0"). When used with OpenSSL version 1.0.0 # or above, it can write out the CRL with two possible hash algorithms at # the same time: the 'old' MD5 of the binary subject DN representation, or # the 'new' SHA1 based digest of the canonical representation. Whether # one or two hashes are written is determined by the "opensslmode" option. # * "pem" writes out the CRL in PEM (RFC1421) format, to the file named # after the "nametemplate_pem" setting (default: @ANCHORNAME@.@R@.crl.pem) # in the output or output_pem directory # * "der" does the same in DER binary format, to a file names # after the "nametemplate_der" setting (default: @ANCHORNAME@.@R@.crl) # in the output or output_der directory # * "nss" adds (or replaces) the named CRL in the NSS database in # /cert8.db, using the Mozilla crlutil tool # # formats = openssl # # --------------------------------------------------------------------------- # specialised output directories # # output_pem = /etc/pki/tls/certs # output_der = /var/tmp # output_nss = /etc/pki/nssdb # # --------------------------------------------------------------------------- # name templates are used to construct the file name of a CRL for installation # based on the meta-data of the CA. It uses token replacement to construct # a specific and unique filename. The tokens recognised are the same as those # of the pre- and postpend URLs: # @ANCHORNAME@ base name of the trust anchor meta-data file name # @ALIAS@ alias name of the trust anchor from the info file (defaults # to the @ANCHORNAME@) # @R@ the sequence number of the CRL for this trust anchor # # nametemplate_der = @ANCHORNAME@.@R@.crl # nametemplate_pem = @ANCHORNAME@.@R@.crl.pem # # --------------------------------------------------------------------------- # catemplate has a (list of) potential names of the certificate of the # trust anchor -- it is used to find the CA data for verifying the # retrieved CRLs. Even if you only use NSS databases, you need a directory # with PEM formatted certificates of the issuing CAs. # # catemplate = @ALIAS@.pem; @ALIAS@.@R@; @ANCHORNAME@.@R@ # # When @HASH@ (c_hash from default OpenSSL version as based on the retrieved # CRL) is used in this template list, a CRL will *always* be retrieved first, # even if no corresponding trust anchor is found later. Use of @HASH@ is # only recommended in case the name of the crl_url or info file is different # from the name of the trust anchor. # # catemplate = @ALIAS@.pem; @ALIAS@.@R@; @ANCHORNAME@.@R@; @HASH@.0 # # --------------------------------------------------------------------------- # opensslmode is used if the openssl format for output is specified and also # OpenSSL version 1.0.0 or higher are used. If so, you can have the CRL data # be written out twice, once with the 'old' and once with the 'new' hash style # Default is dual mode, so if OpenSSL 1.x is present, by default TWO files # are written # # opensslmode = dual # opensslmode = single # # --------------------------------------------------------------------------- # nonssverify disables the checking of imported CRLs into an NSS database. # so that you can create a database withonly CRLs, and no CAs. It passes the # "-B" option to the crlutil tool # # nonssverify # # --------------------------------------------------------------------------- # use up to thread in parallel to retrieve and install CRLs # This feature is likely NOT COMPATIBLE with the use of NSS databases for # CRLs, due to thread contention issues # # parallelism = 5 # # --------------------------------------------------------------------------- # wait up to seconds before doing anything at all # useful for randoming the start time and download from cron across the world # # randomwait = 0 # # --------------------------------------------------------------------------- # logmode defined how the log and error messages are written out: # direct - print them immediately, only the message # qualified - print immediately, but prexif it with the message type # "WARN", "ERROR", "VERBOSE(x)", or "DEBUG(x)" # cache - save messages and dump them all at once at the end # syslog - write the message to system with a decent severity level # using facility (default: daemon) # # logmode = qualified # # --------------------------------------------------------------------------- # wait at most seconds for the retrieval of a data blob # from a remote URL (http, https, or ftp). The timeout covers the whole # retrieval process, incliding DNS resolution. Default is 120 seconds. # # httptimeout = 30 # # --------------------------------------------------------------------------- # httpproxy sets the url for the HTTP proxy to use (in perl LWP style). Or # use ENV to pick up the settings from the environment # # http_proxy = http://localhost:8001/ # # --------------------------------------------------------------------------- # nowarnings suppresses the pritning and logging or any and all warnings (but # not errors or verbose messages) # # nowarnings # # --------------------------------------------------------------------------- # noerrors suppresses the pritning and logging or any and all errors (but # not warnings or verbose messages). It also suppresses retrieval errors. # # noerrors # # --------------------------------------------------------------------------- # rcmode determines if the return code of fetch-crl will be influenced by # CRL retrieval errors. If rcmode is "normal" (default), any reported errors # will cause the return exit status to be "1". # normal - both retrieval and other errors set exit code 1 # differentiated - retrieval errors result in exit code 2, presence # of any other reported errors result in exit 1 # noretrievalerrors - retrieval errors only results in exit code 0, presence # of any other reported errors result in exit 1 # Note that setting "noerrors" will suppress retrieval errors entirely! # # rcmode = normal # # --------------------------------------------------------------------------- # noquiet ignores a single "-q" option on the commandline and honours the # verbosity set here even if -q is specified. To counter this setting, give # at least two (2) "-q" arguments # # noquiet # # --------------------------------------------------------------------------- # agingtolerance sets the time in hours before retrieval warnings become # errors for a CRL retrieval. If you also suppress warnings, you will # prevent any annoying messages for a trust anchor for up to hours. # The IGTF currently recommends an aging tolerance of 24 hours, to allow # for network disruptions and connectivity problems. # # agingtolerance = 24 # # --------------------------------------------------------------------------- # cache_control_request sends a cache-control max-age hint towards the # server in the HTTP request, that suggests to intermediate caches and # reverse proxies to cache CRL replies no longer than the specified time # This control is a hint towards caching servers and CDNs and cannot be # enforced. It does NOT affect the cache local to fetch-crl # Default is unset, and no Cache-control header will be sent unless this # config option is specified # # cache_control_request = 3600 # # --------------------------------------------------------------------------- # prepend_url URLs are tried first before using any URLs form the crl_url # file or the .info crl_url (crl_url.0) fields # # prepend_url = file:///share/grid-security/certificates/@ALIAS@.r@R@ # # --------------------------------------------------------------------------- # postpend_url URLs are tried last, only if all URLs form the crl_url file # or the .info crl_url (crl_url.0) fields have already failed or timed out # # postpend_url = http://dist.eugridpma.info/certificates/@ANCHORNAME@.r@R@ # # --------------------------------------------------------------------------- # path to openssl version to use # openssl = /usr/bin/openssl # # --------------------------------------------------------------------------- # path to use to find utilities like OpenSSL or crlutil. Default leaves it # unmodified # # path = /bin:/usr/bin:/usr/ucb # # --------------------------------------------------------------------------- # settings "backups" will trigger the generation of backup files (~ files) # when writing CRLs to an output destination. # # backups # # --------------------------------------------------------------------------- # stateless supresses any use of the state directory, even if it exists and # is writable # # stateless # # --------------------------------------------------------------------------- # By default, the perl LWP library does not use IPv6 network sockets. The # perl module Net::INET6GLUE::INET6_as_INET can mitigate this behaviour # by re-mapping all INET socket calls to INET6 socket calls. If you have # the Net::INET6Glue module installed, you may enable this flag in the # cofiguration. Note: the Net::INET6Glue module MUST be installed for this # flag to work. Installation of this module is options and it does not # ship by default with fetch-crl3. You can obtain this module from CPAN. # # inet6glue # # --------------------------------------------------------------------------- # To run a script after the completion of every fetch-crl run, set this # path to point to an executable. The named program will be invoked # with the following arguments # "v1" "global" # - return code of the program will influence return status of fetch-crl # - this must be a program path - no arguments are allowed here. Use wrapping # in a script if you must pass your own arguments as well # # postexec = # # --------------------------------------------------------------------------- # override the UserAgent string used for all downloads. This may be needed # if you hit an over-active firewall or proxy in your network path that # blocks apparent libwww-perl user agents. Can also be set per trust anchor # # user_agent = # # --------------------------------------------------------------------------- # override version or packager to influence the User-Agent header in http # requests. But please leave them alone # version = 3.0 # packager = EUGridPMA # =========================================================================== # PER TRUST ANCHOR OVERRIDES # =========================================================================== # # many settings can be overrules in a per-trust anchor section of the # configuration file. For each trust anchor, only a SINGLE override # section will be used. If a section names after the @ALIAS@ exists, # it will take precedence over any section named after @ANCHORNAME@. # # To have a section work with either ".info" or ".crl_url" files, name it # after the @ANCHORNAME@, since that one will be the same for both. # Example: the DutchGrid CA "NIKHEF" can be either [NIKHEF] or [16da7552] # (the latter is the commonly used file name), but using [16da7552] will # result in the section being recognised in both cases # # [16da7552] # --------------------------------------------------------------------------- # agingtolerance for this trust anchor specifically. Use it if the retrieval # for this CA is unreliable. # # agingtolerance = 12 # # --------------------------------------------------------------------------- # replace the list of CRL URLs for this CA and this CRL sequence number # by a completely new set. E.g. from a different place, or a local # cache, or ... # # crl_url.0 = http://ca.dutchgrid.nl/medium/cacrl.pem; file:///etc/grid-security/certificates/16da7552.r0 # # --------------------------------------------------------------------------- # To never hear of this CA again, suppress both errors and warnings: #noerrors #nowarnings # # --------------------------------------------------------------------------- # Do not process symlinked meta-data, preventing triple downloads with # the new-format IGTF distribution before release 1.37 (1.33 up to and # including 1.36 also symlinked the .info file to the hash names) #nosymlinks # # --------------------------------------------------------------------------- # To run a script after the successful completion of each CRL retrieval set # path to point to an executable. The named program will be invoked # with the following arguments # "v1" "ta" # - return code of the program will influence return status of fetch-crl # - program may run IN PARALLEL, so should be written to permit concurrent # execution # - this must be a program path - no arguments are allowed here. Use wrapping # in a script if you must pass your own arguments as well # # postexec = # # --------------------------------------------------------------------------- # You can also (un) set the following on a per-trust anchor basis: # # (no)prepend_url (no)postpend_url (no)http_proxy (no)statedir -- # either remove a global setting, or put in a new setting with value # # (no)warnings (no)noerrors (no)nocache -- # override a global setting (no value possible) # # agingtolerance httptimeout nametemplate_der nametemplate_pem # cadir catemplate user_agent # set these to a local value (but they cannot be unset) # # # Share and enjoy -- and remember that up to 7 verbosity levels are # significant :-) "-vvvvvvvv" is a useful option ... # # fetch-crl-3.0.22/NOTICE0000644001343500074740000000142514147645373013355 0ustar davidgemin Fetch-crl3 - the Certificate Revocation List retrieval tool Copyright 2010 David Groep, Nationaal instituut voor subatomaire fysica Nikhef , Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. fetch-crl-3.0.22/LICENSE0000644001343500074740000002367714147645373013473 0ustar davidgemin Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS fetch-crl-3.0.22/README0000644001343500074740000001103614147645373013330 0ustar davidgemin============================================================================== fetch-crl - the Certificate Revocation List retrieval tool ============================================================================== The fetch-crl utility will retrieve certificate revocation lists (CRLs) for a set of installed trust anchors, based on crl_url files or IGTF-style info files. It will install these for use with OpenSSL, NSS or third-party tools. For more extensive information about fetch-crl3, please look on the web at: http://www.nikhef.nl/grid/fetchcrl3 USAGE ----- Usage: fetch-crl [-c|--config configfile] [-l|--infodir path] [--cadir path] [-s|--statedir path] [-o|--output path] [--format @formats] [-T|--httptimeout seconds] [-p|--parallelism n] [-a|--agingtolerance hours] [-r|--randomwait seconds] [-v|--verbose] [-h|--help] [-q|--quiet] [-d|--debug level] Options: -c | --config path Read configuration data from path, default: /etc/fetch-crl.conf -l | --infodir path Location of the trust anchor meta-data files (crl_url or info), default: /etc/grid-security/certificates --cadir path Location of the trust anchors (default to infodir) -s | --statedir path Location of the historic state data (for caching and delayed-warning) -T | --httptimeout sec Maximum time in seconds to wait for retrieval or a single URL -o | --output path Location of the CRLs written (global default, defaults to infodir --format @formats Format(s) in which the CRLs will be written (openssl, pem, der, nss) -v | --verbose Become more talkative -q | --quiet Become really quiet (overrides verbosity) -p | --parallelism n Run up to n parallel trust anchor retrieval processes -a | --agingtolerance hours Be quiet for up to hours hours before raising an error. Until the tolerance has passed, only warnings are raised -r | --randomwait seconds Introduce a random delay of up to seconds seconds before starting any retrieval processes -h | --help This help text CONFIGURATION ------------- The fetch-crl3 tool has built-in defaults that are suitable for 'grid' setups, where trust anchors are installed in /etc/grid-security/certificates. It will usually do what you want, if you use OpenSSL-like applications. If you want, you can tune fetch-crl in a myriad of ways, by setting any of the flags or options in the configuration file. This configuration file is looked for in "/etc/fetch-crl.conf" by default, but an alternative location can be specified with the "-c" command-line option. Please look at the web site or in the example configuration file for more explanation of the various configuration settings. CONTRIBUTIONS AND ACKNOWLEDGEMENTS ---------------------------------- Fetch-crl3 is a complete re-write of the utility, but of course owes to the extensive experience and contributions made over time by the contributors to fetch-crl 1.x and 2.x, and to the people that reported bugs and feature requests. The original fetch-crl was developed for the acclaimed EU DataGrid project by Fabio Hernandez and many significant contributions were made by Steve Traylen. Fetch-crl3 was developed by David Groep, mainly for enjoyment, with the help of large quantities of coffee and Spa Rood, and minimal quantities of sleep. This work is part of the research programme of the Dutch Foundation for Fundamental Research on Matter (FOM), which is financially supported by the Netherlands Organisation for Scientific Research (NWO). This work is part of the programme of BiG Grid, the Dutch e-Science Grid, which is financially supported by the Nederlandse Organisatie voor Wetenschappelijk Onderzoek (Netherlands Organisation for Scientific Research, NWO). SUPPORT ------- Please send suggestions, bugs and feature requests (and certainly patches) to . Thanks a lot for your help! COPYRIGHT --------- Copyright 2010-2013 David Goep National Institute for Sub-Atomic Physics, FOM-Nikhef Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. fetch-crl-3.0.22/CHANGES0000644001343500074740000004465614147645373013461 0ustar davidgemin============================================================================== CHANGES to fetch-crl - the Certificate Revocation List retrieval tool ============================================================================== The fetch-crl utility will retrieve certificate revocation lists (CRLs) for a set of installed trust anchors, based on crl_url files or IGTF-style info files. It will install these for use with OpenSSL, NSS or third-party tools. Changes in 3.0.22-1 ---------------------- * fix race condition in CRL file re-writing for cases where the CRL directory itself is writable (thanks to Arjen Nienhuis) Changes in 3.0.20-1 ---------------------- * network connection failure messages are pre-filtered and only primary status lines shown in logs for download and head requests (bugzilla #29) Changes in 3.0.19-1 ---------------------- * Do not add spurious newline to DER-format files (fixes report 201670320-01) * run a script after the completion of every fetch-crl run (uses postexec directive in config file) Changes in 3.0.17-1 ---------------------- * Add optional cache-control max-age headers in all requests to hint a maximum caching time to intermediate servers (bugzilla #26) Changes in 3.0.16-1 ---------------------- * Added cache state freshness constraints (default maxcachetime set to 96hrs) * Re-set cache expiry of state data if CRL nextUpdate is within or beyond 7 hrs (config "expirestolerance") claimed URL Expiry or Cache-control max-age Changes in 3.0.15-1 ---------------------- * Fixed issues resulting in undefined attribute values to be returned for CRL Changes in 3.0.14-1 ---------------------- * Requesting CRL retrieval for an empty trust anchor store is now a warning and no longer an error Changes in 3.0.13-1 ---------------------- * Supplied system init script for boot phase will not re-run inadvertently * Add rcmode config option (added differentiated reporting and success-on- solely-retrieval-errors) * Add --define key=val command line argument to augment configuration data * Setting FETCHCRL_OPTIONS in /etc/sysconfig/fetch-crl will add these options to the commandline of fetch-crl on start from cron or at boot time Setting FETCHCRL_BOOT_OPTIONS adds them to the boot init script only (e.g. FETCHCRLBOOTOPTIONS="--define rcmode=differentiated") and FETCHCRL_CRON_OPTIONS does the same only for the cron job script Changes in 3.0.12-1 ---------------------- * PEM formatted CRLs now always include a final newline character (fix provided by Harald Barth ) Changes in 3.0.11-1 ---------------------- * Added reference to /etc/fetch-crl.d/ to the man page, used shortened URL to full documentation in man page * Added version information to help output and added -V option * Added a dangerous clean-crl script to remove stale .r* files (beware!) Changes in 3.0.10-1 ---------------------- * Added a "noquiet" option in the configuration file that will override the default single "-q" option in the cro-job that is shipped with the fetch-crl3 init scripts (feature request by Ryan Taylor) * Added option "--inet6glue" and "inet6glue" config setting to load the Net::INET6Glue perl module (if it is available) to use IPv6 connections in LWP to download CRLs Changes in 3.0.8-1 ---------------------- * Trust anchor name inferrence based on retrieved-CRL added as option (at cost of retrieving CRL even if there is no accompanying trust anchor found later) Option is disabled by default, but can be enabled by using @HASH@ in the ca-template name list. (feature request by Rob van der Wal, SARA, NL) Changes in 3.0.7-1 ---------------------- * CRL modofication time heuristic inadvertently modified file name templates (solves issue kindly reported by Elan Ruusamae) * Expanded representation of tokenisation characters in strings to work around bug in file(1) (rhbz#699546, works around RedHat Bugzilla 699548) Changes in 3.0.6-1 ---------------------- * Response parsing disabled to suppress superfluous warning on unexpected UTF-8 respons when retrieving a CRL (solves RedHat Bugzilla 688902) Changes in 3.0.5-1 ---------------------- * CRLs for multiple similarly-named trust anchors might not all be downloaded. This is fixed in this release. * Spurious "restoreLogMode" internal errors are no longer raised Changes in 3.0.4-1 ---------------------- * Add support for directory based drop-in configuration in /etc/fetch-crl.d/ * Only use cached CRL contents if the nextUpdate time of the cached CRL is still in the future. This will ensure that a new download is attempted each and everytime for CRLs that have already expired. Changes in 3.0.3-1 ---------------------- * Clean up of man page format macro PU (reported by Mattias Ellert) Changes in 3.0.2-1 ---------------------- * Clean up of man page format macro PU (reported by Mattias Ellert) Changes in 3.0.1-1 ---------------------- * hunts through more places to find the latest successful CRL download to set the latest local modification time for a CRL (resolves a comparison error in case output and infodir are unset) Changes in 3.0.0-0.RC4 ---------------------- * the config file name has changed to fetch-crl.conf, although a fetch-crl.cnf file will also be used when present * symlinked meta-data files can be ignored with the --nosymlinks option (or nosymlinks in the configuration file). This allows fetch-crl to be used effectively with new-format IGTF distribution before 1.37 * infinite loop for non-indexed CA file names fixed Changes in fetch-crl 3.0 ------------------------ * fetch-crl 3.0 is a complete re-write, and shares no code with the 1.x and 2.x series utility of the same name, although the function and some of the syntax is obviously the same * support for multiple output formats: OpenSSL 1 in dual-hash mode, specific DER and PEM outputs, and NSS databases * support for multiple CRLs for a single CA, allowing more than one CA with the same subject name but different CLRs. Review your client software to see if and how these CRLs are used. * stateful retrieval helps reduce bandwidth usage by caching the CRLs locally and respecting the Cache Control headers sent by the web server hosting the CRL. This can reduce the number of downloads * support for HEAD-only requests when state preservation is used (initially only retrieve HTTP headers, and only if the CRL actually changed to a full download) * support for more CRL retrieval protocols (file:// and ftp://) * ability to try site-local URLs first, before relying on the URLs shipped with the trust anchor. This allows building an explicit local caching (web) server. * ability to specify additional URLs to try in case the URLs shipped with the trust anchor were not responsive. This allows for automatic fall-back to (local or global) mirror services for CRL downloads * warnings and errors can be suppressed on a per-trust anchor basis, to allow silencing for particularly unstable trust anchors * aging tolerance (the delay time before errors are generated in case downloads consistently fail) can be configured on a per-trust anchor basis * parallel downloading for multiple trust anchors * minimized use of temporary files in the file system (now limited to the invocation of OpenSSL only, and only for brief periods of time) * dependencies on wget, lynx and other unix utilities have been removed * explicit web proxy support (using LWP http proxies) * completely re-written in perl, with some (hopefully minimal) dependencies: LWP, Sys::Syslog, POSIX. And Data::Dumper (when debugging is enabled), and IO::Select (if parallel downloads are enabled). Differences with respect to the previous versions * when downloading CRLs via https, the server certificate is not checked, neither for the correct DNS name nor for being issued by a valid CA. Since the CRL in itself is signed, this is not a security vulnerability. If stricter checking is anyway desired, and the Crypt::SSLeay perl module has been installed, set the HTTPS_CA_FILE environment variable before invoking fetch-crl -- but keep in mind that the DNS name verification is limited and will (incorrectly) reject DNS names if these are listed only in the subjectAlternativeName of the server certificate * Existing files with a name that matches a CRL target name are overwritten, even if they did not originally contain CRL data. In v2 this was configurable via the FORCE_OVERWRITE configuration setting. In version 3, files are overwritten by default, and this can no longer be configured. * fetch-crl3 will no longer check CA certificates for consistency or validity by themselves, only retrieved CRLs are verified Downsides of the new version * it requires perl5 to be installed (tested with perl 5.8.0 and higher) with libwww-perl, whereas version 2 only required a traditional Bourne shell * requires a version of OpenSSL (0.9.5a or better) to be installed. Needs OpenSSL 1.0.0 (at least beta5) for dual-hash support. * when using parallel downloads, it can only run on pure-POSIX systems * parallelism in combination with the NSS database output format is not tested * Even when only the NSS database output format has been selected, OpenSSL is still needed for verification and processing ============================================================================== The change log below applies to the 1.x and 2.x series fetch-crl and is included for historical purposes only. Fetch-crl3, with which this changes file is being shipped, is a complete re-write of the utility. Although a lot of backwards compatibility has been preserves, there have been significant changes and the information below should NOT be used to infer any behaviour of fetch-crl3. Fetch-crl 1.x and 2.x were released under the EU DataGrid License. Changes in version EGP 2.8.5 ---------------------------- (2010.06.03) * fetch-crl was occasionally leaving behind {hash}.r0.XXXXXX.r0 files This has been fixed in this release (patch thanks to Jason Smith, BNL) * man page was not compliant to Debian guidelines, this has been fixed (patch thanks to Mattias Ellert, Uppsala University) Changes in version EGP 2.8.4 ---------------------------- (2010.04.04) * Fixes error when randomWait is not set [RH Bug 579488] Changes in version EGP 2.8.3 ---------------------------- (2010.03.28) * Preserve SELinux context for CRL files if SElinux status program exists and selinux is enabled (RH bug 577403) * Fix argument parsing on syslog facility specification (RH bug 577387) * Increase granularity of the RandomWait and allow for 0 in -r option Changes in version EGP 2.8.2 ---------------------------- (2010.03.03) * Improved support for multiple CRL URLs by downloading until a success is achieved, instead of downloading all of them * Imported randomwait patch from Steve Traylen Changes in version EGP 2.8.1 ---------------------------- (2010.01.26) * The installed CRL file is re-checked for validity to catch file system errors and local disk corruption. When possible, it will try to restore a backup copy. Failures are not subject to aging tolerance. Changes in version EGP 2.8.0 ---------------------------- (2009.09.22) * The RPM packaging has been overhauled and is now sufficiently conformant to EPEL and FedoraProject guidelines. * New init scripts and a cron job entry have been added to allow management of fetch-crl via the chkconfig mechanism These changes were contributed by Steve Traylen (CERN, Geneva, CH). Changes in version EGP 2.7.0 ---------------------------- (2009.01.25) * Warnings and errors are now counted. If there are errors in the download or verification process for one or more CRLs, the exit status will be 1; if there are errors in the local setup or in the script invocation, the exit status will be 2. * The installed CRLs no longer have the textual representation of the CRL, but only the PEM data blob, thus reducing IO and memory requirements. * the CRL aging threshold is now set by default to 24 hours. The previous default was 0. The CRL aging threshold is set in the config file using CRL_AGING_THRESHOLD=, or with the "-a" command-line argument. * Default network timeouts reduced to 10 seconds (was 30) and retries to 2 * Added caching and conditional downloading. When CACHEDIR is set, the original downloads are preserved and wget timestamping mode enabled. When the content did not change, only the timestamp on the installed CRL is updated. If SLOPPYCRLHASHES is set, the has is calculated based on the name of the crl_url file, otherwise it is taken from the CRL itself. - The CACHEDIR must be exclusively writable by the user running fetch-crl - Setting CACHEDIR significantly reduced the bandwidth used by fetch-crl * Added RESETPATHMODE setting in sysconfig. It defines whether or not to set re-set $PATH to "/bin:/usr/bin" before start. The search for OpenSSL may be done based on the old path. yes=always replace; searchopenssl=search for openssl first and then reset; no=keep original path, whatever that me be (may be empty if called from cron) Default="yes". This replaces the hard-coded path in the tool! * Hidden "FORCE_OVERWRITE" option now has a regular name. This is backwards- compatible. Set FORCE_OVERWRITE=yes if you want files overwritten that have a CRL-like name and ought to have CRL content, but currently do not. * Addresses gLite Savannah bugs 28418 and 29559. Bug 27023 is partially addressed. Bug 20062 can be remedied with WGET_OPTS arguments. Addresses OSG ticket 4673. Changes in version EGP 2.6.6 ---------------------------- (2007.09.16) (version 2.5.5 is invalid and was not publicly released) * Added obscure configuration parameter to allow overwriting of arbitrary data files with a downloaded CRL (on request of CERN, see https://savannah.cern.ch/bugs/index.php?29559) Changes in version EGP 2.6.4 ---------------------------- (2007.08.15) * Expired CA issuer certificate now gives a warning instead of an error with the full verification result message * additional logfile output target can be selected via the configuration file * CRL aging threshold documented in manual page. Errors will now also be generated in the CRL download failed consistently and the current CRL has already expired Changes in version EGP 2.6.3 ---------------------------- (2006.11.13) * cron job example: fetch-crl invocation syntax error corrected Changes in version EGP 2.6.2 ---------------------------- (2006.10.27) * fixed bug: older wget versions do not recognise --no-check-certificate Changes in version EGP 2.6.1 ---------------------------- (2006.10.25) * fixed local timezone vs UTC error in LastUpdate CRL validation comparison * fixed time comparison is the one-hour LastUpdate/download tolerance (both fixes thanks to Alain Roy) * added support for directory names containing whitespace * added support for syslog reporting (via -f option or SYSLOGFACILITY directive) * SERVERCERTCHECK=no is now the default. It can be reset via the configuration file, or using the "--check-server-certificate" commandline option * the main configuration file location (formerly fixed to be /etc/sysconfig/fetch-crl) can now be set via the variable $FETCH_CRL_SYSCONFIG * logfile format timestamp and tag have been normalised Changes in version EGP 2.6 -------------------------- (2006.05.20) * if the current local CRL has a lastUpdate time in the future, and the newly downloaded CRL is older that the current one, allow the installation of the newly downloaded CRL and issue a warning. * added non-suppressable warning in case the newly downloaded CRL has a lastUpdate time in the future, but install that CRL anyway (as the local clock might have been wrong). Changes in version EGP 2.5 -------------------------- (2006.01.16) * added additional configuration arguments and configuration variables to skip the server certificate check in wget (to support https:// URLs where the server is authenticated with a certificate that is not part of it's own trusted domain, such as the KISTI URL) Changes in version EGP 2.4 -------------------------- (2005.11.15) * for those platforms that support the stat(1) command, and in case the .crl_url file is named after the hash of the crl subject name to download, error eporting for individual download errors can be suppressed for a configurable amount of time as set via the "-a" option (unit: hours). Changes in version EGP 2.3 -------------------------- (2005.11.05) * do not replace recent CRLs with ones that have an older lastUpdate timestamp (prevents ARP/DNS DoS attacks) Changes in version EGP 2.2 -------------------------- (2005.10.27) * secure http download by wget recognise the CAs in the trusted directory. solves the issue described in the LCG bug tracking system https://savannah.cern.ch/bugs/index.php?func=detailitem&item_id=12182 Changes in version EGP 2.1 -------------------------- (2005.08.12) * specifically look for the most recent version of OpenSSL. The one in GLOBUS_LOCATION (which used to take precedence in the previous releases) is outdated in many cases and caused troubles on the LCG production systems in validating v2 CRLs * added manual page fetch-crl(8) Changes in version EGP 2.0 -------------------------- (2005.02.28) * name of the installed script changed to "fetch-crl" * the cronjob script is no longer installed by default, but supplied as an example in the %doc directory * RPM is now relocatable (default install in /usr) * READMA and CHANGES file now inclued in %doc tree * make install now installs * version increased to 2.0 Changes in version EGP 1.9 -------------------------- (2005.02.24) * the content of the final target CRL file is now checked for containing a valid CRL if it already exists. If it does not contain a CRL, an error is displayed and the file left untouched So making the final ".r0" file in ${outdir} a link to something else will not work, preventing an escalation in the final stage. Changes in version EGP 1.8 -------------------------- (changes from Fabio's version 1.7, 2005.02.24) * All temporary files (the initial CRL download using wget and the PEM-converted version of that file) are now created using mktemp * the RetrieveFileByURL function will not overwrite files that have any data in them * Note that the script can be run by a non-priviledged user, but that the output directory must be made writable by that user in an out-of-band way. EDG version 1.7 --------------- Imported with consent of Fabio Hernandez and Steve Traylen from the original EDG repository. The EU DataGrid License applies, see http://www.eu-datagrid.org/ fetch-crl-3.0.22/fetch-crl.spec0000644001343500074740000000533014147645373015173 0ustar davidgemin%if %{?rhel}%{!?rhel:0} <= 5 %global _initddir %{_initrddir} %endif Name: fetch-crl Version: 3.0.22 Release: 1%{?dist} Summary: Certificate Revocation List retrieval tool Group: Applications/System License: Apache 2.0 URL: https://dist.eugridpma.info/distribution/util/fetch-crl3 Source: http://www.eugridpma.org/distribution/util/fetch-crl3/%{name}-%{version}.tar.gz Vendor: Nikhef BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildArch: noarch Requires: openssl, perl, perl(LWP), perl(POSIX), perl(File::Temp), perl(Sys::Syslog), perl(strict) perl(vars) Autoreq: 0 Prefix: /usr Requires(post): chkconfig Requires(preun): chkconfig %description The fetch-crl utility will retrieve certificate revocation lists (CRLs) for a set of installed trust anchors. Using meta-data with URLs and CAs it will provision CRLs for use by OpenSSL or NSS in their native format (.rX files or cert8.db files). It supports parallel downloads, and has failover and caching capabilities to deal with network interruptions. This associated cron entries can ensure that CRLs are periodically retrieved from the web sites of the respective Certification Authorities (CAs) or other repositories. CA meta-data should be provided in crl_url files or in IGTF-style info files. %prep %setup %build %clean rm -rf $RPM_BUILD_ROOT %install rm -rf $RPM_BUILD_ROOT make install PREFIX=$RPM_BUILD_ROOT%{_usr} ETC=$RPM_BUILD_ROOT%{_sysconfdir} CACHE=$RPM_BUILD_ROOT%{_localstatedir}/cache mkdir -p $RPM_BUILD_ROOT/%{_initddir} install -p -m 755 $RPM_BUILD_ROOT%{_usr}/share/doc/%{name}-%{version}/%{name}-cron.init $RPM_BUILD_ROOT/%{_initddir}/%{name}-cron install -p -m 755 $RPM_BUILD_ROOT%{_usr}/share/doc/%{name}-%{version}/%{name}-boot.init $RPM_BUILD_ROOT/%{_initddir}/%{name}-boot mkdir $RPM_BUILD_ROOT/%{_sysconfdir}/cron.d install -p -m 644 $RPM_BUILD_ROOT%{_usr}/share/doc/%{name}-%{version}/%{name}-cron.cron $RPM_BUILD_ROOT/%{_sysconfdir}/cron.d/%{name}.cron %post # This adds the proper /etc/rc*.d links for the script /sbin/chkconfig --add %{name}-cron /sbin/chkconfig --add %{name}-boot %preun if [ $1 = 0 ] ; then /sbin/service %{name}-cron stop >/dev/null 2>&1 /sbin/service %{name}-boot stop >/dev/null 2>&1 /sbin/chkconfig --del %{name}-cron /sbin/chkconfig --del %{name}-boot fi %files %defattr(-,root,root,-) %{_sbindir}/fetch-crl %{_sbindir}/clean-crl %{_initddir}/%{name}-cron %{_initddir}/%{name}-boot %{_localstatedir}/cache/fetch-crl %doc /usr/share/man/man8 %doc %{_defaultdocdir}/%{name}-%{version} %config(noreplace) %{_sysconfdir}/fetch-crl.conf %config(noreplace) %{_sysconfdir}/cron.d/fetch-crl.cron %changelog * Fri Jun 11 2010 David Groep Initial build of completely rewritten version 3.0 fetch-crl-3.0.22/Makefile0000644001343500074740000001236414147645373014115 0ustar davidgemin# # @(#)$Id$ # Makefile for fetch-crl3 # David Groep, Nikhef # NAME=$(shell echo *.spec | sed 's/\.spec//') VERSION=$(shell egrep '^Version:' ${NAME}.spec | colrm 1 9) RELEASE=${NAME}-${VERSION} PATCHLEVEL=$(shell egrep '^Release:' ${NAME}.spec | colrm 1 9 | sed -e 's/%.*//' ) RPMTOPDIR=$(shell rpm --eval '%_topdir') PREFIX=/usr ETC=/etc CACHE=/var/cache FILES=fetch-crl fetch-crl.8 fetch-crl-cron.cron fetch-crl-cron.init fetch-crl-boot.init fetch-crl.cnf fetch-crl.cnf.example NOTICE LICENSE README CHANGES fetch-crl.spec Makefile clean-crl clean-crl.8 # source files that will constitute fetch-crl as a single file, with the primary perl script first SOURCEFILES=fetch-crl3.pl CRL.pm CRLWriter.pm ConfigTiny.pm FCLog.pm OSSL.pm TrustAnchor.pm base64.pm all: configure tar: clean configure fetch-crl -rm -rf /var/tmp/${RELEASE} /var/tmp/${RELEASE}-buildroot -mkdir /var/tmp/${RELEASE} cp -r ${FILES} /var/tmp/${RELEASE} -chmod -R u+rw /var/tmp/${RELEASE} cd /var/tmp/ ; tar cvfz ${RELEASE}.tar.gz --exclude=CVS \ --exclude='*~' --exclude='#*#' --exclude='20*' ${RELEASE} cd /var/tmp/ ; perl -pe '/^Req/ and s/chkconfig/aaa_base/g; s/_defaultdocdir\}/_usr\}\/share\/doc/;' < ${RELEASE}/fetch-crl.spec > ${RELEASE}/fetch-crl.spec.suse ; mv ${RELEASE}/fetch-crl.spec.suse ${RELEASE}/fetch-crl.spec ; tar cvfz ${RELEASE}.suse.tar.gz --exclude=CVS \ --exclude='*~' --exclude='#*#' --exclude='20*' ${RELEASE} cp /var/tmp/${RELEASE}.tar.gz . cp /var/tmp/${RELEASE}.suse.tar.gz . ##################################################################### # Create substitution script #################################################################### # # This target reads the config file and creates a shell script which # can substitute variables of the form @VAR@ for all config # variables VAR. config.sh: Makefile $(_test_dep) config.mk @cp /dev/null makefile.tmp @echo include config.mk >>makefile.tmp @echo dumpvars: >>makefile.tmp @cat config.mk | \ perl >>makefile.tmp -e 'my $$fmt = "\t\@echo \"-e \\\"s\@%s\@\$$(%s)g\\\" \\\\\"" ; while (<>) { $$v{$$1}=1 if /^([A-Za-z0-9_]+)\s*:?=.*$$/; } map { printf "$$fmt >>config.sh\n", $$_, $$_; } sort(keys(%v)); print "\n"; ' @echo '#!/bin/sh' >config.sh @echo 'sed \' >>config.sh @$(MAKE) -f makefile.tmp dumpvars >/dev/null @echo ' -e "s/\@MSG\@/ ** Generated file : do not edit **/"'>>config.sh @chmod oug+x config.sh @rm makefile.tmp #################################################################### # Configure #################################################################### fetch-crl: $(SOURCEFILES) cat $(SOURCEFILES) > $@ && chmod +x $@ %:: %.cin config.sh @echo configuring $@ ... @rm -f $@ ; cp $< $@ @./config.sh <$< >$@ ; chmod oug-w $@ %.$(MANSECT):: %.$(MANSECT).man.cin @echo creating $@ ... @./config.sh <$< >$@ ; chmod oug-w $@ configure: $(shell find . -name \*\.cin 2>/dev/null | sed -e 's/.cin//' || echo) install: configure mkdir -p $(ETC) mkdir -p $(PREFIX) mkdir -p $(PREFIX)/sbin mkdir -p $(PREFIX)/share mkdir -p $(PREFIX)/share/doc mkdir -p $(PREFIX)/share/doc/$(RELEASE) mkdir -p $(PREFIX)/share/man mkdir -p $(PREFIX)/share/man/man8 install -m755 fetch-crl $(PREFIX)/sbin/fetch-crl install -m755 clean-crl $(PREFIX)/sbin/clean-crl install -m644 fetch-crl-cron.cron $(PREFIX)/share/doc/$(RELEASE)/fetch-crl-cron.cron install -m644 fetch-crl-cron.init $(PREFIX)/share/doc/$(RELEASE)/fetch-crl-cron.init install -m644 fetch-crl-boot.init $(PREFIX)/share/doc/$(RELEASE)/fetch-crl-boot.init install -m644 fetch-crl.8 $(PREFIX)/share/man/man8/fetch-crl.8 install -m644 clean-crl.8 $(PREFIX)/share/man/man8/clean-crl.8 install -m644 fetch-crl.cnf $(ETC)/fetch-crl.conf install -m644 fetch-crl.cnf.example $(PREFIX)/share/doc/$(RELEASE)/fetch-crl.conf.example install -m644 README $(PREFIX)/share/doc/$(RELEASE)/README install -m644 NOTICE $(PREFIX)/share/doc/$(RELEASE)/NOTICE install -m644 LICENSE $(PREFIX)/share/doc/$(RELEASE)/LICENSE install -m644 CHANGES $(PREFIX)/share/doc/$(RELEASE)/CHANGES mkdir -p $(CACHE)/fetch-crl && chmod 0700 $(CACHE)/fetch-crl rpm: tar rpmbuild -ta --eval "%undefine dist" ${RELEASE}.tar.gz rpmbuild -ta -D "dist .suse" ${RELEASE}.suse.tar.gz @if [ -f ${RPMTOPDIR}/SRPMS/${NAME}-${VERSION}-${PATCHLEVEL}.src.rpm ] ; then \ mv ${RPMTOPDIR}/SRPMS/${NAME}*-${VERSION}-${PATCHLEVEL}.src.rpm . ; \ fi @if [ -f ${RPMTOPDIR}/RPMS/i386/${NAME}-${VERSION}-${PATCHLEVEL}.i386.rpm ] ; then \ mv ${RPMTOPDIR}/RPMS/i386/${NAME}*-${VERSION}-${PATCHLEVEL}.i386.rpm . ; \ fi @if [ -f ${RPMTOPDIR}/RPMS/i686/${NAME}-${VERSION}-${PATCHLEVEL}.i686.rpm ] ; then \ mv ${RPMTOPDIR}/RPMS/i686/${NAME}*-${VERSION}-${PATCHLEVEL}.i686.rpm . ; \ fi @if [ -f ${RPMTOPDIR}/RPMS/noarch/${NAME}-${VERSION}-${PATCHLEVEL}.noarch.rpm ] ; then \ mv ${RPMTOPDIR}/RPMS/noarch/${NAME}*-${VERSION}-${PATCHLEVEL}.noarch.rpm . ; \ mv ${RPMTOPDIR}/RPMS/noarch/${NAME}*-${VERSION}-${PATCHLEVEL}.suse.noarch.rpm . ; \ fi @echo DO NOT FORGET TO SIGN THE RPM WITH rpm --resign ${NAME}*-${VERSION}-${PATCHLEVEL}.noarch.rpm clean: -rm -rf *.tar.gz *.rpm fetch-crl fetch-crl3.pl clean-crl config.sh fetch-crl.spec fetch-crl-3.0.22/clean-crl0000755001343500074740000000566114147645373014245 0ustar davidgemin#! /usr/bin/perl -w # use strict; use Getopt::Long qw(:config no_ignore_case bundling); my $sccsid = '@(#)$Id: clean-crl.cin 2649 2013-07-02 18:55:45Z davidg $'; my $targetdir; my $show_help; my $show_version; my $verbose; my $dryrun; sub help() { (my $name = $0) =~ s/.*\///; print < directory to cleanse of old CRL-ish files -v[v...] | --verbose become more verbose and talkative -n | --dryrun do not actually unlink any files -V | --version show a version number -h | --help this help text Examples: $name -l /etc/grid-security/certificates Diagnostics: ". not found": consult an expert. EOHELP return 1; } sub showversion() { (my $name = $0) =~ s/.*\///; print "$name version 3.0.22\n"; return 1; } &GetOptions( "l|cadir=s" => \$targetdir, "n|dryrun" => \$dryrun, "h|help" => \$show_help, "v|verbose+" => \$verbose, "V|version" => \$show_version ) or &help and exit(1); $show_help and &help() and exit (0); $show_version and &showversion() and exit (0); $verbose = 0 unless defined $verbose; $dryrun = 0 unless defined $dryrun; die "Error: target directory undefined, please supply -l argument!\n" unless $targetdir; die "Error: target directory $targetdir does not exist\n" unless -e $targetdir; die "Error: target directory $targetdir is not a directory\n" unless -d $targetdir; # read the directory and find all CA like .\d and CRL like files, # recoding the hashes of the info files in an array, and then in a # second pass weeding out those CRL ".r*" files that do not have # a corresponding info or crl_url file # the remainer is a candidate for deletion my $dh; my @crlfiles; my %infohashes; opendir($dh,$targetdir) or die "Cannot open $targetdir: $!\n"; while ( my $fn = readdir $dh ) { $fn =~ /^([0-9a-f]{8})\.(\d+)$/ and do { $infohashes{$1}=1; ($verbose > 2) and print "Hash $1 belongs to an active CA\n"; }; $fn =~ /^([0-9a-f]{8})\.r(\d+)$/ and do { push @crlfiles,$fn; ($verbose > 2) and print "File $fn is classified as a CRL file\n"; }; } my @candidates = grep { /^([0-9a-f]{8})\.r([0-9]+)$/; ! exists $infohashes{$1}; } @crlfiles; $verbose > 0 and do { if ( $#candidates >= 0 ) { print "The following CRL like files are about to be deleted". ($dryrun?" ... NOT!":".")."\n"; foreach my $fn ( @candidates ) { print " $fn\n"; } } else { print "No orphaned CRL like files found in $targetdir\n"; } }; if ( ! $dryrun ) { foreach my $fn ( @candidates ) { unlink("$targetdir/$fn") or warn "Cannot remove $targetdir/$fn: $!\n"; } } 1; fetch-crl-3.0.22/clean-crl.80000644001343500074740000000340714147645373014404 0ustar davidgemin.\" "@(#)$Id: clean-crl.8 2658 2013-07-06 18:49:46Z davidg $" .\" .\" .TH CLEAN-CRL 8 local "Trust Anchor Utilities" .SH NAME clean-crl \- remove orphaned CRL like files from a certificate directory .SH SYNOPSIS .ll +8 .B clean-crl .RB [ \-l\ crlpath ] .RB [ \-v ] .RB [ \-V ] .RB [ \-n ] .RB [ \-h ] .ll -8 .SH DESCRIPTION The .I clean-crl utility will remove CRL like files named .IR hash .r n from the directory specified with the .B \-l option if there is no corresponding .RI . n file in the same. In effect, if the directory is solely used to hold CA certificates in the common OpenSSL format, it will thus remove CRL files for which the corresponding CA does not or no longer exists in the directory. .SH OPTIONS .TP .B \-h --help Show help text. .TP .B \-l --cadir metadata-directory The script will search this directory for files with the suffix .RI .r i . There is no default - a common choice is /etc/pki/tls/certs, /etc/openldap/cacerts, or /etc/grid-security/certificates. .TP .B \-V --version Display version number (same as corresponding fetch-crl) .TP .B \-v --verbose Verbose mode .TP .B \-n --dryrun Do not actually remove any files (useful primarily with -v) .SH CONFIGURATION None. .SH NOTES This tool does not check the contents of the files removed, and will blindly unlink any file which even remotely looks like an OpenSSL CRL file. Use with extreme caution. .SH "SEE ALSO" fetch-crl(8), openssl(1), http://wiki.nikhef.nl/grid/FetchCRL3 .SH "DIAGNOSTICS" Exit status is normally 0; if an error occurs, exit status is 1 and diagnostics will be written to standard error. .SH LICENSE Licensed under the Apache License, Version 2.0 (the "License"); .B http://www.apache.org/licenses/LICENSE-2.0 .SH BUGS Does not check the contents of the files removed.