create caff configfile on first use
[pgp-tools.git] / caff / caff
index c4365e8ee96c926feb2fa80959ca8633dc72bce7..35be431932aa20e283b390fb3599820552896a9b 100755 (executable)
--- a/caff/caff
+++ b/caff/caff
@@ -85,6 +85,10 @@ Do not sign the keys.
 
 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.
 
+=item B<--key-file> I<file>
+
+Import keys from file. Can be supplied more than once.
+
 =back
 
 =head1 FILES
 =back
 
 =head1 FILES
@@ -169,6 +173,10 @@ Keyserver to download keys from.  Default: B<subkeys.pgp.net>.
 If true, then skip the step of fetching keys from the keyserver.
 Default: B<0>.
 
 If true, then skip the step of fetching keys from the keyserver.
 Default: B<0>.
 
+=item B<key-files> [list of files]
+
+A list of files containing keys to be imported.
+
 =head2 Signing settings
 
 =item B<no-sign> [boolean]
 =head2 Signing settings
 
 =item B<no-sign> [boolean]
@@ -258,9 +266,62 @@ my $REVISION = '$Rev$';
 my ($REVISION_NUMER) = $REVISION =~ /(\d+)/;
 my $VERSION = "0.0.0.$REVISION_NUMER";
 
 my ($REVISION_NUMER) = $REVISION =~ /(\d+)/;
 my $VERSION = "0.0.0.$REVISION_NUMER";
 
+sub generate_config() {
+       die "Error: \$LOGNAME is not set.\n" unless $ENV{LOGNAME};
+       my $gecos = (getpwnam($ENV{LOGNAME}))[6];
+       $gecos =~ s/,.*//;
+
+       my $gpg = GnuPG::Interface->new();
+       $gpg->call( 'gpg' );
+       $gpg->options->hash_init(
+               'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-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 => [ $gecos ]);
+       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.
+       };
+
+       my @keys;
+       unless (@keys = ($stdout =~ /^pub:[^r:]*:(?:[^:]*:){2,2}([^:]+):/mg)) {
+               die "Error: No keys were found using \"gpg --list-public-keys '$gecos'\".\n";
+       }
+       unless ($stdout =~ /^uid:.*<(.+@.+)>.*:/m) {
+               die "Error: No email address was found using \"gpg --list-public-keys '$gecos'\".\n";
+       }
+       my $email = $1;
+
+       return <<EOT;
+# .caffrc -- vim:syntax=perl:
+# This file is in perl(1) format - see caff(1) for details.
+
+\$CONFIG{'owner'}       = '$gecos';
+\$CONFIG{'email'}       = '$email';
+
+# 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
+# your fingerprint.
+
+\$CONFIG{'keyid'}       = [ qw{@keys} ];
+EOT
+};
+
 sub load_config() {
        my $config = $ENV{'HOME'} . '/.caffrc';
 sub load_config() {
        my $config = $ENV{'HOME'} . '/.caffrc';
-       -f $config or die "No file $config present.  See caff(1).\n";
+       unless (-f $config) {
+               print "No configfile $config present, I will use this template:\n";
+               my $template = generate_config();
+               print "$template\nPress enter to continue.";
+               <STDIN>;
+               open F, ">$config" or die "$config: $!";
+               print F $template;
+               close F;
+       }
        unless (scalar eval `cat $config`) {
                die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR;
        };
        unless (scalar eval `cat $config`) {
                die "Couldn't parse $config: $EVAL_ERROR\n" if $EVAL_ERROR;
        };
@@ -282,6 +343,7 @@ sub load_config() {
        $CONFIG{'secret-keyring'} = $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{'secret-keyring'} = $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,
 
        $CONFIG{'mail-template'} = <<'EOM' unless defined $CONFIG{'mail-template'};
 Hi,
 
@@ -438,16 +500,35 @@ sub readwrite_gpg($$$$$%) {
 
 sub ask($$;$$) {
        my ($question, $default, $forceyes, $forceno) = @_;
 
 sub ask($$;$$) {
        my ($question, $default, $forceyes, $forceno) = @_;
-       return $default if $forceyes and $forceno;
-       return 1 if $forceyes;
-       return 0 if $forceno;
        my $answer;
        my $answer;
+       my $yn = $default ? '[Y/n]' : '[y/N]';
        while (1) {
        while (1) {
-               print $question,' ',($default ? '[Y/n]' : '[y/N]'), ' ';
+               print $question,' ',$yn, ' ';
+               if ($forceyes && $forceno) {
+                       print "$default (from config/command line)\n";
+                       return $default;
+               };
+               if ($forceyes) {
+                       print "YES (from config/command line)\n";
+                       return 1;
+               };
+               if ($forceno) {
+                       print "NO (from config/command line)\n";
+                       return 0;
+               };
+
                $answer = <STDIN>;
                $answer = <STDIN>;
+               if (!defined $answer) {
+                       $OUTPUT_AUTOFLUSH = 1;
+                       die "\n\n".
+                           "End of STDIN reached.  Are you using xargs?  Caff wants to read from STDIN,\n".
+                           "so you can't really use it with xargs.  A patch against caff to read from\n".
+                           "the terminal would be appreciated.\n".
+                           "For now instead of   cat keys | xargs caff  do  caff `cat keys`\n";
+               };
                chomp $answer;
                chomp $answer;
-               last if ((defined $answer) && (length $answer <= 1));
-               print "grrrrrr.\n";
+               last if ((length $answer == 0) || ($answer =~ m/^[yYnN]$/) );
+               print "What about $yn is so hard to understand?\nAnswer with either 'n' or 'y' or just press enter for the default.\n";
                sleep 1;
        };
        my $result = $default;
                sleep 1;
        };
        my $result = $default;
@@ -501,9 +582,16 @@ sub export_key($$) {
 
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
 
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
-       $gpg->options->hash_init(
-               'homedir' => $gnupghome,
-               'armor' => 1 );
+       if (defined $gnupghome) {
+               $gpg->options->hash_init(
+                       'homedir' => $gnupghome,
+                       'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
+                       'armor' => 1 );
+       } else {
+               $gpg->options->hash_init(
+                       'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
+                       'armor' => 1 );
+       };
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
        my $pid = $gpg->export_keys(handles => $handles, command_args => [ $keyid ]);
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
        my $pid = $gpg->export_keys(handles => $handles, command_args => [ $keyid ]);
@@ -521,7 +609,9 @@ sub import_key($$) {
 
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
 
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
-       $gpg->options->hash_init( 'homedir' => $gnupghome );
+       $gpg->options->hash_init(
+               'homedir' => $gnupghome,
+               '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);
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
        my $pid = $gpg->import_keys(handles => $handles);
@@ -569,9 +659,9 @@ sub send_mail($$$@) {
                        Type        => "application/pgp-keys",
                        Disposition => 'attachment',
                        Encoding    => "7bit",
                        Type        => "application/pgp-keys",
                        Disposition => 'attachment',
                        Encoding    => "7bit",
-                       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],
                        Data        => $key->{'key'},
                        Data        => $key->{'key'},
-                       Filename    => "0x$key_id.".$key->{'serial'}.".asc");
+                       Filename    => "0x$key_id.".$key->{'serial'}.".signed-by-0x".$CONFIG{'keyid'}[0].".asc");
        };
 
        if ($can_encrypt) {
        };
 
        if ($can_encrypt) {
@@ -580,7 +670,7 @@ sub send_mail($$$@) {
                my $gpg = GnuPG::Interface->new();
                $gpg->call( $CONFIG{'gpg'} );
                $gpg->options->hash_init( 'homedir' => $GNUPGHOME,
                my $gpg = GnuPG::Interface->new();
                $gpg->call( $CONFIG{'gpg'} );
                $gpg->options->hash_init( 'homedir' => $GNUPGHOME,
-                       'extra_args' => '--always-trust',
+                       'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always } ],
                        'armor' => 1 );
                $gpg->options->meta_interactive( 0 );
                my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
                        'armor' => 1 );
                $gpg->options->meta_interactive( 0 );
                my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
@@ -698,6 +788,7 @@ if (!GetOptions (
        '--no-download'   =>  \$params->{'no-download'},
        '-S'              =>  \$params->{'no-sign'},
        '--no-sign'       =>  \$params->{'no-sign'},
        '--no-download'   =>  \$params->{'no-download'},
        '-S'              =>  \$params->{'no-sign'},
        '--no-sign'       =>  \$params->{'no-sign'},
+       '--key-file=s@'   =>  \$params->{'key-files'},
        )) {
        usage(\*STDERR, 1);
 };
        )) {
        usage(\*STDERR, 1);
 };
@@ -725,6 +816,10 @@ if ($params->{'local-user'}) {
 for my $keyid (@ARGV) {
        $keyid =~ s/^0x//i;
        unless ($keyid =~ /^([A-F0-9]{8}|[A-F0-9]{16}||[A-F0-9]{40})$/i) {
 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;
+               };
                print STDERR "$keyid is not a keyid.\n";
                usage(\*STDERR, 1);
        };
                print STDERR "$keyid is not a keyid.\n";
                usage(\*STDERR, 1);
        };
@@ -735,32 +830,59 @@ $CONFIG{'no-download'} = $params->{'no-download'} if defined $params->{'no-downl
 $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'};
 $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
 #################
 
 
 #################
 # import own keys
 #################
+for my $keyid (@{$CONFIG{'keyid'}}) {
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
        $gpg->options->hash_init(
                'homedir' => $GNUPGHOME,
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
        $gpg->options->hash_init(
                'homedir' => $GNUPGHOME,
-               'extra_args' => '--keyserver='.$CONFIG{'keyserver'} );
+               '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();
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
-       $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] );
-       my $pid = $gpg->list_public_keys(handles => $handles, command_args => $CONFIG{'keyid'});
+       my $pid = $gpg->list_public_keys(handles => $handles, command_args => $keyid);
        my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
        waitpid $pid, 0;
        my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
        waitpid $pid, 0;
+
        if ($stdout eq '') {
        if ($stdout eq '') {
-               warn ("No data from gpg for list-key\n");
-               next;
+               warn ("No data from gpg for list-key\n"); # There should be at least 'tru:' everywhere.
        };
        };
-       foreach my $keyid (@{$CONFIG{'keyid'}}) {
-               unless ($stdout =~ /^pub:(?:[^:]*:){3,3}$keyid:/m) {
-                       info("Importing $keyid");
-                       system "gpg --export $keyid | gpg --import --homedir $GNUPGHOME";
-               }
+       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;
+    }
+}
 
 #############################
 # receive keys from keyserver
 
 #############################
 # receive keys from keyserver
@@ -775,7 +897,7 @@ if ($CONFIG{'no-download'}) {
        $gpg->call( $CONFIG{'gpg'} );
        $gpg->options->hash_init(
                'homedir' => $GNUPGHOME,
        $gpg->call( $CONFIG{'gpg'} );
        $gpg->options->hash_init(
                'homedir' => $GNUPGHOME,
-               'extra_args' => '--keyserver='.$CONFIG{'keyserver'} );
+               'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always }, '--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 ]);
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
        my $pid = $gpg->recv_keys(handles => $handles, command_args => [ @KEYIDS ]);
@@ -835,6 +957,8 @@ 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, '--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'} || "";
                push @command, '--edit', $keyid;
                push @command, 'sign';
                push @command, split ' ', $CONFIG{'gpg-sign-args'} || "";
@@ -852,10 +976,11 @@ for my $keyid (@keyids_ok) {
        #################
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
        #################
        my $gpg = GnuPG::Interface->new();
        $gpg->call( $CONFIG{'gpg'} );
-       $gpg->options->hash_init( 'homedir' => $GNUPGHOME );
+       $gpg->options->hash_init(
+               'homedir' => $GNUPGHOME,
+               'extra_args' => [ qw{ --no-auto-check-trustdb --trust-model=always --with-colons --fixed-list-mode } ] );
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
        $gpg->options->meta_interactive( 0 );
        my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
-       $gpg->options->hash_init( 'extra_args' => [ '--with-colons', '--fixed-list-mode' ] );
        my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]);
        my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
        waitpid $pid, 0;
        my $pid = $gpg->list_public_keys(handles => $handles, command_args => [ $keyid ]);
        my ($stdout, $stderr, $status) = readwrite_gpg('', $inputfd, $stdoutfd, $stderrfd, $statusfd);
        waitpid $pid, 0;
@@ -913,7 +1038,7 @@ for my $keyid (@keyids_ok) {
                $gpg->call( $CONFIG{'gpg-delsig'} );
                $gpg->options->hash_init(
                        'homedir' => $tempdir,
                $gpg->call( $CONFIG{'gpg-delsig'} );
                $gpg->options->hash_init(
                        'homedir' => $tempdir,
-                       '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 } ] );
                ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
                $pid = $gpg->wrap_call(
                        commands     => [ '--edit' ],
                ($inputfd, $stdoutfd, $stderrfd, $statusfd, $handles) = make_gpg_fds();
                $pid = $gpg->wrap_call(
                        commands     => [ '--edit' ],
@@ -1059,7 +1184,7 @@ for my $keyid (@keyids_ok) {
                        if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) {
                                my $address = $uid->{'text'};
                                $address =~ s/.*<(.*)>.*/$1/;
                        if (!$uid->{'is_uat'} && ($uid->{'text'} =~ /@/)) {
                                my $address = $uid->{'text'};
                                $address =~ s/.*<(.*)>.*/$1/;
-                               if (ask("Send mail to '$address' for $uid->{'text'}?", 1, $CONFIG{'mail'})) {
+                               if (ask("Mail signature for $uid->{'text'} to '$address'?", 1, $CONFIG{'mail'})) {
                                        my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
 
                                        my $keydir = "$KEYSBASE/$DATE_STRING";
                                        my $mail = send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
 
                                        my $keydir = "$KEYSBASE/$DATE_STRING";