You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
388 lines
12 KiB
388 lines
12 KiB
#!/usr/bin/perl |
|
# |
|
# This is the main ScanCore program. It is started/killed/recovered by anvil-daemon. |
|
# |
|
# Examples; |
|
# |
|
# Exit codes; |
|
# 0 = Normal exit. |
|
# 1 = No database connections available. |
|
# |
|
# TODO: |
|
# - Decide if it's worth having a separate ScanCore.log file or just feed into anvil.log. |
|
# |
|
|
|
use strict; |
|
use warnings; |
|
use Anvil::Tools; |
|
use Data::Dumper; |
|
|
|
# Disable buffering |
|
$| = 1; |
|
|
|
my $THIS_FILE = ($0 =~ /^.*\/(.*)$/)[0]; |
|
my $running_directory = ($0 =~ /^(.*?)\/$THIS_FILE$/)[0]; |
|
if (($running_directory =~ /^\./) && ($ENV{PWD})) |
|
{ |
|
$running_directory =~ s/^\./$ENV{PWD}/; |
|
} |
|
|
|
my $anvil = Anvil::Tools->new({log_level => 2, log_secure => 1}); |
|
|
|
$anvil->Storage->read_config(); |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0115", variables => { program => $THIS_FILE }}); |
|
|
|
# Read switches |
|
$anvil->data->{switches}{'run-once'} = ""; |
|
$anvil->Get->switches; |
|
|
|
# Calculate my sum so that we can exit if it changes later. |
|
$anvil->Storage->record_md5sums; |
|
|
|
# Connect to DBs. |
|
wait_for_database($anvil); |
|
|
|
# If we're not configured, sleep. |
|
wait_until_configured($anvil); |
|
|
|
# Disconnect. We'll reconnect inside the loop |
|
$anvil->Database->disconnect(); |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 3, key => "log_0203"}); |
|
|
|
# The main loop |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0248"}); |
|
while(1) |
|
{ |
|
# Do the various pre-run tasks. |
|
prepare_for_run($anvil); |
|
|
|
# Do we have at least one database? |
|
if ($anvil->data->{sys}{database}{connections}) |
|
{ |
|
# Run the normal tasks |
|
call_agents($anvil); |
|
} |
|
else |
|
{ |
|
# No databases available, we can't do anything this run. Sleep for a couple of seconds and |
|
# then try again. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "log_0202"}); |
|
|
|
my $db_retry_interval = 2; |
|
if ((exists $anvil->data->{scancore}{timing}{db_retry_interval}) && ($anvil->data->{scancore}{timing}{db_retry_interval} =~ /^\d+$/)) |
|
{ |
|
$db_retry_interval = $anvil->data->{scancore}{timing}{db_retry_interval}; |
|
} |
|
sleep($db_retry_interval); |
|
next; |
|
} |
|
|
|
# Exit if 'run-once' selected. |
|
if ($anvil->data->{switches}{'run-once'}) |
|
{ |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, key => "message_0055"}); |
|
$anvil->nice_exit({exit_code => 0}); |
|
} |
|
|
|
# Clean up |
|
cleanup_after_run($anvil); |
|
|
|
# Sleep until it's time to run again. |
|
my $run_interval = 30; |
|
if ((exists $anvil->data->{scancore}{timing}{run_interval}) && ($anvil->data->{scancore}{timing}{run_interval} =~ /^\d+$/)) |
|
{ |
|
$run_interval = $anvil->data->{scancore}{timing}{run_interval}; |
|
} |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0249", variables => { run_interval => $run_interval }}); |
|
sleep($run_interval); |
|
|
|
# In case something has changed, exit. |
|
exit_if_sums_changed($anvil); |
|
} |
|
|
|
$anvil->nice_exit({exit_code => 0}); |
|
|
|
|
|
############################################################################################################# |
|
# Functions # |
|
############################################################################################################# |
|
|
|
# This invokes all scan agents found in 'path::directories::scan_agents' |
|
sub call_agents |
|
{ |
|
my ($anvil) = @_; |
|
|
|
# Get the current list of scan agents on this system. |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { |
|
"path::directories::scan_agents" => $anvil->data->{path}{directories}{scan_agents}, |
|
}}); |
|
scan_directory($anvil, $anvil->data->{path}{directories}{scan_agents}); |
|
|
|
# Now loop through the agents I found and call them. |
|
my $default_timeout = 30; |
|
if ((exists $anvil->data->{scancore}{timing}{agent_runtime}) && ($anvil->data->{scancore}{timing}{agent_runtime} =~ /^\d+$/)) |
|
{ |
|
$default_timeout = $anvil->data->{scancore}{timing}{agent_runtime}; |
|
} |
|
foreach my $agent_name (sort {$a cmp $b} keys %{$anvil->data->{scancore}{agent}}) |
|
{ |
|
my $agent_path = $anvil->data->{scancore}{agent}{$agent_name}; |
|
my $agent_words = $agent_path.".xml"; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { |
|
agent_name => $agent_name, |
|
agent_path => $agent_path, |
|
agent_words => $agent_words, |
|
}}); |
|
|
|
if ((-e $agent_words) && (-r $agent_words)) |
|
{ |
|
# Read the words file so that we can generate alerts later. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0251", variables => { |
|
agent_name => $agent_name, |
|
file => $agent_path, |
|
}}); |
|
$anvil->Words->read({file => $agent_words}); |
|
} |
|
|
|
# Now call the agent. |
|
my $start_time = time; |
|
my $return_code = 9999; |
|
my $timeout = $default_timeout; |
|
if ((exists $anvil->data->{scancore}{agent}{$agent_name}{agent_runtime}) && ($anvil->data->{scancore}{agent}{$agent_name}{agent_runtime} =~ /^\d+$/)) |
|
{ |
|
$timeout = $anvil->data->{scancore}{agent}{$agent_name}{agent_runtime}; |
|
} |
|
my $shell_call = $anvil->data->{path}{exe}{timeout}." ".$timeout." ".$agent_path; |
|
if ($anvil->data->{sys}{'log'}{level}) |
|
{ |
|
$shell_call .= ." ".$anvil->data->{sys}{'log'}{level}; |
|
} |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { shell_call => $shell_call }}); |
|
|
|
# Tell the user this agent is about to run... |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0252", variables => { |
|
agent_name => $agent_name, |
|
timeout => $timeout, |
|
}}); |
|
my ($output, $return_code) = $anvil->System->call({shell_call => $shell_call}); |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { output => $output, return_code => $return_code }}); |
|
foreach my $line (split/\n/, $output) |
|
{ |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { line => $line }}); |
|
} |
|
|
|
# If the return code is '124', timeout popped. |
|
if ($return_code eq "124") |
|
{ |
|
# Register an alert... |
|
$anvil->Alert->register({ |
|
alert_level => "notice", |
|
set_by => $THIS_FILE, |
|
message => "alert_message_0001,!!agent_name!".$agent_name."!!,!!timeout!".$timeout."!!", |
|
}); |
|
} |
|
} |
|
|
|
return(0); |
|
} |
|
|
|
# This cleans things up after a scan run has completed. |
|
sub cleanup_after_run |
|
{ |
|
my ($anvil) = @_; |
|
|
|
# Delete what we know about existing scan agents so that the next scan freshens the data. |
|
foreach my $agent_name (sort {$a cmp $b} keys %{$anvil->data->{scancore}{agent}}) |
|
{ |
|
# Remove the agent's words file data. |
|
my $agent_words = $anvil->data->{scancore}{agent}{$agent_name}.".xml"; |
|
delete $anvil->data->{words}{$agent_words}; |
|
} |
|
|
|
# Disconnect from the database(s) and sleep now. |
|
$anvil->Database->disconnect(); |
|
|
|
# Now delete all the remaining agent data. |
|
delete $anvil->data->{scancore}{agent}; |
|
} |
|
|
|
# This checks to see if any files on disk have changed and, if so, exits. |
|
sub exit_if_sums_changed |
|
{ |
|
my ($anvil) = @_; |
|
|
|
if ($anvil->Storage->check_md5sums) |
|
{ |
|
# NOTE: We exit with '0' to prevent systemctl from showing a scary red message. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 0, priority => "alert", key => "message_0014"}); |
|
$anvil->nice_exit({exit_code => 0}); |
|
} |
|
|
|
return(0); |
|
} |
|
|
|
# Handle pre-run tasks. |
|
sub prepare_for_run |
|
{ |
|
my ($anvil) = @_; |
|
# Reload defaults, re-read the config and then connect to the database(s) |
|
$anvil->_set_paths(); |
|
$anvil->_set_defaults(); |
|
$anvil->Storage->read_config(); |
|
$anvil->Words->read(); |
|
$anvil->Database->connect(); |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0132"}); |
|
|
|
# TODO: Check/configure the mail server. |
|
check_email($anvil); |
|
|
|
return(0); |
|
} |
|
|
|
sub check_email |
|
{ |
|
my ($anvil) = @_; |
|
|
|
# We check to see if there are any emails in the queue. If we see queued emails for more than five |
|
# minutes, and a second mail server is configured, we'll automatically reconfigure for the next |
|
# known server. |
|
|
|
# Before we do anything, we want to make sure all recipients have been registered against all hosts. |
|
$anvil->Email->check_alert_recipients(); |
|
|
|
### TODO: |
|
# If not configured look in variables for 'mail_server::last_used::<mail_server_uuid>'. The first one |
|
# that doesn't have an existing variable will be used. If all known mail servers have variables, the |
|
# oldest is used. |
|
# |
|
# If configured/running, the number of messages in queue is checked. If '0', |
|
# 'mail_server::queue_empty' is updated with the current time. If 1 or more, the time since the queue |
|
# was last 0 is checked. If > 300, the mail server is reconfigured to use the mail server with the |
|
# oldest 'mail_server::last_used::<mail_server_uuid>' time. |
|
# |
|
# In any case where the mail server is configured, the server that is used has their |
|
# 'mail_server::last_used::<mail_server_uuid>' variable set to the current time stamp. |
|
|
|
# Is the postfix daemon running? |
|
|
|
return(0); |
|
} |
|
|
|
# This looks in the passed-in directory for scan agents or sub-directories (which will in turn be scanned). |
|
sub scan_directory |
|
{ |
|
my ($anvil, $directory) = @_; |
|
|
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { directory => $directory }}); |
|
local(*DIRECTORY); |
|
opendir(DIRECTORY, $directory); |
|
while(my $file = readdir(DIRECTORY)) |
|
{ |
|
next if $file eq "."; |
|
next if $file eq ".."; |
|
my $full_path = $directory."/".$file; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { |
|
file => $file, |
|
full_path => $full_path, |
|
}}); |
|
# If we're looking at a directory, scan it. Otherwise, see if it's an executable and that it |
|
# starts with 'scan-*'. |
|
if (-d $full_path) |
|
{ |
|
# This is a directory, dive into it. |
|
scan_directory($anvil, $full_path); |
|
} |
|
elsif (-x $full_path) |
|
{ |
|
# Now I only want to know if the file starts with 'scan-' |
|
next if $file !~ /^scan-/; |
|
|
|
# If I am still alive, I am looking at a scan agent! |
|
$anvil->data->{scancore}{agent}{$file} = $full_path; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { |
|
"scancore::agent::${file}" => $anvil->data->{scancore}{agent}{$file}, |
|
}}); |
|
} |
|
} |
|
closedir(DIRECTORY); |
|
|
|
return(0); |
|
} |
|
|
|
# This loops until it can connect to at least one database. |
|
sub wait_for_database |
|
{ |
|
my ($anvil) = @_; |
|
|
|
$anvil->Database->connect; |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 2, key => "log_0132"}); |
|
if (not $anvil->data->{sys}{database}{connections}) |
|
{ |
|
# No databases, sleep until one comes online. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0244"}); |
|
|
|
until($anvil->data->{sys}{database}{connections}) |
|
{ |
|
# Disconnect, Sleep, then check again. |
|
$anvil->Database->disconnect(); |
|
sleep 60; |
|
|
|
$anvil->_set_paths(); |
|
$anvil->_set_defaults(); |
|
$anvil->Storage->read_config(); |
|
$anvil->Database->connect; |
|
if ($anvil->data->{sys}{database}{connections}) |
|
{ |
|
# We're good |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0245"}); |
|
} |
|
else |
|
{ |
|
# Not yet... |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0244"}); |
|
} |
|
|
|
# In case something has changed, exit. |
|
exit_if_sums_changed($anvil); |
|
} |
|
} |
|
|
|
return(0); |
|
} |
|
|
|
# Wait until the local system has been configured. |
|
sub wait_until_configured |
|
{ |
|
my ($anvil) = @_; |
|
|
|
my $configured = $anvil->System->check_if_configured; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 3, list => { configured => $configured }}); |
|
if (not $configured) |
|
{ |
|
# Sleep for a minute and check again. |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, level => 1, key => "log_0246"}); |
|
until ($configured) |
|
{ |
|
# Sleep, then check. |
|
sleep 60; |
|
|
|
$configured = $anvil->System->check_if_configured; |
|
$anvil->Log->variables({source => $THIS_FILE, line => __LINE__, level => 2, list => { configured => $configured }}); |
|
if ($configured) |
|
{ |
|
# We're good |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 1, key => "log_0247"}); |
|
} |
|
else |
|
{ |
|
# Not yet... |
|
$anvil->Log->entry({source => $THIS_FILE, line => __LINE__, 'print' => 1, level => 2, key => "log_0246"}); |
|
} |
|
|
|
# In case something has changed, exit. |
|
exit_if_sums_changed($anvil); |
|
} |
|
} |
|
|
|
return(0); |
|
}
|
|
|