Files
NetAlertX/pialert/scanners/nmapscan.py
2023-08-07 08:23:39 +10:00

212 lines
9.4 KiB
Python
Executable File

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()