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

Last change on this file since 87 was 53, checked in by rainer, 18 years ago

samhainadmin.pl: check for gpg-agent running if use-agent is set

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