mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2025-12-07 09:36:05 -08:00
NMAP plugin conversion v0.1
This commit is contained in:
@@ -34,7 +34,6 @@ from reporting import check_and_run_event, send_notifications
|
||||
from plugin import run_plugin_scripts
|
||||
|
||||
# different scanners
|
||||
from scanners.nmapscan import performNmapScan
|
||||
from scanners.internet import check_internet_IP
|
||||
|
||||
|
||||
@@ -58,8 +57,7 @@ main structure of Pi Alert
|
||||
run scans
|
||||
run plugins (scheduled)
|
||||
check internet IP
|
||||
check vendor
|
||||
run NMAP
|
||||
check vendor
|
||||
run "scan_network()"
|
||||
processing scan results
|
||||
run plugins (after Scan)
|
||||
@@ -160,25 +158,6 @@ def main ():
|
||||
conf.cycle = 'update_vendors'
|
||||
mylog('verbose', ['[MAIN] cycle:',conf.cycle])
|
||||
update_devices_MAC_vendors(db)
|
||||
|
||||
# Execute scheduled or one-off Nmap scan if enabled and run conditions fulfilled
|
||||
if conf.NMAP_RUN == "schedule" or conf.NMAP_RUN == "once":
|
||||
|
||||
nmapSchedule = [sch for sch in conf.mySchedules if sch.service == "nmap"][0]
|
||||
run = False
|
||||
|
||||
# run once after application starts
|
||||
if conf.NMAP_RUN == "once" and nmapSchedule.last_run == 0:
|
||||
run = True
|
||||
|
||||
# run if overdue scheduled time
|
||||
if conf.NMAP_RUN == "schedule":
|
||||
run = nmapSchedule.runScheduleCheck()
|
||||
|
||||
if run:
|
||||
nmapSchedule.last_run = timeNowTZ()
|
||||
performNmapScan(db, get_all_devices(db))
|
||||
|
||||
|
||||
# Run splugin scripts which are set to run every timne after a scans finished
|
||||
pluginsState = run_plugin_scripts(db,'always_after_scan', pluginsState)
|
||||
@@ -202,11 +181,7 @@ def main ():
|
||||
# new devices were found
|
||||
if len(newDevices) > 0:
|
||||
# run all plugins registered to be run when new devices are found
|
||||
pluginsState = run_plugin_scripts(db, 'on_new_device', pluginsState)
|
||||
|
||||
# Scan newly found devices with Nmap if enabled
|
||||
if conf.NMAP_ACTIVE and len(newDevices) > 0:
|
||||
performNmapScan( db, newDevices)
|
||||
pluginsState = run_plugin_scripts(db, 'on_new_device', pluginsState)
|
||||
|
||||
# send all configured notifications
|
||||
send_notifications(db)
|
||||
|
||||
@@ -3,7 +3,7 @@ import json
|
||||
|
||||
# pialert modules
|
||||
import conf
|
||||
from const import (apiPath, sql_devices_all, sql_nmap_scan_all, sql_pholus_scan_all, sql_events_pending_alert,
|
||||
from const import (apiPath, sql_devices_all, sql_events_pending_alert,
|
||||
sql_settings, sql_plugins_events, sql_plugins_history, sql_plugins_objects,sql_language_strings)
|
||||
from logger import mylog
|
||||
from helper import write_file
|
||||
@@ -26,9 +26,7 @@ def update_api(db, isNotification = False, updateOnlyDataSources = []):
|
||||
|
||||
# prepare database tables we want to expose
|
||||
dataSourcesSQLs = [
|
||||
["devices", sql_devices_all],
|
||||
["nmap_scan", sql_nmap_scan_all],
|
||||
["pholus_scan", sql_pholus_scan_all],
|
||||
["devices", sql_devices_all],
|
||||
["events_pending_alert", sql_events_pending_alert],
|
||||
["settings", sql_settings],
|
||||
["plugins_events", sql_plugins_events],
|
||||
|
||||
@@ -33,7 +33,6 @@ mqtt_connected_to_broker = False
|
||||
mqtt_sensors = []
|
||||
client = None # mqtt client
|
||||
# for notifications
|
||||
changedPorts_json_struc = None
|
||||
|
||||
# ACTUAL CONFIGRATION ITEMS set to defaults
|
||||
|
||||
@@ -43,7 +42,7 @@ LOG_LEVEL = 'verbose'
|
||||
TIMEZONE = 'Europe/Berlin'
|
||||
PIALERT_WEB_PROTECTION = False
|
||||
PIALERT_WEB_PASSWORD = '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92'
|
||||
INCLUDED_SECTIONS = ['internet', 'new_devices', 'down_devices', 'events', 'ports']
|
||||
INCLUDED_SECTIONS = ['internet', 'new_devices', 'down_devices', 'events']
|
||||
DAYS_TO_KEEP_EVENTS = 90
|
||||
REPORT_DASHBOARD_URL = 'http://pi.alert/'
|
||||
DIG_GET_IP_ARG = '-4 myip.opendns.com @resolver1.opendns.com'
|
||||
@@ -103,12 +102,5 @@ DDNS_USER = 'dynu_user'
|
||||
DDNS_PASSWORD = 'A0000000B0000000C0000000D0000000'
|
||||
DDNS_UPDATE_URL = 'https://api.dynu.com/nic/update?'
|
||||
|
||||
# Nmap
|
||||
NMAP_ACTIVE = True
|
||||
NMAP_TIMEOUT = 150
|
||||
NMAP_RUN = 'once'
|
||||
NMAP_RUN_SCHD = '0 2 * * *'
|
||||
NMAP_ARGS = '-p -10000 --max-parallelism 100'
|
||||
|
||||
# API
|
||||
API_CUSTOM_SQL = 'SELECT * FROM Devices WHERE dev_PresentLastScan = 0'
|
||||
@@ -35,8 +35,6 @@ sql_devices_stats = """SELECT Online_Devices as online, Down_Devices as down, A
|
||||
(select count(*) from Devices a where dev_NewDevice = 1 ) as new,
|
||||
(select count(*) from Devices a where dev_Name = '(unknown)' or dev_Name = '(name not found)' ) as unknown
|
||||
from Online_History order by Scan_Date desc limit 1"""
|
||||
sql_nmap_scan_all = "SELECT * FROM Nmap_Scan"
|
||||
sql_pholus_scan_all = "SELECT * FROM Pholus_Scan"
|
||||
sql_events_pending_alert = "SELECT * FROM Events where eve_PendingAlertEmail is not 0"
|
||||
sql_settings = "SELECT * FROM Settings"
|
||||
sql_plugins_objects = "SELECT * FROM Plugins_Objects"
|
||||
|
||||
@@ -89,7 +89,7 @@ def importConfigs (db):
|
||||
conf.PLUGINS_KEEP_HIST = ccd('PLUGINS_KEEP_HIST', 250 , c_d, 'Keep history entries', 'integer', '', 'General')
|
||||
conf.PIALERT_WEB_PROTECTION = ccd('PIALERT_WEB_PROTECTION', False , c_d, 'Enable logon', 'boolean', '', 'General')
|
||||
conf.PIALERT_WEB_PASSWORD = ccd('PIALERT_WEB_PASSWORD', '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92' , c_d, 'Logon password', 'readonly', '', 'General')
|
||||
conf.INCLUDED_SECTIONS = ccd('INCLUDED_SECTIONS', ['internet', 'new_devices', 'down_devices', 'events', 'ports'] , c_d, 'Notify on', 'text.multiselect', "['internet', 'new_devices', 'down_devices', 'events', 'ports', 'plugins']", 'General')
|
||||
conf.INCLUDED_SECTIONS = ccd('INCLUDED_SECTIONS', ['internet', 'new_devices', 'down_devices', 'events'] , c_d, 'Notify on', 'text.multiselect', "['internet', 'new_devices', 'down_devices', 'events', 'plugins']", 'General')
|
||||
conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://pi.alert/' , c_d, 'PiAlert URL', 'text', '', 'General')
|
||||
conf.DIG_GET_IP_ARG = ccd('DIG_GET_IP_ARG', '-4 myip.opendns.com @resolver1.opendns.com' , c_d, 'DIG arguments', 'text', '', 'General')
|
||||
conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'German', 'Spanish']", 'General')
|
||||
@@ -154,13 +154,7 @@ def importConfigs (db):
|
||||
conf.DDNS_PASSWORD = ccd('DDNS_PASSWORD', 'A0000000B0000000C0000000D0000000' , c_d, 'DynDNS password', 'password', '', 'DynDNS')
|
||||
conf.DDNS_UPDATE_URL = ccd('DDNS_UPDATE_URL', 'https://api.dynu.com/nic/update?' , c_d, 'DynDNS update URL', 'text', '', 'DynDNS')
|
||||
|
||||
# Nmap
|
||||
conf.NMAP_ACTIVE = ccd('NMAP_ACTIVE', True , c_d, 'Enable Nmap scans', 'boolean', '', 'Nmap')
|
||||
conf.NMAP_TIMEOUT = ccd('NMAP_TIMEOUT', 150 , c_d, 'Nmap timeout', 'integer', '', 'Nmap')
|
||||
conf.NMAP_RUN = ccd('NMAP_RUN', 'disabled' , c_d, 'Nmap enable schedule', 'text.select', "['disabled', 'once', 'schedule']", 'Nmap')
|
||||
conf.NMAP_RUN_SCHD = ccd('NMAP_RUN_SCHD', '0 2 * * *' , c_d, 'Nmap schedule', 'text', '', 'Nmap')
|
||||
conf.NMAP_ARGS = ccd('NMAP_ARGS', '-p -10000' , c_d, 'Nmap custom arguments', 'text', '', 'Nmap')
|
||||
|
||||
|
||||
# Init timezone in case it changed
|
||||
conf.tz = timezone(conf.TIMEZONE)
|
||||
|
||||
@@ -188,10 +182,6 @@ def importConfigs (db):
|
||||
# reset schedules
|
||||
conf.mySchedules = []
|
||||
|
||||
# init nmap schedule
|
||||
nmapSchedule = Cron(conf.NMAP_RUN_SCHD).schedule(start_date=datetime.datetime.now(conf.tz))
|
||||
conf.mySchedules.append(schedule_class("nmap", nmapSchedule, nmapSchedule.next(), False))
|
||||
|
||||
# Format and prepare the list of subnets
|
||||
conf.userSubnets = updateSubnets(conf.SCAN_SUBNETS)
|
||||
|
||||
@@ -252,10 +242,7 @@ def importConfigs (db):
|
||||
conf.plugins_once_run = False
|
||||
# -----------------
|
||||
# Plugins END
|
||||
|
||||
# write_file(self.path, json.dumps(self.jsonData))
|
||||
|
||||
|
||||
|
||||
|
||||
# Insert settings into the DB
|
||||
sql.execute ("DELETE FROM Settings")
|
||||
@@ -270,9 +257,9 @@ def importConfigs (db):
|
||||
|
||||
# update only the settings datasource
|
||||
update_api(db, False, ["settings"])
|
||||
|
||||
|
||||
# run plugins that are modifying the config
|
||||
pluginsState = run_plugin_scripts(db, 'before_config_save')
|
||||
run_plugin_scripts(db, 'before_config_save' )
|
||||
|
||||
# Used to determine the next import
|
||||
conf.lastImportedConfFile = os.path.getmtime(config_file)
|
||||
|
||||
@@ -3,6 +3,7 @@ import sqlite3
|
||||
import json
|
||||
import subprocess
|
||||
import datetime
|
||||
import base64
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
@@ -16,16 +17,88 @@ from plugin_utils import logEventStatusCounts, get_plugin_string, get_plugin_set
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
class plugins_state:
|
||||
def __init__(self, processScan = False):
|
||||
self.processScan = processScan
|
||||
class plugin_param:
|
||||
def __init__(self, param, plugin, db):
|
||||
|
||||
paramValuesCount = 1
|
||||
|
||||
# Get setting value
|
||||
if param["type"] == "setting":
|
||||
inputValue = get_setting(param["value"])
|
||||
|
||||
if inputValue != None:
|
||||
setVal = inputValue[6] # setting value
|
||||
setTyp = inputValue[3] # setting type
|
||||
|
||||
noConversion = ['text', 'string', 'integer', 'boolean', 'password', 'readonly', 'integer.select', 'text.select', 'integer.checkbox' ]
|
||||
arrayConversion = ['text.multiselect', 'list', 'subnets']
|
||||
jsonConversion = ['.template']
|
||||
|
||||
mylog('debug', f'[Plugins] setTyp: {setTyp}')
|
||||
|
||||
if '.select' in setTyp or setTyp in arrayConversion:
|
||||
paramValuesCount = len(setVal)
|
||||
|
||||
if setTyp in noConversion:
|
||||
resolved = setVal
|
||||
|
||||
elif setTyp in arrayConversion:
|
||||
resolved = flatten_array(setVal)
|
||||
|
||||
elif setTyp in arrayConversionBase64:
|
||||
|
||||
|
||||
resolved = flatten_array(setVal)
|
||||
else:
|
||||
for item in jsonConversion:
|
||||
if setTyp.endswith(item):
|
||||
return json.dumps(setVal)
|
||||
else:
|
||||
mylog('none', ['[Plugins] ERROR: Parameter not converted.'])
|
||||
|
||||
|
||||
# Get SQL result
|
||||
if param["type"] == "sql":
|
||||
inputValue = db.get_sql_array(param["value"])
|
||||
|
||||
resolved = flatten_array(inputValue)
|
||||
|
||||
|
||||
mylog('debug', f'[Plugins] Resolved value: {resolved}')
|
||||
|
||||
# Handle timeout multiplier if script executes multiple time
|
||||
multiplyTimeout = False
|
||||
if 'timeoutMultiplier' in param and param['timeoutMultiplier']:
|
||||
multiplyTimeout = True
|
||||
|
||||
# Handle base64 encoding
|
||||
encodeToBase64 = False
|
||||
if 'base64' in param and param['base64']:
|
||||
encodeToBase64 = True
|
||||
|
||||
|
||||
mylog('debug', f'[Plugins] Convert to Base64: {encodeToBase64}')
|
||||
if encodeToBase64:
|
||||
resolved = str(base64.b64encode(resolved.encode('ascii')))
|
||||
mylog('debug', f'[Plugins] base64 value: {resolved}')
|
||||
|
||||
|
||||
self.resolved = resolved
|
||||
self.inputValue = inputValue
|
||||
self.base64 = encodeToBase64
|
||||
self.name = param["name"]
|
||||
self.type = param["type"]
|
||||
self.value = param["value"]
|
||||
self.paramValuesCount = paramValuesCount
|
||||
self.multiplyTimeout = multiplyTimeout
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def run_plugin_scripts(db, runType, pluginsState = None):
|
||||
class plugins_state:
|
||||
def __init__(self, processScan = False):
|
||||
self.processScan = processScan
|
||||
|
||||
if pluginsState == None:
|
||||
mylog('debug', ['[Plugins] pluginsState initialized '])
|
||||
pluginsState = plugins_state()
|
||||
#-------------------------------------------------------------------------------
|
||||
def run_plugin_scripts(db, runType, pluginsState = plugins_state()):
|
||||
|
||||
# Header
|
||||
updateState(db,"Run: Plugins")
|
||||
@@ -73,6 +146,11 @@ def run_plugin_scripts(db, runType, pluginsState = None):
|
||||
def execute_plugin(db, plugin, pluginsState = plugins_state() ):
|
||||
sql = db.sql
|
||||
|
||||
|
||||
if pluginsState is None:
|
||||
mylog('debug', ['[Plugins] pluginsState is None'])
|
||||
pluginsState = plugins_state()
|
||||
|
||||
# ------- necessary settings check --------
|
||||
set = get_plugin_setting(plugin, "CMD")
|
||||
|
||||
@@ -92,29 +170,26 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
|
||||
|
||||
mylog('debug', ['[Plugins] Timeout: ', set_RUN_TIMEOUT])
|
||||
|
||||
# Prepare custom params
|
||||
# Prepare custom params
|
||||
params = []
|
||||
|
||||
if "params" in plugin:
|
||||
for param in plugin["params"]:
|
||||
resolved = ""
|
||||
for param in plugin["params"]:
|
||||
|
||||
# Get setting value
|
||||
if param["type"] == "setting":
|
||||
resolved = get_setting(param["value"])
|
||||
tempParam = plugin_param(param, plugin, db)
|
||||
|
||||
if resolved != None:
|
||||
resolved = passable_string_from_setting(resolved)
|
||||
|
||||
# Get Sql result
|
||||
if param["type"] == "sql":
|
||||
resolved = flatten_array(db.get_sql_array(param["value"]))
|
||||
|
||||
if resolved == None:
|
||||
mylog('none', [f'[Plugins] The parameter "name":"{param["name"]}" for "value": {param["value"]} was resolved as None'])
|
||||
if tempParam.resolved == None:
|
||||
mylog('none', [f'[Plugins] The parameter "name":"{tempParam.name}" for "value": {tempParam.value} was resolved as None'])
|
||||
|
||||
else:
|
||||
params.append( [param["name"], resolved] )
|
||||
# params.append( [param["name"], resolved] )
|
||||
params.append( [tempParam.name, tempParam.resolved] )
|
||||
|
||||
if tempParam.multiplyTimeout:
|
||||
|
||||
set_RUN_TIMEOUT = set_RUN_TIMEOUT*tempParam.paramValuesCount
|
||||
|
||||
mylog('debug', [f'[Plugins] The parameter "name":"{param["name"]}" will multiply the timeout {tempParam.paramValuesCount} times. Total timeout: {set_RUN_TIMEOUT}s'])
|
||||
|
||||
|
||||
# build SQL query parameters to insert into the DB
|
||||
@@ -299,37 +374,6 @@ def execute_plugin(db, plugin, pluginsState = plugins_state() ):
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Flattens a setting to make it passable to a script
|
||||
def passable_string_from_setting(globalSetting):
|
||||
|
||||
setVal = globalSetting[6] # setting value
|
||||
setTyp = globalSetting[3] # setting type
|
||||
|
||||
noConversion = ['text', 'string', 'integer', 'boolean', 'password', 'readonly', 'integer.select', 'text.select', 'integer.checkbox' ]
|
||||
arrayConversion = ['text.multiselect', 'list']
|
||||
arrayConversionBase64 = ['subnets']
|
||||
jsonConversion = ['.template']
|
||||
|
||||
mylog('debug', f'[Plugins] setTyp: {setTyp}')
|
||||
|
||||
if setTyp in noConversion:
|
||||
return setVal
|
||||
|
||||
if setTyp in arrayConversion:
|
||||
return flatten_array(setVal)
|
||||
|
||||
if setTyp in arrayConversionBase64:
|
||||
|
||||
return flatten_array(setVal, encodeBase64 = True)
|
||||
|
||||
for item in jsonConversion:
|
||||
if setTyp.endswith(item):
|
||||
return json.dumps(setVal)
|
||||
|
||||
mylog('none', ['[Plugins] ERROR: Parameter not converted.'])
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
# Check if watched values changed for the given plugin
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import os
|
||||
import base64
|
||||
import json
|
||||
|
||||
from logger import mylog
|
||||
@@ -72,12 +71,12 @@ def get_plugin_string(props, el):
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def flatten_array(arr, encodeBase64=False):
|
||||
def flatten_array(arr):
|
||||
tmp = ''
|
||||
arrayItemStr = ''
|
||||
|
||||
mylog('debug', '[Plugins] Flattening the below array')
|
||||
mylog('debug', f'[Plugins] Convert to Base64: {encodeBase64}')
|
||||
|
||||
mylog('debug', arr)
|
||||
|
||||
for arrayItem in arr:
|
||||
@@ -93,12 +92,7 @@ def flatten_array(arr, encodeBase64=False):
|
||||
|
||||
tmp = tmp[:-1] # Remove last comma ','
|
||||
|
||||
mylog('debug', f'[Plugins] Flattened array: {tmp}')
|
||||
|
||||
if encodeBase64:
|
||||
tmp = str(base64.b64encode(tmp.encode('ascii')))
|
||||
mylog('debug', f'[Plugins] Flattened array (base64): {tmp}')
|
||||
|
||||
mylog('debug', f'[Plugins] Flattened array: {tmp}')
|
||||
|
||||
return tmp
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ def construct_notifications(db, sqlQuery, tableTitle, skipText = False, supplied
|
||||
def send_notifications (db):
|
||||
|
||||
sql = db.sql #TO-DO
|
||||
global mail_text, mail_html, json_final, changedPorts_json_struc, partial_html, partial_txt, partial_json
|
||||
global mail_text, mail_html, json_final, partial_html, partial_txt, partial_json
|
||||
|
||||
deviceUrl = conf.REPORT_DASHBOARD_URL + '/deviceDetails.php?mac='
|
||||
plugins_report = False
|
||||
@@ -234,24 +234,7 @@ def send_notifications (db):
|
||||
|
||||
mail_text = mail_text.replace ('<SECTION_EVENTS>', notiStruc.text + '\n')
|
||||
mail_html = mail_html.replace ('<EVENTS_TABLE>', notiStruc.html)
|
||||
mylog('verbose', ['[Notification] Events sections done.'])
|
||||
|
||||
if 'ports' in conf.INCLUDED_SECTIONS :
|
||||
# collect "ports" for the webhook json
|
||||
mylog('verbose', ['[Notification] Ports: conf.changedPorts_json_struc:', conf.changedPorts_json_struc])
|
||||
if conf.changedPorts_json_struc is not None:
|
||||
json_ports = conf.changedPorts_json_struc.json["data"]
|
||||
|
||||
notiStruc = construct_notifications(db, "", "Ports", True, conf.changedPorts_json_struc)
|
||||
|
||||
mail_html = mail_html.replace ('<PORTS_TABLE>', notiStruc.html)
|
||||
|
||||
portsTxt = ""
|
||||
if conf.changedPorts_json_struc is not None:
|
||||
portsTxt = "Ports \n---------\n Ports changed! Check PiAlert for details!\n"
|
||||
|
||||
mail_text = mail_text.replace ('<PORTS_TABLE>', portsTxt )
|
||||
mylog('verbose', ['[Notification] Ports sections done.'])
|
||||
mylog('verbose', ['[Notification] Events sections done.'])
|
||||
|
||||
if 'plugins' in conf.INCLUDED_SECTIONS:
|
||||
# Compose Plugins Section
|
||||
@@ -347,9 +330,7 @@ def send_notifications (db):
|
||||
WHERE eve_PendingAlertEmail = 1""")
|
||||
|
||||
# clear plugin events
|
||||
sql.execute ("DELETE FROM Plugins_Events")
|
||||
|
||||
conf.changedPorts_json_struc = None
|
||||
sql.execute ("DELETE FROM Plugins_Events")
|
||||
|
||||
# DEBUG - print number of rows updated
|
||||
mylog('minimal', ['[Notification] Notifications changes: ', sql.rowcount])
|
||||
@@ -486,10 +467,13 @@ def skip_repeated_notifications (db):
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def check_and_run_event(db, pluginsState):
|
||||
mylog('debug', [f'[MAIN] processScan1: {pluginsState.processScan}'])
|
||||
sql = db.sql # TO-DO
|
||||
sql.execute(""" select * from Parameters where par_ID = "Front_Event" """)
|
||||
rows = sql.fetchall()
|
||||
|
||||
mylog('debug', [f'[MAIN] processScan2: {pluginsState.processScan}'])
|
||||
|
||||
event, param = ['','']
|
||||
if len(rows) > 0 and rows[0]['par_Value'] != 'finished':
|
||||
keyValue = rows[0]['par_Value'].split('|')
|
||||
@@ -498,7 +482,7 @@ def check_and_run_event(db, pluginsState):
|
||||
event = keyValue[0]
|
||||
param = keyValue[1]
|
||||
else:
|
||||
return
|
||||
return pluginsState
|
||||
|
||||
if event == 'test':
|
||||
handle_test(param)
|
||||
@@ -511,6 +495,8 @@ def check_and_run_event(db, pluginsState):
|
||||
# commit to DB
|
||||
db.commitDB()
|
||||
|
||||
mylog('debug', [f'[MAIN] processScan3: {pluginsState.processScan}'])
|
||||
|
||||
return pluginsState
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
|
||||
import subprocess
|
||||
|
||||
import conf
|
||||
from const import logPath, sql_nmap_scan_all
|
||||
from helper import json_struc, timeNowTZ, updateState
|
||||
from logger import append_line_to_file, mylog
|
||||
#-------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
class nmap_entry:
|
||||
def __init__(self, mac, time, port, state, service, name = '', extra = '', index = 0):
|
||||
self.mac = mac
|
||||
self.time = time
|
||||
self.port = port
|
||||
self.state = state
|
||||
self.service = service
|
||||
self.name = name
|
||||
self.extra = extra
|
||||
self.index = index
|
||||
self.hash = str(mac) + str(port)+ str(state)+ str(service)
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------------
|
||||
def performNmapScan(db, devicesToScan):
|
||||
"""
|
||||
run nmap scan on a list of devices
|
||||
discovers open ports and keeps track existing and new open ports
|
||||
"""
|
||||
if len(devicesToScan) > 0:
|
||||
|
||||
timeoutSec = conf.NMAP_TIMEOUT
|
||||
|
||||
devTotal = len(devicesToScan)
|
||||
|
||||
updateState(db,"Scan: Nmap")
|
||||
|
||||
mylog('verbose', ['[NMAP Scan] Scan: Nmap for max ', str(timeoutSec), 's ('+ str(round(int(timeoutSec) / 60, 1)) +'min) per device'])
|
||||
mylog('verbose', ["[NMAP Scan] Estimated max delay: ", (devTotal * int(timeoutSec)), 's ', '(', round((devTotal * int(timeoutSec))/60,1) , 'min)' ])
|
||||
|
||||
devIndex = 0
|
||||
for device in devicesToScan:
|
||||
# Execute command
|
||||
output = ""
|
||||
# prepare arguments from user supplied ones
|
||||
nmapArgs = ['nmap'] + conf.NMAP_ARGS.split() + [device["dev_LastIP"]]
|
||||
|
||||
progress = ' (' + str(devIndex+1) + '/' + str(devTotal) + ')'
|
||||
|
||||
try:
|
||||
# try runnning a subprocess with a forced (timeout + 30 seconds) in case the subprocess hangs
|
||||
output = subprocess.check_output (nmapArgs, universal_newlines=True, stderr=subprocess.STDOUT, timeout=(timeoutSec + 30))
|
||||
except subprocess.CalledProcessError as e:
|
||||
# An error occured, handle it
|
||||
mylog('none', ["[NMAP Scan] " ,e.output])
|
||||
mylog('none', ["[NMAP Scan] Error - Nmap Scan - check logs", progress])
|
||||
except subprocess.TimeoutExpired as timeErr:
|
||||
mylog('verbose', ['[NMAP Scan] Nmap TIMEOUT - the process forcefully terminated as timeout reached for ', device["dev_LastIP"], progress])
|
||||
|
||||
if output == "": # check if the subprocess failed
|
||||
mylog('minimal', ['[NMAP Scan] Nmap FAIL for ', device["dev_LastIP"], progress ,' check logs for details'])
|
||||
else:
|
||||
mylog('verbose', ['[NMAP Scan] Nmap SUCCESS for ', device["dev_LastIP"], progress])
|
||||
|
||||
devIndex += 1
|
||||
|
||||
# check the last run output
|
||||
newLines = output.split('\n')
|
||||
|
||||
# regular logging
|
||||
for line in newLines:
|
||||
append_line_to_file (logPath + '/pialert_nmap.log', line +'\n')
|
||||
|
||||
# collect ports / new Nmap Entries
|
||||
newEntriesTmp = []
|
||||
|
||||
index = 0
|
||||
startCollecting = False
|
||||
duration = ""
|
||||
for line in newLines:
|
||||
if 'Starting Nmap' in line:
|
||||
if len(newLines) > index+1 and 'Note: Host seems down' in newLines[index+1]:
|
||||
break # this entry is empty
|
||||
elif 'PORT' in line and 'STATE' in line and 'SERVICE' in line:
|
||||
startCollecting = True
|
||||
elif 'PORT' in line and 'STATE' in line and 'SERVICE' in line:
|
||||
startCollecting = False # end reached
|
||||
elif startCollecting and len(line.split()) == 3:
|
||||
newEntriesTmp.append(nmap_entry(device["dev_MAC"], timeNowTZ(), line.split()[0], line.split()[1], line.split()[2], device["dev_Name"]))
|
||||
elif 'Nmap done' in line:
|
||||
duration = line.split('scanned in ')[1]
|
||||
index += 1
|
||||
mylog('verbose', ['[NMAP Scan] Ports found by NMAP: ', len(newEntriesTmp)])
|
||||
process_discovered_ports(db, device, newEntriesTmp)
|
||||
#end for loop
|
||||
|
||||
|
||||
|
||||
def process_discovered_ports(db, device, discoveredPorts):
|
||||
"""
|
||||
process ports discovered by nmap
|
||||
compare to previosu ports
|
||||
update DB
|
||||
raise notifications
|
||||
"""
|
||||
sql = db.sql # TO-DO
|
||||
# previous Nmap Entries
|
||||
oldEntries = []
|
||||
changedPortsTmp = []
|
||||
|
||||
mylog('verbose', ['[NMAP Scan] Process ports found by NMAP: ', len(discoveredPorts)])
|
||||
|
||||
if len(discoveredPorts) > 0:
|
||||
|
||||
# get all current NMAP ports from the DB
|
||||
rows = db.read(sql_nmap_scan_all)
|
||||
|
||||
for row in rows:
|
||||
# only collect entries matching the current MAC address
|
||||
if row["MAC"] == device["dev_MAC"]:
|
||||
oldEntries.append(nmap_entry(row["MAC"], row["Time"], row["Port"], row["State"], row["Service"], device["dev_Name"], row["Extra"], row["Index"]))
|
||||
|
||||
newEntries = []
|
||||
|
||||
# Collect all entries that don't match the ones in the DB
|
||||
for discoveredPort in discoveredPorts:
|
||||
|
||||
found = False
|
||||
|
||||
# Check the new entry is already available in oldEntries and remove from processing if yes
|
||||
for oldEntry in oldEntries:
|
||||
if discoveredPort.hash == oldEntry.hash:
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
newEntries.append(discoveredPort)
|
||||
|
||||
|
||||
mylog('verbose', ['[NMAP Scan] Nmap newly discovered or changed ports: ', len(newEntries)])
|
||||
|
||||
# collect new ports, find the corresponding old entry and return for notification purposes
|
||||
# also update the DB with the new values after deleting the old ones
|
||||
if len(newEntries) > 0:
|
||||
|
||||
# params to build the SQL query
|
||||
params = []
|
||||
indexesToDelete = ""
|
||||
|
||||
# Find old entry matching the new entry hash
|
||||
for newEntry in newEntries:
|
||||
|
||||
foundEntry = None
|
||||
|
||||
for oldEntry in oldEntries:
|
||||
if oldEntry.hash == newEntry.hash:
|
||||
indexesToDelete = indexesToDelete + str(oldEntry.index) + ','
|
||||
foundEntry = oldEntry
|
||||
|
||||
columnNames = ["Name", "MAC", "Port", "State", "Service", "Extra", "NewOrOld" ]
|
||||
|
||||
# Old entry found
|
||||
if foundEntry is not None:
|
||||
# Build params for sql query
|
||||
params.append((newEntry.mac, newEntry.time, newEntry.port, newEntry.state, newEntry.service, oldEntry.extra))
|
||||
# Build JSON for API and notifications
|
||||
changedPortsTmp.append({
|
||||
"Name" : foundEntry.name,
|
||||
"MAC" : newEntry.mac,
|
||||
"Port" : newEntry.port,
|
||||
"State" : newEntry.state,
|
||||
"Service" : newEntry.service,
|
||||
"Extra" : foundEntry.extra,
|
||||
"NewOrOld" : "New values"
|
||||
})
|
||||
changedPortsTmp.append({
|
||||
"Name" : foundEntry.name,
|
||||
"MAC" : foundEntry.mac,
|
||||
"Port" : foundEntry.port,
|
||||
"State" : foundEntry.state,
|
||||
"Service" : foundEntry.service,
|
||||
"Extra" : foundEntry.extra,
|
||||
"NewOrOld" : "Old values"
|
||||
})
|
||||
# New entry - no matching Old entry found
|
||||
else:
|
||||
# Build params for sql query
|
||||
params.append((newEntry.mac, newEntry.time, newEntry.port, newEntry.state, newEntry.service, ''))
|
||||
# Build JSON for API and notifications
|
||||
changedPortsTmp.append({
|
||||
"Name" : "New device",
|
||||
"MAC" : newEntry.mac,
|
||||
"Port" : newEntry.port,
|
||||
"State" : newEntry.state,
|
||||
"Service" : newEntry.service,
|
||||
"Extra" : "",
|
||||
"NewOrOld" : "New device"
|
||||
})
|
||||
|
||||
conf.changedPorts_json_struc = json_struc({ "data" : changedPortsTmp}, columnNames)
|
||||
|
||||
# Delete old entries if available
|
||||
if len(indexesToDelete) > 0:
|
||||
sql.execute ("DELETE FROM Nmap_Scan where \"Index\" in (" + indexesToDelete[:-1] +")")
|
||||
db.commitDB()
|
||||
|
||||
# Insert new values into the DB
|
||||
sql.executemany ("""INSERT INTO Nmap_Scan ("MAC", "Time", "Port", "State", "Service", "Extra") VALUES (?, ?, ?, ?, ?, ?)""", params)
|
||||
db.commitDB()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user