
########################################################
# 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/
########################################################

########################################################
# freeradius logwatch filter
#	written by Jonas Marczona 28.12.2011 
#
## 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.
########################################################

$^W=1;
use strict;

my $Debug = $ENV{'LOGWATCH_DEBUG'} || 0;
my $Detail = $ENV{'LOGWATCH_DETAIL_LEVEL'} || 0;

my $DebugCounter = 0;

if ( $Debug >= 5 ) {
   print STDERR "\n\nDEBUG: Inside freeradius Filter \n\n";
   $DebugCounter = 1;
}

my %OtherList = ();

my %loginsOk = ();
my %certificateExpired = ();
my %wrongPassword = ();
my %wrongUser = ();
my %wrong_client = ();
my %invalidUser = ();
my %discards = ();
my %warnings = ();
my %givingUps = ();
my $crlExpired = 0;
my $killedChilds = 0;
my $reloaded = 0;
my $requests = 0;
my $requests_duration = 0;
my $started = 0;
my $stopped = 0;

my $ThisLine;
while (defined($ThisLine = <STDIN>)) {
   if ( $Debug >= 5 ) {
      print STDERR "DEBUG($DebugCounter): $ThisLine";
      $DebugCounter++;
   }
   chomp($ThisLine);

   # Strip leading session id
   my ($SessionID) = ($ThisLine =~ s/^\((\d+)\) *//);

   if ( ( $ThisLine =~ /^(?:Info: )?F-TICKS/ ) ||
        ( $ThisLine =~ /^(?:Info: )?Access-Request from/ ) ||
        ( $ThisLine =~ /^(?:Info: )? \.\.\. (?:closing|adding new) socket/ ) ||
        ( $ThisLine =~ /^(?:Info: )?(?:SSL|TLS|rlm_(?:unix|eap|sql|radutmp)|    TLS_accept|  \[ldap\])/ ) ||
        ( $ThisLine =~ /^(?:Info: )?Ready to process requests/ ) ||
        ( $ThisLine =~ /^(?:Info: )?Debugger not attached/ ) ||
        ( $ThisLine =~ /^(?:Info: )?Exiting normally/ ) ||
        ( $ThisLine =~ /^(?:Info: )?Loaded virtual server/ ) ||
        ( $ThisLine =~ /^(?:Info: )?HUP - / ) ||
        ( $ThisLine =~ /^(?:Info: )?Ignoring / ) ||
        ( $ThisLine =~ /^(?:Info: )?Received HUP signal/ ) ||
        ( $ThisLine =~ /^(?:Info: )? ?Module: Reloaded module/ )  ||
        ( $ThisLine =~ /^(?:Info: )?Signalled to terminate/ ) ||
        # TD: # Skipping contents of 'if' as it is always 'false' -- /etc/raddb/sites-enabled/inner-tunnel
        # This is a standard config item
        ( $ThisLine =~ /^(?:Info: )? *# Skipping contents of 'if' as it is always 'false' -- .*inner-tunnel/ ) ||
        # TD:      [/etc/raddb/mods-config/attr_filter/access_reject]:11 Check item "FreeRADIUS-Response-Delay-USec"    found in filter list for realm "DEFAULT".
        # This is triggered by a standard config item and is harmless
        ( $ThisLine =~ /access_reject\]:\d+ Check item "FreeRADIUS-Response-Delay(?:-USec)?"\s*found in filter list for realm/ ) ||
        # These should precede Login incoreect messages
        ( $ThisLine =~ /^eap_tls: *ERROR: \(TLS\) .*(?:certificate.*expired|Error in error)/ ) ||
        # We count completed events below
        ( $ThisLine =~ /^(?:Info: )?(?:Start|Stopp|Reload)ing FreeRADIUS/ )
      ) {
      # ignore
   }

   # TD: Login OK: [user@example.com] (from client radius port 0)
   # TD: Login OK: [user@example.com] (from client radius port 9 cli 00-11-22-33-44-AA;eduroam via TLS tunnel)
   elsif ( my ($user) = ($ThisLine =~ m/^(?:Auth: )?Login OK: \[(.+)\] \(from client [^ ]* port \d{1,10}(?: cli [-0-9a-fA-F.:]+)?(?:;\w+)?(?: via TLS tunnel)?\)/) ) {
      $loginsOk{$user}++;

   }

   # TD: Login incorrect (  [ldap] User not found): [user@example.com] (from client radius port 13 cli 38-16-dd-aa-bb-cc via TLS tunnel)
   # TD: Login incorrect (mschap: External script says Logon failure (0xc000006d)): [user@example.com] (from client radius port 13 cli aa-bb-cc-11-22-33 via TLS tunnel)
   # TD: Login incorrect (TLS Alert write:fatal:handshake failure): [user@example.com] (from client radius port 13 cli aa-bb-cc-11-22-33)
   # TD: Login incorrect (No Auth-Type found: rejecting the user via Post-Auth-Type = Reject): [04d9f5bc5541] (from client nwra port 50104 cli 04-D9-F5-BC-55-41)
   elsif ( my ($user, $client) = ( $ThisLine =~ m/^(?:Auth: )?Login incorrect(?: \([^)]+\))?: \[(.*)\] \(from client [^ ]* port \d{1,10}(?: cli ([-0-9a-fA-F.:]+)(?:;\w+)?)?(?: via TLS tunnel)?\)/) ) {
      if (! $client) { $client = "*not named*"; }
      $wrongUser{$client}{$user}++;
      $wrong_client{$client}++;

   }

   # TD: Login incorrect: [user@example.com] (from client radius port 175143 cli cc08.e051.a240)
   # TD: Login incorrect: [user@example.com] (from client radius1 port 0) 
   elsif ( my ($user, $client) = ($ThisLine =~ m/^(?:Auth: )?Login incorrect: \[(.+)\] \(from client [^ ]* port \d{1,10}(?: cli ([-0-9a-fA-F.:]+))?(?: via TLS tunnel)?\)/) ) {
      if (! $client) { $client = "*not named*"; }
      $wrongPassword{$client}{$user}++;
      $wrong_client{$client}++;
   }

   # TD: Login incorrect (eap_tls: (TLS) OpenSSL says error 10 : certificate has expired): [USERNAME] (from client CLIENTNAME port 50427 cli F8-E4-3B-F1-80-90)
   elsif ( my ($user, $client) = ( $ThisLine =~ m/^(?:Auth: )?Login incorrect \(.*certificate has expired\): \[(.*)\] \(from client [^ ]* port \d{1,10}(?: cli ([-0-9a-fA-F.:]+)(?:;\w+)?)?(?: via TLS tunnel)?\)/) ) {
      if (! $client) { $client = "*not named*"; }
      $certificateExpired{$client}{$user}++;
      $wrong_client{$client}++;
   }

   # TD: Invalid user (  [ldap] Access Attribute denies access): [user@example.com] (from client radius port 13 cli aa-bb-cc-dd-ee-11 via TLS tunnel)
   # TD: Invalid user: [user@example.com] (from client <host> port 13 cli aa-bb-cc-dd-ee-11)
   elsif ( my ($reason, $user, $client) = ($ThisLine =~ m/^(?:Auth: )?Invalid user(?: \(\s*(.+)\))?: \[(.+)\] \(from client [^ ]* port \d{1,10}(?: cli ([-0-9a-fA-F.:]+))?(?: via TLS tunnel)?\)/) ) {
      if (! $client) { $client = "*not named*"; }
      if (! $reason) { $reason = "*no reason*"; }
      $invalidUser{$reason}{$user}++;
   }

   # TD: Discarding duplicate request from client <host> port 47609 - ID: 182 due to unfinished request 12713766
   # TD: Discarding conflicting packet from client <host> port 42221 - ID: 85 due to recent request 9008535.
   elsif ( my ($reason, $client) = ($ThisLine =~ /Discarding (duplicate request|conflicting packet) from client (\S+) port \d+ - ID: \d+ due to (unfinished|recent) request/) ) {
      $discards{$reason}{$client}++;
   }

   # TD: Received conflicting packet from client radius2 port 60612 - ID: 30 due to unfinished request 1136681.  Giving up on old request.
   elsif ( my ($client) = ($ThisLine =~ /Received conflicting packet from client ([^ ]+) port \d{1,10} - ID: \d+ due to unfinished request \d+/) ) {
      $givingUps{$client}++;
   }

   # TD: eap_tls:   ERROR: SSL says error 12 : CRL has expired
   elsif ( $ThisLine =~ m/CRL has expired/ ) {
      $crlExpired++;
   }


   # TD: Child PID 57436 is taking too much time: forcing failure and killing child.
   elsif ( $ThisLine =~ m/Child PID \d+ is taking too much time: forcing failure and killing child/ ) {
      $killedChilds++;
   }

   # TD: Started FreeRADIUS high performance RADIUS server..
   elsif ( $ThisLine =~ /^Started FreeRADIUS/ ) {
      $started++;
   }

   # TD: Stopping FreeRADIUS high performance RADIUS server..
   elsif ( $ThisLine =~ /^Stopped FreeRADIUS/ ) {
      $stopped++;
   }

   # TD: Reloading FreeRADIUS high performance RADIUS server
   elsif ( $ThisLine =~ /^Reloaded FreeRADIUS/ ) {
      $reloaded++;
   }

   # TD: Request 67678577 has been waiting in the processing queue for 378 seconds.  Check that all databases are running properly!
   elsif ($ThisLine =~ m/^Request \d+ has been waiting in the processing queue for (\d+) seconds/) {
      $requests++;
      $requests_duration += $1;
   }

   # TD: WARNING: Unresponsive child for request 4737598, in component accounting module unix
   # TD: WARNING: Child is hung for request 4737598 in component accounting module unix.
   elsif ( $ThisLine =~ m/^WARNING: (Unresponsive child|Child is hung) for request \d+,? in component ([<>\w]+) module ?([<>\w]*)/ ) {
      $warnings{"$1 in component:"}{"$2 [module: $3]"}++;
   }

   # TD: WARNING: Allowing fast client radius2 port 60612 - ID: 102 for recent request 9035637.
   elsif ( $ThisLine =~ m/^WARNING: (Allowing fast client) ([^ ]+) port \d{1,10} - ID: \d+/ ) {
      $warnings{"${1}s:"}{$2}++;
   }

   else {
      # Report any unmatched entries...
      $OtherList{$ThisLine}++;
   }
}


#################################
# Output section
################################

if ($requests > 0) {
   printf "Long running requests: Check that all databases are running properly!\n";
   printf "  %-40s : %5d\n", 'Long running requests', $requests;
   printf "  %-40s : %5d s\n", 'avg queue time per long running request', $requests_duration / $requests;
   print "\n";
}


sub compPerMacAddr {
  return $wrong_client{$b} <=> $wrong_client{$a};
}

if ($Detail >= 8) {
   if (keys %wrong_client) {
      print "\nSum of failed logins per client (wrong password or user)\n";
      foreach my $client (sort compPerMacAddr keys %wrong_client) {
         printf "  %-40s : %5d time(s)\n", $client, $wrong_client{$client};
      } 
   }
}

if (keys %certificateExpired) {
   if ($Detail >= 3) {
      print "\nFailed logins - certificate expired:\n";
      foreach my $client (sort compPerMacAddr keys %certificateExpired) {
         my $users = $certificateExpired{$client};
         printf "  %-40s\n", $client ;
         foreach my $user (sort {$users->{$b} <=> $users->{$a}} keys %$users) {
            #print "    $user ", $users->{$user}, " time(s)\n";
            printf "    %-38s : %5d time(s)\n", $user, $users->{$user};
         }
      }
   } else {
      my $certificateExpiredSum = 0;
      foreach my $client (%certificateExpired) {
         my $users = $certificateExpired{$client};
         foreach my $user (keys %$users) {
            $certificateExpiredSum += $users->{$user};
         }
      }
      printf "\n%-42s : %5d time(s)\n", "Failed logins - certificate expired", $certificateExpiredSum;
   }
}

if (keys %wrongUser) {
   if ($Detail >= 3) {
      print "\nFailed logins - wrong user name:\n";
      foreach my $client (sort compPerMacAddr keys %wrongUser) {
         printf "  %-40s\n",  $client;
         my $users = $wrongUser{$client};
         foreach my $user (sort {$users->{$b} <=> $users->{$a}} keys %$users) {
            printf "    %-38s : %5d time(s)\n", $user, $users->{$user};
         }
      }
   } else {
      my $userSum = 0;
      foreach my $client (keys %wrongUser) {
         my $users = $wrongUser{$client};
         foreach my $user (keys %$users) {
            $userSum += $users->{$user};
         }
      }
      printf "\n%-42s : %5d time(s)\n", "Failed logins - wrong user name", $userSum;
   }
}

if (keys %wrongPassword) {
   if ($Detail >= 6) {
      print "\nFailed logins - wrong password:\n";
      foreach my $client (sort compPerMacAddr keys %wrongPassword) {
         my $users = $wrongPassword{$client};
         printf "  %-40s\n", $client ;
         foreach my $user (sort {$users->{$b} <=> $users->{$a}} keys %$users) {
            #print "    $user ", $users->{$user}, " time(s)\n";
            printf "    %-38s : %5d time(s)\n", $user, $users->{$user};
         }
      }
   } else {
      my $wrongPasswordSum = 0;
      foreach my $client (%wrongPassword) {
         my $users = $wrongPassword{$client};
         foreach my $user (keys %$users) {
            $wrongPasswordSum += $users->{$user};
         }
      }
      printf "\n%-42s : %5d time(s)\n", "Failed logins - wrong password", $wrongPasswordSum;
   }
}

if (keys %invalidUser) {
   if ($Detail >= 6) {
      print "\nInvalid User:\n";
      foreach my $reason (keys %invalidUser) {
         my $users = $invalidUser{$reason};
         printf "  %-40s\n", $reason;
         foreach my $user (sort {$users->{$b} <=> $users->{$a}} keys %$users) {
            printf "    %-38s : %5d time(s)\n", $user, $users->{$user};
         }
      }
   } else {
      my $invalidUserSum = 0;
      foreach my $reason (keys %invalidUser) {
         my $users = $invalidUser{$reason};
         foreach my $user (keys %$users) {
            $invalidUserSum += $users->{$user};
         }
      }
      printf "\n%-42s : %5d time(s)\n", "Invalid Users", $invalidUserSum;
   }
}

if (keys %discards) {
   print "\nDiscards:\n";
   foreach my $reason (keys %discards) {
      my $clients = $discards{$reason};
      printf "  %-40s\n", $reason;
      foreach my $client (keys %$clients) {
         printf "    %-38s : %5d time(s)\n", $client, $clients->{$client};
      }
   }
}

if (keys %givingUps) {
  print "\nGiving up on old requests:\n";
  foreach my $client (keys %givingUps) {
     printf "  %-40s : %5d time(s)\n", $client, $givingUps{$client};
  }
}

if ($crlExpired) {
   printf "\nCRL Expired: %5d time(s)\n", $crlExpired;
}

if ($killedChilds) {
   printf "\n%-42s : %5d time(s)\n", "Killed Childs (taking too much time)", $killedChilds;
}

if (%warnings) {
   print "\nWarnings:\n";
   foreach my $warning (keys %warnings) {
      my $components = $warnings{$warning};
      printf "  %-40s\n", $warning;
      foreach my $component (keys %$components) {
         printf "    %-38s : %5d time(s)\n", $component, $components->{$component};
      }
   }
}

if (keys %loginsOk) {
   if ($Detail >= 10) {
      print "\nSuccessful logins:\n";
      foreach my $user (sort {$loginsOk{$b} <=> $loginsOk{$a}} keys %loginsOk) {
        printf "  %-40s : %5d time(s)\n", $user, $loginsOk{$user};
      }
   } elsif ($Detail >= 6) {
      my $loginsOkSum = 0;
      foreach my $user (keys %loginsOk) {
         $loginsOkSum += $loginsOk{$user};
      }
      printf "\n%-42s : %5d time(s)\n", "Successful logins", $loginsOkSum;
   }
}

if ($Detail >= 5 && $started) {
   printf "\nServer started: %5d time(s)\n", $started;
}

if ($Detail >= 5 && $stopped) {
   printf "\nServer stopped: %5d time(s)\n", $stopped;
}

if ($Detail >= 5 && $reloaded) {
   printf "\nServer reloaded: %5d time(s)\n", $reloaded;
}

if (keys %OtherList) {
   print "\n**** Unmatched entries ****\n";
   foreach (keys %OtherList) {
      print "    $_ : $OtherList{$_} Time(s)\n";
   }
}

exit(0);

# vi: shiftwidth=3 tabstop=3 syntax=perl et

