source: trunk/scripts/samhainadmin-gpg.pl.in@ 589

Last change on this file since 589 was 589, checked in by katerina, 12 hours ago

Fix for ticket #477 (obsolete gpg option --secret-keyring).

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