######################################################## # Please file all bug reports, patches, and feature # requests under: # https://sourceforge.net/p/logwatch/_list/tickets # Help requests and discusion can be filed under: # https://sourceforge.net/p/logwatch/discussion/ ######################################################## ####################################################### ## Copyright (c) 2021-2022 Orion Poplawski ## Covered under the included MIT/X-Consortium License: ## http://www.opensource.org/licenses/mit-license.php ## All modifications and contributions by other persons to ## this script are assumed to have been donated to the ## Logwatch project and thus assume the above copyright ## and licensing terms. If you want to make contributions ## under your own copyright or a different license this ## must be explicitly stated in the contribution an the ## Logwatch project reserves the right to not accept such ## contributions. If you have made significant ## contributions to this script and want to claim ## copyright please contact logwatch-devel@lists.sourceforge.net. ######################################################### use warnings; use strict; use Logwatch ':all'; my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0; my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0; my $IgnoreHost = $ENV{'ignore_host'} || ""; my $IllegalUsersThreshold = $ENV{'illegal_users_threshold'} || 0; $main::DoLookup = $ENV{'sshd_ip_lookup'}; my $DebugCounter = 0; #Init String Containers my ( $Host, $Key, $Method, $Port, $User, $user, ); my %Users = (); my %NonexistentUsers = (); my %BadPassword = (); my %OtherList = (); my $NetworkErrors = 0; my $Kills = 0; while (defined(my $ThisLine = )) { chomp($ThisLine); if ( ($ThisLine =~ /Connection reset by peer/) or ($ThisLine =~ /^Child connection from/) or ($ThisLine =~ /Disconnect received/) or ($ThisLine =~ /Exited normally/) or ($ThisLine =~ /Not backgrounding/) or 0 # This line prevents blame shifting as lines are added above ) { # Ignore these } elsif ( ($Method,$User,$Host,$Port) = ($ThisLine =~ /^(\S+) auth succeeded for '(.*)' from ([\d\.:a-f]+)(?:%\w+)?:(\d+)/) ) { if ($Detail >= 20) { $Users{$User}{$Host}{$Method}++; } else { if ( $Host !~ /$IgnoreHost/ ) { $Users{$User}{$Host}{"(all)"}++; } } } elsif ( ($Method,$User,$Key,$Host,$Port) = ($ThisLine =~ /^(\S+) auth succeeded for '(.*)' with.* key (.*) from ([\d\.:a-f]+)(?:%\w+)?:(\d+)/) ) { if ($Detail >= 20) { $Users{$User}{$Host}{$Method . " ($Key)"}++; } else { if ( $Host !~ /$IgnoreHost/ ) { $Users{$User}{$Host}{"(all)"}++; } } } elsif ( ($ThisLine =~ m/^Error reading/ ) or 0 ) { $NetworkErrors++; } elsif ( ($User,$Host) = ($ThisLine =~ /^Bad password attempt for '(.*)' from ([^ ]*):/)) { $BadPassword{$Host}{$User}++; } elsif ( ($Host) = ($ThisLine =~ /^Login attempt for nonexistent user from ([^ ]*):/)) { $NonexistentUsers{$Host}++; } elsif ( $ThisLine =~ /^Login attempt for nonexistent user/) { $NonexistentUsers{"unknown"}++; } elsif ( $ThisLine =~ m/Terminated by signal/) { $Kills++; } else { $OtherList{$ThisLine} += 1; } } ########################################################### sub timesplural { my ($count) = @_; my $plural = ($count > 1) ? "s" : ""; return "$count Time$plural\n"; } if ($NetworkErrors) { print "\nNetwork Read Write Errors: " . $NetworkErrors . "\n"; } if ($Kills) { print "\nKilled: " . timesplural($Kills); } if (keys %BadPassword) { print "\nBad password attempts:\n"; foreach my $ip (sort SortIP keys %BadPassword) { my $name = LookupIP($ip); my $totcount = 0; foreach my $user (keys %{$BadPassword{$ip}}) { $totcount += $BadPassword{$ip}{$user}; } print " $name: ". timesplural($totcount); if ($Detail >= 5) { my $sort = CountOrder(%{$BadPassword{$ip}}); foreach my $user (sort $sort keys %{$BadPassword{$ip}}) { my $val = $BadPassword{$ip}{$user}; print " $user: " . timesplural($val); } } } } if (keys %NonexistentUsers) { print "\nNon-existent users from"; if ($IllegalUsersThreshold) { print " (with threshold >= $IllegalUsersThreshold)"; } print ":\n"; foreach my $ip (sort SortIP keys %NonexistentUsers) { my $name = LookupIP($ip); print " $name: " . timesplural($NonexistentUsers{$ip}); } } if (keys %Users) { print "\nUsers logging in through sshd:\n"; foreach my $user (sort {$a cmp $b} keys %Users) { print " $user:\n"; if ($Detail < 20) { my $totalSort = TotalCountOrder(%{$Users{$user}}, \&SortIP); foreach my $ip (sort $totalSort keys %{$Users{$user}}) { my $name = LookupIP($ip); my $val = (values %{$Users{$user}{$ip}})[0]; print " $name: " . timesplural($val); } } else { my %Methods = (); foreach my $ip (keys %{$Users{$user}}) { foreach my $method (keys %{$Users{$user}{$ip}}) { $Methods{$method}{$ip} = $Users{$user}{$ip}{$method}; } } if (scalar keys %{$Users{$user}} < scalar %Methods) { my $totalSort = TotalCountOrder(%{$Users{$user}}, \&SortIP); foreach my $ip (sort $totalSort keys %{$Users{$user}}) { my $name = LookupIP($ip); print " $name:\n"; my $sort = CountOrder(%{$Users{$user}{$ip}}); foreach my $method (sort $sort keys %{$Users{$user}{$ip}}) { my $val = $Users{$user}{$ip}{$method}; print " $method: " . timesplural($val); } } } else { my $totalSort = TotalCountOrder(%Methods); foreach my $method (sort $totalSort keys %Methods) { print " $method:\n"; my $sort = CountOrder(%{$Methods{$method}}); foreach my $ip (sort $sort keys %{$Methods{$method}}) { my $name = LookupIP($ip); my $val = (values %{$Users{$user}{$ip}})[0]; print " $name: " . timesplural($val); } } } } } } if (keys %OtherList) { print "\n**Unmatched Entries**\n"; print "$_ : " . timesplural($OtherList{$_}) foreach sort keys %OtherList; } exit(0); # vi: shiftwidth=3 tabstop=3 syntax=perl et # Local Variables: # mode: perl # perl-indent-level: 3 # indent-tabs-mode: nil # End: