diff --git a/config.py b/config.py new file mode 100644 index 0000000..6357dc8 --- /dev/null +++ b/config.py @@ -0,0 +1,7 @@ +#modules selection +config = { +"parser" : "Parser", +"address_generator" : "IpGenerator", +"scanner" : "CoreModel", +"storage" : "JSONStorage" +} diff --git a/core/MainPresenter.py b/core/MainPresenter.py new file mode 100644 index 0000000..339f989 --- /dev/null +++ b/core/MainPresenter.py @@ -0,0 +1,125 @@ +from core import import_utils +from core.communication.ConvertTable import ConvertTable +from threading import RLock +import datetime +from PyQt5.Qt import QThread, pyqtSignal +from PyQt5.QtCore import QObject, pyqtSlot +from config import config +from inspect import isfunction +CoreModel = import_utils.import_class("modules/network_scan/%s.py" % +config["scanner"]) +Parser = import_utils.import_class("modules/address_generation/%s.py" % +config["parser"]) +IpGenerator = import_utils.import_class( +"modules/address_generation/%s.py" % +config["address_generator"] +) +JSONStorage = import_utils.import_class("modules/storage/%s.py" % +config["storage"]) + + +class MainPresenter: + def __init__(self, ui): + self.ui = ui + self.threads = [] + self.isScanEnabled = False + self.convert_table = ConvertTable() + for func in import_utils.import_matching( + "modules/convert_functions/", + lambda name, value: + isfunction(value) and hasattr(value, "__from__") + ): + self.convert_table.add_function(func) + self.parser = Parser() + #needed config to specify path + self.storage = JSONStorage("results.json") + self.exit_lock = RLock() + + def startScan(self, ipRanges, portsStr, threadNumber, timeout): + timeout = 3 if not timeout else int(timeout) + addresses = self.parser.parse_address_field(ipRanges) + ports = self.parser.parse_port_field(portsStr) + self.ip_generator = IpGenerator(addresses, ports, self.convert_table) + self.scanner = CoreModel(timeout) + threadNumber = int(threadNumber) + for i in range(threadNumber): + scan_worker = ScanWorker( + self.ip_generator, + self.scanner, + self.storage + ) + scan_thread = QThread() + scan_worker.log_signal.connect(self.log_text) + scan_worker.moveToThread(scan_thread) + scan_worker.exit_signal.connect(scan_thread.exit) + scan_worker.exit_signal.connect(self.on_worker_exit) + scan_thread.started.connect(scan_worker.work) + self.threads.append((scan_worker, scan_thread)) + self.changeThreadLabel(threadNumber) + for thread in self.threads: + scan_worker, scan_thread = thread + scan_thread.start() + + def changeThreadLabel(self, number_of_threads): + self.number_of_threads = number_of_threads + self.ui.currentThreadsLabel.setText(str(number_of_threads)) + + def on_worker_exit(self): + self.changeThreadLabel(self.number_of_threads - 1) + with self.exit_lock: + for num, thread in enumerate(self.threads): + scan_worker, scan_thread = thread + if not scan_worker.isRunning: + self.threads.pop(num) + break + if self.number_of_threads == 0: + self.on_end_scanning() + + def on_end_scanning(self): + self.isScanEnabled = False + self.ui.startButton.setText("Start") + self.storage.save() + + def stopScan(self): + while self.threads: + scan_worker, scan_thread = self.threads[0] + if scan_worker.isRunning: + scan_worker.stop() + + def log_text(self, string): + self.ui.dataText.append("[" + str(datetime.datetime.now()) + "] " + str(string)) + + +class ScanWorker(QObject): + + log_signal = pyqtSignal(str) + exit_signal = pyqtSignal() + + def __init__(self, ip_generator, scanner, storage, **kwargs): + super().__init__() + self.ip_generator = ip_generator + self.storage = storage + self.scanner = scanner + self.previous_address = None + self.isRunning = True + + @pyqtSlot() + def work(self): + while self.isRunning: + scan_address = self.ip_generator.get_next_address(self.previous_address) + if not scan_address: + break + self.previous_address = scan_address + scan_result = self.scanner.scan_address(scan_address) + self.storage.put_responce(scan_address, scan_result) + string_scan_address = " ".join(key + ":" + str(scan_address[key]) for + key in scan_address.keys()) + if scan_result == 0: + self.log_signal.emit('%s is open' % string_scan_address) + else: + self.log_signal.emit('%s is closed' % string_scan_address) + self.stop() + + def stop(self): + self.isRunning = False + self.exit_signal.emit() diff --git a/core/__init__.py b/core/__init__.py new file mode 100644 index 0000000..d98799d --- /dev/null +++ b/core/__init__.py @@ -0,0 +1,6 @@ +import sys +import os + + +fil = __file__[:__file__.rindex(os.sep)] +sys.path.insert(0,fil) diff --git a/core/communication/CommunicationDictionary.py b/core/communication/CommunicationDictionary.py new file mode 100644 index 0000000..638858f --- /dev/null +++ b/core/communication/CommunicationDictionary.py @@ -0,0 +1,22 @@ +class CommunicationDictionary(dict): + '''This class is used to provide communication between classes using + key-value interface''' + def __init__(self, convert_table): + '''Convert table stores functions used to get value of unable key if + it's possible''' + super(CommunicationDictionary, self).__init__() + self.convert_table = convert_table + + def __getitem__(self, key): + item = None + try: + item = dict.__getitem__(self,key) + except KeyError: + key_to_convert, convert_function = self.convert_table.get_converter( + self.keys(), + key + ) + if key_to_convert is not None: + item = convert_function(dict.__getitem__(self,key_to_convert)) + + return item diff --git a/core/communication/ConvertTable.py b/core/communication/ConvertTable.py new file mode 100644 index 0000000..741b5d2 --- /dev/null +++ b/core/communication/ConvertTable.py @@ -0,0 +1,31 @@ +def convert_function(_from, _to): + '''This decorator is simple way to declare ability of function to be + converter from name _from to name _to in convert table''' + def real_decorator(function): + function.__from__ = _from + function.__to__ = _to + return function + return real_decorator + + +class ConvertTable(): + '''The class is used to store and find the right function to convert value from + one key to another''' + def __init__(self): + self.convert_functions = [] + + def add_function(self, function): + '''Here you can add function to ConvertTable.''' + #TODO: make this method produce new functions, that will be able to + #create converter chains + print("adding function", function) + self.convert_functions.append(function) + + def get_converter(self, from_keys, to_key): + '''This function returns converter function, that can convert one key + to another''' + for function in self.convert_functions: + if function.__from__ in from_keys and function.__to__ == to_key: + return function.__from__, function + print("Can't find converter!") + return None, None diff --git a/core/import_utils.py b/core/import_utils.py new file mode 100644 index 0000000..7329f13 --- /dev/null +++ b/core/import_utils.py @@ -0,0 +1,55 @@ +import importlib +from glob import glob +from os import extsep +from os.path import realpath, dirname, join, basename, splitext +import sys +from inspect import getmembers, isfunction, isclass + + +def module_paths_list(path): + return glob(join(path, "*" + extsep + "py")) + + +def modulename(file_path): + return splitext(basename(file_path))[0] + + +def module_name_list(path): + return [modulename(module_path) for module_path in + module_paths_list(path)] + + +def import_module(path): + if dirname(path) not in sys.path: + sys.path.insert(0, dirname(path)) + name = modulename(path) + module_spec = importlib.util.spec_from_file_location(name, path) + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + return module + + +def import_class(path): + return getattr(import_module(path), modulename(path)) + + +def import_matching(path, matcher_function): + matching = [] + for modulefile in module_paths_list(path): + module = import_module(modulefile) + for name, value in getmembers(module): + if matcher_function(name, value): + matching.append(value) + return matching + + +def import_functions(path): + def matcher(name, value): + return isfunction(value) + return import_matching(path, matcher) + + +def import_classes(path): + def matcher(name, value): + return isclass(value) + return import_matching(path, matcher) diff --git a/main.py b/main.py index 455abd3..92179ff 100644 --- a/main.py +++ b/main.py @@ -4,7 +4,7 @@ from PyQt5.QtCore import * from PyQt5.QtWidgets import * from PyQt5.QtGui import * from main_ui import * -import MainPresenter +import core.MainPresenter class MyWin(QtWidgets.QMainWindow): @@ -14,7 +14,7 @@ class MyWin(QtWidgets.QMainWindow): self.ui = Ui_Form() self.ui.setupUi(self) - self.presenter = MainPresenter.MainPresenter(self.ui) + self.presenter = core.MainPresenter.MainPresenter(self.ui) self.ui.startButton.clicked.connect(self.startButtonClicked) self.isScanActive = False diff --git a/modules/RawTCP.py b/modules/RawTCP.py new file mode 100644 index 0000000..b7aadd7 --- /dev/null +++ b/modules/RawTCP.py @@ -0,0 +1,98 @@ +from struct import pack, unpack +import socket + + +class TCPHeader(): + # TCP header class. Thanks to Silver Moon for the flags calculation and packing order + # This was designed to be re-used. You might want to randomize the seq number + # get_struct performs packing based on if you have a valid checksum or not + def __init__(self, src_port=47123, dst_port=80, seqnum=1000, acknum=0, data_offset=80, fin=0, syn=1, rst=0, psh=0, ack=0, urg=0, window=5840, check=0, urg_ptr=0): + # !=network(big-endian), H=short(2), L=long(4),B=char(1) + self.order = "!HHLLBBHHH" + self.src_port = src_port + self.dst_port = dst_port + self.seqnum = seqnum + self.acknum = acknum + # size of tcp header; size is specified by 4-byte words; This is 80 + # decimal, which is 0x50, which is 20bytes (5words*4bytes). + self.data_offset = data_offset + self.fin = fin + self.syn = syn + self.rst = rst + self.psh = psh + self.ack = ack + self.urg = urg + self.window = socket.htons(window) + self.check = check + self.urg_ptr = urg_ptr + + def flags(self): + return self.fin + (self.syn << 1) + (self.rst << 2) + (self.psh << 3) + (self.ack << 4) + (self.urg << 5) + + def get_struct(self, check=False, checksummed=False): + if check is not False: + self.check = check + if checksummed: + return pack('!HHLLBBH', self.src_port, self.dst_port, self.seqnum, self.acknum, self.data_offset, self.flags(), self.window) + pack('H', self.check) + pack('!H', self.urg_ptr) + else: + return pack(self.order, self.src_port, self.dst_port, self.seqnum, self.acknum, self.data_offset, self.flags(), self.window, self.check, self.urg_ptr) + + +def checksum(msg): + # Shoutout to Silver Moon @ binarytides for this checksum algo. + sum = 0 + for i in range(0, len(msg), 2): + w = msg[i] + (msg[i + 1] << 8) + sum = sum + w + + sum = (sum >> 16) + (sum & 0xffff) + sum = sum + (sum >> 16) + sum = ~sum & 0xffff + return sum + + +def tcp_checksum(source_ip, dest_ip, tcp_header, user_data=b''): + # Calculates the correct checksum for the tcp header + tcp_length = len(tcp_header) + len(user_data) + # This is an IP header w/ TCP as protocol. + ip_header = pack('!4s4sBBH', socket.inet_aton(source_ip), socket.inet_aton( + dest_ip), 0, socket.IPPROTO_TCP, tcp_length) + # Assemble the packet (IP Header + TCP Header + data, and then send it to + # checksum function) + packet = ip_header + tcp_header + user_data + return checksum(packet) + + +def handle_packet(raw_packet): + # Now we need to unpack the packet. It will be an IP/TCP packet + # We are looking for SYN-ACKs from our SYN scan + # Fields to check: IP - src addr; TCP - src port, flags + # We want to pull out and compare only these three + # Heres the math for unpacking: B=1, H=2, L=4, 4s=4 (those are bytes) + packet = raw_packet[0] + # This is the IP header, not including any self.options OR THE DST ADDR. + # Normal length is 20!! Im parsing as little as possible + ip_header = unpack('!BBHHHBBH4s', packet[0:16]) + # If there are any self.options, the length of the IP header will be >20. We + # dont care about self.options + ip_header_length = (ip_header[0] & 0xf) * 4 + # This is the source address (position 8, or the first "4s" in our + # unpack) + src_addr = socket.inet_ntoa(ip_header[8]) + + # We had to get the proper IP Header length to find the TCP header + # offset. + tcp_header_raw = packet[ip_header_length:ip_header_length + 14] + # TCP header structure is pretty straight-forward. We want PORTS and + # FLAGS, so we partial unpack it + tcp_header = unpack('!HHLLBB', tcp_header_raw) + + src_port = tcp_header[0] # self-explanatory + dst_port = tcp_header[1] # self-explanatory FIXME: notused + # We only care about syn-ack, which will be 18 (0x12) + flag = tcp_header[5] + + if flag == 18: + return (src_addr, src_port) + else: + return None diff --git a/modules/TCPScan.py b/modules/TCPScan.py new file mode 100644 index 0000000..dd77e13 --- /dev/null +++ b/modules/TCPScan.py @@ -0,0 +1,89 @@ +from RawTCP import TCPHeader, handle_packet, tcp_checksum +import socket +import sys + + +class TCPScanner: + def __init__(self): + self.source_ips = {} + # TODO: options, threading, input/output queues + self.options = None + return + + def in_scope(self): + """Check that IP is in scanning scope""" + # TODO + pass + + def tcp_listener(self): + # Raw socket listener for when send_raw_syn() is used. This will catch + # return SYN-ACKs + listen = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + while True: + # packet = ('E \x00(\x1f\xaa@\x00w\x06\x99w2\xe0\xc8\xa2\xa2\xf3\xac\x18\xdf\xb3\x00\x16\xb6\x80\xc1\xa0/\xa6=$P\x10\xce\xab\xd1\xe4\x00\x00', ('50.XXX.200.162', 0)) + raw_packet = listen.recvfrom(65565) + ret = handle_packet(raw_packet) + if ret is None: + continue + src_addr, src_port = ret + if self.in_scope(src_addr) and src_port in self.ports: + self.output_queue.put((src_addr, src_port)) + + def send_raw_syn(self, dest_ip, dst_port): + # Use raw sockets to send a SYN packet. + # If you want, you could use the IP header assembled in the tcp_checksum + # function to have a fully custom TCP/IP stack + try: + # Using IPPROTO_TCP so the kernel will deal with the IP packet for us. + # Change to IPPROTO_IP if you want control of IP header as well + s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) + except Exception: + sys.stderr.write("Error creating socket in send_raw_syn\n") + return + if self.options.source == "auto": + # This gets the correct source IP. Just in case of multiple interfaces, + # it will pick the right one + src_addr = self.get_source_ip(dest_ip) + else: + src_addr = self.options.source + src_port = 54321 + make_tcpheader = TCPHeader(src_port, dst_port) + tcp_header = make_tcpheader.get_struct() + packet = make_tcpheader.get_struct(check=tcp_checksum( + src_addr, dest_ip, tcp_header), checksummed=True) + try: + s.sendto(packet, (dest_ip, 0)) + except Exception as e: + sys.stderr.write("Error utilizing raw socket in send_raw_syn: {}\n".format(e)) + + def get_source_ip(self, dst_addr): + # Credit: 131264/alexander from stackoverflow. This gets the correct IP for sending. Useful if you have multiple interfaces + # NOTE: This will send an additional packet for every single IP to confirm + # the route. (but just one packet) + try: + if dst_addr in self.source_ips: + return self.source_ips[dst_addr] + else: + self.source_ips[dst_addr] = [(s.connect((dst_addr, 53)), s.getsockname()[0], s.close( + )) for s in [socket.socket(socket.AF_INET, socket.SOCK_DGRAM)]][0][1] + return self.source_ips[dst_addr] + except Exception: + sys.stderr.write( + "Something went wrong in get_source_ip, results might be wrong\n") + + +def send_full_connect_syn(ip, port, timeout): + # Normal scan using socket to connect. Does 3-way handshack, then graceful + # teardown using FIN + try: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + except Exception as e: + sys.stderr.write("Error creating socket in send_full_connect_syn: {}\n".format(e)) + return False + try: + s.connect((ip, port)) + return True + s.close() + except Exception: + return False diff --git a/modules/address_generation/AbstractAddressGenerator.py b/modules/address_generation/AbstractAddressGenerator.py new file mode 100644 index 0000000..ece05f2 --- /dev/null +++ b/modules/address_generation/AbstractAddressGenerator.py @@ -0,0 +1,15 @@ +from abc import abstractmethod, ABC +from core.communication import CommunicationDictionary + + +class AbstractAddressGenerator(ABC): + '''The class describes addess generation mechanism. + In __init__ method it should get results of parsing fields + and then it returns addresses.''' + @abstractmethod + def get_next_address(self, previous_address, **kwargs) -> CommunicationDictionary: + '''Address - an only, indivisible object, that describes single scan + target address. This method should return next address to scan based on + previous scanned address and result of scanning previous address, that + can be placed in kwargs.''' + pass diff --git a/modules/address_generation/AbstractParser.py b/modules/address_generation/AbstractParser.py new file mode 100644 index 0000000..1e02bdc --- /dev/null +++ b/modules/address_generation/AbstractParser.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod + + +class AbstractParser(ABC): + '''The class describes fields parsing mechanisms''' + + @abstractmethod + def parse_address_field(self, field): + '''In address field can be plased any text, describing address of + scanning target''' + pass + + @abstractmethod + def parse_port_field(self, field): + '''In port field only numbers, whitespaces, comma and '-' allowed''' + pass diff --git a/modules/address_generation/IpGenerator.py b/modules/address_generation/IpGenerator.py new file mode 100644 index 0000000..2623bf9 --- /dev/null +++ b/modules/address_generation/IpGenerator.py @@ -0,0 +1,32 @@ +from AbstractAddressGenerator import AbstractAddressGenerator +from threading import RLock +from core.communication.CommunicationDictionary import CommunicationDictionary + +class IpGenerator(AbstractAddressGenerator): + + def __init__(self, ip_generator, ports, convert_table): + self.convert_table = convert_table + self.ip_generator = ip_generator + self.ports = ports + self.lock = RLock() + + def get_next_port_number(self, previous_port): + return (self.ports.index(previous_port) + 1) % len(self.ports) + + def get_next_address(self, previous_address): + result = CommunicationDictionary(self.convert_table) + with self.lock: + portnum = 0 + next_ip = None + if previous_address: + next_ip = previous_address["ipv4"] + port = previous_address["port"] + portnum = self.get_next_port_number(port) + if (portnum == 0): + try: + next_ip = next(self.ip_generator) + except StopIteration: + return None + result["ipv4"] = next_ip + result["port"] = self.ports[portnum] + return result diff --git a/modules/address_generation/Parser.py b/modules/address_generation/Parser.py new file mode 100644 index 0000000..bf1a079 --- /dev/null +++ b/modules/address_generation/Parser.py @@ -0,0 +1,80 @@ +import ipaddress +from AbstractParser import AbstractParser + + +class Parser(AbstractParser): + + def parse_port_field(self, ports): + """ + Parses ports from string, returns them as integers in the list. + Handles non-existent ports and non-port values. + """ + if ports: + # Using set to avoid repetitions + parsed = set() + ports = ports.split(",") + for port in ports: + try: + # Input is in range form ("100-200"): + if '-' in port: + start, end = map(int, port.split('-')) + parsed.update( + [p for p in range(start, end + 1) if 65355 >= p > 0]) + # Input is a single port ("80"): + else: + parsed.add(int(port)) + except ValueError as e: + # If we get any not integer just ignore it + pass + return sorted(list(parsed)) + else: + # Change to default ports from constant + return [21, 22, 23, 25, 80, 443, 110, 111, 135, 139, 445, 8080, 8443, 53, 143, 989, 990, 3306, 1080, 5554, 6667, 2222, 4444, 666, 6666, 1337, 2020, 31337] + + def parse_address_field(self, ips): + """ + Parses ip input string, returns the generator over them. + + Supports next inputs: + 1) 1.2.3.4 + 2) 192.168.0.0/24 + 3) 1.2.3.4 - 5.6.7.8 + Any non-ip value will be ignored. + """ + + # A set to contain non repeating ip objects from ipaddress + ip_objects = set() + inputs = [ip.strip() for ip in ips.split(',')] + + for input_ in inputs: + try: + # Input is in range form ("1.2.3.4 - 5.6.7.8"): + if '-' in input_: + input_ips = input_.split('-') + ranges = set( + ipaddress.summarize_address_range( + *map(lambda x: ipaddress.IPv4Address(x.strip()), input_ips) + ) + ) + ip_objects.update(ranges) + # Input is in CIDR form ("192.168.0.0/24"): + elif '/' in input_: + network = ipaddress.ip_network(input_) + ip_objects.add(network) + # Input is a single ip ("1.1.1.1"): + else: + ip = ipaddress.ip_address(input_) + ip_objects.add(ip) + except ValueError as e: + print(e) + # If we get any non-ip value just ignore it + pass + + for ip_obj in ip_objects: + # The object is just one ip, simply yield it: + if isinstance(ip_obj, ipaddress.IPv4Address): + yield ip_obj + # The object is a network, yield every host in it: + else: + for host in ip_obj: + yield host diff --git a/modules/address_generation/__init__.py b/modules/address_generation/__init__.py new file mode 100644 index 0000000..d98799d --- /dev/null +++ b/modules/address_generation/__init__.py @@ -0,0 +1,6 @@ +import sys +import os + + +fil = __file__[:__file__.rindex(os.sep)] +sys.path.insert(0,fil) diff --git a/modules/address_generation/test_IpGenerator.py b/modules/address_generation/test_IpGenerator.py new file mode 100644 index 0000000..2979b4d --- /dev/null +++ b/modules/address_generation/test_IpGenerator.py @@ -0,0 +1,27 @@ +from unittest import TestCase, main +from ipaddress import IPv4Address +from IpGenerator import IpGenerator +from Parser import Parser + + +class testIpGenerator(TestCase): + + def setUp(self): + p = Parser() + self.ipgen = IpGenerator( + p.parse_address_field("192.168.1.1 - 192.168.1.10"), [80, 90]) + + def testIpGeneration(self): + '''self.assertEqual( + self.ipgen.get_next_address(None), + ("192.168.1.1", 80))''' + previous_address = None + a = True + while previous_address or a: + previous_address=self.ipgen.get_next_address(previous_address) + a = False + self.assertEqual(self.ipgen.get_next_address(None), None) + + +if __name__ == "__main__": + main() diff --git a/modules/address_generation/test_Parser.py b/modules/address_generation/test_Parser.py new file mode 100644 index 0000000..9ca9456 --- /dev/null +++ b/modules/address_generation/test_Parser.py @@ -0,0 +1,37 @@ +from unittest import main, TestCase +from Parser import Parser +import ipaddress + + +class TestParser(TestCase): + + def setUp(self): + self.parser = Parser() + + def test_port_parsing(self): + self.assertEqual(self.parser.parse_port_field("80,90"), [80, 90]) + + def test_ip_parsing(self): + self.assertEqual( + set(self.parser.parse_address_field("192.168.1.1 - 192.168.1.3")), + {ipaddress.IPv4Address(ip) for ip in + [ + "192.168.1.1", + "192.168.1.2", + "192.168.1.3" + ]}) + self.assertEqual( + set(self.parser.parse_address_field("192.168.1.1")), + {ipaddress.IPv4Address("192.168.1.1")} + ) + self.assertEqual( + set(self.parser.parse_address_field("192.168.1.0/31")), + {ipaddress.IPv4Address(ip) for ip in + [ + '192.168.1.1', + '192.168.1.0' + ]}) + + +if __name__ == "__main__": + main() diff --git a/modules/convert_functions/Ipv4toString.py b/modules/convert_functions/Ipv4toString.py new file mode 100644 index 0000000..dc55801 --- /dev/null +++ b/modules/convert_functions/Ipv4toString.py @@ -0,0 +1,5 @@ +from core.communication.ConvertTable import convert_function + +@convert_function("ipv4","str") +def ipv4tostring(ip): + return str(ip) diff --git a/modules/network_scan/AbstractScanner.py b/modules/network_scan/AbstractScanner.py new file mode 100644 index 0000000..8e59b25 --- /dev/null +++ b/modules/network_scan/AbstractScanner.py @@ -0,0 +1,17 @@ +from abc import ABC, abstractmethod +from core.communication.CommunicationDictionary import CommunicationDictionary + +class AbstractScanner(ABC): + '''The class is used by one thread to scan targets''' + + @abstractmethod + def __init__(self, **kwargs): + '''In this method you can init some + reusable resources needed for scan''' + pass + + @abstractmethod + def scan_address(self, address) -> CommunicationDictionary: + '''This method should contain scanning process of given address. All + items returned will be passed to AbstractStorage and ui''' + pass diff --git a/modules/network_scan/CoreModel.py b/modules/network_scan/CoreModel.py new file mode 100644 index 0000000..fa7613e --- /dev/null +++ b/modules/network_scan/CoreModel.py @@ -0,0 +1,17 @@ +import socket +from AbstractScanner import AbstractScanner +from core.communication.CommunicationDictionary import CommunicationDictionary + +class CoreModel(AbstractScanner): + def __init__(self, timeout): + self.defSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.defSocket.settimeout(int(timeout)) + + def scan_address(self, address: CommunicationDictionary) -> CommunicationDictionary: + host = address["str"] + port = address["port"] + if not host: raise Exception + result = self.defSocket.connect_ex((host, port)) + self.defSocket.close() + self.defSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + return result diff --git a/modules/network_scan/__init__.py b/modules/network_scan/__init__.py new file mode 100644 index 0000000..d98799d --- /dev/null +++ b/modules/network_scan/__init__.py @@ -0,0 +1,6 @@ +import sys +import os + + +fil = __file__[:__file__.rindex(os.sep)] +sys.path.insert(0,fil) diff --git a/modules/storage/AbstractStorage.py b/modules/storage/AbstractStorage.py new file mode 100644 index 0000000..16c30ad --- /dev/null +++ b/modules/storage/AbstractStorage.py @@ -0,0 +1,12 @@ +from abc import ABC, abstractmethod + + +class AbstractStorage(ABC): + + @abstractmethod + def put_responce(self, address, responce): + pass + + @abstractmethod + def save(self): + pass diff --git a/modules/storage/JSONStorage.py b/modules/storage/JSONStorage.py new file mode 100644 index 0000000..3fa9918 --- /dev/null +++ b/modules/storage/JSONStorage.py @@ -0,0 +1,23 @@ +from AbstractStorage import AbstractStorage +import json +from threading import RLock + + +class JSONStorage(AbstractStorage): + + def __init__(self, path): + self.path = path + self.respdict = dict() + self.lock = RLock() + + def put_responce(self, address, responce): + ip, port = address + if ip not in self.respdict.keys(): + self.respdict[ip] = {"open": [], "close": []} + self.respdict[ip]["open" if responce != 0 else "close"].append(port) + + def save(self): + print("saving") + with open(self.path, "w") as f: + json.dump(self.respdict, f) + self.respdict = {} diff --git a/modules/storage/__init__.py b/modules/storage/__init__.py new file mode 100644 index 0000000..d98799d --- /dev/null +++ b/modules/storage/__init__.py @@ -0,0 +1,6 @@ +import sys +import os + + +fil = __file__[:__file__.rindex(os.sep)] +sys.path.insert(0,fil)