#!/usr/bin/perl -w # (c) Christoph Berg # This program is free software covered by the GPL. # $Id: gpgmailsign,v 1.14 2004/07/08 18:01:27 cb Exp $ # # 030714 cb v0.1: Initial version # 030909 cb v0.2: more robust gpg output parsing # 031016 cb v0.3: even more gpg output parsing # 031017 cb v0.4: still more gpg output parsing, reimplemented purgesigs in perl # 031017 cb v0.4a: -qmfu flags # 031019 cb v0.4b: removed stray [ from regexps # 040222 cb v0.5: support for revoked keys (Tollef Fog Heen) # 2004-06-27 cb v0.6: multiple signing keys # 2004-07-02 cb v0.7: process multiple keys from arguments/file, purge subkeys # 2004-07-08 cb v0.7a: fixed silly syntax error in purgesigs.expect (thanks # Alexander Wirt) use strict; my $version = "v0.7a"; use lib '.'; use GnuPG::Wrapper; $ENV{LC_ALL} = "C"; my %opts; use Getopt::Std; getopts('fF:g:hqrsu:v', \%opts); sub help { print < Syntax: $0 [-fqrsv] [-F keys.txt] [-g agpg] [-u local_user{,...}] key -f force output even without email address -F file to read keys from, one keyid per line (will be modified) -g name of gpg-agent wrapper (default: agpg) -q do not ask for confirmation on startup -r call gpg --recv-key before proceeding -s send out mail immediately (default: write shell skript) -v be verbose -u key to sign with (comma-separated for multiple keys, default: use \$PGPKEY) EOT exit 0; } help() if (@ARGV == 0 xor $opts{F}) or $opts{h}; unless (-d "work") { print "Creating work/\n" if $opts{v}; mkdir "work", 0700 or die "mkdir work: $!"; } #print "Loading key into agpg\n" if $opts{v}; #my $agpg = $opts{g} || "agpg"; #system "$agpg < /dev/null"; if(@ARGV) { foreach my $key (@ARGV) { sign_and_mail_key($key); } exit(0); } while(1) { # loop over file my $key; open K, $opts{F} or die "$opts{F}: $!"; while() { chomp; next if /^#/; next if /^\s*$/; die "$opts{F}:$.: parse error: $_" unless /^([x0-9A-F]+)$/i; $key = $1; last; } close K; last unless $key; sign_and_mail_key($key); rename($opts{F}, "$opts{F}.tmp"); open KIN, "$opts{F}.tmp" or die "$opts{F}.tmp: $!"; open KOUT, ">$opts{F}" or die "$opts{F}: $!"; while() { chomp; if($_ eq $key) { print KOUT "# $key (signed ". scalar(localtime) .")\n"; } else { print KOUT "$_\n"; } } close KOUT; close KIN; unlink "$opts{F}.tmp"; } exit(0); sub sign_and_mail_key { my $key = shift; my $gpg = GnuPG::Wrapper::new($opts{g}); $gpg->{verbose} = $opts{v}; my @my_keyids = split(/,/, $opts{u} || $ENV{PGPKEY} || help()); foreach my $keyid (@my_keyids) { $keyid = $gpg->normalize_keyid($keyid); } $gpg->set_mykeyid_list(@my_keyids); if ($opts{r}) { system "gpg --recv-key $key"; } print "Getting list of UIDs\n" if $opts{v}; $gpg->set_keyid($key); $gpg->get_uid_list(); my $uid_count = $gpg->get_uid_list(); die "Key '$key' not found" unless $uid_count; undef $key; $gpg->print_fingerprint(); unless ($opts{q}) { print "Continue y/N, ^C to abort >"; $_ = ; exit 0 unless /^[yj]/i; } my $mail = "work/$gpg->{keyid}.mail"; open M, "> $mail" or die "$mail: $!"; print M "## gpgmailsign $gpg->{keyid}\n"; foreach my $uidnr (1 .. $uid_count) { my $uid = $gpg->set_uidnr($uidnr); unless($uid) { print "Uid $uidnr is revoked, skipping\n"; print M "# Uid $uidnr is revoked, skipping\n"; next; } unless ($uid =~ /<.*@.*\..*>/ or $opts{f}) { print "Uid $uidnr/$uid contains no email adress, skipping\n"; print M "# Uid $uidnr/$uid contains no email adress, skipping\n"; next; } print "Uid $uidnr/$uid:\n"; print M "# Uid $uidnr/$uid\n"; my $uid_quoted = $uid; $uid_quoted =~ s/'/'\\''/g; # sh quoting is evil $gpg->set_keyring("work/$gpg->{keyid}-$uidnr.gpg"); unlink $gpg->{keyring}; $gpg->import_keys(@my_keyids, $gpg->{keyid}); $gpg->purge_key(); # sanity check $gpg->right_uid_selected() or die "purge_key failed."; for my $mykeyid (@my_keyids) { print "Signing uid $uidnr/$uid with key $mykeyid...\n"; $gpg->set_mykeyid($mykeyid); if($gpg->is_signed()) { print "Uid $uidnr/$uid is already signed by $mykeyid, skipping\n"; next; } $gpg->sign_key(); # sanity check $gpg->is_signed() or die "key not signed after signing?!"; } my $file = "work/$gpg->{keyid}-$uidnr.txt"; open T, "> $file" or die "$file: $!"; open I, "key_instructions.txt" or die "key_instructions: $!"; local $_; while() { s/__UID__/$uid/g; s/__UID_QUOTED__/$uid_quoted/g; s/__KEYID__/$gpg->{keyid}/g; s/__SIGNED_KEY__/$gpg->export_key()/e; s!__GPGMAILSIGN_URL__!http://www.df7cb.de/projects/gpgmailsign/!g; s/__GPGMAILSIGN_VERSION__/$version/g; print T; } close I; close T; open I, "mail_template.txt" or die "mail_template: $!"; while() { s/__UID__/$uid/g; s/__UID_QUOTED__/$uid_quoted/g; s/__KEYID__/$gpg->{keyid}/g; s/__ENCRYPTED_KEY__/$gpg->encrypt_file($file)/e; s!__GPGMAILSIGN_URL__!http://www.df7cb.de/projects/gpgmailsign/!g; s/__GPGMAILSIGN_VERSION__/$version/g; print M; } unlink $gpg->{keyring}, "$gpg->{keyring}~"; } close M; print "Output shell skript is in work/$gpg->{keyid}.mail\n"; if ($opts{s}) { system "sh -v work/$gpg->{keyid}.mail"; } undef $gpg; } # sub sign_and_mail_key