more documentation
[pgp-tools.git] / caff / caff
index 54bb9cc75ad44aaadc6b1f9d46d0e29eab6b9a7b..b946a9ab41267a81fc6382f81a368ddb393984f7 100755 (executable)
--- a/caff/caff
+++ b/caff/caff
@@ -40,7 +40,7 @@ caff -- CA - Fire and Forget
 
 =over
 
-=item B<caff> [-mMR] [-u I<yourkeyid>] I<keyid> [I<keyid> ..]
+=item B<caff> [-eEmMRS] [-u I<yourkeyid>] I<keyid> [I<keyid> ..]
 
 =back
 
@@ -56,18 +56,23 @@ sigs and sigs done by you.
 
 =over
 
-=item B<-m>, B<--mail>
+=item B<-e>, B<--export>, B<-E>, B<--no-export>
 
-Send mail after signing. Default is to ask the user for each uid.
+Export/do not export old signatures. Default is to ask the user for each old
+signature.
 
-=item B<-M>, B<--no-mail>
+=item B<-m>, B<--mail>, B<-M>, B<--no-mail>
 
-Do not send mail after signing. Default is to ask the user for each uid.
+Send/do not send mail after signing. Default is to ask the user for each uid.
 
 =item B<-R>, B<--no-download>
 
 Do not retrieve the key to be signed from a keyserver.
 
+=item B<-S>, B<--no-sign>
+
+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.
@@ -141,6 +146,10 @@ Path to your secret keyring.  Default: B<$HOME/.gnupg/secring.gpg>.
 
 An additional keyid to encrypt messages to. Default: none.
 
+=item B<gpg-sign-args> [string]
+
+Additional arguments to pass to gpg. Default: none.
+
 =head2 Keyserver settings
 
 =item B<keyserver> [string]
@@ -176,7 +185,7 @@ $CONFIG{caffhome}/keys/. Default: B<0>.
 
 =item B<mail-template> [string]
 
-Email template which is used as the body text for the email sent out.
+Email template which is used as the body text for the email sent out
 instead of the default text if specified. The following perl variables
 can be used in the template:
 
@@ -410,8 +419,11 @@ sub readwrite_gpg($$$$$%) {
        return ($stdout, $stderr, $status);
 };
 
-sub ask($$) {
-       my ($question, $default) = @_;
+sub ask($$;$$) {
+       my ($question, $default, $forceyes, $forceno) = @_;
+       return $default if $forceyes and $forceno;
+       return 1 if $forceyes;
+       return 0 if $forceno;
        my $answer;
        while (1) {
                print $question,' ',($default ? '[Y/n]' : '[y/N]'), ' ';
@@ -459,7 +471,7 @@ sub version($) {
 sub usage($$) {
        my ($fd, $exitcode) = @_;
        version($fd);
-       print $fd "Usage: $PROGRAM_NAME [-mMR] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
+       print $fd "Usage: $PROGRAM_NAME [-eEmMRS] [-u <yourkeyid>] <keyid> [<keyid> ...]\n";
        print $fd "Consult the manual page for more information.\n";
        exit $exitcode;
 };
@@ -657,12 +669,18 @@ if (!GetOptions (
        '-V'              =>  \$params->{'version'},
        '-u=s'            =>  \$params->{'local-user'},
        '--local-user=s'  =>  \$params->{'local-user'},
+       '-e'              =>  \$params->{'export'},
+       '--export'        =>  \$params->{'export'},
+       '-E'              =>  \$params->{'no-export'},
+       '--no-export'     =>  \$params->{'no-export'},
        '-m'              =>  \$params->{'mail'},
        '--mail'          =>  \$params->{'mail'},
        '-M'              =>  \$params->{'no-mail'},
        '--no-mail'       =>  \$params->{'no-mail'},
        '-R'              =>  \$params->{'no-download'},
        '--no-download'   =>  \$params->{'no-download'},
+       '-S'              =>  \$params->{'no-sign'},
+       '--no-sign'       =>  \$params->{'no-sign'},
        )) {
        usage(\*STDERR, 1);
 };
@@ -680,7 +698,7 @@ usage(\*STDERR, 1) unless scalar @ARGV >= 1;
 if ($params->{'local-user'}) {
        $USER = $params->{'local-user'};
        $USER =~ s/^0x//i;
-       unless ($USER =~ /^[A-Za-z0-9]{8,8}([A-Za-z0-9]{8})?$/) {
+       unless ($USER =~ /^([A-Z0-9]{8}|[A-Z0-9]{16}|[A-Z0-9]{32}|[A-Z0-9]{40})$/i) {
                print STDERR "-u $USER is not a keyid.\n";
                usage(\*STDERR, 1);
        };
@@ -689,7 +707,7 @@ if ($params->{'local-user'}) {
 
 for my $keyid (@ARGV) {
        $keyid =~ s/^0x//i;
-       unless ($keyid =~ /^[A-Za-z0-9]{8}([A-Za-z0-9]{8}|[A-Za-z0-9]{32})?$/) {
+       unless ($keyid =~ /^([A-Z0-9]{8}|[A-Z0-9]{16}|[A-Z0-9]{32}|[A-Z0-9]{40})$/i) {
                print STDERR "$keyid is not a keyid.\n";
                usage(\*STDERR, 1);
        };
@@ -699,6 +717,7 @@ for my $keyid (@ARGV) {
 $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'};
 
 
 #################
@@ -730,10 +749,14 @@ $CONFIG{'mail'}        = $params->{'mail'}        if defined $params->{'mail'};
 # receive keys from keyserver
 #############################
 my @keyids_ok;
-my @keyids_failed;
 if ($CONFIG{'no-download'}) {
        @keyids_ok = @KEYIDS;
 } else {
+       info ("fetching keys, this will take a while...");
+       if (grep { /^[A-Z0-9]{32}$/ } @KEYIDS) {
+               info ("found v3 key fingerprints in argument list - note that HKP keyservers do not support retrieving v3 keys by fingerprint");
+       }
+
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
        $gpg->options->hash_init(
@@ -741,49 +764,45 @@ if ($CONFIG{'no-download'}) {
                'extra_args' => '--keyserver='.$CONFIG{'keyserver'} );
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
-       
-       my @local_keyids = @KEYIDS;
-       for my $keyid (@local_keyids) {
-               info ("fetching $keyid...");
-               my $pid = $gpg->recv_keys(handles => $handles, command_args => [ $keyid ]);
-               my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
-               waitpid $pid, 0;
+       my $pid = $gpg->recv_keys(handles => $handles, command_args => [ @KEYIDS ]);
+       my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
+       waitpid $pid, 0;
 
 # [GNUPG:] IMPORT_OK 0 5B00C96D5D54AEE1206BAF84DE7AAF6E94C09C7F
 # [GNUPG:] NODATA 1
 # [GNUPG:] NODATA 1
 # [GNUPG:] IMPORT_OK 0 25FC1614B8F87B52FF2F99B962AF4031C82E0039
-               my $handled = 0;
-               for my $line (split /\n/, $status) {
-                       if ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{40})/) {
-                               my $imported_key = $1;
-                               if ($keyid ne $imported_key &&
-                                   $keyid ne substr($imported_key, -16) &&
-                                   $keyid ne substr($imported_key, -8)) {
-                                   warn("Imported unexpected key.  expected: $keyid; got: $imported_key.\n");
-                                   next;
-                               };
-                               push @keyids_ok, $keyid;
-                               shift @KEYIDS;
-                               $handled = 1;
-                               last;
-                       } elsif ($line =~ /^\[GNUPG:\] NODATA/) {
-                               push @keyids_failed, $keyid;
-                               shift @KEYIDS;
-                               $handled = 1;
-                               last;
+       my %local_keyids = map { $_ => 1 } @KEYIDS;
+       for my $line (split /\n/, $status) {
+               if ($line =~ /^\[GNUPG:\] IMPORT_OK \d+ ([0-9A-F]{8})([0-9A-F]{16})([0-9A-F]{8})([0-9A-F]{0,8})/) {
+                       my $imported_key;
+                       $imported_key = $1.$2.$3    if $local_keyids{$1.$2.$3}; # v3 key
+                       $imported_key = $1.$2.$3.$4 if $local_keyids{$1.$2.$3.$4};
+                       $imported_key =       $3.$4 if $local_keyids{      $3.$4};
+                       $imported_key =          $4 if $local_keyids{         $4};
+                       unless ($imported_key) {
+                           notice ("Imported unexpected key; got: $1$2$3$4. (This is normal for v3 keys.)\n");
+                           next;
                        };
-               };
-               unless ($handled) {
-                       notice ("Huh, what's up with $keyid?");
-                       push @keyids_failed, $keyid;
-                       shift @KEYIDS;
-               };
+                       debug ("Imported $imported_key");
+                       delete $local_keyids{$imported_key};
+                       unshift @keyids_ok, $imported_key;
+               } elsif ($line =~ /^\[GNUPG:\] (NODATA|IMPORT_RES|IMPORTED) /) {
+               } else {
+                       notice ("got unknown reply from gpg: $line");
+               }
        };
-       die ("Still keys in \@KEYIDS.  This should not happen.") if scalar @KEYIDS;
-       notice ("Import failed for: ". (join ' ', @keyids_failed).".") if scalar @keyids_failed;
+       if (scalar %local_keyids) {
+               notice ("Import failed for: ". (join ' ', keys %local_keyids).".");
+               exit 1 unless ask ("Some keys could not be imported - continue anyway?", 0);
+       }
 };
 
+unless (@keyids_ok) {
+       notice ("No keys to sign found");
+       exit 0;
+}
+
 ###########
 # sign keys
 ###########
@@ -795,8 +814,10 @@ unless ($CONFIG{'no-sign'}) {
                push @command, '--local-user', $USER if (defined $USER);
                push @command, "--homedir=$GNUPGHOME";
                push @command, '--secret-keyring', $CONFIG{'secret-keyring'};
+               push @command, split ' ', $CONFIG{'gpg-sign-args'} || "";
                push @command, '--edit', $keyid;
                push @command, 'sign';
+               push @command, 'save';
                print join(' ', @command),"\n";
                system (@command);
        };
@@ -977,7 +998,7 @@ for my $keyid (@keyids_ok) {
 
                if ($signed_by_me) {
                        if ($NOW - $signed_by_me > $CONFIG{'export-sig-age'} ) {
-                               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}, $params->{'no-export'});
                                next unless $write;
                        };
                        my $keydir = "$KEYSBASE/$DATE_STRING";
@@ -1018,7 +1039,7 @@ for my $keyid (@keyids_ok) {
                        if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) {
                                my $address = $uid->{'text'};
                                $address =~ s/.*<(.*)>.*/$1/;
-                               if ($CONFIG{'mail'} or ask("Send mail to '$address' for $uid->{'text'}?", 1)) {
+                               if (ask("Send mail to '$address' for $uid->{'text'}?", 1, $CONFIG{'mail'})) {
                                        my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
 
                                        my $keydir = "$KEYSBASE/$DATE_STRING";