clean up keyid detection
[pgp-tools.git] / caff / caff
1 #!/usr/bin/perl -w
2
3 # caff -- CA - Fire and Forget
4 # $Id$
5 #
6 # Copyright (c) 2004, 2005 Peter Palfrader <peter@palfrader.org>
7 # Copyright (c) 2005, 2006 Christoph Berg <cb@df7cb.de>
8 #
9 # All rights reserved.
10 #
11 # Redistribution and use in source and binary forms, with or without
12 # modification, are permitted provided that the following conditions
13 # are met:
14 # 1. Redistributions of source code must retain the above copyright
15 # notice, this list of conditions and the following disclaimer.
16 # 2. Redistributions in binary form must reproduce the above copyright
17 # notice, this list of conditions and the following disclaimer in the
18 # documentation and/or other materials provided with the distribution.
19 # 3. The name of the author may not be used to endorse or promote products
20 # derived from this software without specific prior written permission.
21 #
22 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
23 # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
24 # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
25 # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
27 # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
33 =pod
34
35 =head1 NAME
36
37 caff -- CA - Fire and Forget
38
39 =head1 SYNOPSIS
40
41 =over
42
43 =item B<caff> [-eEmMRS] [-u I<yourkeyid>] I<keyid> [I<keyid> ..]
44
45 =back
46
47 =head1 DESCRIPTION
48
49 CA Fire and Forget is a script that helps you in keysigning. It takes a list
50 of keyids on the command line, fetches them from a keyserver and calls GnuPG so
51 that you can sign it. It then mails each key to all its email addresses - only
52 including the one UID that we send to in each mail, pruned from all but self
53 sigs and sigs done by you. The mailed key is encrypted with itself as a means
54 to verify that key belongs to the recipient.
55
56 =head1 OPTIONS
57
58 =over
59
60 =item B<-e>, B<--export-old>
61
62 Export old signatures. Default is to ask the user for each old signature.
63
64 =item B<-E>, B<--no-export-old>
65
66 Do not export old signatures. Default is to ask the user for each old
67 signature.
68
69 =item B<-m>, B<--mail>
70
71 Send mail after signing. Default is to ask the user for each uid.
72
73 =item B<-M>, B<--no-mail>
74
75 Do not send mail after signing. Default is to ask the user for each uid.
76
77 =item B<-R>, B<--no-download>
78
79 Do not retrieve the key to be signed from a keyserver.
80
81 =item B<-S>, B<--no-sign>
82
83 Do not sign the keys.
84
85 =item B<-u> I<yourkeyid>, B<--local-user> I<yourkeyid>
86
87 Select the key that is used for signing, in case you have more than one key.
88 To sign with multiple keys at once, separate multiple keyids by comma.
89
90 =item B<--key-file> I<file>
91
92 Import keys from file. Can be supplied more than once.
93
94 =back
95
96 =head1 FILES
97
98 =over
99
100 =item $HOME/.caffrc - configuration file
101
102 =item $HOME/.caff/keys/yyyy-mm-dd/ - processed keys
103
104 =item $HOME/.caff/gnupghome/ - caff's working dir for gpg
105
106 =item $HOME/.caff/gnupghome/gpg.conf - gpg configuration
107
108 useful options include use-agent, default-cert-level, etc.
109
110 =back
111
112 =head1 CONFIGURATION FILE OPTIONS
113
114 The configuration file is a perl script that sets values in the hash B<%CONFIG>.
115 The file is generated when it does not exist.
116
117 Example:
118
119 $CONFIG{owner} = q{Peter Palfrader};
120 $CONFIG{email} = q{peter@palfrader.org};
121 $CONFIG{keyid} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ];
122
123 =head2 Required basic settings
124
125 =over
126
127 =item B<owner> [string]
128
129 Your name. B<REQUIRED>.
130
131 =item B<email> [string]
132
133 Your email address, used in From: lines. B<REQUIRED>.
134
135 =item B<keyid> [list of keyids]
136
137 A list of your keys. This is used to determine which signatures to keep
138 in the pruning step. If you select a key using B<-u> it has to be in
139 this list. B<REQUIRED>.
140
141 =head2 General settings
142
143 =item B<caffhome> [string]
144
145 Base directory for the files caff stores. Default: B<$HOME/.caff/>.
146
147 =head2 GnuPG settings
148
149 =item B<gpg> [string]
150
151 Path to the GnuPG binary. Default: B<gpg>.
152
153 =item B<gpg-sign> [string]
154
155 Path to the GnuPG binary which is used to sign keys. Default: what
156 B<gpg> is set to.
157
158 =item B<gpg-delsig> [string]
159
160 Path to the GnuPG binary which is used to split off signatures. This was
161 needed while the upstream GnuPG was not fixed. Default: what B<gpg>
162 is set to.
163
164 =item B<secret-keyring> [string]
165
166 Path to your secret keyring. Default: B<$HOME/.gnupg/secring.gpg>.
167
168 =item B<also-encrypt-to> [keyid, or list of keyids]
169
170 Additional keyids to encrypt messages to. Default: none.
171
172 =item B<gpg-sign-args> [string]
173
174 Additional arguments to pass to gpg. Default: none.
175
176 =head2 Keyserver settings
177
178 =item B<keyserver> [string]
179
180 Keyserver to download keys from. Default: B<subkeys.pgp.net>.
181
182 =item B<no-download> [boolean]
183
184 If true, then skip the step of fetching keys from the keyserver.
185 Default: B<0>.
186
187 =item B<key-files> [list of files]
188
189 A list of files containing keys to be imported.
190
191 =head2 Signing settings
192
193 =item B<no-sign> [boolean]
194
195 If true, then skip the signing step. Default: B<0>.
196
197 =item B<ask-sign> [boolean]
198
199 If true, then pause before continuing to the signing step.
200 This is useful for offline signing. Default: B<0>.
201
202 =item B<export-sig-age> [seconds]
203
204 Don't export UIDs by default, on which your latest signature is older
205 than this age. Default: B<24*60*60> (i.e. one day).
206
207 =item B<local-user> [keyid, or list of keyids]
208
209 Select the key that is used for signing, in case you have more than one key.
210 With multiple keyids, sign with each key in turn.
211
212 =head2 Mail settings
213
214 =item B<mail> [boolean]
215
216 Do not prompt for sending mail, just do it. Default: B<0>.
217
218 =item B<no-mail> [boolean]
219
220 Do not prompt for sending mail. The messages are still written to
221 $CONFIG{caffhome}/keys/. Default: B<0>.
222
223 =item B<mail-template> [string]
224
225 Email template which is used as the body text for the email sent out
226 instead of the default text if specified. The following perl variables
227 can be used in the template:
228
229 =over
230
231 =item B<{owner}> [string]
232
233 Your name as specified in the L<B<owner>|/item_owner__5bstring_5d> setting.
234
235 =item B<{key}> [string]
236
237 The keyid of the key you signed.
238
239 =item B<{@uids}> [array]
240
241 The UIDs for which signatures are included in the mail.
242
243 =back
244
245 =item B<reply-to> [string]
246
247 Add a Reply-To: header to messages sent. Default: none.
248
249 =item B<bcc> [string]
250
251 Address to send blind carbon copies to when sending mail.
252 Default: none.
253
254 =item B<mailer-send> [array]
255
256 Parameters to pass to Mail::Mailer.
257 This could for example be
258
259 $CONFIG{mailer-send} = [ 'smtp', Server => 'mail.server', Auth => ['user', 'pass'] ]
260
261 to use the perl SMTP client or
262
263 $CONFIG{mailer-send} = [ 'sendmail', '-o8' ]
264
265 to pass arguments to the sendmail program.
266 For more information run C<< perldoc Mail::Mailer >>.
267 Setting this option is strongly discouraged. Fix your local MTA
268 instead.
269 Default: none.
270
271 =back
272
273 =head1 AUTHORS
274
275 =over
276
277 =item Peter Palfrader <peter@palfrader.org>
278
279 =item Christoph Berg <cb@df7cb.de>
280
281 =back
282
283 =head1 WEBSITE
284
285 http://pgp-tools.alioth.debian.org/
286
287 =head1 SEE ALSO
288
289 gpg(1), pgp-clean(1), /usr/share/doc/signing-party/caff/caffrc.sample.
290
291 =cut
292
293 use strict;
294 use IO::Handle;
295 use English;
296 use File::Path;
297 use File::Temp qw{tempdir};
298 use Text::Template;
299 use MIME::Entity;
300 use Fcntl;
301 use IO::Select;
302 use Getopt::Long;
303 use GnuPG::Interface;
304
305 my %CONFIG;
306 my $REVISION = '$Rev$';
307 my ($REVISION_NUMER) = $REVISION =~ /(\d+)/;
308 my $VERSION = "0.0.0.$REVISION_NUMER";
309
310
311
312 sub mywarn($) {
313 my ($line) = @_;
314 print "[WARN] $line\n";
315 };
316 sub notice($) {
317 my ($line) = @_;
318 print "[NOTICE] $line\n";
319 };
320 sub info($) {
321 my ($line) = @_;
322 print "[INFO] $line\n";
323 };
324 sub debug($) {
325 my ($line) = @_;
326 #print "[DEBUG] $line\n";
327 };
328 sub trace($) {
329 my ($line) = @_;
330 #print "[trace] $line\n";
331 };
332 sub trace2($) {
333 my ($line) = @_;
334 #print "[trace2] $line\n";
335 };
336
337
338 sub generate_config() {
339 notice("Error: \$LOGNAME is not set.\n") unless defined $ENV{'LOGNAME'};
340 my $gecos = defined $ENV{'LOGNAME'} ? (getpwnam($ENV{LOGNAME}))[6] : undef;
341 my $email;
342 my @keys;
343 # BSD does not have hostname -f, so we try without -f first
344 my $hostname = `hostname`;
345 $hostname = `hostname -f` unless $hostname =~ /\./;
346 chomp $hostname;
347 my ($Cgecos,$Cemail,$Ckeys) = ('','','');
348
349 if (defined $gecos) {
350 $gecos =~ s/,.*//;
351
352 my $gpg = GnuPG::Interface->new();
353 $gpg->call( 'gpg' );
354 $gpg->options->hash_init(
355 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode } ] );
356 $gpg->options->meta_interactive( 0 );
357 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
358 my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $gecos ]);
359 my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
360 waitpid $pid, 0;
361
362 if ($stdout eq '') {
363 warn ("No data from gpg for list-key\n"); # There should be at least 'tru:' everywhere.
364 };
365
366 @keys = ($stdout =~ /^pub:[^r:]*:(?:[^:]*:){2,2}([^:]+):/mg);
367 unless (scalar @keys) {
368 info("Error: No keys were found using \"gpg --list-public-keys '$gecos'\".");
369 @keys = qw{0123456789abcdef 89abcdef76543210};
370 $Ckeys = '#';
371 }
372 ($email) = ($stdout =~ /^uid:.*<(.+?@.+?)>.*:/m);
373 unless (defined $email) {
374 info("Error: No email address was found using \"gpg --list-public-keys '$gecos'\".");
375 $email = $ENV{'LOGNAME'}.'@'.$hostname;
376 $Cemail = '#';
377 }
378 } else {
379 $gecos = 'Unknown Caff User';
380 $email = $ENV{'LOGNAME'}.'@'.$hostname;
381 @keys = qw{0123456789abcdef 89abcdef76543210};
382 ($Cgecos,$Cemail,$Ckeys) = ('#','#','#');
383 };
384
385 my $template = <<EOT;
386 # .caffrc -- vim:ft=perl:
387 # This file is in perl(1) format - see caff(1) for details.
388
389 $Cgecos\$CONFIG{'owner'} = '$gecos';
390 $Cemail\$CONFIG{'email'} = '$email';
391 #\$CONFIG{'reply-to'} = 'foo\@bla.org';
392
393 # You can get your long keyid from
394 # gpg --with-colons --list-key <yourkeyid|name|emailaddress..>
395 #
396 # If you have a v4 key, it will simply be the last 16 digits of
397 # your fingerprint.
398 #
399 # Example:
400 # \$CONFIG{'keyid'} = [ qw{FEDCBA9876543210} ];
401 # or, if you have more than one key:
402 # \$CONFIG{'keyid'} = [ qw{0123456789ABCDEF 89ABCDEF76543210} ];
403 $Ckeys\$CONFIG{'keyid'} = [ qw{@keys} ];
404
405 # Select this/these keys to sign with
406 #\$CONFIG{'local-user'} = [ qw{@keys} ];
407
408 # Additionally encrypt messages for these keyids
409 #\$CONFIG{'also-encrypt-to'} = [ qw{@keys} ];
410
411 # Mail template to use for the encrypted part
412 #\$CONFIG{'mail-template'} = << 'EOM';
413 EOT
414
415 $template .= "#$_" foreach <DATA>;
416 $template .= "#EOM\n";
417 return $template;
418 };
419
420 sub check_executable($$) {
421 # (GnuPG::Interface gives lousy errors when the gpg binary isn't found,
422 # so we want to check manually.)
423 my ($purpose, $fn) = @_;
424 # Only check provided fnames with a slash in them.
425 return unless defined $fn;
426 if ($fn =~ m!/!) {
427 die ("$PROGRAM_NAME: $purpose executable '$fn' not found.\n") unless -x $fn;
428 } else {
429 for my $p (split(':', $ENV{PATH})) {
430 return if -x "$p/$fn";
431 };
432 die ("$PROGRAM_NAME: $purpose executable '$fn' not found on path.\n") unless -x $fn;
433 };
434 };
435
436 sub load_config() {
437 my $config = $ENV{'HOME'} . '/.caffrc';
438 unless (-f $config) {
439 print "No configfile $config present, I will use this template:\n";
440 my $template = generate_config();
441 print "$template\nPlease edit $config and run caff again.\n";
442 open F, ">$config" or die "$config: $!";
443 print F $template;
444 close F;
445 exit(1);
446 }
447 unless (scalar eval `cat $config`) {
448 die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR;
449 };
450
451 $CONFIG{'caffhome'}=$ENV{'HOME'}.'/.caff' unless defined $CONFIG{'caffhome'};
452 die ("$PROGRAM_NAME: owner is not defined in $config.\n") unless defined $CONFIG{'owner'};
453 die ("$PROGRAM_NAME: email is not defined in $config.\n") unless defined $CONFIG{'email'};
454 die ("$PROGRAM_NAME: keyid is not defined in $config.\n") unless defined $CONFIG{'keyid'};
455 die ("$PROGRAM_NAME: keyid is not an array ref in $config.\n") unless (ref $CONFIG{'keyid'} eq 'ARRAY');
456 for my $keyid (@{$CONFIG{'keyid'}}) {
457 $keyid =~ /^[A-F0-9]{16}$/i or die ("$PROGRAM_NAME: key $keyid is not a long (16 digit) keyid in $config.\n");
458 };
459 @{$CONFIG{'keyid'}} = map { uc } @{$CONFIG{'keyid'}};
460 $CONFIG{'export-sig-age'}= 24*60*60 unless defined $CONFIG{'export-sig-age'};
461 $CONFIG{'keyserver'} = 'subkeys.pgp.net' unless defined $CONFIG{'keyserver'};
462 $CONFIG{'gpg'} = 'gpg' unless defined $CONFIG{'gpg'};
463 $CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'};
464 $CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'};
465 check_executable("gpg", $CONFIG{'gpg'});
466 check_executable("gpg-sign", $CONFIG{'gpg-sign'});
467 check_executable("gpg-delsig", $CONFIG{'gpg-delsig'});
468 $CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'};
469 $CONFIG{'no-download'} = 0 unless defined $CONFIG{'no-download'};
470 $CONFIG{'no-sign'} = 0 unless defined $CONFIG{'no-sign'};
471 $CONFIG{'key-files'} = () unless defined $CONFIG{'key-files'};
472 $CONFIG{'mailer-send'} = [] unless defined $CONFIG{'mailer-send'};
473 die ("$PROGRAM_NAME: mailer-send is not an array ref in $config.\n") unless (ref $CONFIG{'mailer-send'} eq 'ARRAY');
474 unless (defined $CONFIG{'mail-template'}) {
475 $CONFIG{'mail-template'} .= $_ foreach <DATA>;
476 }
477 };
478
479 sub make_gpg_fds() {
480 my %fds = (
481 stdin => IO::Handle->new(),
482 stdout => IO::Handle->new(),
483 stderr => IO::Handle->new(),
484 status => IO::Handle->new() );
485 my $handles = GnuPG::Handles->new( %fds );
486 return ($fds{'stdin'}, $fds{'stdout'}, $fds{'stderr'}, $fds{'status'}, $handles);
487 };
488
489 sub readwrite_gpg($$$$$%) {
490 my ($in, $inputfd, $stdoutfd, $stderrfd, $statusfd, %options) = @_;
491
492 trace("Entering readwrite_gpg.");
493
494 my ($first_line, undef) = split /\n/, $in;
495 debug("readwrite_gpg sends ".(defined $first_line ? $first_line : "<nothing>"));
496
497 local $INPUT_RECORD_SEPARATOR = undef;
498 my $sout = IO::Select->new();
499 my $sin = IO::Select->new();
500 my $offset = 0;
501
502 trace("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ? $statusfd : 'undef').".");
503
504 $inputfd->blocking(0);
505 $stdoutfd->blocking(0);
506 $statusfd->blocking(0) if defined $statusfd;
507 $stderrfd->blocking(0);
508 $sout->add($stdoutfd);
509 $sout->add($stderrfd);
510 $sout->add($statusfd) if defined $statusfd;
511 $sin->add($inputfd);
512
513 my ($stdout, $stderr, $status) = ("", "", "");
514 my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'};
515 trace("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches;
516
517 my $readwrote_stuff_this_time = 0;
518 my $do_not_wait_on_select = 0;
519 my ($readyr, $readyw, $written);
520 while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) {
521 if (defined $exitwhenstatusmatches) {
522 if ($status =~ /$exitwhenstatusmatches/m) {
523 trace("readwrite_gpg found match on $exitwhenstatusmatches");
524 if ($readwrote_stuff_this_time) {
525 trace("read/write some more\n");
526 $do_not_wait_on_select = 1;
527 } else {
528 trace("that's it in our while loop.\n");
529 last;
530 }
531 };
532 };
533
534 $readwrote_stuff_this_time = 0;
535 trace("select waiting for ".($sout->count())." fds.");
536 ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1);
537 trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0));
538 for my $wfd (@$readyw) {
539 $readwrote_stuff_this_time = 1;
540 if (length($in) != $offset) {
541 trace("writing to $wfd.");
542 $written = $wfd->syswrite($in, length($in) - $offset, $offset);
543 $offset += $written;
544 };
545 if ($offset == length($in)) {
546 trace("writing to $wfd done.");
547 unless ($options{'nocloseinput'}) {
548 close $wfd;
549 trace("$wfd closed.");
550 };
551 $sin->remove($wfd);
552 $sin = undef;
553 }
554 }
555
556 next unless (defined(@$readyr)); # Wait some more.
557
558 for my $rfd (@$readyr) {
559 $readwrote_stuff_this_time = 1;
560 if ($rfd->eof) {
561 trace("reading from $rfd done.");
562 $sout->remove($rfd);
563 close($rfd);
564 next;
565 }
566 trace("reading from $rfd.");
567 if ($rfd == $stdoutfd) {
568 $stdout .= <$rfd>;
569 trace2("stdout is now $stdout\n================");
570 next;
571 }
572 if (defined $statusfd && $rfd == $statusfd) {
573 $status .= <$rfd>;
574 trace2("status is now $status\n================");
575 next;
576 }
577 if ($rfd == $stderrfd) {
578 $stderr .= <$rfd>;
579 trace2("stderr is now $stderr\n================");
580 next;
581 }
582 }
583 }
584 trace("readwrite_gpg done.");
585 return ($stdout, $stderr, $status);
586 };
587
588 sub ask($$;$$) {
589 my ($question, $default, $forceyes, $forceno) = @_;
590 my $answer;
591 my $yn = $default ? '[Y/n]' : '[y/N]';
592 while (1) {
593 print $question,' ',$yn, ' ';
594 if ($forceyes && $forceno) {
595 print "$default (from config/command line)\n";
596 return $default;
597 };
598 if ($forceyes) {
599 print "YES (from config/command line)\n";
600 return 1;
601 };
602 if ($forceno) {
603 print "NO (from config/command line)\n";
604 return 0;
605 };
606
607 $answer = <STDIN>;
608 if (!defined $answer) {
609 $OUTPUT_AUTOFLUSH = 1;
610 die "\n\n".
611 "End of STDIN reached. Are you using xargs? Caff wants to read from STDIN,\n".
612 "so you can't really use it with xargs. A patch against caff to read from\n".
613 "the terminal would be appreciated.\n".
614 "For now instead of cat keys | xargs caff do caff `cat keys`\n";
615 };
616 chomp $answer;
617 last if ((length $answer == 0) || ($answer =~ m/^[yYnN]$/) );
618 print "What about $yn is so hard to understand?\nAnswer with either 'n' or 'y' or just press enter for the default.\n";
619 sleep 1;
620 };
621 my $result = $default;
622 $result = 1 if $answer =~ /y/i;
623 $result = 0 if $answer =~ /n/i;
624 return $result;
625 };
626
627
628
629
630
631 my $KEYEDIT_PROMPT = '^\[GNUPG:\] GET_LINE keyedit.prompt';
632 my $KEYEDIT_DELUID_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.uid.okay';
633 my $KEYEDIT_DELSIG_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.delsig';
634 my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = '^\[GNUPG:\] (GET_BOOL keyedit.delsig|GET_LINE keyedit.prompt)';
635 my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey';
636
637 load_config;
638 my $USER_AGENT = "caff $VERSION - http://pgp-tools.alioth.debian.org/";
639
640 my $KEYSBASE = $CONFIG{'caffhome'}.'/keys';
641 my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome';
642
643 -d $KEYSBASE || mkpath($KEYSBASE , 0, 0700) or die ("Cannot create $KEYSBASE: $!\n");
644 -d $GNUPGHOME || mkpath($GNUPGHOME, 0, 0700) or die ("Cannot create $GNUPGHOME: $!\n");
645
646 my $NOW = time;
647 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($NOW);
648 my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
649
650
651 sub version($) {
652 my ($fd) = @_;
653 print $fd "caff $VERSION - (c) 2004, 2005, 2006 Peter Palfrader et al.\n";
654 };
655
656 sub usage($$) {
657 my ($fd, $exitcode) = @_;
658 version($fd);
659 print $fd "Usage: $PROGRAM_NAME [-eEmMRS] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
660 print $fd "Consult the manual page for more information.\n";
661 exit $exitcode;
662 };
663
664 ######
665 # export key $keyid from $gnupghome
666 ######
667 sub export_key($$) {
668 my ($gnupghome, $keyid) = @_;
669
670 my $gpg = GnuPG::Interface->new();
671 $gpg->call( $CONFIG{'gpg'} );
672 if (defined $gnupghome) {
673 $gpg->options->hash_init(
674 'homedir' => $gnupghome,
675 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
676 'armor' => 1 );
677 } else {
678 $gpg->options->hash_init(
679 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
680 'armor' => 1 );
681 };
682 $gpg->options->meta_interactive( 0 );
683 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
684 my $pid = $gpg->export_keys(handles => $handles, command_args => [ $keyid ]);
685 my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
686 waitpid $pid, 0;
687
688 return $stdout;
689 };
690
691 ######
692 # import a key from the scalar $asciikey into a gpg homedirectory in $tempdir
693 ######
694 sub import_key($$) {
695 my ($gnupghome, $asciikey) = @_;
696
697 my $gpg = GnuPG::Interface->new();
698 $gpg->call( $CONFIG{'gpg'} );
699 $gpg->options->hash_init(
700 'homedir' => $gnupghome,
701 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ] );
702 $gpg->options->meta_interactive( 0 );
703 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
704 my $pid = $gpg->import_keys(handles => $handles);
705 my ($stdout, $stderr, $status) = readwrite_gpg($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd);
706 waitpid $pid, 0;
707
708 if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
709 return undef;
710 };
711 return 1;
712 };
713
714
715 ######
716 # Send an email to $address. If $can_encrypt is true then the mail
717 # will be PGP/MIME encrypted to $longkeyid.
718 #
719 # $longkeyid, $uid, and @attached will be used in the email and the template.
720 ######
721 #send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
722 sub send_mail($$$@) {
723 my ($address, $can_encrypt, $key_id, @keys) = @_;
724
725 my $template = Text::Template->new(TYPE => 'STRING', SOURCE => $CONFIG{'mail-template'})
726 or die "Error creating template: $Text::Template::ERROR";
727
728 my @uids;
729 for my $key (@keys) {
730 push @uids, $key->{'text'};
731 };
732 my $message = $template->fill_in(HASH => { key => $key_id,
733 uids => \@uids,
734 owner => $CONFIG{'owner'}})
735 or die "Error filling template in: $Text::Template::ERROR";
736
737 my $message_entity = MIME::Entity->build(
738 Type => "text/plain",
739 Charset => "utf-8",
740 Disposition => 'inline',
741 Data => $message);
742
743 my @key_entities;
744 for my $key (@keys) {
745 $message_entity->attach(
746 Type => "application/pgp-keys",
747 Disposition => 'attachment',
748 Encoding => "7bit",
749 Description => "PGP Key 0x$key_id, uid ".($key->{'text'}).' ('.($key->{'serial'}).'), signed by 0x'.$CONFIG{'keyid'}[0],
750 Data => $key->{'key'},
751 Filename => "0x$key_id.".$key->{'serial'}.".signed-by-0x".$CONFIG{'keyid'}[0].".asc");
752 };
753
754 if ($can_encrypt) {
755 my $message = $message_entity->stringify();
756
757 my $gpg = GnuPG::Interface->new();
758 $gpg->call( $CONFIG{'gpg'} );
759 $gpg->options->hash_init( 'homedir' => $GNUPGHOME,
760 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
761 'armor' => 1 );
762 $gpg->options->meta_interactive( 0 );
763 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
764 $gpg->options->push_recipients( $key_id );
765 if (defined $CONFIG{'also-encrypt-to'}) {
766 if (ref($CONFIG{'also-encrypt-to'})) {
767 $gpg->options->push_recipients($_)
768 foreach @{$CONFIG{'also-encrypt-to'}};
769 } else {
770 $gpg->options->push_recipients($CONFIG{'also-encrypt-to'});
771 }
772 }
773 my $pid = $gpg->encrypt(handles => $handles);
774 my ($stdout, $stderr, $status) = readwrite_gpg($message, $inputfd, $stdoutfd, $stderrfd, $statusfd);
775 waitpid $pid, 0;
776 if ($stdout eq '') {
777 warn ("No data from gpg for list-key $key_id\n");
778 next;
779 };
780 $message = $stdout;
781
782 $message_entity = MIME::Entity->build(
783 Type => 'multipart/encrypted; protocol="application/pgp-encrypted"',
784 Encoding => '7bit');
785
786 $message_entity->attach(
787 Type => "application/pgp-encrypted",
788 Disposition => 'attachment',
789 Encoding => "7bit",
790 Data => "Version: 1\n");
791
792 $message_entity->attach(
793 Type => "application/octet-stream",
794 Filename => 'msg.asc',
795 Disposition => 'inline',
796 Encoding => "7bit",
797 Data => $message);
798 };
799
800 $message_entity->head->add("Subject", "Your signed PGP key 0x$key_id");
801 $message_entity->head->add("To", $address);
802 $message_entity->head->add("From", '"'.$CONFIG{'owner'}.'" <'.$CONFIG{'email'}.'>');
803 $message_entity->head->add("Reply-To", $CONFIG{'reply-to'}) if defined $CONFIG{'reply-to'};
804 $message_entity->head->add("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'};
805 $message_entity->head->add("User-Agent", $USER_AGENT);
806 mywarn("You have set arguments to pass to Mail::Mailer. Better fix your MTA. (Also, Mail::Mailer's error reporting is non existant, so it won't tell you when it doesn't work.)") if (scalar @{$CONFIG{'mailer-send'}} > 0);
807 $message_entity->send(@{$CONFIG{'mailer-send'}});
808 $message_entity->stringify();
809 };
810
811 ######
812 # clean up a UID so that it can be used on the FS.
813 ######
814 sub sanitize_uid($) {
815 my ($uid) = @_;
816
817 my $good_uid = $uid;
818 $good_uid =~ tr#/:\\#_#;
819 trace2("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid;
820 return $good_uid;
821 };
822
823 sub delete_signatures($$$$$$) {
824 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $keyids) =@_;
825
826 my $signed_by_me = 0;
827
828 my ($stdout, $stderr, $status) =
829 readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1);
830
831 while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) {
832 # sig:?::17:EA2199412477CAF8:1058095214:::::13x:
833 my @sigline = grep { /^sig/ } (split /\n/, $stdout);
834 $stdout =~ s/\n/\\n/g;
835 notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX
836 my $line = pop @sigline;
837 my $answer = "no";
838 if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance
839 debug("[sigremoval] doing line $line.");
840 my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line;
841 if ($signer eq $longkeyid) {
842 debug("[sigremoval] selfsig ($signer).");
843 $answer = "no";
844 } elsif (grep { $signer eq $_ } @{$keyids}) {
845 debug("[sigremoval] signed by us ($signer).");
846 $answer = "no";
847 $signed_by_me = $signed_by_me > $created ? $signed_by_me : $created;
848 } else {
849 debug("[sigremoval] not interested in that sig ($signer).");
850 $answer = "yes";
851 };
852 } else {
853 debug("[sigremoval] no sig line here, only got: ".$stdout);
854 };
855 ($stdout, $stderr, $status) =
856 readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1);
857 };
858
859 return $signed_by_me;
860 };
861
862
863 ###################
864 # argument handling
865 ###################
866 my @KEYIDS;
867 my $params;
868
869 Getopt::Long::config('bundling');
870 if (!GetOptions (
871 '-h' => \$params->{'help'},
872 '--help' => \$params->{'help'},
873 '--version' => \$params->{'version'},
874 '-V' => \$params->{'version'},
875 '-u=s' => \$params->{'local-user'},
876 '--local-user=s' => \$params->{'local-user'},
877 '-e' => \$params->{'export-old'},
878 '--export-old' => \$params->{'export-old'},
879 '-E' => \$params->{'no-export-old'},
880 '--no-export-old' => \$params->{'no-export-old'},
881 '-m' => \$params->{'mail'},
882 '--mail' => \$params->{'mail'},
883 '-M' => \$params->{'no-mail'},
884 '--no-mail' => \$params->{'no-mail'},
885 '-R' => \$params->{'no-download'},
886 '--no-download' => \$params->{'no-download'},
887 '-S' => \$params->{'no-sign'},
888 '--no-sign' => \$params->{'no-sign'},
889 '--key-file=s@' => \$params->{'key-files'},
890 )) {
891 usage(\*STDERR, 1);
892 };
893 if ($params->{'help'}) {
894 usage(\*STDOUT, 0);
895 };
896 if ($params->{'version'}) {
897 version(\*STDOUT);
898 exit(0);
899 };
900 usage(\*STDERR, 1) unless scalar @ARGV >= 1;
901
902 $CONFIG{'local-user'} = $params->{'local-user'} if defined $params->{'local-user'};
903 $CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-download'};
904 $CONFIG{'no-mail'} = $params->{'no-mail'} if defined $params->{'no-mail'};
905 $CONFIG{'mail'} = $params->{'mail'} if defined $params->{'mail'};
906 $CONFIG{'no-sign'} = $params->{'no-sign'} if defined $params->{'no-sign'};
907 push @{$CONFIG{'key-files'}}, @{$params->{'key-files'}} if defined $params->{'key-files'};
908
909 for my $keyid (@ARGV) {
910 $keyid =~ s/^0x//i;
911 if ($keyid =~ /^[A-F0-9]{32}$/i) {
912 info("Ignoring v3 fingerprint $keyid. v3 keys are obsolete.");
913 next;
914 };
915 if ($keyid !~ /^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/i) {
916 print STDERR "$keyid is not a keyid.\n";
917 usage(\*STDERR, 1);
918 };
919 push @KEYIDS, uc($keyid);
920 };
921
922 #################
923 # import own keys
924 #################
925 for my $keyid (@{$CONFIG{'keyid'}}) {
926 my $gpg = GnuPG::Interface->new();
927 $gpg->call( $CONFIG{'gpg'} );
928 $gpg->options->hash_init(
929 'homedir' => $GNUPGHOME,
930 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode --fast-list-mode } ] );
931 $gpg->options->meta_interactive( 0 );
932 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
933 my $pid = $gpg->list_public_keys(handles => $handles, command_args => $keyid);
934 my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
935 waitpid $pid, 0;
936
937 if ($stdout eq '') {
938 warn ("No data from gpg for list-key\n"); # There should be at least 'tru:' everywhere.
939 };
940 unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) {
941 info("Key $keyid not found in caff's home. Getting it from your normal GnuPGHome.");
942 my $key = export_key(undef, $keyid);
943 if (!defined $key || $key eq '') {
944 warn ("Did not get key $keyid from your normal GnuPGHome\n");
945 next;
946 };
947 my $result = import_key($GNUPGHOME, $key);
948 unless ($result) {
949 warn ("Could not import $keyid into caff's gnupghome.\n");
950 next;
951 };
952 }
953 }
954
955 ########################
956 # import keys from files
957 ########################
958 foreach my $keyfile (@{$CONFIG{'key-files'}}) {
959 my $gpg = GnuPG::Interface->new();
960 $gpg->call( $CONFIG{'gpg'} );
961 $gpg->options->hash_init('homedir' => $GNUPGHOME);
962 $gpg->options->meta_interactive( 0 );
963 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
964 my $pid = $gpg->import_keys(handles => $handles, command_args => $keyfile);
965 my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
966 info ("Importing keys from $keyfile");
967 waitpid $pid, 0;
968 if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
969 warn $stderr;
970 }
971 }
972
973 #############################
974 # receive keys from keyserver
975 #############################
976 my @keyids_ok;
977 if ($CONFIG{'no-download'}) {
978 @keyids_ok = @KEYIDS;
979 } else {
980 info ("fetching keys, this will take a while...");
981
982 my $gpg = GnuPG::Interface->new();
983 $gpg->call( $CONFIG{'gpg'} );
984 $gpg->options->hash_init(
985 'homedir' => $GNUPGHOME,
986 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always }, '--keyserver='.$CONFIG{'keyserver'} ] );
987 $gpg->options->meta_interactive( 0 );
988 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
989 my $pid = $gpg->recv_keys(handles => $handles, command_args => [ @KEYIDS ]);
990 my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
991 waitpid $pid, 0;
992
993 # [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F
994 # [GNUPG:] NODATA 1
995 # [GNUPG:] NODATA 1
996 # [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039
997 my %local_keyids = map { $_ => 1 } @KEYIDS;
998 my $had_v3_keys = 0;
999 for my $line (split /\n/, $status) {
1000 if ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{40})/) {
1001 my $imported_key = $1;
1002 my $whole_fpr = $imported_key;
1003 my $long_keyid = substr($imported_key, -16);
1004 my $short_keyid = substr($imported_key, -8);
1005 my $speced_key;
1006 for my $spec (($whole_fpr, $long_keyid, $short_keyid)) {
1007 $speced_key = $spec if $local_keyids{$spec};
1008 };
1009 unless ($speced_key) {
1010 notice ("Imported unexpected key; got: $imported_key\n");
1011 next;
1012 };
1013 debug ("Imported $imported_key for $speced_key");
1014 delete $local_keyids{$speced_key};
1015 unshift @keyids_ok, $imported_key;
1016 } elsif ($line =~ /^\[GNUPG:\] (NODATA|IMPORT_RES|IMPORTED) /) {
1017 } elsif ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{32})/) {
1018 my $imported_key = $1;
1019 notice ("Imported key $1 is a version 3 key. Version 3 keys are obsolete, should not be used, and are not and will not be properly supported.");
1020 $had_v3_keys = 1;
1021 } else {
1022 notice ("got unknown reply from gpg: $line");
1023 }
1024 };
1025 if (scalar %local_keyids) {
1026 notice ("Import failed for: ". (join ' ', keys %local_keyids)."." . ($had_v3_keys ? " (Or maybe it's one of those ugly v3 keys?)" : ""));
1027 exit 1 unless ask ("Some keys could not be imported - continue anyway?", 0);
1028 if (scalar %local_keyids == 1) {
1029 mywarn("Assuming ". (join ' ', keys %local_keyids)." is a fine keyid.");
1030 } else {
1031 mywarn("Assuming ". (join ' ', keys %local_keyids)." are fine keyids.");
1032 };
1033 push @keyids_ok, keys %local_keyids;
1034 }
1035 };
1036
1037 unless (@keyids_ok) {
1038 notice ("No keys to sign found");
1039 exit 0;
1040 }
1041
1042 ###########
1043 # sign keys
1044 ###########
1045 if ($CONFIG{'ask-sign'} && ! $CONFIG{'no-sign'}) {
1046 $CONFIG{'no-sign'} = ! ask("Continue with signing?", 1);
1047 }
1048
1049 unless ($CONFIG{'no-sign'}) {
1050 my @local_user;
1051 if ($CONFIG{'local-user'}) {
1052 if (ref($CONFIG{'local-user'})) {
1053 @local_user = @{$CONFIG{'local-user'}};
1054 } else {
1055 @local_user = split /\s*,\s*/, $CONFIG{'local-user'};
1056 };
1057 foreach (@local_user) {
1058 s/^0x//i;
1059 unless (/^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/i) {
1060 print STDERR "Local-user $_ is not a keyid.\n";
1061 usage(\*STDERR, 1);
1062 };
1063 $_ = uc($_);
1064 };
1065 } else {
1066 @local_user = (undef);
1067 };
1068
1069 info("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key");
1070 for my $keyid (@keyids_ok) {
1071 foreach my $local_user (@local_user) {
1072 my @command;
1073 push @command, $CONFIG{'gpg-sign'};
1074 push @command, '--local-user', $local_user if (defined $local_user);
1075 push @command, "--homedir=$GNUPGHOME";
1076 push @command, '--secret-keyring', $CONFIG{'secret-keyring'};
1077 push @command, '--no-auto-check-trustdb';
1078 push @command, '--trust-model=always';
1079 push @command, '--edit', $keyid;
1080 push @command, 'sign';
1081 push @command, split ' ', $CONFIG{'gpg-sign-args'} || "";
1082 print join(' ', @command),"\n";
1083 system (@command);
1084 };
1085 };
1086 };
1087
1088 ##################
1089 # export and prune
1090 ##################
1091 KEYS:
1092 for my $keyid (@keyids_ok) {
1093 # get key listing
1094 #################
1095 my $gpg = GnuPG::Interface->new();
1096 $gpg->call( $CONFIG{'gpg'} );
1097 $gpg->options->hash_init(
1098 'homedir' => $GNUPGHOME,
1099 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode } ] );
1100 $gpg->options->meta_interactive( 0 );
1101 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
1102 my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]);
1103 my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
1104 waitpid $pid, 0;
1105 if ($stdout eq '') {
1106 warn ("No data from gpg for list-key $keyid\n");
1107 next;
1108 };
1109 my @publine = grep { /^pub/ } (split /\n/, $stdout);
1110 if (scalar @publine == 0) {
1111 warn ("No public keys found with list-key $keyid (note that caff uses its own keyring in $GNUPGHOME).\n");
1112 next;
1113 };
1114 my (undef, undef, undef, undef, $longkeyid, undef, undef, undef, undef, undef, undef, $flags) = split /:/, pop @publine;
1115 if (scalar @publine > 0) {
1116 warn ("More than one key matched $keyid. Try to specify the long keyid or fingerprint\n");
1117 next;
1118 };
1119 unless (defined $longkeyid) {
1120 warn ("Didn't find public keyid in --list-key of key $keyid.\n");
1121 next;
1122 };
1123 unless (defined $flags) {
1124 warn ("Didn't find flags in --list-key of key $keyid.\n");
1125 next;
1126 };
1127 my $can_encrypt = $flags =~ /E/;
1128
1129 # export the key
1130 ################
1131 my $asciikey = export_key($GNUPGHOME, $keyid);
1132 if ($asciikey eq '') {
1133 warn ("No data from gpg for export $keyid\n");
1134 next;
1135 };
1136
1137 my @UIDS;
1138 my $uid_number = 0;
1139 while (1) {
1140 my $this_uid_text = '';
1141 $uid_number++;
1142 debug("Doing key $keyid, uid $uid_number");
1143 my $tempdir = tempdir( "caff-$keyid-XXXXX", DIR => '/tmp/', CLEANUP => 1);
1144
1145 # import into temporary gpghome
1146 ###############################
1147 my $result = import_key($tempdir, $asciikey);
1148 unless ($result) {
1149 warn ("Could not import $keyid into temporary gnupg.\n");
1150 next;
1151 };
1152
1153 # prune it
1154 ##########
1155 $gpg = GnuPG::Interface->new();
1156 $gpg->call( $CONFIG{'gpg-delsig'} );
1157 $gpg->options->hash_init(
1158 'homedir' => $tempdir,
1159 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode --command-fd=0 --no-tty } ] );
1160 ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
1161 $pid = $gpg->wrap_call(
1162 commands => [ '--edit' ],
1163 command_args => [ $keyid ],
1164 handles => $handles );
1165
1166 debug("Starting edit session");
1167 ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1168
1169 # delete other uids
1170 ###################
1171 my $number_of_subkeys = 0;
1172 my $i = 1;
1173 my $have_one = 0;
1174 my $is_uat = 0;
1175 my $delete_some = 0;
1176 debug("Parsing stdout output.");
1177 for my $line (split /\n/, $stdout) {
1178 debug("Checking line $line");
1179 my ($type, undef, undef, undef, undef, undef, undef, undef, undef, $uidtext) = split /:/, $line;
1180 if ($type eq 'sub') {
1181 $number_of_subkeys++;
1182 };
1183 next unless ($type eq 'uid' || $type eq 'uat');
1184 debug("line is interesting.");
1185 if ($uid_number != $i) {
1186 debug("mark for deletion.");
1187 readwrite_gpg("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1188 $delete_some++;
1189 } else {
1190 debug("keep it.");
1191 $have_one = 1;
1192 $this_uid_text = ($type eq 'uid') ? $uidtext : '[attribute]';
1193 $is_uat = $type eq 'uat';
1194 };
1195 $i++;
1196 };
1197 debug("Parsing stdout output done.");
1198 unless ($have_one) {
1199 debug("Uid ".($uid_number-1)." was the last, there is no $uid_number.");
1200 info("key $keyid done.");
1201 last;
1202 };
1203
1204 my $prune_some_sigs_on_uid;
1205 my $prune_all_sigs_on_uid;
1206 if ($is_uat) {
1207 debug("handling attribute userid of key $keyid.");
1208 if ($uid_number == 1) {
1209 debug(" attribute userid is #1, unmarking #2 for deletion.");
1210 readwrite_gpg("2\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1211 $delete_some--;
1212 $prune_some_sigs_on_uid = 1;
1213 $prune_all_sigs_on_uid = 2;
1214 } else {
1215 debug("attribute userid is not #1, unmarking #1 for deletion.");
1216 readwrite_gpg("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1217 $delete_some--;
1218 $prune_some_sigs_on_uid = 2;
1219 $prune_all_sigs_on_uid = 1;
1220 };
1221 } else {
1222 $prune_some_sigs_on_uid = 1;
1223 };
1224
1225 if ($delete_some) {
1226 debug("need to delete $delete_some uids.");
1227 readwrite_gpg("deluid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELUID_PROMPT, nocloseinput => 1);
1228 readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1229 };
1230
1231 # delete subkeys
1232 ################
1233 if ($number_of_subkeys > 0) {
1234 for (my $i=1; $i<=$number_of_subkeys; $i++) {
1235 readwrite_gpg("key $i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1236 };
1237 readwrite_gpg("delkey\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSUBKEY_PROMPT, nocloseinput => 1);
1238 readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1239 };
1240
1241 # delete signatures
1242 ###################
1243 readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig
1244 my $signed_by_me = delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $CONFIG{'keyid'});
1245 readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig
1246 if (defined $prune_all_sigs_on_uid) {
1247 readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig
1248 delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, []);
1249 readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig
1250 };
1251
1252
1253 readwrite_gpg("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd);
1254 waitpid $pid, 0;
1255
1256 my $asciikey = export_key($tempdir, $keyid);
1257 if ($asciikey eq '') {
1258 warn ("No data from gpg for export $keyid\n");
1259 next;
1260 };
1261
1262 if ($signed_by_me) {
1263 if ($NOW - $signed_by_me > $CONFIG{'export-sig-age'} ) {
1264 my $write = ask("Signature on $this_uid_text is old. Export?", 0, $params->{'export-old'}, $params->{'no-export-old'});
1265 next unless $write;
1266 };
1267 my $keydir = "$KEYSBASE/$DATE_STRING";
1268 -d $keydir || mkpath($keydir , 0, 0700) or die ("Cannot create $keydir $!\n");
1269
1270 my $keyfile = "$keydir/$longkeyid.key.$uid_number.".sanitize_uid($this_uid_text).".asc";
1271 open (KEY, ">$keyfile") or die ("Cannot open $keyfile: $!\n");
1272 print KEY $asciikey;
1273 close KEY;
1274
1275 push @UIDS, { text => $this_uid_text, key => $asciikey, serial => $uid_number, "is_uat" => $is_uat };
1276
1277 info("$longkeyid $uid_number $this_uid_text done.");
1278 } else {
1279 info("$longkeyid $uid_number $this_uid_text is not signed by me, not writing.");
1280 };
1281 };
1282
1283 if (scalar @UIDS == 0) {
1284 info("found no signed uids for $keyid");
1285 } else {
1286 next if $CONFIG{'no-mail'}; # do not send mail
1287
1288 my @attached;
1289 for my $uid (@UIDS) {
1290 trace("UID: $uid->{'text'}\n");
1291 if ($uid->{'is_uat'}) {
1292 my $attach = ask("UID $uid->{'text'} is an attribute UID, attach it to every email sent?", 1);
1293 push @attached, $uid if $attach;
1294 } elsif ($uid->{'text'} !~ /@/) {
1295 my $attach = ask("UID $uid->{'text'} is no email address, attach it to every email sent?", 1);
1296 push @attached, $uid if $attach;
1297 };
1298 };
1299
1300 notice("Key has no encryption capabilities, mail will be sent unencrypted") unless $can_encrypt;
1301 for my $uid (@UIDS) {
1302 if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) {
1303 my $address = $uid->{'text'};
1304 $address =~ s/.*<(.*)>.*/$1/;
1305 if (ask("Mail signature for $uid->{'text'} to '$address'?", 1, $CONFIG{'mail'})) {
1306 my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
1307
1308 my $keydir = "$KEYSBASE/$DATE_STRING";
1309 my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid($uid->{'text'});
1310 open (KEY, ">$mailfile") or die ("Cannot open $mailfile: $!\n");
1311 print KEY $mail;
1312 close KEY;
1313 };
1314 };
1315 };
1316 };
1317
1318 };
1319
1320 ###########################
1321 # the default mail template
1322 ###########################
1323
1324 __DATA__
1325 Hi,
1326
1327 please find attached the user id{(scalar @uids >= 2 ? 's' : '')}
1328 {foreach $uid (@uids) {
1329 $OUT .= "\t".$uid."\n";
1330 };}of your key {$key} signed by me.
1331
1332 If you have multiple user ids, I sent the signature for each user id
1333 separately to that user id's associated email address. You can import
1334 the signatures by running each through `gpg --import`.
1335
1336 Note that I did not upload your key to any keyservers. If you want this
1337 new signature to be available to others, please upload it yourself.
1338 With GnuPG this can be done using
1339 gpg --keyserver subkeys.pgp.net --send-key {$key}
1340
1341 If you have any questions, don't hesitate to ask.
1342
1343 Regards,
1344 {$owner}