| 3 |
# caff -- CA - Fire and Forget |
# caff -- CA - Fire and Forget |
| 4 |
# $Id$ |
# $Id$ |
| 5 |
# |
# |
| 6 |
# Copyright (c) 2004 Peter Palfrader <peter@palfrader.org> |
# Copyright (c) 2004, 2005 Peter Palfrader <peter@palfrader.org> |
| 7 |
|
# Copyright (c) 2005 Christoph Berg <cb@df7cb.de> |
| 8 |
# |
# |
| 9 |
# All rights reserved. |
# All rights reserved. |
| 10 |
# |
# |
| 40 |
|
|
| 41 |
=over |
=over |
| 42 |
|
|
| 43 |
=item B<caff> [-u I<yourkeyid>] I<keyid> [I<keyid> ..] |
=item B<caff> [-eEmMRS] [-u I<yourkeyid>] I<keyid> [I<keyid> ..] |
| 44 |
|
|
| 45 |
=back |
=back |
| 46 |
|
|
| 50 |
of keyids on the command line, fetches them from a keyserver and calls GnuPG so |
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 |
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 |
including the one UID that we send to in each mail, pruned from all but self |
| 53 |
sigs and sigs done by you. |
sigs and sigs done by you. The mailed key is encrypted with itself as a means |
| 54 |
|
to verify that key belongs to the recipient. |
| 55 |
|
|
| 56 |
=head1 OPTIONS |
=head1 OPTIONS |
| 57 |
|
|
| 58 |
=over |
=over |
| 59 |
|
|
| 60 |
=item B<-u> I<yourkeyid> |
=item B<-e>, B<--export-old> |
| 61 |
|
|
| 62 |
|
Export old signatures. Default is to ask the user for each old signature. |
| 63 |
|
|
| 64 |
|
=item B<-E>, B<--no-export-old> |
| 65 |
|
|
| 66 |
|
Do not export old signatures. Default is to ask the user for each old |
| 67 |
|
signature. |
| 68 |
|
|
| 69 |
|
=item B<-m>, B<--mail> |
| 70 |
|
|
| 71 |
|
Send mail after signing. Default is to ask the user for each uid. |
| 72 |
|
|
| 73 |
|
=item B<-M>, B<--no-mail> |
| 74 |
|
|
| 75 |
|
Do not send mail after signing. Default is to ask the user for each uid. |
| 76 |
|
|
| 77 |
|
=item B<-R>, B<--no-download> |
| 78 |
|
|
| 79 |
|
Do not retrieve the key to be signed from a keyserver. |
| 80 |
|
|
| 81 |
|
=item B<-S>, B<--no-sign> |
| 82 |
|
|
| 83 |
|
Do not sign the keys. |
| 84 |
|
|
| 85 |
|
=item B<-u> I<yourkeyid>, B<--local-user> I<yourkeyid> |
| 86 |
|
|
| 87 |
Select the key that is used for signing, in case you have more than one key. |
Select the key that is used for signing, in case you have more than one key. |
| 88 |
|
|
| 89 |
|
=item B<--key-file> I<file> |
| 90 |
|
|
| 91 |
|
Import keys from file. Can be supplied more than once. |
| 92 |
|
|
| 93 |
=back |
=back |
| 94 |
|
|
| 95 |
=head1 FILES |
=head1 FILES |
| 98 |
|
|
| 99 |
=item $HOME/.caffrc - configuration file |
=item $HOME/.caffrc - configuration file |
| 100 |
|
|
| 101 |
|
=item $HOME/.caff/keys/yyyy-mm-dd/ - processed keys |
| 102 |
|
|
| 103 |
|
=item $HOME/.caff/gnupghome/ - caff's working dir for gpg |
| 104 |
|
|
| 105 |
|
=item $HOME/.caff/gnupghome/gpg.conf - gpg configuration |
| 106 |
|
|
| 107 |
|
useful options include use-agent, default-cert-level, etc. |
| 108 |
|
|
| 109 |
=back |
=back |
| 110 |
|
|
| 111 |
=head1 CONFIGURATION FILE OPTIONS |
=head1 CONFIGURATION FILE OPTIONS |
| 112 |
|
|
| 113 |
The configuration file is a perl script that sets values in the hash B<%CONFIG>. |
The configuration file is a perl script that sets values in the hash B<%CONFIG>. |
| 114 |
|
The file is generated when it does not exist. |
| 115 |
|
|
| 116 |
Example: |
Example: |
| 117 |
|
|
| 118 |
$CONFIG{'owner'} = 'Peter Palfrader'; |
$CONFIG{owner} = q{Peter Palfrader}; |
| 119 |
$CONFIG{'email'} = 'peter@palfrader.org'; |
$CONFIG{email} = q{peter@palfrader.org}; |
| 120 |
|
$CONFIG{keyid} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ]; |
| 121 |
|
|
| 122 |
=head2 Valid keys |
=head2 Required basic settings |
| 123 |
|
|
| 124 |
=over |
=over |
| 125 |
|
|
|
=item B<caffhome> [string] |
|
|
|
|
|
Base directory for the files caff stores. Default: B<$HOME/.caff/>. |
|
|
|
|
| 126 |
=item B<owner> [string] |
=item B<owner> [string] |
| 127 |
|
|
| 128 |
Your name. B<REQUIRED>. |
Your name. B<REQUIRED>. |
| 137 |
in the pruning step. If you select a key using B<-u> it has to be in |
in the pruning step. If you select a key using B<-u> it has to be in |
| 138 |
this list. B<REQUIRED>. |
this list. B<REQUIRED>. |
| 139 |
|
|
| 140 |
=item B<export-sig-age> [seconds] |
=head2 General settings |
| 141 |
|
|
| 142 |
Don't export UIDs by default, on which your latest signature is older |
=item B<caffhome> [string] |
|
than this age. Default: B<24*60*60> (i.e. one day). |
|
| 143 |
|
|
| 144 |
=item B<keyserver> [string] |
Base directory for the files caff stores. Default: B<$HOME/.caff/>. |
| 145 |
|
|
| 146 |
Keyserver to download keys from. Default: B<subkeys.pgp.net>. |
=head2 GnuPG settings |
| 147 |
|
|
| 148 |
=item B<gpg> [string] |
=item B<gpg> [string] |
| 149 |
|
|
| 154 |
Path to the GnuPG binary which is used to sign keys. Default: what |
Path to the GnuPG binary which is used to sign keys. Default: what |
| 155 |
B<gpg> is set to. |
B<gpg> is set to. |
| 156 |
|
|
| 157 |
=item B<gpg-sdelsig> [string] |
=item B<gpg-delsig> [string] |
| 158 |
|
|
| 159 |
Path to the GnuPG binary which is used to split off signatures. This is |
Path to the GnuPG binary which is used to split off signatures. This was |
| 160 |
needed while the upstream GnuPG is not fixed (there are 2 bugs in the |
needed while the upstream GnuPG was not fixed. Default: what B<gpg> |
| 161 |
Debian Bug Tracking System). Default: what B<gpg> is set to. |
is set to. |
| 162 |
|
|
| 163 |
=item B<secret-keyring> [string] |
=item B<secret-keyring> [string] |
| 164 |
|
|
| 165 |
Path to your secret keyring. Default: B<$HOME/.gnupg/secring.gpg>. |
Path to your secret keyring. Default: B<$HOME/.gnupg/secring.gpg>. |
| 166 |
|
|
| 167 |
|
=item B<also-encrypt-to> [keyid] |
| 168 |
|
|
| 169 |
|
An additional keyid to encrypt messages to. Default: none. |
| 170 |
|
|
| 171 |
|
=item B<gpg-sign-args> [string] |
| 172 |
|
|
| 173 |
|
Additional arguments to pass to gpg. Default: none. |
| 174 |
|
|
| 175 |
|
=head2 Keyserver settings |
| 176 |
|
|
| 177 |
|
=item B<keyserver> [string] |
| 178 |
|
|
| 179 |
|
Keyserver to download keys from. Default: B<subkeys.pgp.net>. |
| 180 |
|
|
| 181 |
|
=item B<no-download> [boolean] |
| 182 |
|
|
| 183 |
|
If true, then skip the step of fetching keys from the keyserver. |
| 184 |
|
Default: B<0>. |
| 185 |
|
|
| 186 |
|
=item B<key-files> [list of files] |
| 187 |
|
|
| 188 |
|
A list of files containing keys to be imported. |
| 189 |
|
|
| 190 |
|
=head2 Signing settings |
| 191 |
|
|
| 192 |
|
=item B<no-sign> [boolean] |
| 193 |
|
|
| 194 |
|
If true, then skip the signing step. Default: B<0>. |
| 195 |
|
|
| 196 |
|
=item B<ask-sign> [boolean] |
| 197 |
|
|
| 198 |
|
If true, then pause before continuing to the signing step. |
| 199 |
|
This is useful for offline signing. Default: B<0>. |
| 200 |
|
|
| 201 |
|
=item B<export-sig-age> [seconds] |
| 202 |
|
|
| 203 |
|
Don't export UIDs by default, on which your latest signature is older |
| 204 |
|
than this age. Default: B<24*60*60> (i.e. one day). |
| 205 |
|
|
| 206 |
|
=head2 Mail settings |
| 207 |
|
|
| 208 |
|
=item B<mail> [boolean] |
| 209 |
|
|
| 210 |
|
Do not prompt for sending mail, just do it. Default: B<0>. |
| 211 |
|
|
| 212 |
|
=item B<no-mail> [boolean] |
| 213 |
|
|
| 214 |
|
Do not prompt for sending mail. The messages are still written to |
| 215 |
|
$CONFIG{caffhome}/keys/. Default: B<0>. |
| 216 |
|
|
| 217 |
|
=item B<mail-template> [string] |
| 218 |
|
|
| 219 |
|
Email template which is used as the body text for the email sent out |
| 220 |
|
instead of the default text if specified. The following perl variables |
| 221 |
|
can be used in the template: |
| 222 |
|
|
| 223 |
|
=over |
| 224 |
|
|
| 225 |
|
=item B<{owner}> [string] |
| 226 |
|
|
| 227 |
|
Your name as specified in the L<B<owner>|/item_owner__5bstring_5d> setting. |
| 228 |
|
|
| 229 |
|
=item B<{key}> [string] |
| 230 |
|
|
| 231 |
|
The keyid of the key you signed. |
| 232 |
|
|
| 233 |
|
=item B<{@uids}> [array] |
| 234 |
|
|
| 235 |
|
The UIDs for which signatures are included in the mail. |
| 236 |
|
|
| 237 |
=back |
=back |
| 238 |
|
|
| 239 |
=head1 AUTHOR |
=item B<reply-to> [string] |
| 240 |
|
|
| 241 |
|
Add a Reply-To: header to messages sent. Default: none. |
| 242 |
|
|
| 243 |
|
=item B<bcc> [string] |
| 244 |
|
|
| 245 |
|
Address to send blind carbon copies to when sending mail. |
| 246 |
|
Default: none. |
| 247 |
|
|
| 248 |
Peter Palfrader <peter@palfrader.org> |
=back |
| 249 |
|
|
| 250 |
|
=head1 AUTHORS |
| 251 |
|
|
| 252 |
|
=over |
| 253 |
|
|
| 254 |
|
=item Peter Palfrader <peter@palfrader.org> |
| 255 |
|
|
| 256 |
|
=item Christoph Berg <cb@df7cb.de> |
| 257 |
|
|
| 258 |
|
=back |
| 259 |
|
|
| 260 |
|
=head1 WEBSITE |
| 261 |
|
|
| 262 |
|
http://pgp-tools.alioth.debian.org/ |
| 263 |
|
|
| 264 |
|
=head1 SEE ALSO |
| 265 |
|
|
| 266 |
|
gpg(1), pgp-clean(1), /usr/share/doc/signing-party/caff/caffrc.sample. |
| 267 |
|
|
| 268 |
=cut |
=cut |
| 269 |
|
|
| 272 |
use English; |
use English; |
| 273 |
use File::Path; |
use File::Path; |
| 274 |
use File::Temp qw{tempdir}; |
use File::Temp qw{tempdir}; |
| 275 |
|
use Text::Template; |
| 276 |
use MIME::Entity; |
use MIME::Entity; |
| 277 |
use Fcntl; |
use Fcntl; |
| 278 |
use IO::Select; |
use IO::Select; |
| 279 |
|
use Getopt::Long; |
| 280 |
use GnuPG::Interface; |
use GnuPG::Interface; |
| 281 |
|
|
| 282 |
my %CONFIG; |
my %CONFIG; |
| 284 |
my ($REVISION_NUMER) = $REVISION =~ /(\d+)/; |
my ($REVISION_NUMER) = $REVISION =~ /(\d+)/; |
| 285 |
my $VERSION = "0.0.0.$REVISION_NUMER"; |
my $VERSION = "0.0.0.$REVISION_NUMER"; |
| 286 |
|
|
|
sub load_config() { |
|
|
my $config = $ENV{'HOME'} . '/.caffrc'; |
|
|
-f $config or die "No file $config present. See caffrc(5).\n"; |
|
|
unless (scalar eval `cat $config`) { |
|
|
die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR; |
|
|
}; |
|
| 287 |
|
|
|
$CONFIG{'caffhome'}=$ENV{'HOME'}.'/.caff' unless defined $CONFIG{'caffhome'}; |
|
|
die ("owner is not defined.\n") unless defined $CONFIG{'owner'}; |
|
|
die ("email is not defined.\n") unless defined $CONFIG{'email'}; |
|
|
die ("keyid is not defined.\n") unless defined $CONFIG{'keyid'}; |
|
|
die ("keyid is not an array ref\n") unless (ref $CONFIG{'keyid'} eq 'ARRAY'); |
|
|
for my $keyid (@{$CONFIG{'keyid'}}) { |
|
|
$keyid =~ /^[A-Fa-z0-9]{16}$/ or die ("key $keyid is not a long (16 digit) keyid.\n"); |
|
|
}; |
|
|
@{$CONFIG{'keyid'}} = map { uc } @{$CONFIG{'keyid'}}; |
|
|
$CONFIG{'export-sig-age'}= 24*60*60 unless defined $CONFIG{'export-sig-age'}; |
|
|
$CONFIG{'keyserver'} = 'subkeys.pgp.net' unless defined $CONFIG{'keyserver'}; |
|
|
$CONFIG{'gpg'} = 'gpg' unless defined $CONFIG{'gpg'}; |
|
|
$CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'}; |
|
|
$CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'}; |
|
|
$CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'}; |
|
|
}; |
|
| 288 |
|
|
| 289 |
sub notice($) { |
sub notice($) { |
| 290 |
my ($line) = @_; |
my ($line) = @_; |
| 307 |
#print "[trace2] $line\n"; |
#print "[trace2] $line\n"; |
| 308 |
}; |
}; |
| 309 |
|
|
| 310 |
|
|
| 311 |
|
sub generate_config() { |
| 312 |
|
notice("Error: \$LOGNAME is not set.\n") unless defined $ENV{'LOGNAME'}; |
| 313 |
|
my $gecos = defined $ENV{'LOGNAME'} ? (getpwnam($ENV{LOGNAME}))[6] : undef; |
| 314 |
|
my $email; |
| 315 |
|
my @keys; |
| 316 |
|
my $hostname = `hostname -f`; |
| 317 |
|
chomp $hostname; |
| 318 |
|
my ($Cgecos,$Cemail,$Ckeys) = ('','',''); |
| 319 |
|
if (defined $gecos) { |
| 320 |
|
$gecos =~ s/,.*//; |
| 321 |
|
|
| 322 |
|
my $gpg = GnuPG::Interface->new(); |
| 323 |
|
$gpg->call( 'gpg' ); |
| 324 |
|
$gpg->options->hash_init( |
| 325 |
|
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode } ] ); |
| 326 |
|
$gpg->options->meta_interactive( 0 ); |
| 327 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 328 |
|
my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $gecos ]); |
| 329 |
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 330 |
|
waitpid $pid, 0; |
| 331 |
|
|
| 332 |
|
if ($stdout eq '') { |
| 333 |
|
warn ("No data from gpg for list-key\n"); # There should be at least 'tru:' everywhere. |
| 334 |
|
}; |
| 335 |
|
|
| 336 |
|
@keys = ($stdout =~ /^pub:[^r:]*:(?:[^:]*:){2,2}([^:]+):/mg); |
| 337 |
|
unless (scalar @keys) { |
| 338 |
|
info("Error: No keys were found using \"gpg --list-public-keys '$gecos'\"."); |
| 339 |
|
@keys = qw{0123456789abcdef 89abcdef76543210}; |
| 340 |
|
$Ckeys = '#'; |
| 341 |
|
} |
| 342 |
|
($email) = ($stdout =~ /^uid:.*<(.+?@.+?)>.*:/m); |
| 343 |
|
unless (defined $email) { |
| 344 |
|
info("Error: No email address was found using \"gpg --list-public-keys '$gecos'\"."); |
| 345 |
|
$email = $ENV{'LOGNAME'}.'@'.$hostname; |
| 346 |
|
$Cemail = '#'; |
| 347 |
|
} |
| 348 |
|
} else { |
| 349 |
|
$gecos = 'Unknown Caff User'; |
| 350 |
|
$email = $ENV{'LOGNAME'}.'@'.$hostname; |
| 351 |
|
@keys = qw{0123456789abcdef 89abcdef76543210}; |
| 352 |
|
($Cgecos,$Cemail,$Ckeys) = ('#','#','#'); |
| 353 |
|
}; |
| 354 |
|
|
| 355 |
|
return <<EOT; |
| 356 |
|
# .caffrc -- vim:syntax=perl: |
| 357 |
|
# This file is in perl(1) format - see caff(1) for details. |
| 358 |
|
|
| 359 |
|
$Cgecos\$CONFIG{'owner'} = '$gecos'; |
| 360 |
|
$Cemail\$CONFIG{'email'} = '$email'; |
| 361 |
|
|
| 362 |
|
# you can get your long keyid from |
| 363 |
|
# gpg --with-colons --list-key <yourkeyid|name|emailaddress..> |
| 364 |
|
# |
| 365 |
|
# if you have a v4 key, it will simply be the last 16 digits of |
| 366 |
|
# your fingerprint. |
| 367 |
|
# |
| 368 |
|
# Example: |
| 369 |
|
# \$CONFIG{'keyid'} = [ qw{FEDCBA9876543210} ]; |
| 370 |
|
# or, if you have more than one key: |
| 371 |
|
# \$CONFIG{'keyid'} = [ qw{0123456789ABCDEF 89ABCDEF76543210} ]; |
| 372 |
|
|
| 373 |
|
$Ckeys\$CONFIG{'keyid'} = [ qw{@keys} ]; |
| 374 |
|
EOT |
| 375 |
|
}; |
| 376 |
|
|
| 377 |
|
sub check_executable($$) { |
| 378 |
|
# (GnuPG::Interface gives lousy errors when the gpg binary isn't found, |
| 379 |
|
# so we want to check manually.) |
| 380 |
|
my ($purpose, $fn) = @_; |
| 381 |
|
# Only check provided fnames with a slash in them. |
| 382 |
|
return unless defined $fn; |
| 383 |
|
if ($fn =~ m!/!) { |
| 384 |
|
die ("$PROGRAM_NAME: $purpose executable '$fn' not found.\n") unless -x $fn; |
| 385 |
|
} else { |
| 386 |
|
for my $p (split(':', $ENV{PATH})) { |
| 387 |
|
return if -x "$p/$fn"; |
| 388 |
|
}; |
| 389 |
|
die ("$PROGRAM_NAME: $purpose executable '$fn' not found on path.\n") unless -x $fn; |
| 390 |
|
}; |
| 391 |
|
}; |
| 392 |
|
|
| 393 |
|
sub load_config() { |
| 394 |
|
my $config = $ENV{'HOME'} . '/.caffrc'; |
| 395 |
|
unless (-f $config) { |
| 396 |
|
print "No configfile $config present, I will use this template:\n"; |
| 397 |
|
my $template = generate_config(); |
| 398 |
|
print "$template\nPlease edit $config and run caff again.\n"; |
| 399 |
|
open F, ">$config" or die "$config: $!"; |
| 400 |
|
print F $template; |
| 401 |
|
close F; |
| 402 |
|
exit(1); |
| 403 |
|
} |
| 404 |
|
unless (scalar eval `cat $config`) { |
| 405 |
|
die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR; |
| 406 |
|
}; |
| 407 |
|
|
| 408 |
|
$CONFIG{'caffhome'}=$ENV{'HOME'}.'/.caff' unless defined $CONFIG{'caffhome'}; |
| 409 |
|
die ("$PROGRAM_NAME: owner is not defined in $config.\n") unless defined $CONFIG{'owner'}; |
| 410 |
|
die ("$PROGRAM_NAME: email is not defined in $config.\n") unless defined $CONFIG{'email'}; |
| 411 |
|
die ("$PROGRAM_NAME: keyid is not defined in $config.\n") unless defined $CONFIG{'keyid'}; |
| 412 |
|
die ("$PROGRAM_NAME: keyid is not an array ref in $config.\n") unless (ref $CONFIG{'keyid'} eq 'ARRAY'); |
| 413 |
|
for my $keyid (@{$CONFIG{'keyid'}}) { |
| 414 |
|
$keyid =~ /^[A-F0-9]{16}$/i or die ("$PROGRAM_NAME: key $keyid is not a long (16 digit) keyid in $config.\n"); |
| 415 |
|
}; |
| 416 |
|
@{$CONFIG{'keyid'}} = map { uc } @{$CONFIG{'keyid'}}; |
| 417 |
|
$CONFIG{'export-sig-age'}= 24*60*60 unless defined $CONFIG{'export-sig-age'}; |
| 418 |
|
$CONFIG{'keyserver'} = 'subkeys.pgp.net' unless defined $CONFIG{'keyserver'}; |
| 419 |
|
$CONFIG{'gpg'} = 'gpg' unless defined $CONFIG{'gpg'}; |
| 420 |
|
$CONFIG{'gpg-sign'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-sign'}; |
| 421 |
|
$CONFIG{'gpg-delsig'} = $CONFIG{'gpg'} unless defined $CONFIG{'gpg-delsig'}; |
| 422 |
|
check_executable("gpg", $CONFIG{'gpg'}); |
| 423 |
|
check_executable("gpg-sign", $CONFIG{'gpg-sign'}); |
| 424 |
|
check_executable("gpg-delsig", $CONFIG{'gpg-delsig'}); |
| 425 |
|
$CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'}; |
| 426 |
|
$CONFIG{'no-download'} = 0 unless defined $CONFIG{'no-download'}; |
| 427 |
|
$CONFIG{'no-sign'} = 0 unless defined $CONFIG{'no-sign'}; |
| 428 |
|
$CONFIG{'key-files'} = () unless defined $CONFIG{'key-files'}; |
| 429 |
|
$CONFIG{'mail-template'} = <<'EOM' unless defined $CONFIG{'mail-template'}; |
| 430 |
|
Hi, |
| 431 |
|
|
| 432 |
|
please find attached the user id{(scalar @uids >= 2 ? 's' : '')}. |
| 433 |
|
{foreach $uid (@uids) { |
| 434 |
|
$OUT .= "\t".$uid."\n"; |
| 435 |
|
};} of your key {$key} signed by me. |
| 436 |
|
|
| 437 |
|
Note that I did not upload your key to any keyservers. |
| 438 |
|
If you have multiple user ids, I sent the signature for each user id |
| 439 |
|
separately to that user id's associated email address. You can import |
| 440 |
|
the signatures by running each through `gpg --import`. |
| 441 |
|
|
| 442 |
|
If you want this new signature to be available to others, please upload |
| 443 |
|
it yourself. With GnuPG this can be done using |
| 444 |
|
gpg --keyserver subkeys.pgp.net --send-key {$key} |
| 445 |
|
|
| 446 |
|
If you have any questions, don't hesitate to ask. |
| 447 |
|
|
| 448 |
|
Regards, |
| 449 |
|
{$owner} |
| 450 |
|
EOM |
| 451 |
|
}; |
| 452 |
|
|
| 453 |
sub make_gpg_fds() { |
sub make_gpg_fds() { |
| 454 |
my %fds = ( |
my %fds = ( |
| 455 |
stdin => IO::Handle->new(), |
stdin => IO::Handle->new(), |
| 465 |
|
|
| 466 |
trace("Entering readwrite_gpg."); |
trace("Entering readwrite_gpg."); |
| 467 |
|
|
| 468 |
my ($first_line, $dummy) = split /\n/, $in; |
my ($first_line, undef) = split /\n/, $in; |
| 469 |
debug("readwrite_gpg sends ".(defined $first_line ? $first_line : "<nothing>")); |
debug("readwrite_gpg sends ".(defined $first_line ? $first_line : "<nothing>")); |
| 470 |
|
|
| 471 |
local $INPUT_RECORD_SEPARATOR = undef; |
local $INPUT_RECORD_SEPARATOR = undef; |
| 486 |
|
|
| 487 |
my ($stdout, $stderr, $status) = ("", "", ""); |
my ($stdout, $stderr, $status) = ("", "", ""); |
| 488 |
my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'}; |
my $exitwhenstatusmatches = $options{'exitwhenstatusmatches'}; |
| 489 |
trace("doign stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches; |
trace("doing stuff until we find $exitwhenstatusmatches") if defined $exitwhenstatusmatches; |
| 490 |
|
|
| 491 |
|
my $readwrote_stuff_this_time = 0; |
| 492 |
|
my $do_not_wait_on_select = 0; |
| 493 |
my ($readyr, $readyw, $written); |
my ($readyr, $readyw, $written); |
| 494 |
while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) { |
while ($sout->count() > 0 || (defined($sin) && ($sin->count() > 0))) { |
| 495 |
if (defined $exitwhenstatusmatches) { |
if (defined $exitwhenstatusmatches) { |
| 496 |
if ($status =~ /$exitwhenstatusmatches/m) { |
if ($status =~ /$exitwhenstatusmatches/m) { |
| 497 |
trace("readwrite_gpg found match on $exitwhenstatusmatches"); |
trace("readwrite_gpg found match on $exitwhenstatusmatches"); |
| 498 |
last; |
if ($readwrote_stuff_this_time) { |
| 499 |
|
trace("read/write some more\n"); |
| 500 |
|
$do_not_wait_on_select = 1; |
| 501 |
|
} else { |
| 502 |
|
trace("that's it in our while loop.\n"); |
| 503 |
|
last; |
| 504 |
|
} |
| 505 |
}; |
}; |
| 506 |
}; |
}; |
| 507 |
|
|
| 508 |
|
$readwrote_stuff_this_time = 0; |
| 509 |
trace("select waiting for ".($sout->count())." fds."); |
trace("select waiting for ".($sout->count())." fds."); |
| 510 |
($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, 1); |
($readyr, $readyw, undef) = IO::Select::select($sout, $sin, undef, $do_not_wait_on_select ? 0 : 1); |
| 511 |
trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0)); |
trace("ready: write: ".(defined $readyw ? scalar @$readyw : 0 )."; read: ".(defined $readyr ? scalar @$readyr : 0)); |
| 512 |
for my $wfd (@$readyw) { |
for my $wfd (@$readyw) { |
| 513 |
|
$readwrote_stuff_this_time = 1; |
| 514 |
if (length($in) != $offset) { |
if (length($in) != $offset) { |
| 515 |
trace("writing to $wfd."); |
trace("writing to $wfd."); |
| 516 |
$written = $wfd->syswrite($in, length($in) - $offset, $offset); |
$written = $wfd->syswrite($in, length($in) - $offset, $offset); |
| 530 |
next unless (defined(@$readyr)); # Wait some more. |
next unless (defined(@$readyr)); # Wait some more. |
| 531 |
|
|
| 532 |
for my $rfd (@$readyr) { |
for my $rfd (@$readyr) { |
| 533 |
|
$readwrote_stuff_this_time = 1; |
| 534 |
if ($rfd->eof) { |
if ($rfd->eof) { |
| 535 |
trace("reading from $rfd done."); |
trace("reading from $rfd done."); |
| 536 |
$sout->remove($rfd); |
$sout->remove($rfd); |
| 559 |
return ($stdout, $stderr, $status); |
return ($stdout, $stderr, $status); |
| 560 |
}; |
}; |
| 561 |
|
|
| 562 |
sub ask($$) { |
sub ask($$;$$) { |
| 563 |
my ($question, $default) = @_; |
my ($question, $default, $forceyes, $forceno) = @_; |
| 564 |
my $answer; |
my $answer; |
| 565 |
|
my $yn = $default ? '[Y/n]' : '[y/N]'; |
| 566 |
while (1) { |
while (1) { |
| 567 |
print $question,' ',($default ? '[Y/n]' : '[y/N]'), ' '; |
print $question,' ',$yn, ' '; |
| 568 |
|
if ($forceyes && $forceno) { |
| 569 |
|
print "$default (from config/command line)\n"; |
| 570 |
|
return $default; |
| 571 |
|
}; |
| 572 |
|
if ($forceyes) { |
| 573 |
|
print "YES (from config/command line)\n"; |
| 574 |
|
return 1; |
| 575 |
|
}; |
| 576 |
|
if ($forceno) { |
| 577 |
|
print "NO (from config/command line)\n"; |
| 578 |
|
return 0; |
| 579 |
|
}; |
| 580 |
|
|
| 581 |
$answer = <STDIN>; |
$answer = <STDIN>; |
| 582 |
|
if (!defined $answer) { |
| 583 |
|
$OUTPUT_AUTOFLUSH = 1; |
| 584 |
|
die "\n\n". |
| 585 |
|
"End of STDIN reached. Are you using xargs? Caff wants to read from STDIN,\n". |
| 586 |
|
"so you can't really use it with xargs. A patch against caff to read from\n". |
| 587 |
|
"the terminal would be appreciated.\n". |
| 588 |
|
"For now instead of cat keys | xargs caff do caff `cat keys`\n"; |
| 589 |
|
}; |
| 590 |
chomp $answer; |
chomp $answer; |
| 591 |
last if ((defined $answer) && (length $answer <= 1)); |
last if ((length $answer == 0) || ($answer =~ m/^[yYnN]$/) ); |
| 592 |
print "grrrrrr.\n"; |
print "What about $yn is so hard to understand?\nAnswer with either 'n' or 'y' or just press enter for the default.\n"; |
| 593 |
sleep 1; |
sleep 1; |
| 594 |
}; |
}; |
| 595 |
my $result = $default; |
my $result = $default; |
| 609 |
my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey'; |
my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey'; |
| 610 |
|
|
| 611 |
load_config; |
load_config; |
| 612 |
my $USER_AGENT = "caff $VERSION - (c) 2004 Peter Palfrader"; |
my $USER_AGENT = "caff $VERSION - (c) 2004, 2005 Peter Palfrader et al."; |
| 613 |
|
|
| 614 |
my $KEYSBASE = $CONFIG{'caffhome'}.'/keys'; |
my $KEYSBASE = $CONFIG{'caffhome'}.'/keys'; |
| 615 |
my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome'; |
my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome'; |
| 622 |
my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday); |
my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday); |
| 623 |
|
|
| 624 |
|
|
| 625 |
sub usage() { |
sub version($) { |
| 626 |
print STDERR "caff $VERSION - (c) 2004 Peter Palfrader\n"; |
my ($fd) = @_; |
| 627 |
print STDERR "Usage: $PROGRAM_NAME [-u <yourkeyid] <keyid> [<keyid> ...]\n"; |
print $fd "caff $VERSION - (c) 2004, 2005 Peter Palfrader et al.\n"; |
| 628 |
exit 1; |
}; |
| 629 |
|
|
| 630 |
|
sub usage($$) { |
| 631 |
|
my ($fd, $exitcode) = @_; |
| 632 |
|
version($fd); |
| 633 |
|
print $fd "Usage: $PROGRAM_NAME [-eEmMRS] [-u <yourkeyid>] <keyid> [<keyid> ...]\n"; |
| 634 |
|
print $fd "Consult the manual page for more information.\n"; |
| 635 |
|
exit $exitcode; |
| 636 |
}; |
}; |
| 637 |
|
|
| 638 |
|
###### |
| 639 |
|
# export key $keyid from $gnupghome |
| 640 |
|
###### |
| 641 |
sub export_key($$) { |
sub export_key($$) { |
| 642 |
my ($gnupghome, $keyid) = @_; |
my ($gnupghome, $keyid) = @_; |
| 643 |
|
|
| 644 |
my $gpg = GnuPG::Interface->new(); |
my $gpg = GnuPG::Interface->new(); |
| 645 |
$gpg->call( $CONFIG{'gpg'} ); |
$gpg->call( $CONFIG{'gpg'} ); |
| 646 |
$gpg->options->hash_init( |
if (defined $gnupghome) { |
| 647 |
'homedir' => $gnupghome, |
$gpg->options->hash_init( |
| 648 |
'armor' => 1 ); |
'homedir' => $gnupghome, |
| 649 |
|
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ], |
| 650 |
|
'armor' => 1 ); |
| 651 |
|
} else { |
| 652 |
|
$gpg->options->hash_init( |
| 653 |
|
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ], |
| 654 |
|
'armor' => 1 ); |
| 655 |
|
}; |
| 656 |
$gpg->options->meta_interactive( 0 ); |
$gpg->options->meta_interactive( 0 ); |
| 657 |
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 658 |
my $pid = $gpg->export_keys(handles => $handles, command_args => [ $keyid ]); |
my $pid = $gpg->export_keys(handles => $handles, command_args => [ $keyid ]); |
| 662 |
return $stdout; |
return $stdout; |
| 663 |
}; |
}; |
| 664 |
|
|
| 665 |
|
###### |
| 666 |
|
# import a key from the scalar $asciikey into a gpg homedirectory in $tempdir |
| 667 |
|
###### |
| 668 |
|
sub import_key($$) { |
| 669 |
|
my ($gnupghome, $asciikey) = @_; |
| 670 |
|
|
| 671 |
|
my $gpg = GnuPG::Interface->new(); |
| 672 |
|
$gpg->call( $CONFIG{'gpg'} ); |
| 673 |
|
$gpg->options->hash_init( |
| 674 |
|
'homedir' => $gnupghome, |
| 675 |
|
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ] ); |
| 676 |
|
$gpg->options->meta_interactive( 0 ); |
| 677 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 678 |
|
my $pid = $gpg->import_keys(handles => $handles); |
| 679 |
|
my ($stdout, $stderr, $status) = readwrite_gpg($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 680 |
|
waitpid $pid, 0; |
| 681 |
|
|
| 682 |
|
if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) { |
| 683 |
|
return undef; |
| 684 |
|
}; |
| 685 |
|
return 1; |
| 686 |
|
}; |
| 687 |
|
|
| 688 |
|
|
| 689 |
|
###### |
| 690 |
|
# Send an email to $address. If $can_encrypt is true then the mail |
| 691 |
|
# will be PGP/MIME encrypted to $longkeyid. |
| 692 |
|
# |
| 693 |
|
# $longkeyid, $uid, and @attached will be used in the email and the template. |
| 694 |
|
###### |
| 695 |
#send_mail($address, $can_encrypt, $longkeyid, $uid, @attached); |
#send_mail($address, $can_encrypt, $longkeyid, $uid, @attached); |
| 696 |
sub send_mail($$$@) { |
sub send_mail($$$@) { |
| 697 |
my ($address, $can_encrypt, $key_id, @keys) = @_; |
my ($address, $can_encrypt, $key_id, @keys) = @_; |
| 698 |
|
|
| 699 |
my $message = "Hi,\n\n"; |
my $template = Text::Template->new(TYPE => 'STRING', SOURCE => $CONFIG{'mail-template'}) |
| 700 |
|
or die "Error creating template: $Text::Template::ERROR"; |
| 701 |
|
|
| 702 |
$message .= 'please find attached the user id'.(scalar @keys >= 2 ? 's' : '')."\n"; |
my @uids; |
| 703 |
for my $key (@keys) { |
for my $key (@keys) { |
| 704 |
$message .= "\t".$key->{'text'}."\n"; |
push @uids, $key->{'text'}; |
| 705 |
}; |
}; |
| 706 |
$message .= qq{of your key $key_id signed by me. |
my $message = $template->fill_in(HASH => { key => $key_id, |
| 707 |
|
uids => \@uids, |
| 708 |
Note that I did not upload your key to any keyservers. If you want this |
owner => $CONFIG{'owner'}}) |
| 709 |
new signature to be available to others, please upload it yourself. |
or die "Error filling template in: $Text::Template::ERROR"; |
|
With GnuPG this can be done using |
|
|
gpg --keyserver subkeys.pgp.net --send-key $key_id |
|
| 710 |
|
|
|
If you have any questions, don't hesitate to ask. |
|
|
|
|
|
Regards, |
|
|
$CONFIG{'owner'} |
|
|
}; |
|
| 711 |
my $message_entity = MIME::Entity->build( |
my $message_entity = MIME::Entity->build( |
| 712 |
Type => "text/plain", |
Type => "text/plain", |
| 713 |
Charset => "utf-8", |
Charset => "utf-8", |
| 720 |
Type => "application/pgp-keys", |
Type => "application/pgp-keys", |
| 721 |
Disposition => 'attachment', |
Disposition => 'attachment', |
| 722 |
Encoding => "7bit", |
Encoding => "7bit", |
| 723 |
Description => "PGP Key 0x$key_id, uid ".($key->{'text'}).' ('.($key->{'serial'}).')', |
Description => "PGP Key 0x$key_id, uid ".($key->{'text'}).' ('.($key->{'serial'}).'), signed by 0x'.$CONFIG{'keyid'}[0], |
| 724 |
Data => $key->{'key'}, |
Data => $key->{'key'}, |
| 725 |
Filename => "0x$key_id.".$key->{'serial'}.".asc"); |
Filename => "0x$key_id.".$key->{'serial'}.".signed-by-0x".$CONFIG{'keyid'}[0].".asc"); |
| 726 |
}; |
}; |
| 727 |
|
|
| 728 |
if ($can_encrypt) { |
if ($can_encrypt) { |
| 731 |
my $gpg = GnuPG::Interface->new(); |
my $gpg = GnuPG::Interface->new(); |
| 732 |
$gpg->call( $CONFIG{'gpg'} ); |
$gpg->call( $CONFIG{'gpg'} ); |
| 733 |
$gpg->options->hash_init( 'homedir' => $GNUPGHOME, |
$gpg->options->hash_init( 'homedir' => $GNUPGHOME, |
| 734 |
'extra_args' => '--always-trust', |
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ], |
| 735 |
'armor' => 1 ); |
'armor' => 1 ); |
| 736 |
$gpg->options->meta_interactive( 0 ); |
$gpg->options->meta_interactive( 0 ); |
| 737 |
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 747 |
$message = $stdout; |
$message = $stdout; |
| 748 |
|
|
| 749 |
$message_entity = MIME::Entity->build( |
$message_entity = MIME::Entity->build( |
| 750 |
Type => 'multipart/encrypted; protocol="application/pgp-encrypted"'); |
Type => 'multipart/encrypted; protocol="application/pgp-encrypted"', |
| 751 |
|
Encoding => '7bit'); |
| 752 |
|
|
| 753 |
$message_entity->attach( |
$message_entity->attach( |
| 754 |
Type => "application/pgp-encrypted", |
Type => "application/pgp-encrypted", |
| 766 |
|
|
| 767 |
$message_entity->head->add("Subject", "Your signed PGP key 0x$key_id"); |
$message_entity->head->add("Subject", "Your signed PGP key 0x$key_id"); |
| 768 |
$message_entity->head->add("To", $address); |
$message_entity->head->add("To", $address); |
| 769 |
$message_entity->head->add("From", $CONFIG{'owner'}.' <'.$CONFIG{'email'}.'>'); |
$message_entity->head->add("From", '"'.$CONFIG{'owner'}.'" <'.$CONFIG{'email'}.'>'); |
| 770 |
|
$message_entity->head->add("Reply-To", $CONFIG{'reply-to'}) if defined $CONFIG{'reply-to'}; |
| 771 |
|
$message_entity->head->add("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'}; |
| 772 |
$message_entity->head->add("User-Agent", $USER_AGENT); |
$message_entity->head->add("User-Agent", $USER_AGENT); |
| 773 |
$message_entity->send(); |
$message_entity->send(); |
| 774 |
$message_entity->stringify(); |
$message_entity->stringify(); |
| 775 |
}; |
}; |
| 776 |
|
|
| 777 |
|
###### |
| 778 |
|
# clean up a UID so that it can be used on the FS. |
| 779 |
|
###### |
| 780 |
|
sub sanitize_uid($) { |
| 781 |
|
my ($uid) = @_; |
| 782 |
|
|
| 783 |
|
my $good_uid = $uid; |
| 784 |
|
$good_uid =~ tr#/:\\#_#; |
| 785 |
|
trace2("[sanitize_uid] changed UID from $uid to $good_uid.\n") if $good_uid ne $uid; |
| 786 |
|
return $good_uid; |
| 787 |
|
}; |
| 788 |
|
|
| 789 |
|
sub delete_signatures($$$$$$) { |
| 790 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $keyids) =@_; |
| 791 |
|
|
| 792 |
|
my $signed_by_me = 0; |
| 793 |
|
|
| 794 |
|
my ($stdout, $stderr, $status) = |
| 795 |
|
readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1); |
| 796 |
|
|
| 797 |
|
while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) { |
| 798 |
|
# sig:?::17:EA2199412477CAF8:1058095214:::::13x: |
| 799 |
|
my @sigline = grep { /^sig/ } (split /\n/, $stdout); |
| 800 |
|
$stdout =~ s/\n/\\n/g; |
| 801 |
|
notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX |
| 802 |
|
my $line = pop @sigline; |
| 803 |
|
my $answer = "no"; |
| 804 |
|
if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance |
| 805 |
|
debug("[sigremoval] doing line $line."); |
| 806 |
|
my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line; |
| 807 |
|
if ($signer eq $longkeyid) { |
| 808 |
|
debug("[sigremoval] selfsig ($signer)."); |
| 809 |
|
$answer = "no"; |
| 810 |
|
} elsif (grep { $signer eq $_ } @{$keyids}) { |
| 811 |
|
debug("[sigremoval] signed by us ($signer)."); |
| 812 |
|
$answer = "no"; |
| 813 |
|
$signed_by_me = $signed_by_me > $created ? $signed_by_me : $created; |
| 814 |
|
} else { |
| 815 |
|
debug("[sigremoval] not interested in that sig ($signer)."); |
| 816 |
|
$answer = "yes"; |
| 817 |
|
}; |
| 818 |
|
} else { |
| 819 |
|
debug("[sigremoval] no sig line here, only got: ".$stdout); |
| 820 |
|
}; |
| 821 |
|
($stdout, $stderr, $status) = |
| 822 |
|
readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1); |
| 823 |
|
}; |
| 824 |
|
|
| 825 |
|
return $signed_by_me; |
| 826 |
|
}; |
| 827 |
|
|
| 828 |
|
|
| 829 |
|
|
| 830 |
my $USER; |
my $USER; |
| 831 |
my @KEYIDS; |
my @KEYIDS; |
| 832 |
|
my $params; |
| 833 |
|
|
| 834 |
|
Getopt::Long::config('bundling'); |
| 835 |
|
if (!GetOptions ( |
| 836 |
|
'-h' => \$params->{'help'}, |
| 837 |
|
'--help' => \$params->{'help'}, |
| 838 |
|
'--version' => \$params->{'version'}, |
| 839 |
|
'-V' => \$params->{'version'}, |
| 840 |
|
'-u=s' => \$params->{'local-user'}, |
| 841 |
|
'--local-user=s' => \$params->{'local-user'}, |
| 842 |
|
'-e' => \$params->{'export-old'}, |
| 843 |
|
'--export-old' => \$params->{'export-old'}, |
| 844 |
|
'-E' => \$params->{'no-export-old'}, |
| 845 |
|
'--no-export-old' => \$params->{'no-export-old'}, |
| 846 |
|
'-m' => \$params->{'mail'}, |
| 847 |
|
'--mail' => \$params->{'mail'}, |
| 848 |
|
'-M' => \$params->{'no-mail'}, |
| 849 |
|
'--no-mail' => \$params->{'no-mail'}, |
| 850 |
|
'-R' => \$params->{'no-download'}, |
| 851 |
|
'--no-download' => \$params->{'no-download'}, |
| 852 |
|
'-S' => \$params->{'no-sign'}, |
| 853 |
|
'--no-sign' => \$params->{'no-sign'}, |
| 854 |
|
'--key-file=s@' => \$params->{'key-files'}, |
| 855 |
|
)) { |
| 856 |
|
usage(\*STDERR, 1); |
| 857 |
|
}; |
| 858 |
|
if ($params->{'help'}) { |
| 859 |
|
usage(\*STDOUT, 0); |
| 860 |
|
}; |
| 861 |
|
if ($params->{'version'}) { |
| 862 |
|
version(\*STDOUT); |
| 863 |
|
exit(0); |
| 864 |
|
}; |
| 865 |
|
usage(\*STDERR, 1) unless scalar @ARGV >= 1; |
| 866 |
|
|
| 867 |
|
|
| 868 |
usage() unless scalar @ARGV >= 1; |
|
| 869 |
if ($ARGV[0] eq '-u') { |
if ($params->{'local-user'}) { |
| 870 |
usage() unless scalar @ARGV >= 3; |
$USER = $params->{'local-user'}; |
| 871 |
shift @ARGV; |
$USER =~ s/^0x//i; |
| 872 |
$USER = shift @ARGV; |
unless ($USER =~ /^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/i) { |
|
unless ($USER =~ /^[A-Za-z0-9]{8,8}([A-Za-z0-9]{8})?$/) { |
|
| 873 |
print STDERR "-u $USER is not a keyid.\n"; |
print STDERR "-u $USER is not a keyid.\n"; |
| 874 |
usage(); |
usage(\*STDERR, 1); |
| 875 |
}; |
}; |
| 876 |
$USER = uc($USER); |
$USER = uc($USER); |
| 877 |
}; |
}; |
| 878 |
|
|
| 879 |
for my $keyid (@ARGV) { |
for my $keyid (@ARGV) { |
| 880 |
unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8})?$/) { |
$keyid =~ s/^0x//i; |
| 881 |
|
unless ($keyid =~ /^([A-F0-9]{8}|[A-F0-9]{16}||[A-F0-9]{40})$/i) { |
| 882 |
|
if ($keyid =~ /^[A-F0-9]{32}$/) { |
| 883 |
|
info("Ignoring v3 fingerprint $keyid. v3 keys are obsolete."); |
| 884 |
|
next; |
| 885 |
|
}; |
| 886 |
print STDERR "$keyid is not a keyid.\n"; |
print STDERR "$keyid is not a keyid.\n"; |
| 887 |
usage(); |
usage(\*STDERR, 1); |
| 888 |
}; |
}; |
| 889 |
push @KEYIDS, uc($keyid); |
push @KEYIDS, uc($keyid); |
| 890 |
}; |
}; |
| 891 |
|
|
| 892 |
|
$CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-download'}; |
| 893 |
|
$CONFIG{'no-mail'} = $params->{'no-mail'} if defined $params->{'no-mail'}; |
| 894 |
|
$CONFIG{'mail'} = $params->{'mail'} if defined $params->{'mail'}; |
| 895 |
|
$CONFIG{'no-sign'} = $params->{'no-sign'} if defined $params->{'no-sign'}; |
| 896 |
|
push @{$CONFIG{'key-files'}}, @{$params->{'key-files'}} if defined $params->{'key-files'}; |
| 897 |
|
|
| 898 |
|
|
| 899 |
|
################# |
| 900 |
|
# import own keys |
| 901 |
|
################# |
| 902 |
|
for my $keyid (@{$CONFIG{'keyid'}}) { |
| 903 |
|
my $gpg = GnuPG::Interface->new(); |
| 904 |
|
$gpg->call( $CONFIG{'gpg'} ); |
| 905 |
|
$gpg->options->hash_init( |
| 906 |
|
'homedir' => $GNUPGHOME, |
| 907 |
|
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode --fast-list-mode } ] ); |
| 908 |
|
$gpg->options->meta_interactive( 0 ); |
| 909 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 910 |
|
my $pid = $gpg->list_public_keys(handles => $handles, command_args => $keyid); |
| 911 |
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 912 |
|
waitpid $pid, 0; |
| 913 |
|
|
| 914 |
|
if ($stdout eq '') { |
| 915 |
|
warn ("No data from gpg for list-key\n"); # There should be at least 'tru:' everywhere. |
| 916 |
|
}; |
| 917 |
|
unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) { |
| 918 |
|
info("Key $keyid not found in caff's home. Getting it from your normal GnuPGHome."); |
| 919 |
|
my $key = export_key(undef, $keyid); |
| 920 |
|
if (!defined $key || $key eq '') { |
| 921 |
|
warn ("Did not get key $keyid from your normal GnuPGHome\n"); |
| 922 |
|
next; |
| 923 |
|
}; |
| 924 |
|
my $result = import_key($GNUPGHOME, $key); |
| 925 |
|
unless ($result) { |
| 926 |
|
warn ("Could not import $keyid into caff's gnupghome.\n"); |
| 927 |
|
next; |
| 928 |
|
}; |
| 929 |
|
} |
| 930 |
|
} |
| 931 |
|
|
| 932 |
|
######################## |
| 933 |
|
# import keys from files |
| 934 |
|
######################## |
| 935 |
|
foreach my $keyfile (@{$CONFIG{'key-files'}}) { |
| 936 |
|
my $gpg = GnuPG::Interface->new(); |
| 937 |
|
$gpg->call( $CONFIG{'gpg'} ); |
| 938 |
|
$gpg->options->hash_init('homedir' => $GNUPGHOME); |
| 939 |
|
$gpg->options->meta_interactive( 0 ); |
| 940 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 941 |
|
my $pid = $gpg->import_keys(handles => $handles, command_args => $keyfile); |
| 942 |
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 943 |
|
info ("Importing keys from $keyfile"); |
| 944 |
|
waitpid $pid, 0; |
| 945 |
|
if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) { |
| 946 |
|
warn $stderr; |
| 947 |
|
} |
| 948 |
|
} |
| 949 |
|
|
| 950 |
############################# |
############################# |
| 951 |
# receive keys from keyserver |
# receive keys from keyserver |
| 952 |
############################# |
############################# |
|
my $gpg = GnuPG::Interface->new(); |
|
|
$gpg->call( $CONFIG{'gpg'} ); |
|
|
$gpg->options->hash_init( |
|
|
'homedir' => $GNUPGHOME, |
|
|
'extra_args' => '--keyserver='.$CONFIG{'keyserver'} ); |
|
|
$gpg->options->meta_interactive( 0 ); |
|
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
|
|
my $pid = $gpg->recv_keys(handles => $handles, command_args => [ @KEYIDS ]); |
|
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
|
|
waitpid $pid, 0; |
|
|
|
|
| 953 |
my @keyids_ok; |
my @keyids_ok; |
| 954 |
my @keyids_failed; |
if ($CONFIG{'no-download'}) { |
| 955 |
|
@keyids_ok = @KEYIDS; |
| 956 |
|
} else { |
| 957 |
|
info ("fetching keys, this will take a while..."); |
| 958 |
|
|
| 959 |
|
my $gpg = GnuPG::Interface->new(); |
| 960 |
|
$gpg->call( $CONFIG{'gpg'} ); |
| 961 |
|
$gpg->options->hash_init( |
| 962 |
|
'homedir' => $GNUPGHOME, |
| 963 |
|
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always }, '--keyserver='.$CONFIG{'keyserver'} ] ); |
| 964 |
|
$gpg->options->meta_interactive( 0 ); |
| 965 |
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 966 |
|
my $pid = $gpg->recv_keys(handles => $handles, command_args => [ @KEYIDS ]); |
| 967 |
|
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 968 |
|
waitpid $pid, 0; |
| 969 |
|
|
| 970 |
# [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F |
# [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F |
| 971 |
# [GNUPG:] NODATA 1 |
# [GNUPG:] NODATA 1 |
| 972 |
# [GNUPG:] NODATA 1 |
# [GNUPG:] NODATA 1 |
| 973 |
# [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039 |
# [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039 |
| 974 |
for my $line (split /\n/, $status) { |
my %local_keyids = map { $_ => 1 } @KEYIDS; |
| 975 |
if ($line =~ /^\[GNUPG:\] IMPORT_OK/) { |
my $had_v3_keys = 0; |
| 976 |
push @keyids_ok, shift @KEYIDS; |
for my $line (split /\n/, $status) { |
| 977 |
} elsif ($line =~ /^\[GNUPG:\] NODATA/) { |
if ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{40})/) { |
| 978 |
push @keyids_failed, shift @KEYIDS; |
my $imported_key = $1; |
| 979 |
|
my $whole_fpr = $imported_key; |
| 980 |
|
my $long_keyid = substr($imported_key, -16); |
| 981 |
|
my $short_keyid = substr($imported_key, -8); |
| 982 |
|
my $speced_key; |
| 983 |
|
for my $spec (($whole_fpr, $long_keyid, $short_keyid)) { |
| 984 |
|
$speced_key = $spec if $local_keyids{$spec}; |
| 985 |
|
}; |
| 986 |
|
unless ($speced_key) { |
| 987 |
|
notice ("Imported unexpected key; got: $imported_key\n"); |
| 988 |
|
next; |
| 989 |
|
}; |
| 990 |
|
debug ("Imported $imported_key for $speced_key"); |
| 991 |
|
delete $local_keyids{$speced_key}; |
| 992 |
|
unshift @keyids_ok, $imported_key; |
| 993 |
|
} elsif ($line =~ /^\[GNUPG:\] (NODATA|IMPORT_RES|IMPORTED) /) { |
| 994 |
|
} elsif ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{32})/) { |
| 995 |
|
my $imported_key = $1; |
| 996 |
|
notice ("Imported key $1 is a version 3 key. Version 3 keys are obsolete, should not be used, and are not and will not be properly supported."); |
| 997 |
|
$had_v3_keys = 1; |
| 998 |
|
} else { |
| 999 |
|
notice ("got unknown reply from gpg: $line"); |
| 1000 |
|
} |
| 1001 |
}; |
}; |
| 1002 |
|
if (scalar %local_keyids) { |
| 1003 |
|
notice ("Import failed for: ". (join ' ', keys %local_keyids)."." . ($had_v3_keys ? " (Or maybe it's one of those ugly v3 keys?)" : "")); |
| 1004 |
|
exit 1 unless ask ("Some keys could not be imported - continue anyway?", 0); |
| 1005 |
|
} |
| 1006 |
|
}; |
| 1007 |
|
|
| 1008 |
|
unless (@keyids_ok) { |
| 1009 |
|
notice ("No keys to sign found"); |
| 1010 |
|
exit 0; |
| 1011 |
} |
} |
|
die ("Still keys in \@KEYIDS. This should not happen.") if scalar @KEYIDS; |
|
|
notice ("Import failed for: ". (join ' ', @keyids_failed).".") if scalar @keyids_failed; |
|
| 1012 |
|
|
| 1013 |
########### |
########### |
| 1014 |
# sign keys |
# sign keys |
| 1015 |
########### |
########### |
| 1016 |
info("Sign the following keys according to your policy..."); |
if ($CONFIG{'ask-sign'} && ! $CONFIG{'no-sign'}) { |
| 1017 |
for my $keyid (@keyids_ok) { |
$CONFIG{'no-sign'} = ! ask("Continue with signing?", 1); |
| 1018 |
my @command; |
} |
| 1019 |
push @command, $CONFIG{'gpg-sign'}; |
|
| 1020 |
push @command, '--local-user', $USER if (defined $USER); |
unless ($CONFIG{'no-sign'}) { |
| 1021 |
push @command, "--homedir=$GNUPGHOME"; |
info("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key"); |
| 1022 |
push @command, '--secret-keyring', $CONFIG{'secret-keyring'}; |
for my $keyid (@keyids_ok) { |
| 1023 |
push @command, '--sign-key', $keyid; |
my @command; |
| 1024 |
print join(' ', @command),"\n"; |
push @command, $CONFIG{'gpg-sign'}; |
| 1025 |
system (@command); |
push @command, '--local-user', $USER if (defined $USER); |
| 1026 |
|
push @command, "--homedir=$GNUPGHOME"; |
| 1027 |
|
push @command, '--secret-keyring', $CONFIG{'secret-keyring'}; |
| 1028 |
|
push @command, '--no-auto-check-trustdb'; |
| 1029 |
|
push @command, '--trust-model=always'; |
| 1030 |
|
push @command, '--edit', $keyid; |
| 1031 |
|
push @command, 'sign'; |
| 1032 |
|
push @command, split ' ', $CONFIG{'gpg-sign-args'} || ""; |
| 1033 |
|
print join(' ', @command),"\n"; |
| 1034 |
|
system (@command); |
| 1035 |
|
}; |
| 1036 |
}; |
}; |
| 1037 |
|
|
| 1038 |
################## |
################## |
| 1042 |
for my $keyid (@keyids_ok) { |
for my $keyid (@keyids_ok) { |
| 1043 |
# get key listing |
# get key listing |
| 1044 |
################# |
################# |
| 1045 |
$gpg = GnuPG::Interface->new(); |
my $gpg = GnuPG::Interface->new(); |
| 1046 |
$gpg->call( $CONFIG{'gpg'} ); |
$gpg->call( $CONFIG{'gpg'} ); |
| 1047 |
$gpg->options->hash_init( 'homedir' => $GNUPGHOME ); |
$gpg->options->hash_init( |
| 1048 |
|
'homedir' => $GNUPGHOME, |
| 1049 |
|
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode } ] ); |
| 1050 |
$gpg->options->meta_interactive( 0 ); |
$gpg->options->meta_interactive( 0 ); |
| 1051 |
($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 1052 |
$gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] ); |
my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]); |
| 1053 |
$pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]); |
my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
|
($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd); |
|
| 1054 |
waitpid $pid, 0; |
waitpid $pid, 0; |
| 1055 |
if ($stdout eq '') { |
if ($stdout eq '') { |
| 1056 |
warn ("No data from gpg for list-key $keyid\n"); |
warn ("No data from gpg for list-key $keyid\n"); |
| 1057 |
next; |
next; |
| 1058 |
}; |
}; |
|
my $keyinfo = $stdout; |
|
| 1059 |
my @publine = grep { /^pub/ } (split /\n/, $stdout); |
my @publine = grep { /^pub/ } (split /\n/, $stdout); |
| 1060 |
my ($dummy1, $dummy2, $dummy3, $dummy4, $longkeyid, $dummy6, $dummy7, $dummy8, $dummy9, $dummy10, $dummy11, $flags) = split /:/, pop @publine; |
if (scalar @publine == 0) { |
| 1061 |
my $can_encrypt = $flags =~ /E/; |
warn ("No public keys found with list-key $keyid (note that caff uses its own keyring in $GNUPGHOME).\n"); |
| 1062 |
|
next; |
| 1063 |
|
}; |
| 1064 |
|
my (undef, undef, undef, undef, $longkeyid, undef, undef, undef, undef, undef, undef, $flags) = split /:/, pop @publine; |
| 1065 |
|
if (scalar @publine > 0) { |
| 1066 |
|
warn ("More than one key matched $keyid. Try to specify the long keyid or fingerprint\n"); |
| 1067 |
|
next; |
| 1068 |
|
}; |
| 1069 |
unless (defined $longkeyid) { |
unless (defined $longkeyid) { |
| 1070 |
warn ("Didn't find public keyid in edit dialog of key $keyid.\n"); |
warn ("Didn't find public keyid in --list-key of key $keyid.\n"); |
| 1071 |
|
next; |
| 1072 |
|
}; |
| 1073 |
|
unless (defined $flags) { |
| 1074 |
|
warn ("Didn't find flags in --list-key of key $keyid.\n"); |
| 1075 |
next; |
next; |
| 1076 |
}; |
}; |
| 1077 |
|
my $can_encrypt = $flags =~ /E/; |
| 1078 |
|
|
| 1079 |
# export the key |
# export the key |
| 1080 |
################ |
################ |
| 1089 |
while (1) { |
while (1) { |
| 1090 |
my $this_uid_text = ''; |
my $this_uid_text = ''; |
| 1091 |
$uid_number++; |
$uid_number++; |
| 1092 |
info("Doing key $keyid, uid $uid_number"); |
debug("Doing key $keyid, uid $uid_number"); |
| 1093 |
|
my $tempdir = tempdir( "caff-$keyid-XXXXX", DIR => '/tmp/', CLEANUP => 1); |
| 1094 |
|
|
| 1095 |
# import into temporary gpghome |
# import into temporary gpghome |
| 1096 |
############################### |
############################### |
| 1097 |
my $tempdir = tempdir( "caff-$keyid-XXXXX", DIR => '/tmp/', CLEANUP => 1); |
my $result = import_key($tempdir, $asciikey); |
| 1098 |
my $gpg = GnuPG::Interface->new(); |
unless ($result) { |
|
$gpg->call( $CONFIG{'gpg'} ); |
|
|
$gpg->options->hash_init( 'homedir' => $tempdir ); |
|
|
$gpg->options->meta_interactive( 0 ); |
|
|
my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
|
|
my $pid = $gpg->import_keys(handles => $handles); |
|
|
my ($stdout, $stderr, $status) = readwrite_gpg($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd); |
|
|
waitpid $pid, 0; |
|
|
|
|
|
if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) { |
|
| 1099 |
warn ("Could not import $keyid into temporary gnupg.\n"); |
warn ("Could not import $keyid into temporary gnupg.\n"); |
| 1100 |
next; |
next; |
| 1101 |
}; |
}; |
| 1106 |
$gpg->call( $CONFIG{'gpg-delsig'} ); |
$gpg->call( $CONFIG{'gpg-delsig'} ); |
| 1107 |
$gpg->options->hash_init( |
$gpg->options->hash_init( |
| 1108 |
'homedir' => $tempdir, |
'homedir' => $tempdir, |
| 1109 |
'extra_args' => [ '--with-colons', '--fixed-list-mode', '--command-fd=0', '--no-tty' ] ); |
'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode --command-fd=0 --no-tty } ] ); |
| 1110 |
($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds(); |
| 1111 |
$pid = $gpg->wrap_call( |
$pid = $gpg->wrap_call( |
| 1112 |
commands => [ '--edit' ], |
commands => [ '--edit' ], |
| 1126 |
debug("Parsing stdout output."); |
debug("Parsing stdout output."); |
| 1127 |
for my $line (split /\n/, $stdout) { |
for my $line (split /\n/, $stdout) { |
| 1128 |
debug("Checking line $line"); |
debug("Checking line $line"); |
| 1129 |
my ($type, $dummy2, $dummy3, $dummy4, $dummy5, $dummy6, $dummy7, $dummy8, $dummy9, $uidtext) = split /:/, $line; |
my ($type, undef, undef, undef, undef, undef, undef, undef, undef, $uidtext) = split /:/, $line; |
| 1130 |
if ($type eq 'sub') { |
if ($type eq 'sub') { |
| 1131 |
$number_of_subkeys++; |
$number_of_subkeys++; |
| 1132 |
}; |
}; |
| 1135 |
if ($uid_number != $i) { |
if ($uid_number != $i) { |
| 1136 |
debug("mark for deletion."); |
debug("mark for deletion."); |
| 1137 |
readwrite_gpg("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); |
readwrite_gpg("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); |
| 1138 |
$delete_some = 1; |
$delete_some++; |
| 1139 |
} else { |
} else { |
| 1140 |
debug("keep it."); |
debug("keep it."); |
| 1141 |
$have_one = 1; |
$have_one = 1; |
| 1142 |
$this_uid_text = ($type eq 'uid') ? $uidtext : 'attribute'; |
$this_uid_text = ($type eq 'uid') ? $uidtext : '[attribute]'; |
| 1143 |
$is_uat = $type eq 'uat'; |
$is_uat = $type eq 'uat'; |
| 1144 |
}; |
}; |
| 1145 |
$i++; |
$i++; |
| 1146 |
}; |
}; |
| 1147 |
debug("Parsing stdout output done."); |
debug("Parsing stdout output done."); |
|
if ($is_uat) { |
|
|
notice("Can't handle attribute userid of key $keyid."); |
|
|
next; |
|
|
}; |
|
| 1148 |
unless ($have_one) { |
unless ($have_one) { |
| 1149 |
|
debug("Uid ".($uid_number-1)." was the last, there is no $uid_number."); |
| 1150 |
info("key $keyid done."); |
info("key $keyid done."); |
| 1151 |
last; |
last; |
| 1152 |
}; |
}; |
| 1153 |
|
|
| 1154 |
|
my $prune_some_sigs_on_uid; |
| 1155 |
|
my $prune_all_sigs_on_uid; |
| 1156 |
|
if ($is_uat) { |
| 1157 |
|
debug("handling attribute userid of key $keyid."); |
| 1158 |
|
if ($uid_number == 1) { |
| 1159 |
|
debug(" attribute userid is #1, unmarking #2 for deletion."); |
| 1160 |
|
readwrite_gpg("2\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); |
| 1161 |
|
$delete_some--; |
| 1162 |
|
$prune_some_sigs_on_uid = 1; |
| 1163 |
|
$prune_all_sigs_on_uid = 2; |
| 1164 |
|
} else { |
| 1165 |
|
debug("attribute userid is not #1, unmarking #1 for deletion."); |
| 1166 |
|
readwrite_gpg("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); |
| 1167 |
|
$delete_some--; |
| 1168 |
|
$prune_some_sigs_on_uid = 2; |
| 1169 |
|
$prune_all_sigs_on_uid = 1; |
| 1170 |
|
}; |
| 1171 |
|
} else { |
| 1172 |
|
$prune_some_sigs_on_uid = 1; |
| 1173 |
|
}; |
| 1174 |
|
|
| 1175 |
if ($delete_some) { |
if ($delete_some) { |
| 1176 |
debug("need to delete a few uids."); |
debug("need to delete $delete_some uids."); |
| 1177 |
readwrite_gpg("deluid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELUID_PROMPT, nocloseinput => 1); |
readwrite_gpg("deluid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELUID_PROMPT, nocloseinput => 1); |
| 1178 |
readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); |
readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); |
| 1179 |
}; |
}; |
| 1190 |
|
|
| 1191 |
# delete signatures |
# delete signatures |
| 1192 |
################### |
################### |
| 1193 |
my $signed_by_me = 0; |
readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig |
| 1194 |
readwrite_gpg("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); |
my $signed_by_me = delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $CONFIG{'keyid'}); |
| 1195 |
($stdout, $stderr, $status) = |
readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig |
| 1196 |
readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1); |
if (defined $prune_all_sigs_on_uid) { |
| 1197 |
|
readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig |
| 1198 |
while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) { |
delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, []); |
| 1199 |
# sig:?::17:EA2199412477CAF8:1058095214:::::13x: |
readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig |
|
my @sigline = grep { /^sig/ } (split /\n/, $stdout); |
|
|
my $line = pop @sigline; |
|
|
my $answer = "no"; |
|
|
if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance |
|
|
my ($dummy1, $dummy2, $dummy3, $dummy4, $signer, $created, $dummy7, $dummy8, $dummy9) = split /:/, $line; |
|
|
if ($signer eq $longkeyid) { |
|
|
$answer = "no"; |
|
|
} elsif (grep { $signer eq $_ } @{$CONFIG{'keyid'}}) { |
|
|
$answer = "no"; |
|
|
$signed_by_me = $signed_by_me > $created ? $signed_by_me : $created; |
|
|
} else { |
|
|
$answer = "yes"; |
|
|
}; |
|
|
}; |
|
|
($stdout, $stderr, $status) = |
|
|
readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1); |
|
| 1200 |
}; |
}; |
| 1201 |
|
|
| 1202 |
|
|
| 1203 |
readwrite_gpg("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd); |
readwrite_gpg("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd); |
| 1204 |
waitpid $pid, 0; |
waitpid $pid, 0; |
| 1205 |
|
|
| 1206 |
my $asciikey = export_key($tempdir, $longkeyid); |
my $asciikey = export_key($tempdir, $keyid); |
| 1207 |
if ($asciikey eq '') { |
if ($asciikey eq '') { |
| 1208 |
warn ("No data from gpg for export $longkeyid\n"); |
warn ("No data from gpg for export $keyid\n"); |
| 1209 |
next; |
next; |
| 1210 |
}; |
}; |
| 1211 |
|
|
| 1212 |
if ($signed_by_me) { |
if ($signed_by_me) { |
| 1213 |
if ($NOW - $signed_by_me > $CONFIG{'export-sig-age'} ) { |
if ($NOW - $signed_by_me > $CONFIG{'export-sig-age'} ) { |
| 1214 |
my $write = ask("Signature on $this_uid_text is old. Export?", 0); |
my $write = ask("Signature on $this_uid_text is old. Export?", 0, $params->{'export-old'}, $params->{'no-export-old'}); |
| 1215 |
next unless $write; |
next unless $write; |
| 1216 |
}; |
}; |
| 1217 |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
| 1218 |
-d $keydir || mkpath($keydir , 0, 0700) or die ("Cannot create $keydir $!\n"); |
-d $keydir || mkpath($keydir , 0, 0700) or die ("Cannot create $keydir $!\n"); |
| 1219 |
|
|
| 1220 |
my $keyfile = "$keydir/$longkeyid.key.$uid_number.$this_uid_text.asc"; |
my $keyfile = "$keydir/$longkeyid.key.$uid_number.".sanitize_uid($this_uid_text).".asc"; |
| 1221 |
open (KEY, ">$keyfile") or die ("Cannot open $keyfile\n"); |
open (KEY, ">$keyfile") or die ("Cannot open $keyfile: $!\n"); |
| 1222 |
print KEY $asciikey; |
print KEY $asciikey; |
| 1223 |
close KEY; |
close KEY; |
| 1224 |
|
|
| 1225 |
push @UIDS, { text => $this_uid_text, key => $asciikey, serial => $uid_number }; |
push @UIDS, { text => $this_uid_text, key => $asciikey, serial => $uid_number, "is_uat" => $is_uat }; |
| 1226 |
|
|
| 1227 |
info("$longkeyid $uid_number $this_uid_text done."); |
info("$longkeyid $uid_number $this_uid_text done."); |
| 1228 |
} else { |
} else { |
| 1233 |
if (scalar @UIDS == 0) { |
if (scalar @UIDS == 0) { |
| 1234 |
info("found no signed uids for $keyid"); |
info("found no signed uids for $keyid"); |
| 1235 |
} else { |
} else { |
| 1236 |
my @attached ; |
next if $CONFIG{'no-mail'}; # do not send mail |
| 1237 |
|
|
| 1238 |
|
my @attached; |
| 1239 |
for my $uid (@UIDS) { |
for my $uid (@UIDS) { |
| 1240 |
unless ($uid->{'text'} =~ /@/) { |
trace("UID: $uid->{'text'}\n"); |
| 1241 |
|
if ($uid->{'is_uat'}) { |
| 1242 |
|
my $attach = ask("UID $uid->{'text'} is an attribute UID, attach it to every email sent?", 1); |
| 1243 |
|
push @attached, $uid if $attach; |
| 1244 |
|
} elsif ($uid->{'text'} !~ /@/) { |
| 1245 |
my $attach = ask("UID $uid->{'text'} is no email address, attach it to every email sent?", 1); |
my $attach = ask("UID $uid->{'text'} is no email address, attach it to every email sent?", 1); |
| 1246 |
push @attached, $uid; |
push @attached, $uid if $attach; |
| 1247 |
}; |
}; |
| 1248 |
}; |
}; |
| 1249 |
|
|
| 1250 |
notice("Key has no encryption capabilities, mail will be sent unencrypted") unless $can_encrypt; |
notice("Key has no encryption capabilities, mail will be sent unencrypted") unless $can_encrypt; |
| 1251 |
for my $uid (@UIDS) { |
for my $uid (@UIDS) { |
| 1252 |
if ($uid->{'text'} =~ /@/) { |
if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) { |
| 1253 |
my $address = $uid->{'text'}; |
my $address = $uid->{'text'}; |
| 1254 |
$address =~ s/.*<(.*)>.*/$1/; |
$address =~ s/.*<(.*)>.*/$1/; |
| 1255 |
my $send = ask("Send mail to '$address' for $uid->{'text'}?", 1); |
if (ask("Mail signature for $uid->{'text'} to '$address'?", 1, $CONFIG{'mail'})) { |
|
if ($send) { |
|
| 1256 |
my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached); |
my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached); |
| 1257 |
|
|
| 1258 |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
my $keydir = "$KEYSBASE/$DATE_STRING"; |
| 1259 |
my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".$uid->{'text'}; |
my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid($uid->{'text'}); |
| 1260 |
open (KEY, ">$mailfile") or die ("Cannot open $mailfile\n"); |
open (KEY, ">$mailfile") or die ("Cannot open $mailfile: $!\n"); |
| 1261 |
print KEY $mail; |
print KEY $mail; |
| 1262 |
close KEY; |
close KEY; |
| 1263 |
}; |
}; |
| 1266 |
}; |
}; |
| 1267 |
|
|
| 1268 |
}; |
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
###############################################################3 |
|
|
#### old fork gpg --edit |
|
|
=cut |
|
|
my ($stdin_read, $stdin_write); |
|
|
my ($stdout_read, $stdout_write); |
|
|
my ($stderr_read, $stderr_write); |
|
|
my ($status_read, $status_write); |
|
|
pipe $stdin_read, $stdin_write; |
|
|
pipe $stdout_read, $stdout_write; |
|
|
pipe $stderr_read, $stderr_write; |
|
|
pipe $status_read, $status_write; |
|
|
|
|
|
$pid = fork(); |
|
|
unless ($pid) { # child |
|
|
close $stdin_write; |
|
|
close $stdout_read; |
|
|
close $stderr_read; |
|
|
close $status_read; |
|
|
|
|
|
my @call; |
|
|
push @call, $CONFIG{'gpg-delsig'}; |
|
|
push @call, "--homedir=$tempdir"; |
|
|
push @call, '--with-colons'; |
|
|
push @call, '--fixed-list-mode'; |
|
|
push @call, '--command-fd=0'; |
|
|
push @call, "--status-fd=".fileno($status_write); |
|
|
push @call, "--no-tty"; |
|
|
push @call, "--edit"; |
|
|
push @call, $keyid; |
|
|
|
|
|
close STDIN; |
|
|
close STDOUT; |
|
|
close STDERR; |
|
|
open (STDIN, "<&".fileno($stdin_read)) or die ("Cannot reopen stdin: $!\n"); |
|
|
open (STDOUT, ">&".fileno($stdout_write)) or die ("Cannot reopen stdout: $!\n"); |
|
|
open (STDERR, ">&".fileno($stderr_write)) or die ("Cannot reopen stderr: $!\n"); |
|
|
|
|
|
fcntl $status_write, F_SETFD, 0; |
|
|
|
|
|
exec (@call); |
|
|
exit; |
|
|
}; |
|
|
close $stdin_read; |
|
|
close $stdout_write; |
|
|
close $stderr_write; |
|
|
close $status_write; |
|
|
|
|
|
$inputfd = $stdin_write; |
|
|
$stdoutfd = $stdout_read; |
|
|
$stderrfd = $stderr_read; |
|
|
$statusfd = $status_read; |
|
|
=cut |
|