From c43bc5c73f47bae9786b93c101292ee939b9f16d Mon Sep 17 00:00:00 2001 From: eaglegai Date: Wed, 9 Sep 2020 14:30:11 +0800 Subject: [PATCH] add new featuer to fix command error when use with non-existen ipset --- ...-implement-policy-objects-internally.patch | 5993 +++++++++++++++++ ...patch-to-default-to-iptables-backend.patch | 110 - ...ix-build-distribute-new-python-files.patch | 35 + ...-po-add-new-python-files-to-POTFILES.patch | 33 + ...bles-calculate-max-name-len-properly.patch | 54 + ...e-listing-rich-rules-in-default-zone.patch | 25 + ...-allow-coalescing-and-breaking-of-ra.patch | 390 ++ ...-improvement-port-simplify-queryPort.patch | 55 + firewalld.spec | 21 +- 9 files changed, 6601 insertions(+), 115 deletions(-) create mode 100644 0001-feat-implement-policy-objects-internally.patch delete mode 100644 0001-fedora-patch-to-default-to-iptables-backend.patch create mode 100644 0001-fix-build-distribute-new-python-files.patch create mode 100644 0001-fix-po-add-new-python-files-to-POTFILES.patch create mode 100644 0001-fix-policy-ipXtables-calculate-max-name-len-properly.patch create mode 100644 0001-fix-zone-listing-rich-rules-in-default-zone.patch create mode 100644 0001-improvement-port-allow-coalescing-and-breaking-of-ra.patch create mode 100644 0001-improvement-port-simplify-queryPort.patch diff --git a/0001-feat-implement-policy-objects-internally.patch b/0001-feat-implement-policy-objects-internally.patch new file mode 100644 index 0000000..0e5cff5 --- /dev/null +++ b/0001-feat-implement-policy-objects-internally.patch @@ -0,0 +1,5993 @@ +From 34bdee40aa61f6f973a76233742d41c20d6c6e0b Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Fri, 4 Oct 2019 15:23:59 -0400 +Subject: [PATCH] feat: implement policy objects internally + +Introduce policy objects internally. There are no user visible changes. +Rule layout for zones has not changed. Zones are translated into +multiple policies. + +This is the first step in introducing policy objects to users and +therefore supporting output/forward filtering. +--- + src/firewall-config.in | 4 +- + src/firewall/core/base.py | 5 +- + src/firewall/core/ebtables.py | 2 +- + src/firewall/core/fw.py | 16 +- + src/firewall/core/fw_ipset.py | 8 +- + src/firewall/core/fw_policy.py | 1552 +++++++++++++++++++++++++++ + src/firewall/core/fw_transaction.py | 20 +- + src/firewall/core/fw_zone.py | 1880 +++++++-------------------------- + src/firewall/core/io/policy.py | 830 +++++++++++++++ + src/firewall/core/ipXtables.py | 315 +++--- + src/firewall/core/nftables.py | 301 +++--- + src/firewall/errors.py | 1 + + src/firewall/functions.py | 11 +- + src/tests/features/service_include.at | 2 - + 14 files changed, 3118 insertions(+), 1829 deletions(-) + create mode 100644 src/firewall/core/fw_policy.py + create mode 100644 src/firewall/core/io/policy.py + +diff --git a/src/firewall-config.in b/src/firewall-config.in +index f5f69fc..1d88b29 100755 +--- a/src/firewall-config.in ++++ b/src/firewall-config.in +@@ -46,7 +46,7 @@ from firewall import config + from firewall import client + from firewall import functions + from firewall.core.base import DEFAULT_ZONE_TARGET, REJECT_TYPES, \ +- ZONE_SOURCE_IPSET_TYPES ++ SOURCE_IPSET_TYPES + from firewall.core.ipset import IPSET_MAXNAMELEN + from firewall.core.helper import HELPER_MAXNAMELEN + from firewall.core.io.zone import Zone +@@ -4188,7 +4188,7 @@ class FirewallConfig(object): + continue + raise + self.activate_exception_handler() +- if settings.getType() not in ZONE_SOURCE_IPSET_TYPES: ++ if settings.getType() not in SOURCE_IPSET_TYPES: + continue + ipsets[x] = settings + else: +diff --git a/src/firewall/core/base.py b/src/firewall/core/base.py +index e2691b5..45522a9 100644 +--- a/src/firewall/core/base.py ++++ b/src/firewall/core/base.py +@@ -22,10 +22,13 @@ + """Base firewall settings""" + + DEFAULT_ZONE_TARGET = "{chain}_{zone}" ++DEFAULT_POLICY_TARGET = "CONTINUE" + + ZONE_TARGETS = [ "ACCEPT", "%%REJECT%%", "DROP", DEFAULT_ZONE_TARGET, + "default" ] + ++POLICY_TARGETS = [ "ACCEPT", "%%REJECT%%", "DROP", "CONTINUE" ] ++ + SHORTCUTS = { + "PREROUTING": "PRE", + "POSTROUTING": "POST", +@@ -49,7 +52,7 @@ REJECT_TYPES = { + # ipset types that can be used as a source in zones + # The match-set option will be src or src,src according to the + # dimension of the ipset. +-ZONE_SOURCE_IPSET_TYPES = [ ++SOURCE_IPSET_TYPES = [ + "hash:ip", "hash:ip,port", "hash:ip,mark", + "hash:net", "hash:net,port", "hash:net,iface", + "hash:mac" +diff --git a/src/firewall/core/ebtables.py b/src/firewall/core/ebtables.py +index a5860bd..8938b75 100644 +--- a/src/firewall/core/ebtables.py ++++ b/src/firewall/core/ebtables.py +@@ -52,7 +52,7 @@ for table in BUILT_IN_CHAINS.keys(): + class ebtables(object): + ipv = "eb" + name = "ebtables" +- zones_supported = False # ebtables only supported with direct interface ++ policies_supported = False # ebtables only supported with direct interface + + def __init__(self): + self._command = COMMANDS[self.ipv] +diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py +index f3f1adf..a9d56a9 100644 +--- a/src/firewall/core/fw.py ++++ b/src/firewall/core/fw.py +@@ -42,6 +42,7 @@ from firewall.core.fw_policies import FirewallPolicies + from firewall.core.fw_ipset import FirewallIPSet + from firewall.core.fw_transaction import FirewallTransaction + from firewall.core.fw_helper import FirewallHelper ++from firewall.core.fw_policy import FirewallPolicy + from firewall.core.fw_nm import nm_get_bus_name, nm_get_interfaces_in_zone + from firewall.core.logger import log + from firewall.core.io.firewalld_conf import firewalld_conf +@@ -98,6 +99,7 @@ class Firewall(object): + self.policies = FirewallPolicies() + self.ipset = FirewallIPSet(self) + self.helper = FirewallHelper(self) ++ self.policy = FirewallPolicy(self) + + self.__init_vars() + +@@ -634,6 +636,7 @@ class Firewall(object): + self.config.cleanup() + self.direct.cleanup() + self.policies.cleanup() ++ self.policy.cleanup() + self._firewalld_conf.cleanup() + self.__init_vars() + +@@ -876,6 +879,12 @@ class Firewall(object): + if self._panic: + raise FirewallError(errors.PANIC_MODE) + ++ def check_policy(self, policy): ++ _policy = policy ++ if _policy not in self.policy.get_policies(): ++ raise FirewallError(errors.INVALID_POLICY, _policy) ++ return _policy ++ + def check_zone(self, zone): + _zone = zone + if not _zone or _zone == "": +@@ -996,8 +1005,11 @@ class Firewall(object): + # add interfaces to zones again + for zone in self.zone.get_zones(): + if zone in _zone_interfaces: +- self.zone.set_settings(zone, { "interfaces": +- _zone_interfaces[zone] }) ++ ++ for interface_id in _zone_interfaces[zone]: ++ self.zone.change_zone_of_interface(zone, interface_id, ++ _zone_interfaces[zone][interface_id]["sender"]) ++ + del _zone_interfaces[zone] + else: + log.info1("New zone '%s'.", zone) +diff --git a/src/firewall/core/fw_ipset.py b/src/firewall/core/fw_ipset.py +index 6136b47..102c7f6 100644 +--- a/src/firewall/core/fw_ipset.py ++++ b/src/firewall/core/fw_ipset.py +@@ -155,8 +155,8 @@ class FirewallIPSet(object): + + # TYPE + +- def get_type(self, name): +- return self.get_ipset(name, applied=True).type ++ def get_type(self, name, applied=True): ++ return self.get_ipset(name, applied=applied).type + + # DIMENSION + def get_dimension(self, name): +@@ -173,8 +173,8 @@ class FirewallIPSet(object): + + # OPTIONS + +- def get_family(self, name): +- obj = self.get_ipset(name, applied=True) ++ def get_family(self, name, applied=True): ++ obj = self.get_ipset(name, applied=applied) + if "family" in obj.options: + if obj.options["family"] == "inet6": + return "ipv6" +diff --git a/src/firewall/core/fw_policy.py b/src/firewall/core/fw_policy.py +new file mode 100644 +index 0000000..d22e126 +--- /dev/null ++++ b/src/firewall/core/fw_policy.py +@@ -0,0 +1,1552 @@ ++# -*- coding: utf-8 -*- ++# ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++import time ++from firewall.core.logger import log ++from firewall.functions import portStr, checkIPnMask, checkIP6nMask, \ ++ checkProtocol, enable_ip_forwarding, check_single_address, \ ++ portInPortRange, get_nf_conntrack_short_name, coalescePortRange, breakPortRange ++from firewall.core.rich import Rich_Rule, Rich_Accept, \ ++ Rich_Mark, Rich_Service, Rich_Port, Rich_Protocol, \ ++ Rich_Masquerade, Rich_ForwardPort, Rich_SourcePort, Rich_IcmpBlock, \ ++ Rich_IcmpType ++from firewall.core.fw_transaction import FirewallTransaction ++from firewall import errors ++from firewall.errors import FirewallError ++from firewall.fw_types import LastUpdatedOrderedDict ++from firewall.core.base import SOURCE_IPSET_TYPES ++ ++class FirewallPolicy(object): ++ def __init__(self, fw): ++ self._fw = fw ++ self._chains = { } ++ self._policies = { } ++ ++ def __repr__(self): ++ return '%s(%r, %r)' % (self.__class__, self._chains, self._policies) ++ ++ def cleanup(self): ++ self._chains.clear() ++ self._policies.clear() ++ ++ # transaction ++ ++ def new_transaction(self): ++ return FirewallTransaction(self._fw) ++ ++ # policies ++ ++ def get_policies(self): ++ return sorted(self._policies.keys()) ++ ++ def get_policy(self, policy): ++ p = self._fw.check_policy(policy) ++ return self._policies[p] ++ ++ def _first_except(self, e, f, name, *args, **kwargs): ++ try: ++ f(name, *args, **kwargs) ++ except FirewallError as error: ++ if not e: ++ return error ++ return e ++ ++ def add_policy(self, obj): ++ obj.settings = { x : LastUpdatedOrderedDict() ++ for x in [ "services", "ports", ++ "masquerade", "forward_ports", ++ "source_ports", ++ "icmp_blocks", "rules", ++ "protocols", "icmp_block_inversion" ] } ++ ++ self._policies[obj.name] = obj ++ self.copy_permanent_to_runtime(obj.name) ++ ++ def remove_policy(self, policy): ++ obj = self._policies[policy] ++ if obj.applied: ++ self.unapply_policy_settings(policy) ++ obj.settings.clear() ++ del self._policies[policy] ++ ++ def copy_permanent_to_runtime(self, policy): ++ obj = self._policies[policy] ++ ++ if obj.applied: ++ return ++ ++ for args in obj.icmp_blocks: ++ self.add_icmp_block(policy, args) ++ for args in obj.forward_ports: ++ self.add_forward_port(policy, *args) ++ for args in obj.services: ++ self.add_service(policy, args) ++ for args in obj.ports: ++ self.add_port(policy, *args) ++ for args in obj.protocols: ++ self.add_protocol(policy, args) ++ for args in obj.source_ports: ++ self.add_source_port(policy, *args) ++ for args in obj.rules: ++ self.add_rule(policy, args) ++ if obj.masquerade: ++ self.add_masquerade(policy) ++ ++ def set_policy_applied(self, policy, applied): ++ obj = self._policies[policy] ++ obj.applied = applied ++ ++ # settings ++ ++ # generate settings record with sender, timeout ++ def __gen_settings(self, timeout, sender): ++ ret = { ++ "date": time.time(), ++ "sender": sender, ++ "timeout": timeout, ++ } ++ return ret ++ ++ def get_settings(self, policy): ++ return self.get_policy(policy).settings ++ ++ def _policy_settings(self, enable, policy, use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ obj = self._policies[_policy] ++ if (enable and obj.applied) or (not enable and not obj.applied): ++ return ++ if enable: ++ obj.applied = True ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ settings = self.get_settings(policy) ++ for key in settings: ++ for args in settings[key]: ++ if key == "icmp_blocks": ++ self._icmp_block(enable, _policy, args, transaction) ++ elif key == "icmp_block_inversion": ++ continue ++ elif key == "forward_ports": ++ self._forward_port(enable, _policy, transaction, ++ *args) ++ elif key == "services": ++ self._service(enable, _policy, args, transaction) ++ elif key == "ports": ++ self._port(enable, _policy, args[0], args[1], ++ transaction) ++ elif key == "protocols": ++ self._protocol(enable, _policy, args, transaction) ++ elif key == "source_ports": ++ self._source_port(enable, _policy, args[0], args[1], ++ transaction) ++ elif key == "masquerade": ++ self._masquerade(enable, _policy, transaction) ++ elif key == "rules": ++ self.__rule(enable, _policy, Rich_Rule(rule_str=args), ++ transaction) ++ else: ++ log.warning("Policy '%s': Unknown setting '%s:%s', " ++ "unable to apply", policy, key, args) ++ ++ if use_transaction is None: ++ transaction.execute(enable) ++ ++ def apply_policy_settings(self, policy, use_transaction=None): ++ self._policy_settings(True, policy, use_transaction=use_transaction) ++ ++ def unapply_policy_settings(self, policy, use_transaction=None): ++ self._policy_settings(False, policy, use_transaction=use_transaction) ++ ++ def get_config_with_settings(self, policy): ++ """ ++ :return: exported config updated with runtime settings ++ """ ++ conf = list(self.get_policy(policy).export_config()) ++ conf[5] = self.list_services(policy) ++ conf[6] = self.list_ports(policy) ++ conf[7] = self.list_icmp_blocks(policy) ++ conf[8] = self.query_masquerade(policy) ++ conf[9] = self.list_forward_ports(policy) ++ conf[12] = self.list_rules(policy) ++ conf[13] = self.list_protocols(policy) ++ conf[14] = self.list_source_ports(policy) ++ conf[15] = self.query_icmp_block_inversion(policy) ++ return tuple(conf) ++ ++ # RICH LANGUAGE ++ ++ def check_rule(self, rule): ++ rule.check() ++ ++ def __rule_id(self, rule): ++ self.check_rule(rule) ++ return str(rule) ++ ++ def _rule_source_ipv(self, source): ++ if not source: ++ return None ++ ++ if source.addr: ++ if checkIPnMask(source.addr): ++ return "ipv4" ++ elif checkIP6nMask(source.addr): ++ return "ipv6" ++ elif hasattr(source, "mac") and source.mac: ++ return "" ++ elif hasattr(source, "ipset") and source.ipset: ++ self._check_ipset_type_for_source(source.ipset) ++ self._check_ipset_applied(source.ipset) ++ return self._ipset_family(source.ipset) ++ ++ return None ++ ++ def __rule(self, enable, policy, rule, transaction): ++ self._rule_prepare(enable, policy, rule, transaction) ++ ++ def add_rule(self, policy, rule, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ rule_id = self.__rule_id(rule) ++ if rule_id in _obj.settings["rules"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s' already in '%s'" % (rule, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self.__rule(True, _policy, rule, transaction) ++ ++ self.__register_rule(_obj, rule_id, timeout, sender) ++ transaction.add_fail(self.__unregister_rule, _obj, rule_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_rule(self, _obj, rule_id, timeout, sender): ++ _obj.settings["rules"][rule_id] = self.__gen_settings( ++ timeout, sender) ++ ++ def remove_rule(self, policy, rule, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ rule_id = self.__rule_id(rule) ++ if rule_id not in _obj.settings["rules"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "'%s' not in '%s'" % (rule, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self.__rule(False, _policy, rule, transaction) ++ ++ transaction.add_post(self.__unregister_rule, _obj, rule_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_rule(self, _obj, rule_id): ++ if rule_id in _obj.settings["rules"]: ++ del _obj.settings["rules"][rule_id] ++ ++ def query_rule(self, policy, rule): ++ return self.__rule_id(rule) in self.get_settings(policy)["rules"] ++ ++ def list_rules(self, policy): ++ return list(self.get_settings(policy)["rules"].keys()) ++ ++ # SERVICES ++ ++ def check_service(self, service): ++ self._fw.check_service(service) ++ ++ def __service_id(self, service): ++ self.check_service(service) ++ return service ++ ++ def add_service(self, policy, service, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ service_id = self.__service_id(service) ++ if service_id in _obj.settings["services"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s' already in '%s'" % (service, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._service(True, _policy, service, transaction) ++ ++ self.__register_service(_obj, service_id, timeout, sender) ++ transaction.add_fail(self.__unregister_service, _obj, service_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_service(self, _obj, service_id, timeout, sender): ++ _obj.settings["services"][service_id] = \ ++ self.__gen_settings(timeout, sender) ++ ++ def remove_service(self, policy, service, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ service_id = self.__service_id(service) ++ if service_id not in _obj.settings["services"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "'%s' not in '%s'" % (service, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._service(False, _policy, service, transaction) ++ ++ transaction.add_post(self.__unregister_service, _obj, service_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_service(self, _obj, service_id): ++ if service_id in _obj.settings["services"]: ++ del _obj.settings["services"][service_id] ++ ++ def query_service(self, policy, service): ++ return self.__service_id(service) in self.get_settings(policy)["services"] ++ ++ def list_services(self, policy): ++ return self.get_settings(policy)["services"].keys() ++ ++ def get_helpers_for_service_helpers(self, helpers): ++ _helpers = [ ] ++ for helper in helpers: ++ try: ++ _helper = self._fw.helper.get_helper(helper) ++ except FirewallError: ++ raise FirewallError(errors.INVALID_HELPER, helper) ++ _helpers.append(_helper) ++ return _helpers ++ ++ def get_helpers_for_service_modules(self, modules, enable): ++ # If automatic helper assignment is turned off, helpers that ++ # do not have ports defined will be replaced by the helpers ++ # that the helper.module defines. ++ _helpers = [ ] ++ for module in modules: ++ try: ++ helper = self._fw.helper.get_helper(module) ++ except FirewallError: ++ raise FirewallError(errors.INVALID_HELPER, module) ++ if len(helper.ports) < 1: ++ _module_short_name = get_nf_conntrack_short_name(helper.module) ++ try: ++ _helper = self._fw.helper.get_helper(_module_short_name) ++ _helpers.append(_helper) ++ except FirewallError: ++ if enable: ++ log.warning("Helper '%s' is not available" % _module_short_name) ++ continue ++ else: ++ _helpers.append(helper) ++ return _helpers ++ ++ # PORTS ++ ++ def check_port(self, port, protocol): ++ self._fw.check_port(port) ++ self._fw.check_tcpudp(protocol) ++ ++ def __port_id(self, port, protocol): ++ self.check_port(port, protocol) ++ return (portStr(port, "-"), protocol) ++ ++ def add_port(self, policy, port, protocol, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s:%s' already in '%s'" % (port, protocol, _name)) ++ ++ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ for range in added_ranges: ++ self._port(True, _policy, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._port(False, _policy, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__port_id(range, protocol) ++ self.__register_port(_obj, port_id, timeout, sender) ++ transaction.add_fail(self.__unregister_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__port_id(range, protocol) ++ transaction.add_post(self.__unregister_port, _obj, port_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_port(self, _obj, port_id, timeout, sender): ++ _obj.settings["ports"][port_id] = \ ++ self.__gen_settings(timeout, sender) ++ ++ def remove_port(self, policy, port, protocol, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ break ++ else: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "'%s:%s' not in '%s'" % (port, protocol, _name)) ++ ++ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ for range in added_ranges: ++ self._port(True, _policy, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._port(False, _policy, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__port_id(range, protocol) ++ self.__register_port(_obj, port_id, 0, None) ++ transaction.add_fail(self.__unregister_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__port_id(range, protocol) ++ transaction.add_post(self.__unregister_port, _obj, port_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_port(self, _obj, port_id): ++ if port_id in _obj.settings["ports"]: ++ del _obj.settings["ports"][port_id] ++ ++ def query_port(self, policy, port, protocol): ++ for (_port, _protocol) in self.get_settings(policy)["ports"]: ++ if portInPortRange(port, _port) and protocol == _protocol: ++ return True ++ ++ return False ++ ++ def list_ports(self, policy): ++ return list(self.get_settings(policy)["ports"].keys()) ++ ++ # PROTOCOLS ++ ++ def check_protocol(self, protocol): ++ if not checkProtocol(protocol): ++ raise FirewallError(errors.INVALID_PROTOCOL, protocol) ++ ++ def __protocol_id(self, protocol): ++ self.check_protocol(protocol) ++ return protocol ++ ++ def add_protocol(self, policy, protocol, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ protocol_id = self.__protocol_id(protocol) ++ if protocol_id in _obj.settings["protocols"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s' already in '%s'" % (protocol, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._protocol(True, _policy, protocol, transaction) ++ ++ self.__register_protocol(_obj, protocol_id, timeout, sender) ++ transaction.add_fail(self.__unregister_protocol, _obj, protocol_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_protocol(self, _obj, protocol_id, timeout, sender): ++ _obj.settings["protocols"][protocol_id] = \ ++ self.__gen_settings(timeout, sender) ++ ++ def remove_protocol(self, policy, protocol, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ protocol_id = self.__protocol_id(protocol) ++ if protocol_id not in _obj.settings["protocols"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "'%s' not in '%s'" % (protocol, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._protocol(False, _policy, protocol, transaction) ++ ++ transaction.add_post(self.__unregister_protocol, _obj, ++ protocol_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_protocol(self, _obj, protocol_id): ++ if protocol_id in _obj.settings["protocols"]: ++ del _obj.settings["protocols"][protocol_id] ++ ++ def query_protocol(self, policy, protocol): ++ return self.__protocol_id(protocol) in self.get_settings(policy)["protocols"] ++ ++ def list_protocols(self, policy): ++ return list(self.get_settings(policy)["protocols"].keys()) ++ ++ # SOURCE PORTS ++ ++ def __source_port_id(self, port, protocol): ++ self.check_port(port, protocol) ++ return (portStr(port, "-"), protocol) ++ ++ def add_source_port(self, policy, port, protocol, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s:%s' already in '%s'" % (port, protocol, _name)) ++ ++ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ for range in added_ranges: ++ self._source_port(True, _policy, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._source_port(False, _policy, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ self.__register_source_port(_obj, port_id, timeout, sender) ++ transaction.add_fail(self.__unregister_source_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ transaction.add_post(self.__unregister_source_port, _obj, port_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_source_port(self, _obj, port_id, timeout, sender): ++ _obj.settings["source_ports"][port_id] = \ ++ self.__gen_settings(timeout, sender) ++ ++ def remove_source_port(self, policy, port, protocol, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ break ++ else: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "'%s:%s' not in '%s'" % (port, protocol, _name)) ++ ++ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ for range in added_ranges: ++ self._source_port(True, _policy, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._source_port(False, _policy, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ self.__register_source_port(_obj, port_id, 0, None) ++ transaction.add_fail(self.__unregister_source_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ transaction.add_post(self.__unregister_source_port, _obj, port_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_source_port(self, _obj, port_id): ++ if port_id in _obj.settings["source_ports"]: ++ del _obj.settings["source_ports"][port_id] ++ ++ def query_source_port(self, policy, port, protocol): ++ for (_port, _protocol) in self.get_settings(policy)["source_ports"]: ++ if portInPortRange(port, _port) and protocol == _protocol: ++ return True ++ ++ return False ++ ++ def list_source_ports(self, policy): ++ return list(self.get_settings(policy)["source_ports"].keys()) ++ ++ # MASQUERADE ++ ++ def __masquerade_id(self): ++ return True ++ ++ def add_masquerade(self, policy, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ masquerade_id = self.__masquerade_id() ++ if masquerade_id in _obj.settings["masquerade"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "masquerade already enabled in '%s'" % _name) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._masquerade(True, _policy, transaction) ++ ++ self.__register_masquerade(_obj, masquerade_id, timeout, sender) ++ transaction.add_fail(self.__unregister_masquerade, _obj, masquerade_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_masquerade(self, _obj, masquerade_id, timeout, sender): ++ _obj.settings["masquerade"][masquerade_id] = \ ++ self.__gen_settings(timeout, sender) ++ ++ def remove_masquerade(self, policy, use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ masquerade_id = self.__masquerade_id() ++ if masquerade_id not in _obj.settings["masquerade"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "masquerade not enabled in '%s'" % _name) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._masquerade(False, _policy, transaction) ++ ++ transaction.add_post(self.__unregister_masquerade, _obj, masquerade_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_masquerade(self, _obj, masquerade_id): ++ if masquerade_id in _obj.settings["masquerade"]: ++ del _obj.settings["masquerade"][masquerade_id] ++ ++ def query_masquerade(self, policy): ++ return self.__masquerade_id() in self.get_settings(policy)["masquerade"] ++ ++ # PORT FORWARDING ++ ++ def check_forward_port(self, ipv, port, protocol, toport=None, toaddr=None): ++ self._fw.check_port(port) ++ self._fw.check_tcpudp(protocol) ++ if toport: ++ self._fw.check_port(toport) ++ if toaddr: ++ if not check_single_address(ipv, toaddr): ++ raise FirewallError(errors.INVALID_ADDR, toaddr) ++ if not toport and not toaddr: ++ raise FirewallError( ++ errors.INVALID_FORWARD, ++ "port-forwarding is missing to-port AND to-addr") ++ ++ def __forward_port_id(self, port, protocol, toport=None, toaddr=None): ++ if check_single_address("ipv6", toaddr): ++ self.check_forward_port("ipv6", port, protocol, toport, toaddr) ++ else: ++ self.check_forward_port("ipv4", port, protocol, toport, toaddr) ++ return (portStr(port, "-"), protocol, ++ portStr(toport, "-"), str(toaddr)) ++ ++ def add_forward_port(self, policy, port, protocol, toport=None, ++ toaddr=None, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ forward_id = self.__forward_port_id(port, protocol, toport, toaddr) ++ if forward_id in _obj.settings["forward_ports"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s:%s:%s:%s' already in '%s'" % \ ++ (port, protocol, toport, toaddr, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._forward_port(True, _policy, transaction, port, protocol, ++ toport, toaddr) ++ ++ self.__register_forward_port(_obj, forward_id, timeout, sender) ++ transaction.add_fail(self.__unregister_forward_port, _obj, forward_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_forward_port(self, _obj, forward_id, timeout, sender): ++ _obj.settings["forward_ports"][forward_id] = \ ++ self.__gen_settings(timeout, sender) ++ ++ def remove_forward_port(self, policy, port, protocol, toport=None, ++ toaddr=None, use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ forward_id = self.__forward_port_id(port, protocol, toport, toaddr) ++ if forward_id not in _obj.settings["forward_ports"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "'%s:%s:%s:%s' not in '%s'" % \ ++ (port, protocol, toport, toaddr, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._forward_port(False, _policy, transaction, port, protocol, ++ toport, toaddr) ++ ++ transaction.add_post(self.__unregister_forward_port, _obj, forward_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_forward_port(self, _obj, forward_id): ++ if forward_id in _obj.settings["forward_ports"]: ++ del _obj.settings["forward_ports"][forward_id] ++ ++ def query_forward_port(self, policy, port, protocol, toport=None, ++ toaddr=None): ++ forward_id = self.__forward_port_id(port, protocol, toport, toaddr) ++ return forward_id in self.get_settings(policy)["forward_ports"] ++ ++ def list_forward_ports(self, policy): ++ return list(self.get_settings(policy)["forward_ports"].keys()) ++ ++ # ICMP BLOCK ++ ++ def check_icmp_block(self, icmp): ++ self._fw.check_icmptype(icmp) ++ ++ def __icmp_block_id(self, icmp): ++ self.check_icmp_block(icmp) ++ return icmp ++ ++ def add_icmp_block(self, policy, icmp, timeout=0, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_timeout(timeout) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ icmp_id = self.__icmp_block_id(icmp) ++ if icmp_id in _obj.settings["icmp_blocks"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s' already in '%s'" % (icmp, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._icmp_block(True, _policy, icmp, transaction) ++ ++ self.__register_icmp_block(_obj, icmp_id, timeout, sender) ++ transaction.add_fail(self.__unregister_icmp_block, _obj, icmp_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_icmp_block(self, _obj, icmp_id, timeout, sender): ++ _obj.settings["icmp_blocks"][icmp_id] = \ ++ self.__gen_settings(timeout, sender) ++ ++ def remove_icmp_block(self, policy, icmp, use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ icmp_id = self.__icmp_block_id(icmp) ++ if icmp_id not in _obj.settings["icmp_blocks"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError(errors.NOT_ENABLED, ++ "'%s' not in '%s'" % (icmp, _name)) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ self._icmp_block(False, _policy, icmp, transaction) ++ ++ transaction.add_post(self.__unregister_icmp_block, _obj, icmp_id) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_icmp_block(self, _obj, icmp_id): ++ if icmp_id in _obj.settings["icmp_blocks"]: ++ del _obj.settings["icmp_blocks"][icmp_id] ++ ++ def query_icmp_block(self, policy, icmp): ++ return self.__icmp_block_id(icmp) in self.get_settings(policy)["icmp_blocks"] ++ ++ def list_icmp_blocks(self, policy): ++ return self.get_settings(policy)["icmp_blocks"].keys() ++ ++ # ICMP BLOCK INVERSION ++ ++ def __icmp_block_inversion_id(self): ++ return True ++ ++ def add_icmp_block_inversion(self, policy, sender=None, ++ use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ icmp_block_inversion_id = self.__icmp_block_inversion_id() ++ if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError( ++ errors.ALREADY_ENABLED, ++ "icmp-block-inversion already enabled in '%s'" % _name) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ # undo icmp blocks ++ for args in self.get_settings(_policy)["icmp_blocks"]: ++ self._icmp_block(False, _policy, args, transaction) ++ ++ self._icmp_block_inversion(False, _policy, transaction) ++ ++ self.__register_icmp_block_inversion(_obj, icmp_block_inversion_id, ++ sender) ++ transaction.add_fail(self.__undo_icmp_block_inversion, _policy, _obj, ++ icmp_block_inversion_id) ++ ++ # redo icmp blocks ++ if _obj.applied: ++ for args in self.get_settings(_policy)["icmp_blocks"]: ++ self._icmp_block(True, _policy, args, transaction) ++ ++ self._icmp_block_inversion(True, _policy, transaction) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __register_icmp_block_inversion(self, _obj, icmp_block_inversion_id, ++ sender): ++ _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] = \ ++ self.__gen_settings(0, sender) ++ ++ def __undo_icmp_block_inversion(self, _policy, _obj, icmp_block_inversion_id): ++ transaction = self.new_transaction() ++ ++ # undo icmp blocks ++ if _obj.applied: ++ for args in self.get_settings(_policy)["icmp_blocks"]: ++ self._icmp_block(False, _policy, args, transaction) ++ ++ if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: ++ del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] ++ ++ # redo icmp blocks ++ if _obj.applied: ++ for args in self.get_settings(_policy)["icmp_blocks"]: ++ self._icmp_block(True, _policy, args, transaction) ++ ++ transaction.execute(True) ++ ++ def remove_icmp_block_inversion(self, policy, use_transaction=None): ++ _policy = self._fw.check_policy(policy) ++ self._fw.check_panic() ++ _obj = self._policies[_policy] ++ ++ icmp_block_inversion_id = self.__icmp_block_inversion_id() ++ if icmp_block_inversion_id not in _obj.settings["icmp_block_inversion"]: ++ _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy ++ raise FirewallError( ++ errors.NOT_ENABLED, ++ "icmp-block-inversion not enabled in '%s'" % _name) ++ ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ if _obj.applied: ++ # undo icmp blocks ++ for args in self.get_settings(_policy)["icmp_blocks"]: ++ self._icmp_block(False, _policy, args, transaction) ++ ++ self._icmp_block_inversion(False, _policy, transaction) ++ ++ self.__unregister_icmp_block_inversion(_obj, ++ icmp_block_inversion_id) ++ transaction.add_fail(self.__register_icmp_block_inversion, _obj, ++ icmp_block_inversion_id, None) ++ ++ # redo icmp blocks ++ if _obj.applied: ++ for args in self.get_settings(_policy)["icmp_blocks"]: ++ self._icmp_block(True, _policy, args, transaction) ++ ++ self._icmp_block_inversion(True, _policy, transaction) ++ ++ if use_transaction is None: ++ transaction.execute(True) ++ ++ return _policy ++ ++ def __unregister_icmp_block_inversion(self, _obj, icmp_block_inversion_id): ++ if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: ++ del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] ++ ++ def query_icmp_block_inversion(self, policy): ++ return self.__icmp_block_inversion_id() in \ ++ self.get_settings(policy)["icmp_block_inversion"] ++ ++ def gen_chain_rules(self, policy, create, table, chain, transaction): ++ # HACK: iptables backend has to support the (raw, PREROUTING) chain in ++ # multiple policies derived for zones - this is due to conntrack ++ # helpers. As such, track it in the policy matching zone --> HOST. ++ obj = self._fw.policy.get_policy(policy) ++ if obj.derived_from_zone and table == "raw" and chain == "PREROUTING": ++ for p in self._fw.zone._zone_policies[obj.derived_from_zone]: ++ p_obj = self._fw.policy.get_policy(p) ++ if p_obj.egress_zones[0] == "HOST": ++ tracking_policy = p ++ break ++ else: ++ tracking_policy = policy ++ ++ if create: ++ if tracking_policy in self._chains and \ ++ table in self._chains[tracking_policy]: ++ return ++ else: ++ if tracking_policy not in self._chains or \ ++ table not in self._chains[tracking_policy]: ++ return ++ ++ for backend in self._fw.enabled_backends(): ++ if backend.policies_supported and \ ++ table in backend.get_available_tables(): ++ rules = backend.build_policy_chain_rules(policy, table) ++ transaction.add_rules(backend, rules) ++ ++ self._register_chains(tracking_policy, create, [table]) ++ transaction.add_fail(self._register_chains, tracking_policy, not create, [table]) ++ ++ def _register_chains(self, policy, create, tables): ++ for table in tables: ++ if create: ++ self._chains.setdefault(policy, []).append(table) ++ else: ++ self._chains[policy].remove(table) ++ if len(self._chains[policy]) == 0: ++ del self._chains[policy] ++ ++ # IPSETS ++ ++ def _ipset_family(self, name): ++ if self._fw.ipset.get_type(name) == "hash:mac": ++ return None ++ return self._fw.ipset.get_family(name) ++ ++ def __ipset_type(self, name): ++ return self._fw.ipset.get_type(name) ++ ++ def _ipset_match_flags(self, name, flag): ++ return ",".join([flag] * self._fw.ipset.get_dimension(name)) ++ ++ def _check_ipset_applied(self, name): ++ return self._fw.ipset.check_applied(name) ++ ++ def _check_ipset_type_for_source(self, name): ++ _type = self.__ipset_type(name) ++ if _type not in SOURCE_IPSET_TYPES: ++ raise FirewallError( ++ errors.INVALID_IPSET, ++ "ipset '%s' with type '%s' not usable as source" % \ ++ (name, _type)) ++ ++ def _rule_prepare(self, enable, policy, rule, transaction): ++ if rule.family is not None: ++ ipvs = [ rule.family ] ++ else: ++ ipvs = [ipv for ipv in ["ipv4", "ipv6"] if self._fw.is_ipv_enabled(ipv)] ++ ++ source_ipv = self._rule_source_ipv(rule.source) ++ if source_ipv is not None and source_ipv != "": ++ if rule.family is not None: ++ # rule family is defined by user, no way to change it ++ if rule.family != source_ipv: ++ raise FirewallError(errors.INVALID_RULE, ++ "Source address family '%s' conflicts with rule family '%s'." % (source_ipv, rule.family)) ++ else: ++ # use the source family as rule family ++ ipvs = [ source_ipv ] ++ ++ # add an element to object to allow backends to know what ipvs this applies to ++ rule.ipvs = ipvs ++ ++ for backend in set([self._fw.get_backend_by_ipv(x) for x in ipvs]): ++ # SERVICE ++ if type(rule.element) == Rich_Service: ++ svc = self._fw.service.get_service(rule.element.name) ++ ++ destinations = [] ++ if len(svc.destination) > 0: ++ if rule.destination: ++ # we can not use two destinations at the same time ++ raise FirewallError(errors.INVALID_RULE, ++ "Destination conflict with service.") ++ for ipv in ipvs: ++ if ipv in svc.destination and backend.is_ipv_supported(ipv): ++ destinations.append(svc.destination[ipv]) ++ else: ++ # dummy for the following for loop ++ destinations.append(None) ++ ++ for destination in destinations: ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ transaction.add_chain(policy, "raw", "PREROUTING") ++ ++ if type(rule.action) == Rich_Accept: ++ # only load modules for accept action ++ helpers = self.get_helpers_for_service_modules(svc.modules, ++ enable) ++ helpers += self.get_helpers_for_service_helpers(svc.helpers) ++ helpers = sorted(set(helpers), key=lambda x: x.name) ++ ++ modules = [ ] ++ for helper in helpers: ++ module = helper.module ++ _module_short_name = get_nf_conntrack_short_name(module) ++ nat_module = module.replace("conntrack", "nat") ++ modules.append(nat_module) ++ if helper.family != "" and not backend.is_ipv_supported(helper.family): ++ # no support for family ipv, continue ++ continue ++ if len(helper.ports) < 1: ++ modules.append(module) ++ else: ++ for (port,proto) in helper.ports: ++ rules = backend.build_policy_helper_ports_rules( ++ enable, policy, proto, port, ++ destination, helper.name, _module_short_name) ++ transaction.add_rules(backend, rules) ++ transaction.add_modules(modules) ++ ++ # create rules ++ for (port,proto) in svc.ports: ++ if enable and type(rule.action) == Rich_Mark: ++ transaction.add_chain(policy, "mangle", "PREROUTING") ++ rules = backend.build_policy_ports_rules( ++ enable, policy, proto, port, destination, rule) ++ transaction.add_rules(backend, rules) ++ ++ for proto in svc.protocols: ++ if enable and type(rule.action) == Rich_Mark: ++ transaction.add_chain(policy, "mangle", "PREROUTING") ++ rules = backend.build_policy_protocol_rules( ++ enable, policy, proto, destination, rule) ++ transaction.add_rules(backend, rules) ++ ++ # create rules ++ for (port,proto) in svc.source_ports: ++ if enable and type(rule.action) == Rich_Mark: ++ transaction.add_chain(policy, "mangle", "PREROUTING") ++ rules = backend.build_policy_source_ports_rules( ++ enable, policy, proto, port, destination, rule) ++ transaction.add_rules(backend, rules) ++ ++ # PORT ++ elif type(rule.element) == Rich_Port: ++ port = rule.element.port ++ protocol = rule.element.protocol ++ self.check_port(port, protocol) ++ ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ if enable and type(rule.action) == Rich_Mark: ++ transaction.add_chain(policy, "mangle", "PREROUTING") ++ ++ rules = backend.build_policy_ports_rules( ++ enable, policy, protocol, port, None, rule) ++ transaction.add_rules(backend, rules) ++ ++ # PROTOCOL ++ elif type(rule.element) == Rich_Protocol: ++ protocol = rule.element.value ++ self.check_protocol(protocol) ++ ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ if enable and type(rule.action) == Rich_Mark: ++ transaction.add_chain(policy, "mangle", "PREROUTING") ++ ++ rules = backend.build_policy_protocol_rules( ++ enable, policy, protocol, None, rule) ++ transaction.add_rules(backend, rules) ++ ++ # MASQUERADE ++ elif type(rule.element) == Rich_Masquerade: ++ if enable: ++ transaction.add_chain(policy, "nat", "POSTROUTING") ++ transaction.add_chain(policy, "filter", "FORWARD_OUT") ++ for ipv in ipvs: ++ if backend.is_ipv_supported(ipv): ++ transaction.add_post(enable_ip_forwarding, ipv) ++ ++ rules = backend.build_policy_masquerade_rules(enable, policy, rule) ++ transaction.add_rules(backend, rules) ++ ++ # FORWARD PORT ++ elif type(rule.element) == Rich_ForwardPort: ++ port = rule.element.port ++ protocol = rule.element.protocol ++ toport = rule.element.to_port ++ toaddr = rule.element.to_address ++ for ipv in ipvs: ++ if backend.is_ipv_supported(ipv): ++ self.check_forward_port(ipv, port, protocol, toport, toaddr) ++ if toaddr and enable: ++ transaction.add_post(enable_ip_forwarding, ipv) ++ ++ if enable: ++ transaction.add_chain(policy, "nat", "PREROUTING") ++ ++ rules = backend.build_policy_forward_port_rules( ++ enable, policy, port, protocol, toport, ++ toaddr, rule) ++ transaction.add_rules(backend, rules) ++ ++ # SOURCE PORT ++ elif type(rule.element) == Rich_SourcePort: ++ port = rule.element.port ++ protocol = rule.element.protocol ++ self.check_port(port, protocol) ++ ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ if enable and type(rule.action) == Rich_Mark: ++ transaction.add_chain(policy, "mangle", "PREROUTING") ++ ++ rules = backend.build_policy_source_ports_rules( ++ enable, policy, protocol, port, None, rule) ++ transaction.add_rules(backend, rules) ++ ++ # ICMP BLOCK and ICMP TYPE ++ elif type(rule.element) == Rich_IcmpBlock or \ ++ type(rule.element) == Rich_IcmpType: ++ ict = self._fw.icmptype.get_icmptype(rule.element.name) ++ ++ if type(rule.element) == Rich_IcmpBlock and \ ++ rule.action and type(rule.action) == Rich_Accept: ++ # icmp block might have reject or drop action, but not accept ++ raise FirewallError(errors.INVALID_RULE, ++ "IcmpBlock not usable with accept action") ++ if ict.destination: ++ for ipv in ipvs: ++ if ipv in ict.destination \ ++ and not backend.is_ipv_supported(ipv): ++ raise FirewallError( ++ errors.INVALID_RULE, ++ "Icmp%s %s not usable with %s" % \ ++ ("Block" if type(rule.element) == \ ++ Rich_IcmpBlock else "Type", ++ rule.element.name, backend.name)) ++ ++ table = "filter" ++ if enable: ++ transaction.add_chain(policy, table, "INPUT") ++ transaction.add_chain(policy, table, "FORWARD_IN") ++ ++ rules = backend.build_policy_icmp_block_rules(enable, policy, ict, rule) ++ transaction.add_rules(backend, rules) ++ ++ elif rule.element is None: ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ if enable and type(rule.action) == Rich_Mark: ++ transaction.add_chain(policy, "mangle", "PREROUTING") ++ ++ rules = backend.build_policy_rich_source_destination_rules( ++ enable, policy, rule) ++ transaction.add_rules(backend, rules) ++ ++ # EVERYTHING ELSE ++ else: ++ raise FirewallError(errors.INVALID_RULE, "Unknown element %s" % ++ type(rule.element)) ++ ++ def _service(self, enable, policy, service, transaction, included_services=None): ++ svc = self._fw.service.get_service(service) ++ helpers = self.get_helpers_for_service_modules(svc.modules, enable) ++ helpers += self.get_helpers_for_service_helpers(svc.helpers) ++ helpers = sorted(set(helpers), key=lambda x: x.name) ++ ++ # First apply any services this service may include ++ if included_services is None: ++ included_services = [service] ++ for include in svc.includes: ++ if include in included_services: ++ continue ++ self.check_service(include) ++ included_services.append(include) ++ self._service(enable, policy, include, transaction, included_services=included_services) ++ ++ if enable: ++ transaction.add_chain(policy, "raw", "PREROUTING") ++ transaction.add_chain(policy, "filter", "INPUT") ++ ++ # build a list of (backend, destination). The destination may be ipv4, ++ # ipv6 or None ++ # ++ backends_ipv = [] ++ for ipv in ["ipv4", "ipv6"]: ++ if not self._fw.is_ipv_enabled(ipv): ++ continue ++ backend = self._fw.get_backend_by_ipv(ipv) ++ if len(svc.destination) > 0: ++ if ipv in svc.destination: ++ backends_ipv.append((backend, svc.destination[ipv])) ++ else: ++ if (backend, None) not in backends_ipv: ++ backends_ipv.append((backend, None)) ++ ++ for (backend,destination) in backends_ipv: ++ for helper in helpers: ++ module = helper.module ++ _module_short_name = get_nf_conntrack_short_name(module) ++ nat_module = helper.module.replace("conntrack", "nat") ++ transaction.add_module(nat_module) ++ if helper.family != "" and not backend.is_ipv_supported(helper.family): ++ # no support for family ipv, continue ++ continue ++ if len(helper.ports) < 1: ++ transaction.add_module(module) ++ else: ++ for (port,proto) in helper.ports: ++ rules = backend.build_policy_helper_ports_rules( ++ enable, policy, proto, port, ++ destination, helper.name, _module_short_name) ++ transaction.add_rules(backend, rules) ++ ++ for (port,proto) in svc.ports: ++ rules = backend.build_policy_ports_rules(enable, policy, proto, ++ port, destination) ++ transaction.add_rules(backend, rules) ++ ++ for protocol in svc.protocols: ++ rules = backend.build_policy_protocol_rules( ++ enable, policy, protocol, destination) ++ transaction.add_rules(backend, rules) ++ ++ for (port,proto) in svc.source_ports: ++ rules = backend.build_policy_source_ports_rules( ++ enable, policy, proto, port, destination) ++ transaction.add_rules(backend, rules) ++ ++ def _port(self, enable, policy, port, protocol, transaction): ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ ++ for backend in self._fw.enabled_backends(): ++ if not backend.policies_supported: ++ continue ++ ++ rules = backend.build_policy_ports_rules(enable, policy, protocol, ++ port) ++ transaction.add_rules(backend, rules) ++ ++ def _protocol(self, enable, policy, protocol, transaction): ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ ++ for backend in self._fw.enabled_backends(): ++ if not backend.policies_supported: ++ continue ++ ++ rules = backend.build_policy_protocol_rules(enable, policy, protocol) ++ transaction.add_rules(backend, rules) ++ ++ def _source_port(self, enable, policy, port, protocol, transaction): ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ ++ for backend in self._fw.enabled_backends(): ++ if not backend.policies_supported: ++ continue ++ ++ rules = backend.build_policy_source_ports_rules(enable, policy, protocol, port) ++ transaction.add_rules(backend, rules) ++ ++ def _masquerade(self, enable, policy, transaction): ++ if enable: ++ transaction.add_chain(policy, "nat", "POSTROUTING") ++ transaction.add_chain(policy, "filter", "FORWARD_OUT") ++ ++ ipv = "ipv4" ++ transaction.add_post(enable_ip_forwarding, ipv) ++ ++ backend = self._fw.get_backend_by_ipv(ipv) ++ rules = backend.build_policy_masquerade_rules(enable, policy) ++ transaction.add_rules(backend, rules) ++ ++ def _forward_port(self, enable, policy, transaction, port, protocol, ++ toport=None, toaddr=None): ++ if check_single_address("ipv6", toaddr): ++ ipv = "ipv6" ++ else: ++ ipv = "ipv4" ++ ++ if enable: ++ transaction.add_chain(policy, "nat", "PREROUTING") ++ ++ if toaddr and enable: ++ transaction.add_post(enable_ip_forwarding, ipv) ++ backend = self._fw.get_backend_by_ipv(ipv) ++ rules = backend.build_policy_forward_port_rules( ++ enable, policy, port, protocol, toport, ++ toaddr) ++ transaction.add_rules(backend, rules) ++ ++ def _icmp_block(self, enable, policy, icmp, transaction): ++ ict = self._fw.icmptype.get_icmptype(icmp) ++ ++ if enable: ++ transaction.add_chain(policy, "filter", "INPUT") ++ transaction.add_chain(policy, "filter", "FORWARD_IN") ++ ++ for backend in self._fw.enabled_backends(): ++ if not backend.policies_supported: ++ continue ++ skip_backend = False ++ ++ if ict.destination: ++ for ipv in ["ipv4", "ipv6"]: ++ if ipv in ict.destination: ++ if not backend.is_ipv_supported(ipv): ++ skip_backend = True ++ break ++ ++ if skip_backend: ++ continue ++ ++ rules = backend.build_policy_icmp_block_rules(enable, policy, ict) ++ transaction.add_rules(backend, rules) ++ ++ def _icmp_block_inversion(self, enable, policy, transaction): ++ target = self._policies[policy].target ++ ++ # Do not add general icmp accept rules into a trusted, block or drop ++ # policy. ++ if target in [ "DROP", "%%REJECT%%", "REJECT" ]: ++ return ++ if not self.query_icmp_block_inversion(policy) and target == "ACCEPT": ++ # ibi target and policy target are ACCEPT, no need to add an extra ++ # rule ++ return ++ ++ transaction.add_chain(policy, "filter", "INPUT") ++ transaction.add_chain(policy, "filter", "FORWARD_IN") ++ ++ for backend in self._fw.enabled_backends(): ++ if not backend.policies_supported: ++ continue ++ ++ rules = backend.build_policy_icmp_block_inversion_rules(enable, policy) ++ transaction.add_rules(backend, rules) ++ ++ def _get_table_chains_for_zone_dispatch(self, policy): ++ """Create a list of (table, chain) needed for zone dispatch""" ++ obj = self._policies[policy] ++ if obj.egress_zones[0] == "HOST": ++ # zone --> Host ++ return [("filter", "INPUT")] ++ elif obj.egress_zones[0] == "ANY": ++ # zone --> any ++ return [("filter", "FORWARD_IN"), ("nat", "PREROUTING"), ++ ("mangle", "PREROUTING"), ("raw", "PREROUTING")] ++ elif obj.ingress_zones[0] == "ANY": ++ # any --> zone ++ return [("filter", "FORWARD_OUT"), ("nat", "POSTROUTING")] ++ else: ++ return FirewallError("Invalid policy: %s" % (policy)) ++ ++ def policy_base_chain_name(self, policy, table, policy_prefix): ++ obj = self._fw.policy.get_policy(policy) ++ if obj.derived_from_zone: ++ if obj.egress_zones[0] == "HOST": ++ # zone --> Host ++ if table == "filter": ++ return "IN_" + obj.derived_from_zone ++ if table == "raw": ++ # FIXME: nftables doesn't actually use this. Only iptables ++ return "PRE_" + obj.derived_from_zone ++ elif obj.egress_zones[0] == "ANY": ++ # zone --> any ++ if table == "filter": ++ return "FWDI_" + obj.derived_from_zone ++ elif table in ["nat", "mangle", "raw"]: ++ return "PRE_" + obj.derived_from_zone ++ elif obj.ingress_zones[0] == "ANY": ++ # any --> zone ++ if table == "filter": ++ return "FWDO_" + obj.derived_from_zone ++ elif table == "nat": ++ return "POST_" + obj.derived_from_zone ++ return FirewallError("Can't convert policy to chain name: %s" % (policy)) ++ else: ++ return policy_prefix + policy +diff --git a/src/firewall/core/fw_transaction.py b/src/firewall/core/fw_transaction.py +index cc8b1e1..7639cdb 100644 +--- a/src/firewall/core/fw_transaction.py ++++ b/src/firewall/core/fw_transaction.py +@@ -36,7 +36,7 @@ class FirewallTransaction(object): + self.pre_funcs = [ ] # [ (func, args),.. ] + self.post_funcs = [ ] # [ (func, args),.. ] + self.fail_funcs = [ ] # [ (func, args),.. ] +- self.chains = [ ] # [ (zone, table, chain),.. ] ++ self.chains = [ ] # [ (table, policy),.. ] + self.modules = [ ] # [ module,.. ] + + def clear(self): +@@ -68,16 +68,16 @@ class FirewallTransaction(object): + def add_fail(self, func, *args): + self.fail_funcs.append((func, args)) + +- def add_chain(self, zone, table, chain): +- ztc = (zone, table, chain) +- if ztc not in self.chains: +- self.fw.zone.gen_chain_rules(zone, True, table, chain, self) +- self.chains.append(ztc) ++ def add_chain(self, policy, table, chain): ++ tp = (table, policy) ++ if tp not in self.chains: ++ self.fw.policy.gen_chain_rules(policy, True, table, chain, self) ++ self.chains.append(tp) + +- def remove_chain(self, zone, table, chain): +- ztc = (zone, table, chain) +- if ztc in self.chains: +- self.chains.remove(ztc) ++ def remove_chain(self, policy, table, chain): ++ tp = (table, policy) ++ if tp in self.chains: ++ self.chains.remove(tp) + + def add_module(self, module): + if module not in self.modules: +diff --git a/src/firewall/core/fw_zone.py b/src/firewall/core/fw_zone.py +index d32d7a8..2f47e33 100644 +--- a/src/firewall/core/fw_zone.py ++++ b/src/firewall/core/fw_zone.py +@@ -20,39 +20,39 @@ + # + + import time +-from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET, \ +- ZONE_SOURCE_IPSET_TYPES +-from firewall.core.logger import log +-from firewall.functions import portStr, checkIPnMask, checkIP6nMask, \ +- checkProtocol, enable_ip_forwarding, check_single_address, check_mac, \ +- portInPortRange, get_nf_conntrack_short_name, coalescePortRange, breakPortRange +-from firewall.core.rich import Rich_Rule, Rich_Accept, \ +- Rich_Mark, Rich_Service, Rich_Port, Rich_Protocol, \ +- Rich_Masquerade, Rich_ForwardPort, Rich_SourcePort, Rich_IcmpBlock, \ +- Rich_IcmpType ++import copy ++from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET, SOURCE_IPSET_TYPES + from firewall.core.fw_transaction import FirewallTransaction ++from firewall.core.io.policy import Policy ++from firewall.core.logger import log ++from firewall.core.rich import Rich_Service, Rich_Port, Rich_Protocol, Rich_SourcePort, Rich_ForwardPort, \ ++ Rich_IcmpBlock, Rich_IcmpType, Rich_Masquerade, Rich_Mark ++from firewall.functions import checkIPnMask, checkIP6nMask, check_mac + from firewall import errors + from firewall.errors import FirewallError + from firewall.fw_types import LastUpdatedOrderedDict + + class FirewallZone(object): ++ ZONE_POLICY_PRIORITY = 0 ++ + def __init__(self, fw): + self._fw = fw +- self._chains = { } + self._zones = { } ++ self._zone_policies = { } + + def __repr__(self): +- return '%s(%r, %r)' % (self.__class__, self._chains, self._zones) ++ return '%s(%r)' % (self.__class__, self._zones) + + def cleanup(self): +- self._chains.clear() + self._zones.clear() +- +- # transaction ++ self._zone_policies.clear() + + def new_transaction(self): + return FirewallTransaction(self._fw) + ++ def policy_name_from_zones(self, fromZone, toZone): ++ return "zone_{fromZone}_{toZone}".format(fromZone=fromZone, toZone=toZone) ++ + # zones + + def get_zones(self): +@@ -78,24 +78,77 @@ class FirewallZone(object): + z = self._fw.check_zone(zone) + return self._zones[z] + +- def _first_except(self, e, f, name, *args, **kwargs): +- try: +- f(name, *args, **kwargs) +- except FirewallError as error: +- if not e: +- return error +- return e ++ def policy_obj_from_zone_obj(self, z_obj, fromZone, toZone): ++ p_obj = Policy() ++ p_obj.derived_from_zone = z_obj.name ++ p_obj.name = self.policy_name_from_zones(fromZone, toZone) ++ p_obj.priority = self.ZONE_POLICY_PRIORITY ++ p_obj.target = z_obj.target ++ p_obj.ingress_zones = [fromZone] ++ p_obj.egress_zones = [toZone] ++ ++ # copy zone permanent config to policy permanent config ++ # WARN: This assumes the same attribute names. ++ # ++ for setting in ["services", "ports", ++ "masquerade", "forward_ports", ++ "source_ports", ++ "icmp_blocks", "rules", ++ "protocols"]: ++ if fromZone == z_obj.name and toZone == "HOST" and \ ++ setting in ["services", "ports", "source_ports", "icmp_blocks", "protocols"]: ++ # zone --> HOST ++ setattr(p_obj, setting, copy.deepcopy(getattr(z_obj, setting))) ++ elif fromZone == "ANY" and toZone == z_obj.name and setting in ["masquerade"]: ++ # any zone --> zone ++ setattr(p_obj, setting, copy.deepcopy(getattr(z_obj, setting))) ++ elif fromZone == z_obj.name and toZone == "ANY" and \ ++ setting in ["icmp_blocks", "forward_ports"]: ++ # zone --> any zone ++ setattr(p_obj, setting, copy.deepcopy(getattr(z_obj, setting))) ++ elif setting in ["rules"]: ++ p_obj.rules = [] ++ for rule in z_obj.rules: ++ current_policy = self.policy_name_from_zones(fromZone, toZone) ++ ++ if current_policy in self._rich_rule_to_policies(z_obj.name, rule): ++ p_obj.rules.append(copy.deepcopy(rule)) ++ ++ return p_obj + + def add_zone(self, obj): + obj.settings = { x : LastUpdatedOrderedDict() +- for x in [ "interfaces", "sources", +- "services", "ports", +- "masquerade", "forward_ports", +- "source_ports", +- "icmp_blocks", "rules", +- "protocols", "icmp_block_inversion" ] } +- ++ for x in ["interfaces", "sources", ++ "icmp_block_inversion"] } + self._zones[obj.name] = obj ++ self._zone_policies[obj.name] = [] ++ ++ # Create policy objects, will need many: ++ # - (zone --> HOST) - ports, service, etc ++ # - (any zone --> zone) - masquerade ++ # - (zone --> any zone) - ICMP block, icmp block inversion ++ # - also includes forward-ports because it works on (nat, ++ # PREROUTING) and therefore applies to redirects to the local ++ # host or dnat to a different host. ++ # - also includes rich rule "mark" action for the same reason ++ # ++ for fromZone,toZone in [(obj.name, "HOST"), ++ ("ANY", obj.name), (obj.name, "ANY")]: ++ p_obj = self.policy_obj_from_zone_obj(obj, fromZone, toZone) ++ self._fw.policy.add_policy(p_obj) ++ self._zone_policies[obj.name].append(p_obj.name) ++ ++ self.copy_permanent_to_runtime(obj.name) ++ ++ def copy_permanent_to_runtime(self, zone): ++ obj = self._zones[zone] ++ ++ for arg in obj.interfaces: ++ self.add_interface(zone, arg, allow_apply=False) ++ for arg in obj.sources: ++ self.add_source(zone, arg, allow_apply=False) ++ if obj.icmp_block_inversion: ++ self.add_icmp_block_inversion(zone) + + def remove_zone(self, zone): + obj = self._zones[zone] +@@ -103,68 +156,14 @@ class FirewallZone(object): + self.unapply_zone_settings(zone) + obj.settings.clear() + del self._zones[zone] ++ del self._zone_policies[zone] + + def apply_zones(self, use_transaction=None): +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- error = None + for zone in self.get_zones(): +- obj = self._zones[zone] +- +- # register icmp block inversion setting but don't apply +- if obj.icmp_block_inversion: +- error = self._first_except(error, self.add_icmp_block_inversion, obj.name, +- use_transaction=transaction) +- +- if len(obj.interfaces) > 0 or len(obj.sources) > 0: +- obj.applied = True +- +- log.debug1("Applying zone '%s'", obj.name) +- +- # load zone in case of missing services, icmptypes etc. +- for args in obj.icmp_blocks: +- error = self._first_except(error, self.add_icmp_block, obj.name, args, +- use_transaction=transaction) +- for args in obj.forward_ports: +- error = self._first_except(error, self.add_forward_port, obj.name, *args, +- use_transaction=transaction) +- for args in obj.services: +- error = self._first_except(error, self.add_service, obj.name, args, +- use_transaction=transaction) +- for args in obj.ports: +- error = self._first_except(error, self.add_port, obj.name, *args, +- use_transaction=transaction) +- for args in obj.protocols: +- error = self._first_except(error, self.add_protocol, obj.name, args, +- use_transaction=transaction) +- for args in obj.source_ports: +- error = self._first_except(error, self.add_source_port, obj.name, *args, +- use_transaction=transaction) +- if obj.masquerade: +- error = self._first_except(error, self.add_masquerade, obj.name, +- use_transaction=transaction) +- for args in obj.rules: +- error = self._first_except(error, self.add_rule, obj.name, args, +- use_transaction=transaction) +- for args in obj.interfaces: +- error = self._first_except(error, self.add_interface, obj.name, args, +- use_transaction=transaction) +- for args in obj.sources: +- error = self._first_except(error, self.add_source, obj.name, args, +- use_transaction=transaction) +- # apply icmp accept/reject rule always +- if obj.applied: +- error = self._first_except(error, self._icmp_block_inversion, True, +- obj.name, transaction) +- +- if use_transaction is None: +- transaction.execute(True) +- +- if error: +- raise error ++ z_obj = self._zones[zone] ++ if len(z_obj.interfaces) > 0 or len(z_obj.sources) > 0: ++ log.debug1("Applying zone '%s'", zone) ++ self.apply_zone_settings(zone, use_transaction=use_transaction) + + def set_zone_applied(self, zone, applied): + obj = self._zones[zone] +@@ -212,19 +211,6 @@ class FirewallZone(object): + if use_transaction is None: + transaction.execute(True) + +- # dynamic chain handling +- +- def _register_chains(self, zone, create, chains): +- for (table, chain) in chains: +- if create: +- self._chains.setdefault(zone, { }).setdefault(table, [ ]).append(chain) +- else: +- self._chains[zone][table].remove(chain) +- if len(self._chains[zone][table]) == 0: +- del self._chains[zone][table] +- if len(self._chains[zone]) == 0: +- del self._chains[zone] +- + # settings + + # generate settings record with sender, timeout +@@ -239,114 +225,62 @@ class FirewallZone(object): + def get_settings(self, zone): + return self.get_zone(zone).settings + +- def set_settings(self, zone, settings): +- _obj = self.get_zone(zone) +- +- try: +- for key in settings: +- for args in settings[key]: +- if args in _obj.settings[key]: +- # do not add things, that are already active in the +- # zone configuration, also do not restore date, +- # sender and timeout +- continue +- if key == "icmp_blocks": +- self.add_icmp_block(zone, args) +- elif key == "forward_ports": +- self.add_forward_port(zone, *args) +- elif key == "services": +- self.add_service(zone, args) +- elif key == "ports": +- self.add_port(zone, *args) +- elif key == "protocols": +- self.add_protocol(zone, *args) +- elif key == "source_ports": +- self.add_source_port(zone, *args) +- elif key == "masquerade": +- self.add_masquerade(zone) +- elif key == "rules": +- self.add_rule(zone, Rich_Rule(rule_str=args)) +- elif key == "interfaces": +- self.change_zone_of_interface(zone, args) +- elif key == "sources": +- self.change_zone_of_source(zone, args) +- else: +- log.warning("Zone '%s': Unknown setting '%s:%s', " +- "unable to restore.", zone, key, args) +- # restore old date, sender and timeout +- if args in _obj.settings[key]: +- _obj.settings[key][args] = settings[key][args] +- +- except FirewallError as msg: +- log.warning(str(msg)) +- +- def __zone_settings(self, enable, zone, use_transaction=None): ++ def _zone_settings(self, enable, zone, transaction): ++ settings = self.get_settings(zone) ++ for key in settings: ++ for args in settings[key]: ++ if key == "interfaces": ++ self._interface(enable, zone, args, transaction) ++ elif key == "sources": ++ self._source(enable, zone, args[0], args[1], transaction) ++ elif key == "icmp_block_inversion": ++ continue ++ else: ++ log.warning("Zone '%s': Unknown setting '%s:%s', " ++ "unable to apply", zone, key, args) ++ # ICMP-block-inversion is always applied ++ if enable: ++ self._icmp_block_inversion(enable, zone, transaction) ++ ++ def apply_zone_settings(self, zone, use_transaction=None): + _zone = self._fw.check_zone(zone) + obj = self._zones[_zone] +- if (enable and obj.applied) or (not enable and not obj.applied): ++ if obj.applied: + return +- if enable: +- obj.applied = True ++ obj.applied = True + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + +- settings = self.get_settings(zone) +- for key in settings: +- for args in settings[key]: +- try: +- if key == "icmp_blocks": +- self._icmp_block(enable, _zone, args, transaction) +- elif key == "icmp_block_inversion": +- continue +- elif key == "forward_ports": +- self._forward_port(enable, _zone, transaction, +- *args) +- elif key == "services": +- self._service(enable, _zone, args, transaction) +- elif key == "ports": +- self._port(enable, _zone, args[0], args[1], +- transaction) +- elif key == "protocols": +- self._protocol(enable, _zone, args, transaction) +- elif key == "source_ports": +- self._source_port(enable, _zone, args[0], args[1], +- transaction) +- elif key == "masquerade": +- self._masquerade(enable, _zone, transaction) +- elif key == "rules": +- self.__rule(enable, _zone, Rich_Rule(rule_str=args), +- transaction) +- elif key == "interfaces": +- self._interface(enable, _zone, args, transaction) +- elif key == "sources": +- self._source(enable, _zone, args[0], args[1], +- transaction) +- else: +- log.warning("Zone '%s': Unknown setting '%s:%s', " +- "unable to apply", zone, key, args) +- except FirewallError as msg: +- log.warning(str(msg)) ++ for policy in self._zone_policies[_zone]: ++ log.debug1("Applying policy (%s) derived from zone '%s'", policy, zone) ++ self._fw.policy.apply_policy_settings(policy, use_transaction=transaction) + +- if enable: +- # add icmp rule(s) always +- self._icmp_block_inversion(True, obj.name, transaction) ++ self._zone_settings(True, _zone, transaction) + + if use_transaction is None: +- transaction.execute(enable) +- +- def apply_zone_settings(self, zone, use_transaction=None): +- self.__zone_settings(True, zone, use_transaction) ++ transaction.execute(True) + + def unapply_zone_settings(self, zone, use_transaction=None): +- self.__zone_settings(False, zone, use_transaction) ++ _zone = self._fw.check_zone(zone) ++ obj = self._zones[_zone] ++ if not obj.applied: ++ return + +- def unapply_zone_settings_if_unused(self, zone): +- obj = self._zones[zone] +- if len(obj.interfaces) == 0 and len(obj.sources) == 0: +- self.unapply_zone_settings(zone) ++ if use_transaction is None: ++ transaction = self.new_transaction() ++ else: ++ transaction = use_transaction ++ ++ for policy in self._zone_policies[_zone]: ++ self._fw.policy.unapply_policy_settings(policy, use_transaction=transaction) ++ ++ self._zone_settings(False, _zone, transaction) ++ ++ if use_transaction is None: ++ transaction.execute(True) + + def get_config_with_settings(self, zone): + """ +@@ -390,7 +324,7 @@ class FirewallZone(object): + return interface + + def add_interface(self, zone, interface, sender=None, +- use_transaction=None): ++ use_transaction=None, allow_apply=True): + self._fw.check_panic() + _zone = self._fw.check_zone(zone) + _obj = self._zones[_zone] +@@ -413,12 +347,13 @@ class FirewallZone(object): + else: + transaction = use_transaction + +- if not _obj.applied: ++ if not _obj.applied and allow_apply: + self.apply_zone_settings(zone, + use_transaction=transaction) + transaction.add_fail(self.set_zone_applied, _zone, False) + +- self._interface(True, _zone, interface, transaction) ++ if allow_apply: ++ self._interface(True, _zone, interface, transaction) + + self.__register_interface(_obj, interface_id, zone, sender) + transaction.add_fail(self.__unregister_interface, _obj, +@@ -494,7 +429,6 @@ class FirewallZone(object): + if use_transaction is None: + transaction.execute(True) + +-# self.unapply_zone_settings_if_unused(_zone) + return _zone + + def __unregister_interface(self, _obj, interface_id): +@@ -509,7 +443,7 @@ class FirewallZone(object): + + # SOURCES + +- def check_source(self, source): ++ def check_source(self, source, applied=False): + if checkIPnMask(source): + return "ipv4" + elif checkIP6nMask(source): +@@ -518,16 +452,18 @@ class FirewallZone(object): + return "" + elif source.startswith("ipset:"): + self._check_ipset_type_for_source(source[6:]) +- self._check_ipset_applied(source[6:]) ++ if applied: ++ self._check_ipset_applied(source[6:]) + return self._ipset_family(source[6:]) + else: + raise FirewallError(errors.INVALID_ADDR, source) + +- def __source_id(self, source): +- ipv = self.check_source(source) ++ def __source_id(self, source, applied=False): ++ ipv = self.check_source(source, applied=applied) + return (ipv, source) + +- def add_source(self, zone, source, sender=None, use_transaction=None): ++ def add_source(self, zone, source, sender=None, use_transaction=None, ++ allow_apply=True): + self._fw.check_panic() + _zone = self._fw.check_zone(zone) + _obj = self._zones[_zone] +@@ -535,7 +471,7 @@ class FirewallZone(object): + if check_mac(source): + source = source.upper() + +- source_id = self.__source_id(source) ++ source_id = self.__source_id(source, applied=allow_apply) + + if source_id in _obj.settings["sources"]: + raise FirewallError(errors.ZONE_ALREADY_SET, +@@ -549,12 +485,13 @@ class FirewallZone(object): + else: + transaction = use_transaction + +- if not _obj.applied: ++ if not _obj.applied and allow_apply: + self.apply_zone_settings(zone, + use_transaction=transaction) + transaction.add_fail(self.set_zone_applied, _zone, False) + +- self._source(True, _zone, source_id[0], source_id[1], transaction) ++ if allow_apply: ++ self._source(True, _zone, source_id[0], source_id[1], transaction) + + self.__register_source(_obj, source_id, zone, sender) + transaction.add_fail(self.__unregister_source, _obj, source_id) +@@ -617,7 +554,6 @@ class FirewallZone(object): + if use_transaction is None: + transaction.execute(True) + +-# self.unapply_zone_settings_if_unused(_zone) + return _zone + + def __unregister_source(self, _obj, source_id): +@@ -632,1328 +568,296 @@ class FirewallZone(object): + def list_sources(self, zone): + return [ k[1] for k in self.get_settings(zone)["sources"].keys() ] + +- # RICH LANGUAGE ++ def _interface(self, enable, zone, interface, transaction, append=False): ++ for backend in self._fw.enabled_backends(): ++ if not backend.policies_supported: ++ continue ++ for policy in self._zone_policies[zone]: ++ for (table, chain) in self._fw.policy._get_table_chains_for_zone_dispatch(policy): ++ # create needed chains if not done already ++ if enable: ++ transaction.add_chain(policy, table, chain) + +- def check_rule(self, rule): +- rule.check() ++ rules = backend.build_zone_source_interface_rules(enable, ++ zone, policy, interface, table, chain, append) ++ transaction.add_rules(backend, rules) + +- def __rule_id(self, rule): +- self.check_rule(rule) +- return str(rule) ++ # IPSETS + +- def _rule_source_ipv(self, source): +- if not source: ++ def _ipset_family(self, name): ++ if self._ipset_type(name) == "hash:mac": + return None ++ return self._fw.ipset.get_family(name, applied=False) + +- if source.addr: +- if checkIPnMask(source.addr): +- return "ipv4" +- elif checkIP6nMask(source.addr): +- return "ipv6" +- elif hasattr(source, "mac") and source.mac: +- return "" +- elif hasattr(source, "ipset") and source.ipset: +- self._check_ipset_type_for_source(source.ipset) +- self._check_ipset_applied(source.ipset) +- return self._ipset_family(source.ipset) +- +- return None +- +- def __rule(self, enable, zone, rule, transaction): +- self._rule_prepare(enable, zone, rule, transaction) +- +- def add_rule(self, zone, rule, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- rule_id = self.__rule_id(rule) +- if rule_id in _obj.settings["rules"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s' already in '%s'" % (rule, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self.__rule(True, _zone, rule, transaction) +- +- self.__register_rule(_obj, rule_id, timeout, sender) +- transaction.add_fail(self.__unregister_rule, _obj, rule_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_rule(self, _obj, rule_id, timeout, sender): +- _obj.settings["rules"][rule_id] = self.__gen_settings( +- timeout, sender) +- +- def remove_rule(self, zone, rule, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- rule_id = self.__rule_id(rule) +- if rule_id not in _obj.settings["rules"]: +- raise FirewallError(errors.NOT_ENABLED, +- "'%s' not in '%s'" % (rule, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self.__rule(False, _zone, rule, transaction) +- +- transaction.add_post(self.__unregister_rule, _obj, rule_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __unregister_rule(self, _obj, rule_id): +- if rule_id in _obj.settings["rules"]: +- del _obj.settings["rules"][rule_id] +- +- def query_rule(self, zone, rule): +- return self.__rule_id(rule) in self.get_settings(zone)["rules"] +- +- def list_rules(self, zone): +- return list(self.get_settings(zone)["rules"].keys()) +- +- # SERVICES +- +- def check_service(self, service): +- self._fw.check_service(service) +- +- def __service_id(self, service): +- self.check_service(service) +- return service +- +- def add_service(self, zone, service, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- service_id = self.__service_id(service) +- if service_id in _obj.settings["services"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s' already in '%s'" % (service, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self._service(True, _zone, service, transaction) +- +- self.__register_service(_obj, service_id, timeout, sender) +- transaction.add_fail(self.__unregister_service, _obj, service_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_service(self, _obj, service_id, timeout, sender): +- _obj.settings["services"][service_id] = \ +- self.__gen_settings(timeout, sender) +- +- def remove_service(self, zone, service, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] ++ def _ipset_type(self, name): ++ return self._fw.ipset.get_type(name, applied=False) + +- service_id = self.__service_id(service) +- if service_id not in _obj.settings["services"]: +- raise FirewallError(errors.NOT_ENABLED, +- "'%s' not in '%s'" % (service, _zone)) ++ def _ipset_match_flags(self, name, flag): ++ return ",".join([flag] * self._fw.ipset.get_dimension(name)) + +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction ++ def _check_ipset_applied(self, name): ++ return self._fw.ipset.check_applied(name) + +- if _obj.applied: +- self._service(False, _zone, service, transaction) ++ def _check_ipset_type_for_source(self, name): ++ _type = self._ipset_type(name) ++ if _type not in SOURCE_IPSET_TYPES: ++ raise FirewallError( ++ errors.INVALID_IPSET, ++ "ipset '%s' with type '%s' not usable as source" % \ ++ (name, _type)) + +- transaction.add_post(self.__unregister_service, _obj, service_id) ++ def _source(self, enable, zone, ipv, source, transaction): ++ # For mac source bindings ipv is an empty string, the mac source will ++ # be added for ipv4 and ipv6 ++ for backend in [self._fw.get_backend_by_ipv(ipv)] if ipv else self._fw.enabled_backends(): ++ if not backend.policies_supported: ++ continue ++ for policy in self._zone_policies[zone]: ++ for (table, chain) in self._fw.policy._get_table_chains_for_zone_dispatch(policy): ++ # create needed chains if not done already ++ if enable: ++ transaction.add_chain(policy, table, chain) + +- if use_transaction is None: +- transaction.execute(True) ++ rules = backend.build_zone_source_address_rules(enable, zone, ++ policy, source, table, chain) ++ transaction.add_rules(backend, rules) + +- return _zone ++ def add_service(self, zone, service, timeout=0, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.add_service(p_name, service, timeout, sender) ++ return zone + +- def __unregister_service(self, _obj, service_id): +- if service_id in _obj.settings["services"]: +- del _obj.settings["services"][service_id] ++ def remove_service(self, zone, service): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.remove_service(p_name, service) ++ return zone + + def query_service(self, zone, service): +- return self.__service_id(service) in self.get_settings(zone)["services"] ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.query_service(p_name, service) + + def list_services(self, zone): +- return self.get_settings(zone)["services"].keys() +- +- def get_helpers_for_service_helpers(self, helpers): +- _helpers = [ ] +- for helper in helpers: +- try: +- _helper = self._fw.helper.get_helper(helper) +- except FirewallError: +- raise FirewallError(errors.INVALID_HELPER, helper) +- _helpers.append(_helper) +- return _helpers +- +- def get_helpers_for_service_modules(self, modules, enable): +- # If automatic helper assignment is turned off, helpers that +- # do not have ports defined will be replaced by the helpers +- # that the helper.module defines. +- _helpers = [ ] +- for module in modules: +- try: +- helper = self._fw.helper.get_helper(module) +- except FirewallError: +- raise FirewallError(errors.INVALID_HELPER, module) +- if len(helper.ports) < 1: +- _module_short_name = get_nf_conntrack_short_name(helper.module) +- try: +- _helper = self._fw.helper.get_helper(_module_short_name) +- _helpers.append(_helper) +- except FirewallError: +- if enable: +- log.warning("Helper '%s' is not available" % _module_short_name) +- continue +- else: +- _helpers.append(helper) +- return _helpers +- +- # PORTS +- +- def check_port(self, port, protocol): +- self._fw.check_port(port) +- self._fw.check_tcpudp(protocol) +- +- def __port_id(self, port, protocol): +- self.check_port(port, protocol) +- return (portStr(port, "-"), protocol) +- +- def add_port(self, zone, port, protocol, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) +- for port_id in existing_port_ids: +- if portInPortRange(port, port_id[0]): +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s:%s' already in '%s'" % (port, protocol, _zone)) +- +- added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- for range in added_ranges: +- self._port(True, _zone, portStr(range, "-"), protocol, transaction) +- for range in removed_ranges: +- self._port(False, _zone, portStr(range, "-"), protocol, transaction) +- +- for range in added_ranges: +- port_id = self.__port_id(range, protocol) +- self.__register_port(_obj, port_id, timeout, sender) +- transaction.add_fail(self.__unregister_port, _obj, port_id) +- for range in removed_ranges: +- port_id = self.__port_id(range, protocol) +- transaction.add_post(self.__unregister_port, _obj, port_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_port(self, _obj, port_id, timeout, sender): +- _obj.settings["ports"][port_id] = \ +- self.__gen_settings(timeout, sender) +- +- def remove_port(self, zone, port, protocol, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) +- for port_id in existing_port_ids: +- if portInPortRange(port, port_id[0]): +- break +- else: +- raise FirewallError(errors.NOT_ENABLED, +- "'%s:%s' not in '%s'" % (port, protocol, _zone)) +- +- added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- for range in added_ranges: +- self._port(True, _zone, portStr(range, "-"), protocol, transaction) +- for range in removed_ranges: +- self._port(False, _zone, portStr(range, "-"), protocol, transaction) +- +- for range in added_ranges: +- port_id = self.__port_id(range, protocol) +- self.__register_port(_obj, port_id, 0, None) +- transaction.add_fail(self.__unregister_port, _obj, port_id) +- for range in removed_ranges: +- port_id = self.__port_id(range, protocol) +- transaction.add_post(self.__unregister_port, _obj, port_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __unregister_port(self, _obj, port_id): +- if port_id in _obj.settings["ports"]: +- del _obj.settings["ports"][port_id] ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.list_services(p_name) ++ ++ def add_port(self, zone, port, protocol, timeout=0, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.add_port(p_name, port, protocol, timeout, sender) ++ return zone ++ ++ def remove_port(self, zone, port, protocol): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.remove_port(p_name, port, protocol) ++ return zone + + def query_port(self, zone, port, protocol): +- for (_port, _protocol) in self.get_settings(zone)["ports"]: +- if portInPortRange(port, _port) and protocol == _protocol: +- return True +- +- return False ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.query_port(p_name, port, protocol) + + def list_ports(self, zone): +- return list(self.get_settings(zone)["ports"].keys()) +- +- # PROTOCOLS +- +- def check_protocol(self, protocol): +- if not checkProtocol(protocol): +- raise FirewallError(errors.INVALID_PROTOCOL, protocol) +- +- def __protocol_id(self, protocol): +- self.check_protocol(protocol) +- return protocol +- +- def add_protocol(self, zone, protocol, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- protocol_id = self.__protocol_id(protocol) +- if protocol_id in _obj.settings["protocols"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s' already in '%s'" % (protocol, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.list_ports(p_name) ++ ++ def add_source_port(self, zone, source_port, protocol, timeout=0, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.add_source_port(p_name, source_port, protocol, timeout, sender) ++ return zone ++ ++ def remove_source_port(self, zone, source_port, protocol): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.remove_source_port(p_name, source_port, protocol) ++ return zone ++ ++ def query_source_port(self, zone, source_port, protocol): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.query_source_port(p_name, source_port, protocol) + +- if _obj.applied: +- self._protocol(True, _zone, protocol, transaction) +- +- self.__register_protocol(_obj, protocol_id, timeout, sender) +- transaction.add_fail(self.__unregister_protocol, _obj, protocol_id) ++ def list_source_ports(self, zone): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.list_source_ports(p_name) ++ ++ def _rich_rule_to_policies(self, zone, rule): ++ zone = self._fw.check_zone(zone) ++ if type(rule.action) == Rich_Mark: ++ return [self.policy_name_from_zones(zone, "ANY")] ++ elif type(rule.element) in [Rich_Service, Rich_Port, Rich_Protocol, ++ Rich_SourcePort]: ++ return [self.policy_name_from_zones(zone, "HOST")] ++ elif type(rule.element) in [Rich_IcmpBlock, Rich_IcmpType]: ++ return [self.policy_name_from_zones(zone, "HOST"), ++ self.policy_name_from_zones(zone, "ANY")] ++ elif type(rule.element) in [Rich_ForwardPort]: ++ return [self.policy_name_from_zones(zone, "ANY")] ++ elif type(rule.element) in [Rich_Masquerade]: ++ return [self.policy_name_from_zones("ANY", zone)] ++ elif rule.element is None: ++ return [self.policy_name_from_zones(zone, "HOST")] ++ else: ++ raise FirewallError("Rich rule type (%s) not handled." % (type(rule.element))) ++ ++ def add_rule(self, zone, rule, timeout=0, sender=None): ++ for p_name in self._rich_rule_to_policies(zone, rule): ++ self._fw.policy.add_rule(p_name, rule, timeout, sender) ++ return zone ++ ++ def remove_rule(self, zone, rule): ++ for p_name in self._rich_rule_to_policies(zone, rule): ++ self._fw.policy.remove_rule(p_name, rule) ++ return zone + +- if use_transaction is None: +- transaction.execute(True) ++ def query_rule(self, zone, rule): ++ ret = True ++ for p_name in self._rich_rule_to_policies(zone, rule): ++ ret = ret and self._fw.policy.query_rule(p_name, rule) ++ return ret + +- return _zone ++ def list_rules(self, zone): ++ ret = set() ++ for p_name in [self.policy_name_from_zones(zone, "ANY"), ++ self.policy_name_from_zones(zone, "HOST"), ++ self.policy_name_from_zones("ANY", zone)]: ++ ret.update(set(self._fw.policy.list_rules(p_name))) ++ return list(ret) ++ ++ def add_protocol(self, zone, protocol, timeout=0, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.add_protocol(p_name, protocol, timeout, sender) ++ return zone ++ ++ def remove_protocol(self, zone, protocol): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.remove_protocol(p_name, protocol) ++ return zone + +- def __register_protocol(self, _obj, protocol_id, timeout, sender): +- _obj.settings["protocols"][protocol_id] = \ +- self.__gen_settings(timeout, sender) ++ def query_protocol(self, zone, protocol): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.query_protocol(p_name, protocol) + +- def remove_protocol(self, zone, protocol, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] ++ def list_protocols(self, zone): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ return self._fw.policy.list_protocols(p_name) ++ ++ def add_masquerade(self, zone, timeout=0, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones("ANY", zone) ++ self._fw.policy.add_masquerade(p_name, timeout, sender) ++ return zone ++ ++ def remove_masquerade(self, zone): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones("ANY", zone) ++ self._fw.policy.remove_masquerade(p_name) ++ return zone + +- protocol_id = self.__protocol_id(protocol) +- if protocol_id not in _obj.settings["protocols"]: +- raise FirewallError(errors.NOT_ENABLED, +- "'%s' not in '%s'" % (protocol, _zone)) ++ def query_masquerade(self, zone): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones("ANY", zone) ++ return self._fw.policy.query_masquerade(p_name) + +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction ++ def add_forward_port(self, zone, port, protocol, toport=None, ++ toaddr=None, timeout=0, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ self._fw.policy.add_forward_port(p_name, port, protocol, toport, toaddr, ++ timeout, sender) ++ return zone + +- if _obj.applied: +- self._protocol(False, _zone, protocol, transaction) ++ def remove_forward_port(self, zone, port, protocol, toport=None, ++ toaddr=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ self._fw.policy.remove_forward_port(p_name, port, protocol, toport, toaddr) ++ return zone + +- transaction.add_post(self.__unregister_protocol, _obj, +- protocol_id) ++ def query_forward_port(self, zone, port, protocol, toport=None, ++ toaddr=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ return self._fw.policy.query_forward_port(p_name, port, protocol, toport, ++ toaddr) + +- if use_transaction is None: +- transaction.execute(True) ++ def list_forward_ports(self, zone): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ return self._fw.policy.list_forward_ports(p_name) + +- return _zone ++ def add_icmp_block(self, zone, icmp, timeout=0, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.add_icmp_block(p_name, icmp, timeout, sender) + +- def __unregister_protocol(self, _obj, protocol_id): +- if protocol_id in _obj.settings["protocols"]: +- del _obj.settings["protocols"][protocol_id] ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ self._fw.policy.add_icmp_block(p_name, icmp, timeout, sender) ++ return zone + +- def query_protocol(self, zone, protocol): +- return self.__protocol_id(protocol) in self.get_settings(zone)["protocols"] ++ def remove_icmp_block(self, zone, icmp): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.remove_icmp_block(p_name, icmp) + +- def list_protocols(self, zone): +- return list(self.get_settings(zone)["protocols"].keys()) ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ self._fw.policy.remove_icmp_block(p_name, icmp) ++ return zone + +- # SOURCE PORTS ++ def query_icmp_block(self, zone, icmp): ++ zone = self._fw.check_zone(zone) ++ p_name_host = self.policy_name_from_zones(zone, "HOST") ++ p_name_fwd = self.policy_name_from_zones(zone, "ANY") ++ return self._fw.policy.query_icmp_block(p_name_host, icmp) and \ ++ self._fw.policy.query_icmp_block(p_name_fwd, icmp) + +- def __source_port_id(self, port, protocol): +- self.check_port(port, protocol) +- return (portStr(port, "-"), protocol) ++ def list_icmp_blocks(self, zone): ++ zone = self._fw.check_zone(zone) ++ p_name_host = self.policy_name_from_zones(zone, "HOST") ++ p_name_fwd = self.policy_name_from_zones(zone, "ANY") ++ return sorted(set(self._fw.policy.list_icmp_blocks(p_name_host) + ++ self._fw.policy.list_icmp_blocks(p_name_fwd))) + +- def add_source_port(self, zone, port, protocol, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] ++ def add_icmp_block_inversion(self, zone, sender=None): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.add_icmp_block_inversion(p_name, sender) + +- existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) +- for port_id in existing_port_ids: +- if portInPortRange(port, port_id[0]): +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s:%s' already in '%s'" % (port, protocol, _zone)) ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ self._fw.policy.add_icmp_block_inversion(p_name, sender) ++ return zone + +- added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ def _icmp_block_inversion(self, enable, zone, transaction): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy._icmp_block_inversion(enable, p_name, transaction) + +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ self._fw.policy._icmp_block_inversion(enable, p_name, transaction) + +- if _obj.applied: +- for range in added_ranges: +- self._source_port(True, _zone, portStr(range, "-"), protocol, transaction) +- for range in removed_ranges: +- self._source_port(False, _zone, portStr(range, "-"), protocol, transaction) ++ def remove_icmp_block_inversion(self, zone): ++ zone = self._fw.check_zone(zone) ++ p_name = self.policy_name_from_zones(zone, "HOST") ++ self._fw.policy.remove_icmp_block_inversion(p_name) + +- for range in added_ranges: +- port_id = self.__source_port_id(range, protocol) +- self.__register_source_port(_obj, port_id, timeout, sender) +- transaction.add_fail(self.__unregister_source_port, _obj, port_id) +- for range in removed_ranges: +- port_id = self.__source_port_id(range, protocol) +- transaction.add_post(self.__unregister_source_port, _obj, port_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_source_port(self, _obj, port_id, timeout, sender): +- _obj.settings["source_ports"][port_id] = \ +- self.__gen_settings(timeout, sender) +- +- def remove_source_port(self, zone, port, protocol, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) +- for port_id in existing_port_ids: +- if portInPortRange(port, port_id[0]): +- break +- else: +- raise FirewallError(errors.NOT_ENABLED, +- "'%s:%s' not in '%s'" % (port, protocol, _zone)) +- +- added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- for range in added_ranges: +- self._source_port(True, _zone, portStr(range, "-"), protocol, transaction) +- for range in removed_ranges: +- self._source_port(False, _zone, portStr(range, "-"), protocol, transaction) +- +- for range in added_ranges: +- port_id = self.__source_port_id(range, protocol) +- self.__register_source_port(_obj, port_id, 0, None) +- transaction.add_fail(self.__unregister_source_port, _obj, port_id) +- for range in removed_ranges: +- port_id = self.__source_port_id(range, protocol) +- transaction.add_post(self.__unregister_source_port, _obj, port_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __unregister_source_port(self, _obj, port_id): +- if port_id in _obj.settings["source_ports"]: +- del _obj.settings["source_ports"][port_id] +- +- def query_source_port(self, zone, port, protocol): +- for (_port, _protocol) in self.get_settings(zone)["source_ports"]: +- if portInPortRange(port, _port) and protocol == _protocol: +- return True +- +- return False +- +- def list_source_ports(self, zone): +- return list(self.get_settings(zone)["source_ports"].keys()) +- +- # MASQUERADE +- +- def __masquerade_id(self): +- return True +- +- def add_masquerade(self, zone, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- masquerade_id = self.__masquerade_id() +- if masquerade_id in _obj.settings["masquerade"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "masquerade already enabled in '%s'" % _zone) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self._masquerade(True, _zone, transaction) +- +- self.__register_masquerade(_obj, masquerade_id, timeout, sender) +- transaction.add_fail(self.__unregister_masquerade, _obj, masquerade_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_masquerade(self, _obj, masquerade_id, timeout, sender): +- _obj.settings["masquerade"][masquerade_id] = \ +- self.__gen_settings(timeout, sender) +- +- def remove_masquerade(self, zone, use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- masquerade_id = self.__masquerade_id() +- if masquerade_id not in _obj.settings["masquerade"]: +- raise FirewallError(errors.NOT_ENABLED, +- "masquerade not enabled in '%s'" % _zone) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self._masquerade(False, _zone, transaction) +- +- transaction.add_post(self.__unregister_masquerade, _obj, masquerade_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __unregister_masquerade(self, _obj, masquerade_id): +- if masquerade_id in _obj.settings["masquerade"]: +- del _obj.settings["masquerade"][masquerade_id] +- +- def query_masquerade(self, zone): +- return self.__masquerade_id() in self.get_settings(zone)["masquerade"] +- +- # PORT FORWARDING +- +- def check_forward_port(self, ipv, port, protocol, toport=None, toaddr=None): +- self._fw.check_port(port) +- self._fw.check_tcpudp(protocol) +- if toport: +- self._fw.check_port(toport) +- if toaddr: +- if not check_single_address(ipv, toaddr): +- raise FirewallError(errors.INVALID_ADDR, toaddr) +- if not toport and not toaddr: +- raise FirewallError( +- errors.INVALID_FORWARD, +- "port-forwarding is missing to-port AND to-addr") +- +- def __forward_port_id(self, port, protocol, toport=None, toaddr=None): +- if check_single_address("ipv6", toaddr): +- self.check_forward_port("ipv6", port, protocol, toport, toaddr) +- else: +- self.check_forward_port("ipv4", port, protocol, toport, toaddr) +- return (portStr(port, "-"), protocol, +- portStr(toport, "-"), str(toaddr)) +- +- def add_forward_port(self, zone, port, protocol, toport=None, +- toaddr=None, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- forward_id = self.__forward_port_id(port, protocol, toport, toaddr) +- if forward_id in _obj.settings["forward_ports"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s:%s:%s:%s' already in '%s'" % \ +- (port, protocol, toport, toaddr, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self._forward_port(True, _zone, transaction, port, protocol, +- toport, toaddr) +- +- self.__register_forward_port(_obj, forward_id, timeout, sender) +- transaction.add_fail(self.__unregister_forward_port, _obj, forward_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_forward_port(self, _obj, forward_id, timeout, sender): +- _obj.settings["forward_ports"][forward_id] = \ +- self.__gen_settings(timeout, sender) +- +- def remove_forward_port(self, zone, port, protocol, toport=None, +- toaddr=None, use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- forward_id = self.__forward_port_id(port, protocol, toport, toaddr) +- if forward_id not in _obj.settings["forward_ports"]: +- raise FirewallError(errors.NOT_ENABLED, +- "'%s:%s:%s:%s' not in '%s'" % \ +- (port, protocol, toport, toaddr, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self._forward_port(False, _zone, transaction, port, protocol, +- toport, toaddr) +- +- transaction.add_post(self.__unregister_forward_port, _obj, forward_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __unregister_forward_port(self, _obj, forward_id): +- if forward_id in _obj.settings["forward_ports"]: +- del _obj.settings["forward_ports"][forward_id] +- +- def query_forward_port(self, zone, port, protocol, toport=None, +- toaddr=None): +- forward_id = self.__forward_port_id(port, protocol, toport, toaddr) +- return forward_id in self.get_settings(zone)["forward_ports"] +- +- def list_forward_ports(self, zone): +- return list(self.get_settings(zone)["forward_ports"].keys()) +- +- # ICMP BLOCK +- +- def check_icmp_block(self, icmp): +- self._fw.check_icmptype(icmp) +- +- def __icmp_block_id(self, icmp): +- self.check_icmp_block(icmp) +- return icmp +- +- def add_icmp_block(self, zone, icmp, timeout=0, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_timeout(timeout) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- icmp_id = self.__icmp_block_id(icmp) +- if icmp_id in _obj.settings["icmp_blocks"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s' already in '%s'" % (icmp, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self._icmp_block(True, _zone, icmp, transaction) +- +- self.__register_icmp_block(_obj, icmp_id, timeout, sender) +- transaction.add_fail(self.__unregister_icmp_block, _obj, icmp_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_icmp_block(self, _obj, icmp_id, timeout, sender): +- _obj.settings["icmp_blocks"][icmp_id] = \ +- self.__gen_settings(timeout, sender) +- +- def remove_icmp_block(self, zone, icmp, use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- icmp_id = self.__icmp_block_id(icmp) +- if icmp_id not in _obj.settings["icmp_blocks"]: +- raise FirewallError(errors.NOT_ENABLED, +- "'%s' not in '%s'" % (icmp, _zone)) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- self._icmp_block(False, _zone, icmp, transaction) +- +- transaction.add_post(self.__unregister_icmp_block, _obj, icmp_id) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __unregister_icmp_block(self, _obj, icmp_id): +- if icmp_id in _obj.settings["icmp_blocks"]: +- del _obj.settings["icmp_blocks"][icmp_id] +- +- def query_icmp_block(self, zone, icmp): +- return self.__icmp_block_id(icmp) in self.get_settings(zone)["icmp_blocks"] +- +- def list_icmp_blocks(self, zone): +- return self.get_settings(zone)["icmp_blocks"].keys() +- +- # ICMP BLOCK INVERSION +- +- def __icmp_block_inversion_id(self): +- return True +- +- def add_icmp_block_inversion(self, zone, sender=None, +- use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- icmp_block_inversion_id = self.__icmp_block_inversion_id() +- if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: +- raise FirewallError( +- errors.ALREADY_ENABLED, +- "icmp-block-inversion already enabled in '%s'" % _zone) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- # undo icmp blocks +- for args in self.get_settings(_zone)["icmp_blocks"]: +- self._icmp_block(False, _zone, args, transaction) +- +- self._icmp_block_inversion(False, _zone, transaction) +- +- self.__register_icmp_block_inversion(_obj, icmp_block_inversion_id, +- sender) +- transaction.add_fail(self.__undo_icmp_block_inversion, _zone, _obj, +- icmp_block_inversion_id) +- +- # redo icmp blocks +- if _obj.applied: +- for args in self.get_settings(_zone)["icmp_blocks"]: +- self._icmp_block(True, _zone, args, transaction) +- +- self._icmp_block_inversion(True, _zone, transaction) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __register_icmp_block_inversion(self, _obj, icmp_block_inversion_id, +- sender): +- _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] = \ +- self.__gen_settings(0, sender) +- +- def __undo_icmp_block_inversion(self, _zone, _obj, icmp_block_inversion_id): +- transaction = self.new_transaction() +- +- # undo icmp blocks +- if _obj.applied: +- for args in self.get_settings(_zone)["icmp_blocks"]: +- self._icmp_block(False, _zone, args, transaction) +- +- if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: +- del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] +- +- # redo icmp blocks +- if _obj.applied: +- for args in self.get_settings(_zone)["icmp_blocks"]: +- self._icmp_block(True, _zone, args, transaction) +- +- transaction.execute(True) +- +- def remove_icmp_block_inversion(self, zone, use_transaction=None): +- _zone = self._fw.check_zone(zone) +- self._fw.check_panic() +- _obj = self._zones[_zone] +- +- icmp_block_inversion_id = self.__icmp_block_inversion_id() +- if icmp_block_inversion_id not in _obj.settings["icmp_block_inversion"]: +- raise FirewallError( +- errors.NOT_ENABLED, +- "icmp-block-inversion not enabled in '%s'" % _zone) +- +- if use_transaction is None: +- transaction = self.new_transaction() +- else: +- transaction = use_transaction +- +- if _obj.applied: +- # undo icmp blocks +- for args in self.get_settings(_zone)["icmp_blocks"]: +- self._icmp_block(False, _zone, args, transaction) +- +- self._icmp_block_inversion(False, _zone, transaction) +- +- self.__unregister_icmp_block_inversion(_obj, +- icmp_block_inversion_id) +- transaction.add_fail(self.__register_icmp_block_inversion, _obj, +- icmp_block_inversion_id, None) +- +- # redo icmp blocks +- if _obj.applied: +- for args in self.get_settings(_zone)["icmp_blocks"]: +- self._icmp_block(True, _zone, args, transaction) +- +- self._icmp_block_inversion(True, _zone, transaction) +- +- if use_transaction is None: +- transaction.execute(True) +- +- return _zone +- +- def __unregister_icmp_block_inversion(self, _obj, icmp_block_inversion_id): +- if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: +- del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] ++ p_name = self.policy_name_from_zones(zone, "ANY") ++ self._fw.policy.remove_icmp_block_inversion(p_name) ++ return zone + + def query_icmp_block_inversion(self, zone): +- return self.__icmp_block_inversion_id() in \ +- self.get_settings(zone)["icmp_block_inversion"] +- +- # dynamic chain handling +- +- def gen_chain_rules(self, zone, create, table, chain, transaction): +- if create: +- if zone in self._chains and \ +- table in self._chains[zone] and \ +- chain in self._chains[zone][table]: +- return +- else: +- if zone not in self._chains or \ +- table not in self._chains[zone] or \ +- chain not in self._chains[zone][table]: +- return +- +- for backend in self._fw.enabled_backends(): +- if backend.zones_supported and \ +- table in backend.get_available_tables(): +- rules = backend.build_zone_chain_rules(zone, table, chain) +- transaction.add_rules(backend, rules) +- +- self._register_chains(zone, create, [(table, chain)]) +- transaction.add_fail(self._register_chains, zone, create, [(table, chain)]) +- +- def _interface(self, enable, zone, interface, transaction, +- append=False): +- for backend in self._fw.enabled_backends(): +- if not backend.zones_supported: +- continue +- for table in backend.get_available_tables(): +- for chain in backend.get_zone_table_chains(table): +- # create needed chains if not done already +- if enable: +- transaction.add_chain(zone, table, chain) +- +- rules = backend.build_zone_source_interface_rules(enable, +- zone, interface, table, chain, append) +- transaction.add_rules(backend, rules) +- +- # IPSETS +- +- def _ipset_family(self, name): +- if self._fw.ipset.get_type(name) == "hash:mac": +- return None +- return self._fw.ipset.get_family(name) +- +- def __ipset_type(self, name): +- return self._fw.ipset.get_type(name) +- +- def _ipset_match_flags(self, name, flag): +- return ",".join([flag] * self._fw.ipset.get_dimension(name)) +- +- def _check_ipset_applied(self, name): +- return self._fw.ipset.check_applied(name) +- +- def _check_ipset_type_for_source(self, name): +- _type = self.__ipset_type(name) +- if _type not in ZONE_SOURCE_IPSET_TYPES: +- raise FirewallError( +- errors.INVALID_IPSET, +- "ipset '%s' with type '%s' not usable as source" % \ +- (name, _type)) +- +- def _source(self, enable, zone, ipv, source, transaction): +- # For mac source bindings ipv is an empty string, the mac source will +- # be added for ipv4 and ipv6 +- for backend in [self._fw.get_backend_by_ipv(ipv)] if ipv else self._fw.enabled_backends(): +- if not backend.zones_supported: +- continue +- for table in backend.get_available_tables(): +- for chain in backend.get_zone_table_chains(table): +- # create needed chains if not done already +- if enable: +- transaction.add_chain(zone, table, chain) +- +- rules = backend.build_zone_source_address_rules(enable, zone, +- source, table, chain) +- transaction.add_rules(backend, rules) +- +- def _rule_prepare(self, enable, zone, rule, transaction): +- if rule.family is not None: +- ipvs = [ rule.family ] +- else: +- ipvs = [ipv for ipv in ["ipv4", "ipv6"] if self._fw.is_ipv_enabled(ipv)] +- +- source_ipv = self._rule_source_ipv(rule.source) +- if source_ipv is not None and source_ipv != "": +- if rule.family is not None: +- # rule family is defined by user, no way to change it +- if rule.family != source_ipv: +- raise FirewallError(errors.INVALID_RULE, +- "Source address family '%s' conflicts with rule family '%s'." % (source_ipv, rule.family)) +- else: +- # use the source family as rule family +- ipvs = [ source_ipv ] +- +- # add an element to object to allow backends to know what ipvs this applies to +- rule.ipvs = ipvs +- +- for backend in set([self._fw.get_backend_by_ipv(x) for x in ipvs]): +- # SERVICE +- if type(rule.element) == Rich_Service: +- svc = self._fw.service.get_service(rule.element.name) +- +- destinations = [] +- if len(svc.destination) > 0: +- if rule.destination: +- # we can not use two destinations at the same time +- raise FirewallError(errors.INVALID_RULE, +- "Destination conflict with service.") +- for ipv in ipvs: +- if ipv in svc.destination and backend.is_ipv_supported(ipv): +- destinations.append(svc.destination[ipv]) +- else: +- # dummy for the following for loop +- destinations.append(None) +- +- for destination in destinations: +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- transaction.add_chain(zone, "raw", "PREROUTING") +- +- if type(rule.action) == Rich_Accept: +- # only load modules for accept action +- helpers = self.get_helpers_for_service_modules(svc.modules, +- enable) +- helpers += self.get_helpers_for_service_helpers(svc.helpers) +- helpers = sorted(set(helpers), key=lambda x: x.name) +- +- modules = [ ] +- for helper in helpers: +- module = helper.module +- _module_short_name = get_nf_conntrack_short_name(module) +- nat_module = module.replace("conntrack", "nat") +- modules.append(nat_module) +- if helper.family != "" and not backend.is_ipv_supported(helper.family): +- # no support for family ipv, continue +- continue +- if len(helper.ports) < 1: +- modules.append(module) +- else: +- for (port,proto) in helper.ports: +- rules = backend.build_zone_helper_ports_rules( +- enable, zone, proto, port, +- destination, helper.name, _module_short_name) +- transaction.add_rules(backend, rules) +- transaction.add_modules(modules) +- +- # create rules +- for (port,proto) in svc.ports: +- if enable and type(rule.action) == Rich_Mark: +- transaction.add_chain(zone, "mangle", "PREROUTING") +- rules = backend.build_zone_ports_rules( +- enable, zone, proto, port, destination, rule) +- transaction.add_rules(backend, rules) +- +- for proto in svc.protocols: +- if enable and type(rule.action) == Rich_Mark: +- transaction.add_chain(zone, "mangle", "PREROUTING") +- rules = backend.build_zone_protocol_rules( +- enable, zone, proto, destination, rule) +- transaction.add_rules(backend, rules) +- +- # create rules +- for (port,proto) in svc.source_ports: +- if enable and type(rule.action) == Rich_Mark: +- transaction.add_chain(zone, "mangle", "PREROUTING") +- rules = backend.build_zone_source_ports_rules( +- enable, zone, proto, port, destination, rule) +- transaction.add_rules(backend, rules) +- +- # PORT +- elif type(rule.element) == Rich_Port: +- port = rule.element.port +- protocol = rule.element.protocol +- self.check_port(port, protocol) +- +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- if enable and type(rule.action) == Rich_Mark: +- transaction.add_chain(zone, "mangle", "PREROUTING") +- +- rules = backend.build_zone_ports_rules( +- enable, zone, protocol, port, None, rule) +- transaction.add_rules(backend, rules) +- +- # PROTOCOL +- elif type(rule.element) == Rich_Protocol: +- protocol = rule.element.value +- self.check_protocol(protocol) +- +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- if enable and type(rule.action) == Rich_Mark: +- transaction.add_chain(zone, "mangle", "PREROUTING") +- +- rules = backend.build_zone_protocol_rules( +- enable, zone, protocol, None, rule) +- transaction.add_rules(backend, rules) +- +- # MASQUERADE +- elif type(rule.element) == Rich_Masquerade: +- if enable: +- transaction.add_chain(zone, "nat", "POSTROUTING") +- transaction.add_chain(zone, "filter", "FORWARD_OUT") +- for ipv in ipvs: +- if backend.is_ipv_supported(ipv): +- transaction.add_post(enable_ip_forwarding, ipv) +- +- rules = backend.build_zone_masquerade_rules(enable, zone, rule) +- transaction.add_rules(backend, rules) +- +- # FORWARD PORT +- elif type(rule.element) == Rich_ForwardPort: +- port = rule.element.port +- protocol = rule.element.protocol +- toport = rule.element.to_port +- toaddr = rule.element.to_address +- for ipv in ipvs: +- if backend.is_ipv_supported(ipv): +- self.check_forward_port(ipv, port, protocol, toport, toaddr) +- if toaddr and enable: +- transaction.add_post(enable_ip_forwarding, ipv) +- +- if enable: +- transaction.add_chain(zone, "nat", "PREROUTING") +- +- rules = backend.build_zone_forward_port_rules( +- enable, zone, port, protocol, toport, +- toaddr, rule) +- transaction.add_rules(backend, rules) +- +- # SOURCE PORT +- elif type(rule.element) == Rich_SourcePort: +- port = rule.element.port +- protocol = rule.element.protocol +- self.check_port(port, protocol) +- +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- if enable and type(rule.action) == Rich_Mark: +- transaction.add_chain(zone, "mangle", "PREROUTING") +- +- rules = backend.build_zone_source_ports_rules( +- enable, zone, protocol, port, None, rule) +- transaction.add_rules(backend, rules) +- +- # ICMP BLOCK and ICMP TYPE +- elif type(rule.element) == Rich_IcmpBlock or \ +- type(rule.element) == Rich_IcmpType: +- ict = self._fw.icmptype.get_icmptype(rule.element.name) +- +- if type(rule.element) == Rich_IcmpBlock and \ +- rule.action and type(rule.action) == Rich_Accept: +- # icmp block might have reject or drop action, but not accept +- raise FirewallError(errors.INVALID_RULE, +- "IcmpBlock not usable with accept action") +- if ict.destination: +- for ipv in ipvs: +- if ipv in ict.destination \ +- and not backend.is_ipv_supported(ipv): +- raise FirewallError( +- errors.INVALID_RULE, +- "Icmp%s %s not usable with %s" % \ +- ("Block" if type(rule.element) == \ +- Rich_IcmpBlock else "Type", +- rule.element.name, backend.name)) +- +- table = "filter" +- if enable: +- transaction.add_chain(zone, table, "INPUT") +- transaction.add_chain(zone, table, "FORWARD_IN") +- +- rules = backend.build_zone_icmp_block_rules(enable, zone, ict, rule) +- transaction.add_rules(backend, rules) +- +- elif rule.element is None: +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- if enable and type(rule.action) == Rich_Mark: +- transaction.add_chain(zone, "mangle", "PREROUTING") +- +- rules = backend.build_zone_rich_source_destination_rules( +- enable, zone, rule) +- transaction.add_rules(backend, rules) +- +- # EVERYTHING ELSE +- else: +- raise FirewallError(errors.INVALID_RULE, "Unknown element %s" % +- type(rule.element)) +- +- def _service(self, enable, zone, service, transaction, included_services=None): +- svc = self._fw.service.get_service(service) +- helpers = self.get_helpers_for_service_modules(svc.modules, enable) +- helpers += self.get_helpers_for_service_helpers(svc.helpers) +- helpers = sorted(set(helpers), key=lambda x: x.name) +- +- # First apply any services this service may include +- if included_services is None: +- included_services = [service] +- for include in svc.includes: +- if include in included_services: +- continue +- self.check_service(include) +- included_services.append(include) +- self._service(enable, zone, include, transaction, included_services=included_services) +- +- if enable: +- transaction.add_chain(zone, "raw", "PREROUTING") +- transaction.add_chain(zone, "filter", "INPUT") +- +- # build a list of (backend, destination). The destination may be ipv4, +- # ipv6 or None +- # +- backends_ipv = [] +- for ipv in ["ipv4", "ipv6"]: +- if not self._fw.is_ipv_enabled(ipv): +- continue +- backend = self._fw.get_backend_by_ipv(ipv) +- if len(svc.destination) > 0: +- if ipv in svc.destination: +- backends_ipv.append((backend, svc.destination[ipv])) +- else: +- if (backend, None) not in backends_ipv: +- backends_ipv.append((backend, None)) +- +- for (backend,destination) in backends_ipv: +- for helper in helpers: +- module = helper.module +- _module_short_name = get_nf_conntrack_short_name(module) +- nat_module = helper.module.replace("conntrack", "nat") +- transaction.add_module(nat_module) +- if helper.family != "" and not backend.is_ipv_supported(helper.family): +- # no support for family ipv, continue +- continue +- if len(helper.ports) < 1: +- transaction.add_module(module) +- else: +- for (port,proto) in helper.ports: +- rules = backend.build_zone_helper_ports_rules( +- enable, zone, proto, port, +- destination, helper.name, _module_short_name) +- transaction.add_rules(backend, rules) +- +- for (port,proto) in svc.ports: +- rules = backend.build_zone_ports_rules(enable, zone, proto, +- port, destination) +- transaction.add_rules(backend, rules) +- +- for protocol in svc.protocols: +- rules = backend.build_zone_protocol_rules( +- enable, zone, protocol, destination) +- transaction.add_rules(backend, rules) +- +- for (port,proto) in svc.source_ports: +- rules = backend.build_zone_source_ports_rules( +- enable, zone, proto, port, destination) +- transaction.add_rules(backend, rules) +- +- def _port(self, enable, zone, port, protocol, transaction): +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- +- for backend in self._fw.enabled_backends(): +- if not backend.zones_supported: +- continue +- +- rules = backend.build_zone_ports_rules(enable, zone, protocol, +- port) +- transaction.add_rules(backend, rules) +- +- def _protocol(self, enable, zone, protocol, transaction): +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- +- for backend in self._fw.enabled_backends(): +- if not backend.zones_supported: +- continue +- +- rules = backend.build_zone_protocol_rules(enable, zone, protocol) +- transaction.add_rules(backend, rules) +- +- def _source_port(self, enable, zone, port, protocol, transaction): +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- +- for backend in self._fw.enabled_backends(): +- if not backend.zones_supported: +- continue +- +- rules = backend.build_zone_source_ports_rules(enable, zone, protocol, port) +- transaction.add_rules(backend, rules) +- +- def _masquerade(self, enable, zone, transaction): +- if enable: +- transaction.add_chain(zone, "nat", "POSTROUTING") +- transaction.add_chain(zone, "filter", "FORWARD_OUT") +- +- ipv = "ipv4" +- transaction.add_post(enable_ip_forwarding, ipv) +- +- backend = self._fw.get_backend_by_ipv(ipv) +- rules = backend.build_zone_masquerade_rules(enable, zone) +- transaction.add_rules(backend, rules) +- +- def _forward_port(self, enable, zone, transaction, port, protocol, +- toport=None, toaddr=None): +- if check_single_address("ipv6", toaddr): +- ipv = "ipv6" +- else: +- ipv = "ipv4" +- +- if enable: +- transaction.add_chain(zone, "nat", "PREROUTING") +- +- if toaddr and enable: +- transaction.add_post(enable_ip_forwarding, ipv) +- backend = self._fw.get_backend_by_ipv(ipv) +- rules = backend.build_zone_forward_port_rules( +- enable, zone, port, protocol, toport, +- toaddr) +- transaction.add_rules(backend, rules) +- +- def _icmp_block(self, enable, zone, icmp, transaction): +- ict = self._fw.icmptype.get_icmptype(icmp) +- +- if enable: +- transaction.add_chain(zone, "filter", "INPUT") +- transaction.add_chain(zone, "filter", "FORWARD_IN") +- +- for backend in self._fw.enabled_backends(): +- if not backend.zones_supported: +- continue +- skip_backend = False +- +- if ict.destination: +- for ipv in ["ipv4", "ipv6"]: +- if ipv in ict.destination: +- if not backend.is_ipv_supported(ipv): +- skip_backend = True +- break +- +- if skip_backend: +- continue +- +- rules = backend.build_zone_icmp_block_rules(enable, zone, ict) +- transaction.add_rules(backend, rules) +- +- def _icmp_block_inversion(self, enable, zone, transaction): +- target = self._zones[zone].target +- +- # Do not add general icmp accept rules into a trusted, block or drop +- # zone. +- if target in [ "DROP", "%%REJECT%%", "REJECT" ]: +- return +- if not self.query_icmp_block_inversion(zone) and target == "ACCEPT": +- # ibi target and zone target are ACCEPT, no need to add an extra +- # rule +- return +- +- transaction.add_chain(zone, "filter", "INPUT") +- transaction.add_chain(zone, "filter", "FORWARD_IN") +- +- for backend in self._fw.enabled_backends(): +- if not backend.zones_supported: +- continue +- +- rules = backend.build_zone_icmp_block_inversion_rules(enable, zone) +- transaction.add_rules(backend, rules) ++ zone = self._fw.check_zone(zone) ++ p_name_host = self.policy_name_from_zones(zone, "HOST") ++ p_name_fwd = self.policy_name_from_zones(zone, "ANY") ++ return self._fw.policy.query_icmp_block_inversion(p_name_host) and \ ++ self._fw.policy.query_icmp_block_inversion(p_name_fwd) +diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py +new file mode 100644 +index 0000000..c0171a6 +--- /dev/null ++++ b/src/firewall/core/io/policy.py +@@ -0,0 +1,830 @@ ++# -*- coding: utf-8 -*- ++# ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++__all__ = [ "Policy", "policy_reader", "policy_writer" ] ++ ++import xml.sax as sax ++import os ++import io ++import shutil ++import copy ++from collections import OrderedDict ++ ++from firewall import config ++from firewall.functions import checkIP, checkIP6 ++from firewall.functions import uniqify, max_policy_name_len, portStr ++from firewall.core.base import DEFAULT_POLICY_TARGET, POLICY_TARGETS ++from firewall.core.io.io_object import IO_Object, \ ++ IO_Object_ContentHandler, IO_Object_XMLGenerator, check_port, \ ++ check_tcpudp, check_protocol ++from firewall.core import rich ++from firewall.core.logger import log ++from firewall import errors ++from firewall.errors import FirewallError ++ ++class Policy(IO_Object): ++ priority_min = -32768 ++ priority_max = 32767 ++ priority_default = -1 ++ ADDITIONAL_ALNUM_CHARS = [ "_", "-", "/" ] ++ PARSER_REQUIRED_ELEMENT_ATTRS = { ++ "short": None, ++ "description": None, ++ "policy": ["target"], ++ "service": [ "name" ], ++ "port": [ "port", "protocol" ], ++ "icmp-block": [ "name" ], ++ "icmp-type": [ "name" ], ++ "forward-port": [ "port", "protocol" ], ++ "rule": None, ++ "destination": [ "address" ], ++ "protocol": [ "value" ], ++ "source-port": [ "port", "protocol" ], ++ "log": None, ++ "audit": None, ++ "accept": None, ++ "reject": None, ++ "drop": None, ++ "mark": [ "set" ], ++ "limit": [ "value" ], ++ "ingress-zone": None, ++ "egress-zone": None, ++ } ++ PARSER_OPTIONAL_ELEMENT_ATTRS = { ++ "policy": [ "version", "priority" ], ++ "forward-port": [ "to-port", "to-addr" ], ++ "rule": [ "family", "priority" ], ++ "destination": [ "invert" ], ++ "log": [ "prefix", "level" ], ++ "reject": [ "type" ], ++ } ++ ++ def __init__(self): ++ super(Policy, self).__init__() ++ self.version = "" ++ self.short = "" ++ self.description = "" ++ self.target = DEFAULT_POLICY_TARGET ++ self.services = [ ] ++ self.ports = [ ] ++ self.protocols = [ ] ++ self.icmp_blocks = [ ] ++ self.masquerade = False ++ self.forward_ports = [ ] ++ self.source_ports = [ ] ++ self.fw_config = None # to be able to check services and a icmp_blocks ++ self.rules = [ ] ++ self.combined = False ++ self.applied = False ++ self.priority = self.priority_default ++ self.derived_from_zone = None ++ self.ingress_zones = [] ++ self.egress_zones = [] ++ ++ def cleanup(self): ++ self.version = "" ++ self.short = "" ++ self.description = "" ++ self.target = DEFAULT_POLICY_TARGET ++ del self.services[:] ++ del self.ports[:] ++ del self.protocols[:] ++ del self.icmp_blocks[:] ++ self.masquerade = False ++ del self.forward_ports[:] ++ del self.source_ports[:] ++ self.fw_config = None # to be able to check services and a icmp_blocks ++ del self.rules[:] ++ self.combined = False ++ self.applied = False ++ self.priority = self.priority_default ++ del self.ingress_zones[:] ++ del self.egress_zones[:] ++ ++ def __getattr__(self, name): ++ if name == "rules_str": ++ rules_str = [str(rule) for rule in self.rules] ++ return rules_str ++ else: ++ return getattr(super(Policy, self), name) ++ ++ def __setattr__(self, name, value): ++ if name == "rules_str": ++ self.rules = [rich.Rich_Rule(rule_str=s) for s in value] ++ else: ++ super(Policy, self).__setattr__(name, value) ++ ++ def _check_config(self, config, item): ++ if item == "services" and self.fw_config: ++ existing_services = self.fw_config.get_services() ++ for service in config: ++ if service not in existing_services: ++ raise FirewallError(errors.INVALID_SERVICE, ++ "'%s' not among existing services" % \ ++ service) ++ elif item == "ports": ++ for port in config: ++ check_port(port[0]) ++ check_tcpudp(port[1]) ++ elif item == "protocols": ++ for proto in config: ++ check_protocol(proto) ++ elif item == "icmp_blocks" and self.fw_config: ++ existing_icmptypes = self.fw_config.get_icmptypes() ++ for icmptype in config: ++ if icmptype not in existing_icmptypes: ++ raise FirewallError(errors.INVALID_ICMPTYPE, ++ "'%s' not among existing icmp types" % \ ++ icmptype) ++ elif item == "forward_ports": ++ for fwd_port in config: ++ check_port(fwd_port[0]) ++ check_tcpudp(fwd_port[1]) ++ if not fwd_port[2] and not fwd_port[3]: ++ raise FirewallError( ++ errors.INVALID_FORWARD, ++ "'%s' is missing to-port AND to-addr " % fwd_port) ++ if fwd_port[2]: ++ check_port(fwd_port[2]) ++ if fwd_port[3]: ++ if not checkIP(fwd_port[3]) and not checkIP6(fwd_port[3]): ++ raise FirewallError( ++ errors.INVALID_ADDR, ++ "to-addr '%s' is not a valid address" % fwd_port[3]) ++ elif item == "source_ports": ++ for port in config: ++ check_port(port[0]) ++ check_tcpudp(port[1]) ++ elif item == "target": ++ if config not in POLICY_TARGETS: ++ raise FirewallError(errors.INVALID_TARGET, config) ++ elif item == "rules_str": ++ for rule in config: ++ rich.Rich_Rule(rule_str=rule) ++ elif item in ["ingress-zone", "egress-zone"] and self.fw_config: ++ existing_zones = self.fw_config.get_zones() ++ for zone in config: ++ if zone not in existing_zones: ++ raise FirewallError(errors.INVALID_SERVICE, ++ "'%s' not among existing zones" % zone) ++ ++ def check_name(self, name): ++ super(Policy, self).check_name(name) ++ if name.startswith('/'): ++ raise FirewallError(errors.INVALID_NAME, ++ "'%s' can't start with '/'" % name) ++ elif name.endswith('/'): ++ raise FirewallError(errors.INVALID_NAME, ++ "'%s' can't end with '/'" % name) ++ elif name.count('/') > 1: ++ raise FirewallError(errors.INVALID_NAME, ++ "more than one '/' in '%s'" % name) ++ else: ++ if "/" in name: ++ checked_name = name[:name.find('/')] ++ else: ++ checked_name = name ++ if len(checked_name) > max_policy_name_len(): ++ raise FirewallError(errors.INVALID_NAME, ++ "Policy of '%s' has %d chars, max is %d %s" % ( ++ name, len(checked_name), ++ max_policy_name_len(), ++ self.combined)) ++ ++ def import_config(self, conf): ++ self.check_config(conf) ++ ++ # FIXME: ++ for key in conf: ++ if not hasattr(self, key): ++ raise FirewallError(errors.UNKNOWN_ERROR, "Internal error. '{}' is not a valid attribute".format(key)) ++ if isinstance(conf[key], list): ++ # maintain list order while removing duplicates ++ setattr(self, key, list(OrderedDict.fromkeys(copy.deepcopy(conf[key])))) ++ else: ++ setattr(self, key, copy.deepcopy(conf[key])) ++ ++ def export_config(self): ++ conf = {} ++ # FIXME ++ type_formats = dict([(x[0], x[1]) for x in self.IMPORT_EXPORT_STRUCTURE]) ++ for key in type_formats: ++ if getattr(self, key): ++ conf[key] = copy.deepcopy(getattr(self, key)) ++ return conf ++ ++ def check_config(self, conf): ++ # FIXME ++ type_formats = dict([(x[0], x[1]) for x in self.IMPORT_EXPORT_STRUCTURE]) ++ for key in conf: ++ if key not in [x for (x,y) in self.IMPORT_EXPORT_STRUCTURE]: ++ raise FirewallError(errors.INVALID_OPTION, "policy option '{}' is not valid".format(key)) ++ self._check_config_structure(conf[key], type_formats[key]) ++ self._check_config(conf[key], key) ++ ++# PARSER ++ ++class policy_ContentHandler(IO_Object_ContentHandler): ++ def __init__(self, item): ++ IO_Object_ContentHandler.__init__(self, item) ++ self._rule = None ++ self._rule_error = False ++ self._limit_ok = None ++ ++ def startElement(self, name, attrs): ++ IO_Object_ContentHandler.startElement(self, name, attrs) ++ if self._rule_error: ++ return ++ ++ self.item.parser_check_element_attrs(name, attrs) ++ ++ if name == "policy": ++ if "version" in attrs: ++ self.item.version = attrs["version"] ++ if "priority" in attrs: ++ self.item.priority = int(attrs["priority"]) ++ target = attrs["target"] ++ if target not in POLICY_TARGETS: ++ raise FirewallError(errors.INVALID_TARGET, target) ++ ++ elif name == "short": ++ pass ++ elif name == "description": ++ pass ++ elif name == "service": ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_Service(attrs["name"]) ++ return ++ if attrs["name"] not in self.item.services: ++ self.item.services.append(attrs["name"]) ++ else: ++ log.warning("Service '%s' already set, ignoring.", ++ attrs["name"]) ++ elif name == "ingress-zone": ++ if attrs["name"] not in self.item.ingress_zones: ++ self.item.ingress_zones.append(attrs["name"]) ++ else: ++ log.warning("Ingress zone '%s' already set, ignoring.", attrs["name"]) ++ elif name == "egress-zone": ++ if attrs["name"] not in self.item.egress_zones: ++ self.item.egress_zones.append(attrs["name"]) ++ else: ++ log.warning("Egress zone '%s' already set, ignoring.", attrs["name"]) ++ ++ elif name == "port": ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_Port(attrs["port"], ++ attrs["protocol"]) ++ return ++ check_port(attrs["port"]) ++ check_tcpudp(attrs["protocol"]) ++ entry = (portStr(attrs["port"], "-"), attrs["protocol"]) ++ if entry not in self.item.ports: ++ self.item.ports.append(entry) ++ else: ++ log.warning("Port '%s/%s' already set, ignoring.", ++ attrs["port"], attrs["protocol"]) ++ ++ elif name == "protocol": ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_Protocol(attrs["value"]) ++ else: ++ check_protocol(attrs["value"]) ++ if attrs["value"] not in self.item.protocols: ++ self.item.protocols.append(attrs["value"]) ++ else: ++ log.warning("Protocol '%s' already set, ignoring.", ++ attrs["value"]) ++ elif name == "icmp-block": ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_IcmpBlock(attrs["name"]) ++ return ++ if attrs["name"] not in self.item.icmp_blocks: ++ self.item.icmp_blocks.append(attrs["name"]) ++ else: ++ log.warning("icmp-block '%s' already set, ignoring.", ++ attrs["name"]) ++ ++ elif name == "icmp-type": ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_IcmpType(attrs["name"]) ++ return ++ else: ++ log.warning("Invalid rule: icmp-block '%s' outside of rule", ++ attrs["name"]) ++ ++ elif name == "masquerade": ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_Masquerade() ++ else: ++ if self.item.masquerade: ++ log.warning("Masquerade already set, ignoring.") ++ else: ++ self.item.masquerade = True ++ ++ elif name == "forward-port": ++ to_port = "" ++ if "to-port" in attrs: ++ to_port = attrs["to-port"] ++ to_addr = "" ++ if "to-addr" in attrs: ++ to_addr = attrs["to-addr"] ++ ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_ForwardPort(attrs["port"], ++ attrs["protocol"], ++ to_port, to_addr) ++ return ++ ++ check_port(attrs["port"]) ++ check_tcpudp(attrs["protocol"]) ++ if to_port: ++ check_port(to_port) ++ if to_addr: ++ if not checkIP(to_addr) and not checkIP6(to_addr): ++ raise FirewallError(errors.INVALID_ADDR, ++ "to-addr '%s' is not a valid address" \ ++ % to_addr) ++ entry = (portStr(attrs["port"], "-"), attrs["protocol"], ++ portStr(to_port, "-"), str(to_addr)) ++ if entry not in self.item.forward_ports: ++ self.item.forward_ports.append(entry) ++ else: ++ log.warning("Forward port %s/%s%s%s already set, ignoring.", ++ attrs["port"], attrs["protocol"], ++ " >%s" % to_port if to_port else "", ++ " @%s" % to_addr if to_addr else "") ++ ++ elif name == "source-port": ++ if self._rule: ++ if self._rule.element: ++ log.warning("Invalid rule: More than one element in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.element = rich.Rich_SourcePort(attrs["port"], ++ attrs["protocol"]) ++ return ++ check_port(attrs["port"]) ++ check_tcpudp(attrs["protocol"]) ++ entry = (portStr(attrs["port"], "-"), attrs["protocol"]) ++ if entry not in self.item.source_ports: ++ self.item.source_ports.append(entry) ++ else: ++ log.warning("Source port '%s/%s' already set, ignoring.", ++ attrs["port"], attrs["protocol"]) ++ ++ elif name == "destination": ++ if not self._rule: ++ log.warning('Invalid rule: Destination outside of rule') ++ self._rule_error = True ++ return ++ if self._rule.destination: ++ log.warning("Invalid rule: More than one destination in rule '%s', ignoring.", ++ str(self._rule)) ++ return ++ invert = False ++ if "invert" in attrs and \ ++ attrs["invert"].lower() in [ "yes", "true" ]: ++ invert = True ++ self._rule.destination = rich.Rich_Destination(attrs["address"], ++ invert) ++ ++ elif name in [ "accept", "reject", "drop", "mark" ]: ++ if not self._rule: ++ log.warning('Invalid rule: Action outside of rule') ++ self._rule_error = True ++ return ++ if self._rule.action: ++ log.warning('Invalid rule: More than one action') ++ self._rule_error = True ++ return ++ if name == "accept": ++ self._rule.action = rich.Rich_Accept() ++ elif name == "reject": ++ _type = None ++ if "type" in attrs: ++ _type = attrs["type"] ++ self._rule.action = rich.Rich_Reject(_type) ++ elif name == "drop": ++ self._rule.action = rich.Rich_Drop() ++ elif name == "mark": ++ _set = attrs["set"] ++ self._rule.action = rich.Rich_Mark(_set) ++ self._limit_ok = self._rule.action ++ ++ elif name == "log": ++ if not self._rule: ++ log.warning('Invalid rule: Log outside of rule') ++ return ++ if self._rule.log: ++ log.warning('Invalid rule: More than one log') ++ return ++ level = None ++ if "level" in attrs: ++ level = attrs["level"] ++ if level not in [ "emerg", "alert", "crit", "error", ++ "warning", "notice", "info", "debug" ]: ++ log.warning('Invalid rule: Invalid log level') ++ self._rule_error = True ++ return ++ prefix = attrs["prefix"] if "prefix" in attrs else None ++ self._rule.log = rich.Rich_Log(prefix, level) ++ self._limit_ok = self._rule.log ++ ++ elif name == "audit": ++ if not self._rule: ++ log.warning('Invalid rule: Audit outside of rule') ++ return ++ if self._rule.audit: ++ log.warning("Invalid rule: More than one audit in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ self._rule.audit = rich.Rich_Audit() ++ self._limit_ok = self._rule.audit ++ ++ elif name == "rule": ++ family = None ++ priority = 0 ++ if "family" in attrs: ++ family = attrs["family"] ++ if family not in [ "ipv4", "ipv6" ]: ++ log.warning('Invalid rule: Rule family "%s" invalid', ++ attrs["family"]) ++ self._rule_error = True ++ return ++ if "priority" in attrs: ++ priority = int(attrs["priority"]) ++ self._rule = rich.Rich_Rule(family=family, priority=priority) ++ ++ elif name == "limit": ++ if not self._limit_ok: ++ log.warning('Invalid rule: Limit outside of action, log and audit') ++ self._rule_error = True ++ return ++ if self._limit_ok.limit: ++ log.warning("Invalid rule: More than one limit in rule '%s', ignoring.", ++ str(self._rule)) ++ self._rule_error = True ++ return ++ value = attrs["value"] ++ self._limit_ok.limit = rich.Rich_Limit(value) ++ ++ else: ++ log.warning("Unknown XML element '%s'", name) ++ return ++ ++ def endElement(self, name): ++ IO_Object_ContentHandler.endElement(self, name) ++ ++ if name == "rule": ++ if not self._rule_error: ++ try: ++ self._rule.check() ++ except Exception as e: ++ log.warning("%s: %s", e, str(self._rule)) ++ else: ++ if str(self._rule) not in \ ++ [ str(x) for x in self.item.rules ]: ++ self.item.rules.append(self._rule) ++ else: ++ log.warning("Rule '%s' already set, ignoring.", ++ str(self._rule)) ++ self._rule = None ++ self._rule_error = False ++ elif name in [ "accept", "reject", "drop", "mark", "log", "audit" ]: ++ self._limit_ok = None ++ ++def policy_reader(filename, path, no_check_name=False): ++ policy = Policy() ++ if not filename.endswith(".xml"): ++ raise FirewallError(errors.INVALID_NAME, ++ "'%s' is missing .xml suffix" % filename) ++ policy.name = filename[:-4] ++ if not no_check_name: ++ policy.check_name(policy.name) ++ policy.filename = filename ++ policy.path = path ++ policy.builtin = False if path.startswith(config.ETC_FIREWALLD) else True ++ policy.default = policy.builtin ++ handler = policy_ContentHandler(policy) ++ parser = sax.make_parser() ++ parser.setContentHandler(handler) ++ name = "%s/%s" % (path, filename) ++ with open(name, "rb") as f: ++ source = sax.InputSource(None) ++ source.setByteStream(f) ++ try: ++ parser.parse(source) ++ except sax.SAXParseException as msg: ++ raise FirewallError(errors.INVALID_ZONE, ++ "not a valid policy file: %s" % \ ++ msg.getException()) ++ del handler ++ del parser ++ return policy ++ ++def policy_writer(policy, path=None): ++ _path = path if path else policy.path ++ ++ if policy.filename: ++ name = "%s/%s" % (_path, policy.filename) ++ else: ++ name = "%s/%s.xml" % (_path, policy.name) ++ ++ if os.path.exists(name): ++ try: ++ shutil.copy2(name, "%s.old" % name) ++ except Exception as msg: ++ log.error("Backup of file '%s' failed: %s", name, msg) ++ ++ dirpath = os.path.dirname(name) ++ if dirpath.startswith(config.ETC_FIREWALLD) and not os.path.exists(dirpath): ++ if not os.path.exists(config.ETC_FIREWALLD): ++ os.mkdir(config.ETC_FIREWALLD, 0o750) ++ os.mkdir(dirpath, 0o750) ++ ++ f = io.open(name, mode='wt', encoding='UTF-8') ++ handler = IO_Object_XMLGenerator(f) ++ handler.startDocument() ++ ++ # start policy element ++ attrs = {} ++ if policy.version and policy.version != "": ++ attrs["version"] = policy.version ++ if policy.priority != policy.priority_default: ++ attrs["priority"] = str(policy.priority) ++ attrs["target"] = policy.target ++ handler.startElement("policy", attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # short ++ if policy.short and policy.short != "": ++ handler.ignorableWhitespace(" ") ++ handler.startElement("short", { }) ++ handler.characters(policy.short) ++ handler.endElement("short") ++ handler.ignorableWhitespace("\n") ++ ++ # description ++ if policy.description and policy.description != "": ++ handler.ignorableWhitespace(" ") ++ handler.startElement("description", { }) ++ handler.characters(policy.description) ++ handler.endElement("description") ++ handler.ignorableWhitespace("\n") ++ ++ # services ++ for service in uniqify(policy.services): ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("service", { "name": service }) ++ handler.ignorableWhitespace("\n") ++ ++ # ingress-zones ++ for zone in uniqify(policy.ingress_zones): ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("ingress-zone", { "name": zone }) ++ handler.ignorableWhitespace("\n") ++ ++ # egress-zones ++ for zone in uniqify(policy.ingress_zones): ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("egress-zone", { "name": zone }) ++ handler.ignorableWhitespace("\n") ++ ++ # ports ++ for port in uniqify(policy.ports): ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("port", { "port": port[0], "protocol": port[1] }) ++ handler.ignorableWhitespace("\n") ++ ++ # protocols ++ for protocol in uniqify(policy.protocols): ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("protocol", { "value": protocol }) ++ handler.ignorableWhitespace("\n") ++ ++ # icmp-blocks ++ for icmp in uniqify(policy.icmp_blocks): ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("icmp-block", { "name": icmp }) ++ handler.ignorableWhitespace("\n") ++ ++ # masquerade ++ if policy.masquerade: ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("masquerade", { }) ++ handler.ignorableWhitespace("\n") ++ ++ # forward-ports ++ for forward in uniqify(policy.forward_ports): ++ handler.ignorableWhitespace(" ") ++ attrs = { "port": forward[0], "protocol": forward[1] } ++ if forward[2] and forward[2] != "" : ++ attrs["to-port"] = forward[2] ++ if forward[3] and forward[3] != "" : ++ attrs["to-addr"] = forward[3] ++ handler.simpleElement("forward-port", attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # source-ports ++ for port in uniqify(policy.source_ports): ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("source-port", { "port": port[0], ++ "protocol": port[1] }) ++ handler.ignorableWhitespace("\n") ++ ++ # rules ++ for rule in policy.rules: ++ attrs = { } ++ if rule.family: ++ attrs["family"] = rule.family ++ if rule.priority != 0: ++ attrs["priority"] = str(rule.priority) ++ handler.ignorableWhitespace(" ") ++ handler.startElement("rule", attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # source ++ if rule.source: ++ attrs = { } ++ if rule.source.addr: ++ attrs["address"] = rule.source.addr ++ if rule.source.mac: ++ attrs["mac"] = rule.source.mac ++ if rule.source.ipset: ++ attrs["ipset"] = rule.source.ipset ++ if rule.source.invert: ++ attrs["invert"] = "True" ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("source", attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # destination ++ if rule.destination: ++ attrs = { "address": rule.destination.addr } ++ if rule.destination.invert: ++ attrs["invert"] = "True" ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("destination", attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # element ++ if rule.element: ++ element = "" ++ attrs = { } ++ ++ if type(rule.element) == rich.Rich_Service: ++ element = "service" ++ attrs["name"] = rule.element.name ++ elif type(rule.element) == rich.Rich_Port: ++ element = "port" ++ attrs["port"] = rule.element.port ++ attrs["protocol"] = rule.element.protocol ++ elif type(rule.element) == rich.Rich_Protocol: ++ element = "protocol" ++ attrs["value"] = rule.element.value ++ elif type(rule.element) == rich.Rich_Masquerade: ++ element = "masquerade" ++ elif type(rule.element) == rich.Rich_IcmpBlock: ++ element = "icmp-block" ++ attrs["name"] = rule.element.name ++ elif type(rule.element) == rich.Rich_IcmpType: ++ element = "icmp-type" ++ attrs["name"] = rule.element.name ++ elif type(rule.element) == rich.Rich_ForwardPort: ++ element = "forward-port" ++ attrs["port"] = rule.element.port ++ attrs["protocol"] = rule.element.protocol ++ if rule.element.to_port != "": ++ attrs["to-port"] = rule.element.to_port ++ if rule.element.to_address != "": ++ attrs["to-addr"] = rule.element.to_address ++ elif type(rule.element) == rich.Rich_SourcePort: ++ element = "source-port" ++ attrs["port"] = rule.element.port ++ attrs["protocol"] = rule.element.protocol ++ else: ++ raise FirewallError( ++ errors.INVALID_OBJECT, ++ "Unknown element '%s' in policy_writer" % type(rule.element)) ++ ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement(element, attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # rule.element ++ ++ # log ++ if rule.log: ++ attrs = { } ++ if rule.log.prefix: ++ attrs["prefix"] = rule.log.prefix ++ if rule.log.level: ++ attrs["level"] = rule.log.level ++ if rule.log.limit: ++ handler.ignorableWhitespace(" ") ++ handler.startElement("log", attrs) ++ handler.ignorableWhitespace("\n ") ++ handler.simpleElement("limit", ++ { "value": rule.log.limit.value }) ++ handler.ignorableWhitespace("\n ") ++ handler.endElement("log") ++ else: ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("log", attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # audit ++ if rule.audit: ++ attrs = {} ++ if rule.audit.limit: ++ handler.ignorableWhitespace(" ") ++ handler.startElement("audit", { }) ++ handler.ignorableWhitespace("\n ") ++ handler.simpleElement("limit", ++ { "value": rule.audit.limit.value }) ++ handler.ignorableWhitespace("\n ") ++ handler.endElement("audit") ++ else: ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement("audit", attrs) ++ handler.ignorableWhitespace("\n") ++ ++ # action ++ if rule.action: ++ action = "" ++ attrs = { } ++ if type(rule.action) == rich.Rich_Accept: ++ action = "accept" ++ elif type(rule.action) == rich.Rich_Reject: ++ action = "reject" ++ if rule.action.type: ++ attrs["type"] = rule.action.type ++ elif type(rule.action) == rich.Rich_Drop: ++ action = "drop" ++ elif type(rule.action) == rich.Rich_Mark: ++ action = "mark" ++ attrs["set"] = rule.action.set ++ else: ++ log.warning("Unknown action '%s'", type(rule.action)) ++ if rule.action.limit: ++ handler.ignorableWhitespace(" ") ++ handler.startElement(action, attrs) ++ handler.ignorableWhitespace("\n ") ++ handler.simpleElement("limit", ++ { "value": rule.action.limit.value }) ++ handler.ignorableWhitespace("\n ") ++ handler.endElement(action) ++ else: ++ handler.ignorableWhitespace(" ") ++ handler.simpleElement(action, attrs) ++ handler.ignorableWhitespace("\n") ++ ++ handler.ignorableWhitespace(" ") ++ handler.endElement("rule") ++ handler.ignorableWhitespace("\n") ++ ++ # end policy element ++ handler.endElement("policy") ++ handler.ignorableWhitespace("\n") ++ handler.endDocument() ++ f.close() ++ del handler +diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py +index b1d6c20..3d05d7b 100644 +--- a/src/firewall/core/ipXtables.py ++++ b/src/firewall/core/ipXtables.py +@@ -22,7 +22,6 @@ + import os.path + import copy + +-from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET + from firewall.core.prog import runProg + from firewall.core.logger import log + from firewall.functions import tempFile, readfile, splitArgs, check_mac, portStr, \ +@@ -33,6 +32,8 @@ from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark, \ + Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock + import string + ++POLICY_CHAIN_PREFIX = "pol_" ++ + BUILT_IN_CHAINS = { + "security": [ "INPUT", "OUTPUT", "FORWARD" ], + "raw": [ "PREROUTING", "OUTPUT" ], +@@ -167,7 +168,7 @@ def common_check_passthrough(args): + class ip4tables(object): + ipv = "ipv4" + name = "ip4tables" +- zones_supported = True ++ policies_supported = True + + def __init__(self, fw): + self._fw = fw +@@ -769,10 +770,9 @@ class ip4tables(object): + + return {} + +- def build_zone_source_interface_rules(self, enable, zone, interface, ++ def build_zone_source_interface_rules(self, enable, zone, policy, interface, + table, chain, append=False): +- # handle all zones in the same way here, now +- # trust and block zone targets are handled now in __chain ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + opt = { + "PREROUTING": "-i", + "POSTROUTING": "-o", +@@ -782,7 +782,6 @@ class ip4tables(object): + "OUTPUT": "-o", + }[chain] + +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) + action = "-g" + + if enable and not append: +@@ -793,13 +792,14 @@ class ip4tables(object): + rule = [ "-D", "%s_ZONES" % chain ] + if not append: + rule += ["%%ZONE_INTERFACE%%"] +- rule += [ "-t", table, opt, interface, action, target ] ++ rule += [ "-t", table, opt, interface, action, _policy ] + return [rule] + +- def build_zone_source_address_rules(self, enable, zone, ++ def build_zone_source_address_rules(self, enable, zone, policy, + address, table, chain): + add_del = { True: "-I", False: "-D" }[enable] + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + opt = { + "PREROUTING": "-s", + "POSTROUTING": "-d", +@@ -814,7 +814,6 @@ class ip4tables(object): + else: + zone_dispatch_chain = "%s_ZONES" % (chain) + +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) + action = "-g" + + if address.startswith("ipset:"): +@@ -828,7 +827,7 @@ class ip4tables(object): + "%%ZONE_SOURCE%%", zone, + "-t", table, + "-m", "set", "--match-set", name, +- flags, action, target ] ++ flags, action, _policy ] + else: + if check_mac(address): + # outgoing can not be set +@@ -838,7 +837,7 @@ class ip4tables(object): + "%%ZONE_SOURCE%%", zone, + "-t", table, + "-m", "mac", "--mac-source", address.upper(), +- action, target ] ++ action, _policy ] + else: + if check_single_address("ipv6", address): + address = normalizeIP6(address) +@@ -848,56 +847,48 @@ class ip4tables(object): + rule = [ add_del, zone_dispatch_chain, + "%%ZONE_SOURCE%%", zone, + "-t", table, +- opt, address, action, target ] ++ opt, address, action, _policy ] + return [rule] + +- def build_zone_chain_rules(self, zone, table, chain): +- _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) ++ def build_policy_chain_rules(self, policy, table): ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + +- self.our_chains[table].update(set([_zone, +- "%s_log" % _zone, +- "%s_deny" % _zone, +- "%s_pre" % _zone, +- "%s_post" % _zone, +- "%s_allow" % _zone])) ++ self.our_chains[table].update(set([_policy, ++ "%s_log" % _policy, ++ "%s_deny" % _policy, ++ "%s_pre" % _policy, ++ "%s_post" % _policy, ++ "%s_allow" % _policy])) + + rules = [] +- rules.append([ "-N", _zone, "-t", table ]) +- rules.append([ "-N", "%s_pre" % _zone, "-t", table ]) +- rules.append([ "-N", "%s_log" % _zone, "-t", table ]) +- rules.append([ "-N", "%s_deny" % _zone, "-t", table ]) +- rules.append([ "-N", "%s_allow" % _zone, "-t", table ]) +- rules.append([ "-N", "%s_post" % _zone, "-t", table ]) +- rules.append([ "-A", _zone, "-t", table, "-j", "%s_pre" % _zone ]) +- rules.append([ "-A", _zone, "-t", table, "-j", "%s_log" % _zone ]) +- rules.append([ "-A", _zone, "-t", table, "-j", "%s_deny" % _zone ]) +- rules.append([ "-A", _zone, "-t", table, "-j", "%s_allow" % _zone ]) +- rules.append([ "-A", _zone, "-t", table, "-j", "%s_post" % _zone ]) +- +- target = self._fw.zone._zones[zone].target ++ rules.append([ "-N", _policy, "-t", table ]) ++ rules.append([ "-N", "%s_pre" % _policy, "-t", table ]) ++ rules.append([ "-N", "%s_log" % _policy, "-t", table ]) ++ rules.append([ "-N", "%s_deny" % _policy, "-t", table ]) ++ rules.append([ "-N", "%s_allow" % _policy, "-t", table ]) ++ rules.append([ "-N", "%s_post" % _policy, "-t", table ]) ++ rules.append([ "-A", _policy, "-t", table, "-j", "%s_pre" % _policy ]) ++ rules.append([ "-A", _policy, "-t", table, "-j", "%s_log" % _policy ]) ++ rules.append([ "-A", _policy, "-t", table, "-j", "%s_deny" % _policy ]) ++ rules.append([ "-A", _policy, "-t", table, "-j", "%s_allow" % _policy ]) ++ rules.append([ "-A", _policy, "-t", table, "-j", "%s_post" % _policy ]) ++ ++ target = self._fw.policy._policies[policy].target + + if self._fw.get_log_denied() != "off": +- if table == "filter" and \ +- chain in [ "INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT" ]: ++ if table == "filter": + if target in [ "REJECT", "%%REJECT%%" ]: +- rules.append([ "-A", _zone, "-t", table, "%%LOGTYPE%%", ++ rules.append([ "-A", _policy, "-t", table, "%%LOGTYPE%%", + "-j", "LOG", "--log-prefix", +- "\"%s_REJECT: \"" % _zone ]) ++ "\"%s_REJECT: \"" % _policy ]) + if target == "DROP": +- rules.append([ "-A", _zone, "-t", table, "%%LOGTYPE%%", ++ rules.append([ "-A", _policy, "-t", table, "%%LOGTYPE%%", + "-j", "LOG", "--log-prefix", +- "\"%s_DROP: \"" % _zone ]) +- +- # Handle trust, block and drop zones: +- # Add an additional rule with the zone target (accept, reject +- # or drop) to the base zone only in the filter table. +- # Otherwise it is not be possible to have a zone with drop +- # target, that is allowing traffic that is locally initiated +- # or that adds additional rules. (RHBZ#1055190) ++ "\"%s_DROP: \"" % _policy ]) ++ + if table == "filter" and \ +- target in [ "ACCEPT", "REJECT", "%%REJECT%%", "DROP" ] and \ +- chain in [ "INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT" ]: +- rules.append([ "-A", _zone, "-t", table, "-j", target ]) ++ target in [ "ACCEPT", "REJECT", "%%REJECT%%", "DROP" ]: ++ rules.append([ "-A", _policy, "-t", table, "-j", target ]) + + return rules + +@@ -944,14 +935,16 @@ class ip4tables(object): + return [] + return ["%%RICH_RULE_PRIORITY%%", rich_rule.priority] + +- def _rich_rule_log(self, rich_rule, enable, table, target, rule_fragment): ++ def _rich_rule_log(self, policy, rich_rule, enable, table, rule_fragment): + if not rich_rule.log: + return [] + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ + add_del = { True: "-A", False: "-D" }[enable] + + chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) +- rule = ["-t", table, add_del, "%s_%s" % (target, chain_suffix)] ++ rule = ["-t", table, add_del, "%s_%s" % (_policy, chain_suffix)] + rule += self._rich_rule_priority_fragment(rich_rule) + rule += rule_fragment + [ "-j", "LOG" ] + if rich_rule.log.prefix: +@@ -962,14 +955,16 @@ class ip4tables(object): + + return rule + +- def _rich_rule_audit(self, rich_rule, enable, table, target, rule_fragment): ++ def _rich_rule_audit(self, policy, rich_rule, enable, table, rule_fragment): + if not rich_rule.audit: + return [] + + add_del = { True: "-A", False: "-D" }[enable] + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ + chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) +- rule = ["-t", table, add_del, "%s_%s" % (target, chain_suffix)] ++ rule = ["-t", table, add_del, "%s_%s" % (_policy, chain_suffix)] + rule += self._rich_rule_priority_fragment(rich_rule) + rule += rule_fragment + if type(rich_rule.action) == Rich_Accept: +@@ -985,14 +980,16 @@ class ip4tables(object): + + return rule + +- def _rich_rule_action(self, zone, rich_rule, enable, table, target, rule_fragment): ++ def _rich_rule_action(self, policy, rich_rule, enable, table, rule_fragment): + if not rich_rule.action: + return [] + + add_del = { True: "-A", False: "-D" }[enable] + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ + chain_suffix = self._rich_rule_chain_suffix(rich_rule) +- chain = "%s_%s" % (target, chain_suffix) ++ chain = "%s_%s" % (_policy, chain_suffix) + if type(rich_rule.action) == Rich_Accept: + rule_action = [ "-j", "ACCEPT" ] + elif type(rich_rule.action) == Rich_Reject: +@@ -1002,10 +999,9 @@ class ip4tables(object): + elif type(rich_rule.action) == Rich_Drop: + rule_action = [ "-j", "DROP" ] + elif type(rich_rule.action) == Rich_Mark: +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], +- zone=zone) + table = "mangle" +- chain = "%s_%s" % (target, chain_suffix) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ chain = "%s_%s" % (_policy, chain_suffix) + rule_action = [ "-j", "MARK", "--set-xmark", rich_rule.action.set ] + else: + raise FirewallError(INVALID_RULE, +@@ -1064,11 +1060,10 @@ class ip4tables(object): + + return rule_fragment + +- def build_zone_ports_rules(self, enable, zone, proto, port, destination=None, rich_rule=None): ++ def build_policy_ports_rules(self, enable, policy, proto, port, destination=None, rich_rule=None): + add_del = { True: "-A", False: "-D" }[enable] + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], +- zone=zone) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + + rule_fragment = [ "-p", proto ] + if port: +@@ -1083,19 +1078,19 @@ class ip4tables(object): + + rules = [] + if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) + else: +- rules.append([add_del, "%s_allow" % (target), "-t", table] + ++ rules.append([add_del, "%s_allow" % (_policy), "-t", table] + + rule_fragment + [ "-j", "ACCEPT" ]) + + return rules + +- def build_zone_protocol_rules(self, enable, zone, protocol, destination=None, rich_rule=None): ++ def build_policy_protocol_rules(self, enable, policy, protocol, destination=None, rich_rule=None): + add_del = { True: "-A", False: "-D" }[enable] + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + + rule_fragment = [ "-p", protocol ] + if destination: +@@ -1108,20 +1103,20 @@ class ip4tables(object): + + rules = [] + if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) + else: +- rules.append([add_del, "%s_allow" % (target), "-t", table] + ++ rules.append([add_del, "%s_allow" % (_policy), "-t", table] + + rule_fragment + [ "-j", "ACCEPT" ]) + + return rules + +- def build_zone_source_ports_rules(self, enable, zone, proto, port, ++ def build_policy_source_ports_rules(self, enable, policy, proto, port, + destination=None, rich_rule=None): + add_del = { True: "-A", False: "-D" }[enable] + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + + rule_fragment = [ "-p", proto ] + if port: +@@ -1136,21 +1131,22 @@ class ip4tables(object): + + rules = [] + if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) + else: +- rules.append([add_del, "%s_allow" % (target), "-t", table] + ++ rules.append([add_del, "%s_allow" % (_policy), "-t", table] + + rule_fragment + [ "-j", "ACCEPT" ]) + + return rules + +- def build_zone_helper_ports_rules(self, enable, zone, proto, port, ++ def build_policy_helper_ports_rules(self, enable, policy, proto, port, + destination, helper_name, module_short_name): ++ table = "raw" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "-A", False: "-D" }[enable] +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], +- zone=zone) +- rule = [ add_del, "%s_allow" % (target), "-t", "raw", "-p", proto ] ++ ++ rule = [ add_del, "%s_allow" % (_policy), "-t", "raw", "-p", proto ] + if port: + rule += [ "--dport", "%s" % portStr(port) ] + if destination: +@@ -1159,10 +1155,11 @@ class ip4tables(object): + + return [rule] + +- def build_zone_masquerade_rules(self, enable, zone, rich_rule=None): ++ def build_policy_masquerade_rules(self, enable, policy, rich_rule=None): ++ table = "nat" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "-A", False: "-D" }[enable] +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["POSTROUTING"], +- zone=zone) ++ + rule_fragment = [] + if rich_rule: + chain_suffix = self._rich_rule_chain_suffix(rich_rule) +@@ -1173,12 +1170,10 @@ class ip4tables(object): + chain_suffix = "allow" + + rules = [] +- rules.append(["-t", "nat", add_del, "%s_%s" % (target, chain_suffix)] ++ rules.append(["-t", "nat", add_del, "%s_%s" % (_policy, chain_suffix)] + + rule_fragment + + [ "!", "-o", "lo", "-j", "MASQUERADE" ]) +- # FORWARD_OUT +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["FORWARD_OUT"], +- zone=zone) ++ + rule_fragment = [] + if rich_rule: + chain_suffix = self._rich_rule_chain_suffix(rich_rule) +@@ -1188,14 +1183,18 @@ class ip4tables(object): + else: + chain_suffix = "allow" + +- rules.append(["-t", "filter", add_del, "%s_%s" % (target, chain_suffix)] ++ table = "filter" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ rules.append(["-t", "filter", add_del, "%s_%s" % (_policy, chain_suffix)] + + rule_fragment + + ["-m", "conntrack", "--ctstate", "NEW,UNTRACKED", "-j", "ACCEPT" ]) + + return rules + +- def build_zone_forward_port_rules(self, enable, zone, port, ++ def build_policy_forward_port_rules(self, enable, policy, port, + protocol, toport, toaddr, rich_rule=None): ++ table = "nat" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "-A", False: "-D" }[enable] + + to = "" +@@ -1207,9 +1206,6 @@ class ip4tables(object): + if toport and toport != "": + to += ":%s" % portStr(toport, "-") + +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], +- zone=zone) +- + rule_fragment = [] + if rich_rule: + chain_suffix = self._rich_rule_chain_suffix(rich_rule) +@@ -1221,16 +1217,17 @@ class ip4tables(object): + + rules = [] + if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, "nat", target, rule_fragment)) +- rules.append(["-t", "nat", add_del, "%s_%s" % (target, chain_suffix)] ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, "nat", rule_fragment)) ++ rules.append(["-t", "nat", add_del, "%s_%s" % (_policy, chain_suffix)] + + rule_fragment + + ["-p", protocol, "--dport", portStr(port), + "-j", "DNAT", "--to-destination", to]) + + return rules + +- def build_zone_icmp_block_rules(self, enable, zone, ict, rich_rule=None): ++ def build_policy_icmp_block_rules(self, enable, policy, ict, rich_rule=None): + table = "filter" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "-A", False: "-D" }[enable] + + if self.ipv == "ipv4": +@@ -1241,93 +1238,87 @@ class ip4tables(object): + match = [ "-m", "icmp6", "--icmpv6-type", ict.name ] + + rules = [] +- for chain in ["INPUT", "FORWARD_IN"]: +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], +- zone=zone) +- if self._fw.zone.query_icmp_block_inversion(zone): +- final_chain = "%s_allow" % target +- final_target = "ACCEPT" +- else: +- final_chain = "%s_deny" % target +- final_target = "%%REJECT%%" +- +- rule_fragment = [] +- if rich_rule: +- rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination) +- rule_fragment += self._rich_rule_source_fragment(rich_rule.source) +- rule_fragment += proto + match +- +- if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) +- if rich_rule.action: +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) +- else: +- chain_suffix = self._rich_rule_chain_suffix(rich_rule) +- rules.append(["-t", table, add_del, "%s_%s" % (target, chain_suffix)] +- + self._rich_rule_priority_fragment(rich_rule) +- + rule_fragment + +- [ "-j", "%%REJECT%%" ]) ++ if self._fw.policy.query_icmp_block_inversion(policy): ++ final_chain = "%s_allow" % (_policy) ++ final_target = "ACCEPT" ++ else: ++ final_chain = "%s_deny" % (_policy) ++ final_target = "%%REJECT%%" ++ ++ rule_fragment = [] ++ if rich_rule: ++ rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination) ++ rule_fragment += self._rich_rule_source_fragment(rich_rule.source) ++ rule_fragment += proto + match ++ ++ if rich_rule: ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) ++ if rich_rule.action: ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) + else: +- if self._fw.get_log_denied() != "off" and final_target != "ACCEPT": +- rules.append([ add_del, final_chain, "-t", table ] +- + rule_fragment + +- [ "%%LOGTYPE%%", "-j", "LOG", +- "--log-prefix", "\"%s_ICMP_BLOCK: \"" % zone ]) ++ chain_suffix = self._rich_rule_chain_suffix(rich_rule) ++ rules.append(["-t", table, add_del, "%s_%s" % (_policy, chain_suffix)] ++ + self._rich_rule_priority_fragment(rich_rule) ++ + rule_fragment + ++ [ "-j", "%%REJECT%%" ]) ++ else: ++ if self._fw.get_log_denied() != "off" and final_target != "ACCEPT": + rules.append([ add_del, final_chain, "-t", table ] + + rule_fragment + +- [ "-j", final_target ]) ++ [ "%%LOGTYPE%%", "-j", "LOG", ++ "--log-prefix", "\"%s_ICMP_BLOCK: \"" % policy ]) ++ rules.append([ add_del, final_chain, "-t", table ] ++ + rule_fragment + ++ [ "-j", final_target ]) + + return rules + +- def build_zone_icmp_block_inversion_rules(self, enable, zone): ++ def build_policy_icmp_block_inversion_rules(self, enable, policy): + table = "filter" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ + rules = [] +- for chain in [ "INPUT", "FORWARD_IN" ]: +- rule_idx = 6 +- _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], +- zone=zone) ++ rule_idx = 6 + +- if self._fw.zone.query_icmp_block_inversion(zone): +- ibi_target = "%%REJECT%%" ++ if self._fw.policy.query_icmp_block_inversion(policy): ++ ibi_target = "%%REJECT%%" + +- if self._fw.get_log_denied() != "off": +- if enable: +- rule = [ "-I", _zone, str(rule_idx) ] +- else: +- rule = [ "-D", _zone ] +- +- rule = rule + [ "-t", table, "-p", "%%ICMP%%", +- "%%LOGTYPE%%", +- "-j", "LOG", "--log-prefix", +- "\"%s_ICMP_BLOCK: \"" % _zone ] +- rules.append(rule) +- rule_idx += 1 +- else: +- ibi_target = "ACCEPT" ++ if self._fw.get_log_denied() != "off": ++ if enable: ++ rule = [ "-I", _policy, str(rule_idx) ] ++ else: ++ rule = [ "-D", _policy ] ++ ++ rule = rule + [ "-t", table, "-p", "%%ICMP%%", ++ "%%LOGTYPE%%", ++ "-j", "LOG", "--log-prefix", ++ "\"%s_ICMP_BLOCK: \"" % _policy ] ++ rules.append(rule) ++ rule_idx += 1 ++ else: ++ ibi_target = "ACCEPT" + +- if enable: +- rule = [ "-I", _zone, str(rule_idx) ] +- else: +- rule = [ "-D", _zone ] +- rule = rule + [ "-t", table, "-p", "%%ICMP%%", "-j", ibi_target ] +- rules.append(rule) ++ if enable: ++ rule = [ "-I", _policy, str(rule_idx) ] ++ else: ++ rule = [ "-D", _policy ] ++ rule = rule + [ "-t", table, "-p", "%%ICMP%%", "-j", ibi_target ] ++ rules.append(rule) + + return rules + +- def build_zone_rich_source_destination_rules(self, enable, zone, rich_rule): ++ def build_policy_rich_source_destination_rules(self, enable, policy, rich_rule): + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], +- zone=zone) + + rule_fragment = [] + rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination) + rule_fragment += self._rich_rule_source_fragment(rich_rule.source) + + rules = [] +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) + + return rules + +diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py +index a6ad5f7..f422556 100644 +--- a/src/firewall/core/nftables.py ++++ b/src/firewall/core/nftables.py +@@ -23,7 +23,6 @@ from __future__ import absolute_import + import copy + import json + +-from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET + from firewall.core.logger import log + from firewall.functions import check_mac, getPortRange, normalizeIP6, \ + check_single_address, check_address +@@ -36,6 +35,7 @@ from nftables.nftables import Nftables + + TABLE_NAME = "firewalld" + TABLE_NAME_POLICY = TABLE_NAME + "_" + "policy_drop" ++POLICY_CHAIN_PREFIX = "policy_" + + # Map iptables (table, chain) to hooks and priorities. + # These are well defined by NF_IP_PRI_* defines in netfilter. +@@ -158,7 +158,7 @@ ICMP_TYPES_FRAGMENTS = { + + class nftables(object): + name = "nftables" +- zones_supported = True ++ policies_supported = True + + def __init__(self, fw): + self._fw = fw +@@ -174,7 +174,6 @@ class nftables(object): + self.nftables.set_echo_output(True) + self.nftables.set_handle_output(True) + +- + def _run_replace_zone_source(self, rule, zone_source_index_cache): + for verb in ["add", "insert", "delete"]: + if verb in rule: +@@ -698,18 +697,19 @@ class nftables(object): + + return [] + +- def build_zone_source_interface_rules(self, enable, zone, interface, ++ def build_zone_source_interface_rules(self, enable, zone, policy, interface, + table, chain, append=False, + family="inet"): + # nat tables needs to use ip/ip6 family + if table == "nat" and family == "inet": + rules = [] +- rules.extend(self.build_zone_source_interface_rules(enable, zone, ++ rules.extend(self.build_zone_source_interface_rules(enable, zone, policy, + interface, table, chain, append, "ip")) +- rules.extend(self.build_zone_source_interface_rules(enable, zone, ++ rules.extend(self.build_zone_source_interface_rules(enable, zone, policy, + interface, table, chain, append, "ip6")) + return rules + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + opt = { + "PREROUTING": "iifname", + "POSTROUTING": "oifname", +@@ -722,16 +722,15 @@ class nftables(object): + if interface[len(interface)-1] == "+": + interface = interface[:len(interface)-1] + "*" + +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) + action = "goto" + + if interface == "*": +- expr_fragments = [{action: {"target": "%s_%s" % (table, target)}}] ++ expr_fragments = [{action: {"target": "%s_%s" % (table, _policy)}}] + else: + expr_fragments = [{"match": {"left": {"meta": {"key": opt}}, + "op": "==", + "right": interface}}, +- {action: {"target": "%s_%s" % (table, target)}}] ++ {action: {"target": "%s_%s" % (table, _policy)}}] + + if enable and not append: + verb = "insert" +@@ -757,7 +756,7 @@ class nftables(object): + + return [{verb: {"rule": rule}}] + +- def build_zone_source_address_rules(self, enable, zone, ++ def build_zone_source_address_rules(self, enable, zone, policy, + address, table, chain, family="inet"): + # nat tables needs to use ip/ip6 family + if table == "nat" and family == "inet": +@@ -768,13 +767,14 @@ class nftables(object): + ipset_family = None + + if check_address("ipv4", address) or check_mac(address) or ipset_family == "ip": +- rules.extend(self.build_zone_source_address_rules(enable, zone, ++ rules.extend(self.build_zone_source_address_rules(enable, zone, policy, + address, table, chain, "ip")) + if check_address("ipv6", address) or check_mac(address) or ipset_family == "ip6": +- rules.extend(self.build_zone_source_address_rules(enable, zone, ++ rules.extend(self.build_zone_source_address_rules(enable, zone, policy, + address, table, chain, "ip6")) + return rules + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "insert", False: "delete" }[enable] + + opt = { +@@ -791,73 +791,64 @@ class nftables(object): + else: + zone_dispatch_chain = "%s_%s_ZONES" % (table, chain) + +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) + action = "goto" + + rule = {"family": family, + "table": TABLE_NAME, + "chain": zone_dispatch_chain, + "expr": [self._rule_addr_fragment(opt, address), +- {action: {"target": "%s_%s" % (table, target)}}]} ++ {action: {"target": "%s_%s" % (table, _policy)}}]} + rule.update(self._zone_source_fragment(zone, address)) + return [{add_del: {"rule": rule}}] + +- def build_zone_chain_rules(self, zone, table, chain, family="inet"): ++ def build_policy_chain_rules(self, policy, table, family="inet"): + # nat tables needs to use ip/ip6 family + if table == "nat" and family == "inet": + rules = [] +- rules.extend(self.build_zone_chain_rules(zone, table, chain, "ip")) +- rules.extend(self.build_zone_chain_rules(zone, table, chain, "ip6")) ++ rules.extend(self.build_policy_chain_rules(policy, table, "ip")) ++ rules.extend(self.build_policy_chain_rules(policy, table, "ip6")) + return rules + +- _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + + rules = [] + rules.append({"add": {"chain": {"family": family, + "table": TABLE_NAME, +- "name": "%s_%s" % (table, _zone)}}}) ++ "name": "%s_%s" % (table, _policy)}}}) + for chain_suffix in ["pre", "log", "deny", "allow", "post"]: + rules.append({"add": {"chain": {"family": family, + "table": TABLE_NAME, +- "name": "%s_%s_%s" % (table, _zone, chain_suffix)}}}) ++ "name": "%s_%s_%s" % (table, _policy, chain_suffix)}}}) + + for chain_suffix in ["pre", "log", "deny", "allow", "post"]: + rules.append({"add": {"rule": {"family": family, + "table": TABLE_NAME, +- "chain": "%s_%s" % (table, _zone), +- "expr": [{"jump": {"target": "%s_%s_%s" % (table, _zone, chain_suffix)}}]}}}) ++ "chain": "%s_%s" % (table, _policy), ++ "expr": [{"jump": {"target": "%s_%s_%s" % (table, _policy, chain_suffix)}}]}}}) + +- target = self._fw.zone._zones[zone].target ++ target = self._fw.policy._policies[policy].target + + if self._fw.get_log_denied() != "off": +- if table == "filter" and \ +- chain in ["INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT"]: ++ if table == "filter": + if target in ["REJECT", "%%REJECT%%", "DROP"]: + log_suffix = target + if target == "%%REJECT%%": + log_suffix = "REJECT" + rules.append({"add": {"rule": {"family": family, + "table": TABLE_NAME, +- "chain": "%s_%s" % (table, _zone), ++ "chain": "%s_%s" % (table, _policy), + "expr": [self._pkttype_match_fragment(self._fw.get_log_denied()), +- {"log": {"prefix": "\"filter_%s_%s: \"" % (_zone, log_suffix)}}]}}}) +- +- # Handle trust, block and drop zones: +- # Add an additional rule with the zone target (accept, reject +- # or drop) to the base zone only in the filter table. +- # Otherwise it is not be possible to have a zone with drop +- # target, that is allowing traffic that is locally initiated +- # or that adds additional rules. (RHBZ#1055190) ++ {"log": {"prefix": "\"filter_%s_%s: \"" % (_policy, log_suffix)}}]}}}) ++ + if table == "filter" and \ +- target in ["ACCEPT", "REJECT", "%%REJECT%%", "DROP"] and \ +- chain in ["INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT"]: ++ target in ["ACCEPT", "REJECT", "%%REJECT%%", "DROP"]: + if target == "%%REJECT%%": + target_fragment = self._reject_fragment() + else: + target_fragment = {target.lower(): None} + rules.append({"add": {"rule": {"family": family, + "table": TABLE_NAME, +- "chain": "%s_%s" % (table, _zone), ++ "chain": "%s_%s" % (table, _policy), + "expr": [target_fragment]}}}) + + return rules +@@ -981,10 +972,12 @@ class nftables(object): + return {} + return {"%%RICH_RULE_PRIORITY%%": rich_rule.priority} + +- def _rich_rule_log(self, rich_rule, enable, table, target, expr_fragments): ++ def _rich_rule_log(self, policy, rich_rule, enable, table, expr_fragments): + if not rich_rule.log: + return {} + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ + add_del = { True: "add", False: "delete" }[enable] + + chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) +@@ -997,37 +990,41 @@ class nftables(object): + + rule = {"family": "inet", + "table": TABLE_NAME, +- "chain": "%s_%s_%s" % (table, target, chain_suffix), ++ "chain": "%s_%s_%s" % (table, _policy, chain_suffix), + "expr": expr_fragments + + [{"log": log_options}, + self._rich_rule_limit_fragment(rich_rule.log.limit)]} + rule.update(self._rich_rule_priority_fragment(rich_rule)) + return {add_del: {"rule": rule}} + +- def _rich_rule_audit(self, rich_rule, enable, table, target, expr_fragments): ++ def _rich_rule_audit(self, policy, rich_rule, enable, table, expr_fragments): + if not rich_rule.audit: + return {} + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ + add_del = { True: "add", False: "delete" }[enable] + + chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) + rule = {"family": "inet", + "table": TABLE_NAME, +- "chain": "%s_%s_%s" % (table, target, chain_suffix), ++ "chain": "%s_%s_%s" % (table, _policy, chain_suffix), + "expr": expr_fragments + + [{"log": {"level": "audit"}}, + self._rich_rule_limit_fragment(rich_rule.audit.limit)]} + rule.update(self._rich_rule_priority_fragment(rich_rule)) + return {add_del: {"rule": rule}} + +- def _rich_rule_action(self, zone, rich_rule, enable, table, target, expr_fragments): ++ def _rich_rule_action(self, policy, rich_rule, enable, table, expr_fragments): + if not rich_rule.action: + return {} + ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ + add_del = { True: "add", False: "delete" }[enable] + + chain_suffix = self._rich_rule_chain_suffix(rich_rule) +- chain = "%s_%s_%s" % (table, target, chain_suffix) ++ chain = "%s_%s_%s" % (table, _policy, chain_suffix) + if type(rich_rule.action) == Rich_Accept: + rule_action = {"accept": None} + elif type(rich_rule.action) == Rich_Reject: +@@ -1038,10 +1035,9 @@ class nftables(object): + elif type(rich_rule.action) == Rich_Drop: + rule_action = {"drop": None} + elif type(rich_rule.action) == Rich_Mark: +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], +- zone=zone) + table = "mangle" +- chain = "%s_%s_%s" % (table, target, chain_suffix) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) ++ chain = "%s_%s_%s" % (table, _policy, chain_suffix) + rule_action = {"mangle": {"key": {"meta": {"key": "mark"}}, + "value": rich_rule.action.set}} + else: +@@ -1121,10 +1117,10 @@ class nftables(object): + else: + return {"range": [range[0], range[1]]} + +- def build_zone_ports_rules(self, enable, zone, proto, port, destination=None, rich_rule=None): ++ def build_policy_ports_rules(self, enable, policy, proto, port, destination=None, rich_rule=None): + add_del = { True: "add", False: "delete" }[enable] + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + + expr_fragments = [] + if rich_rule: +@@ -1146,21 +1142,21 @@ class nftables(object): + + rules = [] + if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) + else: + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, +- "chain": "%s_%s_allow" % (table, target), ++ "chain": "%s_%s_allow" % (table, _policy), + "expr": expr_fragments + [{"accept": None}]}}}) + + return rules + +- def build_zone_protocol_rules(self, enable, zone, protocol, destination=None, rich_rule=None): ++ def build_policy_protocol_rules(self, enable, policy, protocol, destination=None, rich_rule=None): + add_del = { True: "add", False: "delete" }[enable] + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + + expr_fragments = [] + if rich_rule: +@@ -1181,22 +1177,22 @@ class nftables(object): + + rules = [] + if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) + else: + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, +- "chain": "%s_%s_allow" % (table, target), ++ "chain": "%s_%s_allow" % (table, _policy), + "expr": expr_fragments + [{"accept": None}]}}}) + + return rules + +- def build_zone_source_ports_rules(self, enable, zone, proto, port, ++ def build_policy_source_ports_rules(self, enable, policy, proto, port, + destination=None, rich_rule=None): + add_del = { True: "add", False: "delete" }[enable] + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + + expr_fragments = [] + if rich_rule: +@@ -1218,19 +1214,21 @@ class nftables(object): + + rules = [] + if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) + else: + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, +- "chain": "%s_%s_allow" % (table, target), ++ "chain": "%s_%s_allow" % (table, _policy), + "expr": expr_fragments + [{"accept": None}]}}}) + + return rules + +- def build_zone_helper_ports_rules(self, enable, zone, proto, port, +- destination, helper_name, module_short_name): ++ def build_policy_helper_ports_rules(self, enable, policy, proto, port, ++ destination, helper_name, module_short_name): ++ table = "filter" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] + rules = [] + +@@ -1241,8 +1239,6 @@ class nftables(object): + "type": module_short_name, + "protocol": proto}}}) + +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], +- zone=zone) + expr_fragments = [] + if destination: + expr_fragments.append(self._rule_addr_fragment("daddr", destination)) +@@ -1253,15 +1249,15 @@ class nftables(object): + expr_fragments.append({"ct helper": "helper-%s-%s" % (helper_name, proto)}) + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, +- "chain": "filter_%s_allow" % (target), ++ "chain": "filter_%s_allow" % (_policy), + "expr": expr_fragments}}}) + + return rules + +- def _build_zone_masquerade_nat_rules(self, enable, zone, family, rich_rule=None): ++ def _build_policy_masquerade_nat_rules(self, enable, policy, family, rich_rule=None): ++ table = "nat" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["POSTROUTING"], +- zone=zone) + + expr_fragments = [] + if rich_rule: +@@ -1273,7 +1269,7 @@ class nftables(object): + + rule = {"family": family, + "table": TABLE_NAME, +- "chain": "nat_%s_%s" % (target, chain_suffix), ++ "chain": "nat_%s_%s" % (_policy, chain_suffix), + "expr": expr_fragments + + [{"match": {"left": {"meta": {"key": "oifname"}}, + "op": "!=", +@@ -1282,21 +1278,21 @@ class nftables(object): + rule.update(self._rich_rule_priority_fragment(rich_rule)) + return [{add_del: {"rule": rule}}] + +- def build_zone_masquerade_rules(self, enable, zone, rich_rule=None): ++ def build_policy_masquerade_rules(self, enable, policy, rich_rule=None): + # nat tables needs to use ip/ip6 family + rules = [] + if rich_rule and (rich_rule.family and rich_rule.family == "ipv6" + or rich_rule.source and check_address("ipv6", rich_rule.source.addr)): +- rules.extend(self._build_zone_masquerade_nat_rules(enable, zone, "ip6", rich_rule)) ++ rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip6", rich_rule)) + elif rich_rule and (rich_rule.family and rich_rule.family == "ipv4" + or rich_rule.source and check_address("ipv4", rich_rule.source.addr)): +- rules.extend(self._build_zone_masquerade_nat_rules(enable, zone, "ip", rich_rule)) ++ rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip", rich_rule)) + else: +- rules.extend(self._build_zone_masquerade_nat_rules(enable, zone, "ip", rich_rule)) ++ rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip", rich_rule)) + ++ table = "filter" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["FORWARD_OUT"], +- zone=zone) + + expr_fragments = [] + if rich_rule: +@@ -1308,7 +1304,7 @@ class nftables(object): + + rule = {"family": "inet", + "table": TABLE_NAME, +- "chain": "filter_%s_%s" % (target, chain_suffix), ++ "chain": "filter_%s_%s" % (_policy, chain_suffix), + "expr": expr_fragments + + [{"match": {"left": {"ct": {"key": "state"}}, + "op": "in", +@@ -1319,12 +1315,12 @@ class nftables(object): + + return rules + +- def _build_zone_forward_port_nat_rules(self, enable, zone, port, protocol, ++ def _build_policy_forward_port_nat_rules(self, enable, policy, port, protocol, + toaddr, toport, family, + rich_rule=None): ++ table = "nat" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], +- zone=zone) + + expr_fragments = [] + if rich_rule: +@@ -1351,28 +1347,28 @@ class nftables(object): + + rule = {"family": family, + "table": TABLE_NAME, +- "chain": "nat_%s_%s" % (target, chain_suffix), ++ "chain": "nat_%s_%s" % (_policy, chain_suffix), + "expr": expr_fragments} + rule.update(self._rich_rule_priority_fragment(rich_rule)) + return [{add_del: {"rule": rule}}] + +- def build_zone_forward_port_rules(self, enable, zone, port, ++ def build_policy_forward_port_rules(self, enable, policy, port, + protocol, toport, toaddr, rich_rule=None): + rules = [] + if rich_rule and (rich_rule.family and rich_rule.family == "ipv6" + or toaddr and check_single_address("ipv6", toaddr)): +- rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, ++ rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, + port, protocol, toaddr, toport, "ip6", rich_rule)) + elif rich_rule and (rich_rule.family and rich_rule.family == "ipv4" + or toaddr and check_single_address("ipv4", toaddr)): +- rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, ++ rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, + port, protocol, toaddr, toport, "ip", rich_rule)) + else: + if toaddr and check_single_address("ipv6", toaddr): +- rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, ++ rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, + port, protocol, toaddr, toport, "ip6", rich_rule)) + else: +- rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, ++ rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, + port, protocol, toaddr, toport, "ip", rich_rule)) + + return rules +@@ -1384,8 +1380,9 @@ class nftables(object): + raise FirewallError(INVALID_ICMPTYPE, + "ICMP type '%s' not supported by %s" % (icmp_type, self.name)) + +- def build_zone_icmp_block_rules(self, enable, zone, ict, rich_rule=None): ++ def build_policy_icmp_block_rules(self, enable, policy, ict, rich_rule=None): + table = "filter" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] + + if rich_rule and rich_rule.ipvs: +@@ -1401,83 +1398,77 @@ class nftables(object): + + rules = [] + for ipv in ipvs: +- for chain in ["INPUT", "FORWARD_IN"]: +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], +- zone=zone) +- if self._fw.zone.query_icmp_block_inversion(zone): +- final_chain = "%s_%s_allow" % (table, target) +- target_fragment = {"accept": None} +- else: +- final_chain = "%s_%s_deny" % (table, target) +- target_fragment = self._reject_fragment() +- +- expr_fragments = [] +- if rich_rule: +- expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family)) +- expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination)) +- expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source)) +- expr_fragments.extend(self._icmp_types_to_nft_fragments(ipv, ict.name)) +- +- if rich_rule: +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) +- if rich_rule.action: +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) +- else: +- chain_suffix = self._rich_rule_chain_suffix(rich_rule) +- rule = {"family": "inet", +- "table": TABLE_NAME, +- "chain": "%s_%s_%s" % (table, target, chain_suffix), +- "expr": expr_fragments + [self._reject_fragment()]} +- rule.update(self._rich_rule_priority_fragment(rich_rule)) +- rules.append({add_del: {"rule": rule}}) ++ if self._fw.policy.query_icmp_block_inversion(policy): ++ final_chain = "%s_%s_allow" % (table, _policy) ++ target_fragment = {"accept": None} ++ else: ++ final_chain = "%s_%s_deny" % (table, _policy) ++ target_fragment = self._reject_fragment() ++ ++ expr_fragments = [] ++ if rich_rule: ++ expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family)) ++ expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination)) ++ expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source)) ++ expr_fragments.extend(self._icmp_types_to_nft_fragments(ipv, ict.name)) ++ ++ if rich_rule: ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) ++ if rich_rule.action: ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) + else: +- if self._fw.get_log_denied() != "off" and self._fw.zone.query_icmp_block_inversion(zone): +- rules.append({add_del: {"rule": {"family": "inet", +- "table": TABLE_NAME, +- "chain": final_chain, +- "expr": (expr_fragments + +- [self._pkttype_match_fragment(self._fw.get_log_denied()), +- {"log": {"prefix": "\"%s_%s_ICMP_BLOCK: \"" % (table, zone)}}])}}}) ++ chain_suffix = self._rich_rule_chain_suffix(rich_rule) ++ rule = {"family": "inet", ++ "table": TABLE_NAME, ++ "chain": "%s_%s_%s" % (table, _policy, chain_suffix), ++ "expr": expr_fragments + [self._reject_fragment()]} ++ rule.update(self._rich_rule_priority_fragment(rich_rule)) ++ rules.append({add_del: {"rule": rule}}) ++ else: ++ if self._fw.get_log_denied() != "off" and self._fw.policy.query_icmp_block_inversion(policy): + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, + "chain": final_chain, +- "expr": expr_fragments + [target_fragment]}}}) ++ "expr": (expr_fragments + ++ [self._pkttype_match_fragment(self._fw.get_log_denied()), ++ {"log": {"prefix": "\"%s_%s_ICMP_BLOCK: \"" % (table, policy)}}])}}}) ++ rules.append({add_del: {"rule": {"family": "inet", ++ "table": TABLE_NAME, ++ "chain": final_chain, ++ "expr": expr_fragments + [target_fragment]}}}) + + return rules + +- def build_zone_icmp_block_inversion_rules(self, enable, zone): ++ def build_policy_icmp_block_inversion_rules(self, enable, policy): + table = "filter" ++ _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + rules = [] + add_del = { True: "add", False: "delete" }[enable] + +- for chain in ["INPUT", "FORWARD_IN"]: +- _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], +- zone=zone) ++ if self._fw.policy.query_icmp_block_inversion(policy): ++ target_fragment = self._reject_fragment() ++ else: ++ target_fragment = {"accept": None} + +- if self._fw.zone.query_icmp_block_inversion(zone): +- target_fragment = self._reject_fragment() +- else: +- target_fragment = {"accept": None} ++ # WARN: The "index" used here must be kept in sync with ++ # build_policy_chain_rules() ++ # ++ rules.append({add_del: {"rule": {"family": "inet", ++ "table": TABLE_NAME, ++ "chain": "%s_%s" % (table, _policy), ++ "index": 4, ++ "expr": [self._icmp_match_fragment(), ++ target_fragment]}}}) + +- # WARN: The "index" used here must be kept in sync with +- # build_zone_chain_rules() +- # ++ if self._fw.get_log_denied() != "off" and self._fw.policy.query_icmp_block_inversion(policy): + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, +- "chain": "%s_%s" % (table, _zone), ++ "chain": "%s_%s" % (table, _policy), + "index": 4, + "expr": [self._icmp_match_fragment(), +- target_fragment]}}}) +- +- if self._fw.get_log_denied() != "off" and self._fw.zone.query_icmp_block_inversion(zone): +- rules.append({add_del: {"rule": {"family": "inet", +- "table": TABLE_NAME, +- "chain": "%s_%s" % (table, _zone), +- "index": 4, +- "expr": [self._icmp_match_fragment(), +- self._pkttype_match_fragment(self._fw.get_log_denied()), +- {"log": {"prefix": "%s_%s_ICMP_BLOCK: " % (table, _zone)}}]}}}) ++ self._pkttype_match_fragment(self._fw.get_log_denied()), ++ {"log": {"prefix": "%s_%s_ICMP_BLOCK: " % (table, policy)}}]}}}) + return rules + + def build_rpfilter_rules(self, log_denied=False): +@@ -1543,10 +1534,8 @@ class nftables(object): + "expr": expr_fragments}}}) + return rules + +- def build_zone_rich_source_destination_rules(self, enable, zone, rich_rule): ++ def build_policy_rich_source_destination_rules(self, enable, policy, rich_rule): + table = "filter" +- target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], +- zone=zone) + + expr_fragments = [] + expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family)) +@@ -1554,9 +1543,9 @@ class nftables(object): + expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source)) + + rules = [] +- rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) +- rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) ++ rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) ++ rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) + + return rules + +diff --git a/src/firewall/errors.py b/src/firewall/errors.py +index 4589f60..2ad46c2 100644 +--- a/src/firewall/errors.py ++++ b/src/firewall/errors.py +@@ -88,6 +88,7 @@ INVALID_ENTRY = 136 + INVALID_OPTION = 137 + INVALID_HELPER = 138 + INVALID_PRIORITY = 139 ++INVALID_POLICY = 140 + + MISSING_TABLE = 200 + MISSING_CHAIN = 201 +diff --git a/src/firewall/functions.py b/src/firewall/functions.py +index 6bc52d9..d4c5e90 100644 +--- a/src/firewall/functions.py ++++ b/src/firewall/functions.py +@@ -27,7 +27,7 @@ __all__ = [ "PY2", "getPortID", "getPortRange", "portStr", "getServiceName", + "check_single_address", "check_mac", "uniqify", "ppid_of_pid", + "max_zone_name_len", "checkUser", "checkUid", "checkCommand", + "checkContext", "joinArgs", "splitArgs", +- "b2u", "u2b", "u2b_if_py2" ] ++ "b2u", "u2b", "u2b_if_py2", "max_policy_name_len"] + + import socket + import os +@@ -505,6 +505,15 @@ def ppid_of_pid(pid): + return None + return pid + ++def max_policy_name_len(): ++ """ ++ iptables limits length of chain to (currently) 28 chars. ++ The longest chain we create is pol__allow, ++ which leaves 28 - 10 = 18 chars for . ++ """ ++ from firewall.core.ipXtables import POLICY_CHAIN_PREFIX ++ return 28 - (len(POLICY_CHAIN_PREFIX) + len("_allow")) ++ + def max_zone_name_len(): + """ + Netfilter limits length of chain to (currently) 28 chars. +diff --git a/src/tests/features/service_include.at b/src/tests/features/service_include.at +index 7f02701..1c86900 100644 +--- a/src/tests/features/service_include.at ++++ b/src/tests/features/service_include.at +@@ -116,9 +116,7 @@ FWD_CHECK([-q --permanent --zone=drop --add-interface=foobar0]) + FWD_CHECK([-q --permanent --zone=drop --add-service=my-service-with-include]) + FWD_CHECK([-q --permanent --service=my-service-with-include --add-include=does-not-exist]) + FWD_RELOAD(101, [ignore], [ignore], 251) +-FWD_CHECK([--zone=drop --list-services], 0, [dnl + +-]) + FWD_CHECK([--zone=public --list-services], 0, [dnl + dhcpv6-client ssh + ]) +-- +1.8.3.1 + diff --git a/0001-fedora-patch-to-default-to-iptables-backend.patch b/0001-fedora-patch-to-default-to-iptables-backend.patch deleted file mode 100644 index 252ec97..0000000 --- a/0001-fedora-patch-to-default-to-iptables-backend.patch +++ /dev/null @@ -1,110 +0,0 @@ -From a628d6e3a710fb8379cf2fb319cdafc06dd2dad6 Mon Sep 17 00:00:00 2001 -From: Eric Garver -Date: Thu, 30 Aug 2018 16:34:19 -0400 -Subject: [PATCH] fedora patch to default to iptables backend - ---- - config/firewalld.conf | 7 ------- - src/firewall/core/io/firewalld_conf.py | 17 +++++++++++++++++ - src/tests/dbus/firewalld.conf.at | 2 +- - src/tests/functions.at | 4 ++-- - 4 files changed, 20 insertions(+), 10 deletions(-) - -diff --git a/config/firewalld.conf b/config/firewalld.conf -index 532f045..0f64a56 100644 ---- a/config/firewalld.conf -+++ b/config/firewalld.conf -@@ -40,13 +40,6 @@ IndividualCalls=no - # Default: off - LogDenied=off - --# FirewallBackend --# Selects the firewall backend implementation. --# Choices are: --# - nftables (default) --# - iptables (iptables, ip6tables, ebtables and ipset) --FirewallBackend=nftables -- - # FlushAllOnReload - # Flush all runtime rules on a reload. In previous releases some runtime - # configuration was retained during a reload, namely; interface to zone -diff --git a/src/firewall/core/io/firewalld_conf.py b/src/firewall/core/io/firewalld_conf.py -index 7c70921..4e83d6e 100644 ---- a/src/firewall/core/io/firewalld_conf.py -+++ b/src/firewall/core/io/firewalld_conf.py -@@ -268,6 +268,12 @@ class firewalld_conf(object): - if key not in done: - if (key in self._config and \ - self._config[key] != value): -+ # Only write FirewallBackend if it's not the default. -+ # We will change the default in the future. -+ if key == "FirewallBackend" and \ -+ self._config[key] == config.FALLBACK_FIREWALL_BACKEND: -+ done.append(key) -+ continue - empty = False - temp_file.write(u'%s=%s\n' % - (key, self._config[key])) -@@ -275,6 +281,12 @@ class firewalld_conf(object): - elif key in self._deleted: - modified = True - else: -+ # Only write FirewallBackend if it's not the default. -+ # We will change the default in the future. -+ if key == "FirewallBackend" and \ -+ value == config.FALLBACK_FIREWALL_BACKEND: -+ done.append(key) -+ continue - empty = False - temp_file.write(line+u"\n") - done.append(key) -@@ -286,6 +298,11 @@ class firewalld_conf(object): - for (key,value) in self._config.items(): - if key in done: - continue -+ # Only write FirewallBackend if it's not the default. -+ # We will change the default in the future. -+ if key == "FirewallBackend" and \ -+ value == config.FALLBACK_FIREWALL_BACKEND: -+ continue - if key in ["MinimalMark", "AutomaticHelpers"]: # omit deprecated from new config - continue - if not empty: -diff --git a/src/tests/dbus/firewalld.conf.at b/src/tests/dbus/firewalld.conf.at -index cc15318..374312b 100644 ---- a/src/tests/dbus/firewalld.conf.at -+++ b/src/tests/dbus/firewalld.conf.at -@@ -19,7 +19,7 @@ string "AllowZoneDrifting" : variant string "no" - string "AutomaticHelpers" : variant string "no" - string "CleanupOnExit" : variant string "no" - string "DefaultZone" : variant string "public" --string "FirewallBackend" : variant string "nftables" -+string "FirewallBackend" : variant string "iptables" - string "FlushAllOnReload" : variant string "yes" - string "IPv6_rpfilter" : variant string m4_escape(["${EXPECTED_IPV6_RPFILTER_VALUE}"]) - string "IndividualCalls" : variant string m4_escape(["${EXPECTED_INDIVIDUAL_CALLS_VALUE}"]) -diff --git a/src/tests/functions.at b/src/tests/functions.at -index 582fdcc..5a1aad1 100644 ---- a/src/tests/functions.at -+++ b/src/tests/functions.at -@@ -106,7 +106,7 @@ m4_define([FWD_START_TEST], [ - m4_ifdef([TESTING_FIREWALL_OFFLINE_CMD], [ - AT_KEYWORDS(offline) - ], [ -- m4_define_default([FIREWALL_BACKEND], [nftables]) -+ m4_define_default([FIREWALL_BACKEND], [iptables]) - - AT_KEYWORDS(FIREWALL_BACKEND) - -@@ -114,7 +114,7 @@ m4_define([FWD_START_TEST], [ - AT_CHECK([sed -i 's/^CleanupOnExit.*/CleanupOnExit=no/' ./firewalld.conf]) - - dnl set the appropriate backend -- AT_CHECK([sed -i 's/^FirewallBackend.*/FirewallBackend=FIREWALL_BACKEND/' ./firewalld.conf]) -+ AT_CHECK([echo "FirewallBackend=FIREWALL_BACKEND" >> ./firewalld.conf]) - - dnl fib matching is pretty new in nftables. Don't use rpfilter on older - dnl kernels. --- -1.8.3.1 - diff --git a/0001-fix-build-distribute-new-python-files.patch b/0001-fix-build-distribute-new-python-files.patch new file mode 100644 index 0000000..6cdd58f --- /dev/null +++ b/0001-fix-build-distribute-new-python-files.patch @@ -0,0 +1,35 @@ +From e8714cb5e3ad20708b3d481d51c3aa26c04a52d3 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Mon, 6 Apr 2020 16:52:02 -0400 +Subject: [PATCH] fix: build: distribute new python files + +Make sure we distribute the new python files. + +Fixes: 34bdee40aa61 ("feat: implement policy objects internally") +--- + src/Makefile.am | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/Makefile.am b/src/Makefile.am +index 76589d6..985c46a 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -27,6 +27,7 @@ nobase_dist_python_DATA = \ + firewall/core/fw_ipset.py \ + firewall/core/fw_nm.py \ + firewall/core/fw_policies.py \ ++ firewall/core/fw_policy.py \ + firewall/core/fw.py \ + firewall/core/fw_service.py \ + firewall/core/fw_transaction.py \ +@@ -44,6 +45,7 @@ nobase_dist_python_DATA = \ + firewall/core/io/io_object.py \ + firewall/core/io/ipset.py \ + firewall/core/io/lockdown_whitelist.py \ ++ firewall/core/io/policy.py \ + firewall/core/io/service.py \ + firewall/core/io/zone.py \ + firewall/core/ipset.py \ +-- +1.8.3.1 + diff --git a/0001-fix-po-add-new-python-files-to-POTFILES.patch b/0001-fix-po-add-new-python-files-to-POTFILES.patch new file mode 100644 index 0000000..8df6fc8 --- /dev/null +++ b/0001-fix-po-add-new-python-files-to-POTFILES.patch @@ -0,0 +1,33 @@ +From 1bac8783de46896b54161d8fe3cdbe7d1d7a1446 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Wed, 8 Apr 2020 14:42:14 -0400 +Subject: [PATCH] fix: po: add new python files to POTFILES + +Fixes: 34bdee40aa61 ("feat: implement policy objects internally") +--- + po/POTFILES.in | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 918f6f0..56952fe 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -242,6 +242,7 @@ src/firewall/core/fw_ifcfg.py + src/firewall/core/fw_ipset.py + src/firewall/core/fw_nm.py + src/firewall/core/fw_policies.py ++src/firewall/core/fw_policy.py + src/firewall/core/fw.py + src/firewall/core/fw_service.py + src/firewall/core/fw_transaction.py +@@ -259,6 +260,7 @@ src/firewall/core/io/__init__.py + src/firewall/core/io/io_object.py + src/firewall/core/io/ipset.py + src/firewall/core/io/lockdown_whitelist.py ++src/firewall/core/io/policy.py + src/firewall/core/io/service.py + src/firewall/core/io/zone.py + src/firewall/core/ipset.py +-- +1.8.3.1 + diff --git a/0001-fix-policy-ipXtables-calculate-max-name-len-properly.patch b/0001-fix-policy-ipXtables-calculate-max-name-len-properly.patch new file mode 100644 index 0000000..55a15bb --- /dev/null +++ b/0001-fix-policy-ipXtables-calculate-max-name-len-properly.patch @@ -0,0 +1,54 @@ +From c6fe749fb75004c30818bcc0696ac23801239d0b Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 21 Jul 2020 16:03:24 -0400 +Subject: [PATCH] fix(policy): ipXtables: calculate max name len properly + +Policy chain names still need the SHORTCUTS (POST, IN, etc) in the chain +name. As such, calculate the max name length appropriately. + +This also drops the "pol_" prefix for policy chains. Retaining it would +restrict the policy name max length unreasonably so. + +Fixes: 34bdee40aa61 ("feat: implement policy objects internally") +--- + src/firewall/core/ipXtables.py | 2 +- + src/firewall/functions.py | 8 +++++--- + 2 files changed, 6 insertions(+), 4 deletions(-) + +diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py +index b310a74..54c267b 100644 +--- a/src/firewall/core/ipXtables.py ++++ b/src/firewall/core/ipXtables.py +@@ -32,7 +32,7 @@ from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark, \ + Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock + import string + +-POLICY_CHAIN_PREFIX = "pol_" ++POLICY_CHAIN_PREFIX = "" + + BUILT_IN_CHAINS = { + "security": [ "INPUT", "OUTPUT", "FORWARD" ], +diff --git a/src/firewall/functions.py b/src/firewall/functions.py +index d4c5e90..de4e244 100644 +--- a/src/firewall/functions.py ++++ b/src/firewall/functions.py +@@ -508,11 +508,13 @@ def ppid_of_pid(pid): + def max_policy_name_len(): + """ + iptables limits length of chain to (currently) 28 chars. +- The longest chain we create is pol__allow, +- which leaves 28 - 10 = 18 chars for . ++ The longest chain we create is POST__allow, ++ which leaves 28 - 11 = 17 chars for . + """ + from firewall.core.ipXtables import POLICY_CHAIN_PREFIX +- return 28 - (len(POLICY_CHAIN_PREFIX) + len("_allow")) ++ from firewall.core.base import SHORTCUTS ++ longest_shortcut = max(map(len, SHORTCUTS.values())) ++ return 28 - (longest_shortcut + len(POLICY_CHAIN_PREFIX) + len("_allow")) + + def max_zone_name_len(): + """ +-- +1.8.3.1 + diff --git a/0001-fix-zone-listing-rich-rules-in-default-zone.patch b/0001-fix-zone-listing-rich-rules-in-default-zone.patch new file mode 100644 index 0000000..c2cbea3 --- /dev/null +++ b/0001-fix-zone-listing-rich-rules-in-default-zone.patch @@ -0,0 +1,25 @@ +From 3d418e35afecf68ba955915f29a003ad81258037 Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 28 Apr 2020 13:48:53 -0400 +Subject: [PATCH] fix(zone): listing rich rules in default zone + +Fixes: 34bdee40aa61 ("feat: implement policy objects internally") +--- + src/firewall/core/fw_zone.py | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/firewall/core/fw_zone.py b/src/firewall/core/fw_zone.py +index 6f6fba0..7048a90 100644 +--- a/src/firewall/core/fw_zone.py ++++ b/src/firewall/core/fw_zone.py +@@ -744,6 +744,7 @@ class FirewallZone(object): + return ret + + def list_rules(self, zone): ++ zone = self._fw.check_zone(zone) + ret = set() + for p_name in [self.policy_name_from_zones(zone, "ANY"), + self.policy_name_from_zones(zone, "HOST"), +-- +1.8.3.1 + diff --git a/0001-improvement-port-allow-coalescing-and-breaking-of-ra.patch b/0001-improvement-port-allow-coalescing-and-breaking-of-ra.patch new file mode 100644 index 0000000..0147339 --- /dev/null +++ b/0001-improvement-port-allow-coalescing-and-breaking-of-ra.patch @@ -0,0 +1,390 @@ +From a5291bcee84b56b30aac38544d85fb601fe6a25a Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Tue, 17 Mar 2020 13:51:43 -0400 +Subject: [PATCH] improvement: port: allow coalescing and breaking of ranges + +--- + src/firewall/core/fw_zone.py | 106 +++++++++++++++++++++++++++---------- + src/firewall/functions.py | 85 +++++++++++++++++++++++++++++ + src/firewall/server/config_zone.py | 55 ++++++++++++++----- + 3 files changed, 204 insertions(+), 42 deletions(-) + +diff --git a/src/firewall/core/fw_zone.py b/src/firewall/core/fw_zone.py +index 2bc94e3..d32d7a8 100644 +--- a/src/firewall/core/fw_zone.py ++++ b/src/firewall/core/fw_zone.py +@@ -25,7 +25,7 @@ from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET, \ + from firewall.core.logger import log + from firewall.functions import portStr, checkIPnMask, checkIP6nMask, \ + checkProtocol, enable_ip_forwarding, check_single_address, check_mac, \ +- portInPortRange, get_nf_conntrack_short_name ++ portInPortRange, get_nf_conntrack_short_name, coalescePortRange, breakPortRange + from firewall.core.rich import Rich_Rule, Rich_Accept, \ + Rich_Mark, Rich_Service, Rich_Port, Rich_Protocol, \ + Rich_Masquerade, Rich_ForwardPort, Rich_SourcePort, Rich_IcmpBlock, \ +@@ -857,11 +857,13 @@ class FirewallZone(object): + self._fw.check_panic() + _obj = self._zones[_zone] + +- port_id = self.__port_id(port, protocol) +- if port_id in _obj.settings["ports"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s:%s' already in '%s'" % (port, protocol, +- _zone)) ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s:%s' already in '%s'" % (port, protocol, _zone)) ++ ++ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) + + if use_transaction is None: + transaction = self.new_transaction() +@@ -869,10 +871,18 @@ class FirewallZone(object): + transaction = use_transaction + + if _obj.applied: +- self._port(True, _zone, port, protocol, transaction) +- +- self.__register_port(_obj, port_id, timeout, sender) +- transaction.add_fail(self.__unregister_port, _obj, port_id) ++ for range in added_ranges: ++ self._port(True, _zone, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._port(False, _zone, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__port_id(range, protocol) ++ self.__register_port(_obj, port_id, timeout, sender) ++ transaction.add_fail(self.__unregister_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__port_id(range, protocol) ++ transaction.add_post(self.__unregister_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) +@@ -889,20 +899,34 @@ class FirewallZone(object): + self._fw.check_panic() + _obj = self._zones[_zone] + +- port_id = self.__port_id(port, protocol) +- if port_id not in _obj.settings["ports"]: ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ break ++ else: + raise FirewallError(errors.NOT_ENABLED, + "'%s:%s' not in '%s'" % (port, protocol, _zone)) + ++ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: +- self._port(False, _zone, port, protocol, transaction) +- +- transaction.add_post(self.__unregister_port, _obj, port_id) ++ for range in added_ranges: ++ self._port(True, _zone, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._port(False, _zone, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__port_id(range, protocol) ++ self.__register_port(_obj, port_id, 0, None) ++ transaction.add_fail(self.__unregister_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__port_id(range, protocol) ++ transaction.add_post(self.__unregister_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) +@@ -1015,11 +1039,13 @@ class FirewallZone(object): + self._fw.check_panic() + _obj = self._zones[_zone] + +- port_id = self.__source_port_id(port, protocol) +- if port_id in _obj.settings["source_ports"]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "'%s:%s' already in '%s'" % (port, protocol, +- _zone)) ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "'%s:%s' already in '%s'" % (port, protocol, _zone)) ++ ++ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) + + if use_transaction is None: + transaction = self.new_transaction() +@@ -1027,10 +1053,18 @@ class FirewallZone(object): + transaction = use_transaction + + if _obj.applied: +- self._source_port(True, _zone, port, protocol, transaction) +- +- self.__register_source_port(_obj, port_id, timeout, sender) +- transaction.add_fail(self.__unregister_source_port, _obj, port_id) ++ for range in added_ranges: ++ self._source_port(True, _zone, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._source_port(False, _zone, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ self.__register_source_port(_obj, port_id, timeout, sender) ++ transaction.add_fail(self.__unregister_source_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ transaction.add_post(self.__unregister_source_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) +@@ -1047,20 +1081,34 @@ class FirewallZone(object): + self._fw.check_panic() + _obj = self._zones[_zone] + +- port_id = self.__source_port_id(port, protocol) +- if port_id not in _obj.settings["source_ports"]: ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ break ++ else: + raise FirewallError(errors.NOT_ENABLED, + "'%s:%s' not in '%s'" % (port, protocol, _zone)) + ++ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: +- self._source_port(False, _zone, port, protocol, transaction) +- +- transaction.add_post(self.__unregister_source_port, _obj, port_id) ++ for range in added_ranges: ++ self._source_port(True, _zone, portStr(range, "-"), protocol, transaction) ++ for range in removed_ranges: ++ self._source_port(False, _zone, portStr(range, "-"), protocol, transaction) ++ ++ for range in added_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ self.__register_source_port(_obj, port_id, 0, None) ++ transaction.add_fail(self.__unregister_source_port, _obj, port_id) ++ for range in removed_ranges: ++ port_id = self.__source_port_id(range, protocol) ++ transaction.add_post(self.__unregister_source_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) +diff --git a/src/firewall/functions.py b/src/firewall/functions.py +index 6af2206..6bc52d9 100644 +--- a/src/firewall/functions.py ++++ b/src/firewall/functions.py +@@ -72,6 +72,10 @@ def getPortRange(ports): + @return Array containing start and end port id for a valid range or -1 if port can not be found and -2 if port is too big for integer input or -1 for invalid ranges or None if the range is ambiguous. + """ + ++ # (port, port) or [port, port] case ++ if isinstance(ports, tuple) or isinstance(ports, list): ++ return ports ++ + # "" case + if isinstance(ports, int) or ports.isdigit(): + id1 = getPortID(ports) +@@ -155,6 +159,87 @@ def portInPortRange(port, range): + + return False + ++def coalescePortRange(new_range, ranges): ++ """ Coalesce a port range with existing list of port ranges ++ ++ @param new_range tuple/list/string ++ @param ranges list of tuple/list/string ++ @return tuple of (list of ranges added after coalescing, list of removed original ranges) ++ """ ++ ++ coalesced_range = getPortRange(new_range) ++ # normalize singleton ranges, e.g. (x,) --> (x,x) ++ if len(coalesced_range) == 1: ++ coalesced_range = (coalesced_range[0], coalesced_range[0]) ++ _ranges = map(getPortRange, ranges) ++ _ranges = sorted(map(lambda x: (x[0],x[0]) if len(x) == 1 else x, _ranges), key=lambda x: x[0]) ++ ++ removed_ranges = [] ++ for range in _ranges: ++ if coalesced_range[0] <= range[0] and coalesced_range[1] >= range[1]: ++ # new range covers this ++ removed_ranges.append(range) ++ elif coalesced_range[0] <= range[0] and coalesced_range[1] < range[1] and \ ++ coalesced_range[1] >= range[0]: ++ # expand beginning of range ++ removed_ranges.append(range) ++ coalesced_range = (coalesced_range[0], range[1]) ++ elif coalesced_range[0] > range[0] and coalesced_range[1] >= range[1] and \ ++ coalesced_range[0] <= range[1]: ++ # expand end of range ++ removed_ranges.append(range) ++ coalesced_range = (range[0], coalesced_range[1]) ++ ++ # normalize singleton ranges, e.g. (x,x) --> (x,) ++ removed_ranges = list(map(lambda x: (x[0],) if x[0] == x[1] else x, removed_ranges)) ++ if coalesced_range[0] == coalesced_range[1]: ++ coalesced_range = (coalesced_range[0],) ++ ++ return ([coalesced_range], removed_ranges) ++ ++def breakPortRange(remove_range, ranges): ++ """ break a port range from existing list of port ranges ++ ++ @param remove_range tuple/list/string ++ @param ranges list of tuple/list/string ++ @return tuple of (list of ranges added after breaking up, list of removed original ranges) ++ """ ++ ++ remove_range = getPortRange(remove_range) ++ # normalize singleton ranges, e.g. (x,) --> (x,x) ++ if len(remove_range) == 1: ++ remove_range = (remove_range[0], remove_range[0]) ++ _ranges = map(getPortRange, ranges) ++ _ranges = sorted(map(lambda x: (x[0],x[0]) if len(x) == 1 else x, _ranges), key=lambda x: x[0]) ++ ++ removed_ranges = [] ++ added_ranges = [] ++ for range in _ranges: ++ if remove_range[0] <= range[0] and remove_range[1] >= range[1]: ++ # remove entire range ++ removed_ranges.append(range) ++ elif remove_range[0] <= range[0] and remove_range[1] < range[1] and \ ++ remove_range[1] >= range[0]: ++ # remove from beginning of range ++ removed_ranges.append(range) ++ added_ranges.append((remove_range[1] + 1, range[1])) ++ elif remove_range[0] > range[0] and remove_range[1] >= range[1] and \ ++ remove_range[0] <= range[1]: ++ # remove from end of range ++ removed_ranges.append(range) ++ added_ranges.append((range[0], remove_range[0] - 1)) ++ elif remove_range[0] > range[0] and remove_range[1] < range[1]: ++ # remove inside range ++ removed_ranges.append(range) ++ added_ranges.append((range[0], remove_range[0] - 1)) ++ added_ranges.append((remove_range[1] + 1, range[1])) ++ ++ # normalize singleton ranges, e.g. (x,x) --> (x,) ++ removed_ranges = list(map(lambda x: (x[0],) if x[0] == x[1] else x, removed_ranges)) ++ added_ranges = list(map(lambda x: (x[0],) if x[0] == x[1] else x, added_ranges)) ++ ++ return (added_ranges, removed_ranges) ++ + def getServiceName(port, proto): + """ Check and Get service name from port and proto string combination using socket.getservbyport + +diff --git a/src/firewall/server/config_zone.py b/src/firewall/server/config_zone.py +index 1ae20ce..1c05318 100644 +--- a/src/firewall/server/config_zone.py ++++ b/src/firewall/server/config_zone.py +@@ -41,7 +41,8 @@ from firewall.server.decorators import handle_exceptions, \ + dbus_handle_exceptions, dbus_service_method + from firewall import errors + from firewall.errors import FirewallError +-from firewall.functions import portInPortRange ++from firewall.functions import portStr, portInPortRange, coalescePortRange, \ ++ breakPortRange + + ############################################################################ + # +@@ -455,10 +456,16 @@ class FirewallDConfigZone(slip.dbus.service.Object): + protocol) + self.parent.accessCheck(sender) + settings = list(self.getSettings()) +- if (port,protocol) in settings[6]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "%s:%s" % (port, protocol)) +- settings[6].append((port,protocol)) ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[6])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "%s:%s" % (port, protocol)) ++ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ for range in removed_ranges: ++ settings[6].remove((portStr(range, "-"), protocol)) ++ for range in added_ranges: ++ settings[6].append((portStr(range, "-"), protocol)) + self.update(settings) + + @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE, +@@ -471,9 +478,17 @@ class FirewallDConfigZone(slip.dbus.service.Object): + protocol) + self.parent.accessCheck(sender) + settings = list(self.getSettings()) +- if (port,protocol) not in settings[6]: ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[6])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ break ++ else: + raise FirewallError(errors.NOT_ENABLED, "%s:%s" % (port, protocol)) +- settings[6].remove((port,protocol)) ++ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ for range in removed_ranges: ++ settings[6].remove((portStr(range, "-"), protocol)) ++ for range in added_ranges: ++ settings[6].append((portStr(range, "-"), protocol)) + self.update(settings) + + @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE, +@@ -583,10 +598,16 @@ class FirewallDConfigZone(slip.dbus.service.Object): + protocol) + self.parent.accessCheck(sender) + settings = list(self.getSettings()) +- if (port,protocol) in settings[14]: +- raise FirewallError(errors.ALREADY_ENABLED, +- "%s:%s" % (port, protocol)) +- settings[14].append((port,protocol)) ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[14])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ raise FirewallError(errors.ALREADY_ENABLED, ++ "%s:%s" % (port, protocol)) ++ added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ for range in removed_ranges: ++ settings[14].remove((portStr(range, "-"), protocol)) ++ for range in added_ranges: ++ settings[14].append((portStr(range, "-"), protocol)) + self.update(settings) + + @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE, +@@ -599,9 +620,17 @@ class FirewallDConfigZone(slip.dbus.service.Object): + protocol) + self.parent.accessCheck(sender) + settings = list(self.getSettings()) +- if (port,protocol) not in settings[14]: ++ existing_port_ids = list(filter(lambda x: x[1] == protocol, settings[14])) ++ for port_id in existing_port_ids: ++ if portInPortRange(port, port_id[0]): ++ break ++ else: + raise FirewallError(errors.NOT_ENABLED, "%s:%s" % (port, protocol)) +- settings[14].remove((port,protocol)) ++ added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) ++ for range in removed_ranges: ++ settings[14].remove((portStr(range, "-"), protocol)) ++ for range in added_ranges: ++ settings[14].append((portStr(range, "-"), protocol)) + self.update(settings) + + @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_ZONE, +-- +1.8.3.1 + diff --git a/0001-improvement-port-simplify-queryPort.patch b/0001-improvement-port-simplify-queryPort.patch new file mode 100644 index 0000000..74a3745 --- /dev/null +++ b/0001-improvement-port-simplify-queryPort.patch @@ -0,0 +1,55 @@ +From cd8e0c3774a6c7ca6679fd50a0fb6f211528d9cc Mon Sep 17 00:00:00 2001 +From: Eric Garver +Date: Thu, 19 Mar 2020 16:22:18 -0400 +Subject: [PATCH] improvement: port: simplify queryPort + +--- + src/firewall/core/fw_zone.py | 10 +++------- + src/firewall/server/config_zone.py | 10 +++------- + 2 files changed, 6 insertions(+), 14 deletions(-) + +diff --git a/src/firewall/core/fw_zone.py b/src/firewall/core/fw_zone.py +index 5cda560..59d7a44 100644 +--- a/src/firewall/core/fw_zone.py ++++ b/src/firewall/core/fw_zone.py +@@ -914,13 +914,9 @@ class FirewallZone(object): + del _obj.settings["ports"][port_id] + + def query_port(self, zone, port, protocol): +- if self.__port_id(port, protocol) in self.get_settings(zone)["ports"]: +- return True +- else: +- # It might be a single port query that is inside a range +- for (_port, _protocol) in self.get_settings(zone)["ports"]: +- if portInPortRange(port, _port) and protocol == _protocol: +- return True ++ for (_port, _protocol) in self.get_settings(zone)["ports"]: ++ if portInPortRange(port, _port) and protocol == _protocol: ++ return True + + return False + +diff --git a/src/firewall/server/config_zone.py b/src/firewall/server/config_zone.py +index ed4eaba..bbbe7b5 100644 +--- a/src/firewall/server/config_zone.py ++++ b/src/firewall/server/config_zone.py +@@ -484,13 +484,9 @@ class FirewallDConfigZone(slip.dbus.service.Object): + protocol = dbus_to_python(protocol, str) + log.debug1("%s.queryPort('%s', '%s')", self._log_prefix, port, + protocol) +- if (port,protocol) in self.getSettings()[6]: +- return True +- else: +- # It might be a single port query that is inside a range +- for (_port, _protocol) in self.getSettings()[6]: +- if portInPortRange(port, _port) and protocol == _protocol: +- return True ++ for (_port, _protocol) in self.getSettings()[6]: ++ if portInPortRange(port, _port) and protocol == _protocol: ++ return True + + return False + +-- +1.8.3.1 + diff --git a/firewalld.spec b/firewalld.spec index 3e9a057..00d0e44 100644 --- a/firewalld.spec +++ b/firewalld.spec @@ -1,17 +1,21 @@ Name: firewalld Version: 0.8.3 -Release: 1 +Release: 2 Summary: A firewall daemon with D-Bus interface providing a dynamic firewall License: GPLv2+ URL: http://www.firewalld.org Source0: https://github.com/firewalld/firewalld/archive/v%{version}.tar.gz#/%{name}-%{version}.tar.gz #backport from gnome Patch0: firewalld-0.2.6-MDNS-default.patch -#backport from Eric Garver -Patch1: 0001-fedora-patch-to-default-to-iptables-backend.patch - -Patch2: repair-test-cases.patch +Patch1: repair-test-cases.patch +Patch2: 0001-improvement-port-simplify-queryPort.patch +Patch3: 0001-improvement-port-allow-coalescing-and-breaking-of-ra.patch +Patch4: 0001-feat-implement-policy-objects-internally.patch +Patch5: 0001-fix-build-distribute-new-python-files.patch +Patch6: 0001-fix-po-add-new-python-files-to-POTFILES.patch +Patch7: 0001-fix-zone-listing-rich-rules-in-default-zone.patch +Patch8: 0001-fix-policy-ipXtables-calculate-max-name-len-properly.patch BuildArch: noarch BuildRequires: autoconf automake desktop-file-utils gettext intltool glib2 glib2-devel systemd-units docbook-style-xsl @@ -196,6 +200,13 @@ fi %changelog +* Wed Sep 09 2020 gaihuiying - 0.8.3-2 +- Type:bugfix +- ID:NA +- SUG:restart +- DESC:remove unuselful patch:patch to default to iptables + add new featuer to fix command error when use with non-existen ipset + * Wed Apr 29 2020 zhouyihang - 0.8.3-1 - Type:requirement - ID:NA -- Gitee