| 1 |
#!/usr/bin/perl -w |
#!/usr/bin/perl -w |
| 2 |
|
|
| 3 |
# caff -- CA - fire and forget |
# caff -- CA - Fire and Forget |
| 4 |
# $Id$ |
# $Id$ |
| 5 |
# |
# |
| 6 |
# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org> |
# Copyright (c) 2004, 2005 Peter Palfrader <peter@palfrader.org> |
| 7 |
# |
# |
| 8 |
# All rights reserved. |
# All rights reserved. |
| 9 |
# |
# |
| 29 |
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 30 |
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 31 |
|
|
| 32 |
|
=pod |
| 33 |
|
|
| 34 |
|
=head1 NAME |
| 35 |
|
|
| 36 |
|
caff -- CA - Fire and Forget |
| 37 |
|
|
| 38 |
|
=head1 SYNOPSIS |
| 39 |
|
|
| 40 |
|
=over |
| 41 |
|
|
| 42 |
|
=item B<caff> [-mMR] [-u I<yourkeyid>] I<keyid> [I<keyid> ..] |
| 43 |
|
|
| 44 |
|
=back |
| 45 |
|
|
| 46 |
|
=head1 DESCRIPTION |
| 47 |
|
|
| 48 |
|
CA Fire and Forget is a script that helps you in keysigning. It takes a list |
| 49 |
|
of keyids on the command line, fetches them from a keyserver and calls GnuPG so |
| 50 |
|
that you can sign it. It then mails each key to all its email addresses - only |
| 51 |
|
including the one UID that we send to in each mail, pruned from all but self |
| 52 |
|
sigs and sigs done by you. |
| 53 |
|
|
| 54 |
|
=head1 OPTIONS |
| 55 |
|
|
| 56 |
|
=over |
| 57 |
|
|
| 58 |
|
=item B<-m> B<-M> |
| 59 |
|
|
| 60 |
|
Send/do not send mail after signing. Default is to ask the user for each uid. |
| 61 |
|
|
| 62 |
|
=item B<-R> |
| 63 |
|
|
| 64 |
|
Do not retrieve the key to be signed from a keyserver. |
| 65 |
|
|
| 66 |
|
=item B<-u> I<yourkeyid> |
| 67 |
|
|
| 68 |
|
Select the key that is used for signing, in case you have more than one key. |
| 69 |
|
|
| 70 |
|
=back |
| 71 |
|
|
| 72 |
|
=head1 FILES |
| 73 |
|
|
| 74 |
|
=over |
| 75 |
|
|
| 76 |
|
=item $HOME/.caffrc - configuration file |
| 77 |
|
|
| 78 |
|
=back |
| 79 |
|
|
| 80 |
|
=head1 CONFIGURATION FILE OPTIONS |
| 81 |
|
|
| 82 |
|
The configuration file is a perl script that sets values in the hash B<%CONFIG>. |
| 83 |
|
|
| 84 |
|
Example: |
| 85 |
|
|
| 86 |
|
$CONFIG{owner} = q{Peter Palfrader}; |
| 87 |
|
$CONFIG{email} = q{peter@palfrader.org}; |
| 88 |
|
$CONFIG{keyid} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ]; |
| 89 |
|
|
| 90 |
|
=head2 Valid keys |
| 91 |
|
|
| 92 |
|
=over |
| 93 |
|
|
| 94 |
|
=item B<caffhome> [string] |
| 95 |
|
|
| 96 |
|
Base directory for the files caff stores. Default: B<$HOME/.caff/>. |
| 97 |
|
|
| 98 |
|
=item B<owner> [string] |
| 99 |
|
|
| 100 |
|
Your name. B<REQUIRED>. |
| 101 |
|
|
| 102 |
|
=item B<email> [string] |
| 103 |
|
|
| 104 |
|
Your email address, used in From: lines. B<REQUIRED>. |
| 105 |
|
|
| 106 |
|
=item B<keyid> [list of keyids] |
| 107 |
|
|
| 108 |
|
A list of your keys. This is used to determine which signatures to keep |
| 109 |
|
in the pruning step. If you select a key using B<-u> it has to be in |
| 110 |
|
this list. B<REQUIRED>. |
| 111 |
|
|
| 112 |
|
=item B<export-sig-age> [seconds] |
| 113 |
|
|
| 114 |
|
Don't export UIDs by default, on which your latest signature is older |
| 115 |
|
than this age. Default: B<24*60*60> (i.e. one day). |
| 116 |
|
|
| 117 |
|
=item B<keyserver> [string] |
| 118 |
|
|
| 119 |
|
Keyserver to download keys from. Default: B<subkeys.pgp.net>. |
| 120 |
|
|
| 121 |
|
=item B<gpg> [string] |
| 122 |
|
|
| 123 |
|
Path to the GnuPG binary. Default: B<gpg>. |
| 124 |
|
|
| 125 |
|
=item B<gpg-sign> [string] |
| 126 |
|
|
| 127 |
|
Path to the GnuPG binary which is used to sign keys. Default: what |
| 128 |
|
B<gpg> is set to. |
| 129 |
|
|
| 130 |
|
=item B<gpg-delsig> [string] |
| 131 |
|
|
| 132 |
|
Path to the GnuPG binary which is used to split off signatures. This was |
| 133 |
|
needed while the upstream GnuPG was not fixed. Default: what B<gpg> |
| 134 |
|
is set to. |
| 135 |
|
|
| 136 |
|
=item B<secret-keyring> [string] |
| 137 |
|
|
| 138 |
|
Path to your secret keyring. Default: B<$HOME/.gnupg/secring.gpg>. |
| 139 |
|
|
| 140 |
|
=item B<also-encrypt-to> [keyid] |
| 141 |
|
|
| 142 |
|
An additional keyid to encrypt messages to. Default: none. |
| 143 |
|
|
| 144 |
|
=item B<no-download> [boolean] |
| 145 |
|
|
| 146 |
|
If true, then skip the step of fetching keys from the keyserver. |
| 147 |
|
Default: B<0>. |
| 148 |
|
|
| 149 |
|
=item B<no-sign> [boolean] |
| 150 |
|
|
| 151 |
|
If true, then skip the signing step. Default: B<0>. |
| 152 |
|
|
| 153 |
|
=back |
| 154 |
|
|
| 155 |
|
=head1 AUTHOR |
| 156 |
|
|
| 157 |
|
Peter Palfrader <peter@palfrader.org> |
| 158 |
|
|
| 159 |
|
=head1 WEBSITE |
| 160 |
|
|
| 161 |
|
http://pgp-tools.alioth.debian.org/ |
| 162 |
|
|
| 163 |
|
=cut |
| 164 |
|
|
| 165 |
use strict; |
use strict; |
| 166 |
use IO::Handle; |
use IO::Handle; |
| 167 |
use English; |
use English; |
| 170 |
use MIME::Entity; |
use MIME::Entity; |
| 171 |
use Fcntl; |
use Fcntl; |
| 172 |
use IO::Select; |
use IO::Select; |
| 173 |
|
use Getopt::Std; |
| 174 |
use GnuPG::Interface; |
use GnuPG::Interface; |
| 175 |
|
|
| 176 |
my %CONFIG; |
my %CONFIG; |
| 180 |
|
|
| 181 |
sub load_config() { |
sub load_config() { |
| 182 |
my $config = $ENV{'HOME'} . '/.caffrc'; |
my $config = $ENV{'HOME'} . '/.caffrc'; |
| 183 |
-f $config or die "No file $config present. See caffrc(5).\n"; |
-f $config or die "No file $config present. See caff(1).\n"; |
| 184 |
unless (scalar eval `cat $config`) { |
unless (scalar eval `cat $config`) { |
| 185 |
die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR; |
die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR; |
| 186 |
}; |
}; |
| 187 |
|
|
| 188 |
die ("caffhome is not defined.\n") unless defined $CONFIG{'caffhome'}; |
$CONFIG{'caffhome'}=$ENV{'HOME'}.'/.caff' unless defined $CONFIG{'caffhome'}; |
| 189 |
die ("owner is not defined.\n") unless defined $CONFIG{'owner'}; |
die ("owner is not defined.\n") unless defined $CONFIG{'owner'}; |
| 190 |
die ("email is not defined.\n") unless defined $CONFIG{'email'}; |
die ("email is not defined.\n") unless defined $CONFIG{'email'}; |
| 191 |
die ("keyid is not defined.\n") unless defined $CONFIG{'keyid'}; |
die ("keyid is not defined.\n") unless defined $CONFIG{'keyid'}; |
| 200 |
$CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'}; |
$CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'}; |
| 201 |
$CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'}; |
$CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'}; |
| 202 |
$CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'}; |
$CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'}; |
| 203 |
|
$CONFIG{'no-download'} = 0 unless defined $CONFIG{'no-download'}; |
| 204 |
|
$CONFIG{'no-sign'} = 0 unless defined $CONFIG{'no-sign'}; |
| 205 |
}; |
}; |
| 206 |
|
|
| 207 |
sub notice($) { |
sub notice($) { |
| 261 |
|
|
| 262 |
my ($stdout, $stderr, $status) = ("", "", ""); |
my ($stdout, $stderr, $status) = ("", "", ""); |
| 263 |
my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'}; |
my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'}; |
| 264 |
trace("doign stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches; |
trace("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches; |
| 265 |
|
|
| 266 |
|
my $readwrote_stuff_this_time = 0; |
| 267 |
|
my $do_not_wait_on_select = 0; |
| 268 |
my ($readyr, $readyw, $written); |
my ($readyr, $readyw, $written); |
| 269 |
while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) { |
while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) { |
| 270 |
if (defined $exitwhenstatusmatches) { |
if (defined $exitwhenstatusmatches) { |
| 271 |
if ($status =~ /$exitwhenstatusmatches/m) { |
if ($status =~ /$exitwhenstatusmatches/m) { |
| 272 |
trace("readwrite_gpg found match on $exitwhenstatusmatches"); |
trace("readwrite_gpg found match on $exitwhenstatusmatches"); |
| 273 |
last; |
if ($readwrote_stuff_this_time) { |
| 274 |
|
trace("read/write some more\n"); |
| 275 |
|
$do_not_wait_on_select = 1; |
| 276 |
|
} else { |
| 277 |
|
trace("that's it in our while loop.\n"); |
| 278 |
|
last; |
| 279 |
|
} |
| 280 |
}; |
}; |
| 281 |
}; |
}; |
| 282 |
|
|
| 283 |
|
$readwrote_stuff_this_time = 0; |
| 284 |
trace("select waiting for ".($sout->count())." fds."); |
trace("select waiting for ".($sout->count())." fds."); |
| 285 |
($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, 1); |
($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1); |
| 286 |
trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0)); |
trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0)); |
| 287 |
for my $wfd (@$readyw) { |
for my $wfd (@$readyw) { |
| 288 |
|
$readwrote_stuff_this_time = 1; |
| 289 |
if (length($in) != $offset) { |
if (length($in) != $offset) { |
| 290 |
trace("writing to $wfd."); |
trace("writing to $wfd."); |
| 291 |
$written = $wfd->syswrite($in, length($in) - $offset, $offset); |
$written = $wfd->syswrite($in, length($in) - $offset, $offset); |
| 305 |
next unless (defined(@$readyr)); # Wait some more. |
next unless (defined(@$readyr)); # Wait some more. |
| 306 |
|
|
| 307 |
for my $rfd (@$readyr) { |
for my $rfd (@$readyr) { |
| 308 |
|
$readwrote_stuff_this_time = 1; |
| 309 |
if ($rfd->eof) { |
if ($rfd->eof) { |
| 310 |
trace("reading from $rfd done."); |
trace("reading from $rfd done."); |
| 311 |
$sout->remove($rfd); |
$sout->remove($rfd); |
| 362 |
my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey'; |
my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey'; |
| 363 |
|
|
| 364 |
load_config; |
load_config; |
| 365 |
my $USER_AGENT = "caff $VERSION - (c) 2004 Peter Palfrader"; |
my $USER_AGENT = "caff $VERSION - (c) 2004, 2005 Peter Palfrader"; |
| 366 |
|
|
| 367 |
my $KEYSBASE = $CONFIG{'caffhome'}.'/keys'; |
my $KEYSBASE = $CONFIG{'caffhome'}.'/keys'; |
| 368 |
my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome'; |
my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome'; |
| 376 |
|
|
| 377 |
|
|
| 378 |
sub usage() { |
sub usage() { |
| 379 |
print STDERR "caff $VERSION - (c) 2004 Peter Palfrader\n"; |
print STDERR "caff $VERSION - (c) 2004, 2005 Peter Palfrader\n"; |
| 380 |
print STDERR "Usage: $PROGRAM_NAME [-u <yourkeyid] <keyid> [<keyid> ...]\n"; |
print STDERR "Usage: $PROGRAM_NAME [-mMR] [-u <yourkeyid>] <keyid> [<keyid> ...]\n"; |
| 381 |
exit 1; |
exit 1; |
| 382 |
}; |
}; |
| 383 |
|
|
| 477 |
|
|
| 478 |
$message_entity->head->add("Subject", "Your signed PGP key 0x$key_id"); |
$message_entity->head->add("Subject", "Your signed PGP key 0x$key_id"); |
| 479 |
$message_entity->head->add("To", $address); |
$message_entity->head->add("To", $address); |
| 480 |
$message_entity->head->add("From", $CONFIG{'owner'}.' <'.$CONFIG{'email'}.'>'); |
$message_entity->head->add("From", '"'.$CONFIG{'owner'}.'" <'.$CONFIG{'email'}.'>'); |
| 481 |
$message_entity->head->add("User-Agent", $USER_AGENT); |
$message_entity->head->add("User-Agent", $USER_AGENT); |
| 482 |
$message_entity->send(); |
$message_entity->send(); |
| 483 |
$message_entity->stringify(); |
$message_entity->stringify(); |
| 484 |
}; |
}; |
| 485 |
|
|
| 486 |
|
sub sanitize_uid($) { |
| 487 |
|
my ($uid) = @_; |
| 488 |
|
|
| 489 |
|
my $good_uid = $uid; |
| 490 |
|
$good_uid =~ tr#/:\\#_#; |
| 491 |
|
trace2("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid; |
| 492 |
|
return $good_uid; |
| 493 |
|
}; |
| 494 |
|
|
| 495 |
my $USER; |
my $USER; |
| 496 |
my @KEYIDS; |
my @KEYIDS; |
| 497 |
|
my %opt; |
| 498 |
|
|
| 499 |
|
getopts('mMRu:', \%opt); |
| 500 |
|
|
| 501 |
usage() unless scalar @ARGV >= 1; |
usage() unless scalar @ARGV >= 1; |
| 502 |
if ($ARGV[0] eq '-u') { |
if ($opt{u}) { |
| 503 |
usage() unless scalar @ARGV >= 3; |
$USER = $opt{u}; |
| 504 |
shift @ARGV; |
$USER =~ s/^0x//i; |
|
$USER = shift @ARGV; |
|
| 505 |
unless ($USER =~ /^[A-Za-z0-9]{8,8}([A-Za-z0-9]{8})?$/) { |
unless ($USER =~ /^[A-Za-z0-9]{8,8}([A-Za-z0-9]{8})?$/) { |
| 506 |
print STDERR "-u $USER is not a keyid.\n"; |
print STDERR "-u $USER is not a keyid.\n"; |
| 507 |
usage(); |
usage(); |
| 509 |
$USER = uc($USER); |
$USER = uc($USER); |
| 510 |
}; |
}; |
| 511 |
for my $keyid (@ARGV) { |
for my $keyid (@ARGV) { |
| 512 |
|
$keyid =~ s/^0x//i; |
| 513 |
unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8})?$/) { |
unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8})?$/) { |
| 514 |
print STDERR "$keyid is not a keyid.\n"; |
print STDERR "$keyid is not a keyid.\n"; |
| 515 |
usage(); |
usage(); |
| 518 |
}; |
}; |
| 519 |
|
|
| 520 |
|
|
| 521 |
|
|
| 522 |
|
################# |
| 523 |
|
# import own keys |
| 524 |
|
################# |
| 525 |
|
my $gpg = GnuPG::Interface->new(); |
| 526 |
|
$gpg->call( $CONFIG{'gpg'} ); |
| 527 |
|
$gpg->options->hash_init( |
| 528 |
|
'homedir' => $GNUPGHOME, |
| 529 |
|
'extra_args' => '--keyserver='.$CONFIG{'keyserver'} ); |
| 530 |
|
$gpg->options->meta_interactive( 0 ); |
| 531 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 532 |
|
$gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] ); |
| 533 |
|
my $pid = $gpg->list_public_keys(handles => $handles, command_args => $CONFIG{'keyid'}); |
| 534 |
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 535 |
|
waitpid $pid, 0; |
| 536 |
|
if ($stdout eq '') { |
| 537 |
|
warn ("No data from gpg for list-key\n"); |
| 538 |
|
next; |
| 539 |
|
}; |
| 540 |
|
foreach my $keyid (@{$CONFIG{'keyid'}}) { |
| 541 |
|
unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) { |
| 542 |
|
info("Importing $keyid"); |
| 543 |
|
system "gpg --export $keyid | gpg --import --homedir $GNUPGHOME"; |
| 544 |
|
} |
| 545 |
|
} |
| 546 |
|
|
| 547 |
############################# |
############################# |
| 548 |
# receive keys from keyserver |
# receive keys from keyserver |
| 549 |
############################# |
############################# |
|
my $gpg = GnuPG::Interface->new(); |
|
|
$gpg->call( $CONFIG{'gpg'} ); |
|
|
$gpg->options->hash_init( |
|
|
'homedir' => $GNUPGHOME, |
|
|
'extra_args' => '--keyserver='.$CONFIG{'keyserver'} ); |
|
|
$gpg->options->meta_interactive( 0 ); |
|
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
|
|
my $pid = $gpg->recv_keys(handles => $handles, command_args => [ @KEYIDS ]); |
|
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
|
|
waitpid $pid, 0; |
|
|
|
|
| 550 |
my @keyids_ok; |
my @keyids_ok; |
| 551 |
my @keyids_failed; |
my @keyids_failed; |
| 552 |
|
if ($CONFIG{'no-download'} or $opt{R}) { |
| 553 |
|
@keyids_ok = @KEYIDS; |
| 554 |
|
} else { |
| 555 |
|
my $gpg = GnuPG::Interface->new(); |
| 556 |
|
$gpg->call( $CONFIG{'gpg'} ); |
| 557 |
|
$gpg->options->hash_init( |
| 558 |
|
'homedir' => $GNUPGHOME, |
| 559 |
|
'extra_args' => '--keyserver='.$CONFIG{'keyserver'} ); |
| 560 |
|
$gpg->options->meta_interactive( 0 ); |
| 561 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 562 |
|
|
| 563 |
|
my @local_keyids = @KEYIDS; |
| 564 |
|
for my $keyid (@local_keyids) { |
| 565 |
|
info ("fetching $keyid..."); |
| 566 |
|
my $pid = $gpg->recv_keys(handles => $handles, command_args => [ $keyid ]); |
| 567 |
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 568 |
|
waitpid $pid, 0; |
| 569 |
|
|
| 570 |
# [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F |
# [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F |
| 571 |
# [GNUPG:] NODATA 1 |
# [GNUPG:] NODATA 1 |
| 572 |
# [GNUPG:] NODATA 1 |
# [GNUPG:] NODATA 1 |
| 573 |
# [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039 |
# [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039 |
| 574 |
for my $line (split /\n/, $status) { |
my $handled = 0; |
| 575 |
if ($line =~ /^\[GNUPG:\] IMPORT_OK/) { |
for my $line (split /\n/, $status) { |
| 576 |
push @keyids_ok, shift @KEYIDS; |
if ($line =~ /^\[GNUPG:\] IMPORT_OK/) { |
| 577 |
} elsif ($line =~ /^\[GNUPG:\] NODATA/) { |
push @keyids_ok, shift @KEYIDS; |
| 578 |
push @keyids_failed, shift @KEYIDS; |
$handled = 1; |
| 579 |
}; |
last; |
| 580 |
} |
} elsif ($line =~ /^\[GNUPG:\] NODATA/) { |
| 581 |
die ("Still keys in \@KEYIDS. This should not happen.") if scalar @KEYIDS; |
push @keyids_failed, shift @KEYIDS; |
| 582 |
notice ("Import failed for: ". (join ' ', @keyids_failed).".") if scalar @keyids_failed; |
$handled = 1; |
| 583 |
|
last; |
| 584 |
|
}; |
| 585 |
|
}; |
| 586 |
|
unless ($handled) { |
| 587 |
|
notice ("Huh, what's up with $keyid?"); |
| 588 |
|
push @keyids_failed, shift @KEYIDS; |
| 589 |
|
}; |
| 590 |
|
}; |
| 591 |
|
die ("Still keys in \@KEYIDS. This should not happen.") if scalar @KEYIDS; |
| 592 |
|
notice ("Import failed for: ". (join ' ', @keyids_failed).".") if scalar @keyids_failed; |
| 593 |
|
}; |
| 594 |
|
|
| 595 |
########### |
########### |
| 596 |
# sign keys |
# sign keys |
| 597 |
########### |
########### |
| 598 |
info("Sign the following keys according to your policy..."); |
unless ($CONFIG{'no-sign'}) { |
| 599 |
for my $keyid (@keyids_ok) { |
info("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key"); |
| 600 |
my @command; |
for my $keyid (@keyids_ok) { |
| 601 |
push @command, $CONFIG{'gpg-sign'}; |
my @command; |
| 602 |
push @command, '--local-user', $USER if (defined $USER); |
push @command, $CONFIG{'gpg-sign'}; |
| 603 |
push @command, "--homedir=$GNUPGHOME"; |
push @command, '--local-user', $USER if (defined $USER); |
| 604 |
push @command, '--secret-keyring', $CONFIG{'secret-keyring'}; |
push @command, "--homedir=$GNUPGHOME"; |
| 605 |
push @command, '--sign-key', $keyid; |
push @command, '--secret-keyring', $CONFIG{'secret-keyring'}; |
| 606 |
print join(' ', @command),"\n"; |
push @command, '--edit', $keyid; |
| 607 |
system (@command); |
push @command, 'sign'; |
| 608 |
|
print join(' ', @command),"\n"; |
| 609 |
|
system (@command); |
| 610 |
|
}; |
| 611 |
}; |
}; |
| 612 |
|
|
| 613 |
################## |
################## |
| 617 |
for my $keyid (@keyids_ok) { |
for my $keyid (@keyids_ok) { |
| 618 |
# get key listing |
# get key listing |
| 619 |
################# |
################# |
| 620 |
$gpg = GnuPG::Interface->new(); |
my $gpg = GnuPG::Interface->new(); |
| 621 |
$gpg->call( $CONFIG{'gpg'} ); |
$gpg->call( $CONFIG{'gpg'} ); |
| 622 |
$gpg->options->hash_init( 'homedir' => $GNUPGHOME ); |
$gpg->options->hash_init( 'homedir' => $GNUPGHOME ); |
| 623 |
$gpg->options->meta_interactive( 0 ); |
$gpg->options->meta_interactive( 0 ); |
| 624 |
($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 625 |
$gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] ); |
$gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] ); |
| 626 |
$pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]); |
my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]); |
| 627 |
($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 628 |
waitpid $pid, 0; |
waitpid $pid, 0; |
| 629 |
if ($stdout eq '') { |
if ($stdout eq '') { |
| 630 |
warn ("No data from gpg for list-key $keyid\n"); |
warn ("No data from gpg for list-key $keyid\n"); |
| 652 |
while (1) { |
while (1) { |
| 653 |
my $this_uid_text = ''; |
my $this_uid_text = ''; |
| 654 |
$uid_number++; |
$uid_number++; |
| 655 |
info("Doing key $keyid, uid $uid_number"); |
debug("Doing key $keyid, uid $uid_number"); |
| 656 |
|
|
| 657 |
# import into temporary gpghome |
# import into temporary gpghome |
| 658 |
############################### |
############################### |
| 721 |
next; |
next; |
| 722 |
}; |
}; |
| 723 |
unless ($have_one) { |
unless ($have_one) { |
| 724 |
|
debug("Uid ".($uid_number-1)." was the last, there is no $uid_number."); |
| 725 |
info("key $keyid done."); |
info("key $keyid done."); |
| 726 |
last; |
last; |
| 727 |
}; |
}; |
| 751 |
while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) { |
while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) { |
| 752 |
# sig:?::17:EA2199412477CAF8:1058095214:::::13x: |
# sig:?::17:EA2199412477CAF8:1058095214:::::13x: |
| 753 |
my @sigline = grep { /^sig/ } (split /\n/, $stdout); |
my @sigline = grep { /^sig/ } (split /\n/, $stdout); |
| 754 |
|
$stdout =~ s/\n/\\n/g; |
| 755 |
|
notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX |
| 756 |
my $line = pop @sigline; |
my $line = pop @sigline; |
| 757 |
my $answer = "no"; |
my $answer = "no"; |
| 758 |
if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance |
if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance |
| 759 |
|
debug("[sigremoval] doing line $line."); |
| 760 |
my ($dummy1, $dummy2, $dummy3, $dummy4, $signer, $created, $dummy7, $dummy8, $dummy9) = split /:/, $line; |
my ($dummy1, $dummy2, $dummy3, $dummy4, $signer, $created, $dummy7, $dummy8, $dummy9) = split /:/, $line; |
| 761 |
if ($signer eq $longkeyid) { |
if ($signer eq $longkeyid) { |
| 762 |
|
debug("[sigremoval] selfsig ($signer)."); |
| 763 |
$answer = "no"; |
$answer = "no"; |
| 764 |
} elsif (grep { $signer eq $_ } @{$CONFIG{'keyid'}}) { |
} elsif (grep { $signer eq $_ } @{$CONFIG{'keyid'}}) { |
| 765 |
|
debug("[sigremoval] signed by us ($signer)."); |
| 766 |
$answer = "no"; |
$answer = "no"; |
| 767 |
$signed_by_me = $signed_by_me > $created ? $signed_by_me : $created; |
$signed_by_me = $signed_by_me > $created ? $signed_by_me : $created; |
| 768 |
} else { |
} else { |
| 769 |
|
debug("[sigremoval] not interested in that sig ($signer)."); |
| 770 |
$answer = "yes"; |
$answer = "yes"; |
| 771 |
}; |
}; |
| 772 |
|
} else { |
| 773 |
|
debug("[sigremoval] no sig line here, only got: ".$stdout); |
| 774 |
}; |
}; |
| 775 |
($stdout, $stderr, $status) = |
($stdout, $stderr, $status) = |
| 776 |
readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1); |
readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1); |
| 792 |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
| 793 |
-d $keydir || mkpath($keydir , 0, 0700) or die ("Cannot create $keydir $!\n"); |
-d $keydir || mkpath($keydir , 0, 0700) or die ("Cannot create $keydir $!\n"); |
| 794 |
|
|
| 795 |
my $keyfile = "$keydir/$longkeyid.key.$uid_number.$this_uid_text.asc"; |
my $keyfile = "$keydir/$longkeyid.key.$uid_number.".sanitize_uid($this_uid_text).".asc"; |
| 796 |
open (KEY, ">$keyfile") or die ("Cannot open $keyfile\n"); |
open (KEY, ">$keyfile") or die ("Cannot open $keyfile\n"); |
| 797 |
print KEY $asciikey; |
print KEY $asciikey; |
| 798 |
close KEY; |
close KEY; |
| 808 |
if (scalar @UIDS == 0) { |
if (scalar @UIDS == 0) { |
| 809 |
info("found no signed uids for $keyid"); |
info("found no signed uids for $keyid"); |
| 810 |
} else { |
} else { |
| 811 |
my @attached ; |
next if $opt{M}; # do not send mail |
| 812 |
|
|
| 813 |
|
my @attached; |
| 814 |
for my $uid (@UIDS) { |
for my $uid (@UIDS) { |
| 815 |
|
trace("UID: $uid->{'text'}\n"); |
| 816 |
unless ($uid->{'text'} =~ /@/) { |
unless ($uid->{'text'} =~ /@/) { |
| 817 |
my $attach = ask("UID $uid->{'text'} is no email address, attach it to every email sent?", 1); |
my $attach = ask("UID $uid->{'text'} is no email address, attach it to every email sent?", 1); |
| 818 |
push @attached, $uid; |
push @attached, $uid if $attach; |
| 819 |
}; |
}; |
| 820 |
}; |
}; |
| 821 |
|
|
| 824 |
if ($uid->{'text'} =~ /@/) { |
if ($uid->{'text'} =~ /@/) { |
| 825 |
my $address = $uid->{'text'}; |
my $address = $uid->{'text'}; |
| 826 |
$address =~ s/.*<(.*)>.*/$1/; |
$address =~ s/.*<(.*)>.*/$1/; |
| 827 |
my $send = ask("Send mail to '$address' for $uid->{'text'}?", 1); |
if ($opt{m} or ask("Send mail to '$address' for $uid->{'text'}?", 1)) { |
|
if ($send) { |
|
| 828 |
my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached); |
my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached); |
| 829 |
|
|
| 830 |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
| 831 |
my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".$uid->{'text'}; |
my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid($uid->{'text'}); |
| 832 |
open (KEY, ">$mailfile") or die ("Cannot open $mailfile\n"); |
open (KEY, ">$mailfile") or die ("Cannot open $mailfile\n"); |
| 833 |
print KEY $mail; |
print KEY $mail; |
| 834 |
close KEY; |
close KEY; |