367 lines
12 KiB
Perl
Executable File
367 lines
12 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
# (c) Pandora FMS 2014-2023 <info@pandorafms.com>
|
|
# WMI Recon script.
|
|
|
|
use IO::Socket::INET;
|
|
use POSIX qw(setsid strftime strftime ceil);
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
# Default lib dir for RPM and DEB packages
|
|
BEGIN { push @INC, '/usr/lib/perl5'; }
|
|
|
|
use PandoraFMS::Tools;
|
|
use PandoraFMS::DB;
|
|
use PandoraFMS::Core;
|
|
use PandoraFMS::Config;
|
|
use PandoraFMS::NmapParser;
|
|
|
|
# Pandora FMS configuration hash.
|
|
my $OSNAME = $^O;
|
|
my %CONF;
|
|
|
|
if ($OSNAME eq "freebsd") {
|
|
%CONF = ('quiet' => 0,
|
|
'verbosity' => 1,
|
|
'daemon' => 0,
|
|
'PID' => '',
|
|
'pandora_path' => '/usr/local/etc/pandora/pandora_server.conf',
|
|
'networktimeout' => 2,
|
|
'icmp_checks' => 1,
|
|
'nmap_timing_template' => 2,
|
|
'wmi_client' => '/usr/local/bin/wmic');
|
|
} else {
|
|
%CONF = ('quiet' => 0,
|
|
'verbosity' => 1,
|
|
'daemon' => 0,
|
|
'PID' => '',
|
|
'pandora_path' => '/etc/pandora/pandora_server.conf',
|
|
'networktimeout' => 2,
|
|
'icmp_checks' => 1,
|
|
'nmap_timing_template' => 2,
|
|
'wmi_client' => '/usr/bin/wmic');
|
|
}
|
|
|
|
# If set to 1 incidents will be created in the Pandora FMS Console.
|
|
my $CREATE_INCIDENT;
|
|
|
|
# Database connection handler.
|
|
my $DBH;
|
|
|
|
# ID of the group where new agents will be placed.
|
|
my $GROUP_ID;
|
|
|
|
# Comma separated list of target networks.
|
|
my $NETWORKS;
|
|
|
|
# ID of the recon task.
|
|
my $TASK_ID;
|
|
|
|
# Comma separated list of username%password tokens.
|
|
my $WMI_AUTH;
|
|
|
|
##########################################################################
|
|
# 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);
|
|
}
|
|
|
|
##########################################################################
|
|
# Show help
|
|
##########################################################################
|
|
sub show_help {
|
|
print "\nPandora FMS WMI Recon Script.\n";
|
|
print "(c) Pandora FMS 2014-2023 <info\@pandorafms.com>\n\n";
|
|
print "Usage:\n\n";
|
|
print " $0 <task_id> <group_id> <create_incident_flag> <network> <wmi auth>\n\n";
|
|
print " * network: network to scan (e.g. 192.168.100.0/24)\n";
|
|
print " * wmi auth: comma separated list of WMI authentication tokens in the format username%password (e.g. Administrador%pass)\n";
|
|
print "\n The other parameters are automatically filled by the Pandora FMS Server.\n\n\n";
|
|
exit;
|
|
}
|
|
|
|
##########################################################################
|
|
# Get SNMP response.
|
|
##########################################################################
|
|
sub get_snmp_response($$$) {
|
|
my ($target_timeout, $target_community, $addr) = @_;
|
|
|
|
# The OID used is the SysUptime OID
|
|
my $buffer = `/usr/bin/snmpget -v 1 -r0 -t$target_timeout -OUevqt -c '$target_community' $addr .1.3.6.1.2.1.1.3.0 2>/dev/null`;
|
|
|
|
# Remove forbidden caracters
|
|
$buffer =~ s/\l|\r|\"|\n|\<|\>|\&|\[|\]//g;
|
|
|
|
return $buffer;
|
|
}
|
|
|
|
##########################################################################
|
|
# Scan target networks for hosts and execute the given function on each
|
|
# host.
|
|
##########################################################################
|
|
my @ADDED_HOSTS;
|
|
sub recon_scan($$) {
|
|
my ($task, $function) = @_;
|
|
|
|
# Timeout in ms.
|
|
my $timeout = $CONF{'networktimeout'} * 1000;
|
|
|
|
# Added -PE to make nmap behave like ping and avoid confusion if ICMP traffic is blocked.
|
|
my $nmap_args = '-nsP -PE --max-retries ' . $CONF{'icmp_checks'} . ' --host-timeout '.$timeout.' -T'.$CONF{'nmap_timing_template'};
|
|
|
|
# Scan the network.
|
|
my $np = new PandoraFMS::NmapParser;
|
|
$np->parsescan($CONF{'nmap'}, $nmap_args, split(',', $task->{'subnet'}));
|
|
|
|
my @up_hosts = $np->all_hosts ('up');
|
|
my $total_up = scalar (@up_hosts);
|
|
my $progress = 0;
|
|
foreach my $host (@up_hosts) {
|
|
$progress++;
|
|
|
|
# Update the recon task or break if it does not exist anymore.
|
|
last if (update_recon_task ($DBH, $task->{'id_rt'}, ceil ($progress / ($total_up / 101))) eq '0E0');
|
|
|
|
# Get the host address.
|
|
my $addr = $host->addr();
|
|
next unless ($addr ne '0');
|
|
|
|
# Execute the given function on the agent.
|
|
$function->($task, $addr);
|
|
}
|
|
|
|
# Mark the recon task as done.
|
|
update_recon_task ($DBH, $task->{'id_rt'}, -1);
|
|
}
|
|
|
|
##########################################################################
|
|
# Create a Pandora FMS agent for the given address if it does not exist.
|
|
##########################################################################
|
|
sub create_pandora_agent($$) {
|
|
my ($task, $addr) = @_;
|
|
|
|
# Does the agent already exist?
|
|
my $agent = get_agent_from_addr ($DBH, $addr);
|
|
if (! defined ($agent)) {
|
|
$agent = get_agent_from_name ($DBH, $addr);
|
|
}
|
|
|
|
# Create the agent.
|
|
my $agent_id = defined($agent) ? $agent->{'id_agente'} : 0;
|
|
if ($agent_id <= 0) {
|
|
$agent_id = pandora_create_agent(\%CONF, $CONF{'servername'}, $addr, $addr, $GROUP_ID, 0, 9, '', 300, $DBH, 0);
|
|
if ($agent_id <= 0) {
|
|
logger(\%CONF, "Error creating agent '$addr'.", 3);
|
|
return undef;
|
|
}
|
|
|
|
# Generate an event
|
|
pandora_event (\%CONF, "[WMI RECON SCRIPT] New host [$addr] detected on network [" . $task->{'subnet'} . ']', $GROUP_ID, $agent_id, 2, 0, 0, 'recon_host_detected', 0, $DBH);
|
|
|
|
push(@ADDED_HOSTS, $addr);
|
|
|
|
# Get the created agent.
|
|
$agent = get_agent_from_name ($DBH, $addr);
|
|
return undef unless defined($agent);
|
|
}
|
|
|
|
# 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(\%CONF, "Could not add address '$addr' for host '$addr'.", 3);
|
|
return $agent;
|
|
}
|
|
|
|
# Assign the new address to the agent
|
|
my $agent_addr_id = get_agent_addr_id ($DBH, $addr_id, $agent_id);
|
|
if ($agent_addr_id <= 0) {
|
|
db_do ($DBH, 'INSERT INTO taddress_agent (id_a, id_agent) VALUES (?, ?)', $addr_id, $agent_id);
|
|
}
|
|
|
|
return $agent;
|
|
}
|
|
|
|
|
|
########################################################################################
|
|
# Returns the credentials with which the host responds to WMI queries or undef if it
|
|
# does not respond to WMI.
|
|
########################################################################################
|
|
sub responds_to_wmi($) {
|
|
my ($target) = @_;
|
|
|
|
my @auth_array = defined($WMI_AUTH) ? split(',', $WMI_AUTH) : ('');
|
|
foreach my $auth (@auth_array) {
|
|
my @output;
|
|
if ($auth ne '') {
|
|
@output = `$CONF{'wmi_client'} -U $auth //$target "SELECT * FROM Win32_ComputerSystem" 2>&1`;
|
|
}
|
|
else {
|
|
@output = `$CONF{'wmi_client'} -N //$target "SELECT * FROM Win32_ComputerSystem" 2>&1`;
|
|
}
|
|
|
|
foreach my $line (@output) {
|
|
chomp ($line);
|
|
return $auth if ($line =~ m/^CLASS: Win32_ComputerSystem$/);
|
|
}
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
########################################################################################
|
|
# Performs a wmi get requests and returns the response as an array.
|
|
########################################################################################
|
|
sub wmi_get($$$) {
|
|
my ($target, $auth, $query) = @_;
|
|
|
|
my @output;
|
|
if (defined($auth) && $auth ne '') {
|
|
@output = `$CONF{'wmi_client'} -U $auth //$target "$query" 2>&1`;
|
|
}
|
|
else {
|
|
@output = `$CONF{'wmi_client'} -N //$target "$query" 2>&1`;
|
|
}
|
|
|
|
# Something went wrong.
|
|
return () if ($? != 0);
|
|
|
|
return @output;
|
|
}
|
|
|
|
########################################################################################
|
|
# Performs a WMI request and returns the requested column of the first row. Returns
|
|
# undef on error.
|
|
########################################################################################
|
|
sub wmi_get_value($$$$) {
|
|
my ($target, $auth, $query, $column) = @_;
|
|
my @result;
|
|
|
|
my @output = wmi_get($target, $auth, $query);
|
|
return undef unless defined($output[2]);
|
|
|
|
my $line = $output[2];
|
|
chomp($line);
|
|
my @columns = split(/\|/, $line);
|
|
return undef unless defined($columns[$column]);
|
|
|
|
return $columns[$column];
|
|
}
|
|
|
|
########################################################################################
|
|
# Performs a WMI request and returns row values for the requested column in an array.
|
|
########################################################################################
|
|
sub wmi_get_value_array($$$$) {
|
|
my ($target, $auth, $query, $column) = @_;
|
|
my @result;
|
|
|
|
my @output = wmi_get($target, $auth, $query);
|
|
foreach (my $i = 2; defined($output[$i]); $i++) {
|
|
my $line = $output[$i];
|
|
chomp($line);
|
|
my @columns = split(/\|/, $line);
|
|
next unless defined($columns[$column]);
|
|
push(@result, $columns[$column]);
|
|
}
|
|
|
|
return @result;
|
|
}
|
|
|
|
##########################################################################
|
|
# Create a WMI module for the given agent.
|
|
##########################################################################
|
|
sub wmi_module($$$$$$$$;$) {
|
|
my ($agent_id, $target, $wmi_query, $wmi_auth,
|
|
$column, $module_name, $module_description, $module_type,
|
|
$unit) = @_;
|
|
|
|
# Check whether the module already exists.
|
|
my $module_id = get_agent_module_id($DBH, $module_name, $agent_id);
|
|
return if ($module_id > 0);
|
|
|
|
my ($user, $pass) = ($wmi_auth ne '') ? split('%', $wmi_auth) : (undef, undef);
|
|
my %module = ('descripcion' => safe_input($module_description),
|
|
'id_agente' => $agent_id,
|
|
'id_modulo' => 6,
|
|
'id_tipo_modulo' => get_module_id($DBH, $module_type),
|
|
'ip_target' => $target,
|
|
'nombre' => safe_input($module_name),
|
|
'plugin_pass' => defined($pass) ? $pass : '',
|
|
'plugin_user' => defined($user) ? $user : '',
|
|
'snmp_oid' => $wmi_query,
|
|
'tcp_port' => $column,
|
|
'unit' => defined($unit) ? $unit : '');
|
|
pandora_create_module_from_hash(\%CONF, \%module, $DBH);
|
|
}
|
|
|
|
##########################################################################
|
|
# Add wmi modules to the given host.
|
|
##########################################################################
|
|
sub wmi_scan() {
|
|
my ($task, $target) = @_;
|
|
|
|
my $auth = responds_to_wmi($target);
|
|
return unless defined($auth);
|
|
|
|
# Create the agent if it does not exist.
|
|
my $agent = create_pandora_agent($task, $target);
|
|
next unless defined($agent);
|
|
|
|
# CPU.
|
|
my @cpus = wmi_get_value_array($target, $auth, 'SELECT DeviceId FROM Win32_Processor', 0);
|
|
foreach my $cpu (@cpus) {
|
|
wmi_module($agent->{'id_agente'}, $target, "SELECT LoadPercentage FROM Win32_Processor WHERE DeviceId='$cpu'", $auth, 1, "CPU Load $cpu", "Load for $cpu (%)", 'generic_data');
|
|
}
|
|
|
|
# Memory.
|
|
my $mem = wmi_get_value($target, $auth, 'SELECT FreePhysicalMemory FROM Win32_OperatingSystem', 0);
|
|
if (defined($mem)) {
|
|
wmi_module($agent->{'id_agente'}, $target, "SELECT FreePhysicalMemory, TotalVisibleMemorySize FROM Win32_OperatingSystem", $auth, 0, 'FreeMemory', 'Free memory', 'generic_data', 'KB');
|
|
}
|
|
|
|
# Disk.
|
|
my @units = wmi_get_value_array($target, $auth, 'SELECT DeviceID FROM Win32_LogicalDisk', 0);
|
|
foreach my $unit (@units) {
|
|
wmi_module($agent->{'id_agente'}, $target, "SELECT FreeSpace FROM Win32_LogicalDisk WHERE DeviceID='$unit'", $auth, 1, "FreeDisk $unit", 'Available disk space in kilobytes', 'generic_data', 'KB');
|
|
}
|
|
}
|
|
|
|
##########################################################################
|
|
##########################################################################
|
|
## Main.
|
|
##########################################################################
|
|
##########################################################################
|
|
if ($#ARGV < 4){
|
|
show_help();
|
|
}
|
|
|
|
# Passed by the server.
|
|
$TASK_ID = $ARGV[0];
|
|
$GROUP_ID = $ARGV[1];
|
|
$CREATE_INCIDENT = $ARGV[2];
|
|
|
|
# User defined parameters.
|
|
$NETWORKS = $ARGV[3];
|
|
$WMI_AUTH = $ARGV[4];
|
|
|
|
# Read the configuration file.
|
|
pandora_load_config(\%CONF);
|
|
pandora_start_log(\%CONF);
|
|
|
|
# Connect to the DB.
|
|
$DBH = db_connect ($CONF{'dbengine'}, $CONF{'dbname'}, $CONF{'dbhost'}, $CONF{'dbport'}, $CONF{'dbuser'}, $CONF{'dbpass'});
|
|
|
|
# Get the recon task from the database.
|
|
my $task = get_db_single_row ($DBH, 'SELECT * FROM trecon_task WHERE id_rt = ?', $TASK_ID);
|
|
die("Error retrieving recon task ID $TASK_ID\n") unless defined($task);
|
|
|
|
# Scan!
|
|
$task->{'subnet'} = $NETWORKS;
|
|
$task->{'id_group'} = $GROUP_ID;
|
|
recon_scan($task, \&wmi_scan);
|
|
|