3 # caff -- CA - Fire and Forget
6 # Copyright (c) 2004, 2005 Peter Palfrader <peter@palfrader.org>
10 # Redistribution and use in source and binary forms, with or without
11 # modification, are permitted provided that the following conditions
13 # 1. Redistributions of source code must retain the above copyright
14 # notice, this list of conditions and the following disclaimer.
15 # 2. Redistributions in binary form must reproduce the above copyright
16 # notice, this list of conditions and the following disclaimer in the
17 # documentation and/or other materials provided with the distribution.
18 # 3. The name of the author may not be used to endorse or promote products
19 # derived from this software without specific prior written permission.
21 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 # (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.
36 caff -- CA - Fire and Forget
42 =item B<caff> [-mMR] [-u I<yourkeyid>] I<keyid> [I<keyid> ..]
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.
58 =item B<-m>, B<--mail>
60 Send mail after signing. Default is to ask the user for each uid.
62 =item B<-M>, B<--no-mail>
64 Do not send mail after signing. Default is to ask the user for each uid.
66 =item B<-R>, B<--no-download>
68 Do not retrieve the key to be signed from a keyserver.
70 =item B<-u> I<yourkeyid>, B<--local-user> I<yourkeyid>
72 Select the key that is used for signing, in case you have more than one key.
80 =item $HOME/.caffrc - configuration file
84 =head1 CONFIGURATION FILE OPTIONS
86 The configuration file is a perl script that sets values in the hash B<%CONFIG>.
90 $CONFIG{owner} = q{Peter Palfrader};
91 $CONFIG{email} = q{peter@palfrader.org};
92 $CONFIG{keyid} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ];
98 =item B<caffhome> [string]
100 Base directory for the files caff stores. Default: B<$HOME/.caff/>.
102 =item B<owner> [string]
104 Your name. B<REQUIRED>.
106 =item B<email> [string]
108 Your email address, used in From: lines. B<REQUIRED>.
110 =item B<keyid> [list of keyids]
112 A list of your keys. This is used to determine which signatures to keep
113 in the pruning step. If you select a key using B<-u> it has to be in
114 this list. B<REQUIRED>.
116 =item B<export-sig-age> [seconds]
118 Don't export UIDs by default, on which your latest signature is older
119 than this age. Default: B<24*60*60> (i.e. one day).
121 =item B<keyserver> [string]
123 Keyserver to download keys from. Default: B<subkeys.pgp.net>.
125 =item B<gpg> [string]
127 Path to the GnuPG binary. Default: B<gpg>.
129 =item B<gpg-sign> [string]
131 Path to the GnuPG binary which is used to sign keys. Default: what
134 =item B<gpg-delsig> [string]
136 Path to the GnuPG binary which is used to split off signatures. This was
137 needed while the upstream GnuPG was not fixed. Default: what B<gpg>
140 =item B<secret-keyring> [string]
142 Path to your secret keyring. Default: B<$HOME/.gnupg/secring.gpg>.
144 =item B<also-encrypt-to> [keyid]
146 An additional keyid to encrypt messages to. Default: none.
148 =item B<no-download> [boolean]
150 If true, then skip the step of fetching keys from the keyserver.
153 =item B<no-sign> [boolean]
155 If true, then skip the signing step. Default: B<0>.
157 =item B<mail-template> [string]
159 Email template which is used as the body text for the email sent out.
160 instead of the default text if specified. The following perl variables
161 can be used in the template:
165 =item B<{owner}> [string]
167 Your name as specified in the L<B<owner>|/item_owner__5bstring_5d> setting.
169 =item B<{key}> [string]
171 The keyid of the key you signed.
173 =item B<{@uids}> [array]
175 The UIDs for which signatures are included in the mail.
183 Peter Palfrader <peter@palfrader.org>
187 http://pgp-tools.alioth.debian.org/
195 use File
::Temp
qw{tempdir
};
201 use GnuPG
::Interface
;
204 my $REVISION = '$Rev$';
205 my ($REVISION_NUMER) = $REVISION =~ /(\d+)/;
206 my $VERSION = "0.0.0.$REVISION_NUMER";
209 my $config = $ENV{'HOME'} . '/.caffrc';
210 -f
$config or die "No file $config present. See caff(1).\n";
211 unless (scalar eval `cat $config`) {
212 die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR;
215 $CONFIG{'caffhome'}=$ENV{'HOME'}.'/.caff' unless defined $CONFIG{'caffhome'};
216 die ("owner is not defined.\n") unless defined $CONFIG{'owner'};
217 die ("email is not defined.\n") unless defined $CONFIG{'email'};
218 die ("keyid is not defined.\n") unless defined $CONFIG{'keyid'};
219 die ("keyid is not an array ref\n") unless (ref $CONFIG{'keyid'} eq 'ARRAY');
220 for my $keyid (@
{$CONFIG{'keyid'}}) {
221 $keyid =~ /^[A-Fa-z0-9]{16}$/ or die ("key $keyid is not a long (16 digit) keyid.\n");
223 @
{$CONFIG{'keyid'}} = map { uc } @
{$CONFIG{'keyid'}};
224 $CONFIG{'export-sig-age'}= 24*60*60 unless defined $CONFIG{'export-sig-age'};
225 $CONFIG{'keyserver'} = 'subkeys.pgp.net' unless defined $CONFIG{'keyserver'};
226 $CONFIG{'gpg'} = 'gpg' unless defined $CONFIG{'gpg'};
227 $CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'};
228 $CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'};
229 $CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'};
230 $CONFIG{'no-download'} = 0 unless defined $CONFIG{'no-download'};
231 $CONFIG{'no-sign'} = 0 unless defined $CONFIG{'no-sign'};
232 $CONFIG{'mail-template'} = <<'EOM' unless defined $CONFIG{'mail-template'};
235 please find attached the user id{(scalar @uids >= 2 ? 's' : '')}.
236 {foreach $uid (@uids) {
237 $OUT .= "\t".$uid."\n";
238 };} of your key {$key} signed by me.
240 Note that I did not upload your key to any keyservers. If you want this
241 new signature to be available to others, please upload it yourself.
242 With GnuPG this can be done using
243 gpg --keyserver subkeys.pgp.net --send-key {$key}
245 If you have any questions, don't hesitate to ask.
254 print "[NOTICE] $line\n";
258 print "[INFO] $line\n";
262 #print "[DEBUG] $line\n";
266 #print "[trace] $line\n";
270 #print "[trace2] $line\n";
275 stdin
=> IO
::Handle
->new(),
276 stdout
=> IO
::Handle
->new(),
277 stderr
=> IO
::Handle
->new(),
278 status
=> IO
::Handle
->new() );
279 my $handles = GnuPG
::Handles
->new( %fds );
280 return ($fds{'stdin'}, $fds{'stdout'}, $fds{'stderr'}, $fds{'status'}, $handles);
283 sub readwrite_gpg
($$$$$%) {
284 my ($in, $inputfd, $stdoutfd, $stderrfd, $statusfd, %options) = @_;
286 trace
("Entering readwrite_gpg.");
288 my ($first_line, undef) = split /\n/, $in;
289 debug
("readwrite_gpg sends ".(defined $first_line ?
$first_line : "<nothing>"));
291 local $INPUT_RECORD_SEPARATOR = undef;
292 my $sout = IO
::Select
->new();
293 my $sin = IO
::Select
->new();
296 trace
("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ?
$statusfd : 'undef').".");
298 $inputfd->blocking(0);
299 $stdoutfd->blocking(0);
300 $statusfd->blocking(0) if defined $statusfd;
301 $stderrfd->blocking(0);
302 $sout->add($stdoutfd);
303 $sout->add($stderrfd);
304 $sout->add($statusfd) if defined $statusfd;
307 my ($stdout, $stderr, $status) = ("", "", "");
308 my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'};
309 trace
("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches;
311 my $readwrote_stuff_this_time = 0;
312 my $do_not_wait_on_select = 0;
313 my ($readyr, $readyw, $written);
314 while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) {
315 if (defined $exitwhenstatusmatches) {
316 if ($status =~ /$exitwhenstatusmatches/m) {
317 trace
("readwrite_gpg found match on $exitwhenstatusmatches");
318 if ($readwrote_stuff_this_time) {
319 trace
("read/write some more\n");
320 $do_not_wait_on_select = 1;
322 trace
("that's it in our while loop.\n");
328 $readwrote_stuff_this_time = 0;
329 trace
("select waiting for ".($sout->count())." fds.");
330 ($readyr, $readyw, undef) = IO
::Select
::select($sout, $sin, undef, $do_not_wait_on_select ?
0 : 1);
331 trace
("ready: write: ".(defined $readyw ?
scalar @
$readyw : 0 )."; read: ".(defined $readyr ?
scalar @
$readyr : 0));
332 for my $wfd (@
$readyw) {
333 $readwrote_stuff_this_time = 1;
334 if (length($in) != $offset) {
335 trace
("writing to $wfd.");
336 $written = $wfd->syswrite($in, length($in) - $offset, $offset);
339 if ($offset == length($in)) {
340 trace
("writing to $wfd done.");
341 unless ($options{'nocloseinput'}) {
343 trace
("$wfd closed.");
350 next unless (defined(@
$readyr)); # Wait some more.
352 for my $rfd (@
$readyr) {
353 $readwrote_stuff_this_time = 1;
355 trace
("reading from $rfd done.");
360 trace
("reading from $rfd.");
361 if ($rfd == $stdoutfd) {
363 trace2
("stdout is now $stdout\n================");
366 if (defined $statusfd && $rfd == $statusfd) {
368 trace2
("status is now $status\n================");
371 if ($rfd == $stderrfd) {
373 trace2
("stderr is now $stderr\n================");
378 trace
("readwrite_gpg done.");
379 return ($stdout, $stderr, $status);
383 my ($question, $default) = @_;
386 print $question,' ',($default ?
'[Y/n]' : '[y/N]'), ' ';
389 last if ((defined $answer) && (length $answer <= 1));
393 my $result = $default;
394 $result = 1 if $answer =~ /y/i;
395 $result = 0 if $answer =~ /n/i;
403 my $KEYEDIT_PROMPT = '^\[GNUPG:\] GET_LINE keyedit.prompt';
404 my $KEYEDIT_DELUID_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.uid.okay';
405 my $KEYEDIT_DELSIG_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.delsig';
406 my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = '^\[GNUPG:\] (GET_BOOL keyedit.delsig|GET_LINE keyedit.prompt)';
407 my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey';
410 my $USER_AGENT = "caff $VERSION - (c) 2004, 2005 Peter Palfrader";
412 my $KEYSBASE = $CONFIG{'caffhome'}.'/keys';
413 my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome';
415 -d
$KEYSBASE || mkpath
($KEYSBASE , 0, 0700) or die ("Cannot create $KEYSBASE: $!\n");
416 -d
$GNUPGHOME || mkpath
($GNUPGHOME, 0, 0700) or die ("Cannot create $GNUPGHOME: $!\n");
419 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($NOW);
420 my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
425 print $fd "caff $VERSION - (c) 2004, 2005 Peter Palfrader\n";
429 my ($fd, $exitcode) = @_;
431 print $fd "Usage: $PROGRAM_NAME [-mMR] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
432 print $fd "Consult the manual page for more information.\n";
437 # export key $keyid from $gnupghome
440 my ($gnupghome, $keyid) = @_;
442 my $gpg = GnuPG
::Interface
->new();
443 $gpg->call( $CONFIG{'gpg'} );
444 $gpg->options->hash_init(
445 'homedir' => $gnupghome,
447 $gpg->options->meta_interactive( 0 );
448 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
449 my $pid = $gpg->export_keys(handles
=> $handles, command_args
=> [ $keyid ]);
450 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
457 # import a key from the scalar $asciikey into a gpg homedirectory in $tempdir
460 ($gnupghome, $asciikey) = @_;
462 my $gpg = GnuPG
::Interface
->new();
463 $gpg->call( $CONFIG{'gpg'} );
464 $gpg->options->hash_init( 'homedir' => $gnupghome );
465 $gpg->options->meta_interactive( 0 );
466 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
467 my $pid = $gpg->import_keys(handles
=> $handles);
468 my ($stdout, $stderr, $status) = readwrite_gpg
($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd);
471 if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
479 # Send an email to $address. If $can_encrypt is true then the mail
480 # will be PGP/MIME encrypted to $longkeyid.
482 # $longkeyid, $uid, and @attached will be used in the email and the template.
484 #send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
485 sub send_mail
($$$@
) {
486 my ($address, $can_encrypt, $key_id, @keys) = @_;
488 my $template = Text
::Template
->new(TYPE
=> 'STRING', SOURCE
=> $CONFIG{'mail-template'})
489 or die "Error creating template: $Text::Template::ERROR";
492 for my $key (@keys) {
493 push @uids, $key->{'text'};
495 my $message = $template->fill_in(HASH
=> { key
=> $key_id,
497 owner
=> $CONFIG{'owner'}})
498 or die "Error filling template in: $Text::Template::ERROR";
500 my $message_entity = MIME
::Entity
->build(
501 Type
=> "text/plain",
503 Disposition
=> 'inline',
507 for my $key (@keys) {
508 $message_entity->attach(
509 Type
=> "application/pgp-keys",
510 Disposition
=> 'attachment',
512 Description
=> "PGP Key 0x$key_id, uid ".($key->{'text'}).' ('.($key->{'serial'}).')',
513 Data
=> $key->{'key'},
514 Filename
=> "0x$key_id.".$key->{'serial'}.".asc");
518 my $message = $message_entity->stringify();
520 my $gpg = GnuPG
::Interface
->new();
521 $gpg->call( $CONFIG{'gpg'} );
522 $gpg->options->hash_init( 'homedir' => $GNUPGHOME,
523 'extra_args' => '--always-trust',
525 $gpg->options->meta_interactive( 0 );
526 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
527 $gpg->options->push_recipients( $key_id );
528 $gpg->options->push_recipients( $CONFIG{'also-encrypt-to'} ) if defined $CONFIG{'also-encrypt-to'};
529 my $pid = $gpg->encrypt(handles
=> $handles);
530 my ($stdout, $stderr, $status) = readwrite_gpg
($message, $inputfd, $stdoutfd, $stderrfd, $statusfd);
533 warn ("No data from gpg for list-key $key_id\n");
538 $message_entity = MIME
::Entity
->build(
539 Type
=> 'multipart/encrypted; protocol="application/pgp-encrypted"');
541 $message_entity->attach(
542 Type
=> "application/pgp-encrypted",
543 Disposition
=> 'attachment',
545 Data
=> "Version: 1\n");
547 $message_entity->attach(
548 Type
=> "application/octet-stream",
549 Filename
=> 'msg.asc',
550 Disposition
=> 'inline',
555 $message_entity->head->add("Subject", "Your signed PGP key 0x$key_id");
556 $message_entity->head->add("To", $address);
557 $message_entity->head->add("From", '"'.$CONFIG{'owner'}.'" <'.$CONFIG{'email'}.'>');
558 $message_entity->head->add("User-Agent", $USER_AGENT);
559 $message_entity->send();
560 $message_entity->stringify();
564 # clean up a UID so that it can be used on the FS.
566 sub sanitize_uid
($) {
570 $good_uid =~ tr
#/:\\#_#;
571 trace2
("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid;
575 sub delete_signatures
($$$$$) {
576 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $keyids) =@_;
578 my ($stdout, $stderr, $status) =
579 readwrite_gpg
("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_DELSIG_PROMPT, nocloseinput
=> 1);
581 while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) {
582 # sig:?::17:EA2199412477CAF8:1058095214:::::13x:
583 my @sigline = grep { /^sig/ } (split /\n/, $stdout);
584 $stdout =~ s/\n/\\n/g;
585 notice
("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX
586 my $line = pop @sigline;
588 if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance
589 debug
("[sigremoval] doing line $line.");
590 my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line;
591 if ($signer eq $longkeyid) {
592 debug
("[sigremoval] selfsig ($signer).");
594 } elsif (grep { $signer eq $_ } @
{$keyids}) {
595 debug
("[sigremoval] signed by us ($signer).");
597 $signed_by_me = $signed_by_me > $created ?
$signed_by_me : $created;
599 debug
("[sigremoval] not interested in that sig ($signer).");
603 debug
("[sigremoval] no sig line here, only got: ".$stdout);
605 ($stdout, $stderr, $status) =
606 readwrite_gpg
($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput
=> 1);
616 Getopt
::Long
::config
('bundling');
618 '-h' => \
$params->{'help'},
619 '--help' => \
$params->{'help'},
620 '--version' => \
$params->{'version'},
621 '-V' => \
$params->{'version'},
622 '-u=s' => \
$params->{'local-user'},
623 '--local-user=s' => \
$params->{'local-user'},
624 '-m' => \
$params->{'mail'},
625 '--mail' => \
$params->{'mail'},
626 '-M' => \
$params->{'no-mail'},
627 '--no-mail' => \
$params->{'no-mail'},
628 '-R' => \
$params->{'no-download'},
629 '--no-download' => \
$params->{'no-download'},
633 if ($params->{'help'}) {
636 if ($params->{'version'}) {
640 usage
(\
*STDERR
, 1) unless scalar @ARGV >= 1;
644 if ($params->{'local-user'}) {
645 $USER = $params->{'local-user'};
647 unless ($USER =~ /^[A-Za-z0-9]{8,8}([A-Za-z0-9]{8})?$/) {
648 print STDERR
"-u $USER is not a keyid.\n";
654 for my $keyid (@ARGV) {
656 unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8}|[A-Za-z0-9]{32})?$/) {
657 print STDERR
"$keyid is not a keyid.\n";
660 push @KEYIDS, uc($keyid);
663 $CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-download'};
664 $CONFIG{'no-mail'} = $params->{'no-mail'} if defined $params->{'no-mail'};
665 $CONFIG{'mail'} = $params->{'mail'} if defined $params->{'mail'};
671 my $gpg = GnuPG
::Interface
->new();
672 $gpg->call( $CONFIG{'gpg'} );
673 $gpg->options->hash_init(
674 'homedir' => $GNUPGHOME,
675 'extra_args' => '--keyserver='.$CONFIG{'keyserver'} );
676 $gpg->options->meta_interactive( 0 );
677 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
678 $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] );
679 my $pid = $gpg->list_public_keys(handles
=> $handles, command_args
=> $CONFIG{'keyid'});
680 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
683 warn ("No data from gpg for list-key\n");
686 foreach my $keyid (@
{$CONFIG{'keyid'}}) {
687 unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) {
688 info
("Importing $keyid");
689 system "gpg --export $keyid | gpg --import --homedir $GNUPGHOME";
693 #############################
694 # receive keys from keyserver
695 #############################
698 if ($CONFIG{'no-download'}) {
699 @keyids_ok = @KEYIDS;
701 my $gpg = GnuPG
::Interface
->new();
702 $gpg->call( $CONFIG{'gpg'} );
703 $gpg->options->hash_init(
704 'homedir' => $GNUPGHOME,
705 'extra_args' => '--keyserver='.$CONFIG{'keyserver'} );
706 $gpg->options->meta_interactive( 0 );
707 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
709 my @local_keyids = @KEYIDS;
710 for my $keyid (@local_keyids) {
711 info
("fetching $keyid...");
712 my $pid = $gpg->recv_keys(handles
=> $handles, command_args
=> [ $keyid ]);
713 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
716 # [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F
719 # [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039
721 for my $line (split /\n/, $status) {
722 if ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{40})/) {
723 my $imported_key = $1;
724 if ($keyid ne $imported_key &&
725 $keyid ne substr($imported_key, -16) &&
726 $keyid ne substr($imported_key, -8)) {
727 warn("Imported unexpected key. expected: $keyid; got: $imported_key.\n");
730 push @keyids_ok, $keyid;
734 } elsif ($line =~ /^\[GNUPG:\] NODATA/) {
735 push @keyids_failed, $keyid;
742 notice
("Huh, what's up with $keyid?");
743 push @keyids_failed, $keyid;
747 die ("Still keys in \@KEYIDS. This should not happen.") if scalar @KEYIDS;
748 notice
("Import failed for: ". (join ' ', @keyids_failed).".") if scalar @keyids_failed;
754 unless ($CONFIG{'no-sign'}) {
755 info
("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key");
756 for my $keyid (@keyids_ok) {
758 push @command, $CONFIG{'gpg-sign'};
759 push @command, '--local-user', $USER if (defined $USER);
760 push @command, "--homedir=$GNUPGHOME";
761 push @command, '--secret-keyring', $CONFIG{'secret-keyring'};
762 push @command, '--edit', $keyid;
763 push @command, 'sign';
764 print join(' ', @command),"\n";
773 for my $keyid (@keyids_ok) {
776 my $gpg = GnuPG
::Interface
->new();
777 $gpg->call( $CONFIG{'gpg'} );
778 $gpg->options->hash_init( 'homedir' => $GNUPGHOME );
779 $gpg->options->meta_interactive( 0 );
780 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
781 $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] );
782 my $pid = $gpg->list_public_keys(handles
=> $handles, command_args
=> [ $keyid ]);
783 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
786 warn ("No data from gpg for list-key $keyid\n");
789 my @publine = grep { /^pub/ } (split /\n/, $stdout);
790 if (scalar @publine == 0) {
791 warn ("No public keys found with list-key $keyid (note that caff uses its own keyring in $GNUPGHOME).\n");
794 my (undef, undef, undef, undef, $longkeyid, undef, undef, undef, undef, undef, undef, $flags) = split /:/, pop @publine;
795 if (scalar @publine > 0) {
796 warn ("More than one key matched $keyid. Try to specify the long keyid or fingerprint\n");
799 unless (defined $longkeyid) {
800 warn ("Didn't find public keyid in --list-key of key $keyid.\n");
803 unless (defined $flags) {
804 warn ("Didn't find flags in --list-key of key $keyid.\n");
807 my $can_encrypt = $flags =~ /E/;
811 my $asciikey = export_key
($GNUPGHOME, $keyid);
812 if ($asciikey eq '') {
813 warn ("No data from gpg for export $keyid\n");
820 my $this_uid_text = '';
822 debug
("Doing key $keyid, uid $uid_number");
823 my $tempdir = tempdir
( "caff-$keyid-XXXXX", DIR
=> '/tmp/', CLEANUP
=> 1);
825 # import into temporary gpghome
826 ###############################
827 my $result = import_key
($tempdir, $asciikey);
829 warn ("Could not import $keyid into temporary gnupg.\n");
835 $gpg = GnuPG
::Interface
->new();
836 $gpg->call( $CONFIG{'gpg-delsig'} );
837 $gpg->options->hash_init(
838 'homedir' => $tempdir,
839 'extra_args' => [ '--with-colons', '--fixed-list-mode', '--command-fd=0', '--no-tty' ] );
840 ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
841 $pid = $gpg->wrap_call(
842 commands
=> [ '--edit' ],
843 command_args
=> [ $keyid ],
844 handles
=> $handles );
846 debug
("Starting edit session");
847 ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
851 my $number_of_subkeys = 0;
856 debug
("Parsing stdout output.");
857 for my $line (split /\n/, $stdout) {
858 debug
("Checking line $line");
859 my ($type, undef, undef, undef, undef, undef, undef, undef, undef, $uidtext) = split /:/, $line;
860 if ($type eq 'sub') {
861 $number_of_subkeys++;
863 next unless ($type eq 'uid' || $type eq 'uat');
864 debug
("line is interesting.");
865 if ($uid_number != $i) {
866 debug
("mark for deletion.");
867 readwrite_gpg
("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
872 $this_uid_text = ($type eq 'uid') ?
$uidtext : '[attribute]';
873 $is_uat = $type eq 'uat';
877 debug
("Parsing stdout output done.");
879 debug
("Uid ".($uid_number-1)." was the last, there is no $uid_number.");
880 info
("key $keyid done.");
884 notice
("Can't handle attribute userid of key $keyid.");
888 debug
("need to delete a few uids.");
889 readwrite_gpg
("deluid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_DELUID_PROMPT, nocloseinput
=> 1);
890 readwrite_gpg
("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
895 if ($number_of_subkeys > 0) {
896 for (my $i=1; $i<=$number_of_subkeys; $i++) {
897 readwrite_gpg
("key $i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
899 readwrite_gpg
("delkey\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_DELSUBKEY_PROMPT, nocloseinput
=> 1);
900 readwrite_gpg
("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
905 my $signed_by_me = 0;
906 readwrite_gpg
("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
907 delete_signatures
($inputfd, $stdoutfd, $stderrfd, $statusfd, $CONFIG{'keyid'});
911 readwrite_gpg
("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd);
914 my $asciikey = export_key
($tempdir, $keyid);
915 if ($asciikey eq '') {
916 warn ("No data from gpg for export $keyid\n");
921 if ($NOW - $signed_by_me > $CONFIG{'export-sig-age'} ) {
922 my $write = ask
("Signature on $this_uid_text is old. Export?", 0);
925 my $keydir = "$KEYSBASE/$DATE_STRING";
926 -d
$keydir || mkpath
($keydir , 0, 0700) or die ("Cannot create $keydir $!\n");
928 my $keyfile = "$keydir/$longkeyid.key.$uid_number.".sanitize_uid
($this_uid_text).".asc";
929 open (KEY
, ">$keyfile") or die ("Cannot open $keyfile: $!\n");
933 push @UIDS, { text
=> $this_uid_text, key
=> $asciikey, serial
=> $uid_number, "is_uat" => $is_uat };
935 info
("$longkeyid $uid_number $this_uid_text done.");
937 info
("$longkeyid $uid_number $this_uid_text is not signed by me, not writing.");
941 if (scalar @UIDS == 0) {
942 info
("found no signed uids for $keyid");
944 next if $CONFIG{'no-mail'}; # do not send mail
947 for my $uid (@UIDS) {
948 trace
("UID: $uid->{'text'}\n");
949 if ($uid->{'is_uat'}) {
950 my $attach = ask
("UID $uid->{'text'} is an attribute UID, attach it to every email sent?", 1);
951 push @attached, $uid if $attach;
952 } elsif ($uid->{'text'} !~ /@/) {
953 my $attach = ask
("UID $uid->{'text'} is no email address, attach it to every email sent?", 1);
954 push @attached, $uid if $attach;
958 notice
("Key has no encryption capabilities, mail will be sent unencrypted") unless $can_encrypt;
959 for my $uid (@UIDS) {
960 if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) {
961 my $address = $uid->{'text'};
962 $address =~ s/.*<(.*)>.*/$1/;
963 if ($CONFIG{'mail'} or ask
("Send mail to '$address' for $uid->{'text'}?", 1)) {
964 my $mail = send_mail
($address, $can_encrypt, $longkeyid, $uid, @attached);
966 my $keydir = "$KEYSBASE/$DATE_STRING";
967 my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid
($uid->{'text'});
968 open (KEY
, ">$mailfile") or die ("Cannot open $mailfile: $!\n");