1#!/usr/local/bin/perl 2 3# Copyright Rainer Wichmann (2004) 4# 5# License Information: 6# This program is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 2 of the License, or 9# (at your option) any later version. 10# 11# This program is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with this program; if not, write to the Free Software 18# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 19# 20 21use warnings; 22use strict; 23use Getopt::Long; 24use File::Basename; 25use File::Copy; 26use File::stat; 27use File::Temp qw/ tempfile tempdir unlink0 /; 28use IO::Handle; 29use Fcntl qw(:DEFAULT :flock); 30use Tie::File; 31 32# Do I/O to the data file in binary mode (so it 33# wouldn't complain about invalid UTF-8 characters). 34use bytes; 35 36File::Temp->safe_level( File::Temp::HIGH ); 37 38my %opts = (); 39my $action; 40my $file1; 41my $file2; 42my $passphrase; 43my $secretkey; 44my $return_from_sign = 0; 45my $no_print_examine = 0; 46my $no_remove_lock = 0; 47my $base = basename($0); 48 49my $cfgfile = "@myconffile@"; 50my $datafile = "@mydatafile@"; 51my $daemon = "@sbindir@/@install_name@"; 52my $signify = "@mysignify@"; 53 54my $SIGDIR = "$ENV{'HOME'}/.signify"; 55my $KEYID = "@install_name@"; 56 57$cfgfile =~ s/^REQ_FROM_SERVER//; 58$datafile =~ s/^REQ_FROM_SERVER//; 59 60$signify = "signify-openbsd" if ($signify eq ""); 61 62sub usage() { 63 print "Usage:\n"; 64 print " $base { -m F | --create-cfgfile } [options] [in.cfgfile]\n"; 65 print " Sign the configuration file. If in.cfgfile is given, sign it\n"; 66 print " and install it as configuration file.\n\n"; 67 68 print " $base { -m f | --print-cfgfile } [options] \n"; 69 print " Print the configuration file to stdout. Signatures are removed.\n\n"; 70 71 print " $base { -m D | --create-datafile } [options] [in.datafile]\n"; 72 print " Sign the database file. If in.datafile is given, sign it\n"; 73 print " and install it as database file.\n\n"; 74 75 print " $base { -m d | --print-datafile } [options] \n"; 76 print " Print the database file to stdout. Signatures are removed. Use\n"; 77 print " option --list to list files in database rather than printing the raw file.\n\n"; 78 79 print " $base { -m R | --remove-signature } [options] file1 [file2 ...]\n"; 80 print " Remove cleartext signature from input file(s). The file\n"; 81 print " is replaced by the non-signed file.\n\n"; 82 83 print " $base { -m E | --sign } [options] file1 [file2 ...]\n"; 84 print " Sign file(s) with a cleartext signature. The file\n"; 85 print " is replaced by the signed file.\n\n"; 86 87 print " $base { -m e | --examine } [options] file1 [file2 ...]\n"; 88 print " Report signature status of file(s).\n\n"; 89 90 print " $base { -m G | --generate-keys } [options] \n"; 91 print " Generate a signify keypair to use for signing.\n\n"; 92 93 print "Options:\n"; 94 print " -c cfgfile --cfgfile cfgfile\n"; 95 print " Select an alternate configuration file.\n\n"; 96 97 print " -d datafile --datafile datafile\n"; 98 print " Select an alternate database file.\n\n"; 99 100 print " -p passphrase --passphrase passphrase\n"; 101 print " Set the passphrase for signify. By default, signify will ask.\n\n"; 102 103 print " -s signify_dir --signify-dir signify_dir\n"; 104 print " Select an alternate directory to locate the secret keyring.\n"; 105 print " Will use '$ENV{'HOME'}/.signify/' by default.\n\n"; 106 107 print " -k keyid --keyid keyid\n"; 108 print " Select the keyid to use for signing.\n\n"; 109 110 print " -l --list\n"; 111 print " List the files in database rather than printing the raw file.\n\n"; 112 113 print " -v --verbose\n"; 114 print " Verbose output.\n\n"; 115 return; 116} 117 118sub check_signify_uid () { 119 if (0 != $>) { 120 print "--------------------------------------------------\n"; 121 print "\n"; 122 print " You are not root. Please remember that samhain/yule\n"; 123 print " will use the public key of root to verify a signature.\n"; 124 print "\n"; 125 print "--------------------------------------------------\n"; 126 } else { 127 if (!("@yulectl_prg@" =~ //)) { 128 print "--------------------------------------------------\n"; 129 print "\n"; 130 print " Please remember that yule will drop root after startup. Signature\n"; 131 print " verification on SIGHUP will fail if you do not import the public key\n"; 132 print " into the ~/.signify/ directory of the non-root yule user.\n"; 133 print "\n"; 134 print "--------------------------------------------------\n"; 135 } 136 } 137} 138 139sub check_signify_sign () { 140 if ( defined($secretkey)) { 141 if ( (!-d "$secretkey")){ 142 print "--------------------------------------------------\n"; 143 print "\n"; 144 print " Secret key $secretkey not found!\n"; 145 print "\n"; 146 print " Please check the path/name of the alternate secret key.\n"; 147 print "\n"; 148 print "--------------------------------------------------\n"; 149 print "\n"; 150 exit; 151 } 152 } else { 153 if ( (!-d "$SIGDIR") || (!-e "${SIGDIR}/${KEYID}.sec")) { 154 print "--------------------------------------------------\n"; 155 print "\n"; 156 if (!-d "$SIGDIR") { 157 print " Directory $SIGDIR not found!\n"; 158 } else { 159 print " Secret key ${SIGDIR}/${KEYID}.sec not found!\n"; 160 } 161 print "\n"; 162 print " This indicates that you have never created a \n"; 163 print " public/private keypair, and thus cannot sign.\n"; 164 print " \n"; 165 print " Please use $0 --generate-keys or\n"; 166 print " $signify -G -s ${SIGDIR}/${KEYID}.sec -p ${SIGDIR}/${KEYID}.pub\n"; 167 print " to generate a public/private keypair first.\n"; 168 print "\n"; 169 print "--------------------------------------------------\n"; 170 print "\n"; 171 exit; 172 } 173 } 174} 175 176sub check_signify_verify () { 177 if ( (!-d "${SIGDIR}") || (!-e "${SIGDIR}/${KEYID}.pub")) { 178 print "--------------------------------------------------\n"; 179 print "\n"; 180 if (!-d "$SIGDIR") { 181 print " Directory $SIGDIR not found!\n"; 182 } else { 183 print " Public key ${SIGDIR}/${KEYID}.pub not found!\n"; 184 } 185 print "\n"; 186 print " This indicates that you have no public key\n"; 187 print " to verify signatures.\n"; 188 print " \n"; 189 print " Please copy the public key ${KEYID}.pub of\n"; 190 print " the user who is signing the configuration/database files\n"; 191 print " into the directory $SIGDIR.\n"; 192 print "\n"; 193 print "--------------------------------------------------\n"; 194 print "\n"; 195 exit; 196 } 197} 198 199 200sub generate () { 201 my $command = "$signify -G -s ${SIGDIR}/${KEYID}.sec -p ${SIGDIR}/${KEYID}.pub"; 202 if (!-d "${SIGDIR}") { 203 unless(mkdir "$SIGDIR", 0750) { 204 die "Creating directory $SIGDIR failed: $?"; 205 } 206 } 207 check_signify_uid(); 208 system ($command) == 0 209 or die "system $command failed: $?"; 210 exit; 211} 212 213sub examine () { 214 my $iscfg = 0; 215 my $have_fp = 0; 216 my $have_sig = 0; 217 my $message = ''; 218 my $retval = 9; 219 my $fh; 220 my $filename; 221 222 if (!($file1 =~ /^\-$/)) { 223 die ("Cannot open $file1 for read: $!") unless ((-e $file1) && (-r _)); 224 } 225 open FIN, "<$file1" or die "Cannot open $file1 for read: $!"; 226 227 my $dir = tempdir( CLEANUP => 1 ); 228 $filename = $dir . "/exa_jhfdbilw." . $$; 229 open $fh, ">$filename" or die "Cannot open $filename"; 230 autoflush $fh 1; 231 232 while (<FIN>) { 233 print $fh $_; 234 if ($_ =~ /^\s*\[Misc\]/) { 235 $iscfg = 1; 236 } 237 } 238 if ($iscfg == 1) { 239 $message .= "File $file1 is a configuration file\n\n"; 240 } else { 241 $message .= "File $file1 is a database file\n\n"; 242 } 243 244 245 my $command = "$signify -Vem /dev/null -p ${SIGDIR}/${KEYID}.pub "; 246 $command .= "-x $filename "; 247 if (defined($opts{'v'})) { 248 $command .= "2>&1"; 249 } else { 250 $command .= "2>/dev/null"; 251 } 252 253 print STDOUT "Using: $command\n\n" if (defined($opts{'v'})); 254 open SIGIN, "$command |" or die "Cannot fork: $!"; 255 256 while (<SIGIN>) { 257 chomp ($_); 258 if ($_ =~ /^Signature Verified$/) { 259 $message .= "GOOD signature with key: ${SIGDIR}/${KEYID}.pub\n"; 260 $have_sig = 1; 261 $retval = 0; 262 } 263 print STDOUT $_ if (defined($opts{'v'})); 264 } 265 close (SIGIN); 266 print STDOUT "\n" if (defined($opts{'v'})); 267 if ($have_sig == 0) { 268 $message .= "NO valid signature found\n"; 269 } 270 close (FIN); 271 if ($no_print_examine == 0) { 272 print STDOUT $message; 273 } 274 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely"; 275 return $retval; 276} 277 278sub wstrip ($) { 279 $_ = shift; 280 $_ =~ s/\s+//g; 281 return $_; 282} 283 284sub remove () { 285 my $bodystart = 1; 286 my $sigstart = 0; 287 my $sigend = 0; 288 my $filename = ""; 289 my $fh; 290 my $stats; 291 292 open FH, "<$file1" or die "Cannot open file $file1 for read: $!"; 293 if (!($file1 =~ /^\-$/)) { 294 flock(FH, LOCK_EX) unless ($no_remove_lock == 1); 295 my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed"; 296 $filename = $dir . "/rem_iqegBCQb." . $$; 297 open $fh, ">$filename" or die "Cannot open $filename"; 298 $stats = stat($file1); 299 } else { 300 open $fh, ">$file1" or die "Cannot open file $file1 for write: $!"; 301 } 302 autoflush $fh 1; 303 while (<FH>) { 304 if ($_ =~ /^untrusted comment: /) { 305 $sigstart = 1; 306 $bodystart = 0; 307 next; 308 } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) { 309 $sigstart = 0; 310 $bodystart = 1; 311 next; 312 } elsif (($sigstart == 1) && ($bodystart == 0)) { 313 # comment NOT followed by signature 314 $sigstart = 0; 315 next; 316 } 317 318 if ($bodystart == 1) { 319 print $fh $_; 320 } 321 } 322 if (!($file1 =~ /^\-$/)) { 323 copy("$filename", "$file1") 324 or die "Copy $filename to $file1 failed: $!"; 325 chmod $stats->mode, $file1; 326 chown $stats->uid, $stats->gid, $file1; 327 flock(FH, LOCK_UN) unless ($no_remove_lock == 1); 328 close FH; 329 } 330 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely"; 331 return; 332} 333 334sub print_cfgfile () { 335 my $bodystart = 0; 336 my $sigstart = 0; 337 338 if (!defined($file2)) { 339 $file2 = '-'; 340 } 341 342 open FH, "<$file1" or die "Cannot open file $file1 for read: $!"; 343 open FO, ">$file2" or die "Cannot open file $file2 for write: $!"; 344 while (<FH>) { 345 if ($_ =~ /^untrusted comment: /) { 346 $sigstart = 1; 347 $bodystart = 0; 348 next; 349 } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) { 350 $sigstart = 0; 351 $bodystart = 1; 352 next; 353 } elsif (($sigstart == 1) && ($bodystart == 0)) { 354 # comment NOT followed by signature 355 $sigstart = 0; 356 next; 357 } 358 if ($bodystart == 1) { 359 print FO $_; 360 } 361 } 362 exit; 363} 364 365sub print_datafile () { 366 die ("Cannot find program $daemon") 367 unless (-e $daemon); 368 if (defined($opts{'v'})) { 369 open FH, "$daemon --full-detail -d $datafile |" 370 or die "Cannot open datafile $datafile for read: $!"; 371 } else { 372 open FH, "$daemon -d $datafile |" 373 or die "Cannot open datafile $datafile for read: $!"; 374 } 375 while (<FH>) { 376 print $_; 377 } 378 exit; 379} 380 381sub sign_file () { 382 383 my $fileout = ''; 384 my $bodystart = 1; 385 my $sigstart = 0; 386 my $sigend = 0; 387 my $stats; 388 my $fh1; 389 my $filename1; 390 my $flag1 = 0; 391 392 check_signify_uid(); 393 394 if (!defined($file2)) { 395 $file2 = $file1; 396 } 397 398 if ($file1 =~ /^\-$/) { 399 my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed"; 400 $filename1 = $dir . "/sig_vs8827sd." . $$; 401 open $fh1, ">$filename1" or die "Cannot open $filename1"; 402 $flag1 = 1; 403 # my ($fh1, $filename1) = tempfile(UNLINK => 1); 404 405 while (<STDIN>) { 406 if ($_ =~ /^untrusted comment: /) { 407 $sigstart = 1; 408 $bodystart = 0; 409 next; 410 } elsif (($sigstart == 1) && (wstrip($_) =~ m{^(?: [A-Za-z0-9+/]{4} )*(?:[A-Za-z0-9+/]{2} [AEIMQUYcgkosw048]=|[A-Za-z0-9+/][AQgw]==)?$}xm )) { 411 $sigstart = 0; 412 $bodystart = 1; 413 next; 414 } elsif (($sigstart == 1) && ($bodystart == 0)) { 415 #comment NOT followed by signature 416 $sigstart = 0; 417 next; 418 } 419 420 if ($bodystart == 1) { 421 print $fh1 $_; 422 } 423 } 424 $file1 = $filename1; 425 $fileout = '-'; 426 } else { 427 open (LOCKFILE, "<$file1") or die "Cannot open $file1: $!"; 428 flock(LOCKFILE, LOCK_EX); 429 $no_print_examine = 1; 430 $no_remove_lock = 1; 431 if (examine() < 2) { 432 remove(); 433 } 434 $fileout = $file1 . ".sig"; 435 $stats = stat($file1) 436 or die "No file $file1: $!"; 437 } 438 439 my $command = "$signify -Se "; 440 $command .= "-s ${SIGDIR}/${KEYID}.sec "; 441 $command .= "-x ${fileout} "; 442 $command .= "-m $file1"; 443 444 if (defined($passphrase)) { 445 local $SIG{PIPE} = 'IGNORE'; 446 open (FH, "|$command") or die "can't fork: $!"; 447 print FH "$passphrase" or die "can't write: $!"; 448 close FH or die "can't close: status=$?"; 449 } else { 450 system("$command") == 0 451 or die "system $command failed: $?"; 452 } 453 454 if (!($fileout =~ /^\-$/)) { 455 my $st_old = stat($file1) 456 or die "No file $file1: $!"; 457 my $st_new = stat($fileout) 458 or die "No file $fileout: $!"; 459 die ("Signed file is smaller than unsigned file") 460 unless ($st_new->size > $st_old->size); 461 move("$fileout", "$file2") 462 or die "Move $fileout to $file2 failed: $!"; 463 chmod $stats->mode, $file2; 464 chown $stats->uid, $stats->gid, $file2; 465 flock(LOCKFILE, LOCK_UN); 466 } 467 468 if ($flag1 == 1) { 469 unlink0( $fh1, $filename1 ) or die "Cannot unlink $filename1 safely"; 470 } 471 if ($return_from_sign == 1) { 472 return; 473 } 474 exit; 475} 476 477Getopt::Long::Configure ("posix_default"); 478Getopt::Long::Configure ("bundling"); 479# Getopt::Long::Configure ("debug"); 480 481GetOptions (\%opts, 'm=s', 'h|help', 'v|verbose', 'l|list', 482 'c|cfgfile=s', 483 'd|datafile=s', 484 'p|passphrase=s', 485 's|secretkey=s', 486 'k|keyid=s', 487 'create-cfgfile', # -m F 488 'print-cfgfile', # -m f 489 'create-datafile', # -m D 490 'print-datafile', # -m d 491 'remove-signature',# -m R 492 'sign', # -m E 493 'examine', # -m e 494 'generate-keys'); # -m G 495 496if (defined ($opts{'h'})) { 497 usage(); 498 exit; 499} 500 501if (defined($opts{'k'})) { 502 $KEYID = $opts{'k'}; 503} 504if (defined($opts{'c'})) { 505 $cfgfile = $opts{'c'}; 506} 507if (defined($opts{'d'})) { 508 $datafile = $opts{'d'}; 509} 510if (defined($opts{'p'})) { 511 $passphrase = $opts{'p'}; 512} 513if (defined($opts{'s'})) { 514 $SIGDIR = $opts{'s'}; 515} 516 517if (defined ($opts{'m'}) && ($opts{'m'} =~ /[FfDdREeG]{1}/) ) { 518 $action = $opts{'m'}; 519} 520elsif (defined ($opts{'create-cfgfile'})) { 521 $action = 'F'; 522} 523elsif (defined ($opts{'print-cfgfile'})) { 524 $action = 'f'; 525} 526elsif (defined ($opts{'create-datafile'})) { 527 $action = 'D'; 528} 529elsif (defined ($opts{'print-datafile'})) { 530 $action = 'd'; 531} 532elsif (defined ($opts{'remove-signature'})) { 533 $action = 'R'; 534} 535elsif (defined ($opts{'sign'})) { 536 $action = 'E'; 537} 538elsif (defined ($opts{'examine'})) { 539 $action = 'e'; 540} 541elsif (defined ($opts{'generate-keys'})) { 542 $action = 'G'; 543} 544else { 545 usage(); 546 die ("No valid action specified !"); 547} 548 549if (defined($ARGV[0])) { 550 $file1 = $ARGV[0]; 551} 552if (defined($ARGV[1])) { 553 $file2 = $ARGV[1]; 554} 555 556 557if (($action =~ /[REe]{1}/) && !defined($file1)) { 558 usage(); 559 die("Option -m $action requires a filename (or '-' for stdio)\n"); 560} 561 562if ($action =~ /^F$/) { 563 if (!defined($file1)) { 564 $file1 = $cfgfile; 565 } 566 $file2 = $cfgfile; 567 sign_file (); 568} 569 570if ($action =~ /^D$/) { 571 if (!defined($file1)) { 572 $file1 = $datafile; 573 } 574 $file2 = $datafile; 575 sign_file (); 576} 577 578if ($action =~ /^R$/) { 579 # $file1 defined 580 my $i = 0; 581 while (defined($ARGV[$i])) { 582 $file1 = $ARGV[$i]; 583 remove (); 584 ++$i; 585 } 586} 587 588if ($action =~ /^E$/) { 589 # $file1 defined 590 # default: $file2 = $file1 591 check_signify_sign(); 592 my $i = 0; 593 while (defined($ARGV[$i])) { 594 $file1 = $ARGV[$i]; 595 $file2 = $file1; 596 $return_from_sign = 1; 597 sign_file (); 598 ++$i; 599 } 600} 601 602if ($action =~ /^e$/) { 603 # $file1 defined 604 # default: $file2 = stdout 605 check_signify_verify(); 606 my $i = 0; 607 my $ret = 0; 608 while (defined($ARGV[$i])) { 609 print "\n"; 610 $file1 = $ARGV[$i]; 611 $ret += examine (); 612 ++$i; 613 print "\n--------------------------------\n" if (defined($ARGV[$i])); 614 } 615 exit($ret); 616} 617 618if ($action =~ /^f$/) { 619 $file1 = $cfgfile; 620 $file2 = "-"; 621 print_cfgfile (); 622} 623 624if ($action =~ /^d$/) { 625 # $file1 irrelevant 626 if (defined($opts{'l'})) { 627 print_datafile (); 628 } else { 629 $file1 = $datafile; 630 $file2 = "-"; 631 print_cfgfile (); 632 } 633} 634 635 636 637