def allow_ssh_temporarily(_config: SectionProxy) -> None:
            """
            Allows ssh temporarily

            :param _config: the config to use
            """
            number_of_required_chains = len(_config.getlist("ports"))

            for i in range(1, number_of_required_chains):
                self.execute("-N SSH-KNOCKING-{}".format(i))

            command = "-A INPUT -m state --state NEW -m tcp -p tcp"

            if _config.get("interface", None):
                command += " -i " + _config.get("interface")

            command += " --dport " + _config.get("ssh_port", "22")
            command += " -m recent --rcheck --seconds " + _config.get("timeout", 30)
            command += " --name SSH{}".format(len(_config.getlist("ports")) - 1)
            command += \
                ' -m comment --comment "Allow port {} for ssh for {} if the connecting ip is in the list SSH{}"'.format(
                    _config.get("ssh_port", "22"), _config.get("timeout", 30), len(_config.getlist("ports")) - 1
                )

            self.execute(command)
        def allow_ssh_temporarily(_config: SectionProxy) -> None:
            """
            Allows ssh temporarily

            :param _config: the config to use
            """
            number_of_required_chains = len(_config.getlist("ports"))

            for i in range(1, number_of_required_chains):
                self.execute("-N SSH-KNOCKING-{}".format(i))

            command = "-A INPUT -m state --state NEW -m tcp -p tcp"

            if _config.get("interface", None):
                command += " -i " + _config.get("interface")

            command += " --dport " + _config.get("ssh_port", "22")
            command += " -m recent --rcheck --seconds " + _config.get(
                "timeout", 30)
            command += " --name SSH{}".format(
                len(_config.getlist("ports")) - 1)
            command += \
                ' -m comment --comment "Allow port {} for ssh for {} if the connecting ip is in the list SSH{}"'.format(
                    _config.get("ssh_port", "22"), _config.get("timeout", 30), len(_config.getlist("ports")) - 1
                )

            self.execute(command)
def handle_service(config: SectionProxy) -> None:
    """
    Sets a rule or a service

    :param config: the configuration for the rule
    """
    for src in config.getlist("source", [None]):
        for dst in config.getlist("destination", [None]):
            source = None
            destination = None

            if src is not None:
                source = get_ip_address(src)
                if source is None:
                    print(
                        "[ERROR] Could not determine ip address for {} : skipping"
                        .format(src))
                    continue

            if dst is not None:
                destination = get_ip_address(dst)
                if destination is None:
                    print(
                        "[ERROR] Could not determine ip address for {} : skipping"
                        .format(dst))
                    continue

            rule = IptablesRule(name=config.name,
                                interface=config.get("interface"),
                                chain=config.get("chain"),
                                protocol=config.get("protocol"),
                                action=config.get("action"),
                                source=source,
                                destination=destination,
                                sport=config.get("sport"),
                                dport=config.get("dport"),
                                remote=config.get("remote", None))

            if config.getboolean("ipv4", False) and (rule.source is None or rule.source.version == 4) and \
                    (rule.destination is None or rule.source.version == 4):
                ipv4_handler.add_rule(rule)
            if config.getboolean("ipv6") and (rule.source is None or rule.source.version == 6) and \
                    (rule.destination is None or rule.source.version == 6):
                ipv6_handler.add_rule(rule)

            if (rule.source is not None and rule.destination is not None) and \
                    rule.destination.version != rule.source.version:
                print(
                    "[ERROR] Could not add rule with ip versions no matching: {} and {}"
                    .format(str(rule.source, rule.destination)))
    def setup(handler: Iptables, _config: SectionProxy) -> None:
        """
        Sets up the tables to accept new rules : resets all rules, set defaults and allow global traffic

        :param handler: the Iptables instance on which to operate
        :param _config: the configuration used
        """
        handler.reset()
        for chain in _config.getlist("closed_chains", []):
            handler.set_default(chain, "DROP")

        if _config.getboolean("allow_established_traffic", False):
            handler.allow_existing_traffic()

        for interface in _config.getlist("allow_traffic_on_interface", []):
            handler.allow_traffic_on_interface(interface)

        if _config.getboolean("drop_invalid_traffic", False):
            handler.drop_invalid_traffic()
    def enable_ssh_knocking(self, config: SectionProxy) -> None:
        """
        enables iptables-only ssh knocking

        :param config: the configuration to use for the knocking
        """
        def allow_ssh_temporarily(_config: SectionProxy) -> None:
            """
            Allows ssh temporarily

            :param _config: the config to use
            """
            number_of_required_chains = len(_config.getlist("ports"))

            for i in range(1, number_of_required_chains):
                self.execute("-N SSH-KNOCKING-{}".format(i))

            command = "-A INPUT -m state --state NEW -m tcp -p tcp"

            if _config.get("interface", None):
                command += " -i " + _config.get("interface")

            command += " --dport " + _config.get("ssh_port", "22")
            command += " -m recent --rcheck --seconds " + _config.get("timeout", 30)
            command += " --name SSH{}".format(len(_config.getlist("ports")) - 1)
            command += \
                ' -m comment --comment "Allow port {} for ssh for {} if the connecting ip is in the list SSH{}"'.format(
                    _config.get("ssh_port", "22"), _config.get("timeout", 30), len(_config.getlist("ports")) - 1
                )

            self.execute(command)

        def remove_from_list(entry_number: int) -> None:
            """
            Removes the ip from the list given by the number

            :param entry_number: the number for which to remove the list
            """
            command = "-A INPUT -m state --state NEW -m tcp -p tcp -m recent --name SSH{} --remove".format(entry_number)
            command += ' -j DROP -m comment --comment "Remove connecting ip from the SSH{} list"'.format(entry_number)
            self.execute(command)

        def enable_jump(_port: int, entry_number: int) -> None:
            """
            Enables jumping to the given chain

            :param _port: the port on which to enable the jump
            :param entry_number: the number of the entry to which to jump
            """
            command = "-A INPUT -m state --state NEW -m tcp -p tcp --dport {} -m recent --rcheck --name SSH{}".format(
                _port, entry_number - 1
            )
            command += ' -j SSH-KNOCKING-{} -m comment --comment "Checks for the sequence and jumps if correct"'.format(
                entry_number
            )

            self.execute(command)

        def initiate_knocking(_port: int) -> None:
            """
            Sequence initiation for the port knocking

            :param _port: the port on which to knock
            """
            command = "-A INPUT -m state --state NEW -m tcp -p tcp --dport {} -m recent --name SSH0 --set".format(_port)
            command += ' -j DROP -m comment --comment "Sequence initiation for port knocking"'
            self.execute(command)

        def hide_port(number: int) -> None:
            """
            Hides the port given by the number by dropping it

            :param number: the number of the chain on which to drop
            """
            command = "-A INPUT -m recent --name SSH{} --set -j DROP -m comment".format(number)
            command += '--comment "Disguise successful knock as a closed port for obfuscation"'

            self.execute(command)

        # noinspection PyTypeChecker
        allow_ssh_temporarily(config)

        # noinspection PyUnresolvedReferences
        ports = config.getlist("ports")
        for port in reversed(ports):
            remove_from_list(ports.index(port))
            if ports.index(port) != 0:
                enable_jump(port, ports.index(port))
            else:
                initiate_knocking(port)

        for entry in range(len(ports) - 1):
            hide_port(entry)
    def enable_ssh_knocking(self, config: SectionProxy) -> None:
        """
        enables iptables-only ssh knocking

        :param config: the configuration to use for the knocking
        """
        def allow_ssh_temporarily(_config: SectionProxy) -> None:
            """
            Allows ssh temporarily

            :param _config: the config to use
            """
            number_of_required_chains = len(_config.getlist("ports"))

            for i in range(1, number_of_required_chains):
                self.execute("-N SSH-KNOCKING-{}".format(i))

            command = "-A INPUT -m state --state NEW -m tcp -p tcp"

            if _config.get("interface", None):
                command += " -i " + _config.get("interface")

            command += " --dport " + _config.get("ssh_port", "22")
            command += " -m recent --rcheck --seconds " + _config.get(
                "timeout", 30)
            command += " --name SSH{}".format(
                len(_config.getlist("ports")) - 1)
            command += \
                ' -m comment --comment "Allow port {} for ssh for {} if the connecting ip is in the list SSH{}"'.format(
                    _config.get("ssh_port", "22"), _config.get("timeout", 30), len(_config.getlist("ports")) - 1
                )

            self.execute(command)

        def remove_from_list(entry_number: int) -> None:
            """
            Removes the ip from the list given by the number

            :param entry_number: the number for which to remove the list
            """
            command = "-A INPUT -m state --state NEW -m tcp -p tcp -m recent --name SSH{} --remove".format(
                entry_number)
            command += ' -j DROP -m comment --comment "Remove connecting ip from the SSH{} list"'.format(
                entry_number)
            self.execute(command)

        def enable_jump(_port: int, entry_number: int) -> None:
            """
            Enables jumping to the given chain

            :param _port: the port on which to enable the jump
            :param entry_number: the number of the entry to which to jump
            """
            command = "-A INPUT -m state --state NEW -m tcp -p tcp --dport {} -m recent --rcheck --name SSH{}".format(
                _port, entry_number - 1)
            command += ' -j SSH-KNOCKING-{} -m comment --comment "Checks for the sequence and jumps if correct"'.format(
                entry_number)

            self.execute(command)

        def initiate_knocking(_port: int) -> None:
            """
            Sequence initiation for the port knocking

            :param _port: the port on which to knock
            """
            command = "-A INPUT -m state --state NEW -m tcp -p tcp --dport {} -m recent --name SSH0 --set".format(
                _port)
            command += ' -j DROP -m comment --comment "Sequence initiation for port knocking"'
            self.execute(command)

        def hide_port(number: int) -> None:
            """
            Hides the port given by the number by dropping it

            :param number: the number of the chain on which to drop
            """
            command = "-A INPUT -m recent --name SSH{} --set -j DROP -m comment".format(
                number)
            command += '--comment "Disguise successful knock as a closed port for obfuscation"'

            self.execute(command)

        # noinspection PyTypeChecker
        allow_ssh_temporarily(config)

        # noinspection PyUnresolvedReferences
        ports = config.getlist("ports")
        for port in reversed(ports):
            remove_from_list(ports.index(port))
            if ports.index(port) != 0:
                enable_jump(port, ports.index(port))
            else:
                initiate_knocking(port)

        for entry in range(len(ports) - 1):
            hide_port(entry)