source: trunk/scripts/samhainadmin.pl.in@ 37

Last change on this file since 37 was 30, checked in by rainer, 18 years ago

Release candidate 3 for version 2.2.0

File size: 16.5 KB
Line 
1#! /usr/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);
30
31# Do I/O to the data file in binary mode (so it
32# wouldn't complain about invalid UTF-8 characters).
33use bytes;
34
35File::Temp->safe_level( File::Temp::HIGH );
36
37my %opts = ();
38my $action;
39my $file1;
40my $file2;
41my $passphrase;
42my $return_from_sign = 0;
43my $no_print_examine = 0;
44my $no_remove_lock = 0;
45my $base = basename($0);
46
47my $cfgfile = "@myconffile@";
48my $datafile = "@mydatafile@";
49my $daemon = "@sbindir@/@install_name@";
50my $gpg = "@mygpg@";
51
52$cfgfile =~ s/^REQ_FROM_SERVER//;
53$datafile =~ s/^REQ_FROM_SERVER//;
54
55$gpg = "gpg" if ($gpg eq "");
56
57sub usage() {
58 print "Usage:\n";
59 print " $base { -m F | --create-cfgfile } [options] [in.cfgfile]\n";
60 print " Sign the configuration file. If in.cfgfile is given, sign it\n";
61 print " and install it as configuration file.\n\n";
62
63 print " $base { -m f | --print-cfgfile } [options] \n";
64 print " Print the configuration file to stdout. Signatures are removed.\n\n";
65
66 print " $base { -m D | --create-datafile } [options] [in.datafile]\n";
67 print " Sign the database file. If in.datafile is given, sign it\n";
68 print " and install it as database file.\n\n";
69
70 print " $base { -m d | --print-datafile } [options] \n";
71 print " Print the database file to stdout. Signatures are removed. Use\n";
72 print " option --list to list files in database rather than printing the raw file.\n\n";
73
74 print " $base { -m R | --remove-signature } [options] file1 [file2 ...]\n";
75 print " Remove cleartext signature from input file(s). The file\n";
76 print " is replaced by the non-signed file.\n\n";
77
78 print " $base { -m E | --sign } [options] file1 [file2 ...]\n";
79 print " Sign file(s) with a cleartext signature. The file\n";
80 print " is replaced by the signed file.\n\n";
81
82 print " $base { -m e | --examine } [options] file1 [file2 ...]\n";
83 print " Report signature status of file(s).\n\n";
84
85 print " $base { -m G | --generate-keys } [options] \n";
86 print " Generate a PGP keypair to use for signing.\n\n";
87
88 print "Options:\n";
89 print " -c cfgfile --cfgfile cfgfile\n";
90 print " Select an alternate configuration file.\n\n";
91
92 print " -d datafile --datafile datafile\n";
93 print " Select an alternate database file.\n\n";
94
95 print " -p passphrase --passphrase passphrase\n";
96 print " Set the passphrase for gpg. By default, gpg will ask.\n\n";
97
98 print " -l --list\n";
99 print " List the files in database rather than printing the raw file.\n\n";
100
101 print " -v --verbose\n";
102 print " Verbose output.\n\n";
103 return;
104}
105
106sub check_gpg_uid () {
107 if (0 != $>) {
108 print "--------------------------------------------------\n";
109 print "\n";
110 print " You are not root. Please remember that samhain/yule\n";
111 print " will use the public keyring of root to verify a signature.\n";
112 print "\n";
113 print "--------------------------------------------------\n";
114 } else {
115 if (!("@yulectl_prg@" =~ //)) {
116 print "--------------------------------------------------\n";
117 print "\n";
118 print " Please remember that yule will drop root after startup. Signature\n";
119 print " verification on SIGHUP will fail if you do not import the public key\n";
120 print " into the keyring of the non-root yule user.\n";
121 print "\n";
122 print "--------------------------------------------------\n";
123 }
124 }
125}
126
127sub check_gpg_sign () {
128 if ( (!-d "$ENV{'HOME'}/.gnupg") || (!-e "$ENV{'HOME'}/.gnupg/secring.gpg")) {
129 print "--------------------------------------------------\n";
130 print "\n";
131 if (!-d "$ENV{'HOME'}/.gnupg") {
132 print " Directory \$HOME/.gnupg not found!\n";
133 } else {
134 print " Secret keyring \$HOME/.gnupg/secring.gpg not found!\n";
135 }
136 print "\n";
137 print " This indicates that you have never created a \n";
138 print " public/private keypair, and thus cannot sign.\n";
139 print " \n";
140 print " Please use $0 --generate-keys or gpg --gen-key\n";
141 print " to generate a public/private keypair first.\n";
142 print "\n";
143 print "--------------------------------------------------\n";
144 print "\n";
145 exit;
146 }
147}
148
149sub check_gpg_verify () {
150 if ( (!-d "$ENV{'HOME'}/.gnupg") || (!-e "$ENV{'HOME'}/.gnupg/pubring.gpg")) {
151 print "--------------------------------------------------\n";
152 print "\n";
153 if (!-d "$ENV{'HOME'}/.gnupg") {
154 print " Directory \$HOME/.gnupg not found!\n";
155 } else {
156 print " Public keyring \$HOME/.gnupg/pubring.gpg not found!\n";
157 }
158 print "\n";
159 print " This indicates that you have never used gpg before \n";
160 print " and/or have no public keys to verify signatures.\n";
161 print " \n";
162 print " Please use 'gpg --export key_id' to export the public\n";
163 print " signing key of the user who is signing the\n";
164 print " configuration/database files.\n\n";
165 print " Then you can use 'gpg --import keyfile' to import the key\n";
166 print " into this user's public keyring.\n";
167 print "\n";
168 print "--------------------------------------------------\n";
169 print "\n";
170 exit;
171 }
172}
173
174
175sub generate () {
176 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg --gen-key";
177 check_gpg_uid();
178 system ($command) == 0
179 or die "system $command failed: $?";
180 exit;
181}
182
183sub examine () {
184 my $iscfg = 0;
185 my $have_fp = 0;
186 my $have_sig = 0;
187 my $message = '';
188 my $retval = 9;
189 my $fh;
190 my $filename;
191
192 if (!($file1 =~ /^\-$/)) {
193 die ("Cannot open $file1 for read: $!") unless ((-e $file1) && (-r _));
194 }
195 open FIN, "<$file1" or die "Cannot open $file1 for read: $!";
196
197 my $dir = tempdir( CLEANUP => 1 );
198 $filename = $dir . "/exa_jhfdbilw." . $$;
199 open $fh, ">$filename" or die "Cannot open $filename";
200 autoflush $fh 1;
201
202 while (<FIN>) {
203 print $fh $_;
204 if ($_ =~ /^\s*\[Misc\]/) {
205 $iscfg = 1;
206 }
207 }
208 if ($iscfg == 1) {
209 $message .= "File $file1 is a configuration file\n\n";
210 } else {
211 $message .= "File $file1 is a database file\n\n";
212 }
213
214
215 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg --status-fd 1 ";
216 $command .= "--verbose " if (defined($opts{'v'}));
217 $command .= "--verify $filename ";
218 if (defined($opts{'v'})) {
219 $command .= "2>&1";
220 } else {
221 $command .= "2>/dev/null";
222 }
223
224 print STDOUT "Using: $command\n\n" if (defined($opts{'v'}));
225 open GPGIN, "$command |" or die "Cannot fork: $!";
226
227 while (<GPGIN>) {
228 if ($_ =~ /^\[GNUPG:\] GOODSIG ([0-9A-F]+) (.*)$/) {
229 $message .= "GOOD signature with key: $1\n";
230 $message .= "Key owner: $2\n";
231 $have_sig = 1;
232 $retval = 0;
233 }
234 if ($_ =~ /^\[GNUPG:\] VALIDSIG ([0-9A-F]+) ([0-9\-]+)\s/) {
235 $message .= "Key fingerprint: $1\n";
236 $message .= "Signature generated on: $2\n\n";
237 $have_fp = 1;
238 $message .= "This file is signed with a valid signature.\n"
239 if ($have_sig == 1);
240 $have_sig = 1;
241 $have_fp = 1;
242 }
243 if ($_ =~ /^\[GNUPG:\] NODATA 1/) {
244 $message .= "NO signature found.\n\n";
245 $message .= "This file is not signed !!!\n";
246 $have_sig = 1;
247 $have_fp = 1;
248 $retval = 2;
249 }
250 if ($_ =~ /^\[GNUPG:\] BADSIG ([0-9A-F]+) (.*)$/) {
251 $message .= "BAD signature with key: $1\n";
252 $message .= "Key owner: $2\n\n";
253 $message .= "This file is signed with an invalid signature !!!\n";
254 $have_sig = 1;
255 $have_fp = 1;
256 $retval = 1;
257 }
258 if ($_ =~ /^\[GNUPG:\] NO_PUBKEY ([0-9A-F]+)/) {
259 $message .= "NOT CHECKED signature with key: $1\n\n";
260 $message .= "The signature of this file cannot be checked: no public key available !!!\n";
261 $have_sig = 1;
262 $have_fp = 1;
263 $retval = 1;
264 }
265 print STDOUT $_ if (defined($opts{'v'}));
266 }
267 close (GPGIN);
268 print STDOUT "\n" if (defined($opts{'v'}));
269 if ($have_sig == 0) {
270 $message .= "NO valid signature found\n";
271 }
272 elsif ($have_fp == 0) {
273 $message .= "NO fingerprint found\n";
274 }
275 close (FIN);
276 if ($no_print_examine == 0) {
277 print STDOUT $message;
278 }
279 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely";
280 return $retval;
281}
282
283sub remove () {
284 my $bodystart = 1;
285 my $sigstart = 0;
286 my $sigend = 0;
287 my $filename = "";
288 my $fh;
289 my $stats;
290
291 open FH, "<$file1" or die "Cannot open file $file1 for read: $!";
292 if (!($file1 =~ /^\-$/)) {
293 flock(FH, LOCK_EX) unless ($no_remove_lock == 1);
294 my $dir = tempdir( CLEANUP => 1 ) or die "Tempdir failed";
295 $filename = $dir . "/rem_iqegBCQb." . $$;
296 open $fh, ">$filename" or die "Cannot open $filename";
297 $stats = stat($file1);
298 # ($fh, $filename) = tempfile(UNLINK => 1);
299 } else {
300 open $fh, ">$file1" or die "Cannot open file $file1 for write: $!";
301 }
302 autoflush $fh 1;
303 while (<FH>) {
304 if ($_ =~ /^-----BEGIN PGP SIGNED MESSAGE-----/) {
305 $sigstart = 1;
306 $bodystart = 0;
307 next;
308 } elsif (($sigstart == 1) && ($_ =~ /^\s+$/)) {
309 $sigstart = 0;
310 $bodystart = 1;
311 next;
312 } elsif ($_ =~ /^-----BEGIN PGP SIGNATURE-----/) {
313 $bodystart = 0;
314 $sigend = 1;
315 next;
316 } elsif (($sigend == 1) && ($_ =~ /^-----END PGP SIGNATURE-----/)) {
317 $sigend = 0;
318 $bodystart = 1;
319 next;
320 }
321 if ($bodystart == 1) {
322 print $fh $_;
323 }
324 }
325 if (!($file1 =~ /^\-$/)) {
326 copy("$filename", "$file1")
327 or die "Copy $filename to $file1 failed: $!";
328 chmod $stats->mode, $file1;
329 chown $stats->uid, $stats->gid, $file1;
330 flock(FH, LOCK_UN) unless ($no_remove_lock == 1);
331 close FH;
332 }
333 unlink0( $fh, $filename ) or die "Cannot unlink $filename safely";
334 return;
335}
336
337sub print_cfgfile () {
338 my $bodystart = 0;
339 my $sigstart = 0;
340
341 if (!defined($file2)) {
342 $file2 = '-';
343 }
344
345 open FH, "<$file1" or die "Cannot open file $file1 for read: $!";
346 open FO, ">$file2" or die "Cannot open file $file2 for write: $!";
347 while (<FH>) {
348 if ($_ =~ /^-----BEGIN PGP SIGNED MESSAGE-----/) {
349 $sigstart = 1;
350 next;
351 } elsif (($sigstart == 1) && ($_ =~ /^\s+$/)) {
352 $sigstart = 0;
353 $bodystart = 1;
354 next;
355 } elsif ($_ =~ /^-----BEGIN PGP SIGNATURE-----/) {
356 $bodystart = 0;
357 exit;
358 }
359 if ($bodystart == 1) {
360 print FO $_;
361 }
362 }
363 exit;
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_gpg_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 ($_ =~ /^-----BEGIN PGP SIGNED MESSAGE-----/) {
407 $sigstart = 1;
408 $bodystart = 0;
409 next;
410 } elsif (($sigstart == 1) && ($_ =~ /^\s+$/)) {
411 $sigstart = 0;
412 $bodystart = 1;
413 next;
414 } elsif ($_ =~ /^-----BEGIN PGP SIGNATURE-----/) {
415 $bodystart = 0;
416 $sigend = 1;
417 next;
418 } elsif (($sigend == 1) && ($_ =~ /^-----END PGP SIGNATURE-----/)) {
419 $sigend = 0;
420 $bodystart = 1;
421 next;
422 }
423 if ($bodystart == 1) {
424 print $fh1 $_;
425 }
426 #
427 # print $fh1 $_;
428 #
429 }
430 $file1 = $filename1;
431 $fileout = '-';
432 } else {
433 open (LOCKFILE, "<$file1") or die "Cannot open $file1: $!";
434 flock(LOCKFILE, LOCK_EX);
435 $no_print_examine = 1;
436 $no_remove_lock = 1;
437 if (examine() < 2) {
438 remove();
439 }
440 $fileout = $file1 . ".asc";
441 $stats = stat($file1)
442 or die "No file $file1: $!";
443 }
444
445 if (defined($passphrase)) {
446 local $SIG{PIPE} = 'IGNORE';
447 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg --passphrase-fd 0 -a --clearsign -o $fileout --not-dash-escaped $file1";
448 open (FH, "|$command") or die "can't fork: $!";
449 print FH "$passphrase" or die "can't write: $!";
450 close FH or die "can't close: status=$?";
451 } else {
452 my $command = "$gpg --homedir $ENV{'HOME'}/.gnupg -a --clearsign -o $fileout --not-dash-escaped $file1";
453 system("$command") == 0
454 or die "system $command failed: $?";
455 }
456
457 if (!($fileout =~ /^\-$/)) {
458 my $st_old = stat($file1)
459 or die "No file $file1: $!";
460 my $st_new = stat($fileout)
461 or die "No file $fileout: $!";
462 die ("Signed file is smaller than unsigned file")
463 unless ($st_new->size > $st_old->size);
464 move("$fileout", "$file2")
465 or die "Move $fileout to $file2 failed: $!";
466 chmod $stats->mode, $file2;
467 chown $stats->uid, $stats->gid, $file2;
468 flock(LOCKFILE, LOCK_UN);
469 }
470
471 if ($flag1 == 1) {
472 unlink0( $fh1, $filename1 ) or die "Cannot unlink $filename1 safely";
473 }
474 if ($return_from_sign == 1) {
475 return;
476 }
477 exit;
478}
479
480Getopt::Long::Configure ("posix_default");
481Getopt::Long::Configure ("bundling");
482# Getopt::Long::Configure ("debug");
483
484GetOptions (\%opts, 'm=s', 'h|help', 'v|verbose', 'l|list',
485 'c|cfgfile=s',
486 'd|datafile=s',
487 'p|passphrase=s',
488 'create-cfgfile', # -m F
489 'print-cfgfile', # -m f
490 'create-datafile', # -m D
491 'print-datafile', # -m d
492 'remove-signature',# -m R
493 'sign', # -m E
494 'examine', # -m e
495 'generate-keys'); # -m G
496
497if (defined ($opts{'h'})) {
498 usage();
499 exit;
500}
501
502if (defined($opts{'c'})) {
503 $cfgfile = $opts{'c'};
504}
505if (defined($opts{'d'})) {
506 $datafile = $opts{'d'};
507}
508if (defined($opts{'p'})) {
509 $passphrase = $opts{'p'};
510}
511
512if (defined ($opts{'m'}) && ($opts{'m'} =~ /[FfDdREeG]{1}/) ) {
513 $action = $opts{'m'};
514}
515elsif (defined ($opts{'create-cfgfile'})) {
516 $action = 'F';
517}
518elsif (defined ($opts{'print-cfgfile'})) {
519 $action = 'f';
520}
521elsif (defined ($opts{'create-datafile'})) {
522 $action = 'D';
523}
524elsif (defined ($opts{'print-datafile'})) {
525 $action = 'd';
526}
527elsif (defined ($opts{'remove-signature'})) {
528 $action = 'R';
529}
530elsif (defined ($opts{'sign'})) {
531 $action = 'E';
532}
533elsif (defined ($opts{'examine'})) {
534 $action = 'e';
535}
536elsif (defined ($opts{'generate-keys'})) {
537 $action = 'G';
538}
539else {
540 usage();
541 die ("No valid action specified !");
542}
543
544if (defined($ARGV[0])) {
545 $file1 = $ARGV[0];
546}
547if (defined($ARGV[1])) {
548 $file2 = $ARGV[1];
549}
550
551
552if (($action =~ /[REe]{1}/) && !defined($file1)) {
553 usage();
554 die("Option -m $action requires a filename (or '-' for stdio)\n");
555}
556
557if ($action =~ /^F$/) {
558 if (!defined($file1)) {
559 $file1 = $cfgfile;
560 }
561 $file2 = $cfgfile;
562 sign_file ();
563}
564
565if ($action =~ /^D$/) {
566 if (!defined($file1)) {
567 $file1 = $datafile;
568 }
569 $file2 = $datafile;
570 sign_file ();
571}
572
573if ($action =~ /^R$/) {
574 # $file1 defined
575 my $i = 0;
576 while (defined($ARGV[$i])) {
577 $file1 = $ARGV[$i];
578 remove ();
579 ++$i;
580 }
581}
582
583if ($action =~ /^E$/) {
584 # $file1 defined
585 # default: $file2 = $file1
586 check_gpg_sign();
587 my $i = 0;
588 while (defined($ARGV[$i])) {
589 $file1 = $ARGV[$i];
590 $file2 = $file1;
591 $return_from_sign = 1;
592 sign_file ();
593 ++$i;
594 }
595}
596
597if ($action =~ /^e$/) {
598 # $file1 defined
599 # default: $file2 = stdout
600 check_gpg_verify();
601 my $i = 0;
602 while (defined($ARGV[$i])) {
603 print "\n";
604 $file1 = $ARGV[$i];
605 examine ();
606 ++$i;
607 print "\n--------------------------------\n" if (defined($ARGV[$i]));
608 }
609}
610
611if ($action =~ /^f$/) {
612 $file1 = $cfgfile;
613 $file2 = "-";
614 print_cfgfile ();
615}
616
617if ($action =~ /^d$/) {
618 # $file1 irrelevant
619 if (defined($opts{'l'})) {
620 print_datafile ();
621 } else {
622 $file1 = $datafile;
623 $file2 = "-";
624 print_cfgfile ();
625 }
626}
627
628
629
Note: See TracBrowser for help on using the repository browser.