| 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 |
|
|
}; |