diff options
Diffstat (limited to 'scripts/samhainadmin-sig.pl.in')
-rwxr-xr-x | scripts/samhainadmin-sig.pl.in | 636 |
1 files changed, 636 insertions, 0 deletions
diff --git a/scripts/samhainadmin-sig.pl.in b/scripts/samhainadmin-sig.pl.in new file mode 100755 index 0000000..10bb6ea --- /dev/null +++ b/scripts/samhainadmin-sig.pl.in @@ -0,0 +1,636 @@ +#! /usr/bin/perl + +# Copyright Rainer Wichmann (2004) +# +# License Information: +# 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., 675 Mass Ave, Cambridge, MA 02139, USA. +# + +use warnings; +use strict; +use Getopt::Long; +use File::Basename; +use File::Copy; +use File::stat; +use File::Temp qw/ tempfile tempdir unlink0 /; +use IO::Handle; +use Fcntl qw(:DEFAULT :flock); +use Tie::File; + +# Do I/O to the data file in binary mode (so it +# wouldn't complain about invalid UTF-8 characters). +use bytes; + +File::Temp->safe_level( File::Temp::HIGH ); + +my %opts = (); +my $action; +my $file1; +my $file2; +my $passphrase; +my $secretkey; +my $return_from_sign = 0; +my $no_print_examine = 0; +my $no_remove_lock = 0; +my $base = basename($0); + +my $cfgfile = "@myconffile@"; +my $datafile = "@mydatafile@"; +my $daemon = "@sbindir@/@install_name@"; +my $signify = "@mysignify@"; + +my $SIGDIR = "$ENV{'HOME'}/.signify"; +my $KEYID = "@install_name@"; + +$cfgfile =~ s/^REQ_FROM_SERVER//; +$datafile =~ s/^REQ_FROM_SERVER//; + +$signify = "signify-openbsd" if ($signify eq ""); + +sub usage() { + print "Usage:\n"; + print " $base { -m F | --create-cfgfile } [options] [in.cfgfile]\n"; + print " Sign the configuration file. If in.cfgfile is given, sign it\n"; + print " and install it as configuration file.\n\n"; + + print " $base { -m f | --print-cfgfile } [options] \n"; + print " Print the configuration file to stdout. Signatures are removed.\n\n"; + + print " $base { -m D | --create-datafile } [options] [in.datafile]\n"; + print " Sign the database file. If in.datafile is given, sign it\n"; + print " and install it as database file.\n\n"; + + print " $base { -m d | --print-datafile } [options] \n"; + print " Print the database file to stdout. Signatures are removed. Use\n"; + print " option --list to list files in database rather than printing the raw file.\n\n"; + + print " $base { -m R | --remove-signature } [options] file1 [file2 ...]\n"; + print " Remove cleartext signature from input file(s). The file\n"; + print " is replaced by the non-signed file.\n\n"; + + print " $base { -m E | --sign } [options] file1 [file2 ...]\n"; + print " Sign file(s) with a cleartext signature. The file\n"; + print " is replaced by the signed file.\n\n"; + + print " $base { -m e | --examine } [options] file1 [file2 ...]\n"; + print " Report signature status of file(s).\n\n"; + + print " $base { -m G | --generate-keys } [options] \n"; + print " Generate a signify keypair to use for signing.\n\n"; + + print "Options:\n"; + print " -c cfgfile --cfgfile cfgfile\n"; + print " Select an alternate configuration file.\n\n"; + + print " -d datafile --datafile datafile\n"; + print " Select an alternate database file.\n\n"; + + print " -p passphrase --passphrase passphrase\n"; + print " Set the passphrase for signify. By default, signify will ask.\n\n"; + + print " -s signify_dir --signify-dir signify_dir\n"; + print " Select an alternate directory to locate the secret keyring.\n"; + print " Will use '$ENV{'HOME'}/.signify/' by default.\n\n"; + + print " -k keyid --keyid keyid\n"; + print " Select the keyid to use for signing.\n\n"; + + print " -l --list\n"; + print " List the files in database rather than printing the raw file.\n\n"; + + print " -v --verbose\n"; + print " Verbose output.\n\n"; + return; +} + +sub check_signify_uid () { + if (0 != $>) { + print "--------------------------------------------------\n"; + print "\n"; + print " You are not root. Please remember that samhain/yule\n"; + print " will use the public key of root to verify a signature.\n"; + print "\n"; + print "--------------------------------------------------\n"; + } else { + if (!("@yulectl_prg@" =~ //)) { + print "--------------------------------------------------\n"; + print "\n"; + print " Please remember that yule will drop root after startup. Signature\n"; + print " verification on SIGHUP will fail if you do not import the public key\n"; + print " into the ~/.signify/ directory of the non-root yule user.\n"; + print "\n"; + print "--------------------------------------------------\n"; + } + } +} + +sub check_signify_sign () { + if ( defined($secretkey)) { + if ( (!-d "$secretkey")){ + print "--------------------------------------------------\n"; + print "\n"; + print " Secret key $secretkey not found!\n"; + print "\n"; + print " Please check the path/name of the alternate secret key.\n"; + print "\n"; + print "--------------------------------------------------\n"; + print "\n"; + exit; + } + } else { + if ( (!-d "$SIGDIR") || (!-e "${SIGDIR}/${KEYID}.sec")) { + print "--------------------------------------------------\n"; + print "\n"; + if (!-d "$SIGDIR") { + print " Directory $SIGDIR not found!\n"; + } else { + print " Secret key ${SIGDIR}/${KEYID}.sec not found!\n"; + } + print "\n"; + print " This indicates that you have never created a \n"; + print " public/private keypair, and thus cannot sign.\n"; + print " \n"; + print " Please use $0 --generate-keys or\n"; + print " $signify -G -s ${SIGDIR}/${KEYID}.sec -p ${SIGDIR}/${KEYID}.pub\n"; + print " to generate a public/private keypair first.\n"; + print "\n"; + print "--------------------------------------------------\n"; + print "\n"; + exit; + } + } +} + +sub check_signify_verify () { + if ( (!-d "${SIGDIR}") || (!-e "${SIGDIR}/${KEYID}.pub")) { + print "--------------------------------------------------\n"; + print "\n"; + if (!-d "$SIGDIR") { + print " Directory $SIGDIR not found!\n"; + } else { + print " Public key ${SIGDIR}/${KEYID}.pub not found!\n"; + } + print "\n"; + print " This indicates that you have no public key\n"; + print " to verify signatures.\n"; + print " \n"; + print " Please copy the public key ${KEYID}.pub of\n"; + print " the user who is signing the configuration/database files\n"; + print " into the directory $SIGDIR.\n"; + print "\n"; + print "--------------------------------------------------\n"; + print "\n"; + exit; + } +} + + +sub generate () { + my $command = "$signify -G -s ${SIGDIR}/${KEYID}.sec -p ${SIGDIR}/${KEYID}.pub"; + if (!-d "${SIGDIR}") { + unless(mkdir "$SIGDIR", 0750) { + die "Creating directory $SIGDIR failed: $?"; + } + } + check_signify_uid(); + system ($command) == 0 + or die "system $command failed: $?"; + exit; +} + +sub examine () { + my $iscfg = 0; + my $have_fp = 0; + my $have_sig = 0; + my $message = ''; + my $retval = 9; + my $fh; + my $filename; + + if (!($file1 =~ /^\-$/)) { + die ("Cannot open $file1 for read: $!") unless ((-e $file1) && (-r _)); + } + open FIN, "<$file1" or die "Cannot open $file1 for read: $!"; + + my $dir = tempdir( CLEANUP => 1 ); + $filename = $dir . "/exa_jhfdbilw." . $$; + open $fh, ">$filename" or die "Cannot open $filename"; + autoflush $fh 1; + + while (<FIN>) { + print $fh $_; + if ($_ =~ /^\s*\[Misc\]/) { + $iscfg = 1; + } + } + if ($iscfg == 1) { + $message .= "File $file1 is a configuration file\n\n"; + } else { + $message .= "File $file1 is a database file\n\n"; + } + + + my $command = "$signify -Vem /dev/null -p ${SIGDIR}/${KEYID}.pub "; + $command .= "-x $filename "; + if (defined($opts{'v'})) { + $command .= "2>&1"; + } else { + $command .= "2>/dev/null"; + } + + print STDOUT "Using: $command\n\n" if (defined($opts{'v'})); + open SIGIN, "$command |" or die "Cannot fork: $!"; + + while (<SIGIN>) { + chomp ($_); + if ($_ =~ /^Signature Verified$/) { + $message .= "GOOD signature with key: ${SIGDIR}/${KEYID}.pub\n"; + $have_sig = 1; + $retval = 0; + } + print STDOUT $_ if (defined($opts{'v'})); + } + close (SIGIN); + print STDOUT "\n" if (defined($opts{'v'})); + if ($have_sig == 0) { + $message .= "NO valid signature found\n"; + } + close (FIN); + if ($no_print_examine == 0) { + print STDOUT $message; + } + unlink0( $fh, $filename ) or die "Cannot unlink $filename safely"; + return $retval; +} + +sub wstrip ($) { + $_ = shift; + $_ =~ s/\s+//g; + return $_; +} + +sub remove () { + my $bodystart = 1; + my $sigstart = 0; + my $sigend = 0; + my $filename = ""; + my $fh; + my $stats; + + open FH, "<$file1" or die "Cannot open file $file1 for read: $!"; + if (!($file1 =~ /^\-$/)) { + flock(FH, LOCK_EX) unless ($no_remove_lock == 1); + my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed"; + $filename = $dir . "/rem_iqegBCQb." . $$; + open $fh, ">$filename" or die "Cannot open $filename"; + $stats = stat($file1); + } else { + open $fh, ">$file1" or die "Cannot open file $file1 for write: $!"; + } + autoflush $fh 1; + while (<FH>) { + if ($_ =~ /^untrusted comment: /) { + $sigstart = 1; + $bodystart = 0; + next; + } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) { + $sigstart = 0; + $bodystart = 1; + next; + } elsif (($sigstart == 1) && ($bodystart == 0)) { + # comment NOT followed by signature + $sigstart = 0; + next; + } + + if ($bodystart == 1) { + print $fh $_; + } + } + if (!($file1 =~ /^\-$/)) { + copy("$filename", "$file1") + or die "Copy $filename to $file1 failed: $!"; + chmod $stats->mode, $file1; + chown $stats->uid, $stats->gid, $file1; + flock(FH, LOCK_UN) unless ($no_remove_lock == 1); + close FH; + } + unlink0( $fh, $filename ) or die "Cannot unlink $filename safely"; + return; +} + +sub print_cfgfile () { + my $bodystart = 0; + my $sigstart = 0; + + if (!defined($file2)) { + $file2 = '-'; + } + + open FH, "<$file1" or die "Cannot open file $file1 for read: $!"; + open FO, ">$file2" or die "Cannot open file $file2 for write: $!"; + while (<FH>) { + if ($_ =~ /^untrusted comment: /) { + $sigstart = 1; + $bodystart = 0; + next; + } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) { + $sigstart = 0; + $bodystart = 1; + next; + } elsif (($sigstart == 1) && ($bodystart == 0)) { + # comment NOT followed by signature + $sigstart = 0; + next; + } + if ($bodystart == 1) { + print FO $_; + } + } + exit; +} + +sub print_datafile () { + die ("Cannot find program $daemon") + unless (-e $daemon); + if (defined($opts{'v'})) { + open FH, "$daemon --full-detail -d $datafile |" + or die "Cannot open datafile $datafile for read: $!"; + } else { + open FH, "$daemon -d $datafile |" + or die "Cannot open datafile $datafile for read: $!"; + } + while (<FH>) { + print $_; + } + exit; +} + +sub sign_file () { + + my $fileout = ''; + my $bodystart = 1; + my $sigstart = 0; + my $sigend = 0; + my $stats; + my $fh1; + my $filename1; + my $flag1 = 0; + + check_signify_uid(); + + if (!defined($file2)) { + $file2 = $file1; + } + + if ($file1 =~ /^\-$/) { + my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed"; + $filename1 = $dir . "/sig_vs8827sd." . $$; + open $fh1, ">$filename1" or die "Cannot open $filename1"; + $flag1 = 1; + # my ($fh1, $filename1) = tempfile(UNLINK => 1); + + while (<STDIN>) { + if ($_ =~ /^untrusted comment: /) { + $sigstart = 1; + $bodystart = 0; + next; + } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) { + $sigstart = 0; + $bodystart = 1; + next; + } elsif (($sigstart == 1) && ($bodystart == 0)) { + #comment NOT followed by signature + $sigstart = 0; + next; + } + + if ($bodystart == 1) { + print $fh1 $_; + } + } + $file1 = $filename1; + $fileout = '-'; + } else { + open (LOCKFILE, "<$file1") or die "Cannot open $file1: $!"; + flock(LOCKFILE, LOCK_EX); + $no_print_examine = 1; + $no_remove_lock = 1; + if (examine() < 2) { + remove(); + } + $fileout = $file1 . ".sig"; + $stats = stat($file1) + or die "No file $file1: $!"; + } + + my $command = "$signify -Se "; + $command .= "-s ${SIGDIR}/${KEYID}.sec "; + $command .= "-x ${fileout} "; + $command .= "-m $file1"; + + if (defined($passphrase)) { + local $SIG{PIPE} = 'IGNORE'; + open (FH, "|$command") or die "can't fork: $!"; + print FH "$passphrase" or die "can't write: $!"; + close FH or die "can't close: status=$?"; + } else { + system("$command") == 0 + or die "system $command failed: $?"; + } + + if (!($fileout =~ /^\-$/)) { + my $st_old = stat($file1) + or die "No file $file1: $!"; + my $st_new = stat($fileout) + or die "No file $fileout: $!"; + die ("Signed file is smaller than unsigned file") + unless ($st_new->size > $st_old->size); + move("$fileout", "$file2") + or die "Move $fileout to $file2 failed: $!"; + chmod $stats->mode, $file2; + chown $stats->uid, $stats->gid, $file2; + flock(LOCKFILE, LOCK_UN); + } + + if ($flag1 == 1) { + unlink0( $fh1, $filename1 ) or die "Cannot unlink $filename1 safely"; + } + if ($return_from_sign == 1) { + return; + } + exit; +} + +Getopt::Long::Configure ("posix_default"); +Getopt::Long::Configure ("bundling"); +# Getopt::Long::Configure ("debug"); + +GetOptions (\%opts, 'm=s', 'h|help', 'v|verbose', 'l|list', + 'c|cfgfile=s', + 'd|datafile=s', + 'p|passphrase=s', + 's|secretkey=s', + 'k|keyid=s', + 'create-cfgfile', # -m F + 'print-cfgfile', # -m f + 'create-datafile', # -m D + 'print-datafile', # -m d + 'remove-signature',# -m R + 'sign', # -m E + 'examine', # -m e + 'generate-keys'); # -m G + +if (defined ($opts{'h'})) { + usage(); + exit; +} + +if (defined($opts{'k'})) { + $KEYID = $opts{'k'}; +} +if (defined($opts{'c'})) { + $cfgfile = $opts{'c'}; +} +if (defined($opts{'d'})) { + $datafile = $opts{'d'}; +} +if (defined($opts{'p'})) { + $passphrase = $opts{'p'}; +} +if (defined($opts{'s'})) { + $SIGDIR = $opts{'s'}; +} + +if (defined ($opts{'m'}) && ($opts{'m'} =~ /[FfDdREeG]{1}/) ) { + $action = $opts{'m'}; +} +elsif (defined ($opts{'create-cfgfile'})) { + $action = 'F'; +} +elsif (defined ($opts{'print-cfgfile'})) { + $action = 'f'; +} +elsif (defined ($opts{'create-datafile'})) { + $action = 'D'; +} +elsif (defined ($opts{'print-datafile'})) { + $action = 'd'; +} +elsif (defined ($opts{'remove-signature'})) { + $action = 'R'; +} +elsif (defined ($opts{'sign'})) { + $action = 'E'; +} +elsif (defined ($opts{'examine'})) { + $action = 'e'; +} +elsif (defined ($opts{'generate-keys'})) { + $action = 'G'; +} +else { + usage(); + die ("No valid action specified !"); +} + +if (defined($ARGV[0])) { + $file1 = $ARGV[0]; +} +if (defined($ARGV[1])) { + $file2 = $ARGV[1]; +} + + +if (($action =~ /[REe]{1}/) && !defined($file1)) { + usage(); + die("Option -m $action requires a filename (or '-' for stdio)\n"); +} + +if ($action =~ /^F$/) { + if (!defined($file1)) { + $file1 = $cfgfile; + } + $file2 = $cfgfile; + sign_file (); +} + +if ($action =~ /^D$/) { + if (!defined($file1)) { + $file1 = $datafile; + } + $file2 = $datafile; + sign_file (); +} + +if ($action =~ /^R$/) { + # $file1 defined + my $i = 0; + while (defined($ARGV[$i])) { + $file1 = $ARGV[$i]; + remove (); + ++$i; + } +} + +if ($action =~ /^E$/) { + # $file1 defined + # default: $file2 = $file1 + check_signify_sign(); + my $i = 0; + while (defined($ARGV[$i])) { + $file1 = $ARGV[$i]; + $file2 = $file1; + $return_from_sign = 1; + sign_file (); + ++$i; + } +} + +if ($action =~ /^e$/) { + # $file1 defined + # default: $file2 = stdout + check_signify_verify(); + my $i = 0; + my $ret = 0; + while (defined($ARGV[$i])) { + print "\n"; + $file1 = $ARGV[$i]; + $ret += examine (); + ++$i; + print "\n--------------------------------\n" if (defined($ARGV[$i])); + } + exit($ret); +} + +if ($action =~ /^f$/) { + $file1 = $cfgfile; + $file2 = "-"; + print_cfgfile (); +} + +if ($action =~ /^d$/) { + # $file1 irrelevant + if (defined($opts{'l'})) { + print_datafile (); + } else { + $file1 = $datafile; + $file2 = "-"; + print_cfgfile (); + } +} + + + |