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

Last change on this file since 15 was 3, checked in by rainer, 19 years ago

More fixes for update function, released 2.1.1 version.

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