Config option for Bccing the sender. Thanks to Joost van Baal.
[pgp-tools.git] / caff / caff
index 53d76c318a83412a1545540602e89fc5f16a2c10..59ee3b8dcae3e4813fdb9417e6885a26c8c31712 100755 (executable)
--- a/caff/caff
+++ b/caff/caff
@@ -176,6 +176,11 @@ The UIDs for which signatures are included in the mail.
 
 =back
 
+=item B<bcc> [string]
+
+Address to send blind carbon copies to when sending mail.
+Default: none.
+
 =back
 
 =head1 AUTHOR
@@ -433,6 +438,9 @@ sub usage($$) {
        exit $exitcode;
 };
 
+######
+# export key $keyid from $gnupghome
+######
 sub export_key($$) {
        my ($gnupghome, $keyid) = @_;
 
@@ -450,6 +458,34 @@ sub export_key($$) {
        return $stdout;
 };
 
+######
+# import a key from the scalar $asciikey into a gpg homedirectory in $tempdir
+######
+sub import_key($$) {
+       my ($gnupghome, $asciikey) = @_;
+
+       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);
+       my ($stdout, $stderr, $status) = readwrite_gpg($asciikey, $inputfd, $stdoutfd, $stderrfd, $statusfd);
+       waitpid $pid, 0;
+
+       if ($status !~ /^\[GNUPG:\] IMPORT_OK/m) {
+               return undef;
+       };
+       return 1;
+};
+
+
+######
+# Send an email to $address.  If $can_encrypt is true then the mail
+# will be PGP/MIME encrypted to $longkeyid.
+#
+# $longkeyid, $uid, and @attached will be used in the email and the template.
+######
 #send_mail($address, $can_encrypt, $longkeyid, $uid, @attached);
 sub send_mail($$$@) {
        my ($address, $can_encrypt, $key_id, @keys) = @_;
@@ -524,11 +560,15 @@ 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("Bcc", $CONFIG{'bcc'}) if defined $CONFIG{'bcc'};
        $message_entity->head->add("User-Agent", $USER_AGENT);
        $message_entity->send();
        $message_entity->stringify();
 };
 
+######
+# clean up a UID so that it can be used on the FS.
+######
 sub sanitize_uid($) {
        my ($uid) = @_;
 
@@ -538,6 +578,47 @@ sub sanitize_uid($) {
        return $good_uid;
 };
 
+sub delete_signatures($$$$$$) {
+       my ($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $keyids) =@_;
+
+       my $signed_by_me = 0;
+
+       my ($stdout, $stderr, $status) =
+               readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1);
+
+       while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) {
+               # sig:?::17:EA2199412477CAF8:1058095214:::::13x:
+               my @sigline = grep { /^sig/ } (split /\n/, $stdout);
+               $stdout =~ s/\n/\\n/g;
+               notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX
+               my $line = pop @sigline;
+               my $answer = "no";
+               if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance
+                       debug("[sigremoval] doing line $line.");
+                       my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line;
+                       if ($signer eq $longkeyid) {
+                               debug("[sigremoval] selfsig ($signer).");
+                               $answer = "no";
+                       } elsif (grep { $signer eq $_ } @{$keyids}) {
+                               debug("[sigremoval] signed by us ($signer).");
+                               $answer = "no";
+                               $signed_by_me = $signed_by_me > $created ? $signed_by_me : $created;
+                       } else {
+                               debug("[sigremoval] not interested in that sig ($signer).");
+                               $answer = "yes";
+                       };
+               } else {
+                       debug("[sigremoval] no sig line here, only got: ".$stdout);
+               };
+               ($stdout, $stderr, $status) =
+                       readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1);
+       };
+
+       return $signed_by_me;
+};
+
+
+
 my $USER;
 my @KEYIDS;
 my $params;
@@ -749,20 +830,12 @@ for my $keyid (@keyids_ok) {
                my $this_uid_text = '';
                $uid_number++;
                debug("Doing key $keyid, uid $uid_number");
+               my $tempdir = tempdir( "caff-$keyid-XXXXX", DIR => '/tmp/', CLEANUP => 1);
 
                # import into temporary gpghome
                ###############################
-               my $tempdir = tempdir( "caff-$keyid-XXXXX", DIR => '/tmp/', CLEANUP => 1);
-               my $gpg = GnuPG::Interface->new();
-               $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) {
+               my $result = import_key($tempdir, $asciikey);
+               unless ($result) {
                        warn ("Could not import $keyid into temporary gnupg.\n");
                        next;
                };
@@ -802,27 +875,45 @@ for my $keyid (@keyids_ok) {
                        if ($uid_number != $i) {
                                debug("mark for deletion.");
                                readwrite_gpg("$i\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
-                               $delete_some = 1;
+                               $delete_some++;
                        } else {
                                debug("keep it.");
                                $have_one = 1;
-                               $this_uid_text = ($type eq 'uid') ? $uidtext : 'attribute';
+                               $this_uid_text = ($type eq 'uid') ? $uidtext : '[attribute]';
                                $is_uat = $type eq 'uat';
                        };
                        $i++;
                };
                debug("Parsing stdout output done.");
-               if ($is_uat) {
-                       notice("Can't handle attribute userid of key $keyid.");
-                       next;
-               };
                unless ($have_one) {
                        debug("Uid ".($uid_number-1)." was the last, there is no $uid_number.");
                        info("key $keyid done.");
                        last;
                };
+
+               my $prune_some_sigs_on_uid;
+               my $prune_all_sigs_on_uid;
+               if ($is_uat) {
+                       debug("handling attribute userid of key $keyid.");
+                       if ($uid_number == 1) {
+                               debug(" attribute userid is #1, unmarking #2 for deletion.");
+                               readwrite_gpg("2\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
+                               $delete_some--;
+                               $prune_some_sigs_on_uid = 1;
+                               $prune_all_sigs_on_uid = 2;
+                       } else {
+                               debug("attribute userid is not #1, unmarking #1 for deletion.");
+                               readwrite_gpg("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
+                               $delete_some--;
+                               $prune_some_sigs_on_uid = 2;
+                               $prune_all_sigs_on_uid = 1;
+                       };
+               } else {
+                       $prune_some_sigs_on_uid = 1;
+               };
+
                if ($delete_some) {
-                       debug("need to delete a few uids.");
+                       debug("need to delete $delete_some uids.");
                        readwrite_gpg("deluid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELUID_PROMPT, nocloseinput => 1);
                        readwrite_gpg("yes\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
                };
@@ -839,38 +930,16 @@ for my $keyid (@keyids_ok) {
 
                # delete signatures
                ###################
-               my $signed_by_me = 0;
-               readwrite_gpg("1\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1);
-               ($stdout, $stderr, $status) =
-                       readwrite_gpg("delsig\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_DELSIG_PROMPT, nocloseinput => 1);
-
-               while($status =~ /$KEYEDIT_DELSIG_PROMPT/m) {
-                       # sig:?::17:EA2199412477CAF8:1058095214:::::13x:
-                       my @sigline = grep { /^sig/ } (split /\n/, $stdout);
-                       $stdout =~ s/\n/\\n/g;
-                       notice("[sigremoval] why are there ".(scalar @sigline)." siglines in that part of the dialog!? got: $stdout") if scalar @sigline >= 2; # XXX
-                       my $line = pop @sigline;
-                       my $answer = "no";
-                       if (defined $line) { # only if we found a sig here - we never remove revocation packets for instance
-                               debug("[sigremoval] doing line $line.");
-                               my (undef, undef, undef, undef, $signer, $created, undef, undef, undef) = split /:/, $line;
-                               if ($signer eq $longkeyid) {
-                                       debug("[sigremoval] selfsig ($signer).");
-                                       $answer = "no";
-                               } elsif (grep { $signer eq $_ } @{$CONFIG{'keyid'}}) {
-                                       debug("[sigremoval] signed by us ($signer).");
-                                       $answer = "no";
-                                       $signed_by_me = $signed_by_me > $created ? $signed_by_me : $created;
-                               } else {
-                                       debug("[sigremoval] not interested in that sig ($signer).");
-                                       $answer = "yes";
-                               };
-                       } else {
-                               debug("[sigremoval] no sig line here, only got: ".$stdout);
-                       };
-                       ($stdout, $stderr, $status) =
-                               readwrite_gpg($answer."\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_KEYEDIT_OR_DELSIG_PROMPT, nocloseinput => 1);
+               readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig
+               my $signed_by_me = delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, $CONFIG{'keyid'});
+               readwrite_gpg("$prune_some_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig
+               if (defined $prune_all_sigs_on_uid) {
+                       readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # mark uid for delsig
+                       delete_signatures($inputfd, $stdoutfd, $stderrfd, $statusfd, $longkeyid, []);
+                       readwrite_gpg("$prune_all_sigs_on_uid\n", $inputfd, $stdoutfd, $stderrfd, $statusfd, exitwhenstatusmatches => $KEYEDIT_PROMPT, nocloseinput => 1); # unmark uid from delsig
                };
+
+
                readwrite_gpg("save\n", $inputfd, $stdoutfd, $stderrfd, $statusfd);
                waitpid $pid, 0;
 
@@ -893,7 +962,7 @@ for my $keyid (@keyids_ok) {
                        print KEY $asciikey;
                        close KEY;
 
-                       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 };
 
                        info("$longkeyid $uid_number $this_uid_text done.");
                } else {
@@ -909,7 +978,10 @@ for my $keyid (@keyids_ok) {
                my @attached;
                for my $uid (@UIDS) {
                        trace("UID: $uid->{'text'}\n");
-                       unless ($uid->{'text'} =~ /@/) {
+                       if ($uid->{'is_uat'}) {
+                               my $attach = ask("UID $uid->{'text'} is an attribute UID, attach it to every email sent?", 1);
+                               push @attached, $uid if $attach;
+                       } elsif ($uid->{'text'} !~ /@/) {
                                my $attach = ask("UID $uid->{'text'} is no email address, attach it to every email sent?", 1);
                                push @attached, $uid if $attach;
                        };
@@ -917,7 +989,7 @@ for my $keyid (@keyids_ok) {
 
                notice("Key has no encryption capabilities, mail will be sent unencrypted") unless $can_encrypt;
                for my $uid (@UIDS) {
-                       if ($uid->{'text'} =~ /@/) {
+                       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)) {
@@ -934,59 +1006,3 @@ for my $keyid (@keyids_ok) {
        };
 
 };
-
-
-
-
-###############################################################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