Portræt af Arne Jørgensen
Arne Jørgensen
[Download]
#!/usr/bin/env perl

# Copyright (C) 2004, 2005 by Arne Jørgensen

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or (at
# your option) any later version.

# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
# USA.

use strict;
$^W=1; # turn warning on

use Getopt::Long;
use Cwd qw( abs_path );
use LWP::UserAgent;
use Date::Parse;
use File::Temp qw/ :POSIX /;
use File::Copy;
use File::Compare;
use IO::Handle;

my $openssl = "openssl";
my $errorlevel = 1;

my @CApath = ();
my @CAcerts = ();

GetOptions("CApath=s" => \@CApath,
           "openssl:s" => \$openssl,
           "errorlevel:i" => \$errorlevel,
          );
IO::Handle->autoflush();

scan_capaths();
scan_cacerts();
show_cacerts();
#download_crl();

sub scan_capaths {
  msglog(2, "Scaninng CApaths...");
  foreach my $dir ( @CApath ) {
    my $absdir = abs_path($dir);
    msglog(3, "Scanning CApath: $absdir...");
    opendir (DIR, $absdir) || msglog(0, "Unable to read CApath: $absdir.");
    my @certs = grep { /.*\.\d+/ } readdir (DIR);
    close (DIR);
    foreach my $CAfile (@certs) {
      my $certinfo = {absdir => $absdir,
                      CAfile => $CAfile,
                     };
      push @CAcerts, $certinfo;
      msglog(3, "Found (possible) CA-certificate: $CAfile");
    };
  };
  msglog(3, "Scanning CApaths... done.");
};

sub scan_cacerts {
    msglog(2, "Scaninng CA-certificates...");
    foreach my $certinfo ( @CAcerts ) {
        msglog(3, "Scanning CA-cert: " . $certinfo->{CAfile} );
        scan_cacert( $certinfo );
    };
    msglog(2, "Scaninng CA-certificates... done.");
};

sub scan_cacert {
    my $certinfo = $_[0];
    my $crl_dist_point = 0;
    my $validity = 0;
    open (OPENSSL, "$openssl x509 -in " . $certinfo->{absdir} . "/" . $certinfo->{CAfile} . " -noout -text |") || {$certinfo->{badcert} = 1};
    while (<OPENSSL>) {
        chomp;
        $certinfo->{badcert} = 0;
        if ( $_ =~ /Validity/ and $validity == 0 ) { 
            $validity++;
        };
        if ( $_ =~ /Not Before:/ and $validity > 0 and $validity < 3 ) {
            $certinfo->{notbefore} = str2time( ${ [split  (/:/, $_, 2)] }[1] );
            $validity++;
        };
        if ( $_ =~ /Not After :/ and $validity > 0 and $validity < 3 ) {
            $certinfo->{notafter} = str2time( ${ [split(/:/, $_, 2) ]}[1] );
            $validity++;
        };
        if ( $_ =~ /X509v3 CRL Distribution Points:/ ) {
            $crl_dist_point = 1;
        };
        if ( $crl_dist_point eq 1 and $_ =~ /^$/ ) {
            $crl_dist_point = 0;
        };
        if ( $crl_dist_point eq 1 and $_ =~ /URI:/ ) {
            chomp;
            $certinfo->{crluri} = ${ [split(/:/, $_, 2)] }[1];
        };
        if ( $_ =~ /Issuer:/ ) {
            $certinfo->{issuer} = ${ [split(/:/, $_, 2)] }[1];
            msglog(4, "Issuer: " . $certinfo->{issuer});
        };
    };
    close OPENSSL;
};

sub show_cacerts {
    msglog(2, "Downloading CRL's...");
    foreach my $certinfo ( @CAcerts ) {
        if (defined $certinfo->{badcert} && $certinfo->{badcert}) {
            next;
        } elsif (not defined $certinfo->{crluri}) {
            msglog(1, $certinfo->{CAfile} . " has no CRL Distribution Point");
        } else {
            download_crl( $certinfo );
        };
    };
    msglog(2, "Downloading CRL's... done.");
};

sub download_crl {
    my $certinfo = $_[0];
    my $ua = LWP::UserAgent->new();
    my $time = time;
    my $absCAfile = $certinfo->{absdir} . "/" . $certinfo->{CAfile};
    if ( $time < $certinfo->{notbefore} ) {
        msglog(1, "$absCAfile: too new.\n");
    } elsif ( $time > $certinfo->{notafter} ) {
        msglog(0, "$absCAfile: too old.\n");
    } else {
        (my $crlfile = $absCAfile) =~ s/(.*\.)(\d+)/$1r$2/;
        my ($tmpfh, $tmpfile) = tmpnam();
        msglog(2, "Downloading " . $certinfo->{CAfile} . " / " . $certinfo->{crluri} . "...");
        my $response = $ua->get( $certinfo->{crluri} );
        if ($response->is_success) {
            msglog(2, "Downloading " . $certinfo->{CAfile} . " / " . $certinfo->{crluri} . "... " . $response->status_line);
            if ( $response->content =~ /-----BEGIN X509 CRL-----/ ) {
                printf $tmpfh $response->content;
            } else {
                open(OPENSSL, "| $openssl crl -inform DER -outform PEM -out $tmpfile");
                print OPENSSL $response->content;
                close OPENSSL;
            };
            open (OPENSSL, "$openssl crl -in $tmpfile -inform PEM -noout -CAfile $absCAfile 2>&1 |");
            while (<OPENSSL>) {
                if ($_ =~ /verify OK/) {
                    if ( -e $crlfile ) {
                        if ( compare( $tmpfile, $crlfile) == 0 ) {
                            msglog(1, $certinfo->{CAfile} . " / " . $certinfo->{crluri} . " is equal to present CRL.");
                        } else {
                            move( $crlfile, make_backup_filename($crlfile) );
                            move( $tmpfile, $crlfile );
                        }
                    } else {
                        move( $tmpfile, $crlfile );
                    };
                    chmod 0644, $crlfile;
                } else {
                    msglog(0, $certinfo->{CAfile} . " CRL does not verify!");
                };
            };
            close OPENSSL;
            unlink $tmpfile;
        } else {
            msglog(0, "Downloading " . $certinfo->{CAfile} . " / " . $certinfo->{crluri} . "... " . $response->status_line);
        };
    };
};

sub make_backup_filename {
  return $_[0] . "~";
};

sub msglog {
  my ($level, $msg) = @_;
  printf STDERR $msg . "\n" unless $level >= $errorlevel;
};

# crl-update ends here