979 lines
34 KiB
Perl
Executable File
979 lines
34 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
##########################################################################
|
|
# Pandora FMS Server
|
|
# Pandora FMS. the Flexible Monitoring System. http://www.pandorafms.org
|
|
##########################################################################
|
|
# Copyright (c) 2005-2011 Artica Soluciones Tecnologicas S.L
|
|
#
|
|
# 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 the Free Software Foundation; version 2
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
##########################################################################
|
|
|
|
use strict;
|
|
use warnings;
|
|
use POSIX qw(strftime);
|
|
use threads;
|
|
use Digest::MD5 qw(md5_hex);
|
|
|
|
# Default lib dir for RPM and DEB packages
|
|
BEGIN { push @INC, '/usr/lib/perl5'; }
|
|
|
|
# Pandora Modules
|
|
use PandoraFMS::DB;
|
|
use PandoraFMS::Config;
|
|
use PandoraFMS::Tools;
|
|
use PandoraFMS::Core;
|
|
use PandoraFMS::AlertServer;
|
|
use PandoraFMS::DataServer;
|
|
use PandoraFMS::NetworkServer;
|
|
use PandoraFMS::SNMPServer;
|
|
use PandoraFMS::DiscoveryServer;
|
|
use PandoraFMS::WMIServer;
|
|
use PandoraFMS::PluginServer;
|
|
use PandoraFMS::PredictionServer;
|
|
use PandoraFMS::WebServer;
|
|
|
|
# Constants for Win32 services.
|
|
use constant WIN32_SERVICE_STOPPED => 0x01;
|
|
use constant WIN32_SERVICE_RUNNING => 0x04;
|
|
|
|
# Global vars
|
|
my %Config :shared;
|
|
my @Servers;
|
|
my $DBH;
|
|
my $RUN :shared = 1;
|
|
my $MainThread = threads->self;
|
|
|
|
########################################################################################
|
|
# Server shutdown. Handler to do a controlled shutdown.
|
|
########################################################################################
|
|
sub pandora_shutdown () {
|
|
my $signal = shift;
|
|
|
|
logger (\%Config, $Config{'rb_product_name'} . ' Server \'' . $Config{'servername'} . '\' Caught SIG' . $signal . ' by thread(' . threads->self()->tid() . ')', 10);
|
|
|
|
if (!threads->self->equal($MainThread)) {
|
|
# deliver signal to the main thread since no other threads than main thread
|
|
# could disconnet $DBH properly
|
|
$MainThread->kill($signal);
|
|
return;
|
|
}
|
|
logger (\%Config, $Config{'rb_product_name'} . ' Server \'' . $Config{'servername'} . '\' Shutdown by signal ', 1);
|
|
|
|
# Stop servers
|
|
foreach my $server (@Servers) {
|
|
$server->downEvent ();
|
|
$server->stop ();
|
|
}
|
|
@Servers = ();
|
|
|
|
# Stop the netflow daemon
|
|
pandora_stop_netflow_daemon ();
|
|
|
|
# Stop server threads.
|
|
stop_server_threads();
|
|
|
|
# Wait threads.
|
|
my $max_wait = 10;
|
|
my $waiting = 1;
|
|
my $start_waiting = time();
|
|
while ($waiting eq 1) {
|
|
$waiting = 0;
|
|
foreach my $thr (threads->list()) {
|
|
#my $tid = shift @{$self->{'_threads'}};
|
|
#my $thr = threads->object($tid);
|
|
|
|
if (defined($thr)) {
|
|
if ($thr->is_joinable()) {
|
|
$thr->join();
|
|
} else {
|
|
$thr->kill('KILL');
|
|
if (time() - $start_waiting < $max_wait) {
|
|
$waiting = 1;
|
|
} else {
|
|
# Some discovery external scripts tasks.
|
|
$thr->detach();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sleep (1);
|
|
}
|
|
|
|
print_message (\%Config, ' [*] Shutting down ' . $Config{'servername'} . "(received signal)...\n", 1);
|
|
db_disconnect ($DBH);
|
|
if ($Config{'PID'} ne "") {
|
|
unlink($Config{'PID'}) or logger (\%Config, "[W] Could not remove PID file: $!",1);
|
|
}
|
|
exit (0);
|
|
}
|
|
|
|
########################################################################################
|
|
# Server startup.
|
|
########################################################################################
|
|
sub pandora_startup () {
|
|
|
|
# Start logging
|
|
pandora_start_log (\%Config);
|
|
|
|
# Connect to the DB
|
|
$DBH = db_connect ($Config{'dbengine'}, $Config{'dbname'}, $Config{'dbhost'}, $Config{'dbport'},
|
|
$Config{'dbuser'}, $Config{'dbpass'});
|
|
|
|
# Grab config tokens shared with the console and not in the .conf
|
|
pandora_get_sharedconfig (\%Config, $DBH);
|
|
|
|
# Kill any running server threads.
|
|
stop_server_threads();
|
|
|
|
# Start the task execution thread.
|
|
start_server_thread(\&pandora_server_tasks, [\%Config]);
|
|
|
|
# Start the policy queue thread.
|
|
start_server_thread(\&pandora_process_policy_queue, [\%Config]) if ($Config{'__enterprise_enabled'} == 1 && $Config{'policy_manager'} == 1);
|
|
|
|
# Start agent autoconfiguration thread.
|
|
start_server_thread(\&pandora_agent_autoconfiguration_scheduled, [\%Config]) if ($Config{'__enterprise_enabled'} == 1 && $Config{'autoconfigure_agents'} == 1);
|
|
|
|
pandora_audit (\%Config, $Config{'rb_product_name'} . ' Server Daemon starting', 'SYSTEM', 'System', $DBH);
|
|
|
|
# Load servers
|
|
if (!is_metaconsole(\%Config)) {
|
|
pandora_reset_server (\%Config, $DBH);
|
|
push (@Servers, new PandoraFMS::AlertServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::DataServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::NetworkServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::DiscoveryServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::SNMPServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::WMIServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::PluginServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::PredictionServer (\%Config, $DBH));
|
|
push (@Servers, new PandoraFMS::WebServer (\%Config, $DBH));
|
|
} else {
|
|
# Metaconsole service modules are run by the prediction server
|
|
push (@Servers, new PandoraFMS::PredictionServer (\%Config, $DBH));
|
|
}
|
|
|
|
# There are enterprise metaconsole servers!
|
|
enterprise_hook('load_enterprise_servers', [\@Servers, \%Config, $DBH]);
|
|
|
|
# Start the netflow daemon if necessary
|
|
pandora_start_netflow_daemon ();
|
|
|
|
# Remove disabled servers
|
|
@Servers = grep { defined ($_) } @Servers;
|
|
|
|
# Run
|
|
foreach my $server (@Servers) {
|
|
$server->run ();
|
|
}
|
|
}
|
|
|
|
########################################################################################
|
|
# Server restart.
|
|
########################################################################################
|
|
sub pandora_restart (;$) {
|
|
|
|
my $sleep_time = @_ > 0 ? $_[0] : $Config{'restart_delay'};
|
|
|
|
# Stop the servers
|
|
eval {
|
|
foreach my $server (@Servers) {
|
|
$server->stop ();
|
|
}
|
|
};
|
|
|
|
# Remove the servers
|
|
while (pop (@Servers)) {};
|
|
|
|
# Close STDERR, redirected by pandora_start_log
|
|
close (STDERR);
|
|
|
|
# Wait before trying to start again
|
|
sleep ($sleep_time);
|
|
|
|
# Start the servers
|
|
pandora_startup ();
|
|
}
|
|
|
|
########################################################################################
|
|
# Server crash. Handler to write in the log unhandled errors and write it to console
|
|
########################################################################################
|
|
sub pandora_crash () {
|
|
|
|
my $full_error = "";
|
|
|
|
# Avoid show messages about enterprise library loading failurem, VERY
|
|
# confussing, all of them are warnigs and not critical, and user should be
|
|
# worried about that. If perl has a more "clean" way to avoid this messages
|
|
# will be nice to replace this code, but at this time it's the only way I know
|
|
|
|
foreach my $error_line (@_) {
|
|
# Trap the XML error and exit without nasty messages
|
|
if ($error_line =~ m/XML\/Parser/) {
|
|
logger (\%Config, "Problem parsing XML file, XML file discarded: $error_line", 2);
|
|
return;
|
|
}
|
|
|
|
elsif ($error_line !~ m/Enterprise/i && $error_line !~ m/Format_XS/i && $error_line !~ m/ConfigLocal/i){
|
|
logger (\%Config, '[E] \'' . $Config{'servername'} . "': $error_line", 1);
|
|
}
|
|
|
|
|
|
else {
|
|
if ($error_line !~ m/Can\'t\slocate/) {
|
|
logger (\%Config, '[E] \'' . $Config{'servername'} . "': $error_line", 1);
|
|
}
|
|
else {
|
|
# Known errors of loading Enterprise, Format_XS and ConfigLocal
|
|
# modules, non fatal.
|
|
return;
|
|
}
|
|
}
|
|
$full_error .= $error_line;
|
|
}
|
|
|
|
# Could crash before parse configuration.
|
|
$Config{'rb_product_name'} = 'PandoraFMS' unless defined($Config{'rb_product_name'}) && $Config{'rb_product_name'} ne '';
|
|
logger (\%Config, $Config{'rb_product_name'} . ' Server \'' . $Config{'servername'} . '\' unhandled error.', 1);
|
|
|
|
# It's interesting show by console problems, not only in logs. This helps
|
|
# to solve stupid problems like Database credential problems for example
|
|
|
|
print_message (\%Config, ' [E] Unhandled error in "' . $Config{'servername'} . "\". See more information in logfiles at '/var/log/pandora' \n", 0);
|
|
print_message (\%Config, " Error description:\n", 0);
|
|
print_message (\%Config, $full_error, 0);
|
|
|
|
callback_stop() if ($^O eq 'MSWin32' && defined($Config{'win32_service'}));
|
|
}
|
|
|
|
########################################################################################
|
|
# Start the netflow daemon if necessary.
|
|
########################################################################################
|
|
sub pandora_start_netflow_daemon () {
|
|
my $pid_file = '/var/run/pandora_nfcapd.pid';
|
|
|
|
# Check if netflow is enabled
|
|
if ($Config{'activate_netflow'} != 1) {
|
|
logger (\%Config, " [*] Netflow daemon disabled.", 1);
|
|
print_message (\%Config, " [*] Netflow daemon disabled.", 1);
|
|
return;
|
|
}
|
|
|
|
# Stop nfcapd if it's already running
|
|
my $pid = pandora_stop_netflow_daemon ();
|
|
if (pandora_stop_netflow_daemon () != 0) {
|
|
logger (\%Config, "nfcapd (pid $pid) is already running, attempting to kill it...", 1);
|
|
print_message (\%Config, "nfcapd (pid $pid) is already running, attempting to kill it...", 1);
|
|
}
|
|
|
|
# Start nfcapd
|
|
my $command = $Config{'netflow_daemon'} . ' -D -T all -w -t ' . $Config{'netflow_interval'} . ' -P ' . $pid_file . ' -l ' . $Config{'netflow_path'};
|
|
if (system ("$command >/dev/null 2>&1") != 0) {
|
|
logger (\%Config, " [E] Could not start nfcapd: $command", 1);
|
|
print_message (\%Config, " [E] Could not start nfcapd: $command", 1);
|
|
return;
|
|
}
|
|
|
|
logger (\%Config, "[*] Netflow daemon started.", 1);
|
|
print_message (\%Config, "[*] Netflow daemon started.", 1);
|
|
}
|
|
|
|
########################################################################################
|
|
# Stop the netflow daemon if it's running.
|
|
########################################################################################
|
|
sub pandora_stop_netflow_daemon () {
|
|
|
|
my $pid_file = '/var/run/pandora_nfcapd.pid';
|
|
|
|
# Open the pid file
|
|
if ( ! (-e $pid_file && open (PIDFILE, $pid_file))) {
|
|
return 0;
|
|
}
|
|
|
|
my $pid = <PIDFILE>;
|
|
close PIDFILE;
|
|
|
|
# Check if nfcapd is running
|
|
if (kill (0, $pid) > 0) {
|
|
kill (9, $pid);
|
|
return $pid;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub pandora_agent_autoconfiguration_scheduled($) {
|
|
my $pa_config = shift;
|
|
|
|
my %pa_config = %{$pa_config};
|
|
|
|
my $dbh = db_connect ($pa_config{'dbengine'}, $pa_config{'dbname'}, $pa_config{'dbhost'}, $pa_config{'dbport'},
|
|
$pa_config{'dbuser'}, $pa_config{'dbpass'});
|
|
|
|
while ($THRRUN == 1) {
|
|
eval {{
|
|
local $SIG{__DIE__};
|
|
|
|
my @autoconfig = get_db_rows (
|
|
$dbh,
|
|
'SELECT *, DATE_FORMAT(DATE_ADD(periodically_time_from, INTERVAL ' . $pa_config->{'autoconfigure_agents_threshold'} . ' SECOND), "%H:%i:%S") AS time_minutes
|
|
FROM tautoconfig WHERE executed = 0 AND type_execution LIKE "scheduled" AND disabled = 0'
|
|
);
|
|
|
|
# Get current time.
|
|
my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) = localtime(time());
|
|
my $time = sprintf ("%.2d:%.2d:%.2d", $hour, $min, $sec);
|
|
|
|
foreach my $conf (@autoconfig) {
|
|
if (($conf->{'type_periodicity'} eq 'daily') ||
|
|
($conf->{'type_periodicity'} eq 'monthly' && $conf->{'periodically_day_from'} eq $mday) ||
|
|
($conf->{'type_periodicity'} eq 'weekly' && (($conf->{'sunday'} eq 1 && $wday eq 0) ||
|
|
($conf->{'monday'} eq 1 && $wday eq 1) || ($conf->{'tuesday'} eq 1 && $wday eq 2) ||
|
|
($conf->{'wednesday'} eq 1 && $wday eq 3) || ($conf->{'thursday'} eq 1 && $wday eq 4) ||
|
|
($conf->{'friday'} eq 1 && $wday eq 5) || ($conf->{'saturday'} eq 1 && $wday eq 6)))
|
|
) {
|
|
if ($time ge $conf->{'periodically_time_from'} && $time le $conf->{'time_minutes'}) {
|
|
# Update executed.
|
|
db_process_update ($dbh, 'tautoconfig', {'executed' => 1}, {'id' => $conf->{'id'}});
|
|
# Get agents.
|
|
my @agents = get_db_rows(
|
|
$dbh,
|
|
'SELECT id_agente, alias, id_grupo, id_os, os_version, direccion, nombre AS agent_name FROM tagente
|
|
WHERE `disabled` = 0'
|
|
);
|
|
|
|
foreach my $agent (@agents) {
|
|
# Check if the agent matches the rules.
|
|
my $match = enterprise_hook('autoconf_evaluate_rules', [$pa_config, $dbh, $agent, $conf->{'id'}, $agent->{'id_agente'}, 1]);
|
|
if (defined($match) && $match > 0) {
|
|
enterprise_hook('autoconf_execute_actions', [$pa_config, $dbh, $agent->{'id_agente'}, $agent, $conf->{'id'}]);
|
|
}
|
|
}
|
|
|
|
# Update executed.
|
|
db_process_update ($dbh, 'tautoconfig', {'executed' => 0}, {'id' => $conf->{'id'}});
|
|
}
|
|
}
|
|
}
|
|
}};
|
|
|
|
sleep ($pa_config->{'autoconfigure_agents_threshold'});
|
|
}
|
|
|
|
db_disconnect($dbh);
|
|
}
|
|
|
|
########################################################################################
|
|
# Additional tasks executed periodically by the Pandora FMS Server
|
|
########################################################################################
|
|
sub pandora_server_tasks ($) {
|
|
my ($pa_config) = @_;
|
|
|
|
# Get the console DB connection
|
|
my $dbh = db_connect ($pa_config->{'dbengine'}, $pa_config->{'dbname'}, $pa_config->{'dbhost'}, $pa_config->{'dbport'},
|
|
$pa_config->{'dbuser'}, $pa_config->{'dbpass'});
|
|
my $counter = 0;
|
|
my $first_run = 1;
|
|
while ($THRRUN == 1) {
|
|
if (pandora_is_master($pa_config) == 1) {
|
|
|
|
# TASKS EXECUTED ONCE
|
|
# -------------------
|
|
if ($first_run == 1) {
|
|
$first_run = 0;
|
|
|
|
# Update the agent cache.
|
|
enterprise_hook('update_agent_cache', [\%Config]);
|
|
}
|
|
|
|
# TASKS EXECUTED EVERY 5 SECONDS (Low latency tasks)
|
|
# --------------------------------------------------
|
|
if (($counter % 5) == 0) {
|
|
|
|
# Update forced alerts
|
|
pandora_exec_forced_alerts ($pa_config, $dbh);
|
|
}
|
|
|
|
# TASKS EXECUTED EVERY 30 SECONDS (Mid latency tasks)
|
|
# ---------------------------------------------------
|
|
if (($counter % 30) == 0) {
|
|
|
|
# Update module status and fired alert counts
|
|
my @agents = get_db_rows ($dbh, 'SELECT id_agente, nombre, update_module_count, update_alert_count, update_secondary_groups FROM tagente WHERE (update_module_count=1 OR update_alert_count=1 OR update_secondary_groups=1)');
|
|
foreach my $agent (@agents) {
|
|
logger ($pa_config, "Updating module status and fired alert counts for agent " . $agent->{'nombre'}, 10);
|
|
|
|
if ($agent->{'update_module_count'} == 1) {
|
|
pandora_update_agent_module_count ($pa_config, $dbh, $agent->{'id_agente'});
|
|
}
|
|
|
|
if ($agent->{'update_alert_count'} == 1) {
|
|
pandora_update_agent_alert_count ($pa_config, $dbh, $agent->{'id_agente'});
|
|
}
|
|
|
|
if ($agent->{'update_secondary_groups'} == 1) {
|
|
pandora_update_secondary_groups_cache ($pa_config, $dbh, $agent->{'id_agente'});
|
|
}
|
|
}
|
|
|
|
# Keepalive module control.(very DB intensive, not run frecuently
|
|
pandora_module_keep_alive_nd ($pa_config, $dbh);
|
|
|
|
# Set the status of unknown modules
|
|
pandora_module_unknown ($pa_config, $dbh);
|
|
|
|
# Check if an autodisabled agent needs to be autodisable
|
|
pandora_disable_autodisable_agents ($pa_config, $dbh);
|
|
}
|
|
|
|
# TASKS EXECUTED EVERY 60 SECONDS (High latency tasks)
|
|
# ----------------------------------------------------
|
|
if (($counter % 60) == 0) {
|
|
# Downtimes are executed only 30 x Server Threshold secs
|
|
pandora_planned_downtime ($pa_config, $dbh);
|
|
|
|
# Realtime stats (Only master server!) - ( VERY HEAVY !)
|
|
# Realtimestats == 1, generated by WEB Console, not by server!
|
|
if (defined($pa_config->{"realtimestats"}) && $pa_config->{"realtimestats"} == 0){
|
|
|
|
# Check if I need to refresh stats
|
|
my $last_execution_stats = get_db_value ($dbh, "SELECT MAX(utimestamp) FROM tgroup_stat");
|
|
if (!defined($last_execution_stats) || $last_execution_stats < (time() - $pa_config->{"stats_interval"})){
|
|
pandora_group_statistics ($pa_config, $dbh);
|
|
pandora_server_statistics ($pa_config, $dbh);
|
|
}
|
|
}
|
|
|
|
# Event auto-expiry
|
|
my $expiry_time = $pa_config->{"event_expiry_time"};
|
|
my $expiry_window = $pa_config->{"event_expiry_window"};
|
|
if ($expiry_time > 0 && $expiry_window > 0 && $expiry_window > $expiry_time) {
|
|
my $time_ref = time ();
|
|
my $expiry_limit = $time_ref - $expiry_time;
|
|
my $expiry_window = $time_ref - $expiry_window;
|
|
db_do ($dbh, 'UPDATE tevento SET estado=1, ack_utimestamp=? WHERE estado=0 AND utimestamp < ? AND utimestamp > ?', $time_ref, $expiry_limit, $expiry_window);
|
|
}
|
|
}
|
|
}
|
|
|
|
# COMMON TASKS (master and non-master)
|
|
# ---------------------------------------------------------------
|
|
if (($counter % 30) == 0) {
|
|
# Update configuration options from the console.
|
|
pandora_get_sharedconfig ($pa_config, $dbh);
|
|
|
|
# Rotate the log file.
|
|
pandora_rotate_logfile($pa_config);
|
|
|
|
# Set event storm protection
|
|
pandora_set_event_storm_protection (pandora_get_tconfig_token ($dbh, 'event_storm_protection', 0));
|
|
}
|
|
# Pandora self monitoring
|
|
if (defined($pa_config->{"self_monitoring"})
|
|
&& $pa_config->{"self_monitoring"} == 1
|
|
&& !is_metaconsole($pa_config)
|
|
&& $counter % $pa_config->{'self_monitoring_interval'} == 0) {
|
|
pandora_self_monitoring ($pa_config, $dbh);
|
|
}
|
|
|
|
# Pandora sample agent
|
|
if (defined($pa_config->{'sample_agent'})) {
|
|
if ($pa_config->{'sample_agent'} == 1
|
|
&& !is_metaconsole($pa_config)
|
|
&& $counter % $pa_config->{'sample_agent_interval'} == 0){
|
|
pandora_sample_agent ($pa_config);
|
|
}
|
|
pandora_update_config_token ($dbh, 'sample_agent', $pa_config->{'sample_agent'});
|
|
}
|
|
|
|
# Avoid counter overflow
|
|
if ($counter >= ~0){
|
|
$counter = 0;
|
|
}
|
|
else {
|
|
$counter++;
|
|
}
|
|
|
|
sleep (1);
|
|
}
|
|
|
|
db_disconnect($dbh);
|
|
}
|
|
|
|
################################################################################
|
|
## Install the Windows service.
|
|
################################################################################
|
|
sub win32_install_service() {
|
|
|
|
# Load Win32::Daemon.
|
|
eval "use Win32::Daemon";
|
|
die($@) if ($@);
|
|
|
|
# Configure and install the service.
|
|
my $service_path = $0;
|
|
my $service_params = "-S run \"" . $Config{'pandora_path'} ."\"";
|
|
my %service_hash = (
|
|
machine => '',
|
|
name => 'PANDORAFMSSRV',
|
|
display => 'Pandora FMS Server',
|
|
path => $service_path,
|
|
user => '',
|
|
pwd => '',
|
|
description => 'Pandora FMS Server http://pandorafms.com/',
|
|
parameters => $service_params
|
|
);
|
|
|
|
if (Win32::Daemon::CreateService(\%service_hash)) {
|
|
print "Successfully added.\n";
|
|
exit 0;
|
|
} else {
|
|
print "Failed to add service: " . Win32::FormatMessage(Win32::Daemon::GetLastError()) . "\n";
|
|
exit 1;
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
## Install the Windows service.
|
|
################################################################################
|
|
sub win32_uninstall_service() {
|
|
|
|
# Load Win32::Daemon.
|
|
eval "use Win32::Daemon";
|
|
die($@) if ($@);
|
|
|
|
# Uninstall the service.
|
|
if (Win32::Daemon::DeleteService('', 'PANDORAFMSSRV')) {
|
|
print "Successfully deleted.\n";
|
|
exit 0;
|
|
} else {
|
|
print "Failed to delete service: " . Win32::FormatMessage(Win32::Daemon::GetLastError()) . "\n";
|
|
exit 1;
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
## Windows service callback function for the running event.
|
|
################################################################################
|
|
sub callback_running {
|
|
if (Win32::Daemon::State() == WIN32_SERVICE_RUNNING) {
|
|
}
|
|
}
|
|
|
|
################################################################################
|
|
## Windows service callback function for the start event.
|
|
################################################################################
|
|
sub callback_start {
|
|
no strict;
|
|
|
|
# Accept_connections ();
|
|
my $thr = threads->create(\&main);
|
|
if (!defined($thr)) {
|
|
Win32::Daemon::State(WIN32_SERVICE_STOPPED);
|
|
Win32::Daemon::StopService();
|
|
return;
|
|
}
|
|
$thr->detach();
|
|
|
|
Win32::Daemon::State(WIN32_SERVICE_RUNNING);
|
|
}
|
|
|
|
################################################################################
|
|
## Windows service callback function for the stop event.
|
|
################################################################################
|
|
sub callback_stop {
|
|
|
|
$RUN = 0;
|
|
Win32::Daemon::State(WIN32_SERVICE_STOPPED);
|
|
Win32::Daemon::StopService();
|
|
}
|
|
|
|
################################################################################
|
|
# Run as a Windows service.
|
|
################################################################################
|
|
sub win32_service_run() {
|
|
|
|
# Load Win32::Daemon.
|
|
eval "use Win32::Daemon";
|
|
die($@) if ($@);
|
|
|
|
# Run the Pandora FMS Server as a Windows service.
|
|
Win32::Daemon::RegisterCallbacks({
|
|
start => \&callback_start,
|
|
running => \&callback_running,
|
|
stop => \&callback_stop,
|
|
});
|
|
Win32::Daemon::StartService();
|
|
}
|
|
|
|
################################################################################
|
|
## Parse command line options.
|
|
################################################################################
|
|
sub parse_service_options ($) {
|
|
my $config = shift;
|
|
|
|
# Sanity checks.
|
|
return unless defined($config->{'win32_service'});
|
|
die ("[ERROR] Windows services are only available on Win32.\n\n") if ($^O ne 'MSWin32');
|
|
|
|
# Win32 service management.
|
|
eval "use Win32::Daemon";
|
|
die($@) if ($@);
|
|
|
|
if ($config->{'win32_service'} eq 'install') {
|
|
win32_install_service();
|
|
} elsif ($config->{'win32_service'} eq 'uninstall') {
|
|
win32_uninstall_service();
|
|
} elsif ($config->{'win32_service'} eq 'run') {
|
|
} else {
|
|
die("[ERROR] Unknown action: " . $config->{'win32_service'});
|
|
}
|
|
}
|
|
|
|
################################################################
|
|
################################################################
|
|
## Main.
|
|
################################################################
|
|
################################################################
|
|
sub main() {
|
|
|
|
# Daemonize and put in background
|
|
if ($Config{'daemon'} == 1) {
|
|
print_message (\%Config, " [*] Backgrounding " . pandora_get_initial_product_name() . " Server process.\n", 1);
|
|
pandora_daemonize (\%Config);
|
|
}
|
|
|
|
# Load enterprise module
|
|
if (enterprise_load (\%Config) == 0) {
|
|
$Config{'__enterprise_enabled'} = 0;
|
|
print_message (\%Config, " [*] Pandora FMS Enterprise module not available.", 1);
|
|
logger (\%Config, " [*] Pandora FMS Enterprise module not available.", 1);
|
|
} else {
|
|
$Config{'__enterprise_enabled'} = 1;
|
|
print_message (\%Config, " [*] " . pandora_get_initial_product_name() . " Enterprise module loaded.", 1);
|
|
logger (\%Config, " [*] " . pandora_get_initial_product_name() . " Enterprise module loaded.", 1);
|
|
}
|
|
|
|
# Save the start time for warmup intervals.
|
|
$Config{'__start_utimestamp__'} = time();
|
|
|
|
# Start the servers
|
|
pandora_startup ();
|
|
|
|
if ($Config{'warmup_unknown_interval'} > 0) {
|
|
logger(\%Config, "Warmup mode for unknown modules started.", 3);
|
|
pandora_event (\%Config, "Warmup mode for unknown modules started.", 0, 0, 0, 0, 0, 'system', 0, $DBH);
|
|
}
|
|
if ($Config{'warmup_event_interval'} > 0) {
|
|
logger(\%Config, "Warmup mode for alerts started.", 3);
|
|
logger(\%Config, "Warmup mode for events started.", 3);
|
|
pandora_event (\%Config, "Warmup mode for alerts started.", 0, 0, 0, 0, 0, 'system', 0, $DBH);
|
|
pandora_event (\%Config, "Warmup mode for events started.", 0, 0, 0, 0, 0, 'system', 0, $DBH);
|
|
}
|
|
|
|
# Only if console_api_url was not defined
|
|
if( !defined($Config{"console_api_url"}) ) {
|
|
$Config{"console_api_url"} = get_console_api_url(\%Config, $DBH);
|
|
}
|
|
|
|
# Definition of configuration file
|
|
my $cfg_file = $Config{'pandora_path'};
|
|
my $cfg_file_output = $Config{'pandora_path'} . "_backup";
|
|
|
|
# Only if console_api_pass was not defined
|
|
if ( !defined($Config{"console_api_pass"}) ) {
|
|
my $console_api_pass = pandora_get_tconfig_token ($DBH, 'api_password', '');
|
|
# If api_password is empty in database
|
|
if ( $console_api_pass eq '' ) {
|
|
$console_api_pass = '1234';
|
|
db_process_update ($DBH, 'tconfig', {'value' => $console_api_pass}, {'token' => 'api_password'});
|
|
}
|
|
# Definition of console_api_pass in config
|
|
$Config{"console_api_pass"} = $console_api_pass;
|
|
# Watch if paramether is added or not (even if it is commented)
|
|
my $console_api_pass_control = undef;
|
|
if ( open (CFGin, "<$cfg_file") && open (CFGout, ">>$cfg_file_output") ) {
|
|
while(my $row = <CFGin>) {
|
|
if (chomp($row) =~ (m/^#\sconsole_api_pass\s(.*)/i)) {
|
|
$console_api_pass_control = 1;
|
|
print CFGout "\nconsole_api_pass " .$Config{"console_api_pass"} . "\n";
|
|
} else {
|
|
print CFGout "$row\n";
|
|
}
|
|
}
|
|
# Only if the parameter was not added
|
|
if ( !defined($console_api_pass_control) ) {
|
|
print CFGout "\n# console_api_pass: Console password\n";
|
|
print CFGout "console_api_pass " .$Config{"console_api_pass"} . "\n";
|
|
}
|
|
# Close both files
|
|
close (CFGin);
|
|
close (CFGout);
|
|
# Convert the output file in the original configuration file
|
|
rename $cfg_file_output, $cfg_file;
|
|
}
|
|
}
|
|
|
|
# Only if console_pass was not defined.
|
|
if ( !defined($Config{"console_pass"}) ){
|
|
# Randomized parametrization of console_pass.
|
|
if (open (CFG, ">>$cfg_file")) {
|
|
my $valid_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
my $num_char = 8;
|
|
my $randomized_string = '';
|
|
for (my $i = 0; $i < $num_char; $i++) {
|
|
$randomized_string .= substr($valid_chars, rand(length($valid_chars)), 1);
|
|
}
|
|
|
|
$Config{"console_pass"} = $randomized_string;
|
|
print CFG "\n# console_pass: Console password\n";
|
|
print CFG "# To make sure console_api_url, console_api_pass, console_user and console_pass are properly configured run:\n";
|
|
print CFG "# curl '<console_api_url>?op=get&op2=test&apipass=<console_api_pass>&user=<console_user>&pass=<console_pass>'\n";
|
|
print CFG "# It should return a string similar to:\n";
|
|
print CFG "# OK,{VERSION},{BUILD}\n";
|
|
print CFG "console_pass " .$Config{"console_pass"} . "\n";
|
|
|
|
close (CFG);
|
|
|
|
} else {
|
|
logger(\%Config, "[WARNING] Error with configuration file when define `console_pass`: $!", 3);
|
|
}
|
|
}
|
|
|
|
# Only if console_user was not defined
|
|
if ( !defined($Config{"console_user"}) ) {
|
|
my $pandora_uid = pandora_get_tconfig_token ($DBH, 'pandora_uid', '');
|
|
|
|
if ( $pandora_uid ne '' && $pandora_uid ne 'OFFLINE' ) {
|
|
$Config{"console_user"} = "internal_API_$pandora_uid";
|
|
} else {
|
|
$Config{"console_user"} = "internal_API";
|
|
}
|
|
|
|
# If user not exists in DB, is necessary to create it
|
|
if ( get_user_exists($DBH, $Config{"console_user"}) == -1 ) {
|
|
|
|
# Definition of API user parameters
|
|
my $api_user_parameters = {};
|
|
$api_user_parameters->{'id_user'} = $Config{"console_user"};
|
|
$api_user_parameters->{'password'} = md5_hex($Config{"console_pass"});
|
|
$api_user_parameters->{'comments'} = "Internal user, used for generating reports and email attachments";
|
|
$api_user_parameters->{'is_admin'} = 0;
|
|
$api_user_parameters->{'not_login'} = 1;
|
|
|
|
# Profile creation for API purpouses
|
|
my $api_profile_parameters = {};
|
|
$api_profile_parameters->{'id_usuario'} = $Config{"console_user"};
|
|
$api_profile_parameters->{'id_perfil'} = 1;
|
|
$api_profile_parameters->{'id_grupo'} = 0;
|
|
$api_profile_parameters->{'assigned_by'} = "system";
|
|
$api_profile_parameters->{'id_policy'} = 0;
|
|
|
|
# Insert in DB
|
|
my $res_tusuario = db_process_insert($DBH, 'id_user', 'tusuario', $api_user_parameters);
|
|
my $res_tusuario_perfil = db_process_insert($DBH, 'id_user', 'tusuario_perfil', $api_profile_parameters);
|
|
|
|
# If the user was inserted in DB, must write it in configuration file
|
|
if ( $res_tusuario_perfil > 0 ) {
|
|
if (open (CFG, ">>$cfg_file")) {
|
|
print CFG "\n# Console User (created for API use)\n";
|
|
print CFG "console_user " . $Config{"console_user"} . "\n";
|
|
close (CFG);
|
|
|
|
} else {
|
|
logger(\%Config, "Warning. Was not possible edit configuration file for add console user", 3);
|
|
}
|
|
} else {
|
|
logger(\%Config, "Warning. Was not possible creating console user for API.", 3);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Testing API url
|
|
my $curl_execution = "'".$Config{'console_api_url'}."?op=get&op2=test&apipass=".$Config{"console_api_pass"}."&user=".$Config{"console_user"}."&pass=".$Config{"console_pass"}."'";
|
|
# More than 30 secs is highly unrecommendated
|
|
my $command = $Config{'plugin_exec'}.' 30 curl '.$curl_execution.' 2>/dev/null';
|
|
my $exe_testing_api = `$command`;
|
|
my @res_testing_api = split(',', $exe_testing_api);
|
|
if ( $res_testing_api[0] ne 'OK' ) {
|
|
logger(\%Config, "Warning! The server does not have access to the API, this can trigger problems in the generation of reports and graphs.", 1);
|
|
pandora_event (\%Config, "Server does not have access to the API", 0, 0, 0, 0, 0, 'system', 0, $DBH);
|
|
} else {
|
|
# Test successful.
|
|
pandora_set_tconfig_token($DBH, 'internal_user_pass',
|
|
pandora_input_password(\%Config, $Config{"console_pass"})
|
|
);
|
|
pandora_set_tconfig_token($DBH, 'internal_user', $Config{"console_user"});
|
|
}
|
|
|
|
# Generate 'going up' events
|
|
foreach my $server (@Servers) {
|
|
$server->upEvent ();
|
|
}
|
|
|
|
# Check if the Data Server has too many threads
|
|
if ($Config{'dataserver_threads'} > 5) {
|
|
logger (\%Config, "[W] Server " . $Config{'servername'} . " have configured " . $Config{'dataserver_threads'}
|
|
. " threads for the data server. You should not use more than 5 threads for this server", 1);
|
|
print_message (\%Config, " [W] Server " . $Config{'servername'} . " have configured " . $Config{'dataserver_threads'}
|
|
. " threads for the data server. You should not use more than 5 threads for this server", 1);
|
|
pandora_event (\%Config, "Server " . $Config{'servername'} . " have configured "
|
|
. $Config{'dataserver_threads'} . " threads for the data server", 0, 0, 3, 0, 0, 'system', 0, $DBH);
|
|
}
|
|
|
|
# Check if the Pandora Server has too many threads
|
|
my $totalThreads = 0;
|
|
foreach my $server (@Servers) {
|
|
$totalThreads += $server->getNumThreads ();
|
|
}
|
|
if ($totalThreads > 40) {
|
|
logger (\%Config, '[W] Server ' . $Config{'servername'} . ' have configured a total of ' . $totalThreads
|
|
. ' threads. This setup is not recommended, you should reduce the number of total threads below 40', 1);
|
|
print_message (\%Config, ' [W] Server ' . $Config{'servername'} . ' have configured a total of ' . $totalThreads
|
|
. ' threads. This setup is not recommended, you should reduce the number of total threads below 40', 1);
|
|
pandora_event (\%Config, 'Server ' . $Config{'servername'} . ' have configured a total of ' . $totalThreads
|
|
. ' threads', 0, 0, 3, 0, 0, 'system', 0, $DBH);
|
|
}
|
|
|
|
# Check if the log verbosity is set to 10
|
|
if ($Config{'verbosity'} == 10) {
|
|
logger (\%Config, '[W] Log verbosity is set to 10. This will degrade the server performance. Please set to a lower value ASAP', 1);
|
|
print_message (\%Config, ' [W] Log verbosity is set to 10. This will degrade the server performance. Please set to a lower value ASAP', 1);
|
|
pandora_event (\%Config, 'Log verbosity is set to 10. This will degrade the server performance', 0, 0, 1, 0, 0, 'system', 0, $DBH);
|
|
}
|
|
|
|
# Main loop
|
|
my $time_ref = time ();
|
|
my $test_remote_interval = ($Config{'keepalive'}/$Config{'server_threshold'});
|
|
my $test_remote = 0;
|
|
while ($RUN == 1) {
|
|
|
|
eval {
|
|
|
|
# Update server status
|
|
foreach my $server (@Servers) {
|
|
die ($server->getErrStr ()) unless ($server->checkThreads () == 1);
|
|
$server->update();
|
|
}
|
|
|
|
# Make sure all server threads are running.
|
|
die("Server thread crashed.") unless (check_server_threads() == 1);
|
|
|
|
db_do ($DBH,
|
|
"UPDATE tserver SET status = -1
|
|
WHERE UNIX_TIMESTAMP(now())-UNIX_TIMESTAMP(keepalive) > 2*server_keepalive
|
|
AND status != 0 AND server_type != ?",
|
|
PandoraFMS::Tools::SATELLITESERVER
|
|
);
|
|
|
|
# Set the master server
|
|
pandora_set_master(\%Config, $DBH);
|
|
};
|
|
|
|
# Restart on error or auto restart
|
|
if ($@) {
|
|
|
|
if ($Config{'restart'} eq '0') {
|
|
print_message (\%Config, $@, 1);
|
|
pandora_shutdown ();
|
|
}
|
|
|
|
# Generate 'restarting' events
|
|
foreach my $server (@Servers) {
|
|
$server->restartEvent ($@);
|
|
}
|
|
|
|
logger (\%Config, $Config{'rb_product_name'} . ' Server restarting (' . $@ . ') in ' . $Config{'restart_delay'} . ' seconds.', 1);
|
|
pandora_restart ();
|
|
}
|
|
elsif (($Config{'auto_restart'} > 0) && (time () - $time_ref > $Config{'auto_restart'})) {
|
|
$time_ref = time ();
|
|
|
|
# Mute
|
|
open(OLDOUT, ">&STDOUT");
|
|
open (STDOUT, '>/dev/null');
|
|
|
|
# Restart
|
|
pandora_restart ();
|
|
|
|
# Unmute
|
|
open(STDOUT, ">&OLDOUT");
|
|
close (OLDOUT);
|
|
}
|
|
|
|
if ($test_remote >= $test_remote_interval) {
|
|
if ($Config{'remote_config'} == 1 && enterprise_hook ('pandora_remote_config_server', [\%Config])) {
|
|
|
|
# Generate 'restarting' events
|
|
foreach my $server (@Servers) {
|
|
$server->restartEvent ($@);
|
|
}
|
|
|
|
logger (\%Config, $Config{'rb_product_name'} . ' Server restarting (' . $@ . ') in 5 seconds.', 1);
|
|
pandora_load_config (\%Config);
|
|
if (enterprise_load (\%Config) == 0) {
|
|
$Config{'__enterprise_enabled'} = 0;
|
|
print_message (\%Config, " [*] Pandora FMS Enterprise module not available.", 1);
|
|
logger (\%Config, " [*] Pandora FMS Enterprise module not available.", 1);
|
|
} else {
|
|
$Config{'__enterprise_enabled'} = 1;
|
|
print_message (\%Config, " [*] " . pandora_get_initial_product_name() . " Enterprise module loaded.", 1);
|
|
logger (\%Config, " [*] " . pandora_get_initial_product_name() . " Enterprise module loaded.", 1);
|
|
}
|
|
pandora_restart (5);
|
|
}
|
|
$test_remote = 0;
|
|
}
|
|
else {
|
|
$test_remote++;
|
|
}
|
|
|
|
threads->yield;
|
|
sleep ($Config{'server_threshold'});
|
|
}
|
|
|
|
pandora_shutdown();
|
|
}
|
|
|
|
$SIG{'TERM'} = 'pandora_shutdown';
|
|
$SIG{'INT'} = 'pandora_shutdown';
|
|
|
|
# Error handler needs to be reviewed, Enterprise not found errors are too nasty :(
|
|
$SIG{__DIE__} = 'pandora_crash';
|
|
|
|
# Prevent alarm from bombing the main thread when called within a thread
|
|
$SIG{'ALRM'} = 'IGNORE';
|
|
|
|
# Initialize
|
|
pandora_init(\%Config, pandora_get_initial_product_name() . ' Server');
|
|
pandora_load_config (\%Config);
|
|
|
|
# Parse command line options.
|
|
parse_service_options(\%Config);
|
|
|
|
# Run as a regular process.
|
|
if (!defined($Config{'win32_service'})) {
|
|
main();
|
|
}
|
|
# Run as a Windows service.
|
|
else {
|
|
win32_service_run();
|
|
}
|
|
|
|
################################################################################
|
|
# Kill any scripts started by the Pandora FMS Server that are still running.
|
|
################################################################################
|
|
END {{
|
|
local $SIG{HUP} = "IGNORE";
|
|
kill("HUP", -$$);
|
|
}}
|
|
|