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

Last change on this file since 460 was 415, checked in by katerina, 12 years ago

Fixes for tickets #314, #315, #316, #317, #318, #319, #320, and #321.

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