prepare for upload
[pgp-tools.git] / keylookup / keylookup
index a56741f66eecb5dba4fd59f9e3a3dbf987206e11..a69f888ec8857a0eea50a2f0e9c3c14abd194b80 100755 (executable)
@@ -1,9 +1,9 @@
-#!/usr/bin/perl -Tw
+#!/usr/bin/perl -w
 
-# (c) 2000, 2002 Christian Kurz <shorty@debian.org>,
-#                Peter Palfrader <peter@palfrader.org>
+# Copyright (c) 2000, 2002 Christian Kurz <shorty@debian.org>,
+# Copyright (c) 2000, 2002, 2005 Peter Palfrader <peter@palfrader.org>
 #
-# $Id: keylookup,v 1.34 2002/09/19 03:35:10 weasel Exp $
+# $Id$
 #
 #   This program is free software; you can redistribute it and/or modify
 #   it under the terms of the GNU General Public License as published by
 #
 #   You should have received a copy of the GNU General Public License
 #   along with this program; if not, write to the Free Software Foundation,
-#   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#   Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 #
-# Keylookup homepage: http://www.palfrader.org/keylookup/
-#           CVS, BTS: http://savannah.gnu.org/projects/keylookup/
+# Keylookup is part of pgp-tools:
+#           http://pgp-tools.alioth.debian.org/
+#           svn://svn.debian.org/pgp-tools/trunk/
+#           http://svn.debian.org/wsvn/pgp-tools/trunk/
 
-delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV', 'PATH'};
+delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
 $|=1; # Always flush buffers
 
 
@@ -31,26 +33,11 @@ use IO::Socket;
 use IPC::Open3;
 use Getopt::Long;
 
-my $version = '2.1 ($Id: keylookup,v 1.34 2002/09/19 03:35:10 weasel Exp $)';
-
-# The port to use for keyservers unless given otherwise.
-my $PORT=11371;
-# The default proxy port which is used unless the port is explicitly given
-# in the http_proxy environment variable.
-my $PROXY_PORT=3128;
-
-# Name of the GnuPG binary. The executeable must the in the PATH. This may
-# be overriden using the --gnupg= switch.
-my $GnuPG='gpg';
-# Where to find GnuPG's options file.
-my $GNUPGOPTIONS=(defined $ENV{GNUPGHOME} ? $ENV{GNUPGHOME} : $ENV{'HOME'}.'/.gnupg' ) . '/options';
-# Full path to the dialog and whiptail executeable.
-my $Dialog = '/usr/bin/dialog';
-my $Whiptail = '/usr/bin/whiptail';
+my $version = '3.0 ($Id$)';
 
 # Strings to use in the dialog|whiptail frontend
 my $TITLE = 'Import Keys';
-my $BACKTITLE = 'KeyLookup $Revision: 1.34 $';
+my $BACKTITLE = 'KeyLookup $Revision$';
 my $INSTRUCTION = 'Select keys to import:';
 #
 my @TPUTCOL=('tput', 'cols');
@@ -69,59 +56,15 @@ my @WHIPTAILSIZE;
 my $keyserverWasSetOnCmdLine = 0;
 
 
-# Queries a remote keyserver (using a proxy if requested) and returns
-# the keyservers anser. No module is used deliberatly to make this
-# script run on as many systems as possible.
-
-sub fetchIt($$$$) {
-       my $server = shift;
-       my $port = shift;
-       my $requestURL = shift;
-       my $honorproxy = shift;
-       
-       my $result;
-       my $remote;
-
-       if ($honorproxy && defined $ENV{'http_proxy'}) {
-               my $proxyserver;
-               my $proxyport;
-
-               if ( $ENV{'http_proxy'} =~ m#^http://(\S+):(\d+)/?$# ) {
-                       $proxyserver = $1;
-                       $proxyport   = $2;
-               } elsif ( $ENV{'http_proxy'} =~ m#^http://(\S+)/?$# ) {
-                       $proxyserver = $1;
-                       $proxyport   = $PROXY_PORT;
-               } else {
-                       die ("Unkown http_proxy format");
-               };
-
-               $remote = IO::Socket::INET->new(
-                       Proto           => 'tcp',
-                       PeerAddr        => $proxyserver,
-                       PeerPort        => $proxyport
-               ) || die ("Cannot connect to prox: $!");
-
-               my $URL=sprintf("http://%s:%s%s/", $server, $port, $requestURL);
-               printf $remote "GET %s HTTP/1.1\n\n", $URL;
-       } else {
-               $remote = IO::Socket::INET->new(
-                       Proto           => 'tcp',
-                       PeerAddr        => $server,
-                       PeerPort        => $port
-               ) || die ("Cannot connect to keysever: $!");
-
-               printf $remote "GET %s HTTP/1.1\nHost: %s\n\n\n", $requestURL, $server;
-       };
-
-
-       {
-               local $/;
-               $/=undef;
-               $result = <$remote>;
-       };
-       return $result;
-};
+# Maps algorithm numbers to algorithm types as defined in RFC 2400.
+my %ALGOS = (
+                1 => 'R',         # RSA
+                2 => 'r',         # RSA encrypt only  (deprecated)
+                3 => 's',         # RSA sign only (deprecated)
+                16 => 'g',        # ElGamal encrypt only
+                20 => 'g',        # ElGamal sign and encrypt (all OpenPGP implementations cryptographically broken, do not use.  no longer part of OpenPGP)
+                17 => 'D'         # DSA
+            );
 
 
 # getHits receives all options as a parameter, calls fetchIT to
@@ -130,60 +73,54 @@ sub fetchIt($$$$) {
 sub getHits($) {
        my $options = shift;
 
-       die ("$0: No keyserver given!\n") unless (defined $options->{'keyserver'});
-
-       my $result = fetchIt(
-               $options->{'keyserver'}, 
-               $options->{'port'}, 
-               sprintf("/pks/lookup?op=index&search=%s", $options->{'search'}),
-               $options->{'honor-http-proxy'}
-       );
-
-       $result =~ s/<.*?>//g;
-       $result =~ s/&gt;/>/g;
-       $result =~ s/&lt;/</g;
-       $result =~ s/&quot;/"/g;
-       $result =~ s/&amp;/&/g;
-
-       my @lines = split (/\r?\n/, $result);
-
-       shift @lines;
-       shift @lines;
-       shift @lines;
-       shift @lines;
+       my $pid = open(KID, '-|');
+       defined ($pid) or die ("Cannot fork: $!\n");
+       unless ($pid) {
+               close (STDIN);
+               open (STDIN, "/dev/null") || die ("Cannot open /dev/null as stdin: $!\n");
 
+               # child
+               my @ops = ('gpg');
+               if ($options->{'keyserver'}) {
+                       push @ops, '--keyserver='.$options->{'keyserver'};
+               };
+               push @ops, '--command-fd=0';
+               push @ops, '--batch';
+               push @ops, '--no-tty';
+               push @ops, '--with-colons';
+               push @ops, '--fixed-list-mode';
+               push @ops, '--search';
+               push @ops, @{$options->{'search'}};
+               exec(@ops);
+               die ("Cannot exec GnuPG: $!\n");
+       };
        my %keys;
        my $currentKey;
 
-       for (@lines) {
-               if (/^pub /) {
-                       m, ^pub                 # pub at the start of the line
-                          \s+                  # whitespace
-                          (\d+)                # bits                  1024
-                          ([A-Za-z]*)          # optional type (R or D usually) D
-                          /                    # a slash
-                          ([0-9A-Za-z]+)       # keyid                 94C09C7F
-                          \s+                  # whitespace
-                          (\d+)/(\d+)/(\d+)    # date                  1999/11/10
-                          \s+                  # whitespace
-                          (.*)                 # primary user id       Peter Palfrader
-                          ,x or
-                               warn ("Unexpected format: $_\n"), next;
-
-                       $currentKey = { 'bits' => $1,
-                                       'type' => $2,
-                                       'keyid' => $3,
-                                       'year' => $4,
-                                       'month' => $5,
-                                       'day' => $6,
-                                       'uid' => [ $7 ]
+       while (<KID>) {
+               chomp;
+               if ( $_ eq "" ) { next; }
+               my ($type, undef) = split /:/;
+               if ($type eq 'pub') {
+                       my ($type, $keyid, $algo, $bits, $created, undef, $revoked) = split /:/;
+                       $currentKey = { 'bits' => $bits,
+                                       'type' => (defined $ALGOS{$algo} ? $ALGOS{$algo} : '#'.$algo),
+                                       'keyid' => $keyid,
+                                       'created' => $created,
+                                       'revoked' => $revoked,
+                                       'uid' => []
                                      };
-                       $keys{ $3 } = $currentKey;
-               } elsif (defined $currentKey) {
-                       s/^\s+//;
-                       push @{ $currentKey->{'uid'} }, $_;
+                       $keys{ $keyid } = $currentKey;
+               } elsif (defined $currentKey && $type eq 'uid') {
+                       my ($type, $name) = split /:/;
+                       if ($currentKey->{'revoked'} eq 'r') {
+                               $name .= ' [REVOKED]';
+                       };
+                       push @{ $currentKey->{'uid'} }, $name;
                };
        };
+       close KID;
+       waitpid $pid, 0;
 
        return \%keys;
 };
@@ -227,13 +164,15 @@ sub prepareForDialog {
        my $keys = shift;
        my @keyargs = ();
 
-       for my $keyid (keys %$keys) {
+       for my $keyid (sort {- ($keys->{$a}->{'created'} <=> $keys->{$b}->{'created'})} keys %$keys) {
                for (@{ $keys->{$keyid}->{'uid'} }) {
                        push @keyargs,
                                $keys->{$keyid}->{'keyid'},
                                length() <= $MAX_UID_FIELD_LEN ? $_ : substr($_, 0, $MAX_UID_FIELD_LEN-2) . '..',
                                'off';
                };
+               my (undef,undef,undef,$mday,$mon,$year,undef,undef,undef) = localtime ($keys->{$keyid}->{'created'});
+               push @keyargs, $keys->{$keyid}->{'keyid'}, sprintf( "[created: %s-%s-%s]", $year+1900, $mon+1, $mday ), 'off';
                push @keyargs, '-'x8, '-'x40, 'off';
        };
        pop @keyargs;
@@ -247,14 +186,13 @@ sub prepareForTXT {
        my $keys = shift;
        my @lines = ();
 
-       for my $keyid (keys %$keys) {
+       for my $keyid (sort {- ($keys->{$a}->{'created'} <=> $keys->{$b}->{'created'})} keys %$keys) {
+               my (undef,undef,undef,$mday,$mon,$year,undef,undef,undef) = localtime ($keys->{$keyid}->{'created'});
                push @lines, sprintf( "%s%s/%s %s-%s-%s\n",
                                      $keys->{$keyid}->{'bits'},
                                      $keys->{$keyid}->{'type'},
                                      $keys->{$keyid}->{'keyid'},
-                                     $keys->{$keyid}->{'year'},
-                                     $keys->{$keyid}->{'month'},
-                                     $keys->{$keyid}->{'day'} );
+                                     $year+1900, $mon+1, $mday );
                push @lines, map { ' 'x26 . $_ . "\n" } @{ $keys->{$keyid}->{'uid'} };
                push @lines, "\n";
        };
@@ -272,7 +210,8 @@ sub callDialog {
        
        my %unique;
        my @keys = grep { !$unique{$_}++ }
-               grep { /^[0-9A-Fa-f]{8}$/ }
+               # get the keyID; can be 8, 16 or 40 nibbles
+               grep { /^((([a-zA-Z0-9]{24})?[a-zA-Z0-9]{8})?[a-zA-Z0-9]{8})$/ }
                map { s/\s//g; $_ } <ERRFH>;
        wait;
 
@@ -290,14 +229,14 @@ sub selectKeys {
        $frontend = 'dialog' unless (defined $frontend);
 
        if ($frontend eq 'dialog') {
-               unless (-x $Dialog) {
-                       warn("Dialog ($Dialog) not executeable/installed. Falling back to Whiptail\n");
+               unless (`which dialog` && $? == 0) {
+                       warn("Dialog not executeable/installed. Falling back to Whiptail\n");
                        $frontend = 'whiptail';
                }
        };
        if ($frontend eq 'whiptail') {
-               unless (-x $Whiptail ) {
-                       warn("Whiptail ($Whiptail) not executeable/installed. Falling back to plain\n");
+               unless (`which whiptail` && $? == 0 ) {
+                       warn("Whiptail not executeable/installed. Falling back to plain\n");
                        $frontend = 'plain';
                }
        };
@@ -305,7 +244,7 @@ sub selectKeys {
        if ( $frontend eq 'dialog' ) {
                calcDialogSize;
                my @ARGS = (
-                       $Dialog,
+                       'dialog',
                        '--backtitle',
                        $BACKTITLE,
                        '--separate-output',
@@ -319,7 +258,7 @@ sub selectKeys {
        } elsif ( $frontend eq 'whiptail' ) {
                calcDialogSize;
                my @ARGS = (
-                       $Whiptail,
+                       'whiptail',
                        '--backtitle',
                        $BACKTITLE,
                        '--separate-output',
@@ -351,18 +290,19 @@ sub importKeys {
        my $keyids = shift;
        my $options = shift;
 
-       my @args = ( $options->{'gnupg'},
-                 '--keyserver',
-                 $options->{'keyserver'},
-                 '--recv-keys');
+       my @args = ('gpg');
+       if ($options->{'keyserver'}) {
+               push @args, '--keyserver='.$options->{'keyserver'};
+       };
+       push @args,  '--recv-keys';
        for my $keyid (@$keyids) {
                # untaint keyids
-               my ($cleanid) = $keyid =~ /^([a-zA-Z0-9]{8})$/;
+               my ($cleanid) = $keyid =~ /^((([a-zA-Z0-9]{24})?[a-zA-Z0-9]{8})?[a-zA-Z0-9]{8})$/;
                warn ("keyid '$keyid' has unexpected format - skipping\n"), next
                        unless defined $cleanid;
                push @args, $cleanid;
        }
-                 
+
        print "Calling GnuPG...\n";
        exec (@args) || die "can't exec gnupg: $!\n"; # won't return
 };
@@ -375,11 +315,8 @@ Syntax: keylookup [options] <searchstring>
 
 Options:
        --keyserver=<keyserver> Select keyserver
-       --port=<port>           Use a non standard port
        --frontend=<frontend>   One of whiptail, dialog or plain
        --importall             Import all matched keys
-       --gnupg=<gnupg>         use this gnupg binary
-       --honor-http-proxy      honor the http_proxy environment varibale
        --help                  print this message
 
 EOF
@@ -395,49 +332,24 @@ sub version {
        my %options;
        GetOptions( \%options,
                'keyserver=s',
-               'port=i',
                'frontend=s',
                'importall',
-               'gnupg=s',
-               'honor-http-proxy',
                'version',
                'help') or
                &usage(1);
        &version(0) if ($options{'version'});
        &usage(0) if ($options{'help'} || ( scalar(@ARGV) == 0));
 
-       ## If the keyserver was not given on the command line, lurk into
-       ## GnuPG's config file, it might be defined there.
-       $keyserverWasSetOnCmdLine = defined $options{'keyserver'};
-       unless (defined $options{'keyserver'} &&
-               defined $options{'honor-http-proxy'} ) {
-               if ( open(GNUPGOPTIONS, $GNUPGOPTIONS) ) {
-                       my $keyserver = $options{'keyserver'};
-                       while (<GNUPGOPTIONS>) {
-                               $options{'keyserver'} = $1 if (/^\s*keyserver\s+(\S+?)[#\s]/i && ! defined $keyserver);
-                               $options{'honor-http-proxy'} = 1 if /^\s*(keyserver-options\s+)honor-http-proxy\s/i;
-                       };
-                       close(GNUPGOPTIONS) || warn("Cannot close $GNUPGOPTIONS: $!\n");
-               } else {
-                       warn ("Cannot open $GNUPGOPTIONS: $!\n");
-               };
-       };
-       $options{'port'}  = $PORT unless (defined $options{'port'});
-       $options{'gnupg'} = $GnuPG unless (defined $options{'gnupg'});
-
-       # Untaint it
-       $options{'keyserver'} =~ /(.*)/;
-       $options{'keyserver'} = $1;
-
        ## Take all additional arguments to the program as a search target,
        ## escape the string for use in URLs.
-       $options{'search'} = join ' ', @ARGV;
-       $options{'search'} =~ s/ ( [^A-Za-z0-9] )
-                              / '%' . unpack("H2", $1)
-                              /xeg;
+       $options{'search'} = \@ARGV;
        my $keys = getHits( \%options );
        my $keyids;
 
+       if (scalar keys %$keys == 0) {
+               print "GnuPG did not find any keys matching your search string.\n";
+               exit 0;
+       };
        if ($options{'importall'}) {
                my @allkeys = keys %$keys;
                $keyids = \@allkeys;