From 9866dfb204b8bdbb5bf341204371a4e4e458a9c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Benencia?= Date: Wed, 3 Oct 2012 19:46:06 -0300 Subject: Global refactoring --- core.py | 127 --------------------------------------- lib/__init__.py | 0 lib/config.py | 16 +++++ lib/core.py | 139 +++++++++++++++++++++++++++++++++++++++++++ lib/model.py | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ model.py | 179 ------------------------------------------------------- pywhoisd.conf | 8 ++- pywhoisd.py | 33 +++++++---- res/footer.txt | 2 + res/header.txt | 11 ++++ 10 files changed, 375 insertions(+), 321 deletions(-) delete mode 100644 core.py create mode 100644 lib/__init__.py create mode 100644 lib/config.py create mode 100644 lib/core.py create mode 100644 lib/model.py delete mode 100644 model.py create mode 100644 res/footer.txt create mode 100644 res/header.txt diff --git a/core.py b/core.py deleted file mode 100644 index 300f6ff..0000000 --- a/core.py +++ /dev/null @@ -1,127 +0,0 @@ -import socketserver -import ipaddr -import re - -class Daemon(): - def __init__(self, data): - self.data = data - self.domain_regexp = re.compile("(?!-)[A-Z\d-]{1,63}(?\n'.format(adm.name, adm.surname, adm.email) - else: - result += '{0}: {1}\n'.format(k, v) - - return result - - def search_ip(self, ip): - result = {} - - # Iterate over all IP block elements - networks = self.data.get_networks() - for key in networks: - for block in networks[key].ip_blocks: - if ipaddr.IPAddress(ip) in ipaddr.IPNetwork(block): - return networks[key].as_dict(self.data) - - result['error'] = "Network not found" - return result - - def search_domain(self, domain): - # Iterate over all network and check its domains - networks = self.data.get_networks() - domains = self.data.get_domains() - for network in networks.values(): - if any(domains[d].name == domain for d in network.domains): - return network.as_dict(self.data) - - return {'error':'Domain not found'} - - # TODO - - def search_person(self, query): - pass - - def is_ip(self, query): - try: - ipaddr.IPAddress(query) - return True - except ValueError: - return False - - def is_domain(self, hostname): - if len(hostname) > 255: - return False - - if hostname[-1:] == ".": - hostname = hostname[:-1] # strip exactly one dot from the right, if present - - return all(self.domain_regexp.match(x) for x in hostname.split(".")) - - # TODO - def get_help(self): - return "This will be the help" - - # TODO - def get_footer(self): - return "This will be the footer" - - # TODO - def get_header(self): - return "This will be the header" - -class WhoisHandler(socketserver.BaseRequestHandler): - - def setup(self): - self.daemon = self.server.daemon - - def handle(self): - data = str(self.request.recv(100).strip(), 'utf-8') - print('Received: {}'.format(data)) - - #response = self.daemon.get_header() + " \n" - response = self.daemon.query(data) - #response += self.daemon.get_footer() + "\n" - - self.request.sendall(bytes(response, 'utf-8')) - -class ClassicServer(socketserver.ThreadingTCPServer): - def __init__(self, config, daemon): - host = config['Servers']['classic_host'] - port = int(config['Servers']['classic_port']) - self.daemon = daemon - - socketserver.ThreadingTCPServer.__init__(self, (host, port), WhoisHandler) - -class WebServer(socketserver.ThreadingTCPServer): - def __init__(self, config, daemon): - self.host = config['Servers']['web_host'] - self.port = int(config['Servers']['web_port']) - self.daemon = daemon - - socketserver.ThreadingTCPServer.__init__(self, (self.host, self.port), WhoisHandler) diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/config.py b/lib/config.py new file mode 100644 index 0000000..f4fab70 --- /dev/null +++ b/lib/config.py @@ -0,0 +1,16 @@ +import configparser + +class Config: + _instance = None + + def __new__(cls, *args, **kwargs): + if not cls._instance: + cls._instance = super(Config, cls).__new__( + cls, *args, **kwargs) + + return cls._instance + + def __init__(self): + self.parser = configparser.ConfigParser() + self.parser.read('pywhoisd.conf') # Find a way not to hardcode this + diff --git a/lib/core.py b/lib/core.py new file mode 100644 index 0000000..e7ac571 --- /dev/null +++ b/lib/core.py @@ -0,0 +1,139 @@ +import socketserver +import ipaddr +import re + +from lib.config import Config + +class Daemon(): + def __init__(self, data): + self.data = data + self.domain_regexp = re.compile("(?!-)[A-Z\d-]{1,63}(?\n'.format(adm.name, adm.surname, adm.email) + else: + result += '{0}: {1}\n'.format(k, v) + + return result + + def search_ip(self, ip): + result = {} + + # Iterate over all IP block elements + networks = self.data.get_networks() + for key in networks: + for block in networks[key].ip_blocks: + if ipaddr.IPAddress(ip) in ipaddr.IPNetwork(block): + return networks[key].as_dict(self.data) + + result['error'] = "Network not found" + return result + + def search_domain(self, domain): + # Iterate over all network and check its domains + networks = self.data.get_networks() + domains = self.data.get_domains() + for network in networks.values(): + if any(domains[d].name == domain for d in network.domains): + return network.as_dict(self.data) + + return {'error':'Domain not found'} + + # TODO + + def search_person(self, query): + pass + + def is_ip(self, query): + try: + ipaddr.IPAddress(query) + return True + except ValueError: + return False + + def is_domain(self, hostname): + if len(hostname) > 255: + return False + + if hostname[-1:] == ".": + hostname = hostname[:-1] # strip exactly one dot from the right, if present + + return all(self.domain_regexp.match(x) for x in hostname.split(".")) + + # TODO + def get_help(self): + return "This will be the help" + + def get_footer(self): + if not self.footer: + f = open(Config().parser['Printing']['footer']) + self.footer = f.read() + f.close() + + return self.footer + + def get_header(self): + if not self.header: + f = open(Config().parser['Printing']['header']) + self.header = f.read() + f.close() + + return self.header + +class WhoisHandler(socketserver.BaseRequestHandler): + + def setup(self): + self.daemon = self.server.daemon + + def handle(self): + data = str(self.request.recv(100).strip(), 'utf-8') + print('Received: {}'.format(data)) + + response = self.daemon.get_header() + response += self.daemon.query(data) + response += self.daemon.get_footer() + + self.request.sendall(bytes(response, 'utf-8')) + +class ClassicServer(socketserver.ThreadingTCPServer): + def __init__(self, daemon): + host = Config().parser['Servers']['classic_host'] + port = int(Config().parser['Servers']['classic_port']) + self.daemon = daemon + + socketserver.ThreadingTCPServer.__init__(self, (host, port), WhoisHandler) + +class WebServer(socketserver.ThreadingTCPServer): + def __init__(self, daemon): + self.host = Config().parser['Servers']['web_host'] + self.port = int(Config().parser['Servers']['web_port']) + self.daemon = daemon + + socketserver.ThreadingTCPServer.__init__(self, (self.host, self.port), WhoisHandler) diff --git a/lib/model.py b/lib/model.py new file mode 100644 index 0000000..933d901 --- /dev/null +++ b/lib/model.py @@ -0,0 +1,181 @@ +from xml.etree.ElementTree import ElementTree + +from lib.config import Config + +class Network(): + """A simple network definition""" + + def __init__(self): + self.name = "" + self.domains = [] + self.admins = [] + self.ip_blocks = [] + self.data = {} + + def as_dict(self, data): + # Beautify + result = {} + domains = data.get_domains() + persons = data.get_persons() + + result['name'] = self.name + + # Domains + result['domains'] = [] + for d in self.domains: + result['domains'].append(domains[d]) + + # Admins + result['admins'] = [] + for a in self.admins: + result['admins'].append(persons[a]) + + # Networks + result['networks'] = self.ip_blocks + + return result + +class Domain(): + """A simple domain definition""" + + def __init__(self): + self.name = "" + self.admins = [] + self.data = {} + + def add_admin(self, admin): + """Add an administrator for this network""" + + self.admins.append(admin) + +class Person(): + """A simple person definition""" + + def __init__(self): + self.name = "" + self.surname = "" + self.email = "" + self.data = {} + +class Data(): + """Abstract class for storing and getting information""" + + def __init__(self): + self.config = Config().parser + self.networks = None + self.domains = None + self.persons = None + + def parse_config(self): + """Parse neccesary config params depending on the method used + + Abstract method""" + + pass + + def load_data(self): + """Load data from defined source. + + Abstract method""" + + pass + + def get_networks(self): + """Return all networks. Common method for all kind of storages.""" + + if self.networks == None: + self.load_data() + + return self.networks + + def get_domains(self): + """Return all domains. Common method for all kind of storages.""" + + if self.networks == None: + self.load_data() + + return self.domains + + def get_persons(self): + """Return all persons. Common method for all kind of storages.""" + + if self.persons == None: + self.load_data() + + return self.persons + +class DataXML(Data): + """Reads network information from a XML file""" + + def parse_config(self): + """Reads and sets up XML config file fields""" + + self.data_file = self.config['Storage']['xml_file'] + + def load_data(self): + """Parse XML for getting network information""" + + self.parse_config() + root = ElementTree(file=self.data_file).getroot() + + self.networks = {} + self.domains = {} + self.persons = {} + + for elem in root: + if elem.tag == 'person': + self.add_person(elem) + elif elem.tag == 'domain': + self.add_domain(elem) + elif elem.tag == 'network': + self.add_network(elem) + else: + pass # raise TagNotFoundError + + def add_person(self, elem): + """Adds a new person""" + + person = Person() + for e in elem: + if e.tag == 'name': + person.name = e.text + elif e.tag == 'surname': + person.surname = e.text + elif e.tag == 'email': + person.email = e.text + else: + person.data[e.tag] = e.text + + print("[+] Read person: {0} - {1} - {2}".format(person.name, person.surname, person.email)) + self.persons[elem.attrib['id']] = person + + def add_domain(self, elem): + """Adds a new domain""" + + domain = Domain() + for e in elem: + if e.tag == 'name': + domain.name = e.text + else: + domain.data[e.tag] = e.text + + print("[+] Read domain: {}".format(domain.name)) + self.domains[elem.attrib['id']] = domain + + def add_network(self, elem): + """Adds a new network""" + + network = Network() + for e in elem: + if e.tag == 'name': + network.name = e.text + elif e.tag == 'domain': + network.domains.append(e.text) + elif e.tag == 'ip_block': + network.ip_blocks.append(e.text) + elif e.tag == 'admin': + network.admins.append(e.text) + else: + network.data[e.tag] = e.text + + self.networks[elem.attrib['id']] = network diff --git a/model.py b/model.py deleted file mode 100644 index ef5ce28..0000000 --- a/model.py +++ /dev/null @@ -1,179 +0,0 @@ -from xml.etree.ElementTree import ElementTree - -class Network(): - """A simple network definition""" - - def __init__(self): - self.name = "" - self.domains = [] - self.admins = [] - self.ip_blocks = [] - self.data = {} - - def as_dict(self, data): - # Beautify - result = {} - domains = data.get_domains() - persons = data.get_persons() - - result['name'] = self.name - - # Domains - result['domains'] = [] - for d in self.domains: - result['domains'].append(domains[d]) - - # Admins - result['admins'] = [] - for a in self.admins: - result['admins'].append(persons[a]) - - # Networks - result['networks'] = self.ip_blocks - - return result - -class Domain(): - """A simple domain definition""" - - def __init__(self): - self.name = "" - self.admins = [] - self.data = {} - - def add_admin(self, admin): - """Add an administrator for this network""" - - self.admins.append(admin) - -class Person(): - """A simple person definition""" - - def __init__(self): - self.name = "" - self.surname = "" - self.email = "" - self.data = {} - -class Data(): - """Abstract class for storing and getting information""" - - def __init__(self, config): - self.config = config - self.networks = None - self.domains = None - self.persons = None - - def parse_config(self): - """Parse neccesary config params depending on the method used - - Abstract method""" - - pass - - def load_data(self): - """Load data from defined source. - - Abstract method""" - - pass - - def get_networks(self): - """Return all networks. Common method for all kind of storages.""" - - if self.networks == None: - self.load_data() - - return self.networks - - def get_domains(self): - """Return all domains. Common method for all kind of storages.""" - - if self.networks == None: - self.load_data() - - return self.domains - - def get_persons(self): - """Return all persons. Common method for all kind of storages.""" - - if self.persons == None: - self.load_data() - - return self.persons - -class DataXML(Data): - """Reads network information from a XML file""" - - def parse_config(self): - """Reads and sets up XML config file fields""" - - self.data_file = self.config['Storage']['xml_file'] - - def load_data(self): - """Parse XML for getting network information""" - - self.parse_config() - root = ElementTree(file=self.data_file).getroot() - - self.networks = {} - self.domains = {} - self.persons = {} - - for elem in root: - if elem.tag == 'person': - self.add_person(elem) - elif elem.tag == 'domain': - self.add_domain(elem) - elif elem.tag == 'network': - self.add_network(elem) - else: - pass # raise TagNotFoundError - - def add_person(self, elem): - """Adds a new person""" - - person = Person() - for e in elem: - if e.tag == 'name': - person.name = e.text - elif e.tag == 'surname': - person.surname = e.text - elif e.tag == 'email': - person.email = e.text - else: - person.data[e.tag] = e.text - - print("[+] Read person: {0} - {1} - {2}".format(person.name, person.surname, person.email)) - self.persons[elem.attrib['id']] = person - - def add_domain(self, elem): - """Adds a new domain""" - - domain = Domain() - for e in elem: - if e.tag == 'name': - domain.name = e.text - else: - domain.data[e.tag] = e.text - - print("[+] Read domain: {}".format(domain.name)) - self.domains[elem.attrib['id']] = domain - - def add_network(self, elem): - """Adds a new network""" - - network = Network() - for e in elem: - if e.tag == 'name': - network.name = e.text - elif e.tag == 'domain': - network.domains.append(e.text) - elif e.tag == 'ip_block': - network.ip_blocks.append(e.text) - elif e.tag == 'admin': - network.admins.append(e.text) - else: - network.data[e.tag] = e.text - - self.networks[elem.attrib['id']] = network diff --git a/pywhoisd.conf b/pywhoisd.conf index b18da39..d01cfed 100644 --- a/pywhoisd.conf +++ b/pywhoisd.conf @@ -6,7 +6,7 @@ classic = yes # Only makes sense when classic server is enabled classic_host = localhost -classic_port = 4344 +classic_port = 4343 # Run a web whois server? web = no @@ -20,4 +20,8 @@ web_port = 8080 mode = xml # Only makes sense when xml storage mode is enabled -xml_file = examples/networks.xml \ No newline at end of file +xml_file = examples/networks.xml + +[Printing] +header = res/header.txt +footer = res/footer.txt diff --git a/pywhoisd.py b/pywhoisd.py index 11ad133..ec793e7 100755 --- a/pywhoisd.py +++ b/pywhoisd.py @@ -1,16 +1,18 @@ #!/usr/bin/python3 import configparser import concurrent.futures +import signal +import sys -import core -import model +from lib import core +from lib import model +from lib.config import Config class PyWhoisD(): """Main class. It reads the configuration options and starts the server""" def __init__(self): - self.config = configparser.ConfigParser() - self.config.read('pywhoisd.conf') + self.config = Config().parser self.data = None self.daemon = None @@ -19,17 +21,20 @@ class PyWhoisD(): self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=2) - # What kind of storage are we using? + def signal_sigint(self, signal, frame): + print("[+] Received SIGINT signal. Aborting...") + + sys.exit(0) + + # What kind of storage are we using? def config_data(self): - """Config data sources. + """Config data sources.""" - At the moment only XML is supported. - """ - + #At the moment only XML is supported. mode = self.config['Storage']['mode'] if mode == 'xml': - self.data = model.DataXML(self.config) + self.data = model.DataXML() def config_daemon(self): """Config common information source for all configured servers""" @@ -53,12 +58,12 @@ class PyWhoisD(): """Sets up server configuration from config files""" if self.classic(): - self.classic_server = core.ClassicServer(self.config, self.daemon) + self.classic_server = core.ClassicServer(self.daemon) else: print("[+] Classic server is not enabled") if self.web(): - self.web_server = core.WebServer(self.config, self.daemon) + self.web_server = core.WebServer(self.daemon) else: print("[+] Web server is not enabled") @@ -77,11 +82,13 @@ class PyWhoisD(): def main(self): + signal.signal(signal.SIGINT, self.signal_sigint) + self.config_daemon() self.start_servers() # Wait for running server to finish. Probably never. - self.executor.shutdown() + # self.executor.shutdown() if __name__ == "__main__": pwd = PyWhoisD() diff --git a/res/footer.txt b/res/footer.txt new file mode 100644 index 0000000..6139265 --- /dev/null +++ b/res/footer.txt @@ -0,0 +1,2 @@ + +-----END WHOIS ANSWER----- diff --git a/res/header.txt b/res/header.txt new file mode 100644 index 0000000..5eaf530 --- /dev/null +++ b/res/header.txt @@ -0,0 +1,11 @@ + @@@@@ @@@@@ @@@@@ @@@@@ @@@@@ @@@@@@@@@@@@ + @@@@@ @@@@@ @@@@@@ @@@@@ @@@@@ @@@@@@@@@@@@@ + @@@@ @@@@ @@@@@@ @@@@ @@@@ @@@@ @@@@ + @@@@ @@@@ @@@@@@@ @@@@ @@@@ @@@@@@@@@@@@ + @@@@ @@@@ @@@@@@@@@@@@ @@@@ @@@@@@@@@@@ + @@@@ @@@@ @@@@ @@@@@@@ @@@@ @@@@ + @@@@@@@@@@@@ @@@@@ @@@@@@@ @@@@@@@@@@@@ @@@@@ + @@@@@@@@@@ @@@@@ @@@@@@ @@@@@@@@@@@@ @@@@@ + +-----BEGIN WHOIS ANSWER----- + -- cgit v1.2.3