#!/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 # # TO DO LIST: # - Enable child services detection (Windows) # - Make CPU/Memory usage available for child services (Windows) # ################################################### from sys import argv from sys import path from sys import stderr from sys import exit from subprocess import Popen from subprocess import PIPE from subprocess import DEVNULL from subprocess import getstatusoutput import psutil global module_list module_list = [] version = "1.1" ######################################################################################### # 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", "show", "-pLoadState", "-pActiveState", srvc], stdout=PIPE, stdin=DEVNULL, universal_newlines=True) result = syscall.communicate() srvstatus= result[0].strip().lower().split("\n") if srvstatus[0] == "loadstate=not-found": next else: if srvstatus[1] == "activestate=active": modules += service_module(srvc, 1) status = 1 elif srvstatus[1] == "activestate=inactive": modules += service_module(srvc, 0) status = 0 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 = ("\n" "\t\n" "\t" + str(data["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\n" for value in data["value"]: if type(value) is dict and "value" in value: module_xml += "\t\n" module_xml += "\t\t\n" if "timestamp" in value: module_xml += "\t\t\n" module_xml += "\t\n" else: module_xml += "\t\n" if "desc" in data: module_xml += "\t\n" if "unit" in data: module_xml += "\t\n" if "interval" in data: module_xml += "\t\n" if "tags" in data: module_xml += "\t" + str(data["tags"]) + "\n" if "module_group" in data: module_xml += "\t" + str(data["module_group"]) + "\n" if "module_parent" in data and data["module_parent"] != None: module_xml += "\t" + str(data["module_parent"]) + "\n" if "min_warning" in data: module_xml += "\t\n" if "max_warning" in data: module_xml += "\t\n" if "min_critical" in data: module_xml += "\t\n" if "max_critical" in data: module_xml += "\t\n" if "str_warning" in data: module_xml += "\t\n" if "str_critical" in data: module_xml += "\t\n" if "critical_inverse" in data: module_xml += "\t\n" if "warning_inverse" in data: module_xml += "\t\n" if "max" in data: module_xml += "\t\n" if "min" in data: module_xml += "\t\n" if "post_process" in data: module_xml += "\t\n" if "disabled" in data: module_xml += "\t\n" if "min_ff_event" in data: module_xml += "\t\n" if "status" in data: module_xml += "\t\n" if "timestamp" in data: module_xml += "\t\n" if "custom_id" in data: module_xml += "\t\n" if "critical_instructions" in data: module_xml += "\t\n" if "warning_instructions" in data: module_xml += "\t\n" if "unknown_instructions" in data: module_xml += "\t\n" if "quiet" in data: module_xml += "\t\n" if "module_ff_interval" in data: module_xml += "\t\n" if "crontab" in data: module_xml += "\t\n" if "min_ff_event_normal" in data: module_xml += "\t\n" if "min_ff_event_warning" in data: module_xml += "\t\n" if "min_ff_event_critical" in data: module_xml += "\t\n" if "ff_type" in data: module_xml += "\t\n" if "ff_timeout" in data: module_xml += "\t\n" if "each_ff" in data: module_xml += "\t\n" if "module_parent_unlink" in data: module_xml += "\t\n" if "global_alerts" in data: for alert in data["alert"]: module_xml += "\t\n" module_xml += "\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 v{}".format(version)) 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.") print ("\tServices monitored by default for {}:".format(osyst)) print ("\t",", ".join(servicelist)) print ("--list \"\"") 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()