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

Last change on this file since 248 was 191, checked in by katerina, 16 years ago

Patch for GnuPG key ID by Jim Dutton

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