Merge branch 'develop' of brutus.artica.es:artica/pandorafms into ent-5314-autodiscovery-2

This commit is contained in:
Kevin 2020-04-21 13:10:43 +02:00
commit 8801b8a032
37 changed files with 612 additions and 78 deletions

View File

@ -1,5 +1,5 @@
package: pandorafms-agent-unix package: pandorafms-agent-unix
Version: 7.0NG.744-200414 Version: 7.0NG.744-200421
Architecture: all Architecture: all
Priority: optional Priority: optional
Section: admin Section: admin

View File

@ -14,7 +14,7 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
pandora_version="7.0NG.744-200414" pandora_version="7.0NG.744-200421"
echo "Test if you has the tools for to make the packages." echo "Test if you has the tools for to make the packages."
whereis dpkg-deb | cut -d":" -f2 | grep dpkg-deb > /dev/null whereis dpkg-deb | cut -d":" -f2 | grep dpkg-deb > /dev/null

View File

@ -254,6 +254,9 @@ module_plugin pandora_mem_used
module_plugin pandora_netusage module_plugin pandora_netusage
# Service autodiscovery plugin
module_plugin autodiscover --default
# Plugin for inventory on the agent (Only Enterprise) # Plugin for inventory on the agent (Only Enterprise)
#module_plugin inventory 1 cpu ram video nic hd cdrom software init_services filesystem users route #module_plugin inventory 1 cpu ram video nic hd cdrom software init_services filesystem users route

View File

@ -55,7 +55,7 @@ my $Sem = undef;
my $ThreadSem = undef; my $ThreadSem = undef;
use constant AGENT_VERSION => '7.0NG.744'; use constant AGENT_VERSION => '7.0NG.744';
use constant AGENT_BUILD => '200414'; use constant AGENT_BUILD => '200421';
# Agent log default file size maximum and instances # Agent log default file size maximum and instances
use constant DEFAULT_MAX_LOG_SIZE => 600000; use constant DEFAULT_MAX_LOG_SIZE => 600000;

View File

@ -3,7 +3,7 @@
# #
%define name pandorafms_agent_unix %define name pandorafms_agent_unix
%define version 7.0NG.744 %define version 7.0NG.744
%define release 200414 %define release 200421
Summary: Pandora FMS Linux agent, PERL version Summary: Pandora FMS Linux agent, PERL version
Name: %{name} Name: %{name}

View File

@ -3,7 +3,7 @@
# #
%define name pandorafms_agent_unix %define name pandorafms_agent_unix
%define version 7.0NG.744 %define version 7.0NG.744
%define release 200414 %define release 200421
Summary: Pandora FMS Linux agent, PERL version Summary: Pandora FMS Linux agent, PERL version
Name: %{name} Name: %{name}

View File

@ -10,7 +10,7 @@
# ********************************************************************** # **********************************************************************
PI_VERSION="7.0NG.744" PI_VERSION="7.0NG.744"
PI_BUILD="200414" PI_BUILD="200421"
OS_NAME=`uname -s` OS_NAME=`uname -s`
FORCE=0 FORCE=0

View File

@ -245,6 +245,10 @@ module_plugin cscript.exe //B "%ProgramFiles%\Pandora_Agent\util\network.vbs"
#module_crontab * 12-15 * * 1 #module_crontab * 12-15 * * 1
#module_end #module_end
# Service autodiscovery plugin
module_plugin "%PROGRAMFILES%\Pandora_Agent\util\autodiscover.exe" --default
######################################### #########################################
# EXAMPLES # # EXAMPLES #
######################################### #########################################

View File

@ -186,7 +186,7 @@ UpgradeApplicationID
{} {}
Version Version
{200414} {200421}
ViewReadme ViewReadme
{Yes} {Yes}

View File

@ -30,7 +30,7 @@ using namespace Pandora;
using namespace Pandora_Strutils; using namespace Pandora_Strutils;
#define PATH_SIZE _MAX_PATH+1 #define PATH_SIZE _MAX_PATH+1
#define PANDORA_VERSION ("7.0NG.744(Build 200414)") #define PANDORA_VERSION ("7.0NG.744(Build 200421)")
string pandora_path; string pandora_path;
string pandora_dir; string pandora_dir;

View File

@ -11,7 +11,7 @@ BEGIN
VALUE "LegalCopyright", "Artica ST" VALUE "LegalCopyright", "Artica ST"
VALUE "OriginalFilename", "PandoraAgent.exe" VALUE "OriginalFilename", "PandoraAgent.exe"
VALUE "ProductName", "Pandora FMS Windows Agent" VALUE "ProductName", "Pandora FMS Windows Agent"
VALUE "ProductVersion", "(7.0NG.744(Build 200414))" VALUE "ProductVersion", "(7.0NG.744(Build 200421))"
VALUE "FileVersion", "1.0.0.0" VALUE "FileVersion", "1.0.0.0"
END END
END END

View File

@ -1,5 +1,5 @@
package: pandorafms-console package: pandorafms-console
Version: 7.0NG.744-200414 Version: 7.0NG.744-200421
Architecture: all Architecture: all
Priority: optional Priority: optional
Section: admin Section: admin

View File

@ -14,7 +14,7 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
pandora_version="7.0NG.744-200414" pandora_version="7.0NG.744-200421"
package_pear=0 package_pear=0
package_pandora=1 package_pandora=1

View File

@ -917,9 +917,11 @@ if ($get_response) {
if ($perform_event_response) { if ($perform_event_response) {
global $config; global $config;
$command = get_parameter('target', '');
$response_id = get_parameter('response_id'); $response_id = get_parameter('response_id');
$event_id = (int) get_parameter('event_id');
$server_id = (int) get_parameter('server_id', 0);
$command = events_get_response_target($event_id, $response_id, $server_id);
$event_response = db_get_row('tevent_response', 'id', $response_id); $event_response = db_get_row('tevent_response', 'id', $response_id);
@ -1017,6 +1019,7 @@ if ($dialogue_event_response) {
$show_execute_again_btn = get_parameter('show_execute_again_btn'); $show_execute_again_btn = get_parameter('show_execute_again_btn');
$out_iterator = get_parameter('out_iterator'); $out_iterator = get_parameter('out_iterator');
$event_response = db_get_row('tevent_response', 'id', $response_id); $event_response = db_get_row('tevent_response', 'id', $response_id);
$server_id = get_parameter('server_id');
$event = db_get_row('tevento', 'id_evento', $event_id); $event = db_get_row('tevento', 'id_evento', $event_id);
@ -1067,7 +1070,8 @@ if ($dialogue_event_response) {
echo "<br><div id='response_out' style='text-align:left'></div>"; echo "<br><div id='response_out' style='text-align:left'></div>";
echo "<br><div id='re_exec_command' style='display:none;'>"; echo "<br><div id='re_exec_command' style='display:none;'>";
html_print_button(__('Execute again'), 'btn_str', false, 'perform_response(\''.$command.'\', '.$response_id.');', "class='sub next'"); html_print_button(__('Execute again'), 'btn_str', false, "perform_response({'target':'".$command."','event_id':".$event_id.",'server_id':".$server_id.'}, '.$response_id.');', "class='sub next'");
echo '</div>'; echo '</div>';
} }
break; break;

View File

@ -20,7 +20,7 @@
/** /**
* Pandora build version and version * Pandora build version and version
*/ */
$build_version = 'PC200414'; $build_version = 'PC200421';
$pandora_version = 'v7.0NG.744'; $pandora_version = 'v7.0NG.744';
// Do not overwrite default timezone set if defined. // Do not overwrite default timezone set if defined.

View File

@ -118,30 +118,26 @@ function execute_response(event_id, server_id) {
} }
response["target"] = get_response_target(event_id, response_id, server_id); response["target"] = get_response_target(event_id, response_id, server_id);
response["event_id"] = event_id;
response["server_id"] = server_id;
switch (response["type"]) { if (response["type"] == "url" && response["new_window"] == 1) {
case "command": window.open(response["target"], "_blank");
show_response_dialog(event_id, response_id, response); } else {
break; show_response_dialog(response_id, response);
case "url":
if (response["new_window"] == 1) {
window.open(response["target"], "_blank");
} else {
show_response_dialog(event_id, response_id, response);
}
break;
} }
} }
//Show the modal window of an event response //Show the modal window of an event response
function show_response_dialog(event_id, response_id, response) { function show_response_dialog(response_id, response) {
var params = []; var params = [];
params.push("page=include/ajax/events"); params.push("page=include/ajax/events");
params.push("dialogue_event_response=1"); params.push("dialogue_event_response=1");
params.push("massive=0"); params.push("massive=0");
params.push("event_id=" + event_id); params.push("event_id=" + response["event_id"]);
params.push("target=" + response["target"]); params.push("target=" + response["target"]);
params.push("response_id=" + response_id); params.push("response_id=" + response_id);
params.push("server_id=" + response["server_id"]);
jQuery.ajax({ jQuery.ajax({
data: params.join("&"), data: params.join("&"),
@ -159,7 +155,7 @@ function show_response_dialog(event_id, response_id, response) {
draggable: true, draggable: true,
modal: false, modal: false,
open: function() { open: function() {
perform_response(response["target"], response_id); perform_response(response, response_id);
}, },
width: response["modal_width"], width: response["modal_width"],
height: response["modal_height"] height: response["modal_height"]
@ -171,7 +167,6 @@ function show_response_dialog(event_id, response_id, response) {
//Show the modal window of event responses when multiple events are selected //Show the modal window of event responses when multiple events are selected
function show_massive_response_dialog( function show_massive_response_dialog(
event_id,
response_id, response_id,
response, response,
out_iterator, out_iterator,
@ -183,13 +178,14 @@ function show_massive_response_dialog(
params.push("massive=1"); params.push("massive=1");
params.push("end=" + end); params.push("end=" + end);
params.push("out_iterator=" + out_iterator); params.push("out_iterator=" + out_iterator);
params.push("event_id=" + event_id); params.push("event_id=" + response["event_id"]);
params.push("target=" + response["target"]); params.push("target=" + response["target"]);
params.push("response_id=" + response_id); params.push("response_id=" + response_id);
params.push("server_id=" + response["server_id"]);
jQuery.ajax({ jQuery.ajax({
data: params.join("&"), data: params.join("&"),
response_tg: response["target"], response_tg: response,
response_id: response_id, response_id: response_id,
out_iterator: out_iterator, out_iterator: out_iterator,
type: "POST", type: "POST",
@ -384,7 +380,7 @@ function get_response_target(
} }
// Perform a response and put the output into a div // Perform a response and put the output into a div
function perform_response(target, response_id) { function perform_response(response, response_id) {
$("#re_exec_command").hide(); $("#re_exec_command").hide();
$("#response_loading_command").show(); $("#response_loading_command").show();
$("#response_out").html(""); $("#response_out").html("");
@ -392,8 +388,10 @@ function perform_response(target, response_id) {
var params = []; var params = [];
params.push("page=include/ajax/events"); params.push("page=include/ajax/events");
params.push("perform_event_response=1"); params.push("perform_event_response=1");
params.push("target=" + target); params.push("target=" + response["target"]);
params.push("response_id=" + response_id); params.push("response_id=" + response_id);
params.push("event_id=" + response["event_id"]);
params.push("server_id=" + response["server_id"]);
jQuery.ajax({ jQuery.ajax({
data: params.join("&"), data: params.join("&"),
@ -413,7 +411,7 @@ function perform_response(target, response_id) {
} }
// Perform a response and put the output into a div // Perform a response and put the output into a div
function perform_response_massive(target, response_id, out_iterator) { function perform_response_massive(response, response_id, out_iterator) {
$("#re_exec_command").hide(); $("#re_exec_command").hide();
$("#response_loading_command_" + out_iterator).show(); $("#response_loading_command_" + out_iterator).show();
$("#response_out_" + out_iterator).html(""); $("#response_out_" + out_iterator).html("");
@ -421,8 +419,10 @@ function perform_response_massive(target, response_id, out_iterator) {
var params = []; var params = [];
params.push("page=include/ajax/events"); params.push("page=include/ajax/events");
params.push("perform_event_response=1"); params.push("perform_event_response=1");
params.push("target=" + target); params.push("target=" + response["target"]);
params.push("response_id=" + response_id); params.push("response_id=" + response_id);
params.push("event_id=" + response["event_id"]);
params.push("server_id=" + response["server_id"]);
jQuery.ajax({ jQuery.ajax({
data: params.join("&"), data: params.join("&"),
@ -916,17 +916,24 @@ function check_massive_response_event(
$(".chk_val:checked").each(function() { $(".chk_val:checked").each(function() {
var event_id = $(this).val(); var event_id = $(this).val();
var server_id = $("#hidden-server_id_" + event_id).val(); var meta = $("#hidden-meta").val();
var server_id = 0;
if (meta) {
server_id = $("#hidden-server_id_" + event_id).val();
}
response["target"] = get_response_target( response["target"] = get_response_target(
event_id, event_id,
response_id, response_id,
server_id, server_id,
response_command response_command
); );
response["server_id"] = server_id;
response["event_id"] = event_id;
if (total_checked - 1 === counter) end = 1; if (total_checked - 1 === counter) end = 1;
show_massive_response_dialog(event_id, response_id, response, counter, end); show_massive_response_dialog(response_id, response, counter, end);
counter++; counter++;
}); });

View File

@ -110,3 +110,12 @@ span.link.review {
padding-top: 2em; padding-top: 2em;
padding-left: 6.7em; padding-left: 6.7em;
} }
#review .sim-tree li.disabled > a .sim-tree-checkbox {
border-color: #eee;
background-color: #ddd;
}
#task_review {
width: 100% !important;
}

View File

@ -42,6 +42,10 @@ ul.wizard li > textarea {
justify-content: space-between; justify-content: space-between;
} }
.std_input.hidden {
display: none;
}
.wizard .std_input > .label_select { .wizard .std_input > .label_select {
flex: 1 1 100%; flex: 1 1 100%;
} }

View File

@ -129,7 +129,7 @@
<div style='height: 10px'> <div style='height: 10px'>
<?php <?php
$version = '7.0NG.744'; $version = '7.0NG.744';
$build = '200414'; $build = '200421';
$banner = "v$version Build $build"; $banner = "v$version Build $build";
error_reporting(0); error_reporting(0);

View File

@ -1119,12 +1119,13 @@ if ($group_rep == 2) {
server_id, server_id,
response_command response_command
); );
response["server_id"] = server_id;
response["event_id"] = event_id;
if (total_checked-1 === counter) if (total_checked-1 === counter)
end=1; end=1;
show_massive_response_dialog( show_massive_response_dialog(
event_id,
response_id, response_id,
response, response,
counter, counter,

View File

@ -1765,6 +1765,9 @@ function process_datatables_item(item) {
evn += '('+item.event_rep+') '; evn += '('+item.event_rep+') ';
} }
evn += item.evento+'</a>'; evn += item.evento+'</a>';
if(item.meta === true) {
evn += '<input id="hidden-server_id_'+item.id_evento+'" type="hidden" value="'+item.server_id+'">';
}
item.mini_severity = '<div class="event flex-row h100p nowrap">'; item.mini_severity = '<div class="event flex-row h100p nowrap">';
item.mini_severity += output; item.mini_severity += output;

View File

@ -3,7 +3,7 @@
# #
%define name pandorafms_console %define name pandorafms_console
%define version 7.0NG.744 %define version 7.0NG.744
%define release 200414 %define release 200421
# User and Group under which Apache is running # User and Group under which Apache is running
%define httpd_name httpd %define httpd_name httpd

View File

@ -3,7 +3,7 @@
# #
%define name pandorafms_console %define name pandorafms_console
%define version 7.0NG.744 %define version 7.0NG.744
%define release 200414 %define release 200421
# User and Group under which Apache is running # User and Group under which Apache is running
%define httpd_name httpd %define httpd_name httpd

View File

@ -3,7 +3,7 @@
# #
%define name pandorafms_console %define name pandorafms_console
%define version 7.0NG.744 %define version 7.0NG.744
%define release 200414 %define release 200421
%define httpd_name httpd %define httpd_name httpd
# User and Group under which Apache is running # User and Group under which Apache is running
%define httpd_name apache2 %define httpd_name apache2

View File

@ -0,0 +1,398 @@
#!/usr/bin/env python3
###################################################
#
# Pandora FMS Autodiscovery plugin.
# Checks the status of the services in list and monitors CPU and Memory for each of them.
#
# (c) A. Kevin Rojas <kevin.rojas@pandorafms.com>
#
# TO DO LIST:
# - Enable child services detection (Windows)
# - Make CPU/Memory usage available for child services (Windows)
#
###################################################
from sys import argv, path, stderr, exit
import psutil
from subprocess import *
global module_list
module_list = []
#########################################################################################
# Powershell class
#########################################################################################
class PSCheck:
@staticmethod
def check_service(servicename, option=False, memcpu=False):
"""Check services with powershell by parsing their DisplayName. Returns a dict\
list with the name of the service and a boolean with its status.\n
Requires service name (case insensitive)."""
pscall = Popen(["powershell", "Get-Service", "-Name", "'*"+ str(servicename) + "*'",
"|", "Select-Object", "-ExpandProperty", "Name"],
stdout=PIPE, stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True)
result = pscall.communicate()
result = str(result[0]).strip().split("\n")
procname = ''
if result != '':
output = []
for element in result:
if element != '':
# Get process name
procname = PSCheck.get_serviceprocess(element)
# Get process status
parstatus = PSCheck.getstatus(element)
if memcpu == True and parstatus == 1:
usage = get_memcpu(str(procname), str(element))
output += usage
# Generate module with name and status
parent = service_module(str(element), parstatus)
output += parent
if option == True:
children = PSCheck.getchildren(element, memcpu)
if type(children) == list and len(children) > 1:
for child in children:
output += child
else:
output += children
else:
next
#if output != '':
if output and element and procname:
return ({"name" : element, "process" : procname, "modules": output})
else:
return (None)
@staticmethod
def getchildren(servicename, memcpu=False):
"""Gets Dependent services of a given Windows service"""
pschild = Popen(["powershell", "Get-Service", "-Name '" + str(servicename) +
"' -DS", "|", "Select-Object", "-ExpandProperty", "Name"],
stdout=PIPE, stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True)
children = pschild.communicate()[0].strip()
kids = []
for child in (children.split("\n") if children != "" else []):
status = PSCheck.getstatus(child)
kids += service_module(str(child), status, "Service " + str(servicename) + " - Status")
if status:
if memcpu == True:
kidsusage = get_memcpu(str(child))
for usage in kidsusage:
kids += usage
else:
next
return (kids)
@staticmethod
def getstatus(servicename):
"""Gets the status of a given Windows service"""
running = Popen(["powershell", "Get-Service", "-Name '" + str(servicename) +
"' |", "Select-Object", "-ExpandProperty", "Status"],
stdout=PIPE, stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True)
status = running.communicate()[0].strip()
return (int(status == "Running"))
@staticmethod
def get_serviceprocess(servicename):
"""Gets name of the process of the service"""
service = psutil.win_service_get(servicename)
srv_pid = service.pid()
process = psutil.Process(srv_pid)
proc_name = process.name()
return (proc_name)
#########################################################################################
# Services creation
#########################################################################################
def service_module(name, value, parent=None):
#print ("service_module BEGIN "+str(now(0,1)))
module = [{
"name" : "Service "+ name + " - Status",
"type" : "generic_proc",
"value" : value,
"module_parent" : parent,
}]
#print ("service_module END "+str(now(0,1)))
return (module)
def get_memcpu (process, servicename):
"""Creates a module for Memory and CPU for a given process. Returns a list of dictionaries."""
modules = []
if process:
if servicename != None:
parentname = servicename
else:
parentname = process
modules += [{
"name" : "Service "+ process + " - Memory usage",
"type" : "generic_data",
"value" : proc_percentbyname(process)[0],
"unit" : "%",
"module_parent" : "Service "+ parentname + " - Status",
},
{"name" : "Service "+ process + " - CPU usage",
"type" : "generic_data",
"value" : proc_percentbyname(process)[1],
"unit" : "%",
"module_parent" : "Service "+ parentname + " - Status",
}]
return (modules)
def proc_percentbyname(procname): ############# 03/03/2020
"""Gets Memory and CPU usage for a given process. Returns a list."""
#print ("proc_percentbyname BEGIN "+str(now(0,1)))
procs = [p for p in psutil.process_iter() if procname in p.name().lower()]
memory = []
cpu = []
try:
for proc in procs:
if proc.name() == procname:
cpu.append(proc.cpu_percent(interval=0.5))
memory.append(proc.memory_percent())
else:
next
except psutil.NoSuchProcess:
next
#print ("proc_percentbyname END "+str(now(0,1)))
return ([sum(memory),sum(cpu)])
def win_service(servicelist, option=False, memcpu=False):
"""Creates modules for Windows servers."""
modules = []
for srvc in servicelist:
if srvc and len(srvc) > 2:
output = PSCheck.check_service(srvc, option, memcpu)
if output != None and output["modules"]:
modules += PSCheck.check_service(srvc.strip(), option, memcpu)["modules"]
module_list.append(srvc)
winprocess = output["name"]
#if memcpu == True:
# modules += get_memcpu(winprocess) ## Only available for parent service ATM.
else:
next
else:
next
for module in modules:
print_module(module, 1)
def lnx_service(services_list, memcpu=False):
"""Creates modules for Linux servers"""
modules = []
sysctl = getstatusoutput("command -v systemctl")[0]
servic = getstatusoutput("command -v service")[0]
for srvc in services_list:
status = None
if sysctl == 0:
### Systemd available
syscall = Popen(["systemctl", "is-active", srvc], stdout=PIPE,
stdin=DEVNULL, universal_newlines=True)
result = syscall.communicate()
result = result[0].strip().lower()
if result == "active":
modules += service_module(srvc, 1)
status = 1
elif result == "inactive":
modules += service_module(srvc, 0)
status = 0
elif result == "unknown":
next
elif sysctl != 0 and servic == 0:
### Systemd not available, switch to service command
syscall = Popen(["service", srvc, "status"], stdout=PIPE,
stdin=DEVNULL, stderr=DEVNULL, universal_newlines=True)
result = syscall.communicate()[0].lower()
if "is running" in result:
modules += service_module(srvc, 1)
status = 1
elif "is stopped" in result:
modules += service_module(srvc, 0)
status = 0
else:
next
else:
print ("No systemd or service commands available. Exiting...", file=stderr)
exit()
if status:
module_list.append(srvc)
if memcpu == True:
modules += get_memcpu(srvc, None)
for m in modules:
print_module (m, 1)
#########################################################################################
# print_module function
#########################################################################################
def print_module(module, str_flag=False):
"""Returns module in XML format. Accepts only {dict}.\n
+ Only works with one module at a time: otherwise iteration is needed.
+ Module "value" field accepts str type or [list] for datalists.
+ Use not_print_flag to avoid printing the XML (only populates variables).
"""
data = dict(module)
module_xml = ("<module>\n"
"\t<name><![CDATA[" + str(data["name"]) + "]]></name>\n"
"\t<type>" + str(data["type"]) + "</type>\n"
)
#### Strip spaces if module not generic_data_string
if type(data["type"]) is not str and "string" not in data["type"]:
data["value"] = data["value"].strip()
if isinstance(data["value"], list): # Checks if value is a list
module_xml += "\t<datalist>\n"
for value in data["value"]:
if type(value) is dict and "value" in value:
module_xml += "\t<data>\n"
module_xml += "\t\t<value><![CDATA[" + str(value["value"]) + "]]></value>\n"
if "timestamp" in value:
module_xml += "\t\t<timestamp><![CDATA[" + str(value["timestamp"]) + "]]></timestamp>\n"
module_xml += "\t</data>\n"
else:
module_xml += "\t<data><![CDATA[" + str(data["value"]) + "]]></data>\n"
if "desc" in data:
module_xml += "\t<description><![CDATA[" + str(data["desc"]) + "]]></description>\n"
if "unit" in data:
module_xml += "\t<unit><![CDATA[" + str(data["unit"]) + "]]></unit>\n"
if "interval" in data:
module_xml += "\t<module_interval><![CDATA[" + str(data["interval"]) + "]]></module_interval>\n"
if "tags" in data:
module_xml += "\t<tags>" + str(data["tags"]) + "</tags>\n"
if "module_group" in data:
module_xml += "\t<module_group>" + str(data["module_group"]) + "</module_group>\n"
if "module_parent" in data and data["module_parent"] != None:
module_xml += "\t<module_parent>" + str(data["module_parent"]) + "</module_parent>\n"
if "min_warning" in data:
module_xml += "\t<min_warning><![CDATA[" + str(data["min_warning"]) + "]]></min_warning>\n"
if "max_warning" in data:
module_xml += "\t<max_warning><![CDATA[" + str(data["max_warning"]) + "]]></max_warning>\n"
if "min_critical" in data:
module_xml += "\t<min_critical><![CDATA[" + str(data["min_critical"]) + "]]></min_critical>\n"
if "max_critical" in data:
module_xml += "\t<max_critical><![CDATA[" + str(data["max_critical"]) + "]]></max_critical>\n"
if "str_warning" in data:
module_xml += "\t<str_warning><![CDATA[" + str(data["str_warning"]) + "]]></str_warning>\n"
if "str_critical" in data:
module_xml += "\t<str_critical><![CDATA[" + str(data["str_critical"]) + "]]></str_critical>\n"
if "critical_inverse" in data:
module_xml += "\t<critical_inverse><![CDATA[" + str(data["critical_inverse"]) + "]]></critical_inverse>\n"
if "warning_inverse" in data:
module_xml += "\t<warning_inverse><![CDATA[" + str(data["warning_inverse"]) + "]]></warning_inverse>\n"
if "max" in data:
module_xml += "\t<max><![CDATA[" + str(data["max"]) + "]]></max>\n"
if "min" in data:
module_xml += "\t<min><![CDATA[" + str(data["min"]) + "]]></min>\n"
if "post_process" in data:
module_xml += "\t<post_process><![CDATA[" + str(data["post_process"]) + "]]></post_process>\n"
if "disabled" in data:
module_xml += "\t<disabled><![CDATA[" + str(data["disabled"]) + "]]></disabled>\n"
if "min_ff_event" in data:
module_xml += "\t<min_ff_event><![CDATA[" + str(data["min_ff_event"]) + "]]></min_ff_event>\n"
if "status" in data:
module_xml += "\t<status><![CDATA[" + str(data["status"]) + "]]></status>\n"
if "timestamp" in data:
module_xml += "\t<timestamp><![CDATA[" + str(data["timestamp"]) + "]]></timestamp>\n"
if "custom_id" in data:
module_xml += "\t<custom_id><![CDATA[" + str(data["custom_id"]) + "]]></custom_id>\n"
if "critical_instructions" in data:
module_xml += "\t<critical_instructions><![CDATA[" + str(data["critical_instructions"]) + "]]></critical_instructions>\n"
if "warning_instructions" in data:
module_xml += "\t<warning_instructions><![CDATA[" + str(data["warning_instructions"]) + "]]></warning_instructions>\n"
if "unknown_instructions" in data:
module_xml += "\t<unknown_instructions><![CDATA[" + str(data["unknown_instructions"]) + "]]></unknown_instructions>\n"
if "quiet" in data:
module_xml += "\t<quiet><![CDATA[" + str(data["quiet"]) + "]]></quiet>\n"
if "module_ff_interval" in data:
module_xml += "\t<module_ff_interval><![CDATA[" + str(data["module_ff_interval"]) + "]]></module_ff_interval>\n"
if "crontab" in data:
module_xml += "\t<crontab><![CDATA[" + str(data["crontab"]) + "]]></crontab>\n"
if "min_ff_event_normal" in data:
module_xml += "\t<min_ff_event_normal><![CDATA[" + str(data["min_ff_event_normal"]) + "]]></min_ff_event_normal>\n"
if "min_ff_event_warning" in data:
module_xml += "\t<min_ff_event_warning><![CDATA[" + str(data["min_ff_event_warning"]) + "]]></min_ff_event_warning>\n"
if "min_ff_event_critical" in data:
module_xml += "\t<min_ff_event_critical><![CDATA[" + str(data["min_ff_event_critical"]) + "]]></min_ff_event_critical>\n"
if "ff_type" in data:
module_xml += "\t<ff_type><![CDATA[" + str(data["ff_type"]) + "]]></ff_type>\n"
if "ff_timeout" in data:
module_xml += "\t<ff_timeout><![CDATA[" + str(data["ff_timeout"]) + "]]></ff_timeout>\n"
if "each_ff" in data:
module_xml += "\t<each_ff><![CDATA[" + str(data["each_ff"]) + "]]></each_ff>\n"
if "module_parent_unlink" in data:
module_xml += "\t<module_parent_unlink><![CDATA[" + str(data["parent_unlink"]) + "]]></module_parent_unlink>\n"
if "global_alerts" in data:
for alert in data["alert"]:
module_xml += "\t<alert_template><![CDATA[" + alert + "]]></alert_template>\n"
module_xml += "</module>\n"
#### Print flag
if str_flag is not False:
print (module_xml)
return (module_xml)
#########################################################################################
# MAIN
#########################################################################################
def main():
"""Checks OS and calls the discover function."""
if psutil.WINDOWS:
OS = "Windows"
service_list = ["MySQL", "postgresql", "pgsql", "oracle", "MSSQL", "IISADMIN",
"apache", "nginx", "W3svc", "NTDS", "Netlogon", "DNS", "MSExchangeADTopology",
"MSExchangeServiceHost", "MSExchangeSA", "MSExchangeTransport"]
discover(OS, service_list)
elif psutil.LINUX:
OS = "Linux"
service_list = ["httpd", "apache2", "nginx", "ldap", "docker",
"postfix", "mysqld", "postgres", "oracle", "mongod"]
discover(OS, service_list)
else:
print ("OS not recognized. Exiting...", file=stderr)
exit()
def discover(osyst, servicelist):
"""Shows help and triggers the creation of service modules"""
if "--usage" in argv:
memcpu = True
else:
memcpu = False
if len(argv) > 2 and argv[1] == "--list":
servicelist = argv[2].split(",")
if osyst == "Windows":
win_service(servicelist, False, memcpu) ## False won't get children
elif osyst == "Linux":
lnx_service(servicelist, memcpu)
elif len(argv) > 1 and argv[1] == "--default":
if osyst == "Windows":
win_service(servicelist, False, memcpu) ## False won't get children
elif osyst == "Linux":
lnx_service(servicelist, memcpu)
else:
print ("\nPandora FMS Autodiscovery plugin.")
print ("Checks the status of the services in list and monitors CPU and Memory for each of them.\n")
print ("Usage:")
print ("{} [options] [--usage]".format(argv[0]))
print ("--help")
print ("\tPrints this help screen")
print ("--default")
print ("\tRuns this tool with default monitoring.".format(argv[0]))
print ("\tServices monitored by default for {}:".format(osyst))
print ("\t",", ".join(servicelist))
print ("--list \"<srvc1,srvc2,srvc3>\"")
print ("\tReplaces default services for a given list (comma-separated)")
if osyst == "Windows":
print ("\tEach element of the list will be treated as a regexp, but they must be over 2 characters.")
print ("\tElements under 2 characters will be discarded.")
print ("--usage")
print ("\tAdds modules for CPU and Memory usage per service/process (optional, can take some time).\n")
##### RUN ####
main()

View File

@ -1,5 +1,5 @@
package: pandorafms-server package: pandorafms-server
Version: 7.0NG.744-200414 Version: 7.0NG.744-200421
Architecture: all Architecture: all
Priority: optional Priority: optional
Section: admin Section: admin

View File

@ -14,7 +14,7 @@
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details. # GNU General Public License for more details.
pandora_version="7.0NG.744-200414" pandora_version="7.0NG.744-200421"
package_cpan=0 package_cpan=0
package_pandora=1 package_pandora=1

View File

@ -45,7 +45,7 @@ our @EXPORT = qw(
# version: Defines actual version of Pandora Server for this module only # version: Defines actual version of Pandora Server for this module only
my $pandora_version = "7.0NG.744"; my $pandora_version = "7.0NG.744";
my $pandora_build = "200414"; my $pandora_build = "200421";
our $VERSION = $pandora_version." ".$pandora_build; our $VERSION = $pandora_version." ".$pandora_build;
# Setup hash # Setup hash

View File

@ -931,10 +931,12 @@ sub pandora_execute_alert ($$$$$$$$$;$$) {
$pa_config, $pa_config,
"$text (" . safe_output($alert->{'name'}) . ") " . (defined ($module) ? 'assigned to ('. safe_output($module->{'nombre'}) . ")" : ""), "$text (" . safe_output($alert->{'name'}) . ") " . (defined ($module) ? 'assigned to ('. safe_output($module->{'nombre'}) . ")" : ""),
(defined ($agent) ? $agent->{'id_grupo'} : 0), (defined ($agent) ? $agent->{'id_grupo'} : 0),
(defined ($agent) ? $agent->{'id_agente'} : 0), # id agent.
0,
$severity, $severity,
(defined ($alert->{'id_template_module'}) ? $alert->{'id_template_module'} : 0), (defined ($alert->{'id_template_module'}) ? $alert->{'id_template_module'} : 0),
(defined ($alert->{'id_agent_module'}) ? $alert->{'id_agent_module'} : 0), # id agent module.
0,
$event, $event,
0, 0,
$dbh, $dbh,
@ -4206,7 +4208,9 @@ sub on_demand_macro($$$$$$;$) {
my $unit_mod = get_db_value ($dbh, 'SELECT unit FROM tagente_modulo WHERE id_agente_modulo = ?', $id_mod); my $unit_mod = get_db_value ($dbh, 'SELECT unit FROM tagente_modulo WHERE id_agente_modulo = ?', $id_mod);
my $field_value = ""; my $field_value = "";
if ($type_mod eq 3 || $type_mod eq 23|| $type_mod eq 17 || $type_mod eq 10 || $type_mod eq 33 ){ if (defined($type_mod)
&& ($type_mod eq 3 || $type_mod eq 23|| $type_mod eq 17 || $type_mod eq 10 || $type_mod eq 33 )
) {
$field_value = get_db_value($dbh, 'SELECT datos FROM tagente_datos_string where id_agente_modulo = ? order by utimestamp desc limit 1', $id_mod); $field_value = get_db_value($dbh, 'SELECT datos FROM tagente_datos_string where id_agente_modulo = ? order by utimestamp desc limit 1', $id_mod);
} }
else{ else{

View File

@ -1019,35 +1019,63 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
# Agent creation. # Agent creation.
my $agent_id = $data->{'agent'}{'agent_id'}; my $agent_id = $data->{'agent'}{'agent_id'};
my $agent_learning; my $agent_learning;
my $agent_data;
if (defined($agent_id) && $agent_id > 0) { if (defined($agent_id) && $agent_id > 0) {
$agent_learning = get_db_value( $agent_data = get_db_single_row(
$self->{'dbh'}, $self->{'dbh'},
'SELECT modo FROM tagente WHERE id_agente = ?', 'SELECT * FROM tagente WHERE id_agente = ?',
$agent_id $agent_id
); );
$agent_learning = $agent_data->{'modo'} if ref($agent_data) eq 'HASH';
} }
if (!defined($agent_learning)) { if (!defined($agent_learning)) {
# Agent id does not exists or is invalid. # Agent id does not exists or is invalid.
# Check if has been created by another process. # Check if has been created by another process, if not found.
$agent_id = get_db_value( $agent_data = PandoraFMS::Core::locate_agent(
$self->{'dbh'}, $self->{'pa_config'}, $self->{'dbh'}, $data->{'agent'}{'direccion'}
'SELECT id_agente FROM tagente WHERE nombre = ?', ) if ref($agent_data) ne 'HASH';
safe_input($data->{'agent'}{'nombre'})
);
if (!defined($agent_id) || $agent_id <= 0) { $agent_id = $agent_data->{'id_agente'} if ref($agent_data) eq 'HASH';
if (ref($agent_data) eq 'HASH' && $agent_data->{'modo'} != 1) {
# Agent previously exists, but is not in learning mode, so skip
# modules scan and jump directly to parent analysis.
$data->{'agent'}{'agent_id'} = $agent_id;
push @agents, $data->{'agent'};
next;
}
if (!defined($agent_id) || $agent_id <= 0 || !defined($agent_data)) {
# Agent creation. # Agent creation.
$agent_id = pandora_create_agent( $agent_id = pandora_create_agent(
$self->{'pa_config'}, $self->{'servername'}, $data->{'agent'}{'nombre'}, $self->{'pa_config'}, $self->{'servername'}, $data->{'agent'}{'nombre'},
$data->{'agent'}{'direccion'}, $self->{'task_data'}{'id_group'}, $parent_id, $data->{'agent'}{'direccion'}, $self->{'task_data'}{'id_group'}, $parent_id,
$os_id, $data->{'agent'}->{'description'}, $os_id, $data->{'agent'}->{'description'},
$data->{'agent'}{'interval'}, $self->{'dbh'}, $data->{'agent'}{'interval'}, $self->{'dbh'},
$data->{'agent'}{'timezone_offset'} $data->{'agent'}{'timezone_offset'}, undef, undef, undef, undef,
undef, undef, 1, $data->{'agent'}{'alias'}
); );
# Add found IP addresses to the agent.
if (ref($data->{'other_ips'}) eq 'ARRAY') {
foreach my $ip_addr (@{$data->{'other_ips'}}) {
my $addr_id = get_addr_id($self->{'dbh'}, $ip_addr);
$addr_id = add_address($self->{'dbh'}, $ip_addr) unless ($addr_id > 0);
next unless ($addr_id > 0);
# Assign the new address to the agent
my $agent_addr_id = get_agent_addr_id($self->{'dbh'}, $addr_id, $agent_id);
if ($agent_addr_id <= 0) {
db_do(
$self->{'dbh'}, 'INSERT INTO taddress_agent (`id_a`, `id_agent`)
VALUES (?, ?)', $addr_id, $agent_id
);
}
}
}
# Agent autoconfiguration. # Agent autoconfiguration.
if (is_enabled($self->{'autoconfiguration_enabled'})) { if (is_enabled($self->{'autoconfiguration_enabled'})) {
my $agent_data = PandoraFMS::DB::get_db_single_row( my $agent_data = PandoraFMS::DB::get_db_single_row(
@ -1093,6 +1121,25 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
'SELECT modo FROM tagente WHERE id_agente = ?', 'SELECT modo FROM tagente WHERE id_agente = ?',
$agent_id $agent_id
); );
# Update new IPs.
# Add found IP addresses to the agent.
if (ref($data->{'other_ips'}) eq 'ARRAY') {
foreach my $ip_addr (@{$data->{'other_ips'}}) {
my $addr_id = get_addr_id($self->{'dbh'}, $ip_addr);
$addr_id = add_address($self->{'dbh'}, $ip_addr) unless ($addr_id > 0);
next unless ($addr_id > 0);
# Assign the new address to the agent
my $agent_addr_id = get_agent_addr_id($self->{'dbh'}, $addr_id, $agent_id);
if ($agent_addr_id <= 0) {
db_do(
$self->{'dbh'}, 'INSERT INTO taddress_agent (`id_a`, `id_agent`)
VALUES (?, ?)', $addr_id, $agent_id
);
}
}
}
} }
$data->{'agent'}{'agent_id'} = $agent_id; $data->{'agent'}{'agent_id'} = $agent_id;
@ -1215,12 +1262,10 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
); );
} }
} }
# Update parent relationships. # Update parent relationships.
foreach my $agent (@agents) { foreach my $agent (@agents) {
# Avoid processing if does not exist. # Avoid processing if does not exist.
next unless (defined($agent->{'agent_id'})); next unless (defined($agent->{'agent_id'}));
@ -1228,10 +1273,10 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
next unless defined($agent->{'parent'}); next unless defined($agent->{'parent'});
# Get parent id. # Get parent id.
my $parent = get_agent_from_addr($self->{'dbh'}, $agent->{'parent'}); my $parent = PandoraFMS::Core::locate_agent(
if (!defined($parent)) { $self->{'pa_config'}, $self->{'dbh'}, $agent->{'parent'}
$parent = get_agent_from_name($self->{'dbh'}, $agent->{'parent'}); );
}
next unless defined($parent); next unless defined($parent);
# Is the agent in learning mode? # Is the agent in learning mode?
@ -1286,7 +1331,12 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
my @hosts = keys %{$self->{'agents_found'}}; my @hosts = keys %{$self->{'agents_found'}};
$self->{'step'} = STEP_PROCESSING; $self->{'step'} = STEP_PROCESSING;
my ($progress, $step) = (90, 10.0 / scalar(@hosts)); # From 90% to 100%. my ($progress, $step) = (90, 10.0 / scalar(@hosts)); # From 90% to 100%.
foreach my $label (keys %{$self->{'agents_found'}}) {
foreach my $addr (keys %{$self->{'agents_found'}}) {
my $label = $self->{'agents_found'}->{$addr}{'agent'}{'nombre'};
next if is_empty($label);
$self->call('update_progress', $progress); $self->call('update_progress', $progress);
$progress += $step; $progress += $step;
# Store temporally. Wait user approval. # Store temporally. Wait user approval.
@ -1294,7 +1344,7 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
eval { eval {
local $SIG{__DIE__}; local $SIG{__DIE__};
$encoded = encode_base64( $encoded = encode_base64(
encode_json($self->{'agents_found'}->{$label}) encode_json($self->{'agents_found'}->{$addr})
); );
}; };
@ -1307,7 +1357,7 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
if (defined($id)) { if (defined($id)) {
# Already defined. # Already defined.
$self->{'agents_found'}{$label}{'id'} = $id; $self->{'agents_found'}{$addr}{'id'} = $id;
db_do( db_do(
$self->{'dbh'}, $self->{'dbh'},
@ -1321,7 +1371,7 @@ sub PandoraFMS::Recon::Base::report_scanned_agents($;$) {
} }
# Insert. # Insert.
$self->{'agents_found'}{$label}{'id'} = db_insert( $self->{'agents_found'}{$addr}{'id'} = db_insert(
$self->{'dbh'}, $self->{'dbh'},
'id', 'id',
'INSERT INTO tdiscovery_tmp_agents (`id_rt`,`label`,`data`,`created`) ' 'INSERT INTO tdiscovery_tmp_agents (`id_rt`,`label`,`data`,`created`) '
@ -1615,6 +1665,16 @@ sub PandoraFMS::Recon::Base::set_parent($$$) {
$self->{'agents_found'}{$host}{'agent'}{'parent'} = $parent; $self->{'agents_found'}{$host}{'agent'}{'parent'} = $parent;
# Add host alive module for parent.
$self->add_module($parent,
{
'ip_target' => $parent,
'name' => "Host Alive",
'description' => '',
'type' => 'remote_icmp_proc',
'id_modulo' => 2,
}
);
} }
################################################################################ ################################################################################

View File

@ -33,7 +33,7 @@ our @ISA = qw(Exporter);
# version: Defines actual version of Pandora Server for this module only # version: Defines actual version of Pandora Server for this module only
my $pandora_version = "7.0NG.744"; my $pandora_version = "7.0NG.744";
my $pandora_build = "200414"; my $pandora_build = "200421";
our $VERSION = $pandora_version." ".$pandora_build; our $VERSION = $pandora_version." ".$pandora_build;
our %EXPORT_TAGS = ( 'all' => [ qw() ] ); our %EXPORT_TAGS = ( 'all' => [ qw() ] );

View File

@ -8,6 +8,7 @@ use warnings;
# Default lib dir for RPM and DEB packages # Default lib dir for RPM and DEB packages
use NetAddr::IP; use NetAddr::IP;
use IO::Socket::INET;
use POSIX qw/ceil/; use POSIX qw/ceil/;
use Socket qw/inet_aton/; use Socket qw/inet_aton/;
@ -171,6 +172,9 @@ sub new {
# Visited devices (initially empty). # Visited devices (initially empty).
visited_devices => {}, visited_devices => {},
# Inverse relationship for visited devices (initially empty).
addresses => {},
# Per device VLAN cache. # Per device VLAN cache.
vlan_cache => {}, vlan_cache => {},
vlan_cache_enabled => 1, # User configuration. Globally disables the VLAN cache. vlan_cache_enabled => 1, # User configuration. Globally disables the VLAN cache.
@ -308,6 +312,26 @@ sub add_addresses($$$) {
my ($self, $device, $ip_address) = @_; my ($self, $device, $ip_address) = @_;
$self->{'visited_devices'}->{$device}->{'addr'}->{$ip_address} = ''; $self->{'visited_devices'}->{$device}->{'addr'}->{$ip_address} = '';
# Inverse relationship.
$self->{'addresses'}{$ip_address} = $device;
# Update IP references.
if (ref($self->{'agents_found'}{$device}) eq 'HASH') {
my @addresses = $self->get_addresses($device);
$self->{'agents_found'}{$device}{'other_ips'} = \@addresses;
$self->call('message', 'New IP detected for '.$device.': '.$ip_address, 5);
}
}
################################################################################
# Get main address from given address (multi addressed devices).
################################################################################
sub get_main_address($$) {
my ($self, $addr) = @_;
return $self->{'addresses'}{$addr};
} }
################################################################################ ################################################################################
@ -1320,17 +1344,30 @@ sub remote_arp($$) {
################################################################################ ################################################################################
sub prepare_agent($$) { sub prepare_agent($$) {
my ($self, $addr) = @_; my ($self, $addr) = @_;
# Avoid multi-ip agent. No reference, is first encounter.
my $main_address = $self->get_main_address($addr);
return unless is_empty($main_address);
# Resolve hostnames.
my $host_name = (($self->{'resolve_names'} == 1) ? gethostbyaddr(inet_aton($addr), AF_INET) : $addr);
# Fallback to device IP if host name could not be resolved.
$host_name = $addr if (!defined($host_name) || $host_name eq '');
$self->{'agents_found'} = {} if ref($self->{'agents_found'}) ne 'HASH'; $self->{'agents_found'} = {} if ref($self->{'agents_found'}) ne 'HASH';
# Already initialized. # Already initialized.
return if ref($self->{'agents_found'}->{$addr}) eq 'HASH'; return if ref($self->{'agents_found'}->{$host_name}) eq 'HASH';
my @addresses = $self->get_addresses($addr);
$self->{'agents_found'}->{$addr} = { $self->{'agents_found'}->{$addr} = {
'agent' => { 'agent' => {
'nombre' => $addr, 'nombre' => $host_name,
'direccion' => $addr, 'direccion' => $addr,
'alias' => $addr, 'alias' => $host_name,
}, },
'other_ips' => \@addresses,
'pen' => $self->{'pen'}{$addr}, 'pen' => $self->{'pen'}{$addr},
'modules' => [], 'modules' => [],
}; };

View File

@ -3,7 +3,7 @@
# #
%define name pandorafms_server %define name pandorafms_server
%define version 7.0NG.744 %define version 7.0NG.744
%define release 200414 %define release 200421
Summary: Pandora FMS Server Summary: Pandora FMS Server
Name: %{name} Name: %{name}

View File

@ -3,7 +3,7 @@
# #
%define name pandorafms_server %define name pandorafms_server
%define version 7.0NG.744 %define version 7.0NG.744
%define release 200414 %define release 200421
Summary: Pandora FMS Server Summary: Pandora FMS Server
Name: %{name} Name: %{name}

View File

@ -9,7 +9,7 @@
# ********************************************************************** # **********************************************************************
PI_VERSION="7.0NG.744" PI_VERSION="7.0NG.744"
PI_BUILD="200414" PI_BUILD="200421"
MODE=$1 MODE=$1
if [ $# -gt 1 ]; then if [ $# -gt 1 ]; then

View File

@ -35,7 +35,7 @@ use PandoraFMS::Config;
use PandoraFMS::DB; use PandoraFMS::DB;
# version: define current version # version: define current version
my $version = "7.0NG.744 PS200414"; my $version = "7.0NG.744 PS200421";
# Pandora server configuration # Pandora server configuration
my %conf; my %conf;

View File

@ -36,7 +36,7 @@ use Encode::Locale;
Encode::Locale::decode_argv; Encode::Locale::decode_argv;
# version: define current version # version: define current version
my $version = "7.0NG.744 PS200414"; my $version = "7.0NG.744 PS200421";
# save program name for logging # save program name for logging
my $progname = basename($0); my $progname = basename($0);