Pablo de la Concepción Sanz 2f575d378e 2010-02-15 Pablo de la Concepción <pablo.concepcion@artica.es>
* conf/pandora_server.conf, lib/PandoraFMS/Config.pm: New configuration
    parameters: recon_reverse_geolocation_mode, recon_reverse_geolocation_file,
    recon_location_scatter_radius. As now the Recon Server can try to geolocate
    an agent when it's found by revers geoip location.

    * lib/PandoraFMS/Core.pm: Added first version of POD documentation to
    create the manpage and to have most of the subrutines documented. Also
    it's possible to add the description of the location to the GIS information
    on the pandora_update_agent and pandora_create_agent subroutines. And
    finaly moved away the GIS related functions (distance_moved) to their own
    new module PandoraFMS::GIS

    * lib/PandoraFMS/GIS.pm: New module to keep all the GIS related functions
    of Pandora FMS. Now it exports distance_moved, get_reverse_geoip_sql,
    get_reverse_geoip_file and  get_random_close_point.

    * lib/PandoraFMS/ReconServer.pm: Updated to get GIS information by doing
    reverse IP geolocation using GIS module.

    * man/man3/PandoraFMS::Core.pm.3, man/man3/PandoraFMS::GIS.pm.3: New
    manpages for Core and GIS modules.



git-svn-id: https://svn.code.sf.net/p/pandora/code/trunk@2368 c3f86ba8-e40f-0410-aaad-9ba5e7f4b01f
2010-02-15 20:34:57 +00:00

421 lines
17 KiB
Perl

package PandoraFMS::ReconServer;
##########################################################################
# Pandora FMS Recon Server.
# Pandora FMS. the Flexible Monitoring System. http://www.pandorafms.org
##########################################################################
# Copyright (c) 2005-2009 Artica Soluciones Tecnologicas S.L
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser 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 threads;
use threads::shared;
use Thread::Semaphore;
use IO::Socket::INET;
use NetAddr::IP;
use POSIX qw(strftime ceil);
use PandoraFMS::Tools;
use PandoraFMS::DB;
use PandoraFMS::Core;
use PandoraFMS::ProducerConsumerServer;
use PandoraFMS::GIS qw(get_reverse_geoip_sql get_reverse_geoip_file get_random_close_point);
# Inherits from PandoraFMS::ProducerConsumerServer
our @ISA = qw(PandoraFMS::ProducerConsumerServer);
# Global variables
my @TaskQueue :shared;
my %PendingTasks :shared;
my $Sem :shared = Thread::Semaphore->new;
my $TaskSem :shared = Thread::Semaphore->new (0);
my $TracerouteAvailable = (eval 'use Net::Traceroute::PurePerl; 1') ? 1 : 0;
########################################################################################
# Recon Server class constructor.
########################################################################################
sub new ($$$$$$) {
my ($class, $config, $dbh) = @_;
return undef unless $config->{'reconserver'} == 1;
# Call the constructor of the parent class
my $self = $class->SUPER::new($config, 3, \&PandoraFMS::ReconServer::data_producer, \&PandoraFMS::ReconServer::data_consumer, $dbh);
bless $self, $class;
return $self;
}
###############################################################################
# Run.
###############################################################################
sub run ($) {
my $self = shift;
my $pa_config = $self->getConfig ();
print_message ($pa_config, " [*] Starting Pandora FMS Recon Server.", 1);
$self->setNumThreads ($pa_config->{'recon_threads'});
$self->SUPER::run (\@TaskQueue, \%PendingTasks, $Sem, $TaskSem);
}
###############################################################################
# Data producer.
###############################################################################
sub data_producer ($) {
my $self = shift;
my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ());
my @tasks;
my $server_id = get_server_id ($dbh, $pa_config->{'servername'}, $self->getServerType ());
return @tasks unless defined ($server_id);
my @rows = get_db_rows ($dbh, 'SELECT * FROM trecon_task
WHERE id_recon_server = ?
AND (utimestamp = 0 OR (utimestamp + interval_sweep) < UNIX_TIMESTAMP())', $server_id);
foreach my $row (@rows) {
# Update task status
update_recon_task ($dbh, $row->{'id_rt'}, 1);
push (@tasks, $row->{'id_rt'});
}
return @tasks;
}
###############################################################################
# Data consumer.
###############################################################################
sub data_consumer ($$) {
my ($self, $task_id) = @_;
my ($pa_config, $dbh) = ($self->getConfig (), $self->getDBH ());
# Get recon task data
my $task = get_db_single_row ($dbh, 'SELECT * FROM trecon_task WHERE id_rt = ?', $task_id);
return -1 unless defined ($task);
# Get a NetAddr::IP object for the target network
my $net_addr = new NetAddr::IP ($task->{'subnet'});
if (! defined ($net_addr)) {
logger ($pa_config, "Invalid network " . $task->{'subnet'} . " for task '" . $task->{'name'} . "'.", 3);
update_recon_task ($dbh, $task_id, -1);
return -1;
}
# Scan the network for hosts
my ($total_hosts, $hosts_found, $addr_found) = ($net_addr->num, 0, '');
for (my $i = 1, $net_addr++; $net_addr < $net_addr->broadcast; $i++, $net_addr++) {
my $addr = (split(/\//, $net_addr))[0];
# Update the recon task or break if it does not exist anymore
last if (update_recon_task ($dbh, $task_id, ceil ($i / ($total_hosts / 100))) eq '0E0');
# Does the host already exist?
next if (get_agent_from_addr ($dbh, $addr) > 0);
my $alive = 0;
if (pandora_ping ($pa_config, $addr) == 1) {
$alive = 1;
# TCP Port profiling
if ((defined ($task->{'recon_ports'})) && ($task->{'recon_ports'} ne "")) {
$alive = tcp_scan ($pa_config, $addr, $task->{'recon_ports'});
}
}
next unless ($alive > 0);
logger($pa_config, "Found host $addr.", 10);
# Guess the OS and filter
my $id_os = guess_os ($pa_config, $addr);
if ($task->{'id_os'} > 0 && $task->{'id_os'} != $id_os) {
logger($pa_config, "Skipping host $addr os ID $id_os.", 10);
next;
}
$hosts_found ++;
$addr_found .= $addr . " ";
# Resolve the address
my $host_name = gethostbyaddr(inet_aton($addr), AF_INET);
$host_name = $addr unless defined ($host_name);
# Get the parent host
my $parent_id = get_host_parent ($pa_config, $addr, $dbh);
# Add the new address if it does not exist
my $addr_id = get_addr_id ($dbh, $addr);
$addr_id = add_address ($dbh, $addr) unless ($addr_id > 0);
if ($addr_id <= 0) {
logger($pa_config, "Could not add address '$addr' for host '$host_name'.", 3);
next;
}
my $agent_id;
logger($pa_config, "GIS Status = ".$pa_config->{'activate_gis'}, 10);
# If GIS is activated try to geolocate the ip address of the agent and store also it's position.
if($pa_config->{'activate_gis'} == 1 && $pa_config->{'recon_reverse_geolocation_mode'} !~ m/^disabled$/i) {
# Try to get aproximated positional information for the Agent.
my $region_info = undef;
if ($pa_config->{'recon_reverse_geolocation_mode'} =~ m/^sql$/i) {
logger($pa_config, "Trying to get gis data of $addr from the SQL database", 8);
$region_info = get_reverse_geoip_sql($pa_config, $addr, $dbh);
}
elsif ($pa_config->{'recon_reverse_geolocation_mode'} =~ m/^file$/i) {
logger($pa_config, "Trying to get gis data of $addr from the file database", 8);
$region_info = get_reverse_geoip_file($pa_config, $addr);
}
else {
logger($pa_config, "ERROR:Trying to get gis data of $addr. Unknown source", 5);
}
if (defined($region_info)) {
my $location_description = '';
if (defined($region_info->{'region'})) {
$location_description .= "$region_info->{'region'}, ";
}
if (defined($region_info->{'city'})) {
$location_description .= "$region_info->{'city'}, ";
}
if (defined($region_info->{'country_name'})) {
$location_description .= "($region_info->{'country_name'})";
}
# We store a random offset in the coordinates to avoid all the agents apear on the same place.
my ($longitude, $latitude) = get_random_close_point ($pa_config, $region_info->{'longitude'}, $region_info->{'latitude'});
logger($pa_config, "Placing agent on random position (Lon,Lat) = ($longitude, $latitude)", 8);
# Crate a new agent adding the positional info (as is unknown we set 0 time_offset, and 0 altitude)
$agent_id = pandora_create_agent ($pa_config, $pa_config->{'servername'},
$host_name, $addr, $addr_id,
$task->{'id_group'}, $parent_id, $id_os, '', 300, $dbh, 0, $longitude, $latitude, 0, $location_description);
}
else {
logger($pa_config,"Id location of '$addr' for host '$host_name' NOT found", 3);
# Crate a new agent
$agent_id = pandora_create_agent ($pa_config, $pa_config->{'servername'},
$host_name, $addr, $addr_id,
$task->{'id_group'}, $parent_id, $id_os, '', 300, $dbh);
}
}
else {
# Crate a new agent
$agent_id = pandora_create_agent ($pa_config, $pa_config->{'servername'},
$host_name, $addr, $addr_id,
$task->{'id_group'}, $parent_id, $id_os, '', 300, $dbh);
}
# Assign the new address to the agent
db_insert ($dbh, 'INSERT INTO taddress_agent (`id_a`, `id_agent`)
VALUES (?, ?)', $addr_id, $agent_id);
# Crate network profile modules for the agent
create_network_profile_modules ($pa_config, $dbh, $agent_id, $task->{'id_network_profile'}, $addr);
# Generate an event
pandora_event ($pa_config, "[RECON] New host [$host_name] detected on network [" . $task->{'subnet'} . ']',
$task->{'id_group'}, $agent_id, 2, 0, 0, 'recon_host_detected', 0, $dbh);
}
# Create an incident
if ($hosts_found > 0 && $task->{'create_incident'} == 1){
my $text = "At " . strftime ("%Y-%m-%d %H:%M:%S", localtime()) . " ($hosts_found) new hosts were detected by Pandora FMS Recon Server running on [" . $pa_config->{'servername'} . "_Recon]. This incident has been automatically created following instructions for this recon task [" . $task->{'id_group'} . "].\n\n";
if ($task->{'id_network_profile'} > 0) {
$text .= "Aditionally, and following instruction for this task, agent(s) has been created, with modules assigned to network component profile [" . get_nc_profile_name ($dbh, $task->{'id_network_profile'}) . "]. Please check this agent as soon as possible to verify it.";
}
$text .= "\n\nThis is the list of IP addresses found: \n\n$addr_found";
pandora_create_incident ($pa_config, $dbh, "[RECON] New hosts detected", $text, 0, 0, 'Pandora FMS Recon Server', $task->{'id_group'});
}
# Mark recon task as done
update_recon_task ($dbh, $task_id, -1);
}
##############################################################################
# TCP scan the given host/port. Returns 1 if successful, 0 otherwise.
##############################################################################
sub tcp_scan ($$$) {
my ($pa_config, $host, $portlist) = @_;
my $runcommand;
my $nmap = $pa_config->{'nmap'};
eval {
$runcommand = `$nmap -p$portlist $host | grep open | wc -l`;
};
return 0 if ($@);
return $runcommand;
}
##########################################################################
# Guess OS using xprobe2.
##########################################################################
sub guess_os {
my ($pa_config, $host) = @_;
my $nmap = $pa_config->{'nmap'};
my $xprobe = $pa_config->{'xprobe2'};
# if xprobe2 not available, use nmap, if not, not able to detect OS
if (! -e $xprobe){
return 10 if (! -e $nmap);
}
# Execute Nmap (4.x) or Xprobe2
my $output = '';
eval {
if (-e $xprobe){
$output = `$xprobe $host 2> /dev/null | grep 'Running OS' | head -1`;
} else {
$output = `$nmap -F -O $host 2> /dev/null | grep 'Aggressive OS guesses'`;
}
};
# Check for errors
return 10 if ($@);
return pandora_get_os ($output);
}
##########################################################################
# Return the ID of the given address, -1 if it does not exist.
##########################################################################
sub get_addr_id ($$) {
my ($dbh, $addr) = @_;
my $addr_id = get_db_value ($dbh, 'SELECT id_a FROM taddress WHERE ip = ?', $addr);
return (defined ($addr_id) ? $addr_id : -1);
}
##########################################################################
# Return the ID of the agent with the given IP.
##########################################################################
sub get_agent_from_addr ($$) {
my ($dbh, $ip_address) = @_;
return 0 if (! defined ($ip_address) || $ip_address eq '');
my $agent_id = get_db_value ($dbh, 'SELECT id_agent FROM taddress, taddress_agent WHERE taddress_agent.id_a = taddress.id_a AND ip = ?', $ip_address);
return (defined ($agent_id)) ? $agent_id : -1;
}
##########################################################################
# Update recon task status.
##########################################################################
sub update_recon_task ($$$) {
my ($dbh, $id_task, $status) = @_;
db_do ($dbh, 'UPDATE trecon_task SET utimestamp = ?, status = ? WHERE id_rt = ?', time (), $status, $id_task);
}
##########################################################################
# Add the given address to taddress.
##########################################################################
sub add_address ($$) {
my ($dbh, $ip_address) = @_;
return db_insert ($dbh, 'INSERT INTO taddress (ip) VALUES (?)', $ip_address);
}
##########################################################################
# Create network profile modules for the given agent.
##########################################################################
sub create_network_profile_modules {
my ($pa_config, $dbh, $agent_id, $np_id, $addr) = @_;
return unless ($np_id > 0);
# Get network components associated to the network profile
my @np_components = get_db_rows ($dbh, 'SELECT * FROM tnetwork_profile_component WHERE id_np = ?', $np_id);
foreach my $np_component (@np_components) {
# Get network component data
my $component = get_db_single_row ($dbh, 'SELECT * FROM tnetwork_component wHERE id_nc = ?', $np_component->{'id_nc'});
if (! defined ($component)) {
logger($pa_config, "Network component ID " . $np_component->{'id_nc'} . " for agent $addr not found.", 3);
next;
}
logger($pa_config, "Processing network component '" . $component->{'name'} . "' for agent $addr.", 10);
# Create the module
my $module_id = db_insert ($dbh, 'INSERT INTO tagente_modulo (id_agente, id_tipo_modulo, descripcion, nombre, max, min, module_interval, tcp_port, tcp_send, tcp_rcv, snmp_community, snmp_oid, ip_target, id_module_group, flag, disabled, plugin_user, plugin_pass, plugin_parameter, max_timeout, id_modulo )
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, 0, ?, ?, ?, ?, ?)',
$agent_id, $component->{'type'}, $component->{'description'}, $component->{'name'}, $component->{'max'}, $component->{'min'}, $component->{'module_interval'}, $component->{'tcp_port'}, $component->{'tcp_send'}, $component->{'tcp_rcv'}, $component->{'snmp_community'},
$component->{'snmp_oid'}, $addr, $component->{'id_module_group'}, $component->{'plugin_user'}, $component->{'plugin_pass'}, $component->{'plugin_parameter'}, $component->{'max_timeout'}, $component->{'id_modulo'});
# An entry in tagente_estado is necessary for the module to work
db_insert ($dbh, 'INSERT INTO tagente_estado (id_agente_modulo, datos, timestamp, estado, id_agente, last_try, utimestamp, current_interval, running_by)
VALUES (?, \'\', \'0000-00-00 00:00:00\', 1, ?, \'0000-00-00 00:00:00\', 0, ?, 0)',
$module_id, $agent_id, $component->{'module_interval'});
logger($pa_config, 'Creating module ' . $component->{'name'} . " for agent $addr from network component '" . $component->{'name'} . "'.", 10);
}
}
##########################################################################
# Returns the ID of the parent of the given host if available.
##########################################################################
sub get_host_parent ($$){
my ($pa_config, $host, $dbh) = @_;
# Traceroute not available
return 0 unless ($TracerouteAvailable != 0);
my $traceroutetimeout = $pa_config->{'networktimeout'} * 2;
my $tr = Net::Traceroute::PurePerl->new (
backend => 'PurePerl',
host => $host,
debug => 0,
max_ttl => 15,
query_timeout => $traceroutetimeout,
packetlen => 80,
protocol => 'udp', # udp or icmp
);
my $success = 0;
# Call traceroute
eval {
local $SIG{'ALRM'} = sub { return 0; };
alarm($traceroutetimeout);
$success = $tr->traceroute();
alarm(0);
};
# Error or timeout
return 0 if ($@);
# Traceroute was not successful
return 0 if ($tr->hops < 2 || $success == 0);
my $hopstotal = $tr->hops;
$hopstotal--;
# Run all list of parents until find a known parent
my $parent_addr;
my $parent_addr_check;
for (my $ax=$hopstotal; $ax >= 0; $ax--){
$parent_addr = $tr->hop_query_host($ax, 0);
$parent_addr_check = get_addr_id ($dbh, $parent_addr);
if ($parent_addr_check != -1){
return get_agent_from_addr ($dbh, $parent_addr);
}
}
return 0;
}
1;
__END__