#!/usr/bin/perl

=cut
#########################################################
ssh_logger: Accepts input from rsyslog, analyzes ssh messages
            for authorized access.  Sends reports on invalid
            messages
Author:     Doug O'Leary
Created:    03/31/14
#########################################################
=cut
use strict;
use POSIX;
use IO::Socket;
use File::stat;

#########################################################
# Functions
#########################################################

sub check_pids
{   my ($pids, $host, $pid, $fh) = @_;
    our $fkeys;
    my @required_keys = qw / dtg user source finger /;
    my $success = 0;
    my $old_fh = select($fh);
    my $alert  = 'dkoleary\@olearycomputers.com';
    my $Logger = `hostname`; chomp $Logger;
    my $from   = "root\@$host.olearycomputers.com";
    my $subj   = "Unknown ssh key logged to $Logger";
    $| = 1;

    ### Check to see if all required keys are in place:
    foreach my $key (@required_keys)
    {   $success++ if (defined($pids->{$host}->{$pid}->{$key}));    }

    if ($success == 4)
    {   my ($email, $finger);

        if ($pids->{$host}->{$pid}->{finger} ne "password")
        {   (defined($fkeys->{$pids->{$host}->{$pid}->{finger}}->{email})) ?
                ($email = $fkeys->{$pids->{$host}->{$pid}->{finger}}->{email}) :
            ($email = "Unknown");
            $finger = substr($pids->{$host}->{$pid}->{finger},0,11);
        }
        else { $email = "password"; $finger="N/A"}
        printf("%-16s %-16s %8d %-20s %-15s %-12s %s\n",
            $pids->{$host}->{$pid}->{dtg}, $host,
            $pid, $pids->{$host}->{$pid}->{user},
            $pids->{$host}->{$pid}->{source}, $finger, $email)
                if ($email !~ /MPI_shared/);
#       if ($email =~ /unknown/i)
#       {   open (Mail, "| /usr/sbin/sendmail -t") ||
#               print "Can't execute sendmail - ($!)";
#           print Mail "To: $alert\n";
#           print Mail "From: $from\n";
#           print Mail "Subject: $subj\n";
#           printf(Mail "%-16s %-16s %8d %-20s %-15s %-12s %s\n",
#               $pids->{$host}->{$pid}->{dtg}, $host,
#               $pid, $pids->{$host}->{$pid}->{user},
#               $pids->{$host}->{$pid}->{source}, $finger, $email);
#           close Mail;
#       }
        delete $pids->{$host}->{$pid};
    }
    select ($old_fh);
}

sub read_fingerprints
{   my $file = shift;
    my %fkeys;

    open (In, "< $file ") || die "Can't read $file - ($!)";
    while (<In>)
    {   chomp;
        my ($finger, $source, $email) = split;
        $fkeys{$finger}->{source}  = $source;
        $fkeys{$finger}->{email} = $email;
    }
    close In;
    return \%fkeys;
}

my $ssh_log  = "/opt/logs/ssh_access.log";
my $sudo_log = "/opt/logs/sudo_access.log";
my $key_list = "/usr/local/etc/ssh_key_list";
our $fkeys    = read_fingerprints($key_list);
my ($fh, $sufh, %pids);
$SIG{HUP} = sub { our $fkeys = read_fingerprints($key_list);    };

if (! -f $ssh_log || stat($ssh_log)->size == 0)
{   open(Out, "> $ssh_log") || die "Can't create $ssh_log - ($!)";
    printf(Out "%-16s %-16s %8s %-20s %-15s %-12s %s\n", "Date", "Tgt host",
        "PID", "Tgt User", "Source IP", "Fingerprint", "Key owner");
    print Out "=" x 120 . "\n";
    close (Out);
}
open ($fh, ">> $ssh_log") || die "Can't open $ssh_log - ($!)";
open ($sufh, ">> $sudo_log") || die "Can't open $sudo_log - ($!)";

while (<STDIN>)
{   next if (!/accepted (publickey|password)/i && ! /found matching [dr]sa key:/i);
    chomp;
    switch:
    {   if (/accepted password/i)
        {   my ($dtg) = $_ =~ m{(\w+\s+\d+\s+[^\s]*)};
            my ($host_ip) = $_ =~ m{$dtg\s+(\d+.\d+.\d+.\d+).*};
            my ($pid) = $_ =~ m{.*?sshd\[(\d+)\].*};
            my ($user, $source) = $_ =~
                m{.*?accepted password for ([^\s]*) from (\d+.\d+.\d+.\d+).*}i;
            my $host = gethostbyaddr(inet_aton($host_ip), AF_INET);
            (length($host) > 0) ? ($host =~ s/\..*//g) : ($host = $host_ip);

            $pids{$host}->{$pid}->{dtg}    = $dtg;
            $pids{$host}->{$pid}->{user}   = $user;
            $pids{$host}->{$pid}->{source} = $source;
            $pids{$host}->{$pid}->{finger} = 'password';
            check_pids(\%pids, $host, $pid, $fh);
            last switch;
        }
        if (/accepted publickey/i)
        {   my ($dtg) = $_ =~ m{(\w+\s+\d+\s+[^\s]*)};
            my ($host_ip) = $_ =~ m{$dtg\s+(\d+.\d+.\d+.\d+).*};
            my ($pid) = $_ =~ m{.*?sshd\[(\d+)\].*};
            my ($user, $source) = $_ =~
                m{.*?accepted publickey for ([^\s]*) from.*?(\d+.\d+.\d+.\d+).*}i;
            my $host = gethostbyaddr(inet_aton($host_ip), AF_INET);
            (length($host) > 0) ? ($host =~ s/\..*//g) : ($host = $host_ip);

            $pids{$host}->{$pid}->{dtg}    = $dtg;
            $pids{$host}->{$pid}->{user}   = $user;
            $pids{$host}->{$pid}->{source} = $source;
            check_pids(\%pids, $host, $pid, $fh);
            last switch;
        }
        if (/found matching/i)
        {   my ($dtg) = $_ =~ m{(\w+\s+\d+\s+\d+:\d+:\d+) .*};
            my ($host_ip) = $_ =~ m{$dtg\s+(\d+.\d+.\d+.\d+) .*};
            my ($pid, $finger) = $_ =~
                m{.*?sshd\[(\d+)\]: found matching [rd]sa key: (.*)}i;
            my $host = gethostbyaddr(inet_aton($host_ip), AF_INET);
            (length($host) > 0) ? ($host =~ s/\..*//g) : ($host = $host_ip);
            $pids{$host}->{$pid}->{finger} = $finger;
            check_pids(\%pids, $host, $pid, $fh);
            last switch;
        }
    }
}

__DATA__

Read out fkeys:
===============

foreach my $key (sort keys %{$fkeys})
{   printf("%s %-14s %s\n", $key, $fkeys->{$key}->{source}, 
        $fkeys->{$key}->{email});    
}

Apr  8 19:56:18 172.16.77.203 sshd[11050]: Accepted password for jysn from 10.200.23.185 port 52922 ssh2
Apr  8 19:56:18 172.16.77.203 sshd[11050]: pam_unix(sshd:session): session opened for user jysn by (uid=0)
Apr  8 21:11:51 172.16.77.203 sshd[11050]: pam_unix(sshd:session): session closed for user jysn

