"""Extensions to the sphinx.ext.graphviz module."""

import html
import io
import os
import pathlib
import re
import subprocess
import tempfile
from typing import Optional, Any, Tuple, Dict, Type, Sequence, NamedTuple, Union
import xml.etree.ElementTree as ET

import docutils.nodes
import sphinx.application
import sphinx.ext.graphviz
import sphinx.util.docutils
import sphinx.util.logging
from sphinx.writers.html import HTMLTranslator
from sphinx.writers.html5 import HTML5Translator

from . import google_fonts
from . import sphinx_utils

logger = sphinx.util.logging.getLogger(__name__)

def _replace_resolved_xrefs(node: sphinx.ext.graphviz.graphviz, code: str) -> str:
    """Extracts any resolved references, and uses them to replace `xref`
    attributes in the DOT code.
    """
    ref_replacements = {}

    for child in node.children:
        if not isinstance(child, docutils.nodes.container):
            continue
        xref_id = child.get("xref_id")
        if xref_id is None:
            continue
        text = child.astext()
        ref_nodes = list(child.findall(condition=docutils.nodes.reference))

        title = None
        url = None
        target = None
        replacement_text = f"label=<{html.escape(text)}>"
        if ref_nodes:
            ref_node = ref_nodes[-1]
            refuri = ref_node.get("refuri")
            if refuri is not None:
                url = refuri or "#"
            else:
                url = "#" + ref_node["refid"]
            title = ref_node.get("reftitle")
            replacement_text += f' href="{url}"'
            if title is not None:
                replacement_text += f" tooltip=<{html.escape(title)}>"
            target = ref_node.get("target")
            if target is not None:
                replacement_text += f' target="{target}"'

        ref_replacements[xref_id] = replacement_text

    if ref_replacements:
        ref_pattern = "|".join(ref_replacements.keys())
        code = re.sub(ref_pattern, lambda m: ref_replacements[m.group(0)], code)
    return code

class GraphvizConfigInfo(NamedTuple):
    """Adjusted graphviz configuration.

    This extension configures Graphviz to use use the same font (from Google
    Fonts) that is used for other document text.

    Note that when generating SVG output, Graphviz uses the font just to compute
    the size of labels.  The web browser is still responsible for loading the
    font and rendering the text.  Therefore, the font needs to be available both
    to Graphviz and the web browser.

    Since the font already needs to be available to the web browser for other
    document text, there is no added complication there.  But making the font
    available to Graphviz is non-trivial:

    - Graphviz supports several mechanisms for text layout:

      - LibGD, always available on Linux, sometimes on Windows, and allows TTF
        font paths to be specified directly;

      - Pango and/or GDI+ (Windows only), relies on systems-specific font paths
        and does not allow paths to TTF fonts to be specified directly.

      Therefore, LibGD must be used, but unfortunately, depending on how
      Graphviz is built, Pango and/or GDI+ is normally used by default.

    - Graphviz does not provide any command-line options for controlling which
      plugins are loaded.  Instead, plugins can only be loaded through a config
      file.

    Therefore, to ensure LibGD is used for fonts, it is necessary to locate the
    original config file, parse it and remove the section that loads Pango, if
    present, and write it to a new temporary directory.

    """

    orig_config_path: str
    """Path to original config file."""

    new_config: bytes
    """New config content with pango excluded."""

def get_adjusted_graphviz_config(
    app: sphinx.application.Sphinx, dot_command: str
) -> Optional[GraphvizConfigInfo]:
    """Returns the graphviz configuration info for a given `dot_command`.

    The returned config file must be written to a temporary directory with a
    symlink "plugins" to the `orig_config_path`.
    """
    key = "_sphinx_immaterial_graphviz_adjusted_configs"
    configs = getattr(app, key, None)
    if configs is None:
        configs = {}
        setattr(app, key, configs)
    config = configs.get(dot_command, False)
    if config is False:
        config = _make_adjusted_graphviz_config(app, dot_command)
        configs[dot_command] = config
    return config

def _get_orig_config_path(dot_command: str) -> Optional[str]:
    result = subprocess.run(
        [dot_command, "-v"], input="", text=True, capture_output=True, check=True
    )

    m = re.search(
        r"^The plugin configuration file:\s+(.*)$\s+was successfully loaded\.$",
        result.stderr,
        re.MULTILINE,
    )
    if m is None:
        logger.error(
            "Failed to determine graphviz config path from stderr: %r", result.stderr
        )
        return None

    return m.group(1)

def _make_adjusted_graphviz_config(
    app: sphinx.application.Sphinx, dot_command: str
) -> Optional[GraphvizConfigInfo]:
    """Determines the graphviz libdir and generates an adjusted config.

    This is called by `get_adjusted_graphviz_config`.
    """

    orig_config_path = _get_orig_config_path(dot_command)
    if orig_config_path is None:
        return None

    config_content = pathlib.Path(orig_config_path).read_bytes()

    # Strip comments
    config_content = re.sub(b"#[^\n]*", b"", config_content)

    new_config = io.BytesIO()

    prev_index = 0

    def parse_error():
        logger.error(
            "Failed to parse graphviz config file %r, starting at: %r",
            orig_config_path,
            config_content[prev_index:],
        )

    found_gd = False

    # Match plugins
    for m in re.finditer(
        rb"\s*([^\s{}]+)\s+([^\s{}]+)\s*(\{\s*(?:[^{}\s]+\s*\{[^{}]*\}\s*)*\s*\})\s*",
        config_content,
    ):
        if m.start() != prev_index:
            parse_error()
            return None
        prev_index = m.end()
        plugin_path = m.group(1)
        plugin_name = m.group(2)
        plugin_config = m.group(3)
        if plugin_name == b"gd":
            found_gd = True
        else:
            plugin_config = re.sub(rb"\btextlayout\s*\{[^}]*\}", b"", plugin_config)
        if not os.path.isabs(plugin_path):
            plugin_path = os.path.join(b"plugins", plugin_path)
        new_config.write(plugin_path)
        new_config.write(b" ")
        new_config.write(plugin_name)
        new_config.write(b" ")
        new_config.write(plugin_config)

    if prev_index != len(config_content):
        parse_error()
        return None

    if not found_gd:
        if not app.config.graphviz_ignore_incorrect_font_metrics:
            logger.warning(
                "Incorrect font metrics will be used because "
                "graphviz binary %r does not have LibGD support.  This warning is expected on x86_64 Windows "
                "(https://gitlab.com/graphviz/graphviz/-/issues/2267). "
                "Set `graphviz_ignore_incorrect_font_metrics = True` in `conf.py` "
                "to silence this warning.",
                dot_command,
            )
        return None

    return GraphvizConfigInfo(
        orig_config_path=orig_config_path, new_config=new_config.getvalue()
    )

def render_dot_html(
    self: Union[HTMLTranslator, HTML5Translator],
    node: sphinx.ext.graphviz.graphviz,
    code: str,
    options: dict,
    prefix: str = "graphviz",
    imgcls: Optional[str] = None,
    alt: Optional[str] = None,
    filename: Optional[str] = None,
) -> Tuple[str, str]:
    theme_options = self.builder.config["html_theme_options"]
    font: Optional[str] = None
    if isinstance(theme_options["font"], dict) and "text" in theme_options["font"]:
        # using a google font; otherwise
        font = theme_options["font"]["text"]

    ttf_font_paths = google_fonts.get_ttf_font_paths(self.builder.app)
    ttf_font: Optional[str] = None
    if ttf_font_paths and font is not None:
        # can only support the chosen font if cache exists and a Google font is used
        ttf_font = ttf_font_paths[(font, "regular")]

    code = _replace_resolved_xrefs(node, code)

    var_replacements: Dict[str, str] = {}
    replacement_to_var: Dict[str, str] = {}

    def replace_var(var_text: str) -> str:
        replacement_color = var_replacements.setdefault(
            var_text, "#%06x" % (0x123456 + len(var_replacements))
        )
        replacement_to_var.setdefault(replacement_color, var_text)
        return replacement_color

    def replace_var_in_code(m: re.Match) -> str:
        var_text = m.group(1)
        replacement_color = replace_var(var_text)
        return f'"{replacement_color}"'

    fontcolor = replace_var("var(--md-code-fg-color)")
    fontsize = "12"

    graphviz_dot = options.get("graphviz_dot", self.builder.config.graphviz_dot)
    config_info = get_adjusted_graphviz_config(self.builder.app, graphviz_dot)

    if config_info is None:
        ttf_font = font

    command_line_options = [
        "-Ncolor=" + replace_var("var(--md-graphviz-node-fg-color)"),
        "-Nstyle=solid,filled",
        "-Nfillcolor=" + replace_var("var(--md-graphviz-node-bg-color)"),
        "-Nfontcolor=" + fontcolor,
        "-Nfontsize=" + fontsize,
        "-Ecolor=" + replace_var("var(--md-graphviz-edge-color)"),
        "-Efontcolor=" + fontcolor,
        "-Efontsize=" + fontsize,
        "-Gbgcolor=transparent",
        "-Gcolor=" + replace_var("var(--md-graphviz-node-fg-color)"),
        "-Gfontcolor=" + fontcolor,
        "-Gfontsize=" + fontsize,
    ]
    if ttf_font is not None:
        command_line_options.extend(
            [
                "-Nfontname=" + ttf_font,
                "-Efontname=" + ttf_font,
                "-Gfontname=" + ttf_font,
            ]
        )

    code = re.sub(r'"((?:var|calc)\s*\(.*?\))"', replace_var_in_code, code)

    dot_cmd = [graphviz_dot]
    dot_cmd.extend(command_line_options)
    dot_cmd.extend(self.builder.config.graphviz_dot_args)
    dot_cmd.append("-Tsvg")

    with tempfile.TemporaryDirectory() as tempdir:
        env = os.environ.copy()
        if config_info is not None:
            orig_lib_path = pathlib.Path(config_info.orig_config_path)
            new_lib_dir = pathlib.Path(tempdir, "plugins")
            pathlib.Path(tempdir, orig_lib_path.name).write_bytes(
                config_info.new_config
            )

            env["GVBINDIR"] = tempdir
            new_lib_dir.symlink_to(orig_lib_path.parent, target_is_directory=True)
            cwd = str(orig_lib_path.parent)
        else:
            cwd = None
        dot_result = subprocess.run(
            dot_cmd,
            input=code,
            encoding="utf-8",
            check=False,
            env=env,
            cwd=cwd,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
        )
        svg_output = dot_result.stdout
        errors = dot_result.stderr.strip()
        if config_info is None:
            # Filter warnings about fonts
            errors = re.sub(r"^.*couldn\'t load font .*$", "", errors, re.MULTILINE)
        errors = errors.strip()
        if errors or dot_result.returncode != 0:
            error_func = logger.warning if dot_result.returncode == 0 else logger.error
            error_func(
                "Error running %r with env %r: %s", dot_cmd, env, errors, location=node
            )
            if dot_result.returncode != 0:
                raise docutils.nodes.SkipNode

    ET.register_namespace("", "http://www.w3.org/2000/svg")
    root = ET.fromstring(svg_output)
    xlink_href_key = "{http://www.w3.org/1999/xlink}href"
    xlink_title_key = "{http://www.w3.org/1999/xlink}title"
    a_tag = "{http://www.w3.org/2000/svg}a"
    text_tag = "{http://www.w3.org/2000/svg}text"

    for element in root.iter():
        style = ""
        attrib = element.attrib
        for attr in ("fill", "stroke"):
            attrib_val = attrib.get(attr)
            if attrib_val is None:
                continue
            var_val = replacement_to_var.get(attrib_val)
            if var_val is not None:
                del attrib[attr]
                style += f"{attr}: {var_val};"
        font_family = attrib.get("font-family")
        if font is not None and font_family == ttf_font:  # using a cached google font
            attrib["font-family"] = font
        elif font is None and font_family is not None:  # using a system font (via CSS)
            attrib.pop("font-family")
            style += "font-family: var(--md-text-font-family);"
        href = attrib.pop(xlink_href_key, None)
        if href is not None:
            attrib["href"] = href
        title = attrib.pop(xlink_title_key, None)
        if title is not None:
            title_element = ET.Element("title")
            title_element.text = title
            element.append(title_element)
        if element.tag == a_tag:
            for child in element:
                if child.tag == text_tag:
                    child.attrib["within_a"] = "true"
        within_a = attrib.pop("within_a", None)
        if within_a:
            style += "--md-graphviz-hover-color: var(--md-graphviz-a-hover-color);"
        if style:
            attrib["style"] = style

    classes = [imgcls, "graphviz"] + node.get("classes", [])
    imgcls = " ".join(filter(None, classes))
    root.attrib["class"] = (root.attrib.get("class", "") + " " + imgcls).strip()

    base_scale = 0.75

    def convert_width_or_height(s: str):
        assert s.endswith("pt")
        val_pt = float(s[:-2])
        val_px = val_pt / 0.75
        val_rem = val_px / 16 * base_scale
        return f"{val_rem}rem"

    root_style = ""
    for attr in ("width", "height"):
        attrib_val = root.attrib.pop(attr, None)
        if attrib_val is not None:
            root_style += f"{attr}: {convert_width_or_height(attrib_val)};"
    if root_style:
        root.attrib["style"] = root_style

    svg_output = ET.tostring(root, encoding="unicode")

    if alt is None:
        alt = node.get("alt", self.encode(code).strip())
    if "align" in node:
        self.body.append(
            '
' % (node["align"], node["align"])
        )
    self.body.append(svg_output)
    if "align" in node:
        self.body.append("
\n")

    raise docutils.nodes.SkipNode

def _replace_var_refs_with_defaults(code: str) -> str:
    code = re.sub(r'"var\s*\(.*?,\s*(.*)\)"', lambda m: f'"{m.group(1)}"', code)
    return code

def on_build_finished(*args, **kwargs) -> None:
    # Suppress inclusion of the graphviz.css file supplied by
    # `sphinx.ext.graphviz`.  This theme provides its own style rules.
    pass

sphinx.ext.graphviz.on_config_inited = on_build_finished  # type: ignore[attr-defined]
sphinx.ext.graphviz.on_build_finished = on_build_finished  # type: ignore[attr-defined]

def _preprocess_graphviz_node(
    directive: sphinx.util.docutils.SphinxDirective,
    node: sphinx.ext.graphviz.graphviz,
    line_offset: int,
) -> None:
    code = node["code"]

    xrefs: Dict[str, Tuple[str, int]] = {}

    def replace_xref(m: re.Match) -> str:
        line_index = code.count("\n", 0, m.start())
        xref_text = m.group(1).replace(r"\"", '"')
        xref_index = len(xrefs)
        xref_id = f"__SPHINX_IMMATERIAL_XREF_{xref_index}__"
        xref_id, _ = xrefs.setdefault(xref_text, (xref_id, line_index - line_offset))
        return xref_id

    code = re.sub(r'\bxref\s*=\s*"((?:[^\\"]*|(?:\\.|"))*)"', replace_xref, code)

    node["code"] = code

    filename = node.get("filename")

    for xref_text, (xref_id, line_index) in xrefs.items():
        container = docutils.nodes.container()
        container["xref_id"] = xref_id

        # Determine source location
        if filename is None:
            source_path, source_offset = directive.content.items[line_index]
        else:
            source_path = os.path.join(directive.env.app.srcdir, filename)
            source_offset = line_index

        nodes = sphinx_utils.parse_rst(
            state=directive.state,
            text=xref_text,
            source_path=source_path,
            source_line=source_offset,
        )
        container += nodes
        node += container

def _monkey_patch_graphviz_directive(
    directive: Type[sphinx.util.docutils.SphinxDirective], line_offset: int
):
    orig_run = directive.run

    def run(
        self: sphinx.util.docutils.SphinxDirective,
    ) -> Sequence[docutils.nodes.Node]:
        nodes = orig_run(self)
        for node in nodes:
            for graphviz_node in node.findall(condition=sphinx.ext.graphviz.graphviz):
                _preprocess_graphviz_node(self, graphviz_node, line_offset)
        return nodes

    directive.run = run  # type: ignore[assignment]

_monkey_patch_graphviz_directive(sphinx.ext.graphviz.Graphviz, line_offset=0)
_monkey_patch_graphviz_directive(sphinx.ext.graphviz.GraphvizSimple, line_offset=2)

def _monkey_patch_render_dot(name: str) -> None:
    orig_render = getattr(sphinx.ext.graphviz, name)

    def render_dot(
        self, node: sphinx.ext.graphviz.graphviz, code: str, options: Dict, **kwargs
    ):
        code = _replace_resolved_xrefs(node, code)
        code = _replace_var_refs_with_defaults(code)
        return orig_render(self, node, code, options, **kwargs)

    setattr(sphinx.ext.graphviz, name, render_dot)

sphinx.ext.graphviz.render_dot_html = render_dot_html
_monkey_patch_render_dot("render_dot_texinfo")
_monkey_patch_render_dot("render_dot_latex")

def setup(app: sphinx.application.Sphinx) -> Dict[str, Any]:
    app.setup_extension("sphinx.ext.graphviz")
    app.add_config_value(
        "graphviz_ignore_incorrect_font_metrics",
        types=(bool,),
        default=False,
        rebuild="env",
    )
    sphinx_utils.remove_css_file(app, "graphviz.css")
    return {"parallel_read_safe": True, "parallel_write_safe": True}


import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize

def make_grid(x):
    n = np.prod([y.size for y in x])
    d = len(x)
    return np.array(np.meshgrid(*tuple(x))).reshape(d, n).T

def kron_prod(mat_list, b):
    x = b.copy()
    n = [x.shape[1] for x in mat_list]
    for j in range(len(mat_list) - 1, -1, -1):
        d = n[j]
        x = x.reshape(int(x.size / d), d)
        x = mat_list[j].dot(x.T)
        x = x.ravel()
    return x

def kron_prod_naive(mat_list, b):
    res = 1
    for x in mat_list:
        res = np.kron(res, x)
    return res.dot(b)

class GPR:
    def __init__(self):
        self.nr_dimensions = 0
        self.noise = 1e-6
        self.alpha = 1
        self.length_scale = []
        self.minus_log_likelihood = np.inf
        self.weights = np.array(0.0)
        self.weights_are_fitted = False

    def init_kernel(self, d, noise, length_scale):
        if length_scale is None:
            self.length_scale = [1] * d
        else:
            self.length_scale = length_scale
        self.nr_dimensions = d
        self.noise = noise

    def gaussian_kernel(self, x_train, x_eval):
        res = []
        for i, x1, x2 in zip(range(self.nr_dimensions), x_train, x_eval):
            dx = (x1[:, None] - x2[None, :]) / self.length_scale[i]
            _g = np.exp(-0.5 * dx * dx)
            res.append(_g)
        return res

    def fit_weights(self, x, y, noise=1e-6, length_scale=None):
        self.init_kernel(len(x), noise, length_scale)
        self._x_train = x
        gausss_kern = self.gaussian_kernel(x, x)
        self._eig_vec_kern = []
        self._kron_eig_val = 1
        for k in gausss_kern:
            l, o = np.linalg.eigh(k)
            o = o.T
            self._eig_vec_kern.append(o)
            self._kron_eig_val = np.kron(self._kron_eig_val, np.maximum(l, 0))
        self.weights = kron_prod(self._eig_vec_kern, y)
        self.weights = self.weights / (self._kron_eig_val + self.noise ** 2)
        self.weights = kron_prod([x.T for x in self._eig_vec_kern], self.weights) / self.alpha
        self.weights_are_fitted = True
        self.minus_log_likelihood = y.dot(self.weights)
        self.minus_log_likelihood += np.log(self._kron_eig_val + self.noise ** 2).sum()
        self.minus_log_likelihood += np.log(self.alpha)

    def predict(self, x):
        if self.weights_are_fitted:
            gauss_kern = self.gaussian_kernel(x, self._x_train)
            return kron_prod(gauss_kern, self.weights) * self.alpha

    def fit(self, x_train, y_train):
        dx = [(np.diff(x).mean() * 2, (x.max() - x.min())) for x in x_train]

        def target_fun(x):
            self.fit_weights(x_train, y_train, noise=x[-1], length_scale=x[:-1])
            return self.minus_log_likelihood
        x0=np.array([1]*self.nr_dimensions+[1e-6])
        sol = minimize(target_fun,x0)

if __name__ == '__main__':
    d = 2
    x_train = [np.linspace(0, 1, 1000) for i in range(d)]
    x_grid = make_grid(x_train)
    n = x_grid.shape[0]
    y_train = x_grid.sum(axis=1) ** 2 + np.random.normal(0, 0.1, n)

    gpr = GPR()
    gpr.fit_weights(x_train, y_train)
    # plt.plot(gpr.weights)
    #plt.plot(x_grid.ravel(), y_train, '.')
    x_test = [np.sort(np.random.random(100)) for i in range(d)]
    x_grid_test = make_grid(x_test)

    #plt.plot(x_grid_test.ravel(), gpr.predict(x_test))
    #plt.show()
    dx = np.array([(np.diff(x).mean() * 2, (x.max() - x.min())) for x in x_train])

    def target_fun(x):
        q = np.exp(x[:-1])
        q = q / (1 + q)
        length_scale = q * dx[:, 0] + (1 - q) * dx[:, 1]
        print(q, length_scale)
        gpr.fit_weights(x_train, y_train, noise=x[-1], length_scale=length_scale)
        return gpr.minus_log_likelihood

    x0 = np.array([1] * gpr.nr_dimensions + [1e-6])

    #sol = minimize(target_fun, x0)
    y0 = target_fun(x0)


import os

import dbus
import dbus.service

from firewall import config
from firewall.core.base import DEFAULT_ZONE_TARGET
from firewall.core.watcher import Watcher
from firewall.core.logger import log
from firewall.server.dbus import DbusServiceObject
from firewall.server.decorators import handle_exceptions, \
    dbus_handle_exceptions, dbus_service_method, \
    dbus_service_method_deprecated, dbus_service_signal_deprecated, \
    dbus_polkit_require_auth
from firewall.server.config_icmptype import FirewallDConfigIcmpType
from firewall.server.config_service import FirewallDConfigService
from firewall.server.config_zone import FirewallDConfigZone
from firewall.server.config_policy import FirewallDConfigPolicy
from firewall.server.config_ipset import FirewallDConfigIPSet
from firewall.server.config_helper import FirewallDConfigHelper
from firewall.core.io.icmptype import IcmpType
from firewall.core.io.ipset import IPSet
from firewall.core.io.helper import Helper
from firewall.core.io.lockdown_whitelist import LockdownWhitelist
from firewall.core.io.direct import Direct
from firewall.dbus_utils import dbus_to_python, \
    command_of_sender, context_of_sender, uid_of_sender, user_of_uid, \
    dbus_introspection_prepare_properties, \
    dbus_introspection_add_properties, \
    dbus_introspection_add_deprecated
from firewall import errors
from firewall.errors import FirewallError

############################################################################
#
# class FirewallDConfig
#
############################################################################

class FirewallDConfig(DbusServiceObject):
    """FirewallD main class"""

    persistent = True
    """ Make FirewallD persistent. """
    default_polkit_auth_required = config.dbus.PK_ACTION_CONFIG
    """ Use config.dbus.PK_ACTION_INFO as a default """

    @handle_exceptions
    def __init__(self, conf, *args, **kwargs):
        super(FirewallDConfig, self).__init__(*args, **kwargs)
        self.config = conf
        self.busname = args[0]
        self.path = args[1]
        self._init_vars()
        self.watcher = Watcher(self.watch_updater, 5)
        self.watcher.add_watch_dir(config.FIREWALLD_IPSETS)
        self.watcher.add_watch_dir(config.ETC_FIREWALLD_IPSETS)
        self.watcher.add_watch_dir(config.FIREWALLD_ICMPTYPES)
        self.watcher.add_watch_dir(config.ETC_FIREWALLD_ICMPTYPES)
        self.watcher.add_watch_dir(config.FIREWALLD_HELPERS)
        self.watcher.add_watch_dir(config.ETC_FIREWALLD_HELPERS)
        self.watcher.add_watch_dir(config.FIREWALLD_SERVICES)
        self.watcher.add_watch_dir(config.ETC_FIREWALLD_SERVICES)
        self.watcher.add_watch_dir(config.FIREWALLD_ZONES)
        self.watcher.add_watch_dir(config.ETC_FIREWALLD_ZONES)
        self.watcher.add_watch_dir(config.FIREWALLD_POLICIES)
        self.watcher.add_watch_dir(config.ETC_FIREWALLD_POLICIES)
        # Add watches for combined zone directories
        if os.path.exists(config.ETC_FIREWALLD_ZONES):
            for filename in sorted(os.listdir(config.ETC_FIREWALLD_ZONES)):
                path = "%s/%s" % (config.ETC_FIREWALLD_ZONES, filename)
                if os.path.isdir(path):
                    self.watcher.add_watch_dir(path)
        self.watcher.add_watch_file(config.LOCKDOWN_WHITELIST)
        self.watcher.add_watch_file(config.FIREWALLD_DIRECT)
        self.watcher.add_watch_file(config.FIREWALLD_CONF)

        dbus_introspection_prepare_properties(self, config.dbus.DBUS_INTERFACE_CONFIG,
            {
                "CleanupOnExit": "readwrite",
                "CleanupModulesOnExit": "readwrite",
                "IPv6_rpfilter": "readwrite",
                "Lockdown": "readwrite",
                "MinimalMark": "readwrite",
                "IndividualCalls": "readwrite",
                "LogDenied": "readwrite",
                "AutomaticHelpers": "readwrite",
                "FirewallBackend": "readwrite",
                "FlushAllOnReload": "readwrite",
                "RFC3964_IPv4": "readwrite",
                "AllowZoneDrifting": "readwrite",
                "NftablesFlowtable": "readwrite",
                "NftablesCounters": "readwrite",
            }
        )

    @handle_exceptions
    def _init_vars(self):
        self.ipsets = [ ]
        self.ipset_idx = 0
        self.icmptypes = [ ]
        self.icmptype_idx = 0
        self.services = [ ]
        self.service_idx = 0
        self.zones = [ ]
        self.zone_idx = 0
        self.helpers = [ ]
        self.helper_idx = 0
        self.policy_objects = [ ]
        self.policy_object_idx = 0

        for ipset in self.config.get_ipsets():
            self._addIPSet(self.config.get_ipset(ipset))
        for icmptype in self.config.get_icmptypes():
            self._addIcmpType(self.config.get_icmptype(icmptype))
        for service in self.config.get_services():
            self._addService(self.config.get_service(service))
        for zone in self.config.get_zones():
            self._addZone(self.config.get_zone(zone))
        for helper in self.config.get_helpers():
            self._addHelper(self.config.get_helper(helper))
        for policy in self.config.get_policy_objects():
            self._addPolicy(self.config.get_policy_object(policy))

    @handle_exceptions
    def __del__(self):
        pass

    @handle_exceptions
    def reload(self):
        while len(self.ipsets) > 0:
            item = self.ipsets.pop()
            item.unregister()
            del item
        while len(self.icmptypes) > 0:
            item = self.icmptypes.pop()
            item.unregister()
            del item
        while len(self.services) > 0:
            item = self.services.pop()
            item.unregister()
            del item
        while len(self.zones) > 0:
            item = self.zones.pop()
            item.unregister()
            del item
        while len(self.helpers) > 0:
            item = self.helpers.pop()
            item.unregister()
            del item
        while len(self.policy_objects) > 0:
            item = self.policy_objects.pop()
            item.unregister()
            del item
        self._init_vars()

    @handle_exceptions
    def watch_updater(self, name):
        if name == config.FIREWALLD_CONF:
            old_props = self.GetAll(config.dbus.DBUS_INTERFACE_CONFIG)
            log.debug1("config: Reloading firewalld config file '%s'",
                       config.FIREWALLD_CONF)
            try:
                self.config.update_firewalld_conf()
            except Exception as msg:
                log.error("Failed to load firewalld.conf file '%s': %s" % \
                          (name, msg))
                return
            props = self.GetAll(config.dbus.DBUS_INTERFACE_CONFIG).copy()
            for key in list(props.keys()):
                if key in old_props and old_props[key] == props[key]:
                    del props[key]
            if len(props) > 0:
                self.PropertiesChanged(config.dbus.DBUS_INTERFACE_CONFIG,
                                       props, [])
            return

        if (name.startswith(config.FIREWALLD_ICMPTYPES) or \
            name.startswith(config.ETC_FIREWALLD_ICMPTYPES)) and \
           name.endswith(".xml"):
            try:
                (what, obj) = self.config.update_icmptype_from_path(name)
            except Exception as msg:
                log.error("Failed to load icmptype file '%s': %s" % (name, msg))
                return
            if what == "new":
                self._addIcmpType(obj)
            elif what == "remove":
                self.removeIcmpType(obj)
            elif what == "update":
                self._updateIcmpType(obj)

        elif (name.startswith(config.FIREWALLD_SERVICES) or \
              name.startswith(config.ETC_FIREWALLD_SERVICES)) and \
             name.endswith(".xml"):
            try:
                (what, obj) = self.config.update_service_from_path(name)
            except Exception as msg:
                log.error("Failed to load service file '%s': %s" % (name, msg))
                return
            if what == "new":
                self._addService(obj)
            elif what == "remove":
                self.removeService(obj)
            elif what == "update":
                self._updateService(obj)

        elif name.startswith(config.FIREWALLD_ZONES) or \
             name.startswith(config.ETC_FIREWALLD_ZONES):
            if name.endswith(".xml"):
                try:
                    (what, obj) = self.config.update_zone_from_path(name)
                except Exception as msg:
                    log.error("Failed to load zone file '%s': %s" % (name, msg))
                    return
                if what == "new":
                    self._addZone(obj)
                elif what == "remove":
                    self.removeZone(obj)
                elif what == "update":
                    self._updateZone(obj)
            elif name.startswith(config.ETC_FIREWALLD_ZONES):
                # possible combined zone base directory
                _name = name.replace(config.ETC_FIREWALLD_ZONES, "").strip("/")
                if len(_name) < 1 or "/" in _name:
                    # if there is a / in x, then it is a sub sub directory
                    # ignore it
                    return
                if os.path.isdir(name):
                    if not self.watcher.has_watch(name):
                        self.watcher.add_watch_dir(name)
                elif self.watcher.has_watch(name):
                    self.watcher.remove_watch(name)

        elif (name.startswith(config.FIREWALLD_IPSETS) or \
              name.startswith(config.ETC_FIREWALLD_IPSETS)) and \
             name.endswith(".xml"):
            try:
                (what, obj) = self.config.update_ipset_from_path(name)
            except Exception as msg:
                log.error("Failed to load ipset file '%s': %s" % (name,
                                                                  msg))

                return
            if what == "new":
                self._addIPSet(obj)
            elif what == "remove":
                self.removeIPSet(obj)
            elif what == "update":
                self._updateIPSet(obj)

        elif (name.startswith(config.FIREWALLD_HELPERS) or \
              name.startswith(config.ETC_FIREWALLD_HELPERS)) and \
             name.endswith(".xml"):
            try:
                (what, obj) = self.config.update_helper_from_path(name)
            except Exception as msg:
                log.error("Failed to load helper file '%s': %s" % (name,
                                                                  msg))

                return
            if what == "new":
                self._addHelper(obj)
            elif what == "remove":
                self.removeHelper(obj)
            elif what == "update":
                self._updateHelper(obj)

        elif name == config.LOCKDOWN_WHITELIST:
            try:
                self.config.update_lockdown_whitelist()
            except Exception as msg:
                log.error("Failed to load lockdown whitelist file '%s': %s" % \
                          (name, msg))
                return
            self.LockdownWhitelistUpdated()

        elif name == config.FIREWALLD_DIRECT:
            try:
                self.config.update_direct()
            except Exception as msg:
                log.error("Failed to load direct rules file '%s': %s" % (name,
                                                                         msg))
                return
            self.Updated()

        elif (name.startswith(config.FIREWALLD_POLICIES) or \
              name.startswith(config.ETC_FIREWALLD_POLICIES)) and \
             name.endswith(".xml"):
            try:
                (what, obj) = self.config.update_policy_object_from_path(name)
            except Exception as msg:
                log.error("Failed to load policy file '%s': %s" % (name, msg))
                return
            if what == "new":
                self._addPolicy(obj)
            elif what == "remove":
                self.removePolicy(obj)
            elif what == "update":
                self._updatePolicy(obj)

    @handle_exceptions
    def _addIcmpType(self, obj):
        # TODO: check for idx overflow
        config_icmptype = FirewallDConfigIcmpType(
            self, self.config, obj, self.icmptype_idx, self.busname,
            "%s/%d" % (config.dbus.DBUS_PATH_CONFIG_ICMPTYPE,
                       self.icmptype_idx))
        self.icmptypes.append(config_icmptype)
        self.icmptype_idx += 1
        self.IcmpTypeAdded(obj.name)
        return config_icmptype

    @handle_exceptions
    def _updateIcmpType(self, obj):
        for icmptype in self.icmptypes:
            if icmptype.obj.name == obj.name and \
                    icmptype.obj.path == obj.path and \
                    icmptype.obj.filename == obj.filename:
                icmptype.obj = obj
                icmptype.Updated(obj.name)

    @handle_exceptions
    def removeIcmpType(self, obj):
        index = 7 # see IMPORT_EXPORT_STRUCTURE in class Zone(IO_Object)
        for zone in self.zones:
            settings = zone.getSettings()
            # if this IcmpType is used in a zone remove it from that zone first
            if obj.name in settings[index]:
                settings[index].remove(obj.name)
                zone.obj = self.config.set_zone_config(zone.obj, settings)
                zone.Updated(zone.obj.name)

        for policy in self.policy_objects:
            settings = policy.getSettings()
            # if this IcmpType is used in a policy remove it from that policy first
            if "icmp_blocks" in settings and obj.name in settings["icmp_blocks"]:
                settings["icmp_blocks"].remove(obj.name)
                policy.obj = self.config.set_policy_object_config_dict(policy.obj, settings)
                policy.Updated(policy.obj.name)

        for icmptype in self.icmptypes:
            if icmptype.obj == obj:
                icmptype.Removed(obj.name)
                icmptype.unregister()
                self.icmptypes.remove(icmptype)
                del icmptype

    @handle_exceptions
    def _addService(self, obj):
        # TODO: check for idx overflow
        config_service = FirewallDConfigService(
            self, self.config, obj, self.service_idx, self.busname,
            "%s/%d" % (config.dbus.DBUS_PATH_CONFIG_SERVICE, self.service_idx))
        self.services.append(config_service)
        self.service_idx += 1
        self.ServiceAdded(obj.name)
        return config_service

    @handle_exceptions
    def _updateService(self, obj):
        for service in self.services:
            if service.obj.name == obj.name and \
                    service.obj.path == obj.path and \
                    service.obj.filename == obj.filename:
                service.obj = obj
                service.Updated(obj.name)

    @handle_exceptions
    def removeService(self, obj):
        index = 5 # see IMPORT_EXPORT_STRUCTURE in class Zone(IO_Object)
        for zone in self.zones:
            settings = zone.getSettings()
            # if this Service is used in a zone remove it from that zone first
            if obj.name in settings[index]:
                settings[index].remove(obj.name)
                zone.obj = self.config.set_zone_config(zone.obj, settings)
                zone.Updated(zone.obj.name)

        for policy in self.policy_objects:
            settings = policy.getSettings()
            # if this Service is used in a policy remove it from that policy first
            if "services" in settings and obj.name in settings["services"]:
                settings["services"].remove(obj.name)
                policy.obj = self.config.set_policy_object_config_dict(policy.obj, settings)
                policy.Updated(policy.obj.name)

        for service in self.services:
            if service.obj == obj:
                service.Removed(obj.name)
                service.unregister()
                self.services.remove(service)
                del service

    @handle_exceptions
    def _addZone(self, obj):
        # TODO: check for idx overflow
        config_zone = FirewallDConfigZone(
            self, self.config, obj, self.zone_idx, self.busname,
            "%s/%d" % (config.dbus.DBUS_PATH_CONFIG_ZONE, self.zone_idx))
        self.zones.append(config_zone)
        self.zone_idx += 1
        self.ZoneAdded(obj.name)
        return config_zone

    @handle_exceptions
    def _updateZone(self, obj):
        for zone in self.zones:
            if zone.obj.name == obj.name and zone.obj.path == obj.path and \
                    zone.obj.filename == obj.filename:
                zone.obj = obj
                zone.Updated(obj.name)

    @handle_exceptions
    def removeZone(self, obj):
        for zone in self.zones:
            if zone.obj == obj:
                zone.Removed(obj.name)
                zone.unregister()
                self.zones.remove(zone)
                del zone

    @handle_exceptions
    def _addPolicy(self, obj):
        # TODO: check for idx overflow
        config_policy = FirewallDConfigPolicy(
            self, self.config, obj, self.policy_object_idx, self.busname,
            "%s/%d" % (config.dbus.DBUS_PATH_CONFIG_POLICY, self.policy_object_idx))
        self.policy_objects.append(config_policy)
        self.policy_object_idx += 1
        self.PolicyAdded(obj.name)
        return config_policy

    @handle_exceptions
    def _updatePolicy(self, obj):
        for policy in self.policy_objects:
            if policy.obj.name == obj.name and policy.obj.path == obj.path and \
                    policy.obj.filename == obj.filename:
                policy.obj = obj
                policy.Updated(obj.name)

    @handle_exceptions
    def removePolicy(self, obj):
        for policy in self.policy_objects:
            if policy.obj == obj:
                policy.Removed(obj.name)
                policy.unregister()
                self.policy_objects.remove(policy)
                del policy

    @handle_exceptions
    def _addIPSet(self, obj):
        # TODO: check for idx overflow
        config_ipset = FirewallDConfigIPSet(
            self, self.config, obj, self.ipset_idx, self.busname,
            "%s/%d" % (config.dbus.DBUS_PATH_CONFIG_IPSET, self.ipset_idx))
        self.ipsets.append(config_ipset)
        self.ipset_idx += 1
        self.IPSetAdded(obj.name)
        return config_ipset

    @handle_exceptions
    def _updateIPSet(self, obj):
        for ipset in self.ipsets:
            if ipset.obj.name == obj.name and ipset.obj.path == obj.path and \
                    ipset.obj.filename == obj.filename:
                ipset.obj = obj
                ipset.Updated(obj.name)

    @handle_exceptions
    def removeIPSet(self, obj):
        for ipset in self.ipsets:
            if ipset.obj == obj:
                ipset.Removed(obj.name)
                ipset.unregister()
                self.ipsets.remove(ipset)
                del ipset

    # access check

    @handle_exceptions
    def _addHelper(self, obj):
        # TODO: check for idx overflow
        config_helper = FirewallDConfigHelper(
            self, self.config, obj, self.helper_idx, self.busname,
            "%s/%d" % (config.dbus.DBUS_PATH_CONFIG_HELPER, self.helper_idx))
        self.helpers.append(config_helper)
        self.helper_idx += 1
        self.HelperAdded(obj.name)
        return config_helper

    @handle_exceptions
    def _updateHelper(self, obj):
        for helper in self.helpers:
            if helper.obj.name == obj.name and helper.obj.path == obj.path and \
                    helper.obj.filename == obj.filename:
                helper.obj = obj
                helper.Updated(obj.name)

    @handle_exceptions
    def removeHelper(self, obj):
        for helper in self.helpers:
            if helper.obj == obj:
                helper.Removed(obj.name)
                helper.unregister()
                self.helpers.remove(helper)
                del helper

    # access check

    @dbus_handle_exceptions
    def accessCheck(self, sender):
        if self.config._fw._state == "FAILED":
            raise FirewallError(errors.RUNNING_BUT_FAILED,
                    "Changing permanent configuration is not allowed while "
                    "firewalld is in FAILED state. The permanent "
                    "configuration must be fixed and then firewalld "
                    "restarted. Try `firewall-offline-cmd --check-config`.")

        if self.config.lockdown_enabled():
            if sender is None:
                log.error("Lockdown not possible, sender not set.")
                return
            bus = dbus.SystemBus()
            context = context_of_sender(bus, sender)
            if self.config.access_check("context", context):
                return
            uid = uid_of_sender(bus, sender)
            if self.config.access_check("uid", uid):
                return
            user = user_of_uid(uid)
            if self.config.access_check("user", user):
                return
            command = command_of_sender(bus, sender)
            if self.config.access_check("command", command):
                return
            raise FirewallError(errors.ACCESS_DENIED, "lockdown is enabled")

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    # P R O P E R T I E S

    @dbus_handle_exceptions
    def _get_property(self, prop):
        if prop not in [ "DefaultZone", "MinimalMark", "CleanupOnExit",
                         "CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
                         "IndividualCalls", "LogDenied", "AutomaticHelpers",
                         "FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
                         "AllowZoneDrifting", "NftablesFlowtable", "NftablesCounters"]:
            raise dbus.exceptions.DBusException(
                "org.freedesktop.DBus.Error.InvalidArgs: "
                "Property '%s' does not exist" % prop)

        value = self.config.get_firewalld_conf().get(prop)

        if prop == "DefaultZone":
            if value is None:
                value = config.FALLBACK_ZONE
            return dbus.String(value)
        elif prop == "MinimalMark":
            if value is None:
                value = config.FALLBACK_MINIMAL_MARK
            else:
                value = int(value)
            return dbus.Int32(value)
        elif prop == "CleanupOnExit":
            if value is None:
                value = "yes" if config.FALLBACK_CLEANUP_ON_EXIT else "no"
            return dbus.String(value)
        elif prop == "CleanupModulesOnExit":
            if value is None:
                value = "yes" if config.FALLBACK_CLEANUP_MODULES_ON_EXIT else "no"
            return dbus.String(value)
        elif prop == "Lockdown":
            if value is None:
                value = "yes" if config.FALLBACK_LOCKDOWN else "no"
            return dbus.String(value)
        elif prop == "IPv6_rpfilter":
            if value is None:
                value = "yes" if config.FALLBACK_IPV6_RPFILTER else "no"
            return dbus.String(value)
        elif prop == "IndividualCalls":
            if value is None:
                value = "yes" if config.FALLBACK_INDIVIDUAL_CALLS else "no"
            return dbus.String(value)
        elif prop == "LogDenied":
            if value is None:
                value = config.FALLBACK_LOG_DENIED
            return dbus.String(value)
        elif prop == "AutomaticHelpers":
            if value is None:
                value = config.FALLBACK_AUTOMATIC_HELPERS
            return dbus.String(value)
        elif prop == "FirewallBackend":
            if value is None:
                value = config.FALLBACK_FIREWALL_BACKEND
            return dbus.String(value)
        elif prop == "FlushAllOnReload":
            if value is None:
                value = "yes" if config.FALLBACK_FLUSH_ALL_ON_RELOAD else "no"
            return dbus.String(value)
        elif prop == "RFC3964_IPv4":
            if value is None:
                value = "yes" if config.FALLBACK_RFC3964_IPV4 else "no"
            return dbus.String(value)
        elif prop == "AllowZoneDrifting":
            if value is None:
                value = "yes" if config.FALLBACK_ALLOW_ZONE_DRIFTING else "no"
            return dbus.String(value)
        elif prop == "NftablesFlowtable":
            if value is None:
                value = config.FALLBACK_NFTABLES_FLOWTABLE
            return dbus.String(value)
        elif prop == "NftablesCounters":
            if value is None:
                value = "yes" if config.FALLBACK_NFTABLES_COUNTERS else "no"
            return dbus.String(value)

    @dbus_handle_exceptions
    def _get_dbus_property(self, prop):
        if prop == "DefaultZone":
            return dbus.String(self._get_property(prop))
        elif prop == "MinimalMark":
            return dbus.Int32(self._get_property(prop))
        elif prop == "CleanupOnExit":
            return dbus.String(self._get_property(prop))
        elif prop == "CleanupModulesOnExit":
            return dbus.String(self._get_property(prop))
        elif prop == "Lockdown":
            return dbus.String(self._get_property(prop))
        elif prop == "IPv6_rpfilter":
            return dbus.String(self._get_property(prop))
        elif prop == "IndividualCalls":
            return dbus.String(self._get_property(prop))
        elif prop == "LogDenied":
            return dbus.String(self._get_property(prop))
        elif prop == "AutomaticHelpers":
            return dbus.String(self._get_property(prop))
        elif prop == "FirewallBackend":
            return dbus.String(self._get_property(prop))
        elif prop == "FlushAllOnReload":
            return dbus.String(self._get_property(prop))
        elif prop == "RFC3964_IPv4":
            return dbus.String(self._get_property(prop))
        elif prop == "AllowZoneDrifting":
            return dbus.String(self._get_property(prop))
        elif prop == "NftablesFlowtable":
            return dbus.String(self._get_property(prop))
        elif prop == "NftablesCounters":
            return dbus.String(self._get_property(prop))
        else:
            raise dbus.exceptions.DBusException(
                "org.freedesktop.DBus.Error.InvalidArgs: "
                "Property '%s' does not exist" % prop)

    @dbus_service_method(dbus.PROPERTIES_IFACE, in_signature='ss',
                         out_signature='v')
    @dbus_handle_exceptions
    def Get(self, interface_name, property_name, sender=None): # pylint: disable=W0613
        # get a property
        interface_name = dbus_to_python(interface_name, str)
        property_name = dbus_to_python(property_name, str)
        log.debug1("config.Get('%s', '%s')", interface_name, property_name)

        if interface_name == config.dbus.DBUS_INTERFACE_CONFIG:
            return self._get_dbus_property(property_name)
        elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                                 config.dbus.DBUS_INTERFACE_CONFIG_POLICIES ]:
            raise dbus.exceptions.DBusException(
                "org.freedesktop.DBus.Error.InvalidArgs: "
                "Property '%s' does not exist" % property_name)
        else:
            raise dbus.exceptions.DBusException(
                "org.freedesktop.DBus.Error.UnknownInterface: "
                "Interface '%s' does not exist" % interface_name)

    @dbus_service_method(dbus.PROPERTIES_IFACE, in_signature='s',
                         out_signature='a{sv}')
    @dbus_handle_exceptions
    def GetAll(self, interface_name, sender=None): # pylint: disable=W0613
        interface_name = dbus_to_python(interface_name, str)
        log.debug1("config.GetAll('%s')", interface_name)

        ret = { }
        if interface_name == config.dbus.DBUS_INTERFACE_CONFIG:
            for x in [ "DefaultZone", "MinimalMark", "CleanupOnExit",
                       "CleanupModulesOnExit", "Lockdown", "IPv6_rpfilter",
                       "IndividualCalls", "LogDenied", "AutomaticHelpers",
                       "FirewallBackend", "FlushAllOnReload", "RFC3964_IPv4",
                       "AllowZoneDrifting", "NftablesFlowtable",  "NftablesCounters"]:
                ret[x] = self._get_property(x)
        elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                                 config.dbus.DBUS_INTERFACE_CONFIG_POLICIES ]:
            pass
        else:
            raise dbus.exceptions.DBusException(
                "org.freedesktop.DBus.Error.UnknownInterface: "
                "Interface '%s' does not exist" % interface_name)

        return dbus.Dictionary(ret, signature="sv")

    @dbus_polkit_require_auth(config.dbus.PK_ACTION_CONFIG)
    @dbus_service_method(dbus.PROPERTIES_IFACE, in_signature='ssv')
    @dbus_handle_exceptions
    def Set(self, interface_name, property_name, new_value, sender=None):
        interface_name = dbus_to_python(interface_name, str)
        property_name = dbus_to_python(property_name, str)
        new_value = dbus_to_python(new_value)
        log.debug1("config.Set('%s', '%s', '%s')", interface_name,
                   property_name, new_value)
        self.accessCheck(sender)

        if interface_name == config.dbus.DBUS_INTERFACE_CONFIG:
            if property_name in [ "CleanupOnExit",
                                  "CleanupModulesOnExit", "Lockdown",
                                  "IPv6_rpfilter", "IndividualCalls",
                                  "LogDenied",
                                  "FirewallBackend", "FlushAllOnReload",
                                  "RFC3964_IPv4", "NftablesFlowtable",
                                  "NftablesCounters"]:
                if property_name in [ "CleanupOnExit", "CleanupModulesOnExit",
                                      "Lockdown", "IPv6_rpfilter",
                                      "IndividualCalls", "FlushAllOnReload",
                                      "RFC3964_IPv4", "NftablesCounters"]:
                    if new_value.lower() not in [ "yes", "no",
                                                  "true", "false" ]:
                        raise FirewallError(errors.INVALID_VALUE,
                                            "'%s' for %s" % \
                                            (new_value, property_name))
                elif property_name == "LogDenied":
                    if new_value not in config.LOG_DENIED_VALUES:
                        raise FirewallError(errors.INVALID_VALUE,
                                            "'%s' for %s" % \
                                            (new_value, property_name))
                elif property_name == "FirewallBackend":
                    if new_value not in config.FIREWALL_BACKEND_VALUES:
                        raise FirewallError(errors.INVALID_VALUE,
                                            "'%s' for %s" % \
                                            (new_value, property_name))
                else:
                    raise dbus.exceptions.DBusException(
                        "org.freedesktop.DBus.Error.InvalidArgs: "
                        "Property '%s' does not exist" % property_name)

                self.config.get_firewalld_conf().set(property_name, new_value)
                self.config.get_firewalld_conf().write()
                self.PropertiesChanged(interface_name,
                                       { property_name: new_value }, [ ])
            elif property_name in ["MinimalMark", "AutomaticHelpers", "AllowZoneDrifting"]:
                # deprecated fields. Ignore setting them.
                pass
            else:
                raise dbus.exceptions.DBusException(
                    "org.freedesktop.DBus.Error.InvalidArgs: "
                    "Property '%s' does not exist" % property_name)
        elif interface_name in [ config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                                 config.dbus.DBUS_INTERFACE_CONFIG_POLICIES ]:
            raise dbus.exceptions.DBusException(
                "org.freedesktop.DBus.Error.InvalidArgs: "
                "Property '%s' does not exist" % property_name)
        else:
            raise dbus.exceptions.DBusException(
                "org.freedesktop.DBus.Error.UnknownInterface: "
                "Interface '%s' does not exist" % interface_name)

    @dbus.service.signal(dbus.PROPERTIES_IFACE, signature='sa{sv}as')
    def PropertiesChanged(self, interface_name, changed_properties,
                          invalidated_properties):
        interface_name = dbus_to_python(interface_name, str)
        changed_properties = dbus_to_python(changed_properties)
        invalidated_properties = dbus_to_python(invalidated_properties)
        log.debug1("config.PropertiesChanged('%s', '%s', '%s')",
                   interface_name, changed_properties, invalidated_properties)

    @dbus_polkit_require_auth(config.dbus.PK_ACTION_INFO)
    @dbus_service_method(dbus.INTROSPECTABLE_IFACE, out_signature='s')
    @dbus_handle_exceptions
    def Introspect(self, sender=None): # pylint: disable=W0613
        log.debug2("config.Introspect()")

        data = super(FirewallDConfig, self).Introspect(self.path,
                                                       self.busname.get_bus())
        data = dbus_introspection_add_properties(
                    self, data, config.dbus.DBUS_INTERFACE_CONFIG)

        for interface in [config.dbus.DBUS_INTERFACE_CONFIG_DIRECT]:
            data = dbus_introspection_add_deprecated(
                        self, data, interface,
                        dbus_service_method_deprecated().deprecated,
                        dbus_service_signal_deprecated().deprecated)

        return data

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    # policies

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         out_signature=LockdownWhitelist.DBUS_SIGNATURE)
    @dbus_handle_exceptions
    def getLockdownWhitelist(self, sender=None): # pylint: disable=W0613
        log.debug1("config.policies.getLockdownWhitelist()")
        return self.config.get_policies().lockdown_whitelist.export_config()

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature=LockdownWhitelist.DBUS_SIGNATURE)
    @dbus_handle_exceptions
    def setLockdownWhitelist(self, settings, sender=None): # pylint: disable=W0613
        log.debug1("config.policies.setLockdownWhitelist(...)")
        settings = dbus_to_python(settings)
        self.config.get_policies().lockdown_whitelist.import_config(settings, {})
        self.config.get_policies().lockdown_whitelist.write()
        self.LockdownWhitelistUpdated()

    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES)
    @dbus_handle_exceptions
    def LockdownWhitelistUpdated(self):
        log.debug1("config.policies.LockdownWhitelistUpdated()")

    # command

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s')
    @dbus_handle_exceptions
    def addLockdownWhitelistCommand(self, command, sender=None):
        command = dbus_to_python(command)
        log.debug1("config.policies.addLockdownWhitelistCommand('%s')", command)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if command in settings[0]:
            raise FirewallError(errors.ALREADY_ENABLED, command)
        settings[0].append(command)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s')
    @dbus_handle_exceptions
    def removeLockdownWhitelistCommand(self, command, sender=None):
        command = dbus_to_python(command)
        log.debug1("config.policies.removeLockdownWhitelistCommand('%s')",
                   command)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if command not in settings[0]:
            raise FirewallError(errors.NOT_ENABLED, command)
        settings[0].remove(command)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s', out_signature='b')
    @dbus_handle_exceptions
    def queryLockdownWhitelistCommand(self, command, sender=None): # pylint: disable=W0613
        command = dbus_to_python(command)
        log.debug1("config.policies.queryLockdownWhitelistCommand('%s')",
                   command)
        return command in self.getLockdownWhitelist()[0]

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         out_signature='as')
    @dbus_handle_exceptions
    def getLockdownWhitelistCommands(self, sender=None): # pylint: disable=W0613
        log.debug1("config.policies.getLockdownWhitelistCommands()")
        return self.getLockdownWhitelist()[0]

    # context

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s')
    @dbus_handle_exceptions
    def addLockdownWhitelistContext(self, context, sender=None):
        context = dbus_to_python(context)
        log.debug1("config.policies.addLockdownWhitelistContext('%s')", context)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if context in settings[1]:
            raise FirewallError(errors.ALREADY_ENABLED, context)
        settings[1].append(context)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s')
    @dbus_handle_exceptions
    def removeLockdownWhitelistContext(self, context, sender=None):
        context = dbus_to_python(context)
        log.debug1("config.policies.removeLockdownWhitelistContext('%s')",
                   context)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if context not in settings[1]:
            raise FirewallError(errors.NOT_ENABLED, context)
        settings[1].remove(context)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s', out_signature='b')
    @dbus_handle_exceptions
    def queryLockdownWhitelistContext(self, context, sender=None): # pylint: disable=W0613
        context = dbus_to_python(context)
        log.debug1("config.policies.queryLockdownWhitelistContext('%s')",
                   context)
        return context in self.getLockdownWhitelist()[1]

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         out_signature='as')
    @dbus_handle_exceptions
    def getLockdownWhitelistContexts(self, sender=None): # pylint: disable=W0613
        log.debug1("config.policies.getLockdownWhitelistContexts()")
        return self.getLockdownWhitelist()[1]

    # user

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s')
    @dbus_handle_exceptions
    def addLockdownWhitelistUser(self, user, sender=None):
        user = dbus_to_python(user)
        log.debug1("config.policies.addLockdownWhitelistUser('%s')", user)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if user in settings[2]:
            raise FirewallError(errors.ALREADY_ENABLED, user)
        settings[2].append(user)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s')
    @dbus_handle_exceptions
    def removeLockdownWhitelistUser(self, user, sender=None):
        user = dbus_to_python(user)
        log.debug1("config.policies.removeLockdownWhitelistUser('%s')", user)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if user not in settings[2]:
            raise FirewallError(errors.NOT_ENABLED, user)
        settings[2].remove(user)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='s', out_signature='b')
    @dbus_handle_exceptions
    def queryLockdownWhitelistUser(self, user, sender=None): # pylint: disable=W0613
        user = dbus_to_python(user)
        log.debug1("config.policies.queryLockdownWhitelistUser('%s')", user)
        return user in self.getLockdownWhitelist()[2]

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         out_signature='as')
    @dbus_handle_exceptions
    def getLockdownWhitelistUsers(self, sender=None): # pylint: disable=W0613
        log.debug1("config.policies.getLockdownWhitelistUsers()")
        return self.getLockdownWhitelist()[2]

    # uid

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='i')
    @dbus_handle_exceptions
    def addLockdownWhitelistUid(self, uid, sender=None):
        uid = dbus_to_python(uid)
        log.debug1("config.policies.addLockdownWhitelistUid(%d)", uid)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if uid in settings[3]:
            raise FirewallError(errors.ALREADY_ENABLED, uid)
        settings[3].append(uid)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='i')
    @dbus_handle_exceptions
    def removeLockdownWhitelistUid(self, uid, sender=None):
        uid = dbus_to_python(uid)
        log.debug1("config.policies.removeLockdownWhitelistUid(%d)", uid)
        self.accessCheck(sender)
        settings = list(self.getLockdownWhitelist())
        if uid not in settings[3]:
            raise FirewallError(errors.NOT_ENABLED, uid)
        settings[3].remove(uid)
        self.setLockdownWhitelist(settings)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         in_signature='i', out_signature='b')
    @dbus_handle_exceptions
    def queryLockdownWhitelistUid(self, uid, sender=None): # pylint: disable=W0613
        uid = dbus_to_python(uid)
        log.debug1("config.policies.queryLockdownWhitelistUid(%d)", uid)
        return uid in self.getLockdownWhitelist()[3]

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_POLICIES,
                         out_signature='ai')
    @dbus_handle_exceptions
    def getLockdownWhitelistUids(self, sender=None): # pylint: disable=W0613
        log.debug1("config.policies.getLockdownWhitelistUids()")
        return self.getLockdownWhitelist()[3]

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    # I P S E T S

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='ao')
    @dbus_handle_exceptions
    def listIPSets(self, sender=None): # pylint: disable=W0613
        """list ipsets objects paths
        """
        log.debug1("config.listIPSets()")
        return self.ipsets

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='as')
    @dbus_handle_exceptions
    def getIPSetNames(self, sender=None): # pylint: disable=W0613
        """get ipset names
        """
        log.debug1("config.getIPSetNames()")
        ipsets = [ ]
        for obj in self.ipsets:
            ipsets.append(obj.obj.name)
        return sorted(ipsets)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='o')
    @dbus_handle_exceptions
    def getIPSetByName(self, ipset, sender=None): # pylint: disable=W0613
        """object path of ipset with given name
        """
        ipset = dbus_to_python(ipset, str)
        log.debug1("config.getIPSetByName('%s')", ipset)
        for obj in self.ipsets:
            if obj.obj.name == ipset:
                return obj
        raise FirewallError(errors.INVALID_IPSET, ipset)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature='s'+IPSet.DBUS_SIGNATURE,
                         out_signature='o')
    @dbus_handle_exceptions
    def addIPSet(self, ipset, settings, sender=None):
        """add ipset with given name and settings
        """
        ipset = dbus_to_python(ipset, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addIPSet('%s')", ipset)
        self.accessCheck(sender)
        obj = self.config.new_ipset(ipset, settings)
        config_ipset = self._addIPSet(obj)
        return config_ipset

    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG, signature='s')
    @dbus_handle_exceptions
    def IPSetAdded(self, ipset):
        ipset = dbus_to_python(ipset, str)
        log.debug1("config.IPSetAdded('%s')" % (ipset))

    # I C M P T Y P E S

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='ao')
    @dbus_handle_exceptions
    def listIcmpTypes(self, sender=None): # pylint: disable=W0613
        """list icmptypes objects paths
        """
        log.debug1("config.listIcmpTypes()")
        return self.icmptypes

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='as')
    @dbus_handle_exceptions
    def getIcmpTypeNames(self, sender=None): # pylint: disable=W0613
        """get icmptype names
        """
        log.debug1("config.getIcmpTypeNames()")
        icmptypes = [ ]
        for obj in self.icmptypes:
            icmptypes.append(obj.obj.name)
        return sorted(icmptypes)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='o')
    @dbus_handle_exceptions
    def getIcmpTypeByName(self, icmptype, sender=None): # pylint: disable=W0613
        """object path of icmptype with given name
        """
        icmptype = dbus_to_python(icmptype, str)
        log.debug1("config.getIcmpTypeByName('%s')", icmptype)
        for obj in self.icmptypes:
            if obj.obj.name == icmptype:
                return obj
        raise FirewallError(errors.INVALID_ICMPTYPE, icmptype)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature='s'+IcmpType.DBUS_SIGNATURE,
                         out_signature='o')
    @dbus_handle_exceptions
    def addIcmpType(self, icmptype, settings, sender=None):
        """add icmptype with given name and settings
        """
        icmptype = dbus_to_python(icmptype, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addIcmpType('%s')", icmptype)
        self.accessCheck(sender)
        obj = self.config.new_icmptype(icmptype, settings)
        config_icmptype = self._addIcmpType(obj)
        return config_icmptype

    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG, signature='s')
    @dbus_handle_exceptions
    def IcmpTypeAdded(self, icmptype):
        log.debug1("config.IcmpTypeAdded('%s')" % (icmptype))

    # S E R V I C E S

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='ao')
    @dbus_handle_exceptions
    def listServices(self, sender=None): # pylint: disable=W0613
        """list services objects paths
        """
        log.debug1("config.listServices()")
        return self.services

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='as')
    @dbus_handle_exceptions
    def getServiceNames(self, sender=None): # pylint: disable=W0613
        """get service names
        """
        log.debug1("config.getServiceNames()")
        services = [ ]
        for obj in self.services:
            services.append(obj.obj.name)
        return sorted(services)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='o')
    @dbus_handle_exceptions
    def getServiceByName(self, service, sender=None): # pylint: disable=W0613
        """object path of service with given name
        """
        service = dbus_to_python(service, str)
        log.debug1("config.getServiceByName('%s')", service)
        for obj in self.services:
            if obj.obj.name == service:
                return obj
        raise FirewallError(errors.INVALID_SERVICE, service)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature='s(sssa(ss)asa{ss}asa(ss))',
                         out_signature='o')
    @dbus_handle_exceptions
    def addService(self, service, settings, sender=None):
        """add service with given name and settings
        """
        service = dbus_to_python(service, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addService('%s')", service)
        self.accessCheck(sender)
        obj = self.config.new_service(service, settings)
        config_service = self._addService(obj)
        return config_service

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature='sa{sv}',
                         out_signature='o')
    @dbus_handle_exceptions
    def addService2(self, service, settings, sender=None):
        """add service with given name and settings
        """
        service = dbus_to_python(service, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addService2('%s')", service)
        self.accessCheck(sender)
        obj = self.config.new_service_dict(service, settings)
        config_service = self._addService(obj)
        return config_service

    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG, signature='s')
    @dbus_handle_exceptions
    def ServiceAdded(self, service):
        log.debug1("config.ServiceAdded('%s')" % (service))

    # Z O N E S

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='ao')
    @dbus_handle_exceptions
    def listZones(self, sender=None): # pylint: disable=W0613
        """list zones objects paths
        """
        log.debug1("config.listZones()")
        return self.zones

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='as')
    @dbus_handle_exceptions
    def getZoneNames(self, sender=None): # pylint: disable=W0613
        """get zone names
        """
        log.debug1("config.getZoneNames()")
        zones = [ ]
        for obj in self.zones:
            zones.append(obj.obj.name)
        return sorted(zones)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='o')
    @dbus_handle_exceptions
    def getZoneByName(self, zone, sender=None): # pylint: disable=W0613
        """object path of zone with given name
        """
        zone = dbus_to_python(zone, str)
        log.debug1("config.getZoneByName('%s')", zone)
        for obj in self.zones:
            if obj.obj.name == zone:
                return obj
        raise FirewallError(errors.INVALID_ZONE, zone)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='s')
    @dbus_handle_exceptions
    def getZoneOfInterface(self, iface, sender=None): # pylint: disable=W0613
        """name of zone the given interface belongs to
        """
        iface = dbus_to_python(iface, str)
        log.debug1("config.getZoneOfInterface('%s')", iface)
        ret = []
        for obj in self.zones:
            if iface in obj.obj.interfaces:
                ret.append(obj.obj.name)
        if len(ret) > 1:
            # Even it shouldn't happen, it's actually possible that
            # the same interface is in several zone XML files
            return " ".join(ret) + \
                "  (ERROR: interface '%s' is in %s zone XML files, can be only in one)" % \
                (iface, len(ret))
        return ret[0] if ret else ""

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='s')
    @dbus_handle_exceptions
    def getZoneOfSource(self, source, sender=None): # pylint: disable=W0613
        """name of zone the given source belongs to
        """
        source = dbus_to_python(source, str)
        log.debug1("config.getZoneOfSource('%s')", source)
        ret = []
        for obj in self.zones:
            if source in obj.obj.sources:
                ret.append(obj.obj.name)
        if len(ret) > 1:
            # Even it shouldn't happen, it's actually possible that
            # the same source is in several zone XML files
            return " ".join(ret) + \
                "  (ERROR: source '%s' is in %s zone XML files, can be only in one)" % \
                (source, len(ret))
        return ret[0] if ret else ""

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature="s(sssbsasa(ss)asba(ssss)asasasasa(ss)b)",
                         out_signature='o')
    @dbus_handle_exceptions
    def addZone(self, zone, settings, sender=None):
        """add zone with given name and settings
        """
        zone = dbus_to_python(zone, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addZone('%s')", zone)
        self.accessCheck(sender)
        if settings[4] == "default":
            # convert to list, fix target, convert back to tuple
            _settings = list(settings)
            _settings[4] = DEFAULT_ZONE_TARGET
            settings = tuple(_settings)
        obj = self.config.new_zone(zone, settings)
        config_zone = self._addZone(obj)
        return config_zone

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature="sa{sv}",
                         out_signature='o')
    @dbus_handle_exceptions
    def addZone2(self, zone, settings, sender=None):
        """add zone with given name and settings
        """
        zone = dbus_to_python(zone, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addZone('%s')", zone)
        self.accessCheck(sender)
        if "target" in settings and settings["target"] == "default":
            settings["target"] = DEFAULT_ZONE_TARGET
        obj = self.config.new_zone_dict(zone, settings)
        config_zone = self._addZone(obj)
        return config_zone

    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG, signature='s')
    @dbus_handle_exceptions
    def ZoneAdded(self, zone):
        log.debug1("config.ZoneAdded('%s')" % (zone))

    # policies

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='ao')
    @dbus_handle_exceptions
    def listPolicies(self, sender=None):
        """list policies objects paths
        """
        log.debug1("config.listPolicies()")
        return self.policy_objects

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='as')
    @dbus_handle_exceptions
    def getPolicyNames(self, sender=None):
        """get policy names
        """
        log.debug1("config.getPolicyNames()")
        policies = [ ]
        for obj in self.policy_objects:
            policies.append(obj.obj.name)
        return sorted(policies)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='o')
    @dbus_handle_exceptions
    def getPolicyByName(self, policy, sender=None):
        """object path of policy with given name
        """
        policy = dbus_to_python(policy, str)
        log.debug1("config.getPolicyByName('%s')", policy)
        for obj in self.policy_objects:
            if obj.obj.name == policy:
                return obj
        raise FirewallError(errors.INVALID_POLICY, policy)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature="sa{sv}",
                         out_signature='o')
    @dbus_handle_exceptions
    def addPolicy(self, policy, settings, sender=None):
        """add policy with given name and settings
        """
        policy = dbus_to_python(policy, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addPolicy('%s')", policy)
        self.accessCheck(sender)
        obj = self.config.new_policy_object_dict(policy, settings)
        config_policy = self._addPolicy(obj)
        return config_policy

    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG, signature='s')
    @dbus_handle_exceptions
    def PolicyAdded(self, policy):
        log.debug1("config.PolicyAdded('%s')" % (policy))

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #

    # H E L P E R S

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='ao')
    @dbus_handle_exceptions
    def listHelpers(self, sender=None): # pylint: disable=W0613
        """list helpers objects paths
        """
        log.debug1("config.listHelpers()")
        return self.helpers

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, out_signature='as')
    @dbus_handle_exceptions
    def getHelperNames(self, sender=None): # pylint: disable=W0613
        """get helper names
        """
        log.debug1("config.getHelperNames()")
        helpers = [ ]
        for obj in self.helpers:
            helpers.append(obj.obj.name)
        return sorted(helpers)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG, in_signature='s',
                         out_signature='o')
    @dbus_handle_exceptions
    def getHelperByName(self, helper, sender=None): # pylint: disable=W0613
        """object path of helper with given name
        """
        helper = dbus_to_python(helper, str)
        log.debug1("config.getHelperByName('%s')", helper)
        for obj in self.helpers:
            if obj.obj.name == helper:
                return obj
        raise FirewallError(errors.INVALID_HELPER, helper)

    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG,
                         in_signature='s'+Helper.DBUS_SIGNATURE,
                         out_signature='o')
    @dbus_handle_exceptions
    def addHelper(self, helper, settings, sender=None):
        """add helper with given name and settings
        """
        helper = dbus_to_python(helper, str)
        settings = dbus_to_python(settings)
        log.debug1("config.addHelper('%s')", helper)
        self.accessCheck(sender)
        obj = self.config.new_helper(helper, settings)
        config_helper = self._addHelper(obj)
        return config_helper

    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG, signature='s')
    @dbus_handle_exceptions
    def HelperAdded(self, helper):
        helper = dbus_to_python(helper, str)
        log.debug1("config.HelperAdded('%s')" % (helper))

    # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
    # DIRECT

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         out_signature=Direct.DBUS_SIGNATURE)
    @dbus_handle_exceptions
    def getSettings(self, sender=None): # pylint: disable=W0613
        # returns list ipv, table, list of chains
        log.debug1("config.direct.getSettings()")
        return self.config.get_direct().export_config()

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature=Direct.DBUS_SIGNATURE)
    @dbus_handle_exceptions
    def update(self, settings, sender=None): # pylint: disable=W0613
        # returns list ipv, table, list of chains
        log.debug1("config.direct.update()")
        settings = dbus_to_python(settings)
        self.config.get_direct().import_config(settings, {})
        self.config.get_direct().write()
        self.Updated()

    @dbus_service_signal_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus.service.signal(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_handle_exceptions
    def Updated(self):
        log.debug1("config.direct.Updated()")

    # chain

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sss')
    @dbus_handle_exceptions
    def addChain(self, ipv, table, chain, sender=None):
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        log.debug1("config.direct.addChain('%s', '%s', '%s')" % \
                   (ipv, table, chain))
        self.accessCheck(sender)
        idx = tuple((ipv, table, chain))
        settings = list(self.getSettings())
        if idx in settings[0]:
            raise FirewallError(errors.ALREADY_ENABLED,
                                "chain '%s' already is in '%s:%s'" % \
                                (chain, ipv, table))
        settings[0].append(idx)
        self.update(settings)

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sss')
    @dbus_handle_exceptions
    def removeChain(self, ipv, table, chain, sender=None):
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        log.debug1("config.direct.removeChain('%s', '%s', '%s')" % \
                   (ipv, table, chain))
        self.accessCheck(sender)
        idx = tuple((ipv, table, chain))
        settings = list(self.getSettings())
        if idx not in settings[0]:
            raise FirewallError(errors.NOT_ENABLED,
                                "chain '%s' is not in '%s:%s'" % (chain, ipv,
                                                                  table))
        settings[0].remove(idx)
        self.update(settings)

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sss', out_signature='b')
    @dbus_handle_exceptions
    def queryChain(self, ipv, table, chain, sender=None): # pylint: disable=W0613
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        log.debug1("config.direct.queryChain('%s', '%s', '%s')" % \
                   (ipv, table, chain))
        idx = tuple((ipv, table, chain))
        return idx in self.getSettings()[0]

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='ss', out_signature='as')
    @dbus_handle_exceptions
    def getChains(self, ipv, table, sender=None): # pylint: disable=W0613
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        log.debug1("config.direct.getChains('%s', '%s')" % (ipv, table))
        ret = [ ]
        for idx in self.getSettings()[0]:
            if idx[0] == ipv and idx[1] == table:
                ret.append(idx[2])
        return ret

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='', out_signature='a(sss)')
    @dbus_handle_exceptions
    def getAllChains(self, sender=None): # pylint: disable=W0613
        log.debug1("config.direct.getAllChains()")
        return self.getSettings()[0]

    # rule

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sssias')
    @dbus_handle_exceptions
    def addRule(self, ipv, table, chain, priority, args, sender=None): # pylint: disable=R0913
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        priority = dbus_to_python(priority)
        args = dbus_to_python(args)
        log.debug1("config.direct.addRule('%s', '%s', '%s', %d, '%s')" % \
                   (ipv, table, chain, priority, "','".join(args)))
        self.accessCheck(sender)
        idx = (ipv, table, chain, priority, args)
        settings = list(self.getSettings())
        if idx in settings[1]:
            raise FirewallError(errors.ALREADY_ENABLED,
                                "rule '%s' already is in '%s:%s:%s'" % \
                                (args, ipv, table, chain))
        settings[1].append(idx)
        self.update(tuple(settings))

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sssias')
    @dbus_handle_exceptions
    def removeRule(self, ipv, table, chain, priority, args, sender=None): # pylint: disable=R0913
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        priority = dbus_to_python(priority)
        args = dbus_to_python(args)
        log.debug1("config.direct.removeRule('%s', '%s', '%s', %d, '%s')" % \
                   (ipv, table, chain, priority, "','".join(args)))
        self.accessCheck(sender)
        idx = (ipv, table, chain, priority, args)
        settings = list(self.getSettings())
        if idx not in settings[1]:
            raise FirewallError(errors.NOT_ENABLED,
                                "rule '%s' is not in '%s:%s:%s'" % \
                                (args, ipv, table, chain))
        settings[1].remove(idx)
        self.update(tuple(settings))

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sssias', out_signature='b')
    @dbus_handle_exceptions
    def queryRule(self, ipv, table, chain, priority, args, sender=None): # pylint: disable=W0613,R0913
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        priority = dbus_to_python(priority)
        args = dbus_to_python(args)
        log.debug1("config.direct.queryRule('%s', '%s', '%s', %d, '%s')" % \
                   (ipv, table, chain, priority, "','".join(args)))
        idx = (ipv, table, chain, priority, args)
        return idx in self.getSettings()[1]

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sss')
    @dbus_handle_exceptions
    def removeRules(self, ipv, table, chain, sender=None):
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        log.debug1("config.direct.removeRules('%s', '%s', '%s')" % \
                   (ipv, table, chain, ))
        self.accessCheck(sender)
        settings = list(self.getSettings())
        for rule in settings[1][:]:
            if (ipv, table, chain) == (rule[0], rule[1], rule[2]):
                settings[1].remove(rule)
        self.update(tuple(settings))

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sss', out_signature='a(ias)')
    @dbus_handle_exceptions
    def getRules(self, ipv, table, chain, sender=None): # pylint: disable=W0613
        ipv = dbus_to_python(ipv)
        table = dbus_to_python(table)
        chain = dbus_to_python(chain)
        log.debug1("config.direct.getRules('%s', '%s', '%s')" % \
                   (ipv, table, chain))
        ret = [ ]
        for idx in self.getSettings()[1]:
            if idx[0] == ipv and idx[1] == table and idx[2] == chain:
                ret.append((idx[3], idx[4]))
        return ret

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='', out_signature='a(sssias)')
    @dbus_handle_exceptions
    def getAllRules(self, sender=None): # pylint: disable=W0613
        log.debug1("config.direct.getAllRules()")
        return self.getSettings()[1]

    # passthrough

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sas')
    @dbus_handle_exceptions
    def addPassthrough(self, ipv, args, sender=None):
        ipv = dbus_to_python(ipv)
        args = dbus_to_python(args)
        log.debug1("config.direct.addPassthrough('%s', '%s')" % \
                   (ipv, "','".join(args)))
        self.accessCheck(sender)
        idx = (ipv, args)
        settings = list(self.getSettings())
        if idx in settings[2]:
            raise FirewallError(errors.ALREADY_ENABLED,
                                "passthrough '%s', '%s'" % (ipv, args))
        settings[2].append(idx)
        self.update(settings)

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sas')
    @dbus_handle_exceptions
    def removePassthrough(self, ipv, args, sender=None):
        ipv = dbus_to_python(ipv)
        args = dbus_to_python(args)
        log.debug1("config.direct.removePassthrough('%s', '%s')" % \
                   (ipv, "','".join(args)))
        self.accessCheck(sender)
        idx = (ipv, args)
        settings = list(self.getSettings())
        if idx not in settings[2]:
            raise FirewallError(errors.NOT_ENABLED,
                                "passthrough '%s', '%s'" % (ipv, args))
        settings[2].remove(idx)
        self.update(settings)

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='sas', out_signature='b')
    @dbus_handle_exceptions
    def queryPassthrough(self, ipv, args, sender=None): # pylint: disable=W0613
        ipv = dbus_to_python(ipv)
        args = dbus_to_python(args)
        log.debug1("config.direct.queryPassthrough('%s', '%s')" % \
                   (ipv, "','".join(args)))
        idx = (ipv, args)
        return idx in self.getSettings()[2]

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         in_signature='s', out_signature='aas')
    @dbus_handle_exceptions
    def getPassthroughs(self, ipv, sender=None): # pylint: disable=W0613
        ipv = dbus_to_python(ipv)
        log.debug1("config.direct.getPassthroughs('%s')" % (ipv))
        ret = [ ]
        for idx in self.getSettings()[2]:
            if idx[0] == ipv:
                ret.append(idx[1])
        return ret

    @dbus_service_method_deprecated(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT)
    @dbus_service_method(config.dbus.DBUS_INTERFACE_CONFIG_DIRECT,
                         out_signature='a(sas)')
    @dbus_handle_exceptions
    def getAllPassthroughs(self, sender=None): # pylint: disable=W0613
        log.debug1("config.direct.getAllPassthroughs()")
        return self.getSettings()[2]