예제 #1
0
    def __init__(self, *, cfirewall):
        self._initialize = Initialize(Log, 'FirewallControl')

        self.BEFORE = {}
        self.MAIN = {}
        self.AFTER = {}

        # reference to extension CFirewall, which handles nfqueue and initial packet rcv.
        # we will use this reference to modify firewall rules which will be internally accessed
        # by the inspection function callbacks
        self.cfirewall = cfirewall
예제 #2
0
class Configuration:
    _service_setup = False

    def __init__(self, name):
        self.Initialize = Initialize(Log, name)

    @classmethod
    def service_setup(cls, SyslogService):
        '''start threads for tasks required by the syslog service. blocking until settings are loaded/initialized.'''
        if (cls._service_setup):
            raise RuntimeError('service setup should only be called once.')

        cls._service_setup = True

        self = cls(SyslogService.__name__)
        self.SyslogService = SyslogService

        self.Initialize.wait_for_threads(count=1)
        threading.Thread(target=self.get_settings).start()

    @cfg_read_poller('syslog_client')
    def get_settings(self, cfg_file):
        syslog = load_configuration(cfg_file)['syslog']

        SyslogService = self.SyslogService

        SyslogService.syslog_enabled   = syslog['enabled']
        SyslogService.syslog_protocol  = syslog['protocol']
        SyslogService.tls_enabled      = syslog['tls']['enabled']
        SyslogService.self_signed_cert = syslog['tls']['self_signed']
        SyslogService.tcp_fallback     = syslog['tcp']['fallback']
        SyslogService.udp_fallback     = syslog['udp']['fallback']

        syslog_servers = syslog['servers']

        # if service is started without servers configured we will return here.
        if not syslog_servers: return

        names = ['primary', 'secondary']
        with SyslogService.server_lock:
            for name, cfg_server, mem_server in zip(names, syslog_servers.values(), SyslogService.syslog_servers):
                if (cfg_server['ip_address'] == mem_server.get('ip')): continue

                getattr(SyslogService.syslog_servers, name).update({
                    'ip': syslog_servers[name]['ip_address'],
                    PROTO.UDP: True, PROTO.TCP: True, PROTO.DNS_TLS: True
                })

    def get_interface_settings(self):
        interface_settings = load_configuration('config.json')
        self.lan_int = interface_settings['settings']['interface']['inside']
예제 #3
0
class FirewallControl:

    __slots__ = (
        'cfirewall',
        '_initialize',

        # firewall sections (hierarchy)
        # NOTE: these are used primarily to detect config changes to prevent
        # the amount of work/ data conversions that need to be done to load
        # the settings into C data structures.
        'BEFORE',
        'MAIN',
        'AFTER')

    def __init__(self, *, cfirewall):
        self._initialize = Initialize(Log, 'FirewallControl')

        self.BEFORE = {}
        self.MAIN = {}
        self.AFTER = {}

        # reference to extension CFirewall, which handles nfqueue and initial packet rcv.
        # we will use this reference to modify firewall rules which will be internally accessed
        # by the inspection function callbacks
        self.cfirewall = cfirewall

    # threads will be started here.
    def run(self):

        self._init_system_rules()

        threading.Thread(target=self.monitor_zones).start()
        threading.Thread(target=self.monitor_rules).start()

        self._initialize.wait_for_threads(count=2)

    @cfg_read_poller('zone_map', folder='iptables')
    # zone int values are arbitrary / randomly selected on zone creation.
    def monitor_zones(self, zone_map):
        '''calls to Cython are made from within this method block. the GIL must be manually acquired on the Cython
        side or the Python interpreter will crash. Monitors the firewall zone file for changes and loads updates to
        cfirewall.'''

        dnx_zones = load_configuration(zone_map,
                                       filepath='dnx_system/iptables')

        # converting list to python array, then sending to Cython to modify C array.
        # this format is required due to transitioning between python and C. python arrays are
        # compatible in C via memory views and Cython can handle the initial list.
        dnx_zones = array('i', dnx_zones['map'])

        print(f'sending zones to CFirewall: {dnx_zones}')

        # NOTE: gil must be held on the other side of this call
        error = self.cfirewall.update_zones(dnx_zones)
        if (error):
            pass  # TODO: do something here

        self._initialize.done()

    @cfg_read_poller('firewall_active', folder='iptables')
    def monitor_rules(self, fw_rules):
        '''calls to Cython are made from within this method block. the GIL must be manually acquired on the Cython
        side or the Python interpreter will crash. Monitors the active firewall rules file for changes and loads
        updates to cfirewall.'''

        dnx_fw = load_configuration(fw_rules, filepath='dnx_system/iptables')

        # splitting out sections then determine which one has changed. this is to reduce
        # amount of work done on the C side. not for performance, but more for ease of programming.
        # NOTE: index 1 start is needed because SYSTEM rules are held at index 0.
        for i, section in enumerate(['BEFORE', 'MAIN', 'AFTER'], 1):
            current_section = getattr(self, section)
            new_section = dnx_fw[section]

            # unchanged ruleset
            if (current_section == new_section): continue

            # updating ruleset to reflect changes
            setattr(self, section, new_section)

            # converting dict to list and each rule into a py array. this format is required due to
            # transitioning between python and C. python arrays are compatible in C via memory views
            # and Cython can handle the initial list.
            ruleset = [array('L', rule) for rule in new_section.values()]

            # NOTE: gil must be held throughout this call
            error = self.cfirewall.update_ruleset(i, ruleset)
            if (error):
                pass  # TODO: do something here

        self._initialize.done()

    @cfg_read_poller('firewall_system', folder='iptables')
    def monitor_system_rules(self, system_rules):
        # 0-9: reserved - dns, dhcp, loopback, etc
        # 10-159: zone mgmt rules. tens place designates interface index
        #   - 0: webui, 1: cli, 2: ssh, 3: ping
        # 160+: system control (proxy bypass prevention)

        # dnxfirewall services access (all local network interfaces). dhcp, dns, icmp, etc.

        # DHCP discover/request allow
        shell(
            f'iptables -A INPUT -m mark ! --mark {WAN_IN} -p udp --dport 67 -j ACCEPT'
        )

        # implicit DNS allow for local users
        shell(
            f'iptables -A INPUT -m mark ! --mark {WAN_IN} -p udp --dport 53 -j ACCEPT'
        )

        # implicit http/s allow to dnx-web for local LAN users
        shell(
            f'iptables -A INPUT -m mark --mark {LAN_IN} -p tcp --dport 443 -j ACCEPT'
        )
        shell(
            f'iptables -A INPUT -m mark --mark {LAN_IN} -p tcp --dport 80 -j ACCEPT'
        )

        # NOTE: these are default settings of user defined options. these can be removed from the webui after setup and are only
        # here for convenience.

        # dnxfirewall LAN interface ping allow
        shell(
            f'iptables -A MGMT -m mark --mark {LAN_IN} -p icmp -m icmp --icmp-type 8 -j ACCEPT'
        )

        # DMZ webui access
        shell(
            f'iptables -A MGMT -m mark --mark {DMZ_IN} -p tcp --dport 443 -j ACCEPT'
        )
        shell(
            f'iptables -A MGMT -m mark --mark {DMZ_IN} -p tcp --dport 80 -j ACCEPT'
        )

        ruleset = load_configuration(system_rules,
                                     filepath='dnx_system/iptables')

        # sorting merged dict (system + usr), then taking values to convert into python arrays
        ruleset = [array('L', rule) for rule in dict(sorted(ruleset)).values()]

        # NOTE: gil must be held throughout this call
        error = self.cfirewall.update_ruleset(0, ruleset)
        if (error):
            pass  # TODO: do something here
예제 #4
0
 def __init__(self, name):
     self.initialize = Initialize(Log, name)
     self._cfg_change = threading.Event()
예제 #5
0
class Configuration:
    _setup = False

    __slots__ = (
        'initialize',
        'IPS',
        '_cfg_change',
    )

    def __init__(self, name):
        self.initialize = Initialize(Log, name)
        self._cfg_change = threading.Event()

    @classmethod
    def setup(cls, IPS):
        if (cls._setup):
            raise RuntimeError(
                'configuration setup should only be called once.')

        cls._setup = True

        self = cls(IPS.__name__)
        self.IPS = IPS

        self._load_passive_blocking()
        threading.Thread(target=self._get_settings).start()
        threading.Thread(target=self._get_open_ports).start()
        threading.Thread(target=self._update_system_vars).start()

        self.initialize.wait_for_threads(count=3)

        threading.Thread(target=self._clear_ip_tables).start()

    # this resets any passively blocked hosts in the system on startup. persisting this
    # data through service or system restarts is not really worth the energy.
    def _load_passive_blocking(self):
        self.IPS.fw_rules = dict(System.ips_passively_blocked())

    @cfg_read_poller('ips')
    def _get_settings(self, cfg_file):
        ips = load_configuration(cfg_file)

        self.IPS.ids_mode = ips['ids_mode']

        self.IPS.ddos_prevention = ips['ddos']['enabled']
        # ddos CPS configured thresholds
        self.IPS.connection_limits = {
            PROTO.ICMP: ips['ddos']['limits']['source']['icmp'],
            PROTO.TCP: ips['ddos']['limits']['source']['tcp'],
            PROTO.UDP: ips['ddos']['limits']['source']['udp']
        }

        self.IPS.portscan_prevention = ips['port_scan']['enabled']
        self.IPS.portscan_reject = ips['port_scan']['reject']

        if (self.IPS.ddos_prevention and not self.IPS.ids_mode):

            # checking length(hours) to leave IP table rules in place for hosts part of ddos attacks
            self.IPS.block_length = ips['passive_block_ttl'] * ONE_HOUR

            # NOTE: this will provide a simple way to ensure very recently blocked hosts do not get their
            # rule removed if passive blocking is disabled.
            if (not self.IPS.block_length):
                self.IPS.block_length = FIVE_MIN

        # if ddos engine is disabled
        else:
            self.IPS.block_length = NO_DELAY

        # src ips that will not trigger ips
        self.IPS.ip_whitelist = set(
            [IPv4Address(ip) for ip in ips['whitelist']['ip_whitelist']])

        self._cfg_change.set()
        self.initialize.done()

    # NOTE: determine whether default sleep timer is acceptable for this method. if not, figure out how to override
    # the setting set in the decorator or remove the decorator entirely.
    @cfg_read_poller('ips')
    def _get_open_ports(self, cfg_file):
        ips = load_configuration(cfg_file)

        self.IPS.open_ports = {
            PROTO.TCP: {
                int(local_port): int(wan_port)
                for wan_port, local_port in ips['open_protocols']
                ['tcp'].items()
            },
            PROTO.UDP: {
                int(local_port): int(wan_port)
                for wan_port, local_port in ips['open_protocols']
                ['udp'].items()
            }
        }

        self._cfg_change.set()
        self.initialize.done()

    @looper(NO_DELAY)
    def _update_system_vars(self):
        # waiting for any thread to report a change in configuration.
        self._cfg_change.wait()

        # resetting the config change event.
        self._cfg_change.clear()

        open_ports = self.IPS.open_ports[PROTO.TCP] or self.IPS.open_ports[
            PROTO.UDP]

        self.IPS.ps_engine_enabled = True if self.IPS.portscan_prevention and open_ports else False

        self.IPS.ddos_engine_enabled = True if self.IPS.ddos_prevention else False

        # makes some conditions easier when determining what to do with the packet.
        self.IPS.all_engines_enabled = self.IPS.ps_engine_enabled and self.IPS.ddos_engine_enabled

        self.initialize.done()

    @looper(FIVE_MIN)
    # NOTE: refactored function utilizing iptables + timestamp comment to identify rules to be expired.
    # this should inherently make the passive blocking system persist service or system reboots.
    # TODO: consider using the fw_rule dict check before continuing to call System.
    def _clear_ip_tables(self):
        expired_hosts = System.ips_passively_blocked(
            block_length=self.IPS.block_length)
        if (not expired_hosts):
            return

        with IPTablesManager() as iptables:
            for host, timestamp in expired_hosts:
                iptables.proxy_del_rule(host,
                                        timestamp,
                                        table='raw',
                                        chain='IPS')

                # removing host from ips tracker/ suppression dictionary
                self.IPS.fw_rules.pop(IPv4Address(host),
                                      None)  # should never return None
예제 #6
0
 def __init__(self, name):
     self.Initialize = Initialize(Log, name)
예제 #7
0
class LanRestrict:
    '''lan restriction management is done within this class.

    public attributes: is_enabled, is_active

    call run method to start service.

    '''
    _enabled = False
    _active = False

    __slots__ = ('IPProxy', 'initialize')

    def __init__(self, name):
        self.initialize = Initialize(Log, name)

    @classproperty
    def is_enabled(cls):  # pylint: disable=no-self-argument
        return cls._enabled

    @classproperty
    def is_active(cls):  # pylint: disable=no-self-argument
        return cls._active

    @classmethod
    def run(cls, IPProxy):
        '''initializes settings and attributes then runs timer service in a new thread before returning.'''
        self = cls(IPProxy.__name__)
        self.IPProxy = IPProxy

        cls.__load_status()

        threading.Thread(target=self._get_settings).start()
        threading.Thread(target=self._tracker).start()

        self.initialize.wait_for_threads(count=2)

    @cfg_read_poller('ip_proxy')
    def _get_settings(self, cfg_file):
        ip_proxy = load_configuration(cfg_file)

        enabled = ip_proxy['time_restriction']['enabled']
        self._change_attribute('_enabled', enabled)

        self.initialize.done()

    @looper(ONE_MIN)
    def _tracker(self):
        restriction_start, restriction_end, now = self._calculate_times()

        # Log.debug(f'ENABLED: {self.is_enabled} | ACTIVE: {self.is_active}')
        # Log.debug(f'START: {restriction_start}: {datetime.fromtimestamp(restriction_start)}')
        # Log.debug(f'NOW: {now}: {datetime.fromtimestamp(now)}')
        # Log.debug(f'END: {restriction_end}: {datetime.fromtimestamp(restriction_end)}')
        if (not self.is_enabled and self.is_active):
            self._set_restriction_status(active=False)

        # NOTE: validate end check is doing anything. if not remove it to make code easier to deal with
        elif (self.is_enabled and not self.is_active
              and restriction_start < now < restriction_end):
            self._set_restriction_status(active=True)

            Log.notice('LAN restriction in effect.')

        elif (self.is_active and now > restriction_end):
            self._set_restriction_status(active=False)

            Log.notice('LAN restriction released.')

        self.initialize.done()

    # Calculating what the current date and time is and what the current days start time is in epoch
    # this must be calculated daily as the start time epoch is always changing
    def _calculate_times(self):
        restriction_start, restriction_length, offset = self._load_restriction(
        )

        now = fast_time() + offset
        c_d = [int(i) for i in System.date(now)]  # current date
        r_start = [int(i) for i in restriction_start.split(':')]

        restriction_start = datetime(c_d[0], c_d[1], c_d[2], r_start[0],
                                     r_start[1]).timestamp()
        restriction_end = restriction_start + restriction_length

        if (self.is_active):
            restriction_end = load_configuration('ip_proxy_timer')['end']

        else:
            self._write_end_time(restriction_end)

        return restriction_start, restriction_end, now

    # Calculating the time.time() of when timer should end. calculated by current days start time (time since epoch)
    # and then adding seconds of user configured amount to start time.
    def _write_end_time(self, restriction_end):
        with ConfigurationManager('ip_proxy_timer') as dnx:
            time_restriction = dnx.load_configuration()

            time_restriction['end'] = restriction_end

            dnx.write_configuration(time_restriction)

    def _load_restriction(self):
        ip_proxy = load_configuration('ip_proxy')
        logging = load_configuration('logging_client')

        restriction_start = ip_proxy['time_restriction']['start']
        restriction_length = ip_proxy['time_restriction']['length']

        os_direction = logging['time_offset']['direction']
        os_amount = logging['time_offset']['amount']

        offset = int(f'{os_direction}{os_amount}') * ONE_DAY

        return restriction_start, restriction_length, offset

    def _set_restriction_status(self, active):
        self._change_attribute('_active', active)

        with ConfigurationManager('ip_proxy_timer') as dnx:
            time_restriction = dnx.load_configuration()

            time_restriction['active'] = active

            dnx.write_configuration(time_restriction)

    @classmethod
    def __load_status(cls):
        time_restriction = load_configuration('ip_proxy_timer')

        cls._active = time_restriction['active']

    @classmethod
    def _change_attribute(cls, name, status):
        setattr(cls, name, status)
class Configuration:
    _setup = False

    def __init__(self, name):
        self.initialize = Initialize(Log, name)

    @classmethod
    def setup(cls, DHCPServer):
        if (cls._setup):
            raise RuntimeError('configuration setup should only be called once.')

        cls._setup = True

        self = cls(DHCPServer.__name__)
        self.DHCPServer = DHCPServer

        self._load_interfaces()

        threading.Thread(target=self._get_settings).start()
        threading.Thread(target=self._get_server_options).start()
        threading.Thread(target=self._get_reservations).start()
        self.initialize.wait_for_threads(count=3)

    @cfg_read_poller('dhcp_server')
    def _get_settings(self, cfg_file):
        dhcp_settings = load_configuration(cfg_file)

        # updating user configuration items per interface in memory.
        for settings in dhcp_settings['interfaces'].values():

            # NOTE ex. ident: eth0, lo, enp0s3
            intf_identity = settings['ident']

            enabled  = True if settings['enabled'] else False

            # TODO: compare interface status in memory with what is loaded in. if it is different then the setting was just
            # changed and needs to be acted on. implement register/unregister methods available to external callers and use
            # them to act on the disable of an interfaces dhcp service. this should also be the most efficient in that if
            # all listeners are disabled only the automate class will be actively processing on file changes.
            # NOTE: .get is to cover server startup. do not change. test functionality.
            sock_fd = self.DHCPServer.intf_settings[intf_identity]['fileno']
            if (enabled and not self.DHCPServer.intf_settings[intf_identity].get('enabled', False)):
                self.DHCPServer.enable(sock_fd, intf_identity)

            elif (not enabled and self.DHCPServer.intf_settings[intf_identity].get('enabled', True)):
                self.DHCPServer.disable(sock_fd, intf_identity)

            # identity will be kept in settings just in case, though they key is the identity also.
            self.DHCPServer.intf_settings[intf_identity].update(settings)

        self.initialize.done()

    @cfg_read_poller('dhcp_server')
    def _get_server_options(self, cfg_file):
        dhcp_settings = load_configuration(cfg_file)
        server_options = dhcp_settings['options']
        interfaces = dhcp_settings['interfaces']

        # if server options have not changed, the function can return
        if (server_options == self.DHCPServer.options): return

        # will wait for 2 threads to check in before running code. this will allow the necessary settings
        # to be initialized on startup before this thread continues.
        self.initialize.wait_in_line(wait_for=2)

        with self.DHCPServer.options_lock:

            # iterating over server interfaces and populated server option data sets NOTE: consider merging server
            # options with the interface settings since they are technically bound.
            for intf, settings in self.DHCPServer.intf_settings.items():

                for _intf in interfaces.values():

                    # ensuring the interfaces match since we cannot guarantee order
                    if (intf != _intf['ident']): continue

                    # converting keys to integers (json keys are string only), then packing any
                    # option value that is in ip address form to raw bytes.
                    for o_id, values in server_options.items():

                        opt_len, opt_val = values
                        if (not isinstance(opt_val, str)):
                            self.DHCPServer.options[intf][int(o_id)] = (opt_len, opt_val)

                        else:
                            # NOTE: this is temporary to allow interface netmask to be populated correction while migrating
                            # to new system backend functions.
                            if (o_id == '1'):
                                ip_value = get_netmask(interface=intf)
                            else:
                                ip_value = list(settings['ip'].network)[int(opt_val)]

                            # using digit as ipv4 network object index to grab correct ip object, then pack.
                            self.DHCPServer.options[intf][int(o_id)] = (
                                opt_len, ip_value.packed
                            )

        self.initialize.done()

    # loading user configured dhcp reservations from json config file into memory.
    @cfg_read_poller('dhcp_server')
    def _get_reservations(self, cfg_file):
        dhcp_settings = load_configuration(cfg_file)

        # dict comp that retains all info of stored json data, but converts ip address into objects
        self.DHCPServer.reservations = {
            mac: {
                'ip_address': IPv4Address(info['ip_address']),
                'description': info['description']
            }
            for mac, info in dhcp_settings['reservations'].items()
        }

        # creating local reference for iteration performance
        reservations = self.DHCPServer.reservations

        # loaded all reserved ip addressing into a set to be referenced below
        reserved_ips = set([IPv4Address(info['ip_address']) for info in reservations.values()])

        # sets reserved ip addresses lease records to available is there are no longer configured
        dhcp_leases = self.DHCPServer.leases
        for ip, record in dhcp_leases.items():

            # record[0] is record type. cross referencing ip reservation list with current lease table
            # to reset any leased record placeholders for the reserved ip.
            if (record[0] is DHCP.RESERVATION and IPv4Address(ip) not in reserved_ips):
                dhcp_leases[ip] = _NULL_LEASE

        # adding dhcp reservations to lease table to prevent them from being selected during an offer
        self.DHCPServer.leases.update({
            IPv4Address(info['ip_address']): (DHCP.RESERVATION, 0, mac) for mac, info in reservations.items()
        })

        self.initialize.done()

    # accessing class object via local instance to change overall DHCP server enabled ints tuple
    def _load_interfaces(self):
        fw_intf = load_configuration('config')['interfaces']['builtins']
        dhcp_intfs = load_configuration('dhcp_server')['interfaces']

        # interface ident eg. eth0
        for *_, intf in self.DHCPServer._intfs:

            # interface friendly name eg. wan
            for _intf, settings in dhcp_intfs.items():

                # ensuring the interfaces match since we cannot guarantee order
                if (intf != settings['ident']): continue

                # creating ipv4 interface object which will be associated with the ident in the config.
                # this can then be used by the server to identify itself as well as generate its effective
                # subnet based on netmask for ip handouts or membership tests.
                intf_ip = IPv4Interface(str(fw_intf[_intf]['ip']) + '/' + str(fw_intf[_intf]['netmask']))

                # initializing server options so the auto loader doesnt have to worry about it.
                self.DHCPServer.options[intf] = {}

                # updating general network information for interfaces on server class object. these will never change
                # while the server is running. for interfaces changes, the server must be restarted.
                # initializing fileno key in the intf dict to make assignments easier in later calls.
                self.DHCPServer.intf_settings[intf] = {'ip': intf_ip}

                self._create_socket(intf)

        Log.debug(f'loaded interfaces from file: {self.DHCPServer.intf_settings}')

    # this is providing the first portion of creating a socket. this will allow the system to create the socket
    # store the file descriptor id, and then bind when ready per normal registration logic.
    def _create_socket(self, intf):
        l_sock = socket(AF_INET, SOCK_DGRAM)

        # used for converting interface identity to socket object file descriptor number
        self.DHCPServer.intf_settings[intf].update({
            'l_sock': l_sock,
            'fileno': l_sock.fileno()
        })

        Log.debug(f'[{l_sock.fileno()}][{intf}] socket created')
예제 #9
0
 def __init__(self):
     self._initialize = Initialize(Log, 'LogService')
예제 #10
0
class LogService:
    _log_modules = [
        x for x in os.listdir(f'{HOME_DIR}/dnx_system/log')
        if x not in EXCLUDED_MODULES
    ]

    __slots__ = ('log_length', 'log_level', '_initialize')

    def __init__(self):
        self._initialize = Initialize(Log, 'LogService')

    @classmethod
    def run(cls):
        self = cls()

        threading.Thread(target=self.get_settings).start()

        self._initialize.wait_for_threads(count=1)

        threading.Thread(target=self.organize).start()
        threading.Thread(target=self.clean_db_tables).start()
        threading.Thread(target=self.clean_blocked_table).start()

    # Recurring logic to gather all log files and add the mto a signle file (combined logs) every 5 minutes
    @looper(THREE_MIN)
    def organize(self):
        # print('[+] Starting organize operation.')
        log_entries = []

        date = str_join(System.date())
        for module in self._log_modules:
            module_entries = self._combine_logs(module, date)
            if (module_entries):
                log_entries.extend(module_entries)

        sorted_log_entries = sorted(log_entries)
        if (sorted_log_entries):
            self._write_combined_logs(sorted_log_entries, date)

        del log_entries  # to reclaim system memory

    # grabbing the log from the sent in module, splitting the lines, and returning a list
    # TODO: see if we can load file as generator
    @staticmethod
    def _combine_logs(module, date):
        file_entries = []

        if not os.path.isfile(
                f'{HOME_DIR}/dnx_system/log/{module}/{date}-{module}.log'):
            return None

        with open(f'{HOME_DIR}/dnx_system/log/{module}/{date}-{module}.log',
                  'r') as log_file:
            for _ in range(20):
                line = log_file.readline().strip()
                if not line: break

                file_entries.append(line)

        return file_entries

    # writing the log entries to the combined log
    @staticmethod
    def _write_combined_logs(sorted_log_entries, date):
        with open(f'{HOME_DIR}/dnx_system/log/combined/{date}-combined.log',
                  'w+') as system_log:
            # print(f'writing {HOME_DIR}/dnx_system/log/combined/{date[0]}{date[1]}{date[2]}-combined.log')
            for log in sorted_log_entries:
                system_log.write(f'{log}\n')

    @looper(ONE_DAY)
    def clean_db_tables(self):
        # print('[+] Starting general DB table cleaner.')
        with DBConnector(Log) as FirewallDB:
            for table in ['dnsproxy', 'ipproxy', 'ips', 'infectedclients']:
                FirewallDB.table_cleaner(self.log_length, table=table)

        # NOTE: consider moving this into the DBConnector so it can report if no exc are raised.
        Log.notice('completed daily database cleaning')

    @looper(THREE_MIN)
    def clean_blocked_table(self):
        # print('[+] Starting DB blocked table cleaner.')
        with DBConnector(Log) as FirewallDB:
            FirewallDB.blocked_cleaner(table='blocked')

        # NOTE: consider moving this into the DBConnector so it can report if no exc are raised.
        Log.debug('completed blocked database cleaning')

    @cfg_read_poller('logging_client')
    def get_settings(self, cfg_file):
        #        print('[+] Starting settings update poller.')
        log_settings = load_configuration(cfg_file)

        self.log_length = log_settings['logging']['length']
        self.log_level = log_settings['logging']['level']

        self._initialize.done()