#!/usr/bin/perl -w ## Copyright 2013, Aaron Macks. This may be used and distributed as per ## BSD New License http://opensource.org/licenses/BSD-3-Clause use strict; use Getopt::Long; ## for debugging use Data::Dumper; my $logfile; my $namefile; my $name; my $DEBUG=0; ## prototypes sub help(); sub get_name_from_file( $$$ ); ## parse cli args, make sure we have a log and either a file of names ## or a single name my $result = GetOptions ("log=s" => \$logfile, "name=s" => \$name, "namefile=s" => \$namefile, "debug" => \$DEBUG); # flag if (!$result) { warn "Error parsing args"; exit 1; } else { if (!defined $logfile){ warn "Logfile to parse is a required arg, --log=logfile/path"; help(); exit 1; } elsif (!-s $logfile){ warn "cannot read logfile $logfile"; exit 1; } if ((!defined $name) && (!defined $namefile)){ warn "Either a namefile to check or a single name is a "; warn "required arg, --name=NAME or --namefile=path/to/namefile"; exit 1; } else { if ((defined $namefile) && (!-s $namefile)){ warn "namefile $namefile defined but not readable"; exit 1; } } } ## args parsed if (defined $namefile) { warn "Checking $logfile for names in $namefile" if ($DEBUG); } else { warn "Checking $logfile for $name " if ($DEBUG); } my %names = get_name_from_file($name, $namefile, $DEBUG); #warn Dumper(\%names); warn "Number of names: " . keys %names if ($DEBUG); ## now we have a hash of names, we walk through the log and update it with the login times. ## sample log line: ProFTPD Default Installation [26827] 124x38x140x162.ap124.ftth.ucom.ne.jp [19/Apr/2012:20:32:16 -0400] "USER dbiist" 331 my $log_fh; if (open($log_fh, "<", $logfile)){ while (<$log_fh>){ my $line = $_; if ($line =~ /ProFTPD.+USER/m){ my ($source_host, $date, $time, $username) = $line =~ /^.*\[\d+\]\s+([a-z0-9.\-]+)\s+\[(\d+\/\w+\/\d{4}):(\d+:\d+:\d+)\s+[0-9\-]+\]\s+"USER\s+(\w+)\"/; warn "User: $username from $source_host on $date at $time" if ($DEBUG); if (!defined $username) { sleep 0; ## noop } elsif (defined $names{$username}){ my @last_login = ($date, $time, $source_host); $names{$username} = \@last_login; } else { warn "we don't care about $username" if ($DEBUG); } } } close ($log_fh); } else { warn "Error opening $logfile $?"; exit 1; } #print Dumper(\%names); ## now we have a hash with values, let's just output them as csv print "Username, Date, Time, Hostname\n"; for my $user (keys %names){ my $outstring = "$user,"; if (ref($names{$user}) eq "ARRAY") { for my $field (@{$names{$user}}){ $outstring .= $field .","; } chop $outstring; } else { ## hash doesn't contain an array, probably never $outstring .= $names{$user}; } print $outstring . "\n"; } ########### ## Eventually this should have some usage documentation in it. Currently just ## there so it can be called where appropriate sub help() { } ## return a hash with each name as a key sub get_name_from_file( $$$ ){ my ($name, $namefile, $DEBUG) = @_; my @names; if (defined $namefile){ my $fh; open($fh, "<", $namefile) ; while (<$fh>){ chomp; push @names, $_; } close($fh); } else { ## single name push @names, $name; } ## now we have an array of names, make them into a hash my %name_hash; foreach my $lname (@names){ $name_hash{$lname} = "NEVER"; } return %name_hash; }