###############################################################################
# #
# IPFire.org - A linux based firewall #
-# Copyright (C) 2015 IPFire Development Team #
+# Copyright (C) 2015-2016 IPFire Development Team #
# #
# 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 #
close(FILE);
# Send filename and message to the parser,
- # which will return if an action has to be performed.
- my @action = &Guardian::Parser::Parser("$parser", @message);
+ # which will return if any actions have to be performed.
+ my @actions = &Guardian::Parser::Parser("$parser", @message);
# Send the action to the main process and put it into
# the queue.
- if (@action) {
+ if (@actions) {
# Lock the queue.
lock($queue);
- # Put the required action into the queue.
- $queue->enqueue(@action);
- }
- # If no action is returned by the Parser, the message
- # could not be parser corretly.
- else {
- # Log failed parse attempt.
- $logger->Log("err", "Error parsing event: \[$parser - @message\]");
+ # Loop through the actions array, and perform
+ # every single action.
+ foreach my $action (@actions) {
+ # Prevent from enqueuing empty actions.
+ if (defined($action)) {
+ # Put the required action into the queue.
+ $queue->enqueue($action);
+ }
+ }
}
} else {
# Sleep for 10ms until the next round of the loop will start.
#
sub Socket () {
# Create the Server socket by calling the responsible function.
- my $server = &Guardian::Socket::Server();
+ my $server = &Guardian::Socket::Server($mainsettings{SocketOwner});
# Log successfull creation of socket.
$logger->Log("debug", "Listening to Socket...");
$SIG{TERM} = \&Shutdown;
$SIG{QUIT} = \&Shutdown;
$SIG{HUP} = \&Reload;
+ $SIG{USR1} = \&ReloadIgnoreList;
}
#
$mainsettings{Logger} = $logger;
# Update ignore list.
- &Guardian::Events::GenerateIgnoreList($mainsettings{IgnoreFile});
+ &ReloadIgnoreList();
# Re-generate hash of monitored files.
%monitored_files = &Guardian::Base::GenerateMonitoredFiles(\%mainsettings, \%monitored_files);
&StartWorkers();
}
+#
+## ReloadIgnoreList function.
+#
+## This function will be called if the signal handler recieves a "SIGUSR1" signal,
+## or the reload-ignore-list command will be sent via the socket connection. It just
+## performs a simple check if an ignore file has been configured and calls the responsible
+## function on the events module.
+#
+sub ReloadIgnoreList () {
+ # Update ignore list, if an ignorefile has been specified.
+ if (exists($mainsettings{IgnoreFile})) {
+ # Log reload of the ignore list.
+ $logger->Log("info", "Reloading ignore list...");
+
+ # Call responsible function from the events module.
+ &Guardian::Events::GenerateIgnoreList($mainsettings{IgnoreFile});
+ }
+}
+
#
## Shutdown function.
#
return "Invalid LogLevel: $config{LogLevel}";
}
+ # Check if an optional configured SocketOwner is valid.
+ if (exists($config{SocketOwner})) {
+ my ($user, $group) = split(/:/, $config{SocketOwner});
+
+ # Get the ID for the given user name.
+ my $uid = getpwnam($user) or return "The user $user does not exist.";
+
+ # Get the ID for given group name.
+ my $gid = getgrnam($group) or return "The group $group does not exist.";
+ }
+
# The config looks good, so return nothing (no error message).
return undef
}
'unblock' => \&CallUnblock,
'flush' => \&CallFlush,
'reload' => \&main::Reload,
+ 'reload-ignore-list' => \&main::ReloadIgnoreList,
);
# Hash to store addresses and their current count.
#
sub GenerateIgnoreList($) {
my $file = shift;
+ my @include_files;
+
+ # Reset current ignore hash and add
+ # localhost related IP addresses.
+ %ignorehash = &_whitelist_localhost();
# Check if the given IgnoreFile could be opened.
unless(-e $file) {
return;
}
- # Reset current ignore hash and add
- # localhost related IP addresses.
- %ignorehash = &_whitelist_localhost();
-
- # Open the given IgnoreFile.
+ # Open the given IgnoreFile.
open (IGNORE, $file);
# Read-in the file line by line.
# Remove any newlines.
chomp;
- # Check if the line contains a valid single address or network and
- # convert it into binary format. Store the result/start and
- # end values in a temporary array.
- my @values = &Guardian::Base::IPOrNet2Int($_);
+ # Check for an include instruction.
+ if ($_ =~ /^Include_File = (.*)/) {
+ my $include_file = $1;
- # If the function returned any values, the line contained a valid
- # single address or network which successfully has been converted into
- # binary format.
- if (@values) {
- # Assign the array as value to the ignorehash.
- $ignorehash{$_} = [@values];
- } else {
- # Log invalid entry.
- $logger->Log("err", "IgnoreFile contains an invalid address/network: $_");
+ # Check if the parsed include file exists and is read-able.
+ if (-e $include_file) {
+ # Add file to the array of files wich will be included.
+ push(@include_files, $include_file);
- # Skip line.
- next;
+ # Write out log message.
+ $logger->Log("debug", "Addresses from $include_file will be included...");
+ } else {
+ # Log missing file.
+ $logger->Log("err", "$include_file will not be included. File does not exist!");
+ }
+ } else {
+ # Check if the line contains a valid single address or network and
+ # convert it into binary format. Store the result/start and
+ # end values in a temporary array.
+ my @values = &Guardian::Base::IPOrNet2Int($_);
+
+ # If the function returned any values, the line contained a valid
+ # single address or network which successfully has been converted into
+ # binary format.
+ if (@values) {
+ # Assign the array as value to the ignorehash.
+ $ignorehash{$_} = [@values];
+ } else {
+ # Log invalid entry.
+ $logger->Log("err", "IgnoreFile contains an invalid address/network: $_");
+
+ # Skip line.
+ next;
+ }
}
}
# Close filehandle for the IgnoreFile.
close (IGNORE);
+ # Check if any files should be included.
+ if (@include_files) {
+ # Loop through the array of files which should be included.
+ foreach my $file (@include_files) {
+ # Open the file.
+ open(INCLUDE, $file);
+
+ # Read-in file line by line.
+ while(<INCLUDE>) {
+ # Skip any comments.
+ next if (/\#/);
+
+ # Skip any blank lines.
+ next if (/^\s*$/);
+
+ # Chomp any newlines.
+ chomp;
+
+ # Check if the line contains a valid single address or network and
+ # convert it into binary format. Store the result/start and
+ # end values in a temporary array.
+ my @values = &Guardian::Base::IPOrNet2Int($_);
+
+ # If the function returned any values, the line contained a valid
+ # single address or network which successfully has been converted into
+ # binary format.
+ if (@values) {
+ # Assign the array as value to the ignorehash.
+ $ignorehash{$_} = [@values];
+ } else {
+ # Log invalid entry.
+ $logger->Log("err", "$file contains an invalid address/network: $_");
+
+ # Skip line.
+ next;
+ }
+ }
+
+ # Close filehandle.
+ close(INCLUDE);
+ }
+ }
+
# Get amount of current elements in hash.
my $amount = scalar(keys(%ignorehash));
# This hash contains the supported log facilities and their corresponding subroutines.
my %logfacilities = (
"console" => \&LogFacilityConsole,
+ "file" => \&LogFacilityFile,
"syslog" => \&LogFacilitySyslog,
);
my $use_facility = $self->{LogFacility};
# Transmit log message to the correct log facility.
- $logfacilities{$use_facility}->($level, $message);
+ $logfacilities{$use_facility}->($self, $level, $message);
}
}
## message to STDOUT.
#
sub LogFacilityConsole ($$) {
+ my $self = shift;
my ($type, $message) = @_;
# Print message on STDOUT.
## This log facility sends a given log message to the system log service (syslog).
#
sub LogFacilitySyslog ($$) {
+ my $self = shift;
my ($type, $message) = @_;
# The syslog function works best with an array based input,
closelog();
}
+#
+## LogFacilityFile function.
+#
+## This log facility will write any given log messages to a specified log file.
+#
+sub LogFacilityFile ($$) {
+ my $self = shift;
+ my ($type, $message) = @_;
+
+ # Open the logfile for writing.
+ open(LOGFILE, '>>', $self->{LogFile}) or die "Could not write to $self->{LogFile}: $!\n";
+
+ # Write log message to file.
+ print LOGFILE "\[$type\] $message\n";
+
+ # Close filehandle.
+ close(FILE);
+}
+
1;
# This hash contains all supported parsers and which function
# has to be called to parse messages in the right way.
my %logfile_parsers = (
+ "httpd" => \&message_parser_httpd,
"snort" => \&message_parser_snort,
+ "ssh" => \&message_parser_ssh,
);
#
}
# Call responsible message parser.
- my $action = $logfile_parsers{$parser}->(@message);
+ my @actions = $logfile_parsers{$parser}->(@message);
- # If the parser successfully parsed the message, an action
- # has been returned.
- if ($action) {
- # Return which action should be performed.
- return "count $action";
+ # In case an action has been returned, return it too.
+ if (@actions) {
+ # Return which actions should be performed.
+ return @actions;
}
- # If the parser was not able to parse the the given message
- # in the right way, return Nothing.
- return;
+ # Return undef, if no actions are required.
+ return undef;
}
#
return;
}
+#
+## The SSH message parser.
+#
+## This subfunction is used for parsing and detecting different attacks
+## against the SSH service.
+#
+sub message_parser_ssh (@) {
+ my @message = @_;
+ my @actions;
+
+ # The name of the parser module.
+ my $name = "SSH";
+
+ # Variable to store the grabbed IP-address.
+ my $address;
+
+ # Variable to store the parsed event.
+ my $message;
+
+ # Loop through all lines, in case multiple one have
+ # been passed.
+ foreach my $line (@message) {
+ # Check for failed password attempts.
+ if ($line =~/.*sshd.*Failed password for (.*) from (.*) port.*/) {
+ # Store the grabbed IP-address.
+ $address = $2;
+
+ # Set event message.
+ $message = "Possible SSH-Bruteforce Attack for user: $1.";
+ }
+
+ # This should catch Bruteforce Attacks with enabled preauth
+ elsif ($line =~ /.*sshd.*Received disconnect from (.*):.*\[preauth\]/) {
+ # Store obtained IP-address.
+ $address = $1;
+
+ # Set event message.
+ $message = "Possible SSH-Bruteforce Attack - failed preauth.";
+ }
+
+ # Check if at least the IP-address information has been extracted.
+ if (defined ($address)) {
+ # Add the extracted values and event message for the computed
+ # event to the actions array.
+ push(@actions, "count $address $name $message");
+ }
+ }
+
+ # If any actions are required, return the array.
+ if (@actions) {
+ return (@actions);
+ }
+
+ # If we got here, the provided message is not affected by any filter and
+ # therefore can be skipped. Return nothing (False) in this case.
+ return;
+}
+
+#
+## The HTTPD message parser.
+#
+## This subfunction is used for parsing and detecting different attacks
+## against a running HTTPD service.
+#
+sub message_parser_httpd (@) {
+ my @message = @_;
+ my @actions;
+
+ # The name of the parser module.
+ my $name = "HTTPD";
+
+ # Variable to store the grabbed IP-address.
+ my $address;
+
+ # Variable to store the parsed event.
+ my $message;
+
+ # Loop through all lines, in case multiple one have
+ # been passed.
+ foreach my $line (@message) {
+ # This will catch brute-force attacks against htaccess logins (username).
+ if ($line =~ /.*\[error\] \[client (.*)\] user(.*) not found:.*/) {
+ # Store the grabbed IP-address.
+ $address = $1;
+
+ # Set event message.
+ $message = "Possible WUI brute-force attack, wrong user: $2.";
+ }
+
+ # Detect htaccess password brute-forcing against a username.
+ elsif ($line =~ /.*\[error\] \[client (.*)\] user(.*): authentication failure for.*/) {
+ # Store the extracted IP-address.
+ $address = $1;
+
+ # Set event message.
+ $message = "Possible WUI brute-force attack, wrong password for user: $2.";
+ }
+
+ # Check if at least the IP-address information has been extracted.
+ if (defined ($address)) {
+ # Add the extracted values and event message to the actions array.
+ push(@actions, "count $address $name $message");
+ }
+ }
+
+ # If any actions are required, return the array.
+ if (@actions) {
+ return @actions;
+ }
+
+ # If we got here, the provided message is not affected by any filter and
+ # therefore can be skipped. Return nothing (False) in this case.
+ return;
+}
+
1;
'unblock' => 'unblock',
'flush' => 'flush',
'reload' => 'reload',
+ 'reload-ignore-list' => 'reload-ignore-list',
'logrotate' => 'logrotate',
);
## mechanism for guardian. The server function creates an UNIX
## socket.
#
-sub Server () {
+sub Server ($) {
+ my $socket_owner = shift;
+
# If the path for the socketfile does not exist, try to
# create it.
unless (-d "$socketpath") {
Type => SOCK_STREAM,
) or die "Could not create socket: $!";
+
+ # Translate the given user/group name into ID values.
+ if (defined ($socket_owner)) {
+ # Splitt provided user/group into single arguments.
+ my ($username, $groupname) = split(/:/, $socket_owner);
+
+ # Get the ID for the given user name.
+ my $uid = getpwnam($username) or die "Could not get an UID for $username: $!";
+
+ # Get the ID for given group name.
+ my $gid = getgrnam($groupname) or die "Could not get a GID for $groupname: $!";
+
+ # Set new ownership for the socket file.
+ chown($uid, $gid, "$socketfile") or die "Could not change ownership to ($uid:$gid) for $socketfile: $!";
+ }
+
# Return the server object.
return $server;
}