1
2########################################################
3# Please file all bug reports, patches, and feature
4# requests under:
5#      https://sourceforge.net/p/logwatch/_list/tickets
6# Help requests and discusion can be filed under:
7#      https://sourceforge.net/p/logwatch/discussion/
8########################################################
9
10#######################################################
11## Copyright (c) 2008 Kirk Bauer
12## Covered under the included MIT/X-Consortium License:
13##    http://www.opensource.org/licenses/mit-license.php
14## All modifications and contributions by other persons to
15## this script are assumed to have been donated to the
16## Logwatch project and thus assume the above copyright
17## and licensing terms.  If you want to make contributions
18## under your own copyright or a different license this
19## must be explicitly stated in the contribution an the
20## Logwatch project reserves the right to not accept such
21## contributions.  If you have made significant
22## contributions to this script and want to claim
23## copyright please contact logwatch-devel@lists.sourceforge.net.
24#########################################################
25
26use strict;
27use Logwatch ':all';
28
29my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;
30my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;
31my $IgnoreHost = $ENV{'sshd_ignore_host'} || "";
32my $RefusedConnectionsThreshold = $ENV{'refused_connections_threshold'} || 0;
33my $DebugCounter = 0;
34
35# No sense in running if 'sshd' doesn't even exist on this system...
36#unless (( -f "/usr/sbin/sshd" ) or ( -f "/usr/local/sbin/sshd") or ( -f "/usr/lib/ssh/sshd")) {
37#       exit (0);
38#}
39
40my %Users = ();
41my %IllegalUsers = ();
42my %PotentialIllegalUsers = ();
43my %TooManyFailures = ();
44my %NoIdent = ();
45my %BindFailed = ();
46my %BadLogins = ();
47my %NoRevMap = ();
48my %RefusedConnections = ();
49my %RefusedAuthentication = ();
50my %NegotiationFailed = ();
51my %DisconnectReceived = ();
52my %RootLogin = ();
53my %PamReleaseFail = ();
54my %PamError = ();
55my %PamChroot = ();
56my %PamDeny = ();
57my %ShadowInfo = ();
58my %TTYModesFail = ();
59my %LoginLock = ();
60my %PostPonedAuth = ();
61my %LockedAccount = ();
62my %AllowUsers = ();
63my %DenyUsers = ();
64my %AllowGroups = ();
65my %DenyGroups = ();
66my %NoGroups = ();
67my %NoShellUsers = ();
68my %ShellNotExecutableUsers = ();
69my %DeprecatedOption = ();
70my %MisMatch = ();
71my %KrbAutFail = ();
72my %KrbAutErr = ();
73my %KrbErr = ();
74my @Scanned = ();
75my %OtherList = ();
76my %ChmodErr = ();
77my %ChownErr = ();
78my %Krb_realm = ();
79my %ConnectFailed = ();
80my %Chroot = ();
81my %CloseDir = ();
82my %CloseFileReadWrite = ();
83my %OpenDir = ();
84my %OpenFile = ();
85my %RealPath = ();
86my %Session = ();
87my %SetModtime = ();
88my %Stat = ();
89my %ClientVers = ();
90
91my $sftpRequests = 0;
92my $NetworkErrors = 0;
93my $Kills = 0;
94my $Starts = 0;
95my $NetworkErrors = 0;
96my $StatusNoSuchFile = 0;
97my $BytesSent = 0;
98my $BytesReceived = 0;
99
100if ( $Debug >= 5 ) {
101   print STDERR "\n\nDEBUG: Inside SSHD Filter \n\n";
102   $DebugCounter = 1;
103}
104
105while (defined(my $ThisLine = <STDIN>)) {
106   if ( $Debug >= 5 ) {
107      print STDERR "DEBUG($DebugCounter): $ThisLine";
108      $DebugCounter++;
109   }
110   chomp($ThisLine);
111   if (
112       ($ThisLine =~ /^pam_succeed_if: requirement "uid < 100" (not|was) met by user /) or
113       ($ThisLine =~ m/^(log: )?$/ ) or
114       ($ThisLine =~ m/^(log: )?\^\[\[60G/ ) or
115       ($ThisLine =~ m/^(log: )? succeeded$/ ) or
116       ($ThisLine =~ m/^(log: )?Closing connection to/) or
117       ($ThisLine =~ m/^(log: )?Starting sshd:/ ) or
118       ($ThisLine =~ m/^(log: )?sshd \-TERM succeeded/ ) or
119       ($ThisLine =~ m/^Bad protocol version identification .*:? [\d.]+/ ) or
120       ($ThisLine =~ m/^Bad protocol version identification.*Big-Brother-Monitor/ ) or
121       ($ThisLine =~ m/^Connection closed by/) or
122       ($ThisLine =~ m/^Disconnecting: Command terminated on signal \d+/) or
123       ($ThisLine =~ m/^Disconnecting: server_input_channel_req: unknown channel -?\d+/) or
124       ($ThisLine =~ m/^connect from \d+\.\d+\.\d+\.\d+/) or
125       ($ThisLine =~ m/^fatal: Timeout before authentication/ ) or
126       ($ThisLine =~ m/^fatal: no hostkey alg/) or
127       ($ThisLine =~ m/Connection from .* port /) or
128       ($ThisLine =~ m/Postponed (keyboard-interactive|publickey) for [^ ]+ from [^ ]+/) or
129       ($ThisLine =~ m/Read from socket failed/) or
130       ($ThisLine =~ m/sshd startup\s+succeeded/) or
131       ($ThisLine =~ m/sshd shutdown\s+succeeded/) or
132       ($ThisLine =~ m/^Found matching [DR]SA key: /) or
133       ($ThisLine =~ m/^error: key_read: type mismatch: encoding error/) or
134       ($ThisLine =~ m/^channel_lookup: -?\d+: bad id/) or
135       ($ThisLine =~ m/^error: channel \d+: chan_read_failed for istate/) or
136       # Result of setting PermitRootLogin to forced-commands-only
137       ($ThisLine =~ m/^Root login accepted for forced command\.( \[preauth\])?$/) or
138       # usually followed by a session opened for user
139       ($ThisLine =~ m/^pam_krb5\[\d+\]: authentication succeeds for /) or
140       ($ThisLine =~ m/^nss_ldap: reconnect/) or
141       ($ThisLine =~ m/^pam_ldap: error trying to bind as user "[^"]+" \(Invalid credentials\)/) or
142       ($ThisLine =~ m/^pam_ldap: ldap_starttls_s: Can't contact LDAP server/) or
143       ($ThisLine =~ m/^pam_sss\(sshd:.*\)/) or
144       ($ThisLine =~ m/^\(pam_unix\) .*/) or
145       ($ThisLine =~ m/^pam_unix\(.*:.*\)/) or
146       ($ThisLine =~ m/^pam_unix_auth:/) or
147       ($ThisLine =~ m/^pam_sepermit\(.*:.*\)/) or
148       ($ThisLine =~ /pam_krb5: authentication succeeds for `([^ ]*)'/) or
149       ($ThisLine =~ /pam_succeed_if\(.*:.*\): error retrieving information about user [a-zA-Z]*/ ) or
150       ($ThisLine =~ /pam_winbind\(sshd:account\): user .* granted access/) or
151       ($ThisLine =~ /pam_winbind\(sshd:account\): user .* OK/) or
152       ($ThisLine =~ /pam_systemd\(sshd:session\): Moving/) or
153       ($ThisLine =~ /pam_systemd\(sshd:session\): .*: Connection reset by peer/) or
154       ($ThisLine =~ /PAM \d+ more authentication failures?;/) or
155       ($ThisLine =~ /^PAM service\(sshd\) ignoring max retries;/) or
156       ($ThisLine =~ /^Failed keyboard-interactive for <invalid username> from/ ) or
157       ($ThisLine =~ /^Keyboard-interactive \(PAM\) userauth failed/ ) or
158       ($ThisLine =~ /^debug1: /) or
159       ($ThisLine =~ /Set \/proc\/self\/oom_(score_)?adj (from -?\d )?to -?\d/ ) or
160       ($ThisLine =~ /Starting session: (forced-command|subsystem|shell|command)/ ) or
161       ($ThisLine =~ /Found matching \w+ key:/ ) or
162       ($ThisLine =~ /User child is on pid \d/ ) or
163       ($ThisLine =~ /Nasty PTR record .* is set up for [\da-fA-F.:]+, ignoring/) or
164       ($ThisLine =~ /Exiting on signal / ) or
165       ($ThisLine =~ /Disconnected from [\da-fA-F.:]* port \d*/ ) or
166       ($ThisLine =~ /Disconnected from user \S+ [\da-fA-F.:]* port \d*/ ) or
167       ($ThisLine =~ /Disconnected from (authenticating|invalid) user \S+ [\da-fA-F.:]* port \d*/ ) or
168       ($ThisLine =~ /Disconnecting( (authenticating|invalid) user .* port \d+)?: Too many authentication failures \[preauth\]/ ) or
169       ($ThisLine =~ /Disconnecting( (authenticating|invalid) user .* port \d+)?: Change of username or service not allowed: .* \[preauth\]/ ) or
170       ($ThisLine =~ /Failed to release session: Interrupted system call/) or
171       ($ThisLine =~ /Close session: user /) or
172       0 # This line prevents blame shifting as lines are added above
173   ) {
174      # Ignore these
175   } elsif ( my ($Method,$User,$Host,$Port,$Key,$FingerP) = ($ThisLine =~ /^Accepted (\S+) for ((?:invalid user )?\S+) from ([\d\.:a-f]+)(?:%\w+)? port (\d+) ssh[12](?:: (\w+) (.+))?/) ) {
176      if ($Debug >= 5) {
177         print STDERR "DEBUG: Found -$User logged in from $Host using $Method ($Key)\n";
178      }
179      if ($Detail >= 20) {
180         $Users{$User}{$Host}{$Method . ($Key ?
181		"($Key" . (($Detail >= 30) ? " " . $FingerP : "") . ")" :
182		"")}++;
183      } else {
184         if ( $Host !~ /$IgnoreHost/ ) {
185            $Users{$User}{$Host}{"(all)"}++;
186         }
187      }
188   } elsif ( my ($Method, undef,$User,$Host,$Port) = ($ThisLine =~ m/^Failed (\S+) for (illegal|invalid) user (.*) from ([^ ]+) port (\d+)/ ) ) { #openssh
189      $IllegalUsers{$Host}{$User}++;
190   } elsif ( my ($User) = ( $ThisLine =~ /Disconnecting: Too many authentication failures for ([^ ]+)/)) {
191      $TooManyFailures{$User}++;
192   } elsif ( my ($User) = ( $ThisLine =~ /error: maximum authentication attempts exceeded for ([^ ]+) from [^ ]+ port \d+ ssh2 \[preauth\]/)) {
193      $TooManyFailures{$User}++;
194   } elsif ( my ($User,$Host) = ( $ThisLine =~ /error: maximum authentication attempts exceeded for invalid user ([^ ]+) from ([^ ]+) port \d+ ssh2 \[preauth\]/)) {
195      $IllegalUsers{$Host}{$User}++;
196   } elsif ( $ThisLine =~ m/^(fatal: )?Did not receive ident(ification)? string from (\S+)/ ) { # ssh/openssh
197      my $name = LookupIP($3);
198      $NoIdent{$name}++;
199   } elsif ( my ($Host) = ($ThisLine =~ /Could not write ident string to ([^ ]+)$/ )) {
200      my $name = LookupIP($Host);
201      $NoIdent{$name}++;
202   } elsif (
203      ($ThisLine =~ m/^fatal: Connection closed by remote host\./ ) or
204      ($ThisLine =~ m/^(|fatal: )Read error from remote host(| [^ ]+): Connection reset by peer/ ) or
205      ($ThisLine =~ m/^Read error from remote host [^ ]+: (Connection timed out|No route to host)/ ) or
206      ($ThisLine =~ m/^fatal: Read from socket failed: No route to host/) or
207      ($ThisLine =~ m/^fatal: Write failed: Network is unreachable/ ) or
208      ($ThisLine =~ m/^fatal: Write failed: Broken pipe/) or
209      ($ThisLine =~ m/^fatal: Write failed: Connection reset by peer/) or
210      ($ThisLine =~ m/^Connection reset by/) or
211      ($ThisLine =~ m/^channel \d+: open failed: (?:connect failed: Channel open failed\.|administratively prohibited: open failed)/) or
212      ($ThisLine =~ m/^session_input_channel_req: no session \d+ req window-change/) or
213      ($ThisLine =~ m/^error: chan_shutdown_read failed for .+/)
214   ) {
215      $NetworkErrors++;
216   } elsif ( $ThisLine =~ m/^(log: )?Received (signal 15|SIG...); (terminating|restarting)\./) { #ssh/openssh
217      $Kills++;
218      if ( $Debug >= 5 ) {
219         print STDERR "DEBUG: Found -Signal 15 Terminating- line\n";
220      }
221   } elsif ( $ThisLine =~ m/^(log: )?Server listening on( [^ ]+)? port \d+/ ) { #ssh/openssh
222      $Starts++;
223      if ( $Debug >= 5 ) {
224         print STDERR "DEBUG: Found -Listening on port 22- line\n";
225      }
226   } elsif ( my ($Port,$Address,$Reason) = ($ThisLine =~ /^error: Bind to port ([^ ]+) on ([^ ]+) failed: (.+).$/ )) {
227      my $Temp = "$Address port $Port ($Reason)";
228      # Failed to bind on 0.0.0.0 likely due to configured "ListenAddress"
229      # on both IPv4 and IPv6
230      unless ($Address =~ /^0.0.0.0$/) {
231         $BindFailed{$Temp}++;
232      }
233   } elsif ( $ThisLine =~ m/^(log: )?Generating .* \w+ key\./ ) { # ssh/openssh
234      # Don't care about this...
235      if ( $Debug >= 5 ) {
236         print STDERR "DEBUG: Found -Generating RSA key- line\n";
237      }
238   } elsif ( $ThisLine =~ m/^packet_set_maxsize: /) {
239      if ( $Debug >= 5 ) {
240         print STDERR "DEBUG: Found -packet_set_maxsize- line\n";
241      }
242   } elsif ( $ThisLine =~ m/^(log: )?\w+ key generation complete\./ ) { # ssh/openssh
243      # Don't care about this...
244      if ( $Debug >= 5 ) {
245         print STDERR "DEBUG: Found -Keygen complete- line\n";
246      }
247   } elsif ( my ($Method,$User,$Host,undef) = ( $ThisLine =~ m/^Failed (\S+) for (\S+) from ([^ ]+) port (\d+)/ ) ) { #openssh
248      # depending on log mode, openssh may not report these in connection context.
249      if ( $Debug >= 5 ) {
250         print STDERR "DEBUG: Found -Failed login- line\n";
251      }
252      $BadLogins{$Host}{"$User/$Method"}++;
253   } elsif ($ThisLine =~ s/^(log: )?Could not reverse map address ([^ ]*).*$/$2/) {
254      $NoRevMap{$ThisLine}++;
255   } elsif ( my ($Address) = ($ThisLine =~ /^reverse mapping checking getaddrinfo for (\S+( \[\S+\])?) failed - POSSIBLE BREAK-IN ATTEMPT!/)) {
256      $NoRevMap{$Address}++;
257   } elsif ( my ($IP,$Address) = ($ThisLine =~ /^Address ([^ ]*) maps to ([^ ]*), but this does not map back to the address - POSSIBLE BREAK-IN ATTEMPT!/)) {
258      $NoRevMap{"$Address($IP)"}++;
259   } elsif ( my (undef,$Address) = ($ThisLine =~ /^warning: ([^ ]*), line \d+: can't verify hostname: getaddrinfo\(([^ ]*), AF_INET\) failed$/)) {
260      $NoRevMap{$Address}++;
261   } elsif ( my (undef,$Addresses) = ($ThisLine =~ /^warning: ([^ ]*), line \d+: host [^ ]* mismatch: (.*)$/)) {
262      $MisMatch{$Addresses}++;
263   } elsif ( $ThisLine =~ m/subsystem request for sftp/ ) {
264      $sftpRequests++;
265   } elsif ( $ThisLine =~ m/refused connect from (.*)$/ ) {
266      $RefusedConnections{$1}++;
267   } elsif ( my ($Reason) = ($ThisLine =~ /^Authentication refused: (.*)$/ ) ) {
268      $RefusedAuthentication{$Reason}++;
269   } elsif ( my (undef,$Host,$Port,$Reason,$Offer) = ($ThisLine =~ /^(fatal: )?Unable to negotiate with ([^ ]+)( port \d+)?: (.*)\. Their offer: (.*) \[preauth\]$/) ) {
270      $NegotiationFailed{$Reason}{$Host}{$Offer}++;
271   } elsif ( my ($Reason,$Host,$Offer) = ($ThisLine =~ /^(Protocol major versions differ) for ([^ ]+)(?: port \d+): (.*)$/) ) {
272      $NegotiationFailed{$Reason}{$Host}{$Offer}++;
273   } elsif ( my ($Prio,$Host,$Port,$Code,$Reason) = ($ThisLine =~ /^(error: )?Received disconnect from ([^ ]*)( port \d+)?: ?(\d+): (.*)$/)) {
274      # Reason 11 ({SSH,SSH2}_DISCONNECT_BY_APPLICATION) is expected, and logged at severity level INFO
275      if (($Reason =~ /preauth/) || ($Code != 11) || ($Detail >= 30)) {
276	$DisconnectReceived{$Reason}{$Host}++;
277      }
278   } elsif ( my ($Host) = ($ThisLine =~ /^ROOT LOGIN REFUSED FROM ([^ ]*)$/)) {
279      $RootLogin{$Host}++;
280   } elsif ( my ($Error) = ($ThisLine =~ /^Cannot release PAM authentication\[\d\]: (.*)$/)) {
281      $PamReleaseFail{$Error}++;
282   } elsif ( my ($Error) = ($ThisLine =~ /^pam_systemd\(sshd:session\): Failed to release session: (.*)$/)) {
283      $PamReleaseFail{$Error}++;
284   } elsif ( my ($Error) = ( $ThisLine =~ m/^error: PAM: (.*)$/)) {
285      $PamError{$Error}++;
286   } elsif ( my ($Error) = ( $ThisLine =~ m/pam_systemd\(sshd:session\): (Failed to create session: .*)$/)) {
287      $PamError{$Error}++;
288   } elsif ( my ($Reason) = ( $ThisLine =~ m/pam_chroot\(.+\):\s+([^:])/)) {
289      $PamChroot{$Reason}++;
290   } elsif ( my ($Error) = ( $ThisLine =~ m/^error: Could not get shadow information for (.*)$/)) {
291      $ShadowInfo{$Error}++;
292   } elsif ( my ($Reason) = ($ThisLine =~ /^Setting tty modes failed: (.*)$/)) {
293      $TTYModesFail{$Reason}++;
294   } elsif ( my ($User,undef) = ($ThisLine =~ /^User ([^ ]*) not allowed because ([^ ]*) exists$/)) {
295      $LoginLock{$User}++;
296   } elsif ( my ($Method,$InvaUser,$IlegUser,$EmptyUser,$User,$Host) = ($ThisLine =~ /^Postponed ([^ ]*) for ((invalid user) [^ ]*|(illegal user) [^ ]*|([^ ]*)) from ([^ ]*) port \d+ ssh/)) {
297      $PostPonedAuth{"$User/$Method"}{$Host}++;
298      if ($IlegUser =~ /illegal user/) {$IllegalUsers{$Host}{$User}++;}
299   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*) not allowed because account is locked/)) {
300      $LockedAccount{$User}++;
301   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*) from (?:[^ ]*) not allowed because not listed in AllowUsers/)) {
302      $AllowUsers{$User}++;
303   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*)( from [0-9.]*)? not allowed because listed in DenyUsers/)){
304      $DenyUsers{$User}++;
305   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*)( from [0-9.]*)? not allowed because not in any group/)) {
306      $NoGroups{$User}++;
307   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*)( from [^ ]*)? not allowed because a group is listed in DenyGroups/)) {
308      $DenyGroups{$User}++;
309   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*) from ([^ ]*) not allowed because none of user's groups are listed in AllowGroups/)) {
310      $AllowGroups{$User}++;
311   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*) not allowed because shell (\S+) does not exist/)) {
312      $NoShellUsers{$User}++;
313   } elsif ( my ($User) = ($ThisLine =~ /^User ([^ ]*) not allowed because shell (\S+) is not executable/)) {
314      $ShellNotExecutableUsers{$User}++;
315   } elsif ( my ($User) = ($ThisLine =~ /^fatal: Access denied for user ([^ ]+) by PAM account configuration \[preauth\]/)) {
316      $PamDeny{$User}++;
317   } elsif ( my ($IP) = ($ThisLine =~ /^scanned from ([^ ]*)/) ) {
318      push @Scanned, $IP;
319   } elsif ( my (undef,$Line,$Option) = ($ThisLine =~ /^re(xec|process config) line (\d+): Deprecated option (.*)$/)) {
320      $DeprecatedOption{"$Option - line $Line"}++;
321   } elsif ( my ($Pom1,$Pom2,$User) = ($ThisLine =~ /pam_krb5(\[\d*\])?: authentication fails for (`|')([^ ]*)'/)) {
322      $KrbAutFail{$User}++;
323   } elsif ( my ($Error) = ($ThisLine =~ /pam_krb5: authenticate error: (.*)$/)) {
324      $KrbAutErr{$Error}++;
325   } elsif ( ($ThisLine =~ /pam_krb5: unable to determine uid\/gid for user$/)) {
326      $KrbAutErr{"unable to determine uid/gid for user"}++;
327   } elsif ( my ($Error) = ($ThisLine =~ /pam_krb5: error removing file (.*)$/)) {
328      $KrbErr{"error removing file " . $Error}++;
329   } elsif ( my ($Pom,$Error) = ($ThisLine =~ /pam_krb5(\[\d*\]): error resolving user name '[^ ]*' to uid\/gid pai/)) {
330      $KrbErr{"error resolving user name '$Error' to uid\/gid pai"}++;
331   } elsif ( my (undef,$User,$Host) = ($ThisLine =~ m/^(Illegal|Invalid) user (.*) from ([^ ]+)/ )) {
332      $PotentialIllegalUsers{$Host}{$User}++;
333   } elsif ( my (undef,$User) = ($ThisLine =~ /^input_userauth_request: (illegal|invalid) user (.*)$/ )) {
334      if ($User =~ m/(.*) \[preauth\]/) {
335        $User = $1;
336      }
337      $PotentialIllegalUsers{"undef"}{$User}++;
338   } elsif (my ($File,$Perm,$Why) = ($ThisLine =~ /error: chmod (.*) (.*) failed: (.*)/)) {
339      $ChmodErr{"$File,$Perm,$Why"}++;
340   } elsif (my ($File,$From,$To,$Why) = ($ThisLine =~ /error: chown (.*) (.*) (.*) failed: (.*)/)) {
341      $ChownErr{"$File,$From,$To,$Why"}++;
342   } elsif (my ($user,$realm) = ($ThisLine =~ /Authorized to ([^ ]+), krb5 principal \1@([^ ]+) \((?:krb5_kuserok|ssh_gssapi_krb5_cmdok)\)/)) {
343      $Krb_realm{$realm}{$user}++;
344   } elsif (my ($Action,$User) = ($ThisLine =~ /^session ((?:open|clos)ed) for local user (\S+) from /)) {
345      $Session{"Action,$User"}++;
346   } elsif ($ThisLine =~ /^sent status No such file$/) {
347      $StatusNoSuchFile++;
348   } elsif (my ($File,$Flags,$Mode) = ($ThisLine =~ /^open "(.*)" flags (\S+) mode (\d+)/ )) {
349      $OpenFile{"$File:$Flags:$Mode"}++;
350   } elsif (my ($File,$BytesRead,$BytesWritten) = ($ThisLine =~ /^close "(.*)" bytes read (\d+) written (\d+)/ )) {
351      $CloseFileReadWrite{"$File,$BytesRead,$BytesWritten"}++;
352   } elsif (my ($Sent,$Received) = ($ThisLine =~ /^Transferred: sent (\d+), received (\d+) bytes$/ )) {
353      $BytesSent += $Sent;
354      $BytesReceived += $Received;
355   } elsif (my ($File,$Modtime) = ($ThisLine =~ /^set "(.*)" modtime (\d+-\d+:\d+:\d+)$/ )) {
356      $SetModtime{"$Modtime,$File"}++;
357   } elsif (my ($Dir) = ($ThisLine =~ /^opendir "(.*)"$/ )) {
358      $OpenDir{$Dir}++;
359   } elsif (my ($Dir) = ($ThisLine =~ /^closedir "(.*)"$/ )) {
360      $CloseDir{$Dir}++;
361   } elsif (my ($File) = ($ThisLine =~ /^realpath "(.*)"$/ )) {
362      $RealPath{$File}++;
363   } elsif (my ($File) = ($ThisLine =~ /^stat name "(.*)"$/ )) {
364      $Stat{$File}++;
365   } elsif (my ($Dir) = ($ThisLine =~ /^Changed root directory to "(.*)"/ )) {
366      $Chroot{$Dir}++;
367   } elsif (my ($ClientVer) = ($ThisLine =~ /^received client version (\S+)/ )) {
368      $ClientVers{$ClientVer}++;
369   } elsif (my ($Host,$Port) = ($ThisLine =~ /^error: connect_to (\S+) port (\d+): failed\.$/)) {
370      $ConnectFailed{"$Host port $Port"}++;
371   } else {
372      # Report any unmatched entries...
373      unless ($ThisLine =~ /fwd X11 connect/) {
374         $OtherList{$ThisLine} += 1;
375      }
376   }
377}
378
379###########################################################
380
381foreach my $Host (sort keys %PotentialIllegalUsers) {
382   foreach my $User (sort keys %{$PotentialIllegalUsers{$Host}}) {
383      my @user_hosts = grep { $PotentialIllegalUsers{$_}{$User} } keys %PotentialIllegalUsers;
384
385      if ($Host eq "undef") {
386         if ((scalar @user_hosts) == 1 && $user_hosts[0] == "undef") {
387            # Report illegal user from "undef" only if there are no other hosts
388            # for the given user
389            $IllegalUsers{"undef"}{$User}++;
390         }
391      }
392      else {
393         while ($IllegalUsers{$Host}{$User} < $PotentialIllegalUsers{$Host}{$User}) {
394            $IllegalUsers{$Host}{$User}++;
395         }
396      }
397   }
398}
399
400###########################################################
401
402sub timesplural {
403   my ($count) = @_;
404   my $plural = ($count > 1) ? "s" : "";
405   return "$count Time$plural\n";
406}
407
408if ($NetworkErrors) {
409   print "\nNetwork Read Write Errors: " . $NetworkErrors . "\n";
410}
411if ($Kills) {
412   print "\nSSHD Killed: " . timesplural($Kills);
413}
414if ($Starts) {
415   print "\nSSHD Started: " . timesplural($Starts);
416}
417
418if (keys %DeprecatedOption) {
419   print "\nDeprecated options in SSH config:\n";
420   foreach my $Option (sort {$a cmp $b} keys %DeprecatedOption) {
421      print "   $Option\n";
422   }
423}
424
425if (keys %RootLogin) {
426   print "\n\nWARNING!!!\n";
427   print "Refused ROOT login attempt from:\n";
428   foreach my $Host (sort {$a cmp $b} keys %RootLogin) {
429      print "   $Host : " . timesplural($RootLogin{$Host});
430   }
431}
432
433if (keys %BindFailed) {
434   print "\nFailed to bind:\n";
435   foreach my $ThisOne (sort {$a cmp $b} keys %BindFailed) {
436           print "   $ThisOne : " . timesplural($BindFailed{$ThisOne});
437   }
438}
439
440if ($Detail >= 30 && keys %ConnectFailed) {
441   # SSH Socks Forwarding
442   print "\nFailed to connect to:\n";
443   foreach my $ThisOne (sort {$a cmp $b} keys %ConnectFailed) {
444           print "   $ThisOne : " . timesplural($ConnectFailed{$ThisOne});
445   }
446}
447
448if ($Detail >= 10) {
449   if (keys %NoRevMap) {
450      print "\nCouldn't resolve these IPs:\n";
451      foreach my $ThisOne (sort {$a cmp $b} keys %NoRevMap) {
452         print "   $ThisOne: " . timesplural($NoRevMap{$ThisOne});
453      }
454   }
455   if (keys %NoIdent) {
456      print "\nDidn't receive an ident from these IPs:\n";
457      foreach my $ThisOne (sort {$a cmp $b} keys %NoIdent) {
458         print "   $ThisOne: " . timesplural($NoIdent{$ThisOne});
459      }
460   }
461   if (keys %MisMatch) {
462      print "\nMismatched host names and/or IPs:\n";
463      foreach my $ThisOne (sort keys %MisMatch) {
464         print "   $ThisOne: " . timesplural($MisMatch{$ThisOne});
465      }
466   }
467}
468
469if (keys %NegotiationFailed) {
470   print "\nNegotiation failed:\n";
471   foreach my $Reason (sort {$a cmp $b} keys %NegotiationFailed) {
472      my $Total = 0;
473      print "   $Reason";
474      if ( $Detail > 0 ) {
475         print "\n";
476      }
477      foreach my $Host (sort {$a cmp $b} keys %{$NegotiationFailed{$Reason}}) {
478        my $HostTotal = 0;
479        foreach my $Offer (sort {$a cmp $b} keys %{$NegotiationFailed{$Reason}{$Host}}) {
480           $HostTotal += $NegotiationFailed{$Reason}{$Host}{$Offer};
481        }
482        $Total += $HostTotal;
483        if ( $Detail > 0 ) {
484           print "      $Host: " . timesplural($HostTotal);
485        }
486        if ( $Detail > 5 ) {
487           foreach my $Offer (sort {$a cmp $b} keys %{$NegotiationFailed{$Reason}{$Host}}) {
488                 my $tot = $NegotiationFailed{$Reason}{$Host}{$Offer};
489                 print "        $Offer: " . timesplural($tot);
490           }
491        }
492      }
493      if ( $Detail == 0 ) {
494         print ": " . timesplural($Total);
495      }
496   }
497}
498
499if (keys %TooManyFailures) {
500   print "\nDisconnecting after too many authentication failures for user:\n";
501   foreach my $User (sort {$a cmp $b} keys %TooManyFailures) {
502      print "   $User : " . timesplural($TooManyFailures{$User});
503   }
504}
505
506if (keys %BadLogins) {
507   print "\nFailed logins from:\n";
508   foreach my $ip (sort SortIP keys %BadLogins) {
509      my $name = LookupIP($ip);
510      my $totcount = 0;
511      foreach my $user (keys %{$BadLogins{$ip}}) {
512         $totcount += $BadLogins{$ip}{$user};
513      }
514      print "   $name: ". timesplural($totcount);
515      if ($Detail >= 5) {
516         my $sort = CountOrder(%{$BadLogins{$ip}});
517         foreach my $user (sort $sort keys %{$BadLogins{$ip}}) {
518            my $val = $BadLogins{$ip}{$user};
519            print "      $user: " . timesplural($val);
520         }
521      }
522   }
523}
524
525if (keys %IllegalUsers) {
526   print "\nIllegal users from:\n";
527   foreach my $ip (sort SortIP keys %IllegalUsers) {
528      my $name = LookupIP($ip);
529      my $totcount = 0;
530      foreach my $user (keys %{$IllegalUsers{$ip}}) {
531         $totcount += $IllegalUsers{$ip}{$user};
532      }
533      print "   $name: " . timesplural($totcount);
534      if ($Detail >= 5) {
535         my $sort = CountOrder(%{$IllegalUsers{$ip}});
536         foreach my $user (sort $sort keys %{$IllegalUsers{$ip}}) {
537            my $val = $IllegalUsers{$ip}{$user};
538            print "      $user: " . timesplural($val);
539         }
540      }
541   }
542}
543
544if (keys %LockedAccount) {
545   print "\nLocked account login attempts:\n";
546   foreach my $User (sort {$a cmp $b} keys %LockedAccount) {
547      print "   $User : " . timesplural($LockedAccount{$User});
548   }
549}
550
551if (keys %AllowUsers) {
552   print "\nLogin attempted when not in AllowUsers list:\n";
553   foreach my $User (sort {$a cmp $b} keys %AllowUsers) {
554      print "   $User : " . timesplural($AllowUsers{$User});
555   }
556}
557
558if (keys %DenyUsers) {
559   print "\nLogin attempted when in DenyUsers list:\n";
560   foreach my $User (sort {$a cmp $b} keys %DenyUsers) {
561      print "   $User : . " . timesplural($DenyUsers{$User});
562   }
563}
564
565if (keys %AllowGroups) {
566   print "\nLogin attempted when not in AllowGroups list:\n";
567   foreach my $User (sort {$a cmp $b} keys %AllowGroups) {
568      print "   $User : " . timesplural($AllowGroups{$User});
569   }
570}
571
572if (keys %DenyGroups) {
573   print "\nLogin attempted when in DenyGroups list:\n";
574   foreach my $User (sort {$a cmp $b} keys %DenyGroups) {
575      print "   $User : " . timesplural($DenyGroups{$User});
576   }
577}
578
579if (keys %PamDeny) {
580   print "\nLogin attempted when denied by PAM configuration:\n";
581   foreach my $User (sort {$a cmp $b} keys %PamDeny) {
582      print "   $User : " . timesplural($PamDeny{$User});
583   }
584}
585
586if (keys %NoGroups) {
587   print "\nLogin attempted when user is in no group:\n";
588   foreach my $User (sort {$a cmp $b} keys %NoGroups) {
589      print "   $User : " . timesplural($NoGroups{$User});
590   }
591}
592
593if (keys %NoShellUsers) {
594   print "\nLogin attempted when shell does not exist:\n";
595   foreach my $User (sort {$a cmp $b} keys %NoShellUsers) {
596      print "   $User : " . timesplural($NoShellUsers{$User});
597   }
598}
599
600if (keys %ShellNotExecutableUsers) {
601   print "\nLogin attempted when shell is not executable:\n";
602   foreach my $User (sort {$a cmp $b} keys %ShellNotExecutableUsers) {
603      print "   $User : " . timesplural($ShellNotExecutableUsers{$User});
604   }
605}
606
607if ((keys %LoginLock) and ($Detail >= 5)) {
608   print "\nUser login attempt when nologin was set:\n";
609   foreach my $User (sort {$a cmp $b} keys %LoginLock) {
610      print "   $User : " . timesplural($LoginLock{$User});
611   }
612}
613
614if (keys %PostPonedAuth) {
615   print "\nPostponed authentication:\n";
616   foreach my $User (sort {$a cmp $b} keys %PostPonedAuth) {
617      print "   $User:\n";
618      foreach my $Host (sort {$a cmp $b} keys %{$PostPonedAuth{$User}}) {
619         print "      $Host: " . timesplural($PostPonedAuth{$User}{$Host});
620      }
621   }
622}
623
624if (keys %Users) {
625   print "\nUsers logging in through sshd:\n";
626   foreach my $user (sort {$a cmp $b} keys %Users) {
627      print "   $user:\n";
628      if ($Detail < 20) {
629         my $totalSort = TotalCountOrder(%{$Users{$user}}, \&SortIP);
630         foreach my $ip (sort $totalSort keys %{$Users{$user}}) {
631            my $name = LookupIP($ip);
632            my $val  = (values %{$Users{$user}{$ip}})[0];
633            print "      $name: " . timesplural($val);
634         }
635      } else {
636         my %Methods = ();
637         foreach my $ip (keys %{$Users{$user}}) {
638            foreach my $method (keys %{$Users{$user}{$ip}}) {
639               $Methods{$method}{$ip} = $Users{$user}{$ip}{$method};
640            }
641         }
642         if (scalar keys %{$Users{$user}} < scalar %Methods) {
643            my $totalSort = TotalCountOrder(%{$Users{$user}}, \&SortIP);
644            foreach my $ip (sort $totalSort keys %{$Users{$user}}) {
645               my $name = LookupIP($ip);
646               print "      $name:\n";
647               my $sort = CountOrder(%{$Users{$user}{$ip}});
648               foreach my $method (sort $sort keys %{$Users{$user}{$ip}}) {
649                  my $val = $Users{$user}{$ip}{$method};
650                  print "         $method: " . timesplural($val);
651               }
652            }
653         } else {
654            my $totalSort = TotalCountOrder(%Methods);
655            foreach my $method (sort $totalSort keys %Methods) {
656               print "      $method:\n";
657               my $sort = CountOrder(%{$Methods{$method}});
658               foreach my $ip (sort $sort keys %{$Methods{$method}}) {
659                  my $name = LookupIP($ip);
660                  my $val  = (values %{$Users{$user}{$ip}})[0];
661                  print "         $name: " . timesplural($val);
662               }
663            }
664         }
665      }
666   }
667}
668
669if (keys %RefusedAuthentication) {
670   print "\nAuthentication refused:\n";
671   foreach my $Reason (sort {$a cmp $b} keys %RefusedAuthentication) {
672      print "   $Reason : " . timesplural($RefusedAuthentication{$Reason});
673   }
674}
675
676if (keys %KrbAutFail) {
677   print "\n\Failed pam_krb5 authentication:\n";
678   foreach my $User (sort keys %KrbAutFail) {
679      print "   $User: " . timesplural($KrbAutFail{$User});
680   }
681}
682
683if (keys %KrbAutErr) {
684   print "\n\pam_krb5 authentication errors:\n";
685   foreach my $Error (sort keys %KrbAutErr) {
686      print "   $Error: " . timesplural($KrbAutErr{$Error});
687   }
688}
689
690
691if (keys %KrbErr) {
692   print "\n pam_krb5 errors:\n";
693   foreach my $Error (sort keys %KrbErr) {
694      print "   $Error: " . timesplural($KrbErr{$Error});
695   }
696}
697
698
699if (keys %DisconnectReceived) {
700   print "\nReceived disconnect:\n";
701   foreach my $Reason (sort {$a cmp $b} keys %DisconnectReceived) {
702      my $Total = 0;
703      print "   $Reason";
704      foreach my $Host (sort {$a cmp $b} keys %{$DisconnectReceived{$Reason}}) {
705         $Total += $DisconnectReceived{$Reason}{$Host};
706        if( $Detail > 0 ) {
707            print "\n      $Host : $DisconnectReceived{$Reason}{$Host} Time(s)";
708        }
709      }
710      if( $Detail > 0 ) {
711         print "\n";
712      } else {
713         print " : " . timesplural($Total);
714      }
715   }
716}
717
718if ($#Scanned >= 0) {
719   print "\nScanned from:\n";
720   foreach my $ThisOne (sort SortIP @Scanned) {
721      print "   " . LookupIP($ThisOne) . "\n";
722   }
723}
724
725if (keys %RefusedConnections) {
726   my $output;
727   foreach my $badguy (sort {$a cmp $b} keys %RefusedConnections ) {
728      if ($RefusedConnectionsThreshold == 0 || $Detail > 5 || $RefusedConnections{$badguy} >= $RefusedConnectionsThreshold) {
729        $output .= "      $badguy: " . timesplural($RefusedConnections{$badguy});
730      }
731   }
732   if ($output ne '') {
733     print "\nRefused incoming connections:\n";
734     print $output;
735   }
736}
737
738if (keys %PamReleaseFail) {
739   print "\nCannot release PAM authentication:\n";
740   foreach my $Error (sort {$a cmp $b} keys %PamReleaseFail) {
741      print "   $Error : " . timesplural($PamReleaseFail{$Error});
742   }
743}
744
745if (keys %ShadowInfo) {
746   print "\nCould not get shadow information for:\n";
747   foreach my $Error (sort {$a cmp $b} keys %ShadowInfo) {
748      print "   $Error : " . timesplural($ShadowInfo{$Error});
749   }
750}
751
752if (keys %PamError) {
753   print "\nError in PAM authentication:\n";
754   foreach my $Error (sort {$a cmp $b} keys %PamError) {
755      print "   $Error : " . timesplural($PamError{$Error});
756   }
757}
758
759if (keys %PamChroot) {
760   print "\nPAM chroot:\n";
761   foreach my $Reason (sort {$a cmp $b} keys %PamChroot) {
762      print "   $Reason : " . timesplural($PamChroot{$Reason});
763   }
764}
765
766if (keys %TTYModesFail) {
767   print "\nSetting tty modes failed:\n";
768   foreach my $Reason (sort {$a cmp $b} keys %TTYModesFail) {
769      print "   $Reason : " . timesplural($TTYModesFail{$Reason});
770   }
771}
772
773if ($sftpRequests > 0) {
774   print "\nSFTP subsystem requests: $sftpRequests Time(s)\n";
775   if ($Detail >= 50) {
776      foreach my $root (sort {$a cmp $b} keys %Chroot) {
777         print "   Chroot: $root : " . timesplural($Chroot{$root});
778      }
779   }
780   if ($Detail >= 0) {
781      foreach my $dir (sort {$a cmp $b} keys %CloseDir) {
782         if ($CloseDir{$dir} != $OpenDir{$dir} || $Detail >= 40) {
783             print "   Directory $dir: opened/closed $OpenDir{$dir}/$CloseDir{$dir} Time(s)\n";
784             # Note: this number might not match if the open/closes span log windows..."
785         }
786      }
787   }
788   if ($Detail >= 30) {
789      foreach my $details (sort {$a cmp $b} keys %CloseFileReadWrite) {
790         if (my ($file,$read,$written) = ($details =~ /^(.*),(\d+),(\d+)$/)) {
791            if ($read || $written || $Detail >= 40) {
792               print "   Read/Wrote $read/$written byte(s) from file $file\n";
793            }
794         }
795      }
796   }
797   if ($Detail >= 60) {
798      foreach my $details (sort {$a cmp $b} keys %OpenFile) {
799         if (my ($file,$flags,$mode) = ($details =~ /^(.*):([^:]*):(\d+)$/)) {
800            print "   Opened (mode: $mode, flags: $flags) file $file\n";
801         }
802      }
803   }
804   if ($Detail >= 70) {
805      foreach my $path (sort {$a cmp $b} keys %RealPath) {
806         print "   Realpath $path\n";
807      }
808   }
809   if ($Detail >= 50) {
810      foreach my $details (sort {$a cmp $b} keys %SetModtime) {
811         if (my ($modtime,$file) = ($details =~ /^([^,]*),(.*)$/)) {
812            print "   Set modtime $modtime for file $file\n";
813         }
814      }
815   }
816   if ($Detail >= 70) {
817      foreach my $path (sort {$a cmp $b} keys %Stat) {
818         print "   Stat: $path : " . timesplural($Stat{$path});
819      }
820   }
821   if ($Detail >= 40) {
822      if ($StatusNoSuchFile) {
823         print "   Sent status No such file: " . timesplural($StatusNoSuchFile);
824      }
825   }
826   if ($Detail >= 0) {
827      if ($BytesSent || $BytesReceived) {
828         print "   Transferred: sent/received $BytesSent/$BytesReceived byte(s)\n";
829      }
830   }
831   if ($Detail >= 50) {
832      foreach my $client (sort {$a cmp $b} keys %ClientVers) {
833         print "   Client version $client connected " . timesplural($ClientVers{$client});
834      }
835   }
836}
837
838if (keys %ChmodErr) {
839  print "\nChmod errors:\n";
840  foreach (sort keys %ChmodErr) {
841    my ($File,$Perm,$Why)= split ",";
842    print "   " . $File . " " . $Perm . " failed(" . $Why . "): " . timesplural($ChmodErr{"$File,$Perm,$Why"});
843  }
844}
845
846if (keys %ChownErr) {
847  print "\nChown errors:\n";
848  foreach (keys %ChownErr) {
849    my ($File,$From,$To,$Why)= split ",";
850    print "   " . $File . " " . $From . " " .$To . " failed(" . $Why . "): " . timesplural($ChmodErr{"$File,$From,$To,$Why"});
851  }
852}
853
854if ( ($Detail == 7 && keys %Krb_realm > 1) || ($Detail > 8 && keys %Krb_realm) ){
855  print "\nSuccessful Kerberos Authentication from ",(scalar keys %Krb_realm)," realm:\n";
856  foreach my $realm (sort keys %Krb_realm) {
857    if($Detail > 9){
858      print "   ",$realm,":\n";
859      foreach my $user(sort keys %{$Krb_realm{$realm}}){
860        print "     ",$user,": " . timesplural($Krb_realm{$realm}{$user});
861      }
862    }else{
863      print "   ",$realm,": ". (scalar keys %{$Krb_realm{$realm}}) . " User(s)\n";
864    }
865  }
866}
867
868if (keys %OtherList) {
869   print "\n**Unmatched Entries**\n";
870   print "$_ : " . timesplural($OtherList{$_}) foreach sort keys %OtherList;
871}
872
873exit(0);
874
875# vi: shiftwidth=3 tabstop=3 syntax=perl et
876# Local Variables:
877# mode: perl
878# perl-indent-level: 3
879# indent-tabs-mode: nil
880# End:
881