5 # See the pod documentation at the end of this file for author,
6 # copyright, and licence information.
9 # libintl-perl (Locale::Recode)
10 # OR libtext-iconv-perl (Text::Iconv),
11 # OR the "recode" binary
16 # * use the user's normal keyring to find signatures
17 # * support for multiple user keys
18 # * better charset conversion
21 my $VERSION = qq$Rev$;
35 gpgsigs $VERSION- http://pgp-tools.alioth.debian.org/
36 (c) 2004 Uli Martens <uli\@youam.net>
37 (c) 2004, 2005 Peter Palfrader <peter\@palfrader.org>
38 (c) 2004, 2005 Christoph Berg <cb\@df7cb.de>
44 my ($fd, $error) = @_;
49 Usage: $PROGRAM_NAME [-r] [-t <charset>] <keyid> <keytxt> [<outfile>]
51 keyid is a long or short keyid (e.g. DE7AAF6E94C09C7F or 94C09C7F)
52 separate multiple keyids with ','
53 -r call gpg --recv-keys before proceeding
54 -f <charset> convert <keytxt> from charset
55 -t <charset> convert UIDs to charset in output
61 my ($fromcharset, $charset, $recv_keys);
62 Getopt
::Long
::config
('bundling');
64 '-f=s' => \
$fromcharset,
67 help
=> sub { usage
(*STDOUT
, 0); },
68 version
=> sub { version
(*STDOUT
); exit 0;},
69 ) or usage
(*STDERR
, 1);
73 $fromcharset ||= "ISO-8859-1";
74 $charset ||= $ENV{LC_ALL
} || $ENV{LC_CTYPE
} || $ENV{LANG
} || "ISO-8859-1";
75 $charset = "ISO-8859-1" unless $charset =~ /[\.-]/;
79 my ($rf, $rt, $if, $it);
80 if (eval "require Locale::Recode") {
81 $rf = Locale
::Recode
->new (from
=> $fromcharset, to
=> $charset) if $fromcharset;
82 $rt = Locale
::Recode
->new (from
=> 'UTF-8', to
=> $charset);
83 } elsif (eval "require Text::Iconv") {
84 $if = Text
::Iconv
->new($fromcharset, $charset) if $fromcharset;
85 $it = Text
::Iconv
->new("UTF-8", $charset);
94 } elsif (defined $if) {
95 return $if->convert($text);
97 my $pid = open3
(\
*WTRFH
, \
*RDRFH
, \
*ERRFH
, 'recode', "$fromcharset..$charset");
101 my $result = <RDRFH
>;
105 die ("'recode' failed, is it installed?\n") unless defined $result;
116 } elsif (defined $it) {
117 my $result = $it->convert($text);
118 warn ("Could not convert '$text'\n") unless defined $result;
119 return (defined $result) ?
$result : $text
121 my $pid = open3
(\
*WTRFH
, \
*RDRFH
, \
*ERRFH
, 'recode', "utf8..$charset");
125 my $result = <RDRFH
>;
129 warn ("'recode' failed, is it installed?\n") unless defined $result;
130 return (defined $result) ?
$result : $text
136 my $mykey = uc(shift @ARGV);
137 my $keytxt = (shift @ARGV) || usage
(*STDERR
, 1);
138 my $outfile = (shift @ARGV) || '-';
140 my @mykeys = split /,/, $mykey;
141 map { s/^0x//i; } @mykeys;
143 if (!@mykeys || scalar @ARGV) {
146 foreach my $falsekey (grep { $_ !~ /^([0-9A-F]{16,16}|[0-9A-F]{8,8})$/ } @mykeys) {
147 print STDERR
"Invalid keyid $falsekey given\n";
151 -r
$keytxt or die ("$keytxt does not exist\n");
154 # get list of keys in file
156 open (TXT
, $keytxt) or die ("Cannot open $keytxt\n");
158 if ( m/^pub +(?:\d+)[DR]\/([0-9A
-F
]{8}) [0-9]{4}-[0-9]{2}-[0-9]{2} *(.*)/ ) {
165 # get all known signatures
167 print STDERR
"Requesting keys from keyserver\n";
168 system "gpg --recv-keys @keys";
171 print STDERR
"Running --list-sigs, this will take a while ";
172 open SIGS
, "gpg --fixed-list-mode --with-colons --list-sigs @keys 2>/dev/null |"
173 or die "can't get gpg listing";
175 my ($key, $uid, $sigs);
177 if ( m/^pub:(?:.*?:){3,3}([0-9A-F]{16,16}):/ ) {
182 if ( m/^uid:(?:.*?:){8,8}(.*):/s ) {
186 if ( m/^sig:(?:.*?:){3,3}([0-9A-F]{8})([0-9A-F]{8}):(?:.*?:){5,5}(.*?):/ ) {
188 if ($class eq '10x') {
190 } elsif ($class eq '11x') {
192 } elsif ($class eq '12x') {
194 } elsif ($class eq '13x') {
199 # Handle the case where one UID was signed multiple times
200 # with different signature classes.
201 my $before = $sigs->{$key}->{$uid}->{$1.$2};
202 if (defined $before) {
203 if ($before eq 'S' || $before eq 's') {
204 $sigs->{$key}->{$uid}->{$1.$2} = $class;
205 } elsif ($class eq 'S' || $class eq 's') {
206 # intentionall left blank
207 } elsif ($before < $class) {
208 $sigs->{$key}->{$uid}->{$1.$2} = $class;
211 $sigs->{$key}->{$uid}->{$1.$2} .= $class;
213 $sigs->{$key}->{$uid}->{$2} = $sigs->{$key}->{$uid}->{$1.$2};
220 next if ( m/^(rev|sub|tru):/ );
221 warn "unknown value: '$_', key: ".(defined $key ?
$key :'none')."\n";
226 for my $k ( keys %{$sigs} ) {
227 if ( $k =~ m/^[0-9A-F]{8}([0-9A-F]{8})$/ ) {
228 $sigs->{$1} = $sigs->{$k};
234 open MD
, "gpg --print-md md5 $keytxt|" or warn "can't get gpg md5\n";
237 open MD
, "gpg --print-md sha1 $keytxt|" or warn "can't get gpg sha1\n";
243 my $metatxt = quotemeta($keytxt);
244 $MD5 =~ s/^$metatxt:\s*//;
245 $SHA1 =~ s/^$metatxt:\s*//;
251 my ($key, $uid) = @_;
252 if (! defined $sigs->{$key}->{$uid}) {
253 warn "uid '$uid' not found on key $key\n";
254 return '(' . (' ' x
@mykeys) . ')';
257 foreach my $mykey (@mykeys) {
258 $r .= defined $sigs->{$key}->{$uid}->{$mykey} ?
$sigs->{$key}->{$uid}->{$mykey} : ' ';
264 print STDERR
"Annotating $keytxt, writing into $outfile\n";
265 open (TXT
, $keytxt) or die ("Cannot open $keytxt\n");
266 open (WRITE
, '>'.$outfile) or die ("Cannot open $outfile for writing\n");
268 $_ = myfromrecode
($_);
269 if (/^MD5 Checksum:/ && defined $MD5) {
270 s/[_[:xdigit:]][_ [:xdigit:]]+_/$MD5/;
272 if (/^SHA1 Checksum:/ && defined $SHA1) {
273 s/[_[:xdigit:]][_ [:xdigit:]]+_/$SHA1/;
275 if ( m/^pub +(?:\d+)[DR]\/([0-9A
-F
]{8}) [0-9]{4}-[0-9]{2}-[0-9]{2} *(.*)/ ) {
278 #if ($uid) { # in gpg 1.2, the first uid is here
279 # print WRITE print_tag($key, $uid) . " $_";
283 if ( m/^uid +(.*)$/ ) {
285 die "key is undefined" unless defined $key;
286 die "uid is undefined, key $key" unless defined $uid;
287 die "bad tag from $key | $uid" unless defined (print_tag
($key, $uid));
288 print WRITE print_tag
($key, $uid) . " $_";
294 print WRITE
"Legend:\n";
295 foreach my $i (0 .. @mykeys - 1) {
296 print WRITE
'('. ' 'x
$i . 'S' . ' 'x
(@mykeys-$i-1) . ") signed with $mykeys[$i]\n";
304 B<gpgsigs> - annotate list of GnuPG keys with already done signatures
308 B<gpgsigs> [-r] [-f I<charset>] [-t I<charset>] I<keyid>I<[>B<,>I<keyidI<[>B<,>I<...>I<]>>I<]> F<keytxt> [F<outfile>]
312 B<gpgsigs> was written to assist the user in signing keys during a keysigning
313 party. It takes as input a file containing keys in C<gpg --list-keys> format
314 and prepends every line with a tag indicating if the user has already signed
315 that uid. When the file contains C<MD5 Checksum:> or C<SHA1 Checksum:> lines
316 and placeholders (C<__ __>), the checksum is inserted.
324 Call I<gpg --recv-keys> before creating the output.
328 Convert F<keytxt> from I<charset>. The default is ISO-8859-1.
332 Convert UIDs to I<charset>. The default is derived from LC_ALL, LC_CTYPE, and
333 LANG, and if all these are unset, the default is ISO-8859-1.
337 Use this keyid (8 or 16 byte) for annotation. Multiple keyids can be separated
342 Read input from F<keytxt>.
346 Write output to F<outfile>. Default is stdout.
352 The following key signing parties are using B<gpgsigs>:
354 http://www.palfrader.org/ksp-lt2k4.html
356 http://www.palfrader.org/ksp-lt2k5.html
360 B<GnuPG> is known to change its output format quite often. This version has
361 been tested with gpg 1.2.5 and gpg 1.4.1. YMMV.
367 http://pgp-tools.alioth.debian.org/
369 =head1 AUTHORS AND COPYRIGHT
371 (c) 2004 Uli Martens <uli@youam.net>
373 (c) 2004, 2005 Peter Palfrader <peter@palfrader.org>
375 (c) 2004, 2005 Christoph Berg <cb@df7cb.de>
381 Redistribution and use in source and binary forms, with or without
382 modification, are permitted provided that the following conditions
385 1. Redistributions of source code must retain the above copyright
386 notice, this list of conditions and the following disclaimer.
388 2. Redistributions in binary form must reproduce the above copyright
389 notice, this list of conditions and the following disclaimer in the
390 documentation and/or other materials provided with the distribution.
392 3. The name of the author may not be used to endorse or promote products
393 derived from this software without specific prior written permission.
395 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
396 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
397 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
398 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
399 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
400 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
401 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
402 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
403 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
404 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.