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.
60 Send/do not send mail after signing. Default is to ask the user for each uid.
64 Do not retrieve the key to be signed from a keyserver.
66 =item B<-u> I<yourkeyid>
68 Select the key that is used for signing, in case you have more than one key.
76 =item $HOME/.caffrc - configuration file
80 =head1 CONFIGURATION FILE OPTIONS
82 The configuration file is a perl script that sets values in the hash B<%CONFIG>.
86 $CONFIG{owner} = q{Peter Palfrader};
87 $CONFIG{email} = q{peter@palfrader.org};
88 $CONFIG{keyid} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ];
94 =item B<caffhome> [string]
96 Base directory for the files caff stores. Default: B<$HOME/.caff/>.
98 =item B<owner> [string]
100 Your name. B<REQUIRED>.
102 =item B<email> [string]
104 Your email address, used in From: lines. B<REQUIRED>.
106 =item B<keyid> [list of keyids]
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>.
112 =item B<export-sig-age> [seconds]
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).
117 =item B<keyserver> [string]
119 Keyserver to download keys from. Default: B<subkeys.pgp.net>.
121 =item B<gpg> [string]
123 Path to the GnuPG binary. Default: B<gpg>.
125 =item B<gpg-sign> [string]
127 Path to the GnuPG binary which is used to sign keys. Default: what
130 =item B<gpg-delsig> [string]
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>
136 =item B<secret-keyring> [string]
138 Path to your secret keyring. Default: B<$HOME/.gnupg/secring.gpg>.
140 =item B<also-encrypt-to> [keyid]
142 An additional keyid to encrypt messages to. Default: none.
144 =item B<no-download> [boolean]
146 If true, then skip the step of fetching keys from the keyserver.
149 =item B<no-sign> [boolean]
151 If true, then skip the signing step. Default: B<0>.
153 =item B<mail-template> [string]
155 Email template which is used as the body text for the email sent out.
156 instead of the default text if specified. The following perl variables
157 can be used in the template:
161 =item B<{owner}> [string]
163 Your name as specified in the L<B<owner>|/item_owner__5bstring_5d> setting.
165 =item B<{key}> [string]
167 The keyid of the key you signed.
169 =item B<{@uids}> [array]
171 The UIDs for which signatures are included in the mail.
179 Peter Palfrader <peter@palfrader.org>
183 http://pgp-tools.alioth.debian.org/
191 use File
::Temp
qw{tempdir
};
197 use GnuPG
::Interface
;
200 my $REVISION = '$Rev$';
201 my ($REVISION_NUMER) = $REVISION =~ /(\d+)/;
202 my $VERSION = "0.0.0.$REVISION_NUMER";
205 my $config = $ENV{'HOME'} . '/.caffrc';
206 -f
$config or die "No file $config present. See caff(1).\n";
207 unless (scalar eval `cat $config`) {
208 die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR;
211 $CONFIG{'caffhome'}=$ENV{'HOME'}.'/.caff' unless defined $CONFIG{'caffhome'};
212 die ("owner is not defined.\n") unless defined $CONFIG{'owner'};
213 die ("email is not defined.\n") unless defined $CONFIG{'email'};
214 die ("keyid is not defined.\n") unless defined $CONFIG{'keyid'};
215 die ("keyid is not an array ref\n") unless (ref $CONFIG{'keyid'} eq 'ARRAY');
216 for my $keyid (@
{$CONFIG{'keyid'}}) {
217 $keyid =~ /^[A-Fa-z0-9]{16}$/ or die ("key $keyid is not a long (16 digit) keyid.\n");
219 @
{$CONFIG{'keyid'}} = map { uc } @
{$CONFIG{'keyid'}};
220 $CONFIG{'export-sig-age'}= 24*60*60 unless defined $CONFIG{'export-sig-age'};
221 $CONFIG{'keyserver'} = 'subkeys.pgp.net' unless defined $CONFIG{'keyserver'};
222 $CONFIG{'gpg'} = 'gpg' unless defined $CONFIG{'gpg'};
223 $CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'};
224 $CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'};
225 $CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'};
226 $CONFIG{'no-download'} = 0 unless defined $CONFIG{'no-download'};
227 $CONFIG{'no-sign'} = 0 unless defined $CONFIG{'no-sign'};
228 $CONFIG{'mail-template'} = <<'EOM' unless defined $CONFIG{'mail-template'};
231 please find attached the user id{(scalar @uids >= 2 ? 's' : '')}.
232 {foreach $uid (@uids) {
233 $OUT .= "\t".$uid."\n";
234 };} of your key {$key} signed by me.
236 Note that I did not upload your key to any keyservers. If you want this
237 new signature to be available to others, please upload it yourself.
238 With GnuPG this can be done using
239 gpg --keyserver subkeys.pgp.net --send-key {$key}
241 If you have any questions, don't hesitate to ask.
250 print "[NOTICE] $line\n";
254 print "[INFO] $line\n";
258 #print "[DEBUG] $line\n";
262 #print "[trace] $line\n";
266 #print "[trace2] $line\n";
271 stdin
=> IO
::Handle
->new(),
272 stdout
=> IO
::Handle
->new(),
273 stderr
=> IO
::Handle
->new(),
274 status
=> IO
::Handle
->new() );
275 my $handles = GnuPG
::Handles
->new( %fds );
276 return ($fds{'stdin'}, $fds{'stdout'}, $fds{'stderr'}, $fds{'status'}, $handles);
279 sub readwrite_gpg
($$$$$%) {
280 my ($in, $inputfd, $stdoutfd, $stderrfd, $statusfd, %options) = @_;
282 trace
("Entering readwrite_gpg.");
284 my ($first_line, undef) = split /\n/, $in;
285 debug
("readwrite_gpg sends ".(defined $first_line ?
$first_line : "<nothing>"));
287 local $INPUT_RECORD_SEPARATOR = undef;
288 my $sout = IO
::Select
->new();
289 my $sin = IO
::Select
->new();
292 trace
("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ?
$statusfd : 'undef').".");
294 $inputfd->blocking(0);
295 $stdoutfd->blocking(0);
296 $statusfd->blocking(0) if defined $statusfd;
297 $stderrfd->blocking(0);
298 $sout->add($stdoutfd);
299 $sout->add($stderrfd);
300 $sout->add($statusfd) if defined $statusfd;
303 my ($stdout, $stderr, $status) = ("", "", "");
304 my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'};
305 trace
("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches;
307 my $readwrote_stuff_this_time = 0;
308 my $do_not_wait_on_select = 0;
309 my ($readyr, $readyw, $written);
310 while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) {
311 if (defined $exitwhenstatusmatches) {
312 if ($status =~ /$exitwhenstatusmatches/m) {
313 trace
("readwrite_gpg found match on $exitwhenstatusmatches");
314 if ($readwrote_stuff_this_time) {
315 trace
("read/write some more\n");
316 $do_not_wait_on_select = 1;
318 trace
("that's it in our while loop.\n");
324 $readwrote_stuff_this_time = 0;
325 trace
("select waiting for ".($sout->count())." fds.");
326 ($readyr, $readyw, undef) = IO
::Select
::select($sout, $sin, undef, $do_not_wait_on_select ?
0 : 1);
327 trace
("ready: write: ".(defined $readyw ?
scalar @
$readyw : 0 )."; read: ".(defined $readyr ?
scalar @
$readyr : 0));
328 for my $wfd (@
$readyw) {
329 $readwrote_stuff_this_time = 1;
330 if (length($in) != $offset) {
331 trace
("writing to $wfd.");
332 $written = $wfd->syswrite($in, length($in) - $offset, $offset);
335 if ($offset == length($in)) {
336 trace
("writing to $wfd done.");
337 unless ($options{'nocloseinput'}) {
339 trace
("$wfd closed.");
346 next unless (defined(@
$readyr)); # Wait some more.
348 for my $rfd (@
$readyr) {
349 $readwrote_stuff_this_time = 1;
351 trace
("reading from $rfd done.");
356 trace
("reading from $rfd.");
357 if ($rfd == $stdoutfd) {
359 trace2
("stdout is now $stdout\n================");
362 if (defined $statusfd && $rfd == $statusfd) {
364 trace2
("status is now $status\n================");
367 if ($rfd == $stderrfd) {
369 trace2
("stderr is now $stderr\n================");
374 trace
("readwrite_gpg done.");
375 return ($stdout, $stderr, $status);
379 my ($question, $default) = @_;
382 print $question,' ',($default ?
'[Y/n]' : '[y/N]'), ' ';
385 last if ((defined $answer) && (length $answer <= 1));
389 my $result = $default;
390 $result = 1 if $answer =~ /y/i;
391 $result = 0 if $answer =~ /n/i;
399 my $KEYEDIT_PROMPT = '^\[GNUPG:\] GET_LINE keyedit.prompt';
400 my $KEYEDIT_DELUID_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.uid.okay';
401 my $KEYEDIT_DELSIG_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.delsig';
402 my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = '^\[GNUPG:\] (GET_BOOL keyedit.delsig|GET_LINE keyedit.prompt)';
403 my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey';
406 my $USER_AGENT = "caff $VERSION - (c) 2004, 2005 Peter Palfrader";
408 my $KEYSBASE = $CONFIG{'caffhome'}.'/keys';
409 my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome';
411 -d
$KEYSBASE || mkpath
($KEYSBASE , 0, 0700) or die ("Cannot create $KEYSBASE: $!\n");
412 -d
$GNUPGHOME || mkpath
($GNUPGHOME, 0, 0700) or die ("Cannot create $GNUPGHOME: $!\n");
415 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($NOW);
416 my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
420 print STDERR
"caff $VERSION - (c) 2004, 2005 Peter Palfrader\n";
421 print STDERR
"Usage: $PROGRAM_NAME [-mMR] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
426 my ($gnupghome, $keyid) = @_;
428 my $gpg = GnuPG
::Interface
->new();
429 $gpg->call( $CONFIG{'gpg'} );
430 $gpg->options->hash_init(
431 'homedir' => $gnupghome,
433 $gpg->options->meta_interactive( 0 );
434 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
435 my $pid = $gpg->export_keys(handles
=> $handles, command_args
=> [ $keyid ]);
436 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
442 #send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
443 sub send_mail
($$$@
) {
444 my ($address, $can_encrypt, $key_id, @keys) = @_;
446 my $template = Text
::Template
->new(TYPE
=> 'STRING', SOURCE
=> $CONFIG{'mail-template'})
447 or die "Error creating template: $Text::Template::ERROR";
450 for my $key (@keys) {
451 push @uids, $key->{'text'};
453 my $message = $template->fill_in(HASH
=> { key
=> $key_id,
455 owner
=> $CONFIG{'owner'}})
456 or die "Error filling template in: $Text::Template::ERROR";
458 my $message_entity = MIME
::Entity
->build(
459 Type
=> "text/plain",
461 Disposition
=> 'inline',
465 for my $key (@keys) {
466 $message_entity->attach(
467 Type
=> "application/pgp-keys",
468 Disposition
=> 'attachment',
470 Description
=> "PGP Key 0x$key_id, uid ".($key->{'text'}).' ('.($key->{'serial'}).')',
471 Data
=> $key->{'key'},
472 Filename
=> "0x$key_id.".$key->{'serial'}.".asc");
476 my $message = $message_entity->stringify();
478 my $gpg = GnuPG
::Interface
->new();
479 $gpg->call( $CONFIG{'gpg'} );
480 $gpg->options->hash_init( 'homedir' => $GNUPGHOME,
481 'extra_args' => '--always-trust',
483 $gpg->options->meta_interactive( 0 );
484 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
485 $gpg->options->push_recipients( $key_id );
486 $gpg->options->push_recipients( $CONFIG{'also-encrypt-to'} ) if defined $CONFIG{'also-encrypt-to'};
487 my $pid = $gpg->encrypt(handles
=> $handles);
488 my ($stdout, $stderr, $status) = readwrite_gpg
($message, $inputfd, $stdoutfd, $stderrfd, $statusfd);
491 warn ("No data from gpg for list-key $key_id\n");
496 $message_entity = MIME
::Entity
->build(
497 Type
=> 'multipart/encrypted; protocol="application/pgp-encrypted"');
499 $message_entity->attach(
500 Type
=> "application/pgp-encrypted",
501 Disposition
=> 'attachment',
503 Data
=> "Version: 1\n");
505 $message_entity->attach(
506 Type
=> "application/octet-stream",
507 Filename
=> 'msg.asc',
508 Disposition
=> 'inline',
513 $message_entity->head->add("Subject", "Your signed PGP key 0x$key_id");
514 $message_entity->head->add("To", $address);
515 $message_entity->head->add("From", '"'.$CONFIG{'owner'}.'" <'.$CONFIG{'email'}.'>');
516 $message_entity->head->add("User-Agent", $USER_AGENT);
517 $message_entity->send();
518 $message_entity->stringify();
521 sub sanitize_uid
($) {
525 $good_uid =~ tr
#/:\\#_#;
526 trace2
("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid;
534 getopts
('mMRu:', \
%opt);
536 usage
() unless scalar @ARGV >= 1;
540 unless ($USER =~ /^[A-Za-z0-9]{8,8}([A-Za-z0-9]{8})?$/) {
541 print STDERR
"-u $USER is not a keyid.\n";
546 for my $keyid (@ARGV) {
548 unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8}|[A-Za-z0-9]{32})?$/) {
549 print STDERR
"$keyid is not a keyid.\n";
552 push @KEYIDS, uc($keyid);
560 my $gpg = GnuPG
::Interface
->new();
561 $gpg->call( $CONFIG{'gpg'} );
562 $gpg->options->hash_init(
563 'homedir' => $GNUPGHOME,
564 'extra_args' => '--keyserver='.$CONFIG{'keyserver'} );
565 $gpg->options->meta_interactive( 0 );
566 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
567 $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] );
568 my $pid = $gpg->list_public_keys(handles
=> $handles, command_args
=> $CONFIG{'keyid'});
569 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
572 warn ("No data from gpg for list-key\n");
575 foreach my $keyid (@
{$CONFIG{'keyid'}}) {
576 unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) {
577 info
("Importing $keyid");
578 system "gpg --export $keyid | gpg --import --homedir $GNUPGHOME";
582 #############################
583 # receive keys from keyserver
584 #############################
587 if ($CONFIG{'no-download'} or $opt{R
}) {
588 @keyids_ok = @KEYIDS;
590 my $gpg = GnuPG
::Interface
->new();
591 $gpg->call( $CONFIG{'gpg'} );
592 $gpg->options->hash_init(
593 'homedir' => $GNUPGHOME,
594 'extra_args' => '--keyserver='.$CONFIG{'keyserver'} );
595 $gpg->options->meta_interactive( 0 );
596 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
598 my @local_keyids = @KEYIDS;
599 for my $keyid (@local_keyids) {
600 info
("fetching $keyid...");
601 my $pid = $gpg->recv_keys(handles
=> $handles, command_args
=> [ $keyid ]);
602 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
605 # [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F
608 # [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039
610 for my $line (split /\n/, $status) {
611 if ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{40})/) {
612 my $imported_key = $1;
613 if ($keyid ne $imported_key &&
614 $keyid ne substr($imported_key, -16) &&
615 $keyid ne substr($imported_key, -8)) {
616 warn("Imported unexpected key. expected: $keyid; got: $imported_key.\n");
619 push @keyids_ok, $keyid;
623 } elsif ($line =~ /^\[GNUPG:\] NODATA/) {
624 push @keyids_failed, $keyid;
631 notice
("Huh, what's up with $keyid?");
632 push @keyids_failed, $keyid;
636 die ("Still keys in \@KEYIDS. This should not happen.") if scalar @KEYIDS;
637 notice
("Import failed for: ". (join ' ', @keyids_failed).".") if scalar @keyids_failed;
643 unless ($CONFIG{'no-sign'}) {
644 info
("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key");
645 for my $keyid (@keyids_ok) {
647 push @command, $CONFIG{'gpg-sign'};
648 push @command, '--local-user', $USER if (defined $USER);
649 push @command, "--homedir=$GNUPGHOME";
650 push @command, '--secret-keyring', $CONFIG{'secret-keyring'};
651 push @command, '--edit', $keyid;
652 push @command, 'sign';
653 print join(' ', @command),"\n";
662 for my $keyid (@keyids_ok) {
665 my $gpg = GnuPG
::Interface
->new();
666 $gpg->call( $CONFIG{'gpg'} );
667 $gpg->options->hash_init( 'homedir' => $GNUPGHOME );
668 $gpg->options->meta_interactive( 0 );
669 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
670 $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] );
671 my $pid = $gpg->list_public_keys(handles
=> $handles, command_args
=> [ $keyid ]);
672 my ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
675 warn ("No data from gpg for list-key $keyid\n");
678 my @publine = grep { /^pub/ } (split /\n/, $stdout);
679 if (scalar @publine == 0) {
680 warn ("No public keys found with list-key $keyid (note that caff uses its own keyring in $GNUPGHOME).\n");
683 my (undef, undef, undef, undef, $longkeyid, undef, undef, undef, undef, undef, undef, $flags) = split /:/, pop @publine;
684 if (scalar @publine > 0) {
685 warn ("More than one key matched $keyid. Try to specify the long keyid or fingerprint\n");
688 unless (defined $longkeyid) {
689 warn ("Didn't find public keyid in --list-key of key $keyid.\n");
692 unless (defined $flags) {
693 warn ("Didn't find flags in --list-key of key $keyid.\n");
696 my $can_encrypt = $flags =~ /E/;
700 my $asciikey = export_key
($GNUPGHOME, $keyid);
701 if ($asciikey eq '') {
702 warn ("No data from gpg for export $keyid\n");
709 my $this_uid_text = '';
711 debug
("Doing key $keyid, uid $uid_number");
713 # import into temporary gpghome
714 ###############################
715 my $tempdir = tempdir
( "caff-$keyid-XXXXX", DIR
=> '/tmp/', CLEANUP
=> 1);
716 my $gpg = GnuPG
::Interface
->new();
717 $gpg->call( $CONFIG{'gpg'} );
718 $gpg->options->hash_init( 'homedir' => $tempdir );
719 $gpg->options->meta_interactive( 0 );
720 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
721 my $pid = $gpg->import_keys(handles
=> $handles);
722 my ($stdout, $stderr, $status) = readwrite_gpg
($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd);
725 if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
726 warn ("Could not import $keyid into temporary gnupg.\n");
732 $gpg = GnuPG
::Interface
->new();
733 $gpg->call( $CONFIG{'gpg-delsig'} );
734 $gpg->options->hash_init(
735 'homedir' => $tempdir,
736 'extra_args' => [ '--with-colons', '--fixed-list-mode', '--command-fd=0', '--no-tty' ] );
737 ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds
();
738 $pid = $gpg->wrap_call(
739 commands
=> [ '--edit' ],
740 command_args
=> [ $keyid ],
741 handles
=> $handles );
743 debug
("Starting edit session");
744 ($stdout, $stderr, $status) = readwrite_gpg
('', $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
748 my $number_of_subkeys = 0;
753 debug
("Parsing stdout output.");
754 for my $line (split /\n/, $stdout) {
755 debug
("Checking line $line");
756 my ($type, undef, undef, undef, undef, undef, undef, undef, undef, $uidtext) = split /:/, $line;
757 if ($type eq 'sub') {
758 $number_of_subkeys++;
760 next unless ($type eq 'uid' || $type eq 'uat');
761 debug
("line is interesting.");
762 if ($uid_number != $i) {
763 debug
("mark for deletion.");
764 readwrite_gpg
("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
769 $this_uid_text = ($type eq 'uid') ?
$uidtext : 'attribute';
770 $is_uat = $type eq 'uat';
774 debug
("Parsing stdout output done.");
776 notice
("Can't handle attribute userid of key $keyid.");
780 debug
("Uid ".($uid_number-1)." was the last, there is no $uid_number.");
781 info
("key $keyid done.");
785 debug
("need to delete a few uids.");
786 readwrite_gpg
("deluid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_DELUID_PROMPT, nocloseinput
=> 1);
787 readwrite_gpg
("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
792 if ($number_of_subkeys > 0) {
793 for (my $i=1; $i<=$number_of_subkeys; $i++) {
794 readwrite_gpg
("key $i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
796 readwrite_gpg
("delkey\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_DELSUBKEY_PROMPT, nocloseinput
=> 1);
797 readwrite_gpg
("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
802 my $signed_by_me = 0;
803 readwrite_gpg
("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_PROMPT, nocloseinput
=> 1);
804 ($stdout, $stderr, $status) =
805 readwrite_gpg
("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_DELSIG_PROMPT, nocloseinput
=> 1);
807 while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) {
808 # sig:?::17:EA2199412477CAF8:1058095214:::::13x:
809 my @sigline = grep { /^sig/ } (split /\n/, $stdout);
810 $stdout =~ s/\n/\\n/g;
811 notice
("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX
812 my $line = pop @sigline;
814 if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance
815 debug
("[sigremoval] doing line $line.");
816 my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line;
817 if ($signer eq $longkeyid) {
818 debug
("[sigremoval] selfsig ($signer).");
820 } elsif (grep { $signer eq $_ } @
{$CONFIG{'keyid'}}) {
821 debug
("[sigremoval] signed by us ($signer).");
823 $signed_by_me = $signed_by_me > $created ?
$signed_by_me : $created;
825 debug
("[sigremoval] not interested in that sig ($signer).");
829 debug
("[sigremoval] no sig line here, only got: ".$stdout);
831 ($stdout, $stderr, $status) =
832 readwrite_gpg
($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches
=> $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput
=> 1);
834 readwrite_gpg
("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd);
837 my $asciikey = export_key
($tempdir, $keyid);
838 if ($asciikey eq '') {
839 warn ("No data from gpg for export $keyid\n");
844 if ($NOW - $signed_by_me > $CONFIG{'export-sig-age'} ) {
845 my $write = ask
("Signature on $this_uid_text is old. Export?", 0);
848 my $keydir = "$KEYSBASE/$DATE_STRING";
849 -d
$keydir || mkpath
($keydir , 0, 0700) or die ("Cannot create $keydir $!\n");
851 my $keyfile = "$keydir/$longkeyid.key.$uid_number.".sanitize_uid
($this_uid_text).".asc";
852 open (KEY
, ">$keyfile") or die ("Cannot open $keyfile: $!\n");
856 push @UIDS, { text
=> $this_uid_text, key
=> $asciikey, serial
=> $uid_number };
858 info
("$longkeyid $uid_number $this_uid_text done.");
860 info
("$longkeyid $uid_number $this_uid_text is not signed by me, not writing.");
864 if (scalar @UIDS == 0) {
865 info
("found no signed uids for $keyid");
867 next if $opt{M
}; # do not send mail
870 for my $uid (@UIDS) {
871 trace
("UID: $uid->{'text'}\n");
872 unless ($uid->{'text'} =~ /@/) {
873 my $attach = ask
("UID $uid->{'text'} is no email address, attach it to every email sent?", 1);
874 push @attached, $uid if $attach;
878 notice
("Key has no encryption capabilities, mail will be sent unencrypted") unless $can_encrypt;
879 for my $uid (@UIDS) {
880 if ($uid->{'text'} =~ /@/) {
881 my $address = $uid->{'text'};
882 $address =~ s/.*<(.*)>.*/$1/;
883 if ($opt{m
} or ask
("Send mail to '$address' for $uid->{'text'}?", 1)) {
884 my $mail = send_mail
($address, $can_encrypt, $longkeyid, $uid, @attached);
886 my $keydir = "$KEYSBASE/$DATE_STRING";
887 my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid
($uid->{'text'});
888 open (KEY
, ">$mailfile") or die ("Cannot open $mailfile: $!\n");
901 ###############################################################3
902 #### old fork gpg --edit
904 my ($stdin_read, $stdin_write);
905 my ($stdout_read, $stdout_write);
906 my ($stderr_read, $stderr_write);
907 my ($status_read, $status_write);
908 pipe $stdin_read, $stdin_write;
909 pipe $stdout_read, $stdout_write;
910 pipe $stderr_read, $stderr_write;
911 pipe $status_read, $status_write;
914 unless ($pid) { # child
921 push @call, $CONFIG{'gpg-delsig'};
922 push @call, "--homedir=$tempdir";
923 push @call, '--with-colons';
924 push @call, '--fixed-list-mode';
925 push @call, '--command-fd=0';
926 push @call, "--status-fd=".fileno($status_write);
927 push @call, "--no-tty";
928 push @call, "--edit";
934 open (STDIN
, "<&".fileno($stdin_read)) or die ("Cannot reopen stdin: $!\n");
935 open (STDOUT
, ">&".fileno($stdout_write)) or die ("Cannot reopen stdout: $!\n");
936 open (STDERR
, ">&".fileno($stderr_write)) or die ("Cannot reopen stderr: $!\n");
938 fcntl $status_write, F_SETFD
, 0;
948 $inputfd = $stdin_write;
949 $stdoutfd = $stdout_read;
950 $stderrfd = $stderr_read;
951 $statusfd = $status_read;