#! /usr/bin/perl
# $Header: /global/src/usr.bin/src/RCS/ssh-keyfinger.pl,v 1.2 2009/04/08 18:25:31 pb22 Exp $

use strict;
use Getopt::Long;
use Pod::Usage;
use File::Temp "tempfile";

$"=','; # Interpolate list values in strings with comma separators

# This  program implements something like the following with bells on:
# grep $1 /etc/ssh/ssh_known_hosts2 | 
#   while read i ; do 
#     echo $i >/tmp/key
#     ssh-keygen -l -f /tmp/key
#   done

# Version number
my $version="1.3";

# What are we called?
my $prog;
($prog = $0) =~ s/^.*?([\w.]*)$/$1/;

my $ssh_keygen='/usr/bin/ssh-keygen';
my $opt_help='';
my $opt_version='';
my $opt_number='';
my $opt_debug='';
my $opt_verbose;
my $opt_output='';
my $opt_keyfile='/etc/ssh/ssh_known_hosts2';
my $opt_keytype='both';
$opt_keyfile='/etc/ssh/ssh_known_hosts-out' unless -r $opt_keyfile;

Getopt::Long::Configure ("bundling");
my $result = GetOptions (
                         'out|o=s' => \$opt_output,
                         'file|f=s' => \$opt_keyfile,
                         'type|t=s' => \$opt_keytype,
                         'help|h' => \$opt_help,
                         'verbose|v' => \$opt_verbose,
                         'debug|d+' => \$opt_debug,
                         'version|V' => \$opt_version);

print STDERR "$prog: DEBUG: level=$opt_debug.\n" if $opt_debug;

# if --help print usage and exit
if ($opt_help) {            
   pod2usage(1);
   exit 0;
}

# if --version print the name and version number
if ($opt_version) {
   print STDOUT "$prog $version\n";
   exit 0;
}

# If GetOptions failed then print usage and exit
if (!$result || $#ARGV != 0) {
   pod2usage(1);
   exit 1;
}

if ($opt_output) {
   open (STDOUT, ">$opt_output") || die "$prog: error: Can't open $opt_output fo
r writing. $!.\n";
}

$opt_keytype = lc $opt_keytype;
if ($opt_keytype !~ m/^(rsa)|(dss)|(both)$/) {
   die "$prog: error: key type must be one of 'rsa', 'dss' or 'both'.\n";
}

$opt_keytype =~ s/both/(rsa)|(dss)/;
$opt_keytype =~ s/rsa/ssh-rsa/;
$opt_keytype =~ s/dss/ssh-dss/;

open (KEYS, "<$opt_keyfile") || die "$prog: error: Can't open $opt_keyfile for reading. $!.\n";

if (! -x $ssh_keygen) {
   die "$prog: error: $ssh_keygen is not executable.";
}

while (<KEYS>) {
   my @fields = split;
   my @aliases = split /,/, $fields[0];
   my $cname = $aliases[0];
   my @matches;
   if (@matches = grep m/$ARGV[0]/, @aliases) {
      my ($hostnames, $keytype, $keytext, $created) = split;
      if ($keytype =~ m/^[0-9]+$/) { # If sshV1 key
         $keytype = 'sshV1';
      } else {
         next if ($keytype !~ m/$opt_keytype/);
      }
      print "$keytype $created\n" if $opt_debug;
      print "$cname [@matches] $keytype\n" if $opt_verbose;
      my ($fh, $fn) = tempfile("${prog}XXXXXX", SUFFIX=>".tmp");
      print $fh $_;
      close $fh || die "$prog: error writing $fn. $!.\n";
      print "Wrote $fn OK.\n" if $opt_debug > 1;
      system "$ssh_keygen", ('-l', '-f', "$fn");
      if ($? >> 8) {die "$prog: error: '$ssh_keygen -l -f $fn' failed.\n"};
      unlink $fn || die "$prog: error: can't unlink $fn. $!.\n";
   }
}

__END__

=head1 NAME

ssh-keyfinger - Outputs fingerprints of ssh host keys.

=head1 SYNOPSIS

ssh-keyfinger [I<OPTIONS>] I<pattern>

ssh-keyfinger [-B<-V,--version>] 

ssh-keyfinger [-B<-h,--help>] 

=head1 OPTIONS

=over 8

=item B<-t> I<key_type> 

=item B<--type>=I<key_type> 

Output fingerprints of keys of type I<key_type> which can be one of B<rsa>,
B<dss> or B<both>.

=item B<-f> I<keys> 

=item B<--file>=I<keys> 

Reads keys from I<file> instead of the default I</etc/ssh/ssh_lnown_hosts2>.

=item B<-o> I<output-file>

=item B<--out>=I<output-file>

Output keys to file I<output-file> instead of standard output.

=item B<-v>

=item B<--verbose>

Output the matching host name(s) and the key types as well as the fingerprints. 

=back

=head1 DESCRIPTION

Outputs the ssh key fingerprint of the keys matching the perl regular
expression I<pattern>.  The keys are taken from the file I<keys> or
I</etc/ssh/ssh_known_hosts2> if none specified.

=head1 SEE ALSO

ssh-keygen(1)

=cut

