caff: brace-style fixes
[pgp-tools.git] / caff / caff
index 9ce3a966b8c1a91e8133e642778b80ae8b28548f..f32d5b5da615e71526f8c3cc2e32045f969490db 100755 (executable)
--- a/caff/caff
+++ b/caff/caff
@@ -40,7 +40,7 @@ caff -- CA - Fire and Forget
 
 =over
 
-=item B<caff> [-eEmMRS] [-u I<yourkeyid>] I<keyid> [I<keyid> ..]
+=item B<caff> [-eERS] [-m I<yes|ask-yes|ask-no|no>] [-u I<yourkeyid>] I<keyid> [I<keyid> ..]
 
 =back
 
@@ -66,13 +66,10 @@ Export old signatures. Default is to ask the user for each old signature.
 Do not export old signatures. Default is to ask the user for each old
 signature.
 
-=item B<-m>, B<--mail>
+=item B<-m>, B<--mail> I<yes|ask-yes|ask-no|no>
 
-Send mail after signing. Default is to ask the user for each uid.
-
-=item B<-M>, B<--no-mail>
-
-Do not send mail after signing. Default is to ask the user for each uid.
+Whether to send mail after signing. Default is to ask, for each uid,
+with a default value of yes.
 
 =item B<-R>, B<--no-download>
 
@@ -85,7 +82,9 @@ 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.
+To sign with multiple keys at once, separate multiple keyids by comma. This
+option requires the key(s) to be defined through the keyid variable in the
+configuration file.
 
 =item B<--key-file> I<file>
 
@@ -138,14 +137,22 @@ A list of your keys.  This is used to determine which signatures to keep
 in the pruning step.  If you select a key using B<-u> it has to be in
 this list.  B<REQUIRED>.
 
+=back
+
 =head2 General settings
 
+=over
+
 =item B<caffhome> [string]
 
 Base directory for the files caff stores.  Default: B<$HOME/.caff/>.
 
+=back
+
 =head2 GnuPG settings
 
+=over
+
 =item B<gpg> [string]
 
 Path to the GnuPG binary.  Default: B<gpg>.
@@ -174,11 +181,15 @@ Additional keyids to encrypt messages to. Default: none.
 Additional commands to pass to gpg after the "sign" command.
 Default: none.
 
+=back
+
 =head2 Keyserver settings
 
+=over
+
 =item B<keyserver> [string]
 
-Keyserver to download keys from.  Default: B<subkeys.pgp.net>.
+Keyserver to download keys from.  Default: B<pool.sks-keyservers.net>.
 
 =item B<no-download> [boolean]
 
@@ -189,8 +200,12 @@ Default: B<0>.
 
 A list of files containing keys to be imported.
 
+=back
+
 =head2 Signing settings
 
+=over
+
 =item B<no-sign> [boolean]
 
 If true, then skip the signing step. Default: B<0>.
@@ -210,16 +225,19 @@ than this age.  Default: B<24*60*60> (i.e. one day).
 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.
 
+=back
+
 =head2 Mail settings
 
-=item B<mail> [boolean]
+=over
 
-Do not prompt for sending mail, just do it. Default: B<0>.
+=item B<mail> [boolean]
 
-=item B<no-mail> [boolean]
+Whether to send mails. This is a quad-option, with which you can set the
+behaviour: yes always sends, no never sends; ask-yes and ask-no asks, for
+each uid, with according defaults for the question. Default: B<ask-yes>.
 
-Do not prompt for sending mail. The messages are still written to
-$CONFIG{'caffhome'}/keys/. Default: B<0>.
+In any case, the messages are also written to $CONFIG{'caffhome'}/keys/
 
 =item B<mail-template> [string]
 
@@ -309,7 +327,21 @@ my $REVISION = '$Rev$';
 my ($REVISION_NUMER) = $REVISION =~ /(\d+)/;
 my $VERSION = "0.0.0.$REVISION_NUMER";
 
+# Global variables
+my @KEYIDS;
+my $params;
 
+##
+# Display an error message on STDERR and then exit.
+#
+# @param $exitcode exit code status to use to end the program
+# @param $line     error message to display on STDERR
+#
+sub myerror($$) {
+       my ($exitcode, $line) = @_;
+       print "[ERROR] $line\n";        
+       exit $exitcode;
+};
 
 sub mywarn($) {
        my ($line) = @_;
@@ -460,7 +492,7 @@ sub load_config() {
        };
        @{$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{'keyserver'} = 'pool.sks-keyservers.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'};
@@ -662,7 +694,7 @@ sub version($) {
 sub usage($$) {
        my ($fd, $exitcode) = @_;
        version($fd);
-       print $fd "Usage: $PROGRAM_NAME [-eEmMRS] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
+       print $fd "Usage: $PROGRAM_NAME [-eERS] [-m <yes|ask-yes|ask-no|no>] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
        print $fd "Consult the manual page for more information.\n";
        exit $exitcode;
 };
@@ -779,8 +811,11 @@ sub send_mail($$$@) {
                            (defined $CONFIG{'also-encrypt-to'})) {
                                my $reason = $1;
                                my $keyid = $2;
-                               if (grep { %_ eq $keyid } @{$CONFIG{'also-encrypt-to'}}) {
-                                       warn("Could not encrypt to $keyid, spezified in CONFIG{'also-encrypt-to'}.  Try to update the key using gpg --homedir=\$HOME/caff/gnupghome --import <keyd>.");
+                               if (grep { $_ eq $keyid } @{$CONFIG{'also-encrypt-to'}}) {
+                                       warn("Could not encrypt to $keyid, specified in CONFIG{'also-encrypt-to'}.\n".
+                                            "Try to update the key using gpg --homedir=$GNUPGHOME --import <exported key>\n".
+                                            "or try the following if you are slightly more daring:\n".
+                                            " gpg --export $keyid | gpg --homedir=$GNUPGHOME --import\n");
                                        return;
                                };
                        };
@@ -795,6 +830,7 @@ sub send_mail($$$@) {
 
                $message_entity->attach(
                        Type        => "application/pgp-encrypted",
+                       Filename    => "signedkey.msg",
                        Disposition => 'attachment',
                        Encoding    => "7bit",
                        Data        => "Version: 1\n");
@@ -810,6 +846,7 @@ 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", '"'.Encode::encode('MIME-Q', $CONFIG{'owner'}).'" <'.$CONFIG{'email'}.'>');
+       $message_entity->head->add("Sender", '"'.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);
@@ -869,13 +906,161 @@ sub delete_signatures($$$$$$) {
        return $signed_by_me;
 };
 
+##
+# Check the local user keys.
+#
+# This function checks if the keyids defined through the --local-user
+# command line option or set in ~/.caffrc are valid and known to be one of the
+# keyids listed in ~/.caffrc.
+#
+# @return an array containing the local user keys\n
+#         (undef) if no valid key has been found
+#
+sub get_local_user_keys() {
+       my @local_user = ();
+       my @key_list;
+       
+       # No user-defined key id has been specified by the user, no need for
+       # further checks
+       if (!$CONFIG{'local-user'}) {
+               return (undef);
+       }       
+       
+       # Parse the list of keys
+       if (ref($CONFIG{'local-user'})) {
+               @key_list = @{$CONFIG{'local-user'}};
+       }
+       else {
+               @key_list = split /\s*,\s*/, $CONFIG{'local-user'};
+       }
+
+       # Check every key defined by the user...
+       for my $user_key (@key_list) {
+               
+               $user_key =~ s/^0x//i;
+               $user_key = uc($user_key);
+               
+               unless ($user_key =~ m/^([A-F0-9]{8}|[A-F0-9]{16}|[A-F0-9]{40})$/) {
+                       mywarn "Local-user $user_key is not a valid keyid.";
+                       next;
+               }
+               
+               unless (grep (/$user_key$/, @{$CONFIG{'keyid'}})) {
+                       mywarn "Local-user $user_key is not defined as one of your keyid in ~/.caffrc (it will not be used).";
+                       next;
+               }
+               
+               push (@local_user, $user_key);
+       }
+
+       # If no local-user key are valid, there is no need to go further
+       unless (defined $local_user[0]) {
+               myerror (1, "None of the local-user keys seem to be known as a keyid listed in ~/.caffrc.");
+       }
+
+       return @local_user;
+}
+
+##
+# Import a key from the user gnupghome into a specified gnupghome.
+#
+# @param asciikey      ascii format of the gpg key to import
+# @param dst_gnupghome gnupghome directory where to import the key
+#
+# @return 0 if successful\n
+#         1 if the key could not be exported.\n
+#         2 if the key could not be imported.
+#
+sub import_key_from_user_gnupghome() {
+    my $err;
+    my ($asciikey, $dst_gpghome) = @_;
+
+    trace("Exporting key $asciikey from your normal GnuPGHOME.");
+    my $key = export_key(undef, $asciikey);
+    if (defined $key && $key ne '') {
+        trace("Importing key $asciikey into $GNUPGHOME.");
+        if (import_key($GNUPGHOME, $key)) {
+            $err = 0;
+        } else {
+            warn("Could not import $asciikey into caff's gnupghome.");
+            $err = 2;
+        }
+    } else {
+        $err = 1;
+    }
+
+    return $err;
+}
+
+##
+# Import a key file into a specified gnupghome.
+#
+# @param keyfile       file containing the keys to import
+# @param dst_gnupghome gnupghome directory where to import the key
+#
+# @return 0 if successful\n
+#         1 if an error occured.
+#
+sub import_key_files() {
+    my $err;
+    my ($keyfile, $dst_gpghome) = @_;
+
+    my $gpg = GnuPG::Interface->new();
+    $gpg->call( $CONFIG{'gpg'} );
+    $gpg->options->hash_init(
+        'homedir' => $dst_gpghome,
+        'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ] );
+    $gpg->options->meta_interactive( 0 );
+    my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
+    my $pid = $gpg->import_keys(handles => $handles, command_args => $keyfile);
+    my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
+    info("Importing keys from file $keyfile");
+    waitpid $pid, 0;
+
+    if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
+        warn $stderr;
+        $err = 1;
+    } else {
+        $err = 0;
+    }
+
+    return $err;
+}
+
+##
+# Import keys to be signed into caff gnupghome directory.
+#
+# This function imports the keys the user wants to sign into the caff gnupghome
+# directory. We looks for the keys in the the user gnupghome directory first,
+# and in the key files specified by the user if not all of the keys have been
+# found.
+#
+sub import_keys_to_sign() {
+    # Check if we can find the gpg key from our normal gnupghome, and then
+    # try to import it into our working gnupghome directory
+    my $imported_keys = 0;
+    foreach my $keyid (@KEYIDS) {
+        if (!&import_key_from_user_gnupghome($keyid, $GNUPGHOME)) {
+            info("Key $keyid imported from your normal GnuPGHOME.");
+            $imported_keys++;
+        }
+    }
+
+    # If all of the keys have been successfully imported, there is no need to
+    # go further
+    return 1 if ($imported_keys == scalar (@KEYIDS));
+
+    # Import user specified key files
+    foreach my $keyfile (@{$CONFIG{'key-files'}}) {
+        &import_key_files($keyfile, $GNUPGHOME);
+    }
+
+    return 0;
+}
 
 ###################
 # argument handling
 ###################
-my @KEYIDS;
-my $params;
-
 Getopt::Long::config('bundling');
 if (!GetOptions (
        '-h'              =>  \$params->{'help'},
@@ -888,8 +1073,8 @@ if (!GetOptions (
        '--export-old'    =>  \$params->{'export-old'},
        '-E'              =>  \$params->{'no-export-old'},
        '--no-export-old' =>  \$params->{'no-export-old'},
-       '-m'              =>  \$params->{'mail'},
-       '--mail'          =>  \$params->{'mail'},
+       '-m:s'              =>  \$params->{'mail'},
+       '--mail:s'          =>  \$params->{'mail'},
        '-M'              =>  \$params->{'no-mail'},
        '--no-mail'       =>  \$params->{'no-mail'},
        '-R'              =>  \$params->{'no-download'},
@@ -911,9 +1096,20 @@ usage(\*STDERR, 1) unless scalar @ARGV >= 1;
 
 $CONFIG{'local-user'}  = $params->{'local-user'}  if defined $params->{'local-user'};
 $CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-download'};
+$CONFIG{'no-sign'}     = $params->{'no-sign'}     if defined $params->{'no-sign'};
+
 $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'};
+
+# If old 'no-mail' parameter, or if the 'mail' parameter is set to 'no'
+if ( defined $CONFIG{'no-mail'} || 
+     ( defined $CONFIG{'mail'} && $CONFIG{'mail'}  eq 'no' ) ) {
+       $CONFIG{'mail'} = 'no';
+
+} elsif ( !defined $CONFIG{'mail'} ) {
+       $CONFIG{'mail'} = 'ask-yes';
+}
+
 push @{$CONFIG{'key-files'}}, @{$params->{'key-files'}} if defined $params->{'key-files'};
 
 for my $keyid (map { split /\n/ } @ARGV) { # caff "`cat txt`" is a single argument
@@ -934,53 +1130,14 @@ for my $keyid (map { split /\n/ } @ARGV) { # caff "`cat txt`" is a single argume
 # import own keys
 #################
 for my $keyid (@{$CONFIG{'keyid'}}) {
-       my $gpg = GnuPG::Interface->new();
-       $gpg->call( $CONFIG{'gpg'} );
-       $gpg->options->hash_init(
-               'homedir' => $GNUPGHOME,
-               'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode --fast-list-mode } ] );
-       $gpg->options->meta_interactive( 0 );
-       my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
-       my $pid = $gpg->list_public_keys(handles => $handles, command_args => $keyid);
-       my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
-       waitpid $pid, 0;
-
-       if ($stdout eq '') {
-               warn ("No data from gpg for list-key\n"); # There should be at least 'tru:' everywhere.
-       };
-       unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) {
-               info("Key $keyid not found in caff's home.  Getting it from your normal GnuPGHome.");
-               my $key = export_key(undef, $keyid);
-               if (!defined $key || $key eq '') {
-                       warn ("Did not get key $keyid from your normal GnuPGHome\n");
-                       next;
-               };
-               my $result = import_key($GNUPGHOME, $key);
-               unless ($result) {
-                       warn ("Could not import $keyid into caff's gnupghome.\n");
-                       next;
-               };
-       }
-}
-
-########################
-# import keys from files
-########################
-foreach my $keyfile (@{$CONFIG{'key-files'}}) {
-    my $gpg = GnuPG::Interface->new();
-    $gpg->call( $CONFIG{'gpg'} );
-    $gpg->options->hash_init('homedir' => $GNUPGHOME);
-    $gpg->options->meta_interactive( 0 );
-    my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
-    my $pid = $gpg->import_keys(handles => $handles, command_args => $keyfile);
-    my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
-    info ("Importing keys from $keyfile");
-    waitpid $pid, 0;
-    if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
-       warn $stderr;
+    info("Importing key $keyid from your normal GnuPGHome.");
+    if (&import_key_from_user_gnupghome($keyid, $GNUPGHOME)) {
+        mywarn("Key $keyid not found.");
     }
 }
 
+&import_keys_to_sign();
+
 #############################
 # receive keys from keyserver
 #############################
@@ -1058,24 +1215,7 @@ 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);
-       };
+       my @local_user = &get_local_user_keys();
 
        info("Sign the following keys according to your policy, then exit gpg with 'save' after signing each key");
        for my $keyid (@keyids_ok) {
@@ -1294,7 +1434,7 @@ for my $keyid (@keyids_ok) {
        if (scalar @UIDS == 0) {
                info("found no signed uids for $keyid");
        } else {
-               next if $CONFIG{'no-mail'}; # do not send mail
+               next if ($CONFIG{'mail'} eq 'no'); # do not send mail
 
                my @attached;
                for my $uid (@UIDS) {
@@ -1313,14 +1453,17 @@ for my $keyid (@keyids_ok) {
                        if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) {
                                my $address = $uid->{'text'};
                                $address =~ s/.*<(.*)>.*/$1/;
-                               if (ask("Mail signature for $uid->{'text'} to '$address'?", 1, $CONFIG{'mail'})) {
+                               if (ask("Mail signature for $uid->{'text'} to '$address'?", $CONFIG{'mail'} ne 'ask-no', $CONFIG{'mail'} eq 'yes')) {
                                        my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
-
-                                       my $keydir = "$KEYSBASE/$DATE_STRING";
-                                       my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid($uid->{'text'});
-                                       open (KEY, ">$mailfile") or die ("Cannot open $mailfile: $!\n");
-                                       print KEY $mail;
-                                       close KEY;
+                                       if (defined $mail) {
+                                               my $keydir = "$KEYSBASE/$DATE_STRING";
+                                               my $mailfile = "$keydir/$longkeyid.mail.".$uid->{'serial'}.".".sanitize_uid($uid->{'text'});
+                                               open (KEY, ">$mailfile") or die ("Cannot open $mailfile: $!\n");
+                                               print KEY $mail;
+                                               close KEY;
+                                       } else {
+                                               warn "Generating mail failed.\n";
+                                       };
                                };
                        };
                };
@@ -1347,7 +1490,7 @@ 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}
+       gpg --keyserver pool.sks-keyservers.net --send-key {$key}
 
 If you have any questions, don't hesitate to ask.