Skip to content

Instantly share code, notes, and snippets.

@mschmitt
Created April 23, 2014 11:56
Show Gist options
  • Save mschmitt/11212470 to your computer and use it in GitHub Desktop.
Save mschmitt/11212470 to your computer and use it in GitHub Desktop.
This correlates per-session sshd logfile entries on a very busy server with many concurrent sessions.
#!/usr/bin/perl -w
use strict;
use diagnostics;
#use File::Navigate; (bundled below)
my $nav = File::Navigate->new($ARGV[0]);
my @connects = @{$nav->find(qr/sshd\[\d+\]: Connection from .+ port \d/)};
foreach (@connects){
$nav->cursor($_);
my $line = $nav->get();
print "$line\n";
$line =~ /sshd\[(\d+)\]:/;
my $parent_pid = $1;
my $child_pid = 0;
my $walked_lines = 0;
my $auth_success = 0;
while ($line = $nav->getnext()){
next unless ($line =~ /sshd\[(\d+)\]:/);
my $this_pid = $1;
if ($line =~ /sshd\[$parent_pid\]: Connection from .+ port \d/){
print "Stop: New connection on parent PID $parent_pid\n";
last;
}elsif ($line =~ /sshd\[$child_pid\]: Connection from .+ port \d/){
print "Stop: New connection on child PID $child_pid\n";
last;
}elsif ($line =~ /User child is on pid $parent_pid\b/){
print "Stop: Another user child on parent PID: $parent_pid\n";
last;
}elsif ($line =~ /User child is on pid $child_pid\b/){
print "Stop: Another user child on child PID: $child_pid\n";
last;
}elsif ($line =~ /sshd\[$parent_pid\]:.+session closed for user/){
$line .= " <<< Done.";
print "$line\n";
last;
}
if ($this_pid == $parent_pid){
if ($line =~ /sshd\[$parent_pid\]: Accepted publickey/){
$line .= " <<<< PUBKEY ACCEPTED";
}elsif ($line =~ /sshd\[$parent_pid\]:.+session opened/){
$line .= " <<<< SESSION OPENED";
$auth_success = 1;
}elsif ($line =~ /sshd\[$parent_pid\]:.+User child is on pid (\d+)/){
$child_pid = $1;
$line .= " <<< Got child PID: $child_pid";
}
print "$line\n" unless ($line =~ /Deprecated pam_stack module/);
}elsif ($this_pid == $child_pid){
print "$line\n" unless ($line =~ /Deprecated pam_stack module/);
}
if (++$walked_lines >= 1000){
print "Giving up search for parent $parent_pid / child $child_pid after $walked_lines lines.\n";
last;
}
}
print "Successful auth: " ;
if($auth_success){
print "YES";
}else{
print "NO";
}
print "\n";
print "---\n";
}
exit;
package File::Navigate;
use strict;
use warnings;
=head1 NAME
File::Navigate - Navigate freely inside a text file
=head1 DESCRIPTION
The module is a glorified wrapper for tell() and seek().
It aims to simplify the creation of logfile analysis tools by
providing a facility to jump around freely inside the contents
of large files without creating the need to slurp excessive
amounts of data.
=head1 SYNOPSIS
use File::Navigate;
my $nav = File::Navigate->new('/var/log/messages');
# Read what's below the "cursor":
my $first = $nav->get;
# Advance the cursor before reading:
my $second = $nav->getnext;
my $third = $nav->getnext;
# Advance the cursor by hand:
$nav->next;
my $fourth = $nav->get;
# Position the cursor onto an arbitrary line:
$nav->cursor(10);
my $tenth = $nav->get;
# Reverse the cursor one line backward:
$nav->prev;
my $ninth = $nav->get;
# Reverse the cursor before reading:
my $eigth = $nav->getprev;
# Read an arbitrary line:
my $sixth = $nav->get(6);
=cut
our @ISA = qw(Exporter);
our @EXPORT_OK = qw();
our $VERSION = '1.0';
=head1 CLASS METHODS
=head2 I<new()>
Open the file and create an index of the lines inside of it.
my $mapper = File::Navigate->new($filename);
=cut
sub new($){
my $class = shift;
my $file;
unless ($file = shift){
die "No file specified\n";
}
unless (-e $file){
die "File not found: $file\n";
}
unless (-r $file){
die "File not readable: $file\n";
}
my $self = {};
$self->{'cursor'} = 1;
$self->{'lineindex'} = {};
$self->{'lineindex'}->{1} = 0;
open my $fh, "$file"
or die "Can't open $file: $!\n";
while (<$fh>){
my $thisline = $.;
my $nextline = $thisline + 1;
$self->{'lineindex'}->{$nextline} = tell $fh;
}
$self->{'length'} = scalar(keys %{$self->{'lineindex'}}) - 1 ;
$self->{'fh'} = $fh;
bless $self;
}
=head1 OBJECT METHODS
=head2 I<count()>
Returns the number of lines in the file ("wc -l")
my $lines = $nav->count;
=cut
sub length(){
my $self = shift;
return $self->{'length'};
}
=head2 I<cursor()>
Returns the current cursor position and/or sets the cursor.
my $cursor = $nav->cursor(); # Query cursor position.
my $cursor = $nav->cursor(10); # Set cursor to line 10
=cut
sub cursor($){
my $self = shift;
if (my $goto = shift){
$self->{'cursor'} = $goto;
}
return $self->{'cursor'};
}
=head2 I<get()>
Gets the line at the cursor position or at the given position.
my $line = $nav->get(); # Get line at cursor
my $line = $nav->get(10); # Get line 10
=cut
sub get($){
my $self = shift;
my $fh = $self->{'fh'};
my $getline;
$getline = $self->{'cursor'} unless ($getline = shift);
if ($getline < 1){
warn "WARNING: Seek before first line.";
return undef;
}elsif($getline > $self->{'length'}){
warn "WARNING: Seek beyond last line.";
return undef;
}
seek ($fh, $self->{'lineindex'}->{$getline}, 0);
my $gotline = <$fh>;
chomp $gotline;
return $gotline;
}
=head2 I<next()>
Advance the cursor position by one line. Returns the new cursor position.
Returns I<undef> if the cursor is already on the last line.
my $newcursor = $nav->next();
=cut
sub next(){
my $self = shift;
if ($self->{'cursor'} == $self->{'length'}){
return undef;
}
$self->{'cursor'}++;
return $self->{'cursor'};
}
=head2 I<prev()>
Reverse the cursor position by one line. Returns the new cursor position.
Returns I<undef> if the cursor is already on line 1.
my $newcursor = $nav->prev();
=cut
sub prev(){
my $self = shift;
if ($self->{'cursor'} == 1){
return undef;
}
$self->{'cursor'}--;
return $self->{'cursor'};
}
=head2 I<getnext()>
Advance to the next line and return it.
Returns I<undef> if the cursor is already on the last line.
my $newcursor = $nav->getnext();
=cut
sub getnext(){
my $self = shift;
$self->next or return undef;
return $self->get;
}
=head2 I<getprev()>
Reverse to the previous line and return it:
Returns I<undef> if the cursor is already on line 1.
my $newcursor = $nav->getprev();
=cut
sub getprev(){
my $self = shift;
$self->prev or return undef;
return $self->get;
}
=head2 I<find()>
Find lines containing given regex. Returns array with line numbers.
my @lines = @{$nav->find(qr/foo/)};
=cut
sub find($){
my $self = shift;
my $regex = shift;
my @results;
for (my $lineno = 1; $lineno <= $self->{'length'}; $lineno++){
my $line = $self->get($lineno);
if ($line =~ $regex){
push @results, $lineno;
}
}
return \@results;
}
sub DESTROY(){
my $self = shift;
close $self->{'fh'};
}
=head1 EXAMPLE
I<tac>, the opposite of I<cat>, in Perl using File::Navigate:
#!/usr/bin/perl -w
use strict;
use File::Navigate;
foreach my $file (reverse(@ARGV)){
my $nav = File::Navigate->new($file);
# Force cursor beyond last line
$nav->cursor($nav->length()+1);
print $nav->get()."\n" while $nav->prev();
}
=head1 BUGS
Seems to lack proper error handling.
=head1 LIMITATIONS
Works only on plain text files. Sockets, STDIO etc. are not supported.
=head1 PREREQUISITES
Tested on Perl 5.6.1.
=head1 STATUS
Mostly harmless.
=head1 AUTHOR
Martin Schmitt <mas at scsy dot de>
=cut
1;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment