#!/usr/bin/perl -w eval 'exec perl -S $0 "$@"' if 0; # # authsumm.pl - Produce summaries of authorization entries in logfiles # Copyright (C) 1998-2004 by James S. Seymour (jseymour@LinxNet.com) # (See "License", below.) Release 0.0.12 # # # Usage: # authsumm [-q] [-d ] [file1 [file2 [filen [...]]]] # # Options: # -d today means just today # -d yesterday means just "yesterday" # # -q quiet - don't print headings for empty reports # # If no file(s) specified, reads from stdin. # # Unmatched entries go into a report that is automatically suppressed # if there's nothing in it. # # Typical usage: # Produce a report of previous day's activities: # authsumm.pl -d yesterday /var/log/messages /var/log/secure # A report of prior week's activities (after logs rotated): # authsumm.pl /var/log/messages.1 /var/log/secure.1 # What's happened so far today: # authsumm.pl -d today /var/log/messages /var/log/secure # # TBD: # date ranges, "lastweek", etc.?) # # License: # 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 the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You may 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. # # An on-line copy of the GNU General Public License can be found # http://www.fsf.org/copyleft/gpl.html. use Getopt::Std; use strict; # Known DHCPD mac addresses # (Would be nice to just snag these out of dhcpd.conf) my %knownMacs = ( 'xx:xx:xx:xx:xx:xx' => '1', ); my $hostName = `uname -n`; chomp($hostName); # like this, perhaps? my $usageMsg = "usage: authsumm [-q] [-d ]"; use vars qw($opt_q $opt_d); my (@suSuccess, @suFailure, @logins, @failedLogins, @anonFtpLogins, @ftpLogins, @failedFtpLogins, @failedAnonFtpLogins, @refusedFtpConns, @portSentryAdminMsgs, @portSentryAttackMsgs, @portSentryUnkMsgs, @fwMon, @sshFailedLogins, @sshGoodLogins, @sshRefusedConns, @sshMiscMsgs, %dhcpKnown, @dhcpUnknown, @unMatched ); my $lastMsgIgnored = 0; $opt_q = 0; getopts('qd:') || die "$usageMsg\n"; my $dateStr = get_datestr($opt_d) if(defined($opt_d)); while(<>) { chomp; next if(defined($dateStr) && ! /^$dateStr/); if(/^(.*) $hostName last message repeated \d+ times?/o && $lastMsgIgnored) { $lastMsgIgnored = 0; next; } $lastMsgIgnored = 0; if(/^(.*) $hostName .* \(su\) session opened for user ([^ ]+) by ([^(]+)/o) { push(@suSuccess, "$1 $3 -> $2"); } elsif(/^(.*) $hostName su: 'su ([^']+)' succeeded for ([^ ]+) on /o) { push(@suSuccess, "$1 $3 -> $2"); } elsif(/^(.*) $hostName .* auth.*failure; ([^(]+).* -> ([^ ]+) for su/o) { push(@suFailure, "$1 $2 -> $3"); } elsif(/^(.*) $hostName su: 'su ([^']+)' failed for ([^ ]+) on /o) { push(@suFailure, "$1 $2 -> $3"); } elsif(/^(.*) $hostName .* LOGIN ON ([^ ]+) BY ([^ ]+) FROM ([^ ]+)/o) { push(@logins, "$1 $3 ($2/$4)"); } elsif(/^(.*) $hostName .* LOGIN ON ([^ ]+) BY ([^ ]+)/o) { push(@logins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName login: (ROOT) LOGIN ([^ ]+)/o) { push(@logins, "$1 $2 ($3)"); } elsif(/^(.*) $hostName .* FAILED LOGIN [0-9] FROM ([^ ]+) FOR ([^,]+)/o) { push(@failedLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName .* ANONYMOUS FTP LOGIN FROM ([^,]+), ([^ ]+)/o) { push(@anonFtpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName proftpd\[.* \(([^\)]+)\) - ANON (anonymous|ftp):/o) { push(@anonFtpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName .* FTP LOGIN FROM ([^,]+), ([^ ]+)/o) { push(@ftpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName proftpd\[.* \(([^\)]+)\) - USER ([^:]+): Login successful/o) { push(@ftpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName ftpd.* failed login from ([^,]+), ([^ ]+)/o) { push(@failedFtpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName proftpd\[.* \(([^\)]+)\) - USER ([^:]+) \(Login failed\):/o) { push(@failedFtpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName proftpd\[.* \(([^\)]+)\) - no such user '([^']+)'/o) { push(@failedFtpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName proftpd\[.* \(([^\)]+)\) - SECURITY VIOLATION: (\S+) login attempted\./o) { push(@failedFtpLogins, "$1 $3 ($2)"); } elsif(/^(.*) $hostName ftpd.* FTP ACCESS REFUSED \(([^\)]+)\) from (.+)$/o) { push(@failedAnonFtpLogins, "$1 $2 ($3)"); } elsif(/^(.*) $hostName proftpd\[\d+\]: refused connect from ((\d{1,3}\.){3}\d{1,3})$/o) { push(@refusedFtpConns, "$1 $2"); } elsif(/^(.*) $hostName proftpd\[\d+\]: .+ \((.*?\[(:?[0-9]+\.){3}[0-9]+\])\) - Connection refused \((max clients \d+)\)\.$/o) { push(@refusedFtpConns, "$1 $2 - $4"); } elsif(/^(.*) $hostName portsentry\[[0-9]+\]: (\w+): (.+)$/o) { unless($3 =~ /^Going into listen mode on /o) { if($2 eq "adminalert" ) { unless($3 =~ /^PortSentry is now active and listening\./o) { push(@portSentryAdminMsgs, "$1 $3"); } } elsif($2 eq "attackalert") { unless($3 =~ /Host [\d\.]+ has been blocked via /o || $3 =~ /Host: [\d\.]+ is already blocked\. Ignoring$/o || $3 =~ /External command run for host: [\d\.]+ /o) { push(@portSentryAttackMsgs, "$1 $3"); } } else { push(@portSentryUnkMsgs, "$1 $2: $3"); } } } elsif(/^(.*) $hostName fwmon\[[0-9]+\]: (.+)$/o) { unless($2 =~ /^system ok, rechecking in /o) { push(@fwMon, "$1 $2"); } } elsif(/^(.*) $hostName sshd\[[0-9]+\]: (.+)$/o) { my $tTime = $1; my $tMsg = $2; if($tMsg =~ /Wrong response to RSA authentication challenge\./o || $tMsg =~ /Failed rsa for .+ from /o || $tMsg =~ /Failed password for .+ from .+ port .+ ssh2/o) { $tMsg =~ s/ port .+$//o; push(@sshFailedLogins, "$tTime $tMsg"); } elsif($tMsg =~ /Accepted rsa for .+ from /o || $tMsg =~ /Accepted publickey for .+ from .+ port .+ ssh2/o) { $tMsg =~ s/ port .+$//o; push(@sshGoodLogins, "$tTime $tMsg"); } elsif($tMsg =~ s/refused connect from //o) { push(@sshRefusedConns, "$tTime $tMsg"); } elsif( $tMsg =~ /^Closing connection to .+$/o || $tMsg =~ /^Connection closed by .+$/o || $tMsg =~ /^Connection closed by remote host\.$/o || $tMsg =~ /^Enabling compatibility mode for protocol 2\.0$/o || $tMsg =~ /^Failed none for .+ from .+ port [0-9]+ ssh2$/o || $tMsg =~ /^Generating new [0-9]+ bit RSA key\.$/o || $tMsg =~ /^RSA key generation complete\.$/o || $tMsg =~ /^WARNING: .+\/primes does not exist, using old prime$/o ) { next; } else { push(@sshMiscMsgs, "$tTime $tMsg"); } } elsif(/^(.*) $hostName dhcpd: (.+)$/o) { my $tTime = $1; (my $tMsg = $2) =~ s/^(DHCP|BOOT)//o; if($tMsg =~ /^(?:DISCOVER|REQUEST) from ((?:[a-f0-9]{2}:){5}[a-f0-9]{2}) via /o || $tMsg =~ /^(?:REQUEST|ACK|OFFER|NAK|RELEASE) (?:on|for|of) (?:[0-9]{1,3}\.){3}[0-9]{1,3} (?:from|to) ((?:[a-f0-9]{2}:){5}[a-f0-9]{2}) via /o || $tMsg =~ /^REPLY for (?:[0-9]{1,3}\.){3}[0-9]{1,3} to .+? \(((?:[a-f0-9]{2}:){5}[a-f0-9]{2})\) via /o) { if($knownMacs{$1}) { ++$dhcpKnown{$tMsg}; } else { push(@dhcpUnknown, "$tTime $tMsg"); } }else{ push(@dhcpUnknown, "$tTime $tMsg"); } } elsif( /^.{15} $hostName ftpd\[[0-9]+\]: FTP session closed$/o || /^.{15} $hostName identd\[[0-9]+\]: connect from /o || /^.{15} $hostName in\.ftpd\[[0-9]+\]: connect from /o || /^.{15} $hostName in\.tftpd\[[0-9]+\]: connect from 172\.16\.104\.80/o || /^.{15} $hostName in\.telnetd\[[0-9]+\]: connect from /o || /^.{15} $hostName login: DIALUP AT ttyS0 BY /o || /^.{15} $hostName mgetty\[\d+\]: data dev=ttyS0, pid=\d+, caller='none', conn='[^']+', name='', cmd='\/bin\/login', user='[^']+'/o || /^.{15} $hostName named\[[0-9]+\]:\s+([XN]STATS|USAGE|Cleaned cache of|listening on|deleting interface|dangling CNAME|Lame server|bad referral) /o || /^.{15} $hostName named\[\d+\]: ns_(forw|resp): (query\(\S+\) All possible A RR's lame|TCP truncated: )/o || /^.{15} $hostName named\[\d+\]: .*(points to (a )?CNAME|A RR negative cache entry) /o || /^.{15} $hostName PAM_pwdb\[[0-9]+\]: /o || /^.{15} $hostName postfix((\/\w+)?\[[0-9]+\]|-script):\s+/o || /^.{15} $hostName proftpd\[[0-9]+\]: .* - FTP (no transfer timeout|login timed out), disconnected\.\s*$/o || /^.{15} $hostName proftpd\[[0-9]+\]: .* - FTP session (opened|closed)\.\s*$/o || /^.{15} $hostName proftpd\[[0-9]+\]: .+? - PAM\(.+?\): Authentication fail(ure|ed)\.\s*$/o || /^.{15} $hostName proftpd\[[0-9]+\]: pam_authenticate: error Authentication fail(ure|ed)\s*$/o || /^.{15} $hostName proftpd\[[0-9]+\]: .+? - USER .+?: no such user found from /o || /^.{15} $hostName proftpd\[[0-9]+\]:\s+connect from ([0-9]+\.){3}[0-9]+\s*$/o || /^.{15} $hostName rpc\.rstatd\[[0-9]+\]: connect from 127.0.0.1/o || /^.{15} $hostName su: pam_authenticate: error Authentication failed$/o || /^.{15} $hostName syslogd .+: restart\.$/o || /^.{15} $hostName xntpd\[[0-9]+\]: /o ) { ++$lastMsgIgnored; next; } else { push(@unMatched, "$_"); } } if(defined($dateStr)) { print "Auth summaries for $dateStr\n"; } print_summary(\@suFailure, "Failed \"su\"s"); print_summary(\@suSuccess, "Successful \"su\"s"); print_summary(\@failedLogins, "Failed logins"); print_summary(\@logins, "Successful logins"); print_summary(\@sshFailedLogins, "SSHD Failed Logins"); print_summary(\@sshGoodLogins, "SSHD Successful Logins"); print_summary(\@sshRefusedConns, "SSHD Refused Connects"); print_summary(\@sshMiscMsgs, "SSHD Misc Messages"); print_summary(\@failedFtpLogins, "Failed ftp logins"); print_summary(\@ftpLogins, "Real ftp logins"); print_summary(\@anonFtpLogins, "Anonymous ftp logins"); print_summary(\@failedAnonFtpLogins, "Failed anonymous ftp logins"); print_summary(\@refusedFtpConns, "Refused ftp connections"); print_summary(\@portSentryAttackMsgs, "PortSentry Attack Detections"); print_summary(\@portSentryAdminMsgs, "PortSentry Admin Messages"); print_summary(\@portSentryUnkMsgs, "PortSentry Misc Messages"); print_summary(\@fwMon, "Firewall Monitor Messages"); print_hash_summary(\%dhcpKnown, "DHCP Known Activity"); print_summary(\@dhcpUnknown, "DHCP Unknown Activity"); print_summary(\@unMatched, "Unprocessed Log Entries") if(@unMatched); # print array info sub print_summary { my($arrName, $title) = @_; my $dottedLine; unless(@$arrName) { return if($opt_q); $dottedLine = ": none"; } else { $dottedLine = "\n" . "-" x length($title); } print "\n$title$dottedLine\n"; foreach (@$arrName) { print " $_\n"; } } sub print_hash_summary { my($hashRef, $title) = @_; my $dottedLine; unless(%$hashRef) { return if($opt_q); $dottedLine = ": none"; } else { $dottedLine = "\n" . "-" x length($title); } print "\n$title$dottedLine\n"; foreach (sort { $hashRef->{$b} <=> $hashRef->{$a} } keys %$hashRef) { printf(" %4d %s\n", $hashRef->{$_}, $_); } } # return a date string to match in log sub get_datestr { my @monthNames = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); my $aDay = 60 * 60 * 24; my $dateOpt = $_[0]; my $time = time(); if($dateOpt eq "yesterday") { $time -= $aDay; } elsif($dateOpt ne "today") { die "$usageMsg\n"; } my ($t_mday, $t_mon) = (localtime($time))[3,4]; return sprintf("%s %2d", $monthNames[$t_mon], $t_mday); }