+ Fix perl warnings when calling pgp-fixkey with unknown keyid.
[pgp-tools.git] / caff / caff
index 47c4ec89802c7211bfe5f46f18ab63d951fc3dd9..2a575c45ce38c5fb388d1ddc70d5d020bb1b50c7 100755 (executable)
--- a/caff/caff
+++ b/caff/caff
@@ -3,8 +3,8 @@
 # caff  --  CA - Fire and Forget
 # $Id$
 #
-# Copyright (c) 2004, 2005 Peter Palfrader <peter@palfrader.org>
-# Copyright (c) 2005 Christoph Berg <cb@df7cb.de>
+# Copyright (c) 2004, 2005, 2006 Peter Palfrader <peter@palfrader.org>
+# Copyright (c) 2005, 2006 Christoph Berg <cb@df7cb.de>
 #
 # All rights reserved.
 #
@@ -85,6 +85,7 @@ Do not sign the keys.
 =item B<-u> I<yourkeyid>, B<--local-user> I<yourkeyid>
 
 Select the key that is used for signing, in case you have more than one key.
+To sign with multiple keys at once, separate multiple keyids by comma.
 
 =item B<--key-file> I<file>
 
@@ -104,7 +105,7 @@ Import keys from file. Can be supplied more than once.
 
 =item $HOME/.caff/gnupghome/gpg.conf  -  gpg configuration
 
-useful options include use-agent, default-cert-level, etc.
+useful options include use-agent, keyserver-options, default-cert-level, etc.
 
 =back
 
@@ -115,9 +116,9 @@ The file is generated when it does not exist.
 
 Example:
 
-       $CONFIG{owner} = q{Peter Palfrader};
-       $CONFIG{email} = q{peter@palfrader.org};
-       $CONFIG{keyid} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ];
+       $CONFIG{'owner'} = q{Peter Palfrader};
+       $CONFIG{'email'} = q{peter@palfrader.org};
+       $CONFIG{'keyid'} = [ qw{DE7AAF6E94C09C7F 62AF4031C82E0039} ];
 
 =head2 Required basic settings
 
@@ -164,9 +165,9 @@ is set to.
 
 Path to your secret keyring.  Default: B<$HOME/.gnupg/secring.gpg>.
 
-=item B<also-encrypt-to> [keyid]
+=item B<also-encrypt-to> [keyid, or list of keyids]
 
-An additional keyid to encrypt messages to. Default: none.
+Additional keyids to encrypt messages to. Default: none.
 
 =item B<gpg-sign-args> [string]
 
@@ -203,6 +204,11 @@ This is useful for offline signing. Default: B<0>.
 Don't export UIDs by default, on which your latest signature is older
 than this age.  Default: B<24*60*60> (i.e. one day).
 
+=item B<local-user> [keyid, or list of keyids]
+
+Select the key that is used for signing, in case you have more than one key.
+With multiple keyids, sign with each key in turn.
+
 =head2 Mail settings
 
 =item B<mail> [boolean]
@@ -212,7 +218,7 @@ Do not prompt for sending mail, just do it. Default: B<0>.
 =item B<no-mail> [boolean]
 
 Do not prompt for sending mail. The messages are still written to
-$CONFIG{caffhome}/keys/. Default: B<0>.
+$CONFIG{'caffhome'}/keys/. Default: B<0>.
 
 =item B<mail-template> [string]
 
@@ -245,6 +251,23 @@ Add a Reply-To: header to messages sent. Default: none.
 Address to send blind carbon copies to when sending mail.
 Default: none.
 
+=item B<mailer-send> [array]
+
+Parameters to pass to Mail::Mailer.
+This could for example be
+
+       $CONFIG{'mailer-send'} =  [ 'smtp', Server => 'mail.server', Auth => ['user', 'pass'] ];
+
+to use the perl SMTP client or
+
+       $CONFIG{'mailer-send'} =  [ 'sendmail', '-o8' ];
+
+to pass arguments to the sendmail program.
+For more information run C<< perldoc Mail::Mailer >>.
+Setting this option is strongly discouraged.  Fix your local MTA
+instead.
+Default: none.
+
 =back
 
 =head1 AUTHORS
@@ -274,6 +297,7 @@ use File::Path;
 use File::Temp qw{tempdir};
 use Text::Template;
 use MIME::Entity;
+use Encode;
 use Fcntl;
 use IO::Select;
 use Getopt::Long;
@@ -286,6 +310,10 @@ my $VERSION = "0.0.0.$REVISION_NUMER";
 
 
 
+sub mywarn($) {
+       my ($line) = @_;
+       print "[WARN] $line\n";
+};
 sub notice($) {
        my ($line) = @_;
        print "[NOTICE] $line\n";
@@ -313,9 +341,12 @@ sub generate_config() {
        my $gecos = defined $ENV{'LOGNAME'} ? (getpwnam($ENV{LOGNAME}))[6] : undef;
        my $email;
        my @keys;
-       my $hostname = `hostname -f`;
+       # BSD does not have hostname -f, so we try without -f first
+       my $hostname = `hostname`;
+       $hostname = `hostname -f` unless $hostname =~ /\./;
        chomp $hostname;
        my ($Cgecos,$Cemail,$Ckeys) = ('','','');
+
        if (defined $gecos) {
                $gecos =~ s/,.*//;
 
@@ -352,26 +383,39 @@ sub generate_config() {
                ($Cgecos,$Cemail,$Ckeys) = ('#','#','#');
        };
 
-       return <<EOT;
-# .caffrc -- vim:syntax=perl:
+       my $template = <<EOT;
+# .caffrc -- vim:ft=perl:
 # This file is in perl(1) format - see caff(1) for details.
 
-$Cgecos\$CONFIG{'owner'}       = '$gecos';
-$Cemail\$CONFIG{'email'}       = '$email';
+$Cgecos\$CONFIG{'owner'} = '$gecos';
+$Cemail\$CONFIG{'email'} = '$email';
+#\$CONFIG{'reply-to'} = 'foo\@bla.org';
 
-# you can get your long keyid from
+# You can get your long keyid from
 #   gpg --with-colons --list-key <yourkeyid|name|emailaddress..>
 #
-# if you have a v4 key, it will simply be the last 16 digits of
+# If you have a v4 key, it will simply be the last 16 digits of
 # your fingerprint.
 #
 # Example:
-#   \$CONFIG{'keyid'}       = [ qw{FEDCBA9876543210} ];
+#   \$CONFIG{'keyid'} = [ qw{FEDCBA9876543210} ];
 #  or, if you have more than one key:
-#   \$CONFIG{'keyid'}       = [ qw{0123456789ABCDEF 89ABCDEF76543210} ];
+#   \$CONFIG{'keyid'} = [ qw{0123456789ABCDEF 89ABCDEF76543210} ];
+$Ckeys\$CONFIG{'keyid'} = [ qw{@keys} ];
+
+# Select this/these keys to sign with
+#\$CONFIG{'local-user'} = [ qw{@keys} ];
 
-$Ckeys\$CONFIG{'keyid'}       = [ qw{@keys} ];
+# Additionally encrypt messages for these keyids
+#\$CONFIG{'also-encrypt-to'} = [ qw{@keys} ];
+
+# Mail template to use for the encrypted part
+#\$CONFIG{'mail-template'} = << 'EOM';
 EOT
+
+       $template .= "#$_" foreach <DATA>;
+       $template .= "#EOM\n";
+       return $template;
 };
 
 sub check_executable($$) {
@@ -422,32 +466,16 @@ sub load_config() {
        check_executable("gpg", $CONFIG{'gpg'});
        check_executable("gpg-sign", $CONFIG{'gpg-sign'});
        check_executable("gpg-delsig", $CONFIG{'gpg-delsig'});
-       $CONFIG{'secret-keyring'} = $ENV{'HOME'}.'/.gnupg/secring.gpg' unless defined $CONFIG{'secret-keyring'};
+       $CONFIG{'secret-keyring'} = ($ENV{'GNUPGHOME'} || "$ENV{'HOME'}/.gnupg") . '/secring.gpg'
+               unless defined $CONFIG{'secret-keyring'};
        $CONFIG{'no-download'} = 0 unless defined $CONFIG{'no-download'};
        $CONFIG{'no-sign'} = 0 unless defined $CONFIG{'no-sign'};
        $CONFIG{'key-files'} = () unless defined $CONFIG{'key-files'};
-       $CONFIG{'mail-template'} = <<'EOM' unless defined $CONFIG{'mail-template'};
-Hi,
-
-please find attached the user id{(scalar @uids >= 2 ? 's' : '')}.
-{foreach $uid (@uids) {
-    $OUT .= "\t".$uid."\n";
-};} of your key {$key} signed by me.
-
-Note that I did not upload your key to any keyservers.
-If you have multiple user ids, I sent the signature for each user id
-separately to that user id's associated email address. You can import
-the signatures by running each through `gpg --import`.
-
-If you want this new signature to be available to others, please upload
-it yourself. With GnuPG this can be done using
-       gpg --keyserver subkeys.pgp.net --send-key {$key}
-
-If you have any questions, don't hesitate to ask.
-
-Regards,
-{$owner}
-EOM
+       $CONFIG{'mailer-send'} = [] unless defined $CONFIG{'mailer-send'};
+       die ("$PROGRAM_NAME: mailer-send is not an array ref in $config.\n") unless (ref $CONFIG{'mailer-send'} eq 'ARRAY');
+       unless (defined $CONFIG{'mail-template'}) {
+               $CONFIG{'mail-template'} .= $_ foreach <DATA>;
+       }
 };
 
 sub make_gpg_fds() {
@@ -609,7 +637,7 @@ my $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT = '^\[GNUPG:\] (GET_BOOL keyedit.delsig|GET
 my $KEYEDIT_DELSUBKEY_PROMPT = '^\[GNUPG:\] GET_BOOL keyedit.remove.subkey';
 
 load_config;
-my $USER_AGENT = "caff $VERSION - (c) 2004, 2005 Peter Palfrader et al.";
+my $USER_AGENT = "caff $VERSION - http://pgp-tools.alioth.debian.org/";
 
 my $KEYSBASE =  $CONFIG{'caffhome'}.'/keys';
 my $GNUPGHOME = $CONFIG{'caffhome'}.'/gnupghome';
@@ -624,7 +652,7 @@ my $DATE_STRING = sprintf("%04d-%02d-%02d", $year+1900, $mon+1, $mday);
 
 sub version($) {
        my ($fd) = @_;
-       print $fd "caff $VERSION - (c) 2004, 2005 Peter Palfrader et al.\n";
+       print $fd "caff $VERSION - (c) 2004, 2005, 2006 Peter Palfrader et al.\n";
 };
 
 sub usage($$) {
@@ -736,7 +764,14 @@ sub send_mail($$$@) {
                $gpg->options->meta_interactive( 0 );
                my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
                $gpg->options->push_recipients( $key_id );
-               $gpg->options->push_recipients( $CONFIG{'also-encrypt-to'} ) if defined $CONFIG{'also-encrypt-to'};
+               if (defined $CONFIG{'also-encrypt-to'}) {
+                       if (ref($CONFIG{'also-encrypt-to'})) {
+                               $gpg->options->push_recipients($_)
+                                       foreach @{$CONFIG{'also-encrypt-to'}};
+                       } else {
+                               $gpg->options->push_recipients($CONFIG{'also-encrypt-to'});
+                       }
+               }
                my $pid = $gpg->encrypt(handles => $handles);
                my ($stdout, $stderr, $status) = readwrite_gpg($message, $inputfd, $stdoutfd, $stderrfd, $statusfd);
                waitpid $pid, 0;
@@ -766,11 +801,12 @@ sub send_mail($$$@) {
 
        $message_entity->head->add("Subject", "Your signed PGP key 0x$key_id");
        $message_entity->head->add("To", $address);
-       $message_entity->head->add("From", '"'.$CONFIG{'owner'}.'" <'.$CONFIG{'email'}.'>');
+       $message_entity->head->add("From", '"'.Encode::encode('MIME-Q', $CONFIG{'owner'}).'" <'.$CONFIG{'email'}.'>');
        $message_entity->head->add("Reply-To", $CONFIG{'reply-to'}) if defined $CONFIG{'reply-to'};
        $message_entity->head->add("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'};
        $message_entity->head->add("User-Agent", $USER_AGENT);
-       $message_entity->send();
+       mywarn("You have set arguments to pass to Mail::Mailer.  Better fix your MTA.  (Also, Mail::Mailer's error reporting is non existant, so it won't tell you when it doesn't work.)") if (scalar @{$CONFIG{'mailer-send'}} > 0);
+       $message_entity->send(@{$CONFIG{'mailer-send'}});
        $message_entity->stringify();
 };
 
@@ -826,8 +862,9 @@ sub delete_signatures($$$$$$) {
 };
 
 
-
-my $USER;
+###################
+# argument handling
+###################
 my @KEYIDS;
 my $params;
 
@@ -864,38 +901,26 @@ if ($params->{'version'}) {
 };
 usage(\*STDERR, 1) unless scalar @ARGV >= 1;
 
-
-
-if ($params->{'local-user'}) {
-       $USER = $params->{'local-user'};
-       $USER =~ s/^0x//i;
-       unless ($USER =~ /^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/i) {
-               print STDERR "-u $USER is not a keyid.\n";
-               usage(\*STDERR, 1);
-       };
-       $USER = uc($USER);
-};
+$CONFIG{'local-user'}  = $params->{'local-user'}  if defined $params->{'local-user'};
+$CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-download'};
+$CONFIG{'no-mail'}     = $params->{'no-mail'}     if defined $params->{'no-mail'};
+$CONFIG{'mail'}        = $params->{'mail'}        if defined $params->{'mail'};
+$CONFIG{'no-sign'}     = $params->{'no-sign'}     if defined $params->{'no-sign'};
+push @{$CONFIG{'key-files'}}, @{$params->{'key-files'}} if defined $params->{'key-files'};
 
 for my $keyid (@ARGV) {
        $keyid =~ s/^0x//i;
-       unless ($keyid =~ /^([A-F0-9]{8}|[A-F0-9]{16}||[A-F0-9]{40})$/i) {
-               if ($keyid =~ /^[A-F0-9]{32}$/) {
-                       info("Ignoring v3 fingerprint $keyid.  v3 keys are obsolete.");
-                       next;
-               };
+       if ($keyid =~ /^[A-F0-9]{32}$/i) {
+               info("Ignoring v3 fingerprint $keyid.  v3 keys are obsolete.");
+               next;
+       };
+       if ($keyid !~ /^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/i) {
                print STDERR "$keyid is not a keyid.\n";
                usage(\*STDERR, 1);
        };
        push @KEYIDS, uc($keyid);
 };
 
-$CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-download'};
-$CONFIG{'no-mail'}     = $params->{'no-mail'}     if defined $params->{'no-mail'};
-$CONFIG{'mail'}        = $params->{'mail'}        if defined $params->{'mail'};
-$CONFIG{'no-sign'}     = $params->{'no-sign'}     if defined $params->{'no-sign'};
-push @{$CONFIG{'key-files'}}, @{$params->{'key-files'}} if defined $params->{'key-files'};
-
-
 #################
 # import own keys
 #################
@@ -1002,6 +1027,12 @@ if ($CONFIG{'no-download'}) {
        if (scalar %local_keyids) {
                notice ("Import failed for: ". (join ' ', keys %local_keyids)."." . ($had_v3_keys ? " (Or maybe it's one of those ugly v3 keys?)" :  ""));
                exit 1 unless ask ("Some keys could not be imported - continue anyway?", 0);
+               if (scalar keys %local_keyids == 1) {
+                       mywarn("Assuming ". (join ' ', keys %local_keyids)." is a fine keyid.");
+               } else {
+                       mywarn("Assuming ". (join ' ', keys %local_keyids)." are fine keyids.");
+               };
+               push @keyids_ok, keys %local_keyids;
        }
 };
 
@@ -1018,20 +1049,41 @@ if ($CONFIG{'ask-sign'} && ! $CONFIG{'no-sign'}) {
 }
        
 unless ($CONFIG{'no-sign'}) {
+       my @local_user;
+       if ($CONFIG{'local-user'}) {
+               if (ref($CONFIG{'local-user'})) {
+                       @local_user = @{$CONFIG{'local-user'}};
+               } else {
+                       @local_user = split /\s*,\s*/, $CONFIG{'local-user'};
+               };
+               foreach (@local_user) {
+                       s/^0x//i;
+                       unless (/^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/i) {
+                               print STDERR "Local-user $_ is not a keyid.\n";
+                               usage(\*STDERR, 1);
+                       };
+                       $_ = uc($_);
+               };
+       } else {
+               @local_user = (undef);
+       };
+
        info("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key");
        for my $keyid (@keyids_ok) {
-               my @command;
-               push @command, $CONFIG{'gpg-sign'};
-               push @command, '--local-user', $USER if (defined $USER);
-               push @command, "--homedir=$GNUPGHOME";
-               push @command, '--secret-keyring', $CONFIG{'secret-keyring'};
-               push @command, '--no-auto-check-trustdb';
-               push @command, '--trust-model=always';
-               push @command, '--edit', $keyid;
-               push @command, 'sign';
-               push @command, split ' ', $CONFIG{'gpg-sign-args'} || "";
-               print join(' ', @command),"\n";
-               system (@command);
+               foreach my $local_user (@local_user) {
+                       my @command;
+                       push @command, $CONFIG{'gpg-sign'};
+                       push @command, '--local-user', $local_user if (defined $local_user);
+                       push @command, "--homedir=$GNUPGHOME";
+                       push @command, '--secret-keyring', $CONFIG{'secret-keyring'};
+                       push @command, '--no-auto-check-trustdb';
+                       push @command, '--trust-model=always';
+                       push @command, '--edit', $keyid;
+                       push @command, 'sign';
+                       push @command, split ' ', $CONFIG{'gpg-sign-args'} || "";
+                       print join(' ', @command),"\n";
+                       system (@command);
+               };
        };
 };
 
@@ -1266,3 +1318,29 @@ for my $keyid (@keyids_ok) {
        };
 
 };
+
+###########################
+# the default mail template
+###########################
+
+__DATA__
+Hi,
+
+please find attached the user id{(scalar @uids >= 2 ? 's' : '')}
+{foreach $uid (@uids) {
+    $OUT .= "\t".$uid."\n";
+};}of your key {$key} signed by me.
+
+If you have multiple user ids, I sent the signature for each user id
+separately to that user id's associated email address. You can import
+the signatures by running each through `gpg --import`.
+
+Note that I did not upload your key to any keyservers. If you want this
+new signature to be available to others, please upload it yourself.
+With GnuPG this can be done using
+       gpg --keyserver subkeys.pgp.net --send-key {$key}
+
+If you have any questions, don't hesitate to ask.
+
+Regards,
+{$owner}