/[pgp-tools]/trunk/caff/caff
ViewVC logotype

Contents of /trunk/caff/caff

Parent Directory Parent Directory | Revision Log Revision Log


Revision 133 - (hide annotations) (download)
Tue Jul 19 16:50:48 2005 UTC (7 years, 10 months ago) by weasel
File size: 34805 byte(s)
Change wording for send mail question - maybe it makes at least some people send more
1 weasel 5 #!/usr/bin/perl -w
2    
3 weasel 9 # caff -- CA - Fire and Forget
4 weasel 6 # $Id$
5 weasel 5 #
6 weasel 36 # Copyright (c) 2004, 2005 Peter Palfrader <peter@palfrader.org>
7 myon-guest 102 # Copyright (c) 2005 Christoph Berg <cb@df7cb.de>
8 weasel 5 #
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 weasel 9 =pod
34    
35     =head1 NAME
36    
37     caff -- CA - Fire and Forget
38    
39     =head1 SYNOPSIS
40    
41     =over
42    
43 myon-guest 106 =item B<caff> [-eEmMRS] [-u I<yourkeyid>] I<keyid> [I<keyid> ..]
44 weasel 9
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.
54    
55     =head1 OPTIONS
56    
57     =over
58    
59 weasel 109 =item B<-e>, B<--export-old>
60 myon-guest 79
61 weasel 109 Export old signatures. Default is to ask the user for each old signature.
62    
63     =item B<-E>, B<--no-export-old>
64    
65     Do not export old signatures. Default is to ask the user for each old
66 myon-guest 106 signature.
67 myon-guest 79
68 weasel 109 =item B<-m>, B<--mail>
69 myon-guest 79
70 weasel 109 Send mail after signing. Default is to ask the user for each uid.
71 weasel 95
72 weasel 109 =item B<-M>, B<--no-mail>
73    
74     Do not send mail after signing. Default is to ask the user for each uid.
75    
76 weasel 95 =item B<-R>, B<--no-download>
77    
78 myon-guest 79 Do not retrieve the key to be signed from a keyserver.
79    
80 myon-guest 106 =item B<-S>, B<--no-sign>
81    
82     Do not sign the keys.
83    
84 weasel 95 =item B<-u> I<yourkeyid>, B<--local-user> I<yourkeyid>
85 weasel 9
86     Select the key that is used for signing, in case you have more than one key.
87    
88     =back
89    
90     =head1 FILES
91    
92     =over
93    
94     =item $HOME/.caffrc - configuration file
95    
96     =back
97    
98     =head1 CONFIGURATION FILE OPTIONS
99    
100     The configuration file is a perl script that sets values in the hash B<%CONFIG>.
101    
102     Example:
103    
104 myon-guest 41 $CONFIG{owner} = q{Peter Palfrader};
105     $CONFIG{email} = q{peter@palfrader.org};
106     $CONFIG{keyid} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ];
107 weasel 9
108 myon-guest 102 =head2 Required basic settings
109 weasel 9
110     =over
111    
112     =item B<owner> [string]
113    
114     Your name. B<REQUIRED>.
115    
116     =item B<email> [string]
117    
118     Your email address, used in From: lines. B<REQUIRED>.
119    
120     =item B<keyid> [list of keyids]
121    
122     A list of your keys. This is used to determine which signatures to keep
123     in the pruning step. If you select a key using B<-u> it has to be in
124     this list. B<REQUIRED>.
125    
126 myon-guest 102 =head2 General settings
127 weasel 9
128 myon-guest 102 =item B<caffhome> [string]
129 weasel 9
130 myon-guest 102 Base directory for the files caff stores. Default: B<$HOME/.caff/>.
131 weasel 9
132 myon-guest 102 =head2 GnuPG settings
133 weasel 9
134     =item B<gpg> [string]
135    
136     Path to the GnuPG binary. Default: B<gpg>.
137    
138     =item B<gpg-sign> [string]
139    
140     Path to the GnuPG binary which is used to sign keys. Default: what
141     B<gpg> is set to.
142    
143 weasel 18 =item B<gpg-delsig> [string]
144 weasel 9
145 weasel 69 Path to the GnuPG binary which is used to split off signatures. This was
146     needed while the upstream GnuPG was not fixed. Default: what B<gpg>
147     is set to.
148 weasel 9
149     =item B<secret-keyring> [string]
150    
151     Path to your secret keyring. Default: B<$HOME/.gnupg/secring.gpg>.
152    
153 weasel 18 =item B<also-encrypt-to> [keyid]
154    
155     An additional keyid to encrypt messages to. Default: none.
156    
157 myon-guest 106 =item B<gpg-sign-args> [string]
158    
159 myon-guest 119 Additional arguments to pass to gpg. Default: none.
160 myon-guest 106
161 myon-guest 102 =head2 Keyserver settings
162    
163     =item B<keyserver> [string]
164    
165     Keyserver to download keys from. Default: B<subkeys.pgp.net>.
166    
167 weasel 18 =item B<no-download> [boolean]
168    
169     If true, then skip the step of fetching keys from the keyserver.
170     Default: B<0>.
171    
172 myon-guest 102 =head2 Signing settings
173    
174 weasel 18 =item B<no-sign> [boolean]
175    
176     If true, then skip the signing step. Default: B<0>.
177    
178 kink-guest 126 =item B<ask-sign> [boolean]
179    
180     If true, then pause before continuing to the signing step.
181     This is useful for offline signing. Default: B<0>.
182    
183 myon-guest 102 =item B<export-sig-age> [seconds]
184    
185     Don't export UIDs by default, on which your latest signature is older
186     than this age. Default: B<24*60*60> (i.e. one day).
187    
188     =head2 Mail settings
189    
190     =item B<mail> [boolean]
191    
192     Do not prompt for sending mail, just do it. Default: B<0>.
193    
194     =item B<no-mail> [boolean]
195    
196     Do not prompt for sending mail. The messages are still written to
197     $CONFIG{caffhome}/keys/. Default: B<0>.
198    
199 weasel 93 =item B<mail-template> [string]
200    
201 weasel 104 Email template which is used as the body text for the email sent out
202 weasel 93 instead of the default text if specified. The following perl variables
203     can be used in the template:
204    
205     =over
206    
207     =item B<{owner}> [string]
208    
209     Your name as specified in the L<B<owner>|/item_owner__5bstring_5d> setting.
210    
211     =item B<{key}> [string]
212    
213     The keyid of the key you signed.
214    
215     =item B<{@uids}> [array]
216    
217     The UIDs for which signatures are included in the mail.
218    
219 weasel 9 =back
220    
221 myon-guest 101 =item B<bcc> [string]
222    
223     Address to send blind carbon copies to when sending mail.
224     Default: none.
225    
226 weasel 93 =back
227    
228 myon-guest 102 =head1 AUTHORS
229 weasel 9
230 myon-guest 102 =over
231 weasel 9
232 myon-guest 102 =item Peter Palfrader <peter@palfrader.org>
233    
234     =item Christoph Berg <cb@df7cb.de>
235    
236     =back
237    
238 myon-guest 41 =head1 WEBSITE
239    
240     http://pgp-tools.alioth.debian.org/
241    
242 weasel 9 =cut
243    
244 weasel 5 use strict;
245     use IO::Handle;
246     use English;
247     use File::Path;
248     use File::Temp qw{tempdir};
249 weasel 93 use Text::Template;
250 weasel 5 use MIME::Entity;
251     use Fcntl;
252     use IO::Select;
253 weasel 95 use Getopt::Long;
254 weasel 5 use GnuPG::Interface;
255    
256     my %CONFIG;
257 weasel 6 my $REVISION = '$Rev$';
258 weasel 7 my ($REVISION_NUMER) = $REVISION =~ /(\d+)/;
259     my $VERSION = "0.0.0.$REVISION_NUMER";
260 weasel 5
261     sub load_config() {
262     my $config = $ENV{'HOME'} . '/.caffrc';
263 myon-guest 41 -f $config or die "No file $config present. See caff(1).\n";
264 weasel 5 unless (scalar eval `cat $config`) {
265     die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR;
266     };
267    
268 weasel 9 $CONFIG{'caffhome'}=$ENV{'HOME'}.'/.caff' unless defined $CONFIG{'caffhome'};
269 weasel 5 die ("owner is not defined.\n") unless defined $CONFIG{'owner'};
270     die ("email is not defined.\n") unless defined $CONFIG{'email'};
271     die ("keyid is not defined.\n") unless defined $CONFIG{'keyid'};
272     die ("keyid is not an array ref\n") unless (ref $CONFIG{'keyid'} eq 'ARRAY');
273     for my $keyid (@{$CONFIG{'keyid'}}) {
274 weasel 128 $keyid =~ /^[A-F0-9]{16}$/i or die ("key $keyid is not a long (16 digit) keyid.\n");
275 weasel 5 };
276     @{$CONFIG{'keyid'}} = map { uc } @{$CONFIG{'keyid'}};
277     $CONFIG{'export-sig-age'}= 24*60*60 unless defined $CONFIG{'export-sig-age'};
278     $CONFIG{'keyserver'} = 'subkeys.pgp.net' unless defined $CONFIG{'keyserver'};
279     $CONFIG{'gpg'} = 'gpg' unless defined $CONFIG{'gpg'};
280     $CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'};
281     $CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'};
282     $CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'};
283 weasel 18 $CONFIG{'no-download'} = 0 unless defined $CONFIG{'no-download'};
284     $CONFIG{'no-sign'} = 0 unless defined $CONFIG{'no-sign'};
285 weasel 93 $CONFIG{'mail-template'} = <<'EOM' unless defined $CONFIG{'mail-template'};
286     Hi,
287    
288     please find attached the user id{(scalar @uids >= 2 ? 's' : '')}.
289     {foreach $uid (@uids) {
290     $OUT .= "\t".$uid."\n";
291     };} of your key {$key} signed by me.
292    
293 kink-guest 123 Note that I did not upload your key to any keyservers.
294     If you have multiple user ids, I sent the signature for each user id
295     separately to that user id's associated email address. You can import
296     the signatures by running each through `gpg --import`.
297    
298     If you want this new signature to be available to others, please upload
299     it yourself. With GnuPG this can be done using
300 weasel 93 gpg --keyserver subkeys.pgp.net --send-key {$key}
301    
302     If you have any questions, don't hesitate to ask.
303    
304     Regards,
305     {$owner}
306     EOM
307 weasel 5 };
308    
309     sub notice($) {
310     my ($line) = @_;
311     print "[NOTICE] $line\n";
312     };
313     sub info($) {
314     my ($line) = @_;
315     print "[INFO] $line\n";
316     };
317     sub debug($) {
318     my ($line) = @_;
319 enrico 25 #print "[DEBUG] $line\n";
320 weasel 5 };
321     sub trace($) {
322     my ($line) = @_;
323     #print "[trace] $line\n";
324     };
325     sub trace2($) {
326     my ($line) = @_;
327     #print "[trace2] $line\n";
328     };
329    
330     sub make_gpg_fds() {
331     my %fds = (
332     stdin => IO::Handle->new(),
333     stdout => IO::Handle->new(),
334     stderr => IO::Handle->new(),
335     status => IO::Handle->new() );
336     my $handles = GnuPG::Handles->new( %fds );
337     return ($fds{'stdin'}, $fds{'stdout'}, $fds{'stderr'}, $fds{'status'}, $handles);
338     };
339    
340     sub readwrite_gpg($$$$$%) {
341     my ($in, $inputfd, $stdoutfd, $stderrfd, $statusfd, %options) = @_;
342    
343     trace("Entering readwrite_gpg.");
344    
345 weasel 88 my ($first_line, undef) = split /\n/, $in;
346 weasel 5 debug("readwrite_gpg sends ".(defined $first_line ? $first_line : "<nothing>"));
347    
348     local $INPUT_RECORD_SEPARATOR = undef;
349     my $sout = IO::Select->new();
350     my $sin = IO::Select->new();
351     my $offset = 0;
352    
353     trace("input is $inputfd; output is $stdoutfd; err is $stderrfd; status is ".(defined $statusfd ? $statusfd : 'undef').".");
354    
355     $inputfd->blocking(0);
356     $stdoutfd->blocking(0);
357     $statusfd->blocking(0) if defined $statusfd;
358     $stderrfd->blocking(0);
359     $sout->add($stdoutfd);
360     $sout->add($stderrfd);
361     $sout->add($statusfd) if defined $statusfd;
362     $sin->add($inputfd);
363    
364     my ($stdout, $stderr, $status) = ("", "", "");
365     my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'};
366 weasel 26 trace("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches;
367 weasel 5
368 weasel 24 my $readwrote_stuff_this_time = 0;
369     my $do_not_wait_on_select = 0;
370 weasel 5 my ($readyr, $readyw, $written);
371     while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) {
372     if (defined $exitwhenstatusmatches) {
373     if ($status =~ /$exitwhenstatusmatches/m) {
374     trace("readwrite_gpg found match on $exitwhenstatusmatches");
375 weasel 24 if ($readwrote_stuff_this_time) {
376     trace("read/write some more\n");
377     $do_not_wait_on_select = 1;
378     } else {
379     trace("that's it in our while loop.\n");
380     last;
381     }
382 weasel 5 };
383     };
384    
385 weasel 24 $readwrote_stuff_this_time = 0;
386 weasel 5 trace("select waiting for ".($sout->count())." fds.");
387 weasel 24 ($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1);
388 weasel 5 trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0));
389     for my $wfd (@$readyw) {
390 weasel 24 $readwrote_stuff_this_time = 1;
391 weasel 5 if (length($in) != $offset) {
392     trace("writing to $wfd.");
393     $written = $wfd->syswrite($in, length($in) - $offset, $offset);
394     $offset += $written;
395     };
396     if ($offset == length($in)) {
397     trace("writing to $wfd done.");
398     unless ($options{'nocloseinput'}) {
399     close $wfd;
400     trace("$wfd closed.");
401     };
402     $sin->remove($wfd);
403     $sin = undef;
404     }
405     }
406    
407     next unless (defined(@$readyr)); # Wait some more.
408    
409     for my $rfd (@$readyr) {
410 weasel 24 $readwrote_stuff_this_time = 1;
411 weasel 5 if ($rfd->eof) {
412     trace("reading from $rfd done.");
413     $sout->remove($rfd);
414     close($rfd);
415     next;
416     }
417     trace("reading from $rfd.");
418     if ($rfd == $stdoutfd) {
419     $stdout .= <$rfd>;
420     trace2("stdout is now $stdout\n================");
421     next;
422     }
423     if (defined $statusfd && $rfd == $statusfd) {
424     $status .= <$rfd>;
425     trace2("status is now $status\n================");
426     next;
427     }
428     if ($rfd == $stderrfd) {
429     $stderr .= <$rfd>;
430     trace2("stderr is now $stderr\n================");
431     next;
432     }
433     }
434     }
435     trace("readwrite_gpg done.");
436     return ($stdout, $stderr, $status);
437     };
438    
439 myon-guest 106 sub ask($$;$$) {
440     my ($question, $default, $forceyes, $forceno) = @_;
441     return $default if $forceyes and $forceno;
442     return 1 if $forceyes;
443     return 0 if $forceno;
444 weasel 5 my $answer;
445     while (1) {
446     print $question,' ',($default ? '[Y/n]' : '[y/N]'), ' ';
447     $answer = <STDIN>;
448     chomp $answer;
449     last if ((defined $answer) && (length $answer <= 1));
450     print "grrrrrr.\n";
451     sleep 1;
452     };
453     my $result = $default;
454     $result = 1 if $answer =~ /y/i;
455     $result = 0 if $answer =~ /n/i;
456     return $result;
457     };
458    
459    
460    
461    
462    
463     my $KEYEDIT_PROMPT = '^\[GNUPG:\] GET_LINE keyedit.prompt';
464     my $KEYEDIT_DELUID_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.uid.okay';
465     my $KEYEDIT_DELSIG_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.delsig';
466     my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = '^\[GNUPG:\] (GET_BOOL keyedit.delsig|GET_LINE keyedit.prompt)';
467     my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey';
468    
469     load_config;
470 myon-guest 102 my $USER_AGENT = "caff $VERSION - (c) 2004, 2005 Peter Palfrader et al.";
471 weasel 5
472     my $KEYSBASE = $CONFIG{'caffhome'}.'/keys';
473     my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome';
474    
475     -d $KEYSBASE || mkpath($KEYSBASE , 0, 0700) or die ("Cannot create $KEYSBASE: $!\n");
476     -d $GNUPGHOME || mkpath($GNUPGHOME, 0, 0700) or die ("Cannot create $GNUPGHOME: $!\n");
477    
478     my $NOW = time;
479     my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($NOW);
480     my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
481    
482    
483 weasel 95 sub version($) {
484     my ($fd) = @_;
485 myon-guest 102 print $fd "caff $VERSION - (c) 2004, 2005 Peter Palfrader et al.\n";
486 weasel 5 };
487    
488 weasel 95 sub usage($$) {
489     my ($fd, $exitcode) = @_;
490     version($fd);
491 myon-guest 106 print $fd "Usage: $PROGRAM_NAME [-eEmMRS] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
492 weasel 95 print $fd "Consult the manual page for more information.\n";
493     exit $exitcode;
494     };
495    
496 weasel 97 ######
497     # export key $keyid from $gnupghome
498     ######
499 weasel 5 sub export_key($$) {
500     my ($gnupghome, $keyid) = @_;
501    
502     my $gpg = GnuPG::Interface->new();
503     $gpg->call( $CONFIG{'gpg'} );
504 weasel 131 if (defined $gnupghome) {
505     $gpg->options->hash_init(
506     'homedir' => $gnupghome,
507     'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
508     'armor' => 1 );
509     } else {
510     $gpg->options->hash_init(
511     'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
512     'armor' => 1 );
513     };
514 weasel 5 $gpg->options->meta_interactive( 0 );
515     my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
516     my $pid = $gpg->export_keys(handles => $handles, command_args => [ $keyid ]);
517     my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
518     waitpid $pid, 0;
519    
520     return $stdout;
521     };
522    
523 weasel 97 ######
524     # import a key from the scalar $asciikey into a gpg homedirectory in $tempdir
525     ######
526     sub import_key($$) {
527 weasel 100 my ($gnupghome, $asciikey) = @_;
528 weasel 97
529     my $gpg = GnuPG::Interface->new();
530     $gpg->call( $CONFIG{'gpg'} );
531 weasel 130 $gpg->options->hash_init(
532     'homedir' => $gnupghome,
533     'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ] );
534 weasel 97 $gpg->options->meta_interactive( 0 );
535     my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
536     my $pid = $gpg->import_keys(handles => $handles);
537     my ($stdout, $stderr, $status) = readwrite_gpg($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd);
538     waitpid $pid, 0;
539    
540     if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
541     return undef;
542     };
543     return 1;
544     };
545    
546    
547     ######
548     # Send an email to $address. If $can_encrypt is true then the mail
549     # will be PGP/MIME encrypted to $longkeyid.
550     #
551     # $longkeyid, $uid, and @attached will be used in the email and the template.
552     ######
553 weasel 5 #send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
554     sub send_mail($$$@) {
555     my ($address, $can_encrypt, $key_id, @keys) = @_;
556    
557 weasel 93 my $template = Text::Template->new(TYPE => 'STRING', SOURCE => $CONFIG{'mail-template'})
558     or die "Error creating template: $Text::Template::ERROR";
559 weasel 5
560 weasel 93 my @uids;
561 weasel 5 for my $key (@keys) {
562 weasel 93 push @uids, $key->{'text'};
563 weasel 5 };
564 weasel 93 my $message = $template->fill_in(HASH => { key => $key_id,
565     uids => \@uids,
566     owner => $CONFIG{'owner'}})
567     or die "Error filling template in: $Text::Template::ERROR";
568 weasel 5
569     my $message_entity = MIME::Entity->build(
570     Type => "text/plain",
571     Charset => "utf-8",
572     Disposition => 'inline',
573     Data => $message);
574    
575     my @key_entities;
576     for my $key (@keys) {
577     $message_entity->attach(
578     Type => "application/pgp-keys",
579     Disposition => 'attachment',
580     Encoding => "7bit",
581     Description => "PGP Key 0x$key_id, uid ".($key->{'text'}).' ('.($key->{'serial'}).')',
582     Data => $key->{'key'},
583     Filename => "0x$key_id.".$key->{'serial'}.".asc");
584     };
585    
586     if ($can_encrypt) {
587     my $message = $message_entity->stringify();
588    
589     my $gpg = GnuPG::Interface->new();
590     $gpg->call( $CONFIG{'gpg'} );
591     $gpg->options->hash_init( 'homedir' => $GNUPGHOME,
592 weasel 130 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
593 weasel 5 'armor' => 1 );
594     $gpg->options->meta_interactive( 0 );
595     my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
596     $gpg->options->push_recipients( $key_id );
597     $gpg->options->push_recipients( $CONFIG{'also-encrypt-to'} ) if defined $CONFIG{'also-encrypt-to'};
598     my $pid = $gpg->encrypt(handles => $handles);
599     my ($stdout, $stderr, $status) = readwrite_gpg($message, $inputfd, $stdoutfd, $stderrfd, $statusfd);
600     waitpid $pid, 0;
601     if ($stdout eq '') {
602     warn ("No data from gpg for list-key $key_id\n");
603     next;
604     };
605     $message = $stdout;
606    
607     $message_entity = MIME::Entity->build(
608     Type => 'multipart/encrypted; protocol="application/pgp-encrypted"');
609    
610     $message_entity->attach(
611     Type => "application/pgp-encrypted",
612     Disposition => 'attachment',
613     Encoding => "7bit",
614     Data => "Version: 1\n");
615    
616     $message_entity->attach(
617     Type => "application/octet-stream",
618     Filename => 'msg.asc',
619     Disposition => 'inline',
620     Encoding => "7bit",
621     Data => $message);
622     };
623    
624     $message_entity->head->add("Subject", "Your signed PGP key 0x$key_id");
625     $message_entity->head->add("To", $address);
626 weasel 28 $message_entity->head->add("From", '"'.$CONFIG{'owner'}.'" <'.$CONFIG{'email'}.'>');
627 myon-guest 101 $message_entity->head->add("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'};
628 weasel 5 $message_entity->head->add("User-Agent", $USER_AGENT);
629     $message_entity->send();
630     $message_entity->stringify();
631     };
632    
633 weasel 97 ######
634     # clean up a UID so that it can be used on the FS.
635     ######
636 weasel 15 sub sanitize_uid($) {
637     my ($uid) = @_;
638    
639 weasel 16 my $good_uid = $uid;
640     $good_uid =~ tr#/:\\#_#;
641 weasel 15 trace2("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid;
642     return $good_uid;
643     };
644    
645 weasel 100 sub delete_signatures($$$$$$) {
646     my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $keyids) =@_;
647 weasel 97
648 weasel 100 my $signed_by_me = 0;
649    
650 weasel 97 my ($stdout, $stderr, $status) =
651     readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1);
652    
653     while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) {
654     # sig:?::17:EA2199412477CAF8:1058095214:::::13x:
655     my @sigline = grep { /^sig/ } (split /\n/, $stdout);
656     $stdout =~ s/\n/\\n/g;
657     notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX
658     my $line = pop @sigline;
659     my $answer = "no";
660     if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance
661     debug("[sigremoval] doing line $line.");
662     my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line;
663     if ($signer eq $longkeyid) {
664     debug("[sigremoval] selfsig ($signer).");
665     $answer = "no";
666     } elsif (grep { $signer eq $_ } @{$keyids}) {
667     debug("[sigremoval] signed by us ($signer).");
668     $answer = "no";
669     $signed_by_me = $signed_by_me > $created ? $signed_by_me : $created;
670     } else {
671     debug("[sigremoval] not interested in that sig ($signer).");
672     $answer = "yes";
673     };
674     } else {
675     debug("[sigremoval] no sig line here, only got: ".$stdout);
676     };
677     ($stdout, $stderr, $status) =
678     readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1);
679     };
680 weasel 100
681     return $signed_by_me;
682 weasel 97 };
683    
684    
685    
686 weasel 5 my $USER;
687     my @KEYIDS;
688 weasel 95 my $params;
689 weasel 5
690 weasel 95 Getopt::Long::config('bundling');
691     if (!GetOptions (
692     '-h' => \$params->{'help'},
693     '--help' => \$params->{'help'},
694     '--version' => \$params->{'version'},
695     '-V' => \$params->{'version'},
696     '-u=s' => \$params->{'local-user'},
697     '--local-user=s' => \$params->{'local-user'},
698 weasel 109 '-e' => \$params->{'export-old'},
699     '--export-old' => \$params->{'export-old'},
700     '-E' => \$params->{'no-export-old'},
701     '--no-export-old' => \$params->{'no-export-old'},
702 weasel 95 '-m' => \$params->{'mail'},
703     '--mail' => \$params->{'mail'},
704     '-M' => \$params->{'no-mail'},
705     '--no-mail' => \$params->{'no-mail'},
706     '-R' => \$params->{'no-download'},
707     '--no-download' => \$params->{'no-download'},
708 myon-guest 106 '-S' => \$params->{'no-sign'},
709     '--no-sign' => \$params->{'no-sign'},
710 weasel 95 )) {
711     usage(\*STDERR, 1);
712     };
713     if ($params->{'help'}) {
714     usage(\*STDOUT, 0);
715     };
716     if ($params->{'version'}) {
717     version(\*STDOUT);
718     exit(0);
719     };
720     usage(\*STDERR, 1) unless scalar @ARGV >= 1;
721 myon-guest 79
722 weasel 95
723    
724     if ($params->{'local-user'}) {
725     $USER = $params->{'local-user'};
726 myon-guest 58 $USER =~ s/^0x//i;
727 weasel 127 unless ($USER =~ /^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/i) {
728 weasel 5 print STDERR "-u $USER is not a keyid.\n";
729 weasel 95 usage(\*STDERR, 1);
730 weasel 5 };
731     $USER = uc($USER);
732     };
733 weasel 95
734 weasel 5 for my $keyid (@ARGV) {
735 myon-guest 58 $keyid =~ s/^0x//i;
736 weasel 127 unless ($keyid =~ /^([A-F0-9]{8}|[A-F0-9]{16}||[A-F0-9]{40})$/i) {
737 weasel 132 if ($keyid =~ /^[A-F0-9]{32}$/) {
738     info("Ignoring v3 fingerprint $keyid. v3 keys are obsolete.");
739     next;
740     };
741 weasel 5 print STDERR "$keyid is not a keyid.\n";
742 weasel 95 usage(\*STDERR, 1);
743 weasel 5 };
744     push @KEYIDS, uc($keyid);
745     };
746    
747 weasel 95 $CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-download'};
748     $CONFIG{'no-mail'} = $params->{'no-mail'} if defined $params->{'no-mail'};
749     $CONFIG{'mail'} = $params->{'mail'} if defined $params->{'mail'};
750 myon-guest 106 $CONFIG{'no-sign'} = $params->{'no-sign'} if defined $params->{'no-sign'};
751 weasel 5
752 weasel 18
753 myon-guest 41 #################
754     # import own keys
755     #################
756 weasel 131 for my $keyid (@{$CONFIG{'keyid'}}) {
757 myon-guest 41 my $gpg = GnuPG::Interface->new();
758     $gpg->call( $CONFIG{'gpg'} );
759     $gpg->options->hash_init(
760     'homedir' => $GNUPGHOME,
761 weasel 131 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode --fast-list-mode } ] );
762 myon-guest 41 $gpg->options->meta_interactive( 0 );
763     my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
764 weasel 131 my $pid = $gpg->list_public_keys(handles => $handles, command_args => $keyid);
765 myon-guest 41 my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
766     waitpid $pid, 0;
767 weasel 131
768 myon-guest 41 if ($stdout eq '') {
769 weasel 131 warn ("No data from gpg for list-key\n"); # There should be at least 'tru:' everywhere.
770 myon-guest 41 };
771 weasel 131 unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) {
772     info("Key $keyid not found in caff's home. Getting it from your normal GnuPGHome.");
773     my $key = export_key(undef, $keyid);
774     if (!defined $key || $key eq '') {
775     warn ("Did not get key $keyid from your normal GnuPGHome\n");
776     next;
777     };
778     my $result = import_key($GNUPGHOME, $key);
779     unless ($result) {
780     warn ("Could not import $keyid into caff's gnupghome.\n");
781     next;
782     };
783 myon-guest 41 }
784 weasel 131 }
785 myon-guest 41
786 weasel 5 #############################
787     # receive keys from keyserver
788     #############################
789     my @keyids_ok;
790 weasel 95 if ($CONFIG{'no-download'}) {
791 weasel 18 @keyids_ok = @KEYIDS;
792     } else {
793 myon-guest 106 info ("fetching keys, this will take a while...");
794    
795 weasel 18 my $gpg = GnuPG::Interface->new();
796     $gpg->call( $CONFIG{'gpg'} );
797     $gpg->options->hash_init(
798     'homedir' => $GNUPGHOME,
799 weasel 130 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always }, '--keyserver='.$CONFIG{'keyserver'} ] );
800 weasel 18 $gpg->options->meta_interactive( 0 );
801     my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
802 myon-guest 105 my $pid = $gpg->recv_keys(handles => $handles, command_args => [ @KEYIDS ]);
803     my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
804     waitpid $pid, 0;
805 weasel 18
806 weasel 5 # [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F
807     # [GNUPG:] NODATA 1
808     # [GNUPG:] NODATA 1
809     # [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039
810 myon-guest 106 my %local_keyids = map { $_ => 1 } @KEYIDS;
811 myon-guest 105 for my $line (split /\n/, $status) {
812 weasel 110 if ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{40})/) {
813     my $imported_key = $1;
814     my $whole_fpr = $imported_key;
815     my $long_keyid = substr($imported_key, -16);
816     my $short_keyid = substr($imported_key, -8);
817     my $speced_key;
818     for my $spec (($whole_fpr, $long_keyid, $short_keyid)) {
819     $speced_key = $spec if $local_keyids{$spec};
820     };
821     unless ($speced_key) {
822     notice ("Imported unexpected key; got: $imported_key\n");
823 myon-guest 105 next;
824 weasel 30 };
825 weasel 110 debug ("Imported $imported_key for $speced_key");
826     delete $local_keyids{$speced_key};
827 myon-guest 105 unshift @keyids_ok, $imported_key;
828 myon-guest 107 } elsif ($line =~ /^\[GNUPG:\] (NODATA|IMPORT_RES|IMPORTED) /) {
829 myon-guest 106 } else {
830     notice ("got unknown reply from gpg: $line");
831 myon-guest 105 }
832 weasel 30 };
833 myon-guest 106 if (scalar %local_keyids) {
834     notice ("Import failed for: ". (join ' ', keys %local_keyids).".");
835     exit 1 unless ask ("Some keys could not be imported - continue anyway?", 0);
836     }
837 weasel 18 };
838 weasel 5
839 myon-guest 106 unless (@keyids_ok) {
840     notice ("No keys to sign found");
841     exit 0;
842     }
843    
844 weasel 5 ###########
845     # sign keys
846     ###########
847 kink-guest 126 if ($CONFIG{'ask-sign'} && ! $CONFIG{'no-sign'}) {
848     $CONFIG{'no-sign'} = ! ask("Continue with signing?", 1);
849     }
850    
851 weasel 18 unless ($CONFIG{'no-sign'}) {
852 weasel 35 info("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key");
853 weasel 18 for my $keyid (@keyids_ok) {
854     my @command;
855     push @command, $CONFIG{'gpg-sign'};
856     push @command, '--local-user', $USER if (defined $USER);
857     push @command, "--homedir=$GNUPGHOME";
858     push @command, '--secret-keyring', $CONFIG{'secret-keyring'};
859 weasel 130 push @command, '--no-auto-check-trustdb';
860     push @command, '--trust-model=always';
861 weasel 34 push @command, '--edit', $keyid;
862     push @command, 'sign';
863 myon-guest 119 push @command, split ' ', $CONFIG{'gpg-sign-args'} || "";
864 weasel 18 print join(' ', @command),"\n";
865     system (@command);
866     };
867 weasel 5 };
868    
869     ##################
870     # export and prune
871     ##################
872     KEYS:
873     for my $keyid (@keyids_ok) {
874     # get key listing
875     #################
876 weasel 18 my $gpg = GnuPG::Interface->new();
877 weasel 5 $gpg->call( $CONFIG{'gpg'} );
878 weasel 130 $gpg->options->hash_init(
879     'homedir' => $GNUPGHOME,
880     'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode } ] );
881 weasel 5 $gpg->options->meta_interactive( 0 );
882 weasel 18 my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
883     my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]);
884     my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
885 weasel 5 waitpid $pid, 0;
886     if ($stdout eq '') {
887     warn ("No data from gpg for list-key $keyid\n");
888     next;
889     };
890     my @publine = grep { /^pub/ } (split /\n/, $stdout);
891 weasel 90 if (scalar @publine == 0) {
892     warn ("No public keys found with list-key $keyid (note that caff uses its own keyring in $GNUPGHOME).\n");
893     next;
894     };
895 weasel 88 my (undef, undef, undef, undef, $longkeyid, undef, undef, undef, undef, undef, undef, $flags) = split /:/, pop @publine;
896 weasel 89 if (scalar @publine > 0) {
897     warn ("More than one key matched $keyid. Try to specify the long keyid or fingerprint\n");
898     next;
899     };
900 weasel 5 unless (defined $longkeyid) {
901 weasel 89 warn ("Didn't find public keyid in --list-key of key $keyid.\n");
902 weasel 5 next;
903     };
904 weasel 89 unless (defined $flags) {
905     warn ("Didn't find flags in --list-key of key $keyid.\n");
906     next;
907     };
908     my $can_encrypt = $flags =~ /E/;
909 weasel 5
910     # export the key
911     ################
912     my $asciikey = export_key($GNUPGHOME, $keyid);
913     if ($asciikey eq '') {
914     warn ("No data from gpg for export $keyid\n");
915     next;
916     };
917    
918     my @UIDS;
919     my $uid_number = 0;
920     while (1) {
921     my $this_uid_text = '';
922     $uid_number++;
923 enrico 25 debug("Doing key $keyid, uid $uid_number");
924 weasel 97 my $tempdir = tempdir( "caff-$keyid-XXXXX", DIR => '/tmp/', CLEANUP => 1);
925 weasel 5
926     # import into temporary gpghome
927     ###############################
928 weasel 97 my $result = import_key($tempdir, $asciikey);
929     unless ($result) {
930 weasel 5 warn ("Could not import $keyid into temporary gnupg.\n");
931     next;
932     };
933    
934     # prune it
935     ##########
936     $gpg = GnuPG::Interface->new();
937     $gpg->call( $CONFIG{'gpg-delsig'} );
938     $gpg->options->hash_init(
939     'homedir' => $tempdir,
940 weasel 130 'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode --command-fd=0 --no-tty } ] );
941 weasel 5 ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
942     $pid = $gpg->wrap_call(
943     commands => [ '--edit' ],
944     command_args => [ $keyid ],
945     handles => $handles );
946    
947     debug("Starting edit session");
948     ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
949    
950     # delete other uids
951     ###################
952     my $number_of_subkeys = 0;
953     my $i = 1;
954     my $have_one = 0;
955     my $is_uat = 0;
956     my $delete_some = 0;
957     debug("Parsing stdout output.");
958     for my $line (split /\n/, $stdout) {
959     debug("Checking line $line");
960 weasel 88 my ($type, undef, undef, undef, undef, undef, undef, undef, undef, $uidtext) = split /:/, $line;
961 weasel 5 if ($type eq 'sub') {
962     $number_of_subkeys++;
963     };
964     next unless ($type eq 'uid' || $type eq 'uat');
965     debug("line is interesting.");
966     if ($uid_number != $i) {
967     debug("mark for deletion.");
968     readwrite_gpg("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
969 weasel 100 $delete_some++;
970 weasel 5 } else {
971     debug("keep it.");
972     $have_one = 1;
973 weasel 98 $this_uid_text = ($type eq 'uid') ? $uidtext : '[attribute]';
974 weasel 5 $is_uat = $type eq 'uat';
975     };
976     $i++;
977     };
978     debug("Parsing stdout output done.");
979     unless ($have_one) {
980 enrico 25 debug("Uid ".($uid_number-1)." was the last, there is no $uid_number.");
981 weasel 5 info("key $keyid done.");
982     last;
983     };
984 weasel 100
985     my $prune_some_sigs_on_uid;
986     my $prune_all_sigs_on_uid;
987 weasel 99 if ($is_uat) {
988 weasel 100 debug("handling attribute userid of key $keyid.");
989     if ($uid_number == 1) {
990     debug(" attribute userid is #1, unmarking #2 for deletion.");
991     readwrite_gpg("2\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
992     $delete_some--;
993     $prune_some_sigs_on_uid = 1;
994     $prune_all_sigs_on_uid = 2;
995     } else {
996     debug("attribute userid is not #1, unmarking #1 for deletion.");
997     readwrite_gpg("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
998     $delete_some--;
999     $prune_some_sigs_on_uid = 2;
1000     $prune_all_sigs_on_uid = 1;
1001     };
1002     } else {
1003     $prune_some_sigs_on_uid = 1;
1004 weasel 99 };
1005 weasel 100
1006 weasel 5 if ($delete_some) {
1007 weasel 100 debug("need to delete $delete_some uids.");
1008 weasel 5 readwrite_gpg("deluid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELUID_PROMPT, nocloseinput => 1);
1009     readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1010     };
1011    
1012     # delete subkeys
1013     ################
1014     if ($number_of_subkeys > 0) {
1015     for (my $i=1; $i<=$number_of_subkeys; $i++) {
1016     readwrite_gpg("key $i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1017     };
1018     readwrite_gpg("delkey\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSUBKEY_PROMPT, nocloseinput => 1);
1019     readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
1020     };
1021    
1022     # delete signatures
1023     ###################
1024 weasel 100 readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig
1025     my $signed_by_me = delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $CONFIG{'keyid'});
1026     readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig
1027     if (defined $prune_all_sigs_on_uid) {
1028     readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig
1029     delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, []);
1030     readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig
1031     };
1032 weasel 5
1033 weasel 97
1034 weasel 5 readwrite_gpg("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd);
1035     waitpid $pid, 0;
1036    
1037 weasel 89 my $asciikey = export_key($tempdir, $keyid);
1038 weasel 5 if ($asciikey eq '') {
1039 weasel 89 warn ("No data from gpg for export $keyid\n");
1040 weasel 5 next;
1041     };
1042    
1043     if ($signed_by_me) {
1044     if ($NOW - $signed_by_me > $CONFIG{'export-sig-age'} ) {
1045 weasel 109 my $write = ask("Signature on $this_uid_text is old. Export?", 0, $params->{'export-old'}, $params->{'no-export-old'});
1046 weasel 5 next unless $write;
1047     };
1048     my $keydir = "$KEYSBASE/$DATE_STRING";
1049     -d $keydir || mkpath($keydir , 0, 0700) or die ("Cannot create $keydir $!\n");
1050    
1051 weasel 15 my $keyfile = "$keydir/$longkeyid.key.$uid_number.".sanitize_uid($this_uid_text).".asc";
1052 weasel 91 open (KEY, ">$keyfile") or die ("Cannot open $keyfile: $!\n");
1053 weasel 5 print KEY $asciikey;
1054     close KEY;
1055    
1056 weasel 98 push @UIDS, { text => $this_uid_text, key => $asciikey, serial => $uid_number, "is_uat" => $is_uat };
1057 weasel 5
1058     info("$longkeyid $uid_number $this_uid_text done.");
1059     } else {
1060     info("$longkeyid $uid_number $this_uid_text is not signed by me, not writing.");
1061     };
1062     };
1063    
1064     if (scalar @UIDS == 0) {
1065     info("found no signed uids for $keyid");
1066     } else {
1067 weasel 95 next if $CONFIG{'no-mail'}; # do not send mail
1068 myon-guest 79
1069     my @attached;
1070 weasel 5 for my $uid (@UIDS) {
1071 weasel 15 trace("UID: $uid->{'text'}\n");
1072 weasel 98 if ($uid->{'is_uat'}) {
1073     my $attach = ask("UID $uid->{'text'} is an attribute UID, attach it to every email sent?", 1);
1074     push @attached, $uid if $attach;
1075     } elsif ($uid->{'text'} !~ /@/) {
1076 weasel 5 my $attach = ask("UID $uid->{'text'} is no email address, attach it to every email sent?", 1);
1077 weasel 16 push @attached, $uid if $attach;
1078 weasel 5 };
1079     };
1080    
1081     notice("Key has no encryption capabilities, mail will be sent unencrypted") unless $can_encrypt;
1082     for my $uid (@UIDS) {
1083 weasel 98 if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) {
1084 weasel 5 my $address = $uid->{'text'};
1085     $address =~ s/.*<(.*)>.*/$1/;
1086 weasel 133 if (ask("Send signature for $uid->{'text'} to '$address'?", 1, $CONFIG{'mail'})) {
1087 weasel 5 my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
1088    
1089     my $keydir = "$KEYSBASE/$DATE_STRING";
1090 weasel 15 my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid($uid->{'text'});
1091 weasel 91 open (KEY, ">$mailfile") or die ("Cannot open $mailfile: $!\n");
1092 weasel 5 print KEY $mail;
1093     close KEY;
1094     };
1095     };
1096     };
1097     };
1098    
1099     };

Properties

Name Value
svn:executable *
svn:keywords Id Rev

  ViewVC Help
Powered by ViewVC 1.1.5