Add a new cnfvar2 API
authorSamir Aguiar <samir.aguiar@intra2net.com>
Sun, 15 Aug 2021 17:43:00 +0000 (14:43 -0300)
committerChristian Herdtweck <christian.herdtweck@intra2net.com>
Mon, 4 Apr 2022 12:24:59 +0000 (14:24 +0200)
It is composed of three main modules:
- binary: wrappers around the get_ and set_cnf binaries
- model: provides Cnf and CnfList classes, which are Python
representation of cnfvars with dozens of facility methods to
manipulate them
- store: classes implemented using the repository pattern that
run queries against the arnied API or the binaries using the above
module and return CnfList instances

setup.py
src/cnfvar/__init__.py [new file with mode: 0644]
src/cnfvar/binary.py [new file with mode: 0644]
src/cnfvar/model.py [new file with mode: 0644]
src/cnfvar/store.py [new file with mode: 0644]
test/cnfvar/__init__.py [new file with mode: 0644]
test/cnfvar/cnfs.cnf [new file with mode: 0644]
test/cnfvar/test_binary.py [new file with mode: 0644]
test/cnfvar/test_model.py [new file with mode: 0644]
test/cnfvar/test_store.py [new file with mode: 0644]
test/test_arnied_api.py

index 6fca91c..0a9293e 100644 (file)
--- a/setup.py
+++ b/setup.py
@@ -18,7 +18,7 @@
 # This exception does not invalidate any other reasons why a work based
 # on this file might be covered by the GNU General Public License.
 #
-# Copyright (c) 2016-2018 Intra2net AG <info@intra2net.com>
+# Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
 
 # For packaging pyi2ncommon into an rpm, use make_dist.py
 
@@ -33,7 +33,7 @@ setup(name='pyi2ncommon',
       author='Intra2net AG',
       author_email='info@intra2net.com',
       url='http://www.intra2net.com',
-      packages=['pyi2ncommon', 'pyi2ncommon.cnfline'],
+      packages=['pyi2ncommon', 'pyi2ncommon.cnfline', 'pyi2ncommon.cnfvar'],
       package_dir={'pyi2ncommon': 'src'},
       license_files=('COPYING.GPL', 'Linking-Exception.txt'),
       license='GPLv2 + linking exception',
diff --git a/src/cnfvar/__init__.py b/src/cnfvar/__init__.py
new file mode 100644 (file)
index 0000000..dd9a20e
--- /dev/null
@@ -0,0 +1,6 @@
+from .model import Cnf, CnfList
+from .binary import CnfBinary
+from .store import CnfStore, BinaryCnfStore, CommitException
+
+__all__ = ["Cnf, CnfList, CnfBinary", "CnfStore",
+           "BinaryCnfStore", "CommitException"]
diff --git a/src/cnfvar/binary.py b/src/cnfvar/binary.py
new file mode 100644 (file)
index 0000000..3d71c19
--- /dev/null
@@ -0,0 +1,231 @@
+# The software in this package is distributed under the GNU General
+# Public License version 2 (with a special exception described below).
+#
+# A copy of GNU General Public License (GPL) is included in this distribution,
+# in the file COPYING.GPL.
+#
+# As a special exception, if other files instantiate templates or use macros
+# or inline functions from this file, or you compile this file and link it
+# with other works to produce a work based on this file, this file
+# does not by itself cause the resulting work to be covered
+# by the GNU General Public License.
+#
+# However the source code for this file must still be made available
+# in accordance with section (3) of the GNU General Public License.
+#
+# This exception does not invalidate any other reasons why a work based
+# on this file might be covered by the GNU General Public License.
+#
+# Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
+
+"""
+binary: wrappers around binaries that work with the CNF store.
+
+Featuring
+- CnfBinary: stateless class that can be used to invoke the different binaries
+in a Python-friendly way.
+
+.. note:: It is written as a class on purpose for it to be easily extended to
+support invoking non-local binaries, but methods are all class methods since
+the class is stateless.
+
+.. codeauthor:: Intra2net
+"""
+
+import subprocess
+import logging
+import shlex
+import html
+import re
+
+log = logging.getLogger("pyi2ncommon.cnfvar.store")
+
+#: default get_cnf binary
+BIN_GET_CNF = "/usr/intranator/bin/get_cnf"
+#: default set_cnf binary
+BIN_SET_CNF = "/usr/intranator/bin/set_cnf"
+#: encoding used by the get_cnf and set_cnf binaries
+ENCODING = "latin1"
+
+
+class CnfBinary(object):
+    """Provide wrappers around the multiple binaries to handle the CNF store."""
+
+    @classmethod
+    def run_cmd(cls, cmd, cmd_input=None, ignore_errors=False, timeout=60, encoding=ENCODING):
+        """
+        Run commands on the local machine with input via stdin.
+
+        :param cmd: command to run
+        :type cmd: str or list
+        :param str cmd_input: input to give to the command
+        :param bool ignore_errors: whether to ignore the exit code or raise if not zero
+        :param int timeout: amount of seconds to wait for the command to complete
+        :param str encoding: encoding to use (pass latin1 when not working with JSON)
+        :returns: command result
+        :rtype: :py:class:`subprocess.CompletedProcess`
+        """
+        if isinstance(cmd, str):
+            cmd = shlex.split(cmd)
+
+        log.debug("Running binary cmd `%s` with input:\n%s",
+                  ' '.join(cmd), cmd_input)
+        retval = subprocess.run(cmd, input=cmd_input, check=not ignore_errors,
+                                capture_output=True, encoding=encoding, timeout=timeout)
+        log.debug("Command exited with \n"
+                  "\treturncode=%d\n"
+                  "\tstderr=%s\n"
+                  "\tstdout=%s", retval.returncode, retval.stderr, retval.stdout)
+        return retval
+
+    @classmethod
+    def get_cnf(cls, name=None, instance=None, no_children=False,
+                as_json=False):
+        """
+        Wrapper around the `get_cnf` binary.
+
+        :param str name: optional name of the CNFs to get
+        :param instance: CNF instance
+        :type instance: str or int
+        :param bool no_children: whether to return child CNFs
+        :param bool as_json: whether to return a JSON-formatted string
+        :returns: output of the tool
+        :rtype: str
+
+        .. note:: being a wrapper, this function does not do anything extra
+        like checking if arnied is running or waiting for generate.
+        """
+        if name is None and instance is not None:
+            raise ValueError("cannot pass `instance` without a `name`")
+
+        if isinstance(instance, str):
+            instance = int(instance)  # validate
+        elif instance is not None and not isinstance(instance, int):
+            raise TypeError(f"`instance` is of wrong type {type(instance)}")
+
+        cmd = f"{BIN_GET_CNF} {name or ''} {instance or ''}"
+
+        encoding = ENCODING
+        if no_children:
+            cmd += " --no-childs"
+        if as_json:
+            cmd += " --json"
+            encoding = "utf8"
+
+        # TODO: should error handling be improved so error messages
+        # from the binary are converted into specific exceptions types?
+        output = cls.run_cmd(cmd, encoding=encoding).stdout.strip()
+        # remove escape chars (such as '//')
+        output = output.replace('\\"', '"')
+        return output
+
+    @classmethod
+    def set_cnf(cls, input_str=None, input_file=None, delete=False,
+                delete_file=False, as_json=False, fix_problems=False, **kwargs):
+        """
+        Wrapper around the `set_cnf` binary.
+
+        :param str input_str: string to send as input to the binary
+        :param str input_file: path to a file to pass to the binary
+        :param bool delete: whether to delete the corresponding CNFs
+        :param bool delete_file: whether to delete the file passed as input
+        :param bool as_json: whether to interpret input as JSON
+        :param bool fix_problems: whether to automatically fix errors in the vars
+        :param kwargs: extra arguments to pass to the binary - underscores are
+                       replaced by dash, e.g. set_cnf(..., enable_queue=True)
+                       becomes `/usr/intranator/bin/set_cnf --enable-queue`
+        :raises: :py:class:`SetCnfException` in case the binary errors out
+
+        .. note:: being a wrapper, this function does not do anything extra
+        like checking if arnied is running or waiting for generate.
+        """
+        if input_str is None and input_file is None:
+            raise ValueError("Need to pass either a string or a file")
+
+        if delete_file is True and input_file is None:
+            raise ValueError("Cannot delete an unspecified file")
+
+        cmd = f"{BIN_SET_CNF}"
+        encoding = ENCODING
+        if as_json:
+            cmd += " --json"
+            encoding = "utf8"
+        if delete:
+            cmd += " -x"
+        if delete_file:
+            cmd += " --delete-file"
+        if fix_problems:
+            cmd += " --fix-problems"
+
+        for k, v in kwargs.items():
+            if v is True:
+                k = "-".join(k.split("_"))
+                cmd += f" --{k}"
+
+        if input_file:
+            cmd += f" {input_file}"
+
+        try:
+            cls.run_cmd(cmd, cmd_input=input_str, encoding=encoding)
+        except subprocess.CalledProcessError as ex:
+            # clean up the error message
+            ex.stderr = cls._clean_set_cnf_error(ex.stderr)
+            raise
+
+    @classmethod
+    def _clean_set_cnf_error(cls, err):
+        """
+        Turn the error from the set_cnf binary into a more readable message.
+
+        :param str err: error message from the binary
+        :returns: the clean error message
+        :rtype: str
+
+        Keep only offending lines and strip out HTML tags.
+        """
+        def get_error_lines(output_lines):
+            for cnfline in output_lines:
+                buffer = ""
+                quoted = False
+                parts = []
+                for c in cnfline:
+                    last_chr = buffer[-1] if buffer else None
+                    # this is a literal (unescaped) quote
+                    if c == "\"" and last_chr != "\\":
+                        quoted = not quoted
+
+                    if c == " " and not quoted:
+                        parts.append(buffer)
+                        buffer = ""
+                    else:
+                        buffer += c
+                parts.append(buffer)
+
+                if parts[-2] == "0":
+                    # no errors, ignore
+                    continue
+
+                lineno, name, instance, parent, value, exit_code, error = parts
+
+                # the binary outputs HTML strings
+                error = html.unescape(error)
+                # replace line breaks for readability
+                error = re.sub(r"(<br\s*>)+", " ", error)
+                # clean up HTML tags
+                error = re.sub(r"<[^<]+?>", "", error)
+
+                if parent == "-1":
+                    message = f"`` {lineno} {name},{instance}: {value} ``"
+                else:
+                    message = f"`` {lineno}   ({parent}) {name},{instance}: {value} ``"
+                yield f"Error in {message}: {error} (code={exit_code})"
+
+        lines = err.splitlines()
+        if len(lines) == 0:
+            return err
+
+        if "fatal errors in changes" not in lines[0]:
+            return err
+
+        errors = list(get_error_lines(lines[1:]))
+        return "\n".join(errors)
diff --git a/src/cnfvar/model.py b/src/cnfvar/model.py
new file mode 100644 (file)
index 0000000..3c3fbbf
--- /dev/null
@@ -0,0 +1,951 @@
+# The software in this package is distributed under the GNU General
+# Public License version 2 (with a special exception described below).
+#
+# A copy of GNU General Public License (GPL) is included in this distribution,
+# in the file COPYING.GPL.
+#
+# As a special exception, if other files instantiate templates or use macros
+# or inline functions from this file, or you compile this file and link it
+# with other works to produce a work based on this file, this file
+# does not by itself cause the resulting work to be covered
+# by the GNU General Public License.
+#
+# However the source code for this file must still be made available
+# in accordance with section (3) of the GNU General Public License.
+#
+# This exception does not invalidate any other reasons why a work based
+# on this file might be covered by the GNU General Public License.
+#
+# Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
+
+"""
+model: Cnf classes, collection of Cnf classes and multiple filtering methods.
+
+Featuring
+- Cnf: class representing a CNF variabe
+
+- CnfList: a collection of `Cnf` instances
+
+The classes above inherit from their base types with added mixins which
+extend them with extra functionality.
+
+.. codeauthor:: Intra2net
+"""
+
+import json
+
+from .. import cnfvar_old, arnied_api
+
+#: value used to detect unspecified arguments
+DEFAULT = object()
+#: encoding used by the get_cnf and set_cnf binaries
+ENCODING = "latin1"
+
+
+###############################################################################
+# HELPERS
+###############################################################################
+
+
+class CnfName(str):
+    """
+    Custom string where comparisons are case-insensitive.
+
+    With this class we do not have to worry about case when comparing against
+    the name of cnfvars when filtering. The cnfvar backend is already case-
+    insensitive anyway.
+    """
+
+    def __eq__(self, other):
+        if not isinstance(other, str):
+            return False
+        return self.lower() == other.lower()
+
+    def __contains__(self, name):
+        return name.lower() in self.lower()
+
+    def startswith(self, prefix):
+        return self.lower().startswith(prefix.lower())
+
+    def endswith(self, prefix):
+        return self.lower().endswith(prefix.lower())
+
+
+###############################################################################
+# BASE API
+###############################################################################
+
+
+class BaseCnfList(list):
+    """Base class representing a CNF list with minimal functionality."""
+
+    def __init__(self, cnf_iter=None, renumber=False):
+        """
+        Class constructor.
+
+        :param cnf_iter: iterator producing CNF elements or arguments for the
+                         constructor of the :py:class:`Cnf` class
+        :type: :py:class:`collections.abc.Iterator` producing elements of type
+               :py:class:`Cnf`
+        :param bool renumber: whether to fix up the number/ids of the CNFs
+
+        Example::
+            cnf = Cnf("my_cnf", "value")
+            cnf_list = CnfList([
+                cnf,
+                ("other_cnf", "other value"),
+                ("user", "john", instance=3)
+            ])
+        """
+        # Map the values of the iterator to support constructing this list
+        # from Cnf instances or arguments to the Cnf constructor
+        if cnf_iter is not None:
+            iter_ = map(lambda c: c if isinstance(c, Cnf) else Cnf(*c), cnf_iter)
+        else:
+            iter_ = []
+        super().__init__(iter_)
+        if renumber:
+            self.renumber()
+
+    def renumber(self):
+        """Fix line numbers of CNF variables from this list."""
+        # NOTE: we don't keep track of operations that change the list as this
+        # would require us to reimplement most of the methods. At least for now
+        # this method should be called again when serializing.
+        self._counter = 0
+
+        def renumber_fn(cnf):
+            self._counter += 1
+            cnf.lineno = self._counter
+
+        self.for_each_all(renumber_fn)
+
+    def where(self, where_filter):
+        """
+        Filter CNFs matching a given predicate.
+
+        :param where_filter: predicate to apply against CNFs
+        :type where_filter: function accepting a CNF and returning a boolean
+        :returns: a instance of this class with filtered members
+        :rtype: :py:class:`CnfList`
+        """
+        return CnfList(c for c in self if where_filter(c))
+
+    def where_child(self, where_filter):
+        """
+        Filter CNFs with children matching a given predicate.
+
+        :param where_filter: predicate to apply against children
+        :type where_filter: function accepting a CNF and returning a boolean
+        :returns: a instance of this class with filtered members
+        :rtype: :py:class:`CnfList`
+        """
+        def upper_filter(cnf):
+            return any(ch for ch in cnf.children if where_filter(ch))
+        return self.where(upper_filter)
+
+    def remove_where(self, where_filter):
+        """
+        Remove all CNFs from this list matching the given predicate.
+
+        :param where_filter: predicate to apply against children
+        :type where_filter: function accepting a CNF and returning a boolean
+        :returns: a list of the removed CNF variables
+        :rtype: [:py:class:`Cnf`]
+        """
+        r = []
+        # iterate by index for speed and in reverse to keep indexes valid
+        for i in range(len(self) - 1, -1, -1):
+            cnf = self[i]
+            if where_filter(cnf):
+                del self[i]
+                r.append(cnf)
+        return r
+
+    def for_each(self, fn):
+        """
+        Apply a function to each element of this list.
+
+        :param fn: function to apply to the elements
+        :type fn: function accepting a CNF (result value is ignored)
+        :returns: this same instance
+        :rtype: :py:class:`CnfList`
+
+        .. note:: this is mostly the same as the built-in map() function,
+        except that it changes the list in place.
+        """
+        for c in self:
+            try:
+                fn(c)
+            except StopIteration:
+                # support breaking
+                break
+        return self
+
+    def for_each_child(self, fn):
+        """
+        Apply a function to each child of the elements of this list.
+
+        :param fn: function to apply to the elements
+        :type fn: function accepting a CNF (result value is ignored)
+        :returns: this same instance
+        :rtype: :py:class:`CnfList`
+
+        .. note:: if a CNF does not have children, it is ignored
+        """
+        for c in self:
+            children = c.children or CnfList()
+            for ch in children:
+                try:
+                    fn(ch)
+                except StopIteration:
+                    # support breaking
+                    break
+            # apply recursively, too
+            children.for_each_child(fn)
+        return self
+
+    def for_each_all(self, fn):
+        """
+        Apply a function to every CNF of this list, parent or child.
+
+        :param fn: function to apply to the elements
+        :type fn: function accepting a CNF (result value is ignored)
+        :returns: this same instance
+        :rtype: :py:class:`CnfList`
+        """
+        for c in self:
+            try:
+                fn(c)
+            except StopIteration:
+                # support breaking
+                break
+            children = c.children or CnfList()
+            children.for_each_all(fn)
+        return self
+
+    def __str__(self):
+        """
+        Get a string representation of this instance.
+
+        :returns: a string in the cnfvar format
+        :rtype: str
+        """
+        return "\n".join((str(c) for c in self))
+
+    def __add__(self, other):
+        return CnfList(super().__add__(other))
+
+
+class BaseCnf:
+    """Base class representing a CNF variable with minimal functionality."""
+
+    _PARENT_TEMPLATE = "{lineno} {name},{instance}: \"{value}\""
+    _CHILD_TEMPLATE = "{lineno} {indent}({parent}) {name},{instance}: \"{value}\""
+    _NEST_INDENT = "  "
+
+    def __init__(self, name, value, instance=-1, parent=None,
+                 lineno=None, comment=None):
+        """
+        Create this instance.
+
+        :param str name: name of the cnfvar (case does not matter)
+        :param str value: value for this cnfvar (will be converted to string
+                          if it is not of this type)
+        :param int instance: instance of this cnfvar
+        :param parent: a parent Cnf instance
+        :type parent: :py:class:`BaseCnf`
+        :param int lineno: line number
+        :param str comment: cnfvar comment
+        """
+        self.name = CnfName(name)
+        self.value = value
+        self.instance = int(instance)
+        self.parent = parent
+        self.lineno = int(lineno or 0)
+        self.comment = comment
+        self.__children = CnfList()
+
+    # Use getters and setters to keep internal consistency and fail-fast
+    # preventing invalid data from being sent to the cnfvar backend.
+
+    def _get_name(self):
+        return self.__name
+
+    def _set_name(self, value):
+        # convert Python strings passed as name to our custom string
+        self.__name = CnfName(value)
+    name = property(_get_name, _set_name)
+
+    def _get_instance(self):
+        return self.__instance
+
+    def _set_instance(self, value):
+        # fail-fast and make sure instance is a valid integer
+        self.__instance = int(value)
+    instance = property(_get_instance, _set_instance)
+
+    def _get_lineno(self):
+        return self.__lineno
+
+    def _set_lineno(self, value):
+        # fail-fast and make sure lineno is a valid integer
+        self.__lineno = int(value)
+    lineno = property(_get_lineno, _set_lineno)
+
+    def _get_children(self):
+        return self.__children
+    # No setter to sure that the children property will not
+    # be replaced by something other than a `CnfList`
+    children = property(_get_children)
+
+    def _get_value(self):
+        return self.__value
+
+    def _set_value(self, value):
+        # Make sure the value is always stored as a string
+        # (no other types make sense to the cnfvar backend)
+        self.__value = str(value)
+    value = property(_get_value, _set_value)
+
+    def add_child(self, *args, **kwargs):
+        """
+        Add a child CNF variable.
+
+        Arguments can either be a single instance of the :py:class:`Cnf`
+        class or a list of arguments to be passed to the constructor of
+        that class.
+
+        :returns: the instance that was created
+        :rtype: :py:class:`Cnf`
+
+        Example::
+
+            cnf = Cnf("my_parent_cnf", "parent")
+            cnf2 = Cnf("my_child_cnf", "john")
+
+            # adding a child as a CNF instance
+            cnf.add_child(cnf2)
+
+            # adding a child passing arguments of the Cnf constructor
+            cnf.add_child("my_child_cnf", "jane", instance=2)
+        """
+        # support passing a Cnf instance
+        if len(args) == 1 and not kwargs:
+            cnf = args[0]
+            assert isinstance(cnf, Cnf), \
+                   "With one argument, a Cnf instance is mandatory"
+        else:
+            cnf = Cnf(*args, **kwargs)
+
+        # store a reference to parent to easily access it
+        cnf.parent = self
+
+        # It seems the CNF backend (at least using set_cnf as opposed to the varlink
+        # API) only accepts instance with value of -1 for top-level variables, so
+        # just in case fix up instances when adding children with the default value.
+        if cnf.instance == -1:
+            cnf.instance = 0
+            for c in self.children:
+                if c.name == cnf.name:
+                    cnf.instance += 1
+
+        self.children.append(cnf)
+        return cnf
+
+    def add_children(self, *children):
+        """
+        Add multiple child CNF variables.
+
+        Each argument must be either an instance of the :py:class:`Cnf` class
+        or a tuple/list to be expanded and passed to construct that instance.
+
+        :returns: a list of the instances that were created
+        :rtype: :py:class:`CnfList`
+
+        Example::
+            cnf = Cnf("my_parent_cnf", "parent")
+            cnf2 = Cnf("my_child_cnf", "john")
+
+            cnf.add_children(
+                cnf2,                                  # cnf instance directly
+                ("my_child_cnf", "jane", instance=2),  # pass a tuple with args
+                ["my_child_cnf", "jack", instance=3])  # pass a list with args
+
+            # adding a child passing arguments of the Cnf constructor
+            cnf.add_child("my_child_cnf", "jane", instance=2)
+        """
+        added_children = CnfList()
+        for c in children:
+            if isinstance(c, Cnf):
+                new_child = self.add_child(c)
+            elif isinstance(c, tuple) or isinstance(c, list):
+                new_child = self.add_child(*c)
+            else:
+                raise ValueError(f"Children item {c} must be either a Cnf, a tuple or a list")
+            added_children.append(new_child)
+        return added_children
+
+    def __eq__(self, other):
+        """
+        Equality implementation.
+
+        :param other: object to compare this instance against
+        :type other: any
+        :returns: whether `other` is equal to this instance
+        :rtype: bool
+
+        This is particularly useful when comparing instances of
+        :py:class:`CnfList`
+        """
+        if not isinstance(other, Cnf):
+            return False
+
+        # NOTE: we try to define two variables as equal in the same way as the
+        # set_cnf binary would if we were passing it an updated CNF variable.
+        # It does not take comments, children and lineno into account when we
+        # pass it a variable; it will rather compare the data we compare here,
+        # and if it finds a match it will update it with the changed children.
+        return self.name == other.name \
+            and self.value == other.value \
+            and self.instance == other.instance \
+            and self.parent == other.parent
+
+    def __str__(self):
+        """
+        Get a string representation of this instance.
+
+        :returns: a string in the cnfvar format
+        :rtype: str
+        """
+        if self.parent is None:
+            this_str = self._PARENT_TEMPLATE.format(
+                lineno=self.lineno,
+                name=self.name.upper(),
+                instance=self.instance,
+                value=self.value
+            )
+        else:
+            depth = 0
+            curr = self
+            while curr.parent is not None:
+                depth += 1
+                curr = curr.parent
+
+            this_str = self._CHILD_TEMPLATE.format(
+                lineno=self.lineno,
+                indent=self._NEST_INDENT * depth,
+                parent=self.parent.lineno,
+                name=self.name.upper(),
+                instance=self.instance,
+                value=self.value
+            )
+
+        if self.comment is not None:
+            this_str += f" # {self.comment}"
+
+        for child in self.children:
+            this_str += f"\n{child}"
+
+        return this_str
+
+    def __repr__(self):
+        """
+        Get a printable representation of this instance.
+
+        :returns: a string in the cnfvar format
+        :rtype: str
+        """
+        repr_ = self._PARENT_TEMPLATE.format(
+            lineno=self.lineno,
+            name=self.name.upper(),
+            instance=self.instance,
+            value=self.value
+        ) if self.parent is None else self._CHILD_TEMPLATE.format(
+            lineno=self.lineno,
+            indent="",
+            parent=self.parent.lineno,
+            name=self.name.upper(),
+            instance=self.instance,
+            value=self.value
+        )
+        return f"Cnf{{ {repr_} [children={len(self.children)}] }}"
+
+
+###############################################################################
+# MIXINS
+###############################################################################
+#
+# These mixins add functionality to the base API without polluting it.
+#
+
+class CnfListSerializationMixin(BaseCnfList):
+    """Add serialization support to BaseCnfList."""
+
+    def to_cnf_structure(self, renumber=True):
+        """
+        Convert this list to an object meaningful to :py:mod:`cnfvar`.
+
+        :param bool renumber: whether to fix up the number/ids of the CNFs
+        :returns: a dictionary with the converted values
+        :rtype: {str, {str, str or int}}
+        """
+        if renumber:
+            self.renumber()
+        return {"cnf": [x.to_cnfvar_dict() for x in self]}
+
+    def to_cnf_file(self, path, renumber=True, encoding=ENCODING):
+        """
+        Dump a string representation of this list in the cnfvar format to a file.
+
+        :param str path: path to the file to write to
+        :param bool renumber: whether to fix the lineno of the cnfvars
+        :param str encoding: encoding to use to save the file
+        """
+        if renumber:
+            self.renumber()
+        with open(path, "w", encoding=encoding) as fp:
+            fp.write(str(self))
+
+    def to_json_string(self, renumber=True):
+        """
+        Generate a JSON representation of this list in the cnfvar format.
+
+        :param bool renumber: whether to fix the lineno of the cnfvars
+        :returns: the JSON string
+        :rtype: str
+        """
+        def _to_dict(cnf):
+            d = {
+                "number": cnf.lineno,
+                "varname": cnf.name,
+                "data": cnf.value,
+                "instance": cnf.instance
+            }
+            if cnf.parent and cnf.parent.lineno:
+                d["parent"] = cnf.parent.lineno
+            if cnf.comment is not None:
+                d["comment"] = cnf.comment
+            if len(cnf.children) > 0:
+                d["children"] = [_to_dict(c) for c in cnf.children]
+            return d
+        if renumber:
+            renumber = True
+        json_list = [_to_dict(c) for c in self]
+        return json.dumps({"cnf": json_list})
+
+    def to_json_file(self, path, renumber=True):
+        """
+        Dump a JSON representation of this list to a file.
+
+        :param str path: path to the file to write to
+        :param bool renumber: whether to fix the lineno of the cnfvars
+        """
+        with open(path, "w", encoding="utf8") as fp:
+            fp.write(self.to_json_string(renumber=renumber))
+
+    @classmethod
+    def from_cnf_structure(cls, obj):
+        """
+        Create a list from a cnfvar object from the :py:mod:`cnfvar` module.
+
+        :param obj: an object as defined in the :py:mod:`cnfvar`
+        :type obj: {str, {str, str or int}}
+        :returns: a list of cnfvars
+        :rtype: :py:class:`CnfList`
+        """
+        return cls(map(Cnf.from_cnf_structure, obj["cnf"]))
+
+    @classmethod
+    def from_cnf_string(cls, data):
+        """
+        Create a list from a cnfvar string.
+
+        :param str data: string to generate the list from
+        :returns: a list of cnfvars
+        :rtype: :py:class:`CnfList`
+        """
+        cnf_obj = cnfvar_old.read_cnf(data)
+        return CnfList.from_cnf_structure(cnf_obj)
+
+    @classmethod
+    def from_json_string(cls, data):
+        """
+        Create a list from a json string.
+
+        :param str data: string to generate the list from
+        :returns: a list of cnfvars
+        :rtype: :py:class:`CnfList`
+        """
+        cnf_obj = json.loads(data)
+        return CnfList.from_cnf_structure(cnf_obj)
+
+    @classmethod
+    def from_cnf_file(cls, path, encoding=ENCODING):
+        """
+        Create a list from a cnfvar file.
+
+        :param str path: path to the file to read
+        :param str encoding: encoding to use to open the file (defaults to
+                             latin1 as this is the default encoding)
+        :returns: a list of cnfvars
+        :rtype: :py:class:`CnfList`
+        """
+        with open(path, "r", encoding=encoding) as fp:
+            return CnfList.from_cnf_string(fp.read())
+
+    @classmethod
+    def from_json_file(cls, path):
+        """
+        Create a list from a json file.
+
+        :param str path: path to the file to read
+        :returns: a list of cnfvars
+        :rtype: :py:class:`CnfList`
+        """
+        with open(path, "r", encoding="utf8") as fp:
+            return CnfList.from_json_string(fp.read())
+
+
+class CnfSerializationMixin(BaseCnf):
+    """Add serialization support to BaseCnf."""
+
+    def to_cnfvar_dict(self):
+        """
+        Convert this instance to dictionary from the :py:mod:`cnfvar` module.
+
+        :returns: the dictionary created
+        :rtype: {str, str or int}
+
+        .. todo:: this method is still needed because dumping cnf variables
+        to strings (json or not) is still delegated to the old cnfvar module.
+        """
+        d = {
+            "number": self.lineno,
+            "varname": self.name,
+            "data": self.value,
+            "instance": self.instance
+        }
+        if self.parent and self.parent.lineno:
+            d["parent"] = self.parent.lineno
+        if self.comment is not None:
+            d["comment"] = self.comment
+        if len(self.children) > 0:
+            d["children"] = [c.to_cnfvar_dict() for c in self.children]
+        return d
+
+    def to_json_string(self, renumber=True):
+        """
+        Convert this instance to a JSON string.
+
+        :param bool renumber: whether to fix the lineno of the cnfvars
+        :returns: the JSON string
+        :rtype: str
+        """
+        return CnfList([self]).to_json_string(renumber=renumber)
+
+    def to_cnf_file(self, path, renumber=True, encoding=ENCODING):
+        """
+        Dump a string representation of this instance to a file.
+
+        :param str path: path to the file to write to
+        :param bool renumber: whether to fix the lineno of this cnfvar and its children
+        :param str encoding: encoding to use to save the file
+        """
+        CnfList([self]).to_cnf_file(path, renumber=renumber, encoding=encoding)
+
+    def to_json_file(self, path, renumber=True):
+        """
+        Dump a JSON representation of this instance to a file.
+
+        :param str path: path to the file to write to
+        :param bool renumber: whether to fix the lineno of this cnfvar and its children
+        """
+        CnfList([self]).to_json_file(path, renumber=renumber)
+
+    @classmethod
+    def from_cnf_structure(cls, obj):
+        """
+        Create an instance from a dictionary from the :py:mod:`cnfvar` module.
+
+        :param obj: dictionary to convert to this instance
+        :type obj: {str, str or int}
+        :returns: the cnf instance created
+        :rtype: :py:class:`Cnf`
+        """
+        cnf = Cnf(obj["varname"], obj["data"],
+                  instance=obj["instance"], lineno=obj["number"],
+                  comment=obj.get("comment", None))
+        for ch_obj in obj.get("children", []):
+            child_cnf = Cnf.from_cnf_structure(ch_obj)
+            cnf.add_child(child_cnf)
+        return cnf
+
+    @classmethod
+    def from_cnf_string(cls, data):
+        """
+        Create an instance of this class from a cnfvar string.
+
+        :param str data: cnfvar string to convert
+        :returns: the cnf instance created
+        :rtype: :py:class:`Cnf`
+        """
+        return CnfListSerializationMixin.from_cnf_string(data).single()
+
+    @classmethod
+    def from_json_string(cls, data):
+        """
+        Create an instance of this class from a JSON string.
+
+        :param str data: JSON string to convert
+        :returns: the cnf instance created
+        :rtype: :py:class:`Cnf`
+        """
+        return CnfListSerializationMixin.from_json_string(data).single()
+
+    @classmethod
+    def from_cnf_file(cls, path, encoding=ENCODING):
+        """
+        Create an instance of this class from a cnfvar file.
+
+        :param str path: path to the file to read
+        :param str encoding: encoding to use to read the file
+        :returns: the cnf instance created
+        :rtype: :py:class:`Cnf`
+        """
+        return CnfListSerializationMixin.from_cnf_file(path, encoding=encoding).single()
+
+    @classmethod
+    def from_json_file(cls, path):
+        """
+        Create an instance of this class from a json file.
+
+        :param str path: path to the file to read
+        :returns: the cnf instance created
+        :rtype: :py:class:`Cnf`
+        """
+        return CnfListSerializationMixin.from_json_file(path).single()
+
+
+class CnfListArniedApiMixin(BaseCnfList):
+    """Add support for converting this class to and from Arnied API classes."""
+
+    def to_api_structure(self):
+        """
+        Convert this list to the corresponding object in the arnied API.
+
+        :returns: the converted object
+        :rtype: [:py:class:`arnied_api.CnfVar`]
+        """
+        return [c.to_api_structure() for c in self]
+
+    @classmethod
+    def from_api_structure(cls, cnfvar_list):
+        """
+        Convert a list from the arnied API into a list of this type.
+
+        :param cnfvar_list: list to convert
+        :type cnfvar_list: [:py:class:`arnied_api.CnfVar`]
+        :returns: the list created
+        :rtype: :py:class:`CnfList`
+        """
+        return CnfList((Cnf.from_api_structure(c) for c in cnfvar_list),
+                       renumber=True)
+
+
+class CnfArniedApiMixin(BaseCnf):
+    """Add support for converting this class to and from Arnied API classes."""
+
+    def to_api_structure(self):
+        """
+        Convert this instance to the corresponding object in the arnied API.
+
+        :returns: the converted object
+        :rtype: :py:class:`arnied_api.CnfVar`
+        """
+        return arnied_api.CnfVar(
+            self.name.upper(),
+            self.instance,
+            self.value,
+            False,  # default here to False
+            children=[c.to_api_structure() for c in self.children])
+
+    @classmethod
+    def from_api_structure(cls, cnfobj):
+        """
+        Convert an object from the arnied API into an instance of this type.
+
+        :param cnfobj: object to convert
+        :type cnfobj: :py:class:`arnied_api.CnfVar`
+        :returns: the instance created
+        :rtype: :py:class:`Cnf`
+        """
+        cnf = Cnf(cnfobj.name, cnfobj.data, cnfobj.instance)
+        children = CnfList((Cnf.from_api_structure(c) for c in cnfobj.children))
+        for c in children:
+            c.parent = cnf
+        cnf.children.extend(children)
+        return cnf
+
+
+class CnfShortcutsMixin(BaseCnf):
+    """Extend the base CNF class with useful methods."""
+
+    def enable(self):
+        """Treat this variable as a boolean var and set its value to 1."""
+        self.value = "1"
+
+    def disable(self):
+        """Treat this variable as a boolean var and set its value to 0."""
+        self.value = "0"
+
+    def is_enabled(self):
+        """Treat this variable as a boolean var and check if its value is 1."""
+        return self.value == "1"
+
+    def enable_child_flag(self, name):
+        """
+        Set the value of the child CNF matching `name` to "1".
+
+        :param str name: name of the child whose value to enable
+
+        .. note:: child will be created if it does not exist.
+        """
+        cnf = self.children.first_with_name(name, default=None)
+        if cnf is None:
+            cnf = self.add_child(name, "1")
+        else:
+            cnf.enable()
+
+    def disable_child_flag(self, name):
+        """
+        Set the value of the child CNF matching `name` to "0".
+
+        :param str name: name of the child whose value to disable
+
+        .. note:: child will be created if it does not exist.
+        """
+        cnf = self.children.first_with_name(name, default=None)
+        if cnf is None:
+            cnf = self.add_child(name, "0")
+        else:
+            cnf.disable()
+
+    def child_flag_enabled(self, name):
+        """
+        Check if a given child has a value equal to `1`.
+
+        :param str name: name of the child to check
+        :returns: whether the value of the given child, if it exists, is 1
+        :rtype: bool
+        """
+        cnf = self.children.first_with_name(name, default=None)
+        return cnf.is_enabled() if cnf is not None else False
+
+
+class CnfListQueryingMixin(BaseCnfList):
+    """Mixing adding shortcuts for common filter operations."""
+
+    def single(self, where_filter=None, default=DEFAULT):
+        """
+        Get the only CNF of this list or raise if none or more than one exist.
+
+        :param where_filter: predicate to apply against CNFs beforehand
+        :type where_filter: function accepting a CNF and returning a boolean
+        :param default: value to return in case the list is empty
+        :type default: any
+        :raises: :py:class:`ValueError` if a single value cannot be found and
+                 a default value was not specified
+        :returns: the first and only element of this list, or default if set
+                  and no element is present
+        :rtype: :py:class:`Cnf`
+        """
+        list_ = self.where(where_filter) if where_filter is not None else self
+
+        if len(list_) == 1:
+            return list_[0]
+        elif len(list_) == 0 and default != DEFAULT:
+            return default
+        else:
+            raise ValueError(f"CnfList does not contain a single item (len={len(list_)})")
+
+    def first(self, where_filter=None, default=DEFAULT):
+        """
+        Get the first element in this list or raise if the list is empty.
+
+        :param where_filter: predicate to apply against CNFs beforehand
+        :type where_filter: function accepting a CNF and returning a boolean
+        :param default: value to return in case the list is empty
+        :type default: any
+        :raises: :py:class:`ValueError` if a single value cannot be found and
+                 a default value was not specified
+        :returns: the first element of this list, or default if set and
+                  no element is present
+        :rtype: :py:class:`Cnf`
+        """
+        list_ = self.where(where_filter) if where_filter is not None else self
+        if len(list_) > 0:
+            return list_[0]
+        elif default != DEFAULT:
+            return default
+        else:
+            raise ValueError("Cannot get the first item - CnfList is empty")
+
+    def with_value(self, value):
+        """Shortcut method for filtering by value."""
+        return self.where(lambda c: c.value == value)
+
+    def with_name(self, name):
+        """Shortcut method for filtering by name."""
+        return self.where(lambda c: c.name == name)
+
+    def with_instance(self, instance):
+        """Shortcut method for filtering by instance."""
+        return self.where(lambda c: c.instance == instance)
+
+    def single_with_name(self, name, default=DEFAULT):
+        """Shortcut method for getting the single item with a given name."""
+        return self.with_name(name).single(default=default)
+
+    def single_with_value(self, value, default=DEFAULT):
+        """Shortcut method for getting the single item with a given value."""
+        return self.with_value(value).single(default=default)
+
+    def single_with_instance(self, instance, default=DEFAULT):
+        """Shortcut method for getting the single item with a given instance."""
+        return self.with_instance(instance).single(default=default)
+
+    def first_with_name(self, name, default=DEFAULT):
+        """Shortcut method for getting the first item with a given name."""
+        return self.with_name(name).first(default=default)
+
+    def first_with_value(self, value, default=DEFAULT):
+        """Shortcut method for getting the first item with a given value."""
+        return self.with_value(value).first(default=default)
+
+    def first_with_instance(self, instance, default=DEFAULT):
+        """Shortcut method for getting the first item with a given instance."""
+        return self.with_instance(instance).first(default=default)
+
+
+###############################################################################
+# PUBLIC CLASSES
+###############################################################################
+#
+# Set up the classes with the mixins we want to be available by default.
+#
+
+
+class CnfList(CnfListSerializationMixin, CnfListArniedApiMixin, CnfListQueryingMixin):
+    """Collection of Cnf variables."""
+
+    pass
+
+
+class Cnf(CnfSerializationMixin, CnfArniedApiMixin, CnfShortcutsMixin):
+    """Class representing a cnfvar."""
+
+    pass
+
+
+__all__ = ["CnfList", "Cnf"]
diff --git a/src/cnfvar/store.py b/src/cnfvar/store.py
new file mode 100644 (file)
index 0000000..9872c16
--- /dev/null
@@ -0,0 +1,376 @@
+# The software in this package is distributed under the GNU General
+# Public License version 2 (with a special exception described below).
+#
+# A copy of GNU General Public License (GPL) is included in this distribution,
+# in the file COPYING.GPL.
+#
+# As a special exception, if other files instantiate templates or use macros
+# or inline functions from this file, or you compile this file and link it
+# with other works to produce a work based on this file, this file
+# does not by itself cause the resulting work to be covered
+# by the GNU General Public License.
+#
+# However the source code for this file must still be made available
+# in accordance with section (3) of the GNU General Public License.
+#
+# This exception does not invalidate any other reasons why a work based
+# on this file might be covered by the GNU General Public License.
+#
+# Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
+
+"""
+store: implementations of CNF stores using varlink and *et_cnf binaries.
+
+Featuring
+- CnfStore: the main store class that is implemented using the varlink API
+
+- BinaryCnfStore: alternative store class implementation using the set_ and
+get_cnf binaries
+
+.. codeauthor:: Intra2net
+"""
+import subprocess
+import logging
+import time
+import html
+
+from .. import arnied_wrapper, arnied_api
+from . import CnfBinary, Cnf, CnfList
+
+log = logging.getLogger("pyi2ncommon.cnfvar.store")
+
+
+class CnfStore:
+    """Main CNF store class that uses the varlink API."""
+
+    def __init__(self, backend_driver=arnied_api.Arnied):
+        """
+        Initialize this class.
+
+        :param backend_driver: driver to use to talk to the cnfvar backend
+        :type: :py:class:`arnied_api.Arnied`
+        """
+        self._driver = backend_driver
+        log.debug("Initialized CnfStore with driver `%s`", type(backend_driver))
+
+    def query(self, name=None, instance=None):
+        """
+        Query the CNF store and return a list of parsed CNFs.
+
+        :param str name: optional name of the CNFs to query
+        :param instance: optional CNF instance
+        :type instance: str or int
+        :returns: list of parsed CNF values
+        :rtype: :py:class:`CnfList`
+
+        Example::
+            store = CnfStore()
+            user_cnfs = store.query("USER")
+        """
+        log.debug("Querying CnfStore with name=%s and instance=%s via arnied API",
+                  name, instance)
+
+        # the API only expects an object if there is a name (and it's not case-insensitive)
+        query = arnied_api.GetCnfQuery(name.upper(), instance) if name else None
+        api_ret = self._driver.get_cnf(query)
+
+        # NOTE: logging all output here would result in huge lines when querying
+        # all variables via `store.query()`
+        log.debug("Arnied API returned %d cnfvars", len(api_ret.vars))
+        return CnfList.from_api_structure(api_ret.vars)
+
+    def commit(self, cnf, fix_problems=False):
+        """
+        Update or insert CNF variables from a list.
+
+        :param cnf: CNF instance or list of CNFs to update or insert
+        :type cnf: :py:class:`Cnf` or :py:class:`CnfList`
+        :raises: :py:class:`CommitException` if the arnied API complains
+        :param bool fix_problems: whether to automatically fix errors in the vars
+
+        .. note:: you can mix variables to insert and variables to update
+        in the same list as the system should handle it nicely
+
+        Example::
+            store = CnfStore()
+            user_cnf = store.query("USER")\
+                            .single_with_value("joe")
+            user_cnf.add_child("user_group_member_ref", "3")
+            store.commit(user_cnf)
+        """
+        cnf = self._cnf_or_list(cnf, operation="commit")
+        self._autofix_instances(cnf)
+        log.debug("Committing variables via arnied API:\n%s", cnf)
+
+        arnied_cnfs = cnf.to_api_structure()
+        self._do_commit(cnf, arnied_cnfs, fix_problems=fix_problems)
+
+    def delete(self, cnf, fix_problems=False):
+        """
+        Delete a list of top-level CNF variables.
+
+        :param cnf: a single CNF value or a list of values
+        :type cnf: :py:class:`CnfList` or :py:class:`Cnf`
+        :param bool fix_problems: whether to automatically fix errors in the vars
+
+        Example::
+            store = CnfStore()
+            user_cnfs = store.query("USER")\
+                             .where(lambda c: c.value in ["joe", "jane"])
+            store.delete(user_cnfs)
+        """
+        cnf = self._cnf_or_list(cnf, operation="delete")
+        if any((c.parent is not None for c in cnf)):
+            raise RuntimeError("Calling delete is only supported on top-level CNF variables")
+        log.debug("Deleting variables via arnied API:\n%s", cnf)
+
+        arnied_cnfs = cnf.to_api_structure()
+        for c in arnied_cnfs:
+            c.deleted = True
+            c.children.clear()
+        self._do_commit(cnf, arnied_cnfs, fix_problems=fix_problems)
+
+    def _autofix_instances(self, cnfs):
+        """
+        Auto-assign a valid instance value to all top-level vars in a list.
+
+        :param cnfs: list of cnfvars to fix
+        :type cnfs: :py:class`CnfList`
+
+        When the instance of a cnfvar is -1, the backend initializes it
+        automatically. However it starts on 1, whereas many variables are not
+        allowed to start on one (those that are meant to be unique, f.e.).
+        This method can be used in child classes to use an alternative scheme,
+        however for performance reasons the base API class uses the default and
+        relies on the cnfvar backend to do this job.
+        """
+
+    def _do_commit(self, original_cnfs, arnied_cnfs, fix_problems=False):
+        """
+        Set cnfvars and commit changes.
+
+        :param original_cnfs: list of CNFs to print in case of errors
+        :type original_cnfs: :py:class:`CnfList`
+        :param arnied_cnfs: list of cnfvars to pass to the arnied API
+        :type arnied_cnfs: [:py:class:`arnied_api.CnfVar`]
+        :param bool fix_problems: whether to automatically fix errors in the vars
+        :raises: :py:class:`CommitException` if the arnied API complains
+        """
+        try:
+            ret = self._driver.set_commit_cnf(vars=arnied_cnfs, username=None,
+                                              nogenerate=False,
+                                              fix_commit=fix_problems)
+        except arnied_api.CnfCommitError as ex:
+            # fatal errors, we will handle it in our custom exception
+            ret = ex
+
+        errors = []
+        for r in ret.results:
+            # message can contain HTML escape codes
+            msg = html.unescape(r.result_msg)
+
+            # `result_type` is defined as int in the varlink API,
+            # but the meaning of the codes are:
+            # enum result_type { OK=0, WARN=1, FAIL_TEMP=2, FAIL=3 }
+            if r.result_type == 1:
+                log.debug("Warning in `` %s,%s: \"%s\" ``: {msg} (code=%s)",
+                          r.name, r.instance, r.data, r.result_type)
+            elif r.result_type > 1:
+                errors.append(f"Error in `` {r.name},{r.instance}: \"{r.data}\" ``"
+                              f": {msg}")
+
+        if len(errors) > 0:
+            log.debug("Error sending variables:\n%s", arnied_cnfs)
+            raise CommitException(original_cnfs, "\n".join(errors))
+
+        self._wait_for_generate()
+
+    def _wait_for_generate(self, timeout=300):
+        """
+        Wait for the 'generate' program to end.
+
+        :param int timeout: program run timeout
+        :raises: :py:class:`TimeoutError` if the program did not finish on time
+        """
+        def scheduled_or_running(progname):
+            ret = self._driver.is_scheduled_or_running(progname)
+            return ret.status in [arnied_api.ProgramStatus.Scheduled,
+                                  arnied_api.ProgramStatus.Running]
+
+        def wait_for_program(progname):
+            log.debug("Waiting for `%s` to be running", progname)
+            for _ in range(10):
+                if scheduled_or_running(progname):
+                    # if is running or scheduled, break to wait for completion
+                    break
+                time.sleep(1)
+            else:
+                # after trying and retrying, program is not scheduled nor
+                # running, so it is safe to assume it has already executed
+                return
+
+            # program running or scheduled, wait
+            log.debug("Waiting for `%s` to finish", progname)
+            for _ in range(0, timeout):
+                if not scheduled_or_running(progname):
+                    # finished executing, bail out
+                    return
+                time.sleep(1)
+            raise TimeoutError(f"Program `{progname}` did not end in time")
+
+        wait_for_program("GENERATE")
+        wait_for_program("GENERATE_OFFLINE")
+
+    def _cnf_or_list(self, cnf, operation) -> CnfList:
+        """
+        Validate and wrap a CNF value into a list.
+
+        :param cnf: a single CNF value or a list of values
+        :type cnf: :py:class:`CnfList` or :py:class:`Cnf`
+        :param str operation: name of the operation that is being done
+        :raises: :py:class:`TypeError` if the type of the CNF object
+                 is neither a list nor a CNF value
+        :returns: wrapped CNF value
+        :rtype: :py:class:`CnfList`
+        """
+        if isinstance(cnf, Cnf):
+            cnf = CnfList([cnf])
+        elif not isinstance(cnf, CnfList):
+            raise TypeError(f"Cannot {operation} value(s) of type `{type(cnf)}`")
+        return cnf
+
+
+class BinaryCnfStore(CnfStore):
+    """Implementation of the CNF store that uses get_ and set_cnf."""
+
+    #: how much to wait for arnied to report running
+    ARNIED_TIMEOUT = 30
+
+    def __init__(self, backend_driver=CnfBinary):
+        """
+        Initialize this class.
+
+        :param backend_driver: driver to use to talk to the cnfvar backend
+        :type: :py:class:`CnfBinary`
+        """
+        super().__init__(backend_driver=backend_driver)
+        log.debug("Initialized BinaryCnfStore with driver `%s`", type(backend_driver))
+
+    def query(self, name=None, instance=None):
+        """
+        Query the CNF store and return a list of parsed CNFs.
+
+        :param str name: optional name of the CNFs to query
+        :param instance: optional CNF instance
+        :type instance: str or int
+        :returns: list of parsed CNF values
+        :rtype: :py:class:`CnfList`
+
+        Example::
+            store = CnfStore()
+            user_cnfs = store.query("USER")
+        """
+        log.debug("Querying BinaryCnfStore with name=%s and instance=%s",
+                  name, instance)
+        output = self._driver.get_cnf(name, instance)
+
+        if len(output) == 0:
+            # otherwise cnfvar raises a generic Malformed exception
+            return CnfList()
+
+        return CnfList.from_cnf_string(output)
+
+    def commit(self, cnf, fix_problems=False):
+        """
+        Update or insert CNF variables from a list.
+
+        :param cnf: CNF instance or list of CNFs to update or insert
+        :type cnf: :py:class:`Cnf` or :py:class:`CnfList`
+        :param bool fix_problems: whether to automatically fix errors in the vars
+
+        .. note:: you can mix variables to insert and variables to update
+        in the same list as the system should handle it nicely
+
+        Example::
+            store = CnfStore()
+            user_cnf = store.query("USER")\
+                            .single_with_value("joe")
+            user_cnf.add_child("user_group_member_ref", "3")
+            store.commit(user_cnf)
+        """
+        cnf = self._cnf_or_list(cnf, operation="commit")
+        self._autofix_instances(cnf)
+
+        # set_cnf is demaning on lineno's
+        cnf.renumber()
+        log.debug("Committing variables via binaries:\n%s", cnf)
+
+        self._call_arnied(arnied_wrapper.verify_running, timeout=self.ARNIED_TIMEOUT)
+        try:
+            self._driver.set_cnf(input_str=str(cnf), fix_problems=fix_problems)
+        except subprocess.CalledProcessError as ex:
+            raise CommitException(cnf, ex.stderr) from None
+        self._call_arnied(arnied_wrapper.wait_for_generate)
+
+    def delete(self, cnf, fix_problems=False):
+        """
+        Delete a list of top-level CNF variables.
+
+        :param cnf: a single CNF value or a list of values
+        :type cnf: :py:class:`CnfList` or :py:class:`Cnf`
+        :param bool fix_problems: whether to automatically fix errors in the vars
+
+        Example::
+            store = CnfStore()
+            user_cnfs = store.query("USER")\
+                             .where(lambda c: c.value in ["joe", "jane"])
+            store.delete(user_cnfs)
+        """
+        cnf = self._cnf_or_list(cnf, operation="delete")
+        if any((c.parent is not None for c in cnf)):
+            raise RuntimeError("Calling delete is only supported on top-level CNF variables")
+
+        # set_cnf is demaning on lineno's
+        cnf.renumber()
+        log.debug("Deleting variables via binaries:\n%s", cnf)
+
+        self._call_arnied(arnied_wrapper.verify_running, timeout=self.ARNIED_TIMEOUT)
+        try:
+            self._driver.set_cnf(input_str=str(cnf), delete=True, fix_problems=fix_problems)
+        except subprocess.CalledProcessError as ex:
+            raise CommitException(cnf, ex.stderr) from None
+        self._call_arnied(arnied_wrapper.wait_for_generate)
+
+    def _call_arnied(self, fn, *args, **kwargs):
+        """
+        Simple proxy around the arnied to be overwritten in child classes.
+
+        :param fn: function to invoke in the arnied wrapper
+        :type fn: py:function
+        :param args: arguments to be passed to function
+        :param kwargs: named arguments to be passed to function
+        """
+        return fn(*args, **kwargs)
+
+
+class CommitException(Exception):
+    """Custom exception for commit errors."""
+
+    def __init__(self, cnfvars, msg):
+        """
+        Initialize this exception.
+
+        :param cnfvars: list of CNF variables that would be committed
+        :type cnfvars: CnfList
+        :param str msg: error message
+        """
+        self.message = f"""\
+Error committing CNF variables!
+----------------------------
+Input:
+{cnfvars}
+----------------------------
+Error:
+{msg}
+"""
+        super().__init__(msg)
diff --git a/test/cnfvar/__init__.py b/test/cnfvar/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/test/cnfvar/cnfs.cnf b/test/cnfvar/cnfs.cnf
new file mode 100644 (file)
index 0000000..0b5e70c
--- /dev/null
@@ -0,0 +1,1350 @@
+1    ACME_DEBUG_ENABLE,0: "0"
+2    ACME_KEY_RENEWAL_DAYS,0: "30"
+3    ADTASKS_CRON,0: "0123456"
+4       (3) ADTASKS_CRON_BEGIN,0: "0"
+5       (3) ADTASKS_CRON_END,0: "86400"
+6       (3) ADTASKS_CRON_EVERY,0: "3600"
+7    AD_ENABLE_UI,0: "0"
+8    AD_LDAP_BIND_ACCOUNT,0: ""
+9    AD_LDAP_BIND_PASSWORD,0: ""
+10   AD_MACHINE,0: ""
+11   AD_MACHINE_CHANGEPW_INTERVAL,0: "30"
+12   AD_MACHINE_CREDENTIALS,0: ""
+13   BACKUP_ACCESS_RESTRICTED_ONLY,0: "1"
+14   BACKUP_BACKEND,0: "NEW"
+15   BACKUP_COMPRESS_ENABLE,0: "1"
+16   BACKUP_CRON,1: "6"
+17      (16) BACKUP_CRON_BEGIN,0: "79200"
+18   BACKUP_CRON,2: "123456"
+19      (18) BACKUP_CRON_BEGIN,0: "23400"
+20      (18) BACKUP_CRON_IS_DIFF_BACKUP,0: "1"
+21   BACKUP_CRON,3: "123456"
+22      (21) BACKUP_CRON_BEGIN,0: "45000"
+23      (21) BACKUP_CRON_IS_DIFF_BACKUP,0: "1"
+24   BACKUP_CRON,4: "0123456"
+25      (24) BACKUP_CRON_BEGIN,0: "68400"
+26      (24) BACKUP_CRON_IS_DIFF_BACKUP,0: "1"
+27   BACKUP_DAYS_TO_KEEP,0: "3"
+28   BACKUP_ENCRYPT_ENABLE,0: "0"
+29   BACKUP_ENCRYPT_PASSWORD,0: ""
+30   BACKUP_ENCRYPT_SECRET_HASH,0: ""
+31   BACKUP_PUSH_DAYS_TO_KEEP,0: "3"
+32   BACKUP_PUSH_SERVERTYPE,0: "SMB"
+33   BACKUP_PUSH_SETS_TO_KEEP,0: "1"
+34   BACKUP_RESTRICT_AUTH_ENABLE,0: "1"
+35   BACKUP_RESTRICT_IP_ENABLE,0: "0"
+36   BACKUP_RESTRICT_IP_REF,0: "0"
+37   BACKUP_RESTRICT_LOGIN,0: "backup"
+38   BACKUP_RESTRICT_PASSWORD,0: "kadkiovop0"
+39   BACKUP_SETS_TO_KEEP,0: "2"
+40   BACKUP_VOLUME_SIZE,0: "649"
+41   CNFCLEAN_CRON,0: "0123456"
+42      (41) CNFCLEAN_CRON_BEGIN,0: "0"
+43      (41) CNFCLEAN_CRON_END,0: "86400"
+44      (41) CNFCLEAN_CRON_EVERY,0: "3600"
+45   DATA_PRIVACY_ATTACHMENT_QUARANTINE_ACCESS,0: "FULL"
+46   DATA_PRIVACY_SPAM_QUARANTINE_ACCESS,0: "PROTECTED"
+47   DHCP_DNS_SERVER,0: ""
+48   DHCP_DWARDEN_ENABLE,0: "1"
+49   DHCP_ENABLE,0: "0"
+50   DHCP_EXCLUDE_NIC_REF,0: "0"
+51   DHCP_EXCLUDE_NIC_REF,2: "2"
+52   DHCP_EXCLUDE_NIC_REF,3: "3"
+53   DHCP_GATEWAY_IP,0: ""
+54   DHCP_LEASE_TIME_SEC,0: "86400"
+55   DHCP_WINS_SERVER,0: ""
+56   DIALOUT_TIMESERVER,0: "0.intra2net.pool.ntp.org"
+57   DIALOUT_TIMESERVER,1: "1.intra2net.pool.ntp.org"
+58   DIALOUT_TIMESERVER,2: "2.intra2net.pool.ntp.org"
+59   DISPLAY_RESTART_CRON,0: "0123456"
+60      (59) DISPLAY_RESTART_CRON_BEGIN,0: "1620"
+61      (59) DISPLAY_RESTART_CRON_END,0: "84780"
+62      (59) DISPLAY_RESTART_CRON_EVERY,0: "3600"
+63   DNS_REBIND_PREVENTION,0: "1"
+64   EMAILFILTER_BAN_ACTION,0: "REPLACE"
+65   EMAILFILTER_BAN_DEFAULT_FILTERLIST_REF,0: "2"
+66   EMAILFILTER_BAN_DELETEDAYS,0: "60"
+67   EMAILFILTER_BAN_FILTERLIST,0: "Vordefiniert: Alles erlaubt"
+68      (67) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "ALLOW"
+69      (67) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: ""
+70      (67) EMAILFILTER_BAN_FILTERLIST_FILTER_OFFICE_FILES,0: "BY_FILTERLIST"
+71      (67) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: ""
+72      (67) EMAILFILTER_BAN_FILTERLIST_MODE,0: "BLOCK"
+73      (67) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "0"
+74   EMAILFILTER_BAN_FILTERLIST,1: "Vordefiniert: Alles verboten"
+75      (74) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK"
+76      (74) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: ""
+77      (74) EMAILFILTER_BAN_FILTERLIST_FILTER_OFFICE_FILES,0: "BY_FILTERLIST"
+78      (74) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: ""
+79         (78) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "text/plain"
+80         (78) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "text/html"
+81      (74) EMAILFILTER_BAN_FILTERLIST_MODE,0: "ALLOW"
+82      (74) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "1"
+83   EMAILFILTER_BAN_FILTERLIST,2: "Vordefiniert: Ausführbare Dateien"
+84      (83) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK"
+85      (83) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: ""
+86         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,0: "ace"
+87         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,1: "ade"
+88         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,2: "adp"
+89         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,3: "ani"
+90         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,4: "application"
+91         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,5: "arc"
+92         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,6: "arj"
+93         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,7: "bas"
+94         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,8: "bat"
+95         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,9: "cdxml"
+96         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,10: "chm"
+97         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,11: "cmd"
+98         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,12: "com"
+99         (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,13: "cpl"
+100        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,14: "crt"
+101        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,15: "cur"
+102        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,16: "dll"
+103        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,17: "exe"
+104        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,18: "gadget"
+105        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,19: "grp"
+106        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,20: "hlp"
+107        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,21: "hta"
+108        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,22: "ico"
+109        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,23: "inf"
+110        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,24: "ins"
+111        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,25: "iso"
+112        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,26: "isp"
+113        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,27: "jar"
+114        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,28: "js"
+115        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,29: "jse"
+116        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,30: "lha"
+117        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,31: "lnk"
+118        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,32: "mdb"
+119        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,33: "mde"
+120        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,34: "msc"
+121        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,35: "msh"
+122        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,36: "msh1"
+123        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,37: "msh1xml"
+124        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,38: "msh2"
+125        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,39: "msh2xml"
+126        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,40: "mshxml"
+127        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,41: "msi"
+128        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,42: "msp"
+129        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,43: "mst"
+130        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,44: "ocx"
+131        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,45: "pcd"
+132        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,46: "pif"
+133        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,47: "ps1"
+134        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,48: "ps1xml"
+135        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,49: "ps2"
+136        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,50: "ps2xml"
+137        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,51: "psc1"
+138        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,52: "psc2"
+139        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,53: "psd1"
+140        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,54: "psm1"
+141        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,55: "pssc"
+142        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,56: "reg"
+143        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,57: "scf"
+144        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,58: "scr"
+145        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,59: "sct"
+146        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,60: "shb"
+147        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,61: "shs"
+148        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,62: "swf"
+149        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,63: "url"
+150        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,64: "vb"
+151        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,65: "vbe"
+152        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,66: "vbs"
+153        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,67: "ws"
+154        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,68: "wsc"
+155        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,69: "wsf"
+156        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,70: "wsh"
+157        (85) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS_NAME,71: "zoo"
+158     (83) EMAILFILTER_BAN_FILTERLIST_FILTER_OFFICE_FILES,0: "SUSPICIOUS_MACROS"
+159     (83) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: ""
+160        (159) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "application/x-msdos-program"
+161        (159) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "application/x-msdownload"
+162     (83) EMAILFILTER_BAN_FILTERLIST_MODE,0: "BLOCK"
+163     (83) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "2"
+164  EMAILFILTER_BAN_INCOMING,0: "1"
+165  EMAILFILTER_BAN_NOTIFY_ADMIN,0: "1"
+166  EMAILFILTER_BAN_NOTIFY_ADMIN_REF,0: "-1"
+167  EMAILFILTER_BAN_NOTIFY_LOCAL_SENDER,0: "0"
+168  EMAILFILTER_BAN_OUTGOING,0: "0"
+169  EMAIL_AUTH_ONLY_CONNECTION_ENCRYPTED,0: "1"
+170  EMAIL_BLOCK_NEW,0: "0"
+171  EMAIL_DELAYWARNING_HOURS,0: "24"
+172  EMAIL_FORWARD_USE_SERVER_NAME,0: "0"
+173  EMAIL_ILLEGAL_CHAR_HANDLING,0: "STRIP"
+174  EMAIL_MAX_INET_PARALLEL_SEND,0: "5"
+175  EMAIL_MAX_QUEUE_LIFETIME,0: "120"
+176  EMAIL_MAX_RECIPIENTS,0: "100"
+177  EMAIL_MAX_SIZE,0: "100"
+178  EMAIL_POSTMASTER_NOTIFY_BOUNCE,0: "0"
+179  EMAIL_POSTMASTER_REF,0: "1"
+180  EMAIL_RELAY,0: "Standard"
+181     (180) EMAIL_RELAY_AUTH_LOGIN,0: ""
+182     (180) EMAIL_RELAY_AUTH_MODE,0: "none"
+183     (180) EMAIL_RELAY_AUTH_PASSWORD,0: ""
+184     (180) EMAIL_RELAY_MODE,0: "direct"
+185     (180) EMAIL_RELAY_SERVER,0: ""
+186     (180) EMAIL_RELAY_SERVER_PORT,0: "25"
+187     (180) EMAIL_RELAY_TYPE,0: "DEFAULT"
+188  EMAIL_RELAY_DEFAULT_REF,0: "0"
+189  EMAIL_TIMEOUT_FETCHMAIL,0: "90"
+190  EMAIL_TLS_RECEIVE,0: "1"
+191  EMAIL_TLS_SEND,0: "1"
+192  EMAIL_TRANSFER_DISABLED,0: "0"
+193  FAX_IDENTIFICATION_NAME,0: "Fax"
+194  FAX_IDENTIFICATION_NUMBER,0: ""
+195  FAX_RECEIVE_ENABLE,0: "0"
+196  FAX_SEND_ENABLE,0: "0"
+197  FAX_SEND_MSN_REF,0: "-1"
+198  FIREWALL_BLOCK_PASSWORD_GUESSING,0: "1"
+199  FIREWALL_CHECK_LOCAL_SPOOFING,0: "1"
+200  FIREWALL_CHECK_MACADDR,0: "1"
+201  FIREWALL_NETGROUP,99: "QA host IP"
+202     (201) FIREWALL_NETGROUP_NETWORK,0: ""
+203        (202) FIREWALL_NETGROUP_NETWORK_IP,0: "192.168.1.254"
+204        (202) FIREWALL_NETGROUP_NETWORK_NETMASK,0: "255.255.255.255"
+205  FIREWALL_RULESET,1: "Basis LAN und lokale Netze"
+206     (205) FIREWALL_RULESET_AUTOMATIC_ANSWER_RULE,0: "1"
+207     (205) FIREWALL_RULESET_PREDEFINED_ID,0: "1"
+208     (205) FIREWALL_RULESET_PROFILE_TYPE,0: "FULL"
+209     (205) FIREWALL_RULESET_RULE,1: ""
+210        (209) FIREWALL_RULESET_RULE_ACTION,0: "ACCEPT"
+211        (209) FIREWALL_RULESET_RULE_DST_MAGIC,0: "0"
+212        (209) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+213        (209) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "0"
+214        (209) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: ""
+215        (209) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: ""
+216        (209) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "0"
+217        (209) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: ""
+218        (209) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,0: "1"
+219        (209) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,1: "2"
+220        (209) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,2: "22"
+221        (209) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,3: "39"
+222        (209) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,4: "6"
+223        (209) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,5: "9"
+224        (209) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,6: "24"
+225     (205) FIREWALL_RULESET_RULE,2: "Zugriff auf alle lokalen Netze"
+226        (225) FIREWALL_RULESET_RULE_ACTION,0: "ACCEPT"
+227        (225) FIREWALL_RULESET_RULE_DST_MAGIC,0: "0"
+228           (227) FIREWALL_RULESET_RULE_DST_MAGIC_IS_NOT,0: "1"
+229        (225) FIREWALL_RULESET_RULE_DST_MAGIC,1: "1"
+230        (225) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+231        (225) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "0"
+232        (225) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: ""
+233        (225) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: ""
+234        (225) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "0"
+235        (225) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: ""
+236     (205) FIREWALL_RULESET_RULE,3: ""
+237        (236) FIREWALL_RULESET_RULE_ACTION,0: "REJECT"
+238        (236) FIREWALL_RULESET_RULE_CHECK_TCP_FLAGS,0: "DISABLED"
+239        (236) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+240        (236) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "1"
+241        (236) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: "5"
+242        (236) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_PERIOD,0: "SEC"
+243        (236) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: "5"
+244        (236) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "1"
+245        (236) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: "REJECT local"
+246        (236) FIREWALL_RULESET_RULE_TIME_INCLUDE_TIME_REF,0: "-1"
+247     (205) FIREWALL_RULESET_USAGE,0: "LANVPN"
+248  FIREWALL_RULESET,2: "Nur E-Mail"
+249     (248) FIREWALL_RULESET_CLIENT_ACCESSRIGHT,0: "sl_mail"
+250     (248) FIREWALL_RULESET_CLIENT_ACCESS_ALL_NETWORKS,0: "1"
+251     (248) FIREWALL_RULESET_CLIENT_FAX_SEND_ALLOWED,0: "1"
+252     (248) FIREWALL_RULESET_CLIENT_MAIL_LOCAL_ONLY,0: "1"
+253     (248) FIREWALL_RULESET_CLIENT_PROXY_MODE,0: "blocked"
+254     (248) FIREWALL_RULESET_PREDEFINED_ID,0: "2"
+255     (248) FIREWALL_RULESET_PROFILE_TYPE,0: "SIMPLE_CLIENT"
+256  FIREWALL_RULESET,3: "WWW, FTP, E-Mail"
+257     (256) FIREWALL_RULESET_CLIENT_ACCESSRIGHT,0: "sl_web"
+258     (256) FIREWALL_RULESET_CLIENT_ACCESS_ALL_NETWORKS,0: "1"
+259     (256) FIREWALL_RULESET_CLIENT_FAX_SEND_ALLOWED,0: "1"
+260     (256) FIREWALL_RULESET_CLIENT_MAIL_LOCAL_ONLY,0: "1"
+261     (256) FIREWALL_RULESET_CLIENT_PROXY_MODE,0: "allowed"
+262     (256) FIREWALL_RULESET_PREDEFINED_ID,0: "3"
+263     (256) FIREWALL_RULESET_PROFILE_TYPE,0: "SIMPLE_CLIENT"
+264  FIREWALL_RULESET,4: "WWW, FTP, E-Mail + Transp. Proxy"
+265     (264) FIREWALL_RULESET_CLIENT_ACCESSRIGHT,0: "sl_web"
+266     (264) FIREWALL_RULESET_CLIENT_ACCESS_ALL_NETWORKS,0: "1"
+267     (264) FIREWALL_RULESET_CLIENT_FAX_SEND_ALLOWED,0: "1"
+268     (264) FIREWALL_RULESET_CLIENT_MAIL_LOCAL_ONLY,0: "1"
+269     (264) FIREWALL_RULESET_CLIENT_PROXY_MODE,0: "trans"
+270     (264) FIREWALL_RULESET_PREDEFINED_ID,0: "4"
+271     (264) FIREWALL_RULESET_PROFILE_TYPE,0: "SIMPLE_CLIENT"
+272  FIREWALL_RULESET,5: "Vollzugriff"
+273     (272) FIREWALL_RULESET_CLIENT_ACCESSRIGHT,0: "sl_all"
+274     (272) FIREWALL_RULESET_CLIENT_ACCESS_ALL_NETWORKS,0: "1"
+275     (272) FIREWALL_RULESET_CLIENT_FAX_SEND_ALLOWED,0: "1"
+276     (272) FIREWALL_RULESET_CLIENT_MAIL_LOCAL_ONLY,0: "0"
+277     (272) FIREWALL_RULESET_CLIENT_PROXY_MODE,0: "allowed"
+278     (272) FIREWALL_RULESET_PREDEFINED_ID,0: "5"
+279     (272) FIREWALL_RULESET_PROFILE_TYPE,0: "SIMPLE_CLIENT"
+280  FIREWALL_RULESET,6: "Basis Provider"
+281     (280) FIREWALL_RULESET_PREDEFINED_ID,0: "6"
+282     (280) FIREWALL_RULESET_PROFILE_TYPE,0: "SIMPLE_PROVIDER"
+283     (280) FIREWALL_RULESET_PROVIDER_HTTPS_OPEN,0: "0"
+284     (280) FIREWALL_RULESET_PROVIDER_HTTP_OPEN,0: "0"
+285     (280) FIREWALL_RULESET_PROVIDER_POP3SIMAPS_OPEN,0: "0"
+286     (280) FIREWALL_RULESET_PROVIDER_PORT_FORWARDING_ENABLE,0: "1"
+287     (280) FIREWALL_RULESET_PROVIDER_SMTP_OPEN,0: "0"
+288     (280) FIREWALL_RULESET_PROVIDER_VPN_OPEN,0: "0"
+289  FIREWALL_RULESET,7: "HTTPS, Let's Encrypt + VPN"
+290     (289) FIREWALL_RULESET_PREDEFINED_ID,0: "7"
+291     (289) FIREWALL_RULESET_PROFILE_TYPE,0: "SIMPLE_PROVIDER"
+292     (289) FIREWALL_RULESET_PROVIDER_HTTPS_OPEN,0: "1"
+293     (289) FIREWALL_RULESET_PROVIDER_HTTP_OPEN,0: "1"
+294     (289) FIREWALL_RULESET_PROVIDER_POP3SIMAPS_OPEN,0: "0"
+295     (289) FIREWALL_RULESET_PROVIDER_PORT_FORWARDING_ENABLE,0: "1"
+296     (289) FIREWALL_RULESET_PROVIDER_SMTP_OPEN,0: "0"
+297     (289) FIREWALL_RULESET_PROVIDER_VPN_OPEN,0: "1"
+298  FIREWALL_RULESET,8: "Nur Internet"
+299     (298) FIREWALL_RULESET_PREDEFINED_ID,0: "8"
+300     (298) FIREWALL_RULESET_PROFILE_TYPE,0: "FULL"
+301     (298) FIREWALL_RULESET_RULE,1: ""
+302        (301) FIREWALL_RULESET_RULE_ACTION,0: "ACCEPT"
+303        (301) FIREWALL_RULESET_RULE_DST_MAGIC,0: "8"
+304        (301) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+305        (301) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "0"
+306        (301) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: ""
+307        (301) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: ""
+308        (301) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "0"
+309        (301) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: ""
+310     (298) FIREWALL_RULESET_USAGE,0: "SOCKS"
+311  FIREWALL_RULESET,9: "Basis LAN"
+312     (311) FIREWALL_RULESET_AUTOMATIC_ANSWER_RULE,0: "1"
+313     (311) FIREWALL_RULESET_PREDEFINED_ID,0: "9"
+314     (311) FIREWALL_RULESET_PROFILE_TYPE,0: "FULL"
+315     (311) FIREWALL_RULESET_RULE,1: ""
+316        (315) FIREWALL_RULESET_RULE_ACTION,0: "ACCEPT"
+317        (315) FIREWALL_RULESET_RULE_DST_MAGIC,0: "0"
+318        (315) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+319        (315) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "0"
+320        (315) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: ""
+321        (315) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: ""
+322        (315) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "0"
+323        (315) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: ""
+324        (315) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,0: "1"
+325        (315) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,1: "2"
+326        (315) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,2: "22"
+327        (315) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,3: "39"
+328        (315) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,4: "6"
+329        (315) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,5: "9"
+330        (315) FIREWALL_RULESET_RULE_SERVICE_INCLUDE_SERVICEGROUP_REF,6: "24"
+331     (311) FIREWALL_RULESET_RULE,2: ""
+332        (331) FIREWALL_RULESET_RULE_ACTION,0: "REJECT"
+333        (331) FIREWALL_RULESET_RULE_CHECK_TCP_FLAGS,0: "DISABLED"
+334        (331) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+335        (331) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "1"
+336        (331) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: "5"
+337        (331) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_PERIOD,0: "SEC"
+338        (331) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: "5"
+339        (331) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "1"
+340        (331) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: "REJECT local"
+341        (331) FIREWALL_RULESET_RULE_TIME_INCLUDE_TIME_REF,0: "-1"
+342     (311) FIREWALL_RULESET_USAGE,0: "LANVPN"
+343  FIREWALL_RULESET,99: "Pyro4 + Basis-LAN"
+344     (343) FIREWALL_RULESET_AUTOMATIC_ANSWER_RULE,0: "1"
+345     (343) FIREWALL_RULESET_PROFILE_TYPE,0: "FULL"
+346     (343) FIREWALL_RULESET_RULE,1: "Pyro4 access"
+347        (346) FIREWALL_RULESET_RULE_ACTION,0: "ACCEPT"
+348        (346) FIREWALL_RULESET_RULE_DST_MAGIC,0: "0"
+349        (346) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+350        (346) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "0"
+351        (346) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: ""
+352        (346) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: ""
+353        (346) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "0"
+354        (346) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: ""
+355        (346) FIREWALL_RULESET_RULE_SRC_INCLUDE_NETGROUP_REF,0: "99"
+356     (343) FIREWALL_RULESET_RULE,2: "Redirection"
+357        (356) FIREWALL_RULESET_RULE_ACTION,0: "REDIRECT"
+358        (356) FIREWALL_RULESET_RULE_ACTION_REDIRECT_REF,0: "1"
+359        (356) FIREWALL_RULESET_RULE_LIMIT_FOR_ACTION_ENABLE,0: "0"
+360        (356) FIREWALL_RULESET_RULE_LIMIT_FOR_LOG_ENABLE,0: "0"
+361        (356) FIREWALL_RULESET_RULE_LIMIT_PACKETS_AVERAGE_COUNT,0: ""
+362        (356) FIREWALL_RULESET_RULE_LIMIT_PACKETS_PEAK_COUNT,0: ""
+363        (356) FIREWALL_RULESET_RULE_LOG_ENABLE,0: "0"
+364        (356) FIREWALL_RULESET_RULE_LOG_MESSAGE,0: ""
+365     (343) FIREWALL_RULESET_USAGE,0: "LANVPN"
+366  FIREWALL_SERVICEGROUP,1: "http"
+367     (366) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "1"
+368     (366) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+369        (368) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+370        (368) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "80"
+371  FIREWALL_SERVICEGROUP,2: "https"
+372     (371) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "2"
+373     (371) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+374        (373) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+375        (373) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "443"
+376  FIREWALL_SERVICEGROUP,3: "ftp-data"
+377     (376) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "3"
+378     (376) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+379        (378) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+380        (378) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_SRC_PORT,0: "20"
+381  FIREWALL_SERVICEGROUP,4: "ftp-control"
+382     (381) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "4"
+383     (381) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+384        (383) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+385        (383) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "21"
+386  FIREWALL_SERVICEGROUP,5: "ftp"
+387     (386) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "3"
+388     (386) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "4"
+389     (386) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "5"
+390  FIREWALL_SERVICEGROUP,6: "ssh"
+391     (390) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "6"
+392     (390) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+393        (392) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+394        (392) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "22"
+395  FIREWALL_SERVICEGROUP,7: "telnet"
+396     (395) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "7"
+397     (395) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+398        (397) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+399        (397) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "23"
+400  FIREWALL_SERVICEGROUP,8: "smtp"
+401     (400) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "8"
+402     (400) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+403        (402) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+404        (402) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "25"
+405  FIREWALL_SERVICEGROUP,9: "dns"
+406     (405) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "9"
+407     (405) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+408        (407) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+409        (407) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "53"
+410     (405) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+411        (410) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+412        (410) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "53"
+413  FIREWALL_SERVICEGROUP,10: "dhcp-client"
+414     (413) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "10"
+415     (413) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+416        (415) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+417        (415) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_SRC_PORT,0: "68"
+418  FIREWALL_SERVICEGROUP,11: "dhcp-server"
+419     (418) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "11"
+420     (418) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+421        (420) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+422        (420) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "67"
+423  FIREWALL_SERVICEGROUP,12: "dhcp"
+424     (423) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "10"
+425     (423) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "11"
+426     (423) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "12"
+427  FIREWALL_SERVICEGROUP,13: "tftp"
+428     (427) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "13"
+429     (427) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+430        (429) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+431        (429) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "69"
+432  FIREWALL_SERVICEGROUP,14: "pop3"
+433     (432) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "14"
+434     (432) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+435        (434) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+436        (434) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "110"
+437  FIREWALL_SERVICEGROUP,15: "ident"
+438     (437) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "15"
+439     (437) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+440        (439) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+441        (439) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "113"
+442  FIREWALL_SERVICEGROUP,16: "nntp"
+443     (442) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "16"
+444     (442) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+445        (444) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+446        (444) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "119"
+447  FIREWALL_SERVICEGROUP,17: "ntp"
+448     (447) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "17"
+449     (447) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+450        (449) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+451        (449) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "123"
+452     (447) FIREWALL_SERVICEGROUP_TYPE,1: "UDP"
+453        (452) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+454        (452) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "123"
+455  FIREWALL_SERVICEGROUP,18: "imap"
+456     (455) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "18"
+457     (455) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+458        (457) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+459        (457) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "143"
+460  FIREWALL_SERVICEGROUP,19: "netbios-name"
+461     (460) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "19"
+462     (460) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+463        (462) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+464        (462) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "137"
+465     (460) FIREWALL_SERVICEGROUP_TYPE,1: "UDP"
+466        (465) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+467        (465) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "137"
+468  FIREWALL_SERVICEGROUP,20: "netbios-datagram"
+469     (468) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "20"
+470     (468) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+471        (470) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+472        (470) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "138"
+473     (468) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+474        (473) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+475        (473) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "138"
+476  FIREWALL_SERVICEGROUP,21: "netbios-session"
+477     (476) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "21"
+478     (476) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+479        (478) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+480        (478) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "139"
+481     (476) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+482        (481) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+483        (481) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "139"
+484  FIREWALL_SERVICEGROUP,22: "netbios"
+485     (484) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "19"
+486     (484) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "20"
+487     (484) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,2: "21"
+488     (484) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "22"
+489  FIREWALL_SERVICEGROUP,23: "ldap"
+490     (489) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "23"
+491     (489) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+492        (491) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+493        (491) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "389"
+494  FIREWALL_SERVICEGROUP,24: "cifs"
+495     (494) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "24"
+496     (494) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+497        (496) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+498        (496) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "445"
+499     (494) FIREWALL_SERVICEGROUP_TYPE,1: "UDP"
+500        (499) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+501        (499) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "445"
+502  FIREWALL_SERVICEGROUP,25: "ike"
+503     (502) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "25"
+504     (502) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+505        (504) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+506        (504) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "500"
+507  FIREWALL_SERVICEGROUP,26: "syslog"
+508     (507) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "26"
+509     (507) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+510        (509) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+511        (509) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "514"
+512  FIREWALL_SERVICEGROUP,27: "nntps"
+513     (512) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "27"
+514     (512) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+515        (514) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+516        (514) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "563"
+517  FIREWALL_SERVICEGROUP,28: "ipp"
+518     (517) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "28"
+519     (517) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+520        (519) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+521        (519) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "631"
+522  FIREWALL_SERVICEGROUP,29: "ldaps"
+523     (522) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "29"
+524     (522) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+525        (524) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+526        (524) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "636"
+527  FIREWALL_SERVICEGROUP,30: "rsync"
+528     (527) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "30"
+529     (527) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+530        (529) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+531        (529) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "873"
+532  FIREWALL_SERVICEGROUP,31: "imaps"
+533     (532) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "31"
+534     (532) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+535        (534) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+536        (534) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "993"
+537  FIREWALL_SERVICEGROUP,32: "pop3s"
+538     (537) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "32"
+539     (537) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+540        (539) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+541        (539) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "995"
+542  FIREWALL_SERVICEGROUP,33: "socks"
+543     (542) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "33"
+544     (542) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+545        (544) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+546        (544) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "1080"
+547  FIREWALL_SERVICEGROUP,34: "snmp"
+548     (547) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "34"
+549     (547) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+550        (549) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+551        (549) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "161"
+552     (547) FIREWALL_SERVICEGROUP_TYPE,1: "UDP"
+553        (552) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+554        (552) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "162"
+555  FIREWALL_SERVICEGROUP,35: "http-proxy"
+556     (555) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "35"
+557     (555) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+558        (557) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+559        (557) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "3128"
+560     (555) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+561        (560) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+562        (560) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "8080"
+563  FIREWALL_SERVICEGROUP,36: "http, https, ftp"
+564     (563) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "1"
+565     (563) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "2"
+566     (563) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,2: "5"
+567     (563) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,3: "35"
+568     (563) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "36"
+569  FIREWALL_SERVICEGROUP,37: "email"
+570     (569) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "8"
+571     (569) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "14"
+572     (569) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,2: "18"
+573     (569) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,3: "31"
+574     (569) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,4: "32"
+575     (569) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,5: "83"
+576     (569) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "37"
+577  FIREWALL_SERVICEGROUP,38: "ping"
+578     (577) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "38"
+579     (577) FIREWALL_SERVICEGROUP_TYPE,0: "ICMP"
+580        (579) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+581        (579) FIREWALL_SERVICEGROUP_TYPE_ICMP_NUM,0: "8"
+582     (577) FIREWALL_SERVICEGROUP_TYPE,1: "ICMP"
+583        (582) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+584        (582) FIREWALL_SERVICEGROUP_TYPE_ICMP_NUM,0: "0"
+585  FIREWALL_SERVICEGROUP,39: "icmp-basis"
+586     (585) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "38"
+587     (585) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "39"
+588     (585) FIREWALL_SERVICEGROUP_TYPE,0: "ICMP"
+589        (588) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+590        (588) FIREWALL_SERVICEGROUP_TYPE_ICMP_NUM,0: "12"
+591     (585) FIREWALL_SERVICEGROUP_TYPE,1: "ICMP"
+592        (591) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+593        (591) FIREWALL_SERVICEGROUP_TYPE_ICMP_NUM,0: "3"
+594     (585) FIREWALL_SERVICEGROUP_TYPE,2: "ICMP"
+595        (594) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+596        (594) FIREWALL_SERVICEGROUP_TYPE_ICMP_NUM,0: "11"
+597  FIREWALL_SERVICEGROUP,40: "hylafax"
+598     (597) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "40"
+599     (597) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+600        (599) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "Data"
+601        (599) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_SRC_PORT,0: "4558"
+602     (597) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+603        (602) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "Control"
+604        (602) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "4559"
+605  FIREWALL_SERVICEGROUP,41: "tcp"
+606     (605) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "41"
+607     (605) FIREWALL_SERVICEGROUP_TYPE,0: "OTHER"
+608        (607) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "tcp"
+609        (607) FIREWALL_SERVICEGROUP_TYPE_OTHER_IP_PROT_NUM,0: "6"
+610  FIREWALL_SERVICEGROUP,42: "udp"
+611     (610) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "42"
+612     (610) FIREWALL_SERVICEGROUP_TYPE,0: "OTHER"
+613        (612) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "udp"
+614        (612) FIREWALL_SERVICEGROUP_TYPE_OTHER_IP_PROT_NUM,0: "17"
+615  FIREWALL_SERVICEGROUP,43: "l2tp"
+616     (615) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "43"
+617     (615) FIREWALL_SERVICEGROUP_TYPE,0: "OTHER"
+618        (617) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "l2tp"
+619        (617) FIREWALL_SERVICEGROUP_TYPE_OTHER_IP_PROT_NUM,0: "115"
+620  FIREWALL_SERVICEGROUP,44: "gre"
+621     (620) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "44"
+622     (620) FIREWALL_SERVICEGROUP_TYPE,0: "OTHER"
+623        (622) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "gre"
+624        (622) FIREWALL_SERVICEGROUP_TYPE_OTHER_IP_PROT_NUM,0: "47"
+625  FIREWALL_SERVICEGROUP,45: "esp"
+626     (625) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "45"
+627     (625) FIREWALL_SERVICEGROUP_TYPE,0: "OTHER"
+628        (627) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "esp"
+629        (627) FIREWALL_SERVICEGROUP_TYPE_OTHER_IP_PROT_NUM,0: "50"
+630  FIREWALL_SERVICEGROUP,46: "ah"
+631     (630) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "46"
+632     (630) FIREWALL_SERVICEGROUP_TYPE,0: "OTHER"
+633        (632) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "ah"
+634        (632) FIREWALL_SERVICEGROUP_TYPE_OTHER_IP_PROT_NUM,0: "51"
+635  FIREWALL_SERVICEGROUP,47: "irc-assigned"
+636     (635) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "47"
+637     (635) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+638        (637) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "irc"
+639        (637) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "194"
+640  FIREWALL_SERVICEGROUP,48: "ircs"
+641     (640) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "48"
+642     (640) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+643        (642) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "ircs"
+644        (642) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "994"
+645  FIREWALL_SERVICEGROUP,49: "irc-common"
+646     (645) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "49"
+647     (645) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+648        (647) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+649        (647) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "6667"
+650     (645) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+651        (650) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+652        (650) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "6666"
+653  FIREWALL_SERVICEGROUP,50: "bittorrent-dst"
+654     (653) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "50"
+655     (653) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+656        (655) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+657        (655) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "6881"
+658        (655) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT_END,0: "6889"
+659  FIREWALL_SERVICEGROUP,51: "bittorrent-src"
+660     (659) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "51"
+661     (659) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+662        (661) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+663        (661) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_SRC_PORT,0: "6881"
+664        (661) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_SRC_PORT_END,0: "6889"
+665  FIREWALL_SERVICEGROUP,52: "bittorrent"
+666     (665) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "50"
+667     (665) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "51"
+668     (665) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "52"
+669  FIREWALL_SERVICEGROUP,53: "edonkey-server"
+670     (669) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "53"
+671     (669) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+672        (671) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+673        (671) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "4662"
+674     (669) FIREWALL_SERVICEGROUP_TYPE,1: "UDP"
+675        (674) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+676        (674) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "4665"
+677  FIREWALL_SERVICEGROUP,54: "edonkey-client"
+678     (677) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "54"
+679     (677) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+680        (679) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+681        (679) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_SRC_PORT,0: "4662"
+682     (677) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+683        (682) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+684        (682) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "4662"
+685  FIREWALL_SERVICEGROUP,55: "edonkey"
+686     (685) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "53"
+687     (685) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "54"
+688     (685) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "55"
+689  FIREWALL_SERVICEGROUP,56: "kazaa"
+690     (689) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "56"
+691     (689) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+692        (691) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+693        (691) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "1214"
+694  FIREWALL_SERVICEGROUP,57: "gnutella"
+695     (694) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "57"
+696     (694) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+697        (696) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "gnutella-rtr"
+698        (696) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "6348"
+699     (694) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+700        (699) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "gnutella-svc"
+701        (699) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "6347"
+702  FIREWALL_SERVICEGROUP,58: "icq/aol-im"
+703     (702) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "58"
+704     (702) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+705        (704) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+706        (704) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5190"
+707  FIREWALL_SERVICEGROUP,59: "peer 2 peer"
+708     (707) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "52"
+709     (707) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "55"
+710     (707) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,2: "56"
+711     (707) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,3: "57"
+712     (707) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "59"
+713  FIREWALL_SERVICEGROUP,60: "chat"
+714     (713) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "58"
+715     (713) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "61"
+716     (713) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,2: "64"
+717     (713) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "60"
+718  FIREWALL_SERVICEGROUP,61: "irc"
+719     (718) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "47"
+720     (718) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "48"
+721     (718) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,2: "49"
+722     (718) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "61"
+723  FIREWALL_SERVICEGROUP,62: "pptp-control"
+724     (723) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "62"
+725     (723) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+726        (725) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+727        (725) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "1723"
+728  FIREWALL_SERVICEGROUP,63: "pcanywhere"
+729     (728) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "63"
+730     (728) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+731        (730) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "pcanywhere-stat"
+732        (730) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5632"
+733     (728) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+734        (733) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: "pcanywhere-data"
+735        (733) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5631"
+736  FIREWALL_SERVICEGROUP,64: "msn messenger"
+737     (736) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "64"
+738     (736) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+739        (738) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+740        (738) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "1863"
+741  FIREWALL_SERVICEGROUP,65: "printserver"
+742     (741) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "65"
+743     (741) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+744        (743) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+745        (743) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "9100"
+746  FIREWALL_SERVICEGROUP,66: "lotus notes"
+747     (746) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "66"
+748     (746) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+749        (748) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+750        (748) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "1352"
+751  FIREWALL_SERVICEGROUP,67: "rdp"
+752     (751) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "67"
+753     (751) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+754        (753) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+755        (753) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "3389"
+756  FIREWALL_SERVICEGROUP,68: "hbci"
+757     (756) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "68"
+758     (756) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+759        (758) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+760        (758) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "3000"
+761  FIREWALL_SERVICEGROUP,69: "pptp"
+762     (761) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "44"
+763     (761) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "62"
+764     (761) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "69"
+765  FIREWALL_SERVICEGROUP,70: "ipsec"
+766     (765) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "25"
+767     (765) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "45"
+768     (765) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,2: "46"
+769     (765) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,3: "82"
+770     (765) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "70"
+771  FIREWALL_SERVICEGROUP,71: "rpc"
+772     (771) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "71"
+773     (771) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+774        (773) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+775        (773) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "111"
+776     (771) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+777        (776) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+778        (776) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "111"
+779  FIREWALL_SERVICEGROUP,72: "ms rpc mapper"
+780     (779) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "72"
+781     (779) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+782        (781) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+783        (781) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "135"
+784  FIREWALL_SERVICEGROUP,73: "kerberos"
+785     (784) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "73"
+786     (784) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+787        (786) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+788        (786) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "88"
+789     (784) FIREWALL_SERVICEGROUP_TYPE,1: "UDP"
+790        (789) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+791        (789) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "88"
+792  FIREWALL_SERVICEGROUP,74: "ldap global catalog"
+793     (792) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "74"
+794     (792) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+795        (794) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+796        (794) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "3268"
+797  FIREWALL_SERVICEGROUP,75: "ldaps global catalog"
+798     (797) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "75"
+799     (797) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+800        (799) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+801        (799) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "3269"
+802  FIREWALL_SERVICEGROUP,76: "citrix ica"
+803     (802) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "76"
+804     (802) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+805        (804) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+806        (804) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "1494"
+807     (802) FIREWALL_SERVICEGROUP_TYPE,1: "TCP"
+808        (807) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+809        (807) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "2598"
+810  FIREWALL_SERVICEGROUP,77: "citrix browsing"
+811     (810) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "77"
+812     (810) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+813        (812) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+814        (812) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "1604"
+815  FIREWALL_SERVICEGROUP,78: "citrix"
+816     (815) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,0: "76"
+817     (815) FIREWALL_SERVICEGROUP_INCLUDE_SERVICEGROUP_REF,1: "77"
+818     (815) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "78"
+819  FIREWALL_SERVICEGROUP,79: "elster"
+820     (819) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "79"
+821     (819) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+822        (821) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+823        (821) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "8000"
+824  FIREWALL_SERVICEGROUP,80: "sip"
+825     (824) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "80"
+826     (824) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+827        (826) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+828        (826) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5060"
+829     (824) FIREWALL_SERVICEGROUP_TYPE,1: "UDP"
+830        (829) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+831        (829) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5060"
+832  FIREWALL_SERVICEGROUP,81: "sips"
+833     (832) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "81"
+834     (832) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+835        (834) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+836        (834) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5061"
+837  FIREWALL_SERVICEGROUP,82: "ike nat-t"
+838     (837) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "82"
+839     (837) FIREWALL_SERVICEGROUP_TYPE,0: "UDP"
+840        (839) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+841        (839) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "4500"
+842  FIREWALL_SERVICEGROUP,83: "smtp-submission"
+843     (842) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "83"
+844     (842) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+845        (844) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+846        (844) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "587"
+847  FIREWALL_SERVICEGROUP,84: "vnc listen"
+848     (847) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "84"
+849     (847) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+850        (849) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+851        (849) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5500"
+852  FIREWALL_SERVICEGROUP,85: "vnc java server"
+853     (852) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "85"
+854     (852) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+855        (854) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+856        (854) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5800"
+857  FIREWALL_SERVICEGROUP,86: "vnc server"
+858     (857) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "86"
+859     (857) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+860        (859) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+861        (859) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5900"
+862  FIREWALL_SERVICEGROUP,87: "citrix ima"
+863     (862) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "87"
+864     (862) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+865        (864) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+866        (864) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "2512"
+867  FIREWALL_SERVICEGROUP,88: "citrix cmc"
+868     (867) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "88"
+869     (867) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+870        (869) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+871        (869) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "2513"
+872  FIREWALL_SERVICEGROUP,89: "Fernwartung Hersteller"
+873     (872) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "89"
+874     (872) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+875        (874) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+876        (874) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "5000"
+877        (874) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT_END,0: "5050"
+878  GENERATE_CHGNR,0: "226"
+879  GROUP,1: "Administratoren"
+880     (879) GROUP_ACCESS_GO_ONLINE_ALLOWED,0: "1"
+881     (879) GROUP_EMAILFILTER_BAN_FILTERLIST_REF,0: "-1"
+882     (879) GROUP_EMAIL_RELAY_RIGHTS,0: "RELAY_FROM_INTRANET"
+883     (879) GROUP_PROXY_PROFILE_REF,0: "1"
+884  GROUP,2: "Alle"
+885     (884) GROUP_ACCESS_GO_ONLINE_ALLOWED,0: "1"
+886     (884) GROUP_ACCESS_GROUPWARE_ALLOWED,0: "1"
+887     (884) GROUP_ACCESS_INFORMATION_VERSION_ALLOWED,0: "1"
+888     (884) GROUP_ACCESS_MAINPAGE_ALLOWED,0: "1"
+889     (884) GROUP_ACCESS_USERMANAGER_OWN_PROFILE_FORWARDING_ALLOWED,0: "1"
+890     (884) GROUP_ACCESS_USERMANAGER_OWN_PROFILE_GROUPWARE_ALLOWED,0: "1"
+891     (884) GROUP_ACCESS_USERMANAGER_OWN_PROFILE_SETTINGS_ALLOWED,0: "1"
+892     (884) GROUP_ACCESS_USERMANAGER_OWN_PROFILE_SORTING_ALLOWED,0: "1"
+893     (884) GROUP_ACCESS_USERMANAGER_OWN_PROFILE_SPAMFILTER_ALLOWED,0: "1"
+894     (884) GROUP_ACCESS_USERMANAGER_OWN_PROFILE_VACATION_ALLOWED,0: "1"
+895     (884) GROUP_EMAILFILTER_BAN_FILTERLIST_REF,0: "-1"
+896     (884) GROUP_EMAIL_RELAY_RIGHTS,0: "RELAY_FROM_INTRANET"
+897     (884) GROUP_PROXY_PROFILE_REF,0: "1"
+898  GROUP_ADMINISTRATOR_GROUP_REF,0: "1"
+899  GROUP_DEFAULT_GROUP_REF,0: "2"
+900  KEY_OWN,1: "mis1.net.lan"
+901     (900) KEY_OWN_FINGERPRINT_MD5,0: "B7:2D:79:24:17:8C:C4:78:CD:9A:BF:D5:20:B5:DD:26"
+902     (900) KEY_OWN_FINGERPRINT_SHA1,0: "B4:18:35:0D:71:01:67:BE:D5:7D:D7:49:35:80:43:0F:CB:9E:BC:57"
+903     (900) KEY_OWN_HASH_ALGO,0: "SHA2_256"
+904     (900) KEY_OWN_ID_X509,0: "CN=mis1.net.lan"
+905     (900) KEY_OWN_ISSUER,0: "CN=mis1.net.lan"
+906     (900) KEY_OWN_KEYSIZE,0: "2048"
+907     (900) KEY_OWN_PRIVATE_KEY,0: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDVtDB06LaTO2Tw\n3VneaACgPG0sKXkNNZa6NAZp9hNbHiaxZeJ41NZUCHCO+7fGHWbtyMrzqdnXtvKX\nG+HOOuA3ak+rl3tX+JgcNiPEVw6to/tXYFUNTwrVdXc/npZOZTqDEElNK1PiMCVT\niECdhjUyYRMJ9upzxksNueiy2yhhnJIX9irPJ3RdvZRnKCTeGPBLEF4YDzAMGmMx\nocXDDQm4znt694wY4rd4SicPV1FNPA/lMkT6r6ZsqncMYR8COyszyKsdCbqkHVqS\n5D1rZo/MulvKRceJmbtw+ehPQwMVAdo6yTeUYxmML4KNoW7n4SjY8oV6vm5jt44w\nnKrz6hoBAgMBAAECggEAf61XIhIDOrXKmgl1buZuMU3I4BCrLRQuSHNavaXybIb4\nERbRFuX6Y1xje1mys1/PphNIBgodh77a8yMKZr9QRGt0zA0F+Uoh/wDtn5lvwJhv\nSwXX6sqlq2Cx7UWfgZkDO1jYoo1XYZToSB7ok/SiBcZvrI1kB+WZ48qF144OczoT\nmITqUGXAxzMY4y6fH5wkHLjbCkkCx7xtZDUXgqY0pWkbaGRqVSlxEZRrSx0c/ydw\nXVHhQ5LK/BZcxFnesBNgd4hL1+bVRn8LEdTP8yIZ2v+wae/RgKt1L9IiJ2PJVKzW\nFHGuysxs1ghnAqs/RCaiSNRSwtGn+leuS6NWUz4/QQKBgQDudNhZDC1lWxxjkaWY\nUrTG9ys8HOm5VOosT6hGtu2KiLN6apsSGUBLnPwzJYILbF+VtkYkP3MmEbU3ny4T\njVATkCFkgUsLPNCxazgcxJdO7J/uEiMz2Bd0W/xUwrDLhxEu+D1VZRIU6C/NxED1\nRKjpxC+Bnc+L4cN8Aj10Fa5wGQKBgQDlbSXDqCgwwPmFKSzHrHjvUoq3+kUYP3sH\ne9bpVteuIlTPEEwI9RBVBNTZGohqqqaekcpKDk2A3bYn539MbtFeKqJotA/u5kmI\nJsBTQcnudIqFnKxndAtX/NzfdAcHYpdfezhql8z7tODzWx915SnwK2Y45jiYzAGg\nfNufMxgWKQKBgBAsUqQvhEC0nkdOkJm1OleoWgFYwK12Se+5c2NgctoLjYjHBD+F\nAOxf+GcLqzdVIWKJG1bICupRFyZvoHNpCT5abzle35UiidEOwZ6J4u1SdUJzbnnM\ntUKKHqItFkqnnY2+/oH1OfW7Dcr5/aHlB0Pbr5Et/+6v4E9f44UlF1C5AoGAWYUx\nSxr+M6fYtODQHElsFtX6UrdQ5pk+hDkKLpo4JjY3KWSOhqA48zOjaeM0f4p+4Ti5\n2hOiojngrclinoxPHZM9M+WuoeUxo/Ka8OGmzWij9Gahzuw00OxVsssvQu5xakxG\nQzFgRobZj5tqtzHoH1KP3HvYcaaIq/qMJ55kM6ECgYEAn8C3qa7AOTegurVhtRFO\nvZ0ePEohDpQeqnRsscJEf36YwY7c+irIuYkTAFqZ2/UxhEFHecURyKAM4VnTsiFU\nUSARiwQ0V3NWQVXgjpRWSQxtV7pfmRB6faWA1VX2kV+BPVTQd3j4vY1DvHvuYbFo\n/knYLrPRmQRNtBbjtKPX788=\n-----END PRIVATE KEY-----\n"
+908     (900) KEY_OWN_PUBLIC_KEY,0: "-----BEGIN CERTIFICATE-----\nMIIDTTCCAjWgAwIBAgIEYJvZqTANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDEwxt\naXMxLm5ldC5sYW4wHhcNMjEwNTEyMTMzNTM3WhcNMjMwNTEyMTMzNTM3WjAXMRUw\nEwYDVQQDEwxtaXMxLm5ldC5sYW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK\nAoIBAQDVtDB06LaTO2Tw3VneaACgPG0sKXkNNZa6NAZp9hNbHiaxZeJ41NZUCHCO\n+7fGHWbtyMrzqdnXtvKXG+HOOuA3ak+rl3tX+JgcNiPEVw6to/tXYFUNTwrVdXc/\nnpZOZTqDEElNK1PiMCVTiECdhjUyYRMJ9upzxksNueiy2yhhnJIX9irPJ3RdvZRn\nKCTeGPBLEF4YDzAMGmMxocXDDQm4znt694wY4rd4SicPV1FNPA/lMkT6r6ZsqncM\nYR8COyszyKsdCbqkHVqS5D1rZo/MulvKRceJmbtw+ehPQwMVAdo6yTeUYxmML4KN\noW7n4SjY8oV6vm5jt44wnKrz6hoBAgMBAAGjgaAwgZ0wHQYDVR0OBBYEFO8JKHC3\nHAN/Z2w7mwYuZF4Qw3q2MA4GA1UdDwEB/wQEAwIB/jAPBgNVHRMBAf8EBTADAQH/\nMEIGA1UdIwQ7MDmAFO8JKHC3HAN/Z2w7mwYuZF4Qw3q2oRukGTAXMRUwEwYDVQQD\nEwxtaXMxLm5ldC5sYW6CBGCb2akwFwYDVR0RBBAwDoIMbWlzMS5uZXQubGFuMA0G\nCSqGSIb3DQEBCwUAA4IBAQBKhYg43FAsn7zCFufcF2N0HTGk5RKBXh9c1ybo3Xke\nI+5BskmDNvEEt7tIvlRvC62o76VCr3Gt1sIOSWKY+m4FryvZR/yZrdvO9OfeZkAY\nSBoxczZbxeLWqWkt7henP3Fxm8X+CDHGjRuEljIW7/lI9fWP2Fezu4tMgnvbfIl3\nnIkbakOtvWDX8rC9dT++5hXbOuhHJvQyq3gD73dyZYb/HzmIZf2c1Aiv6gNrXA7Y\nctpuQaFsuss1z8IdUQgTTgJfZtFOjKbnyYEnUqYWFkMqGBrHMpcUhAun708LMOWB\n53mHNi9AFb9EqDrwWv3PjUJNDWC0Wa1DGHcXJRnjmvpn\n-----END CERTIFICATE-----\n"
+909     (900) KEY_OWN_REQUEST,0: "-----BEGIN CERTIFICATE REQUEST-----\nMIIDDjCCAfYCAQAwFzEVMBMGA1UEAxMMbWlzMS5uZXQubGFuMIIBIjANBgkqhkiG\n9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1bQwdOi2kztk8N1Z3mgAoDxtLCl5DTWWujQG\nafYTWx4msWXieNTWVAhwjvu3xh1m7cjK86nZ17bylxvhzjrgN2pPq5d7V/iYHDYj\nxFcOraP7V2BVDU8K1XV3P56WTmU6gxBJTStT4jAlU4hAnYY1MmETCfbqc8ZLDbno\nstsoYZySF/Yqzyd0Xb2UZygk3hjwSxBeGA8wDBpjMaHFww0JuM57eveMGOK3eEon\nD1dRTTwP5TJE+q+mbKp3DGEfAjsrM8irHQm6pB1akuQ9a2aPzLpbykXHiZm7cPno\nT0MDFQHaOsk3lGMZjC+CjaFu5+Eo2PKFer5uY7eOMJyq8+oaAQIDAQABoIGxMIGu\nBgkqhkiG9w0BCQ4xgaAwgZ0wHQYDVR0OBBYEFO8JKHC3HAN/Z2w7mwYuZF4Qw3q2\nMA4GA1UdDwEB/wQEAwIB/jAPBgNVHRMBAf8EBTADAQH/MEIGA1UdIwQ7MDmAFO8J\nKHC3HAN/Z2w7mwYuZF4Qw3q2oRukGTAXMRUwEwYDVQQDEwxtaXMxLm5ldC5sYW6C\nBGCb2akwFwYDVR0RBBAwDoIMbWlzMS5uZXQubGFuMA0GCSqGSIb3DQEBCwUAA4IB\nAQCBs9qZKirZlsGq+dVvl9F5xgJc55uCV5LCEnHKCdbsLvtKyjWtZzbJUnicelX6\nkLD3qdsH3soZJrWLapOoIgGLleI3MFEMPmx8JG5Z86oeXjREM4ygeyUizE5QUSqa\nkKYUHcFNFzZMMboGQNeCvYA191njXoyGB8zTnAjkja56W7PKRJAUM1RrmJNJOuwd\n+GGMSulhDJn/VKGrPVAwuIDsA9FV5MZm8TV7JK3rkmu+/WsTxqWdPOeQdj7/gKTy\nUOVawbvKQWq21dxtxT9R7YDE2Rm/m88je8VX4EDgRysSQTn7ySMOl9baRBCSA8Px\nVNyLrzaLHL/qPnJSkBZGCpmW\n-----END CERTIFICATE REQUEST-----\n"
+910     (900) KEY_OWN_SUBJECT,0: "CN=mis1.net.lan"
+911     (900) KEY_OWN_SUBJECT_ALT,0: "mis1.net.lan"
+912        (911) KEY_OWN_SUBJECT_ALT_TYPE,0: "DNS"
+913     (900) KEY_OWN_TYPE,0: "SELF"
+914     (900) KEY_OWN_VALIDFROM,0: "20210512T133537"
+915     (900) KEY_OWN_VALIDTILL,0: "20230512T133537"
+916  LICENSE_ACCEPTED,0: "1"
+917  LICENSE_EXPIRED,0: "1"
+918  LICENSE_FEATURES,1: "ANTIVIRUS"
+919  LICENSE_FEATURES,2: "FAX"
+920  LICENSE_FEATURES,3: "FIREWALL-XL"
+921  LICENSE_FEATURES,4: "MAILFILTER"
+922  LICENSE_FEATURES,5: "MAILSERVER"
+923  LICENSE_FEATURES,6: "PROXY"
+924  LICENSE_FEATURES,7: "VPN"
+925  LICENSE_FEATURES,8: "WEBFILTER"
+926  LICENSE_FEATURES,9: "WEBMAIL"
+927  LICENSE_MAXUSERS,0: "UNLIMITED"
+928  LICENSE_TIMESTAMP,0: "0"
+929  LICENSE_TRIAL_PRODUCT,0: "IBS"
+930  LOCAL_BOOT_ESP_UUID,0: "4C43-4BE9"
+931  LOCAL_BOOT_PASSWORD_MD5,0: ""
+932  LOCAL_BOOT_PASSWORD_PBKDF2,0: "grub.pbkdf2.sha512.10000.7A4E5B58E3E06F9ACF0B79C0556E7DC909EBA0FAB8102CE8214DBDB4F2734853AC1F3AE03D487A6D321BEBBDEF663CD1374D75434266F1EC30D8102109F826A6.A09FC67EAED887ADFE3F8BD7B88BA25662435FC6995C8B9CC33352DDE9A0850991A8512A8326F70A4C7D278C4E1342C94B9427B0EC8C2BAF8BCF1D39A6AD3A96"
+933  LOCAL_BOOT_RAID,0: "0"
+934  LOCAL_BOOT_RESCUE_DEVICE,0: "/dev/mapper/system-rescue"
+935  LOCAL_BOOT_ROOT_DEVICE,0: "/dev/mapper/system-root"
+936  LOCAL_BOOT_SAFEMODE,0: "0"
+937  LOCAL_CONFIG_CREATED_VERSION,0: "6.9.1"
+938  LOCAL_COUNTRY,0: "de"
+939  LOCAL_DEFAULTMSN_REF,0: "0"
+940  LOCAL_DEFAULT_THEME_REF,0: "0"
+941  LOCAL_DOMAIN,0: "net.lan"
+942  LOCAL_DOMAIN_DNS_AUTHORITY,0: "LOCAL"
+943  LOCAL_HARDWARE_DISPLAY,0: ""
+944  LOCAL_HARDWARE_ISDN,0: ""
+945  LOCAL_HARDWARE_ISDN_FIRMWARE,0: ""
+946  LOCAL_HARDWARE_MEMSIZE,0: "1020356"
+947  LOCAL_HWSERIAL,0: ""
+948  LOCAL_INSTALL_ID,0: "5f4c41cde04d4cda97111df2600ec39c21effe3a"
+949  LOCAL_INSTALL_TIME,0: "0"
+950  LOCAL_INSTALL_VERSION,0: "6.9.1"
+951  LOCAL_ISDN_AREACODE,0: ""
+952  LOCAL_ISDN_CAPI_GLOBALCALL,0: "0"
+953  LOCAL_ISDN_COUNTRYCODE,0: "49"
+954  LOCAL_ISDN_DIALPREFIX,0: ""
+955  LOCAL_ISDN_INTERNATPREFIX,0: "00"
+956  LOCAL_ISDN_LONGDISTPREFIX,0: "0"
+957  LOCAL_LOCALE,0: "de_DE"
+958  LOCAL_LOG_DEBUG_AVSCAN,0: "0"
+959  LOCAL_LOG_DEBUG_NAMED,0: "0"
+960  LOCAL_LOG_DEBUG_VPN,0: "0"
+961  LOCAL_LOG_FETCHMAIL_MAX_DAYS,0: "14"
+962  LOCAL_LOG_MAILLOG_MAX_DAYS,0: "14"
+963  LOCAL_LOG_MESSAGES_MAX_DAYS,0: "14"
+964  LOCAL_MSN,0: "0"
+965  LOCAL_MSN_COMMENT,0: ""
+966  LOCAL_NAME,0: "mis1"
+967  LOCAL_TIMEZONE,0: "Europe/Berlin"
+968  LOCAL_TIMEZONE_ISDST,0: "1"
+969  LOCAL_TRIAL_BEGIN,0: "1620826533"
+970  LOCAL_UI_FORCE_SSL,0: "1"
+971  LOCAL_VERSION,0: "6.9.1"
+972  LOGCHECK_EMAIL_TIMEPOINT,0: "25200"
+973  MON_ENABLE,0: "1"
+974  NIC,0: ""
+975     (974) NIC_COMMENT,0: "b0"
+976     (974) NIC_DRIVER,0: "virtio_net"
+977     (974) NIC_LAN_DNS_RELAYING_ALLOWED,0: "0"
+978     (974) NIC_LAN_EMAIL_RELAYING_ALLOWED,0: "0"
+979     (974) NIC_LAN_FIREWALL_RULESET_REF,0: "99"
+980     (974) NIC_LAN_IP,0: "192.168.1.1"
+981     (974) NIC_LAN_NAT_INTO,0: "0"
+982     (974) NIC_LAN_NETMASK,0: "255.255.255.0"
+983     (974) NIC_LAN_PROXY_PROFILE_REF,0: "-1"
+984     (974) NIC_MAC,0: "02:00:00:00:01:01"
+985     (974) NIC_TYPE,0: "NATLAN"
+986  NIC,1: ""
+987     (986) NIC_COMMENT,0: "b1"
+988     (986) NIC_DRIVER,0: "virtio_net"
+989     (986) NIC_MAC,0: "02:00:00:00:01:02"
+990     (986) NIC_TYPE,0: "DSLROUTER"
+991  NIC,2: ""
+992     (991) NIC_COMMENT,0: "b2"
+993     (991) NIC_DRIVER,0: "virtio_net"
+994     (991) NIC_LAN_DNS_RELAYING_ALLOWED,0: "0"
+995     (991) NIC_LAN_EMAIL_RELAYING_ALLOWED,0: "0"
+996     (991) NIC_LAN_FIREWALL_RULESET_REF,0: "1"
+997     (991) NIC_LAN_IP,0: "172.17.0.1"
+998     (991) NIC_LAN_NAT_INTO,0: "0"
+999     (991) NIC_LAN_NETMASK,0: "255.255.0.0"
+1000    (991) NIC_LAN_PROXY_PROFILE_REF,0: "-1"
+1001    (991) NIC_MAC,0: "02:00:00:00:01:03"
+1002    (991) NIC_TYPE,0: "NATLAN"
+1003 NIC,3: ""
+1004    (1003) NIC_COMMENT,0: "b3"
+1005    (1003) NIC_DRIVER,0: "virtio_net"
+1006    (1003) NIC_LAN_DNS_RELAYING_ALLOWED,0: "0"
+1007    (1003) NIC_LAN_EMAIL_RELAYING_ALLOWED,0: "0"
+1008    (1003) NIC_LAN_FIREWALL_RULESET_REF,0: "1"
+1009    (1003) NIC_LAN_IP,0: "172.24.0.1"
+1010    (1003) NIC_LAN_NAT_INTO,0: "0"
+1011    (1003) NIC_LAN_NETMASK,0: "255.255.0.0"
+1012    (1003) NIC_LAN_PROXY_PROFILE_REF,0: "-1"
+1013    (1003) NIC_MAC,0: "02:00:00:00:01:04"
+1014    (1003) NIC_TYPE,0: "NATLAN"
+1015 PINGCHECK_SERVERLIST,0: "Intra2net Server"
+1016    (1015) PINGCHECK_SERVERLIST_PREDEFINED_ID,0: "0"
+1017    (1015) PINGCHECK_SERVERLIST_SERVER,0: "0.ping.intra2net.com"
+1018    (1015) PINGCHECK_SERVERLIST_SERVER,1: "1.ping.intra2net.com"
+1019    (1015) PINGCHECK_SERVERLIST_SERVER,2: "2.ping.intra2net.com"
+1020    (1015) PINGCHECK_SERVERLIST_SERVER,3: "3.ping.intra2net.com"
+1021    (1015) PINGCHECK_SERVERLIST_SERVER,4: "4.ping.intra2net.com"
+1022    (1015) PINGCHECK_SERVERLIST_SERVER,5: "5.ping.intra2net.com"
+1023    (1015) PINGCHECK_SERVERLIST_SERVER,6: "6.ping.intra2net.com"
+1024    (1015) PINGCHECK_SERVERLIST_SERVER,7: "7.ping.intra2net.com"
+1025 PINGCHECK_SERVERLIST_DEFAULT_REF,0: "0"
+1026 PROXYLIZER_CRON,0: "0123456"
+1027    (1026) PROXYLIZER_CRON_BEGIN,0: "0"
+1028    (1026) PROXYLIZER_CRON_END,0: "86400"
+1029    (1026) PROXYLIZER_CRON_EVERY,0: "3600"
+1030 PROXY_ACCESSLIST,0: "Vordefiniert: Werbung"
+1031    (1030) PROXY_ACCESSLIST_ENTRY_COUNT,0: "39678"
+1032    (1030) PROXY_ACCESSLIST_MODE,0: "1"
+1033    (1030) PROXY_ACCESSLIST_PREDEFINED_ID,0: "0"
+1034    (1030) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1035    (1030) PROXY_ACCESSLIST_TYPE,0: "0"
+1036 PROXY_ACCESSLIST,1: "Vordefiniert: Extremismus"
+1037    (1036) PROXY_ACCESSLIST_ENTRY_COUNT,0: "678"
+1038    (1036) PROXY_ACCESSLIST_MODE,0: "1"
+1039    (1036) PROXY_ACCESSLIST_PREDEFINED_ID,0: "1"
+1040    (1036) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1041    (1036) PROXY_ACCESSLIST_TYPE,0: "0"
+1042 PROXY_ACCESSLIST,2: "Vordefiniert: Audio-Video"
+1043    (1042) PROXY_ACCESSLIST_ENTRY_COUNT,0: "7718"
+1044    (1042) PROXY_ACCESSLIST_MODE,0: "1"
+1045    (1042) PROXY_ACCESSLIST_PREDEFINED_ID,0: "2"
+1046    (1042) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1047    (1042) PROXY_ACCESSLIST_TYPE,0: "0"
+1048 PROXY_ACCESSLIST,3: "Vordefiniert: Drogen"
+1049    (1048) PROXY_ACCESSLIST_ENTRY_COUNT,0: "5012"
+1050    (1048) PROXY_ACCESSLIST_MODE,0: "1"
+1051    (1048) PROXY_ACCESSLIST_PREDEFINED_ID,0: "3"
+1052    (1048) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1053    (1048) PROXY_ACCESSLIST_TYPE,0: "0"
+1054 PROXY_ACCESSLIST,4: "Vordefiniert: Gluecksspiel"
+1055    (1054) PROXY_ACCESSLIST_ENTRY_COUNT,0: "11013"
+1056    (1054) PROXY_ACCESSLIST_MODE,0: "1"
+1057    (1054) PROXY_ACCESSLIST_PREDEFINED_ID,0: "4"
+1058    (1054) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1059    (1054) PROXY_ACCESSLIST_TYPE,0: "0"
+1060 PROXY_ACCESSLIST,5: "Vordefiniert: Hacking"
+1061    (1060) PROXY_ACCESSLIST_ENTRY_COUNT,0: "134116"
+1062    (1060) PROXY_ACCESSLIST_MODE,0: "1"
+1063    (1060) PROXY_ACCESSLIST_PREDEFINED_ID,0: "5"
+1064    (1060) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1065    (1060) PROXY_ACCESSLIST_TYPE,0: "0"
+1066 PROXY_ACCESSLIST,6: "Vordefiniert: Mail"
+1067    (1066) PROXY_ACCESSLIST_ENTRY_COUNT,0: "4958"
+1068    (1066) PROXY_ACCESSLIST_MODE,0: "1"
+1069    (1066) PROXY_ACCESSLIST_PREDEFINED_ID,0: "6"
+1070    (1066) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1071    (1066) PROXY_ACCESSLIST_TYPE,0: "0"
+1072 PROXY_ACCESSLIST,7: "Vordefiniert: Erotik"
+1073    (1072) PROXY_ACCESSLIST_ENTRY_COUNT,0: "2817405"
+1074    (1072) PROXY_ACCESSLIST_MODE,0: "1"
+1075    (1072) PROXY_ACCESSLIST_PREDEFINED_ID,0: "7"
+1076    (1072) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1077    (1072) PROXY_ACCESSLIST_TYPE,0: "0"
+1078 PROXY_ACCESSLIST,8: "Vordefiniert: Offene Proxies"
+1079    (1078) PROXY_ACCESSLIST_ENTRY_COUNT,0: "141226"
+1080    (1078) PROXY_ACCESSLIST_MODE,0: "1"
+1081    (1078) PROXY_ACCESSLIST_PREDEFINED_ID,0: "8"
+1082    (1078) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1083    (1078) PROXY_ACCESSLIST_TYPE,0: "0"
+1084 PROXY_ACCESSLIST,10: "Vordefiniert: Gewalt"
+1085    (1084) PROXY_ACCESSLIST_ENTRY_COUNT,0: "6562"
+1086    (1084) PROXY_ACCESSLIST_MODE,0: "1"
+1087    (1084) PROXY_ACCESSLIST_PREDEFINED_ID,0: "10"
+1088    (1084) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1089    (1084) PROXY_ACCESSLIST_TYPE,0: "0"
+1090 PROXY_ACCESSLIST,11: "Vordefiniert: Warez"
+1091    (1090) PROXY_ACCESSLIST_ENTRY_COUNT,0: "2010"
+1092    (1090) PROXY_ACCESSLIST_MODE,0: "1"
+1093    (1090) PROXY_ACCESSLIST_PREDEFINED_ID,0: "11"
+1094    (1090) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1095    (1090) PROXY_ACCESSLIST_TYPE,0: "0"
+1096 PROXY_ACCESSLIST,12: "Alles verboten"
+1097    (1096) PROXY_ACCESSLIST_ENTRY_COUNT,0: "0"
+1098    (1096) PROXY_ACCESSLIST_MODE,0: "0"
+1099    (1096) PROXY_ACCESSLIST_SIZETYPE,0: "1"
+1100    (1096) PROXY_ACCESSLIST_TYPE,0: "0"
+1101 PROXY_ACCESSLIST,13: "Vordefiniert: Spiele"
+1102    (1101) PROXY_ACCESSLIST_ENTRY_COUNT,0: "22438"
+1103    (1101) PROXY_ACCESSLIST_MODE,0: "1"
+1104    (1101) PROXY_ACCESSLIST_PREDEFINED_ID,0: "12"
+1105    (1101) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1106    (1101) PROXY_ACCESSLIST_TYPE,0: "0"
+1107 PROXY_ACCESSLIST,14: "Vordefiniert: Chat"
+1108    (1107) PROXY_ACCESSLIST_ENTRY_COUNT,0: "1762"
+1109    (1107) PROXY_ACCESSLIST_MODE,0: "1"
+1110    (1107) PROXY_ACCESSLIST_PREDEFINED_ID,0: "13"
+1111    (1107) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1112    (1107) PROXY_ACCESSLIST_TYPE,0: "0"
+1113 PROXY_ACCESSLIST,15: "Vordefiniert: Soziale Netzwerke"
+1114    (1113) PROXY_ACCESSLIST_ENTRY_COUNT,0: "3076"
+1115    (1113) PROXY_ACCESSLIST_MODE,0: "1"
+1116    (1113) PROXY_ACCESSLIST_PREDEFINED_ID,0: "14"
+1117    (1113) PROXY_ACCESSLIST_SIZETYPE,0: "0"
+1118    (1113) PROXY_ACCESSLIST_TYPE,0: "0"
+1119 PROXY_ALLOWED_PORTS,0: ""
+1120    (1119) PROXY_ALLOWED_PORTS_PORT,0: "21"
+1121    (1119) PROXY_ALLOWED_PORTS_PORT,2: "80"
+1122 PROXY_ALLOWED_SSL_PORTS,0: ""
+1123    (1122) PROXY_ALLOWED_SSL_PORTS_PORT,0: "443"
+1124 PROXY_CACHE_DELETE,0: "0"
+1125 PROXY_CACHE_SIZE,0: "100"
+1126 PROXY_DIGESTAUTH_ENABLE,0: "1"
+1127 PROXY_LOGS,0: "DISABLED"
+1128 PROXY_LOGS_KEEPMONTHS,0: "6"
+1129 PROXY_PORT,0: "3128"
+1130 PROXY_PROFILE,1: "Alles erlaubt"
+1131 PROXY_PROFILE,2: "Alles verboten"
+1132    (1131) PROXY_PROFILE_ACCESSLIST_REF,0: "12"
+1133 PROXY_REVERSE_LOOKUP,0: "1"
+1134 SARULES_UPDATE_CRON,0: "0123456"
+1135    (1134) SARULES_UPDATE_CRON_BEGIN,0: "27057"
+1136 SESSION_MAXLIFETIME,0: "30"
+1137 SOCKS_ENABLE,0: "0"
+1138 SOCKS_OUTGOING_FIREWALL_RULESET_REF,0: "8"
+1139 SPAMFILTER_ACTIVE,0: "1"
+1140 SPAMFILTER_BAYES_WEIGHT,0: "2"
+1141 SPAMFILTER_CALIBRATOR_CRON,0: "0123456"
+1142    (1141) SPAMFILTER_CALIBRATOR_CRON_BEGIN,0: "0"
+1143    (1141) SPAMFILTER_CALIBRATOR_CRON_END,0: "86400"
+1144    (1141) SPAMFILTER_CALIBRATOR_CRON_EVERY,0: "3600"
+1145 SPAMFILTER_DNSCHECK,0: "1"
+1146 SPAMFILTER_DNSCHECK_CALIBRATION_PROGRESS,0: "0"
+1147 SPAMFILTER_DNSCHECK_MODE,0: "ALL-RECEIVED-LINES"
+1148 SPAMFILTER_GLOBAL_POTENTIAL_SPAM_ACTION,0: "NONE"
+1149 SPAMFILTER_GLOBAL_POTENTIAL_SPAM_THRESHOLD,0: "1050"
+1150 SPAMFILTER_GLOBAL_QUARANTINE_DELETEDAYS,0: "30"
+1151 SPAMFILTER_GLOBAL_QUARANTINE_REPORT_ADMIN_REF,0: "-1"
+1152 SPAMFILTER_GLOBAL_QUARANTINE_REPORT_CRON,1: "12345"
+1153    (1152) SPAMFILTER_GLOBAL_QUARANTINE_REPORT_CRON_BEGIN,0: "27000"
+1154 SPAMFILTER_GLOBAL_QUARANTINE_REPORT_ITEM_COUNT,0: "50"
+1155 SPAMFILTER_GLOBAL_QUARANTINE_REPORT_MODE,0: "OFF"
+1156 SPAMFILTER_GLOBAL_SPAM_ACTION,0: "NONE"
+1157 SPAMFILTER_GLOBAL_SPAM_THRESHOLD,0: "1080"
+1158 SPAMFILTER_HASHCHECK,0: "1"
+1159 SPAMFILTER_RAZOR_ENABLE,0: "1"
+1160 SPAMFILTER_RECHECK_EVERY,0: "30"
+1161 SPAMFILTER_SMTP_REJECT,0: "1"
+1162 SSH_ENABLE_PAM,0: "0"
+1163 SSH_PERMIT_ROOT_LOGIN_WITH_PASSWORD,0: "1"
+1164 SSL_LOCAL_KEY_REF,0: "1"
+1165 SSL_LOCAL_STRENGTH,0: "WIN7IMAP"
+1166 SSL_REMOTE_KEY_REF,0: "1"
+1167 SSL_REMOTE_STRENGTH,0: "STRONG"
+1168 STATISTICS_CRON,0: "0123456"
+1169    (1168) STATISTICS_CRON_BEGIN,0: "0"
+1170    (1168) STATISTICS_CRON_END,0: "86400"
+1171    (1168) STATISTICS_CRON_EVERY,0: "900"
+1172 STATISTICS_DELETEDAYS,0: "730"
+1173 SYSLOG_REMOTELOG_ENABLE,0: "0"
+1174 THEME,0: "Intra2net System"
+1175    (1174) THEME_ARROW_FILENAME,0: "arrow_intranator.gif"
+1176    (1174) THEME_CSS_FILENAME,0: "intranator.css"
+1177    (1174) THEME_DEFAULTVALUES_FILENAME,0: "defaultvalues-intranator.cnf"
+1178    (1174) THEME_FAVICON_FILENAME,0: "favicon_intranator.ico"
+1179    (1174) THEME_LICENSE_AGREEMENT_FILENAME,0: "license_agreement_intranator.html"
+1180    (1174) THEME_LOGIN_FILENAME,0: "login_intranator.gif"
+1181    (1174) THEME_LOGO_FILENAME,0: "intranator.gif"
+1182    (1174) THEME_STATISTICS_FILENAME,0: "templates-intranator/arnielizer-config.xml"
+1183 THEME,1: "Xerberus"
+1184    (1183) THEME_ARROW_FILENAME,0: "arrow_xerberus.gif"
+1185    (1183) THEME_CSS_FILENAME,0: "xerberus.css"
+1186    (1183) THEME_DEFAULTVALUES_FILENAME,0: "defaultvalues-xerberus.cnf"
+1187    (1183) THEME_FAVICON_FILENAME,0: "favicon_xerberus.ico"
+1188    (1183) THEME_LICENSE_AGREEMENT_FILENAME,0: "license_agreement_xerberus.html"
+1189    (1183) THEME_LOGIN_FILENAME,0: "login_xerberus.gif"
+1190    (1183) THEME_LOGO_FILENAME,0: "xerberus.gif"
+1191    (1183) THEME_STATISTICS_FILENAME,0: "templates-xerberus/arnielizer-config.xml"
+1192 UI_HIDE_NAVIGATION_ENTRIES,0: "0"
+1193 UI_REMOTE_PORT,0: "443"
+1194 UPDATE_CRON,0: "0123456"
+1195    (1194) UPDATE_CRON_BEGIN,0: "27057"
+1196 UPDATE_EXPIRED,0: "0"
+1197 UPDATE_URL_BASE,0: "https://update.intra2net.com/"
+1198 UPDATE_VALIDATION_GROUP,0: "normal"
+1199 UPS_LOCAL_KILLPOWER_ENABLE,0: "1"
+1200 UPS_LOCAL_MINIMUM_RESTART_CAPACITY,0: "15"
+1201 USER,1: "admin"
+1202    (1201) USER_DISABLED,0: "0"
+1203    (1201) USER_FULLNAME,0: "Administrator"
+1204    (1201) USER_GROUPWARE_FOLDER_CALENDAR,0: "INBOX/Kalender"
+1205    (1201) USER_GROUPWARE_FOLDER_CONTACTS,0: "INBOX/Kontakte"
+1206    (1201) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
+1207    (1201) USER_GROUPWARE_FOLDER_NOTES,0: "INBOX/Notizen"
+1208    (1201) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Elemente"
+1209    (1201) USER_GROUPWARE_FOLDER_TASKS,0: "INBOX/Aufgaben"
+1210    (1201) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
+1211    (1201) USER_GROUP_MEMBER_REF,0: "1"
+1212    (1201) USER_GROUP_MEMBER_REF,1: "2"
+1213    (1201) USER_PASSWORD,0: "admin"
+1214    (1201) USER_TRASH_DELETEDAYS,0: "30"
+1215 USERSYNC_DELETED_TTL,0: "60"
+1216 VIRSCAN_BACKEND,0: "SAVAPI"
+1217 VIRSCAN_DISABLE_FPC,0: "0"
+1218 VIRSCAN_EMAIL_CLOUD_ENABLE,0: "PE-ONLY"
+1219 VIRSCAN_EMAIL_DETECT_ADWARE,0: "1"
+1220 VIRSCAN_EMAIL_ENABLE,0: "1"
+1221 VIRSCAN_EMAIL_MACRO_HEURISTIC,0: "STRONG"
+1222 VIRSCAN_EMAIL_QUARANTINE_MAX_ENTRIES,0: "15"
+1223 VIRSCAN_PROXY_AUTOMATIC_UNBLOCK,0: "60"
+1224 VIRSCAN_PROXY_CLOUD_ENABLE,0: "PE-ONLY"
+1225 VIRSCAN_PROXY_DETECT_ADWARE,0: "1"
+1226 VIRSCAN_PROXY_MACRO_HEURISTIC,0: "STRONG"
+1227 VIRSCAN_PROXY_PASS_MIMETYPES,0: ""
+1228    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,0: "audio/mpeg"
+1229    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,1: "audio/x-mpeg"
+1230    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,2: "audio/x-pn-realaudio"
+1231    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,3: "audio/x-wav"
+1232    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,4: "audio/x-realaudio"
+1233    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,6: "audio/vnd.rn-realaudio"
+1234    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,7: "video/mpeg"
+1235    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,8: "video/x-mpeg2"
+1236    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,9: "video/acorn-replay"
+1237    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,10: "video/quicktime"
+1238    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,11: "video/x-msvideo"
+1239    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,12: "video/msvideo"
+1240    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,13: "video/vnd.rn-realvideo"
+1241    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,14: "image/png"
+1242    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,15: "image/gif"
+1243    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,16: "image/tiff"
+1244    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,17: "application/vnd.ms.wms-hdr.asfv1"
+1245    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,18: "application/x-mms-framed"
+1246    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,19: "video/x-ms-asf"
+1247    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,20: "video/x-flv"
+1248    (1227) VIRSCAN_PROXY_PASS_MIMETYPES_NAME,21: "audio/x-scpls"
+1249 VIRSCAN_PROXY_TRICKLE_WORKAROUND_ONLY_TEXT,0: "1"
+1250 VIRSCAN_UPDATE_DNS_PUSH,0: "0"
+1251 VPN_CERTREQ_LIMIT,0: "16"
+1252 VPN_DEFAULT_ENCRYPTION_PROFILE_REF,0: "1"
+1253 VPN_DEFAULT_FIREWALL_RULESET_REF,0: "9"
+1254 VPN_DEFAULT_KEY_FOREIGN_CREATE_DAYS,0: "1825"
+1255 VPN_DEFAULT_KEY_OWN_REF,0: "1"
+1256 VPN_DEFAULT_MODECONFIG_BASE_IP,0: "192.168.40.1"
+1257 VPN_DEFAULT_PROXY_PROFILE_REF,0: "-1"
+1258 VPN_ENCRYPTION_PROFILE,0: "Standard (mit PFS)"
+1259    (1258) VPN_ENCRYPTION_PROFILE_IKE,0: ""
+1260       (1259) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES192"
+1261       (1259) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1262       (1259) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA2_256"
+1263    (1258) VPN_ENCRYPTION_PROFILE_IKE,1: ""
+1264       (1263) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES192"
+1265       (1263) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1266       (1263) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA2_256"
+1267    (1258) VPN_ENCRYPTION_PROFILE_IKE,2: ""
+1268       (1267) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES128"
+1269       (1267) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1270       (1267) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1271    (1258) VPN_ENCRYPTION_PROFILE_IKE,3: ""
+1272       (1271) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES128"
+1273       (1271) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1274       (1271) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1275    (1258) VPN_ENCRYPTION_PROFILE_IKE,4: ""
+1276       (1275) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1277       (1275) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1278       (1275) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1279    (1258) VPN_ENCRYPTION_PROFILE_IKE,5: ""
+1280       (1279) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1281       (1279) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1282       (1279) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1283    (1258) VPN_ENCRYPTION_PROFILE_IKE,6: ""
+1284       (1283) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1285       (1283) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1286       (1283) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "MD5"
+1287    (1258) VPN_ENCRYPTION_PROFILE_IKE,7: ""
+1288       (1287) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1289       (1287) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1290       (1287) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "MD5"
+1291    (1258) VPN_ENCRYPTION_PROFILE_IPSEC,0: ""
+1292       (1291) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "AES192"
+1293       (1291) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "SHA2_256"
+1294    (1258) VPN_ENCRYPTION_PROFILE_IPSEC,1: ""
+1295       (1294) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "AES128"
+1296       (1294) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "SHA1"
+1297    (1258) VPN_ENCRYPTION_PROFILE_IPSEC,2: ""
+1298       (1297) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "3DES"
+1299       (1297) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "SHA1"
+1300    (1258) VPN_ENCRYPTION_PROFILE_IPSEC,3: ""
+1301       (1300) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "3DES"
+1302       (1300) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "MD5"
+1303    (1258) VPN_ENCRYPTION_PROFILE_PFSGROUP,0: "Phase1"
+1304 VPN_ENCRYPTION_PROFILE,1: "Standard (ohne PFS)"
+1305    (1304) VPN_ENCRYPTION_PROFILE_IKE,0: ""
+1306       (1305) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES192"
+1307       (1305) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1308       (1305) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA2_256"
+1309    (1304) VPN_ENCRYPTION_PROFILE_IKE,1: ""
+1310       (1309) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES192"
+1311       (1309) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1312       (1309) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA2_256"
+1313    (1304) VPN_ENCRYPTION_PROFILE_IKE,2: ""
+1314       (1313) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES128"
+1315       (1313) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1316       (1313) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1317    (1304) VPN_ENCRYPTION_PROFILE_IKE,3: ""
+1318       (1317) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "AES128"
+1319       (1317) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1320       (1317) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1321    (1304) VPN_ENCRYPTION_PROFILE_IKE,4: ""
+1322       (1321) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1323       (1321) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1324       (1321) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1325    (1304) VPN_ENCRYPTION_PROFILE_IKE,5: ""
+1326       (1325) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1327       (1325) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1328       (1325) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "SHA"
+1329    (1304) VPN_ENCRYPTION_PROFILE_IKE,6: ""
+1330       (1329) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1331       (1329) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1536"
+1332       (1329) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "MD5"
+1333    (1304) VPN_ENCRYPTION_PROFILE_IKE,7: ""
+1334       (1333) VPN_ENCRYPTION_PROFILE_IKE_ENCRYPTION,0: "3DES"
+1335       (1333) VPN_ENCRYPTION_PROFILE_IKE_GROUP,0: "MODP1024"
+1336       (1333) VPN_ENCRYPTION_PROFILE_IKE_HASH,0: "MD5"
+1337    (1304) VPN_ENCRYPTION_PROFILE_IPSEC,0: ""
+1338       (1337) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "AES192"
+1339       (1337) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "SHA2_256"
+1340    (1304) VPN_ENCRYPTION_PROFILE_IPSEC,1: ""
+1341       (1340) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "AES128"
+1342       (1340) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "SHA1"
+1343    (1304) VPN_ENCRYPTION_PROFILE_IPSEC,2: ""
+1344       (1343) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "3DES"
+1345       (1343) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "SHA1"
+1346    (1304) VPN_ENCRYPTION_PROFILE_IPSEC,3: ""
+1347       (1346) VPN_ENCRYPTION_PROFILE_IPSEC_ENCRYPTION,0: "3DES"
+1348       (1346) VPN_ENCRYPTION_PROFILE_IPSEC_HASH,0: "MD5"
+1349    (1304) VPN_ENCRYPTION_PROFILE_PFSGROUP,0: "None"
+1350 VPN_NAT_T,0: "1"
diff --git a/test/cnfvar/test_binary.py b/test/cnfvar/test_binary.py
new file mode 100644 (file)
index 0000000..3a2bf79
--- /dev/null
@@ -0,0 +1,142 @@
+# The software in this package is distributed under the GNU General
+# Public License version 2 (with a special exception described below).
+#
+# A copy of GNU General Public License (GPL) is included in this distribution,
+# in the file COPYING.GPL.
+#
+# As a special exception, if other files instantiate templates or use macros
+# or inline functions from this file, or you compile this file and link it
+# with other works to produce a work based on this file, this file
+# does not by itself cause the resulting work to be covered
+# by the GNU General Public License.
+#
+# However the source code for this file must still be made available
+# in accordance with section (3) of the GNU General Public License.
+#
+# This exception does not invalidate any other reasons why a work based
+# on this file might be covered by the GNU General Public License.
+#
+# Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
+
+"""
+test_binary.py: unit tests for cnfvar/binary.py.
+
+Tests classes and functions in cnfvar/binary.py
+
+For help see :py:mod:`unittest`
+"""
+
+import unittest
+from textwrap import dedent
+from types import SimpleNamespace
+from unittest.mock import patch, ANY
+
+from src.cnfvar import binary
+from src.cnfvar.binary import CnfBinary
+
+
+class CnfBinaryTest(unittest.TestCase):
+    """Test class CnfBinary."""
+
+    #: return value for the run method
+    CMD_STDOUT = ""
+    CMD_STDERR = ""
+
+    def setUp(self):
+        """Set up mocks."""
+        self.CMD_STDOUT = ""
+        self.CMD_STDERR = ""
+        self._proc_patch = patch.object(binary.subprocess, "run", side_effect=self._fake_subprocess_run)
+        self._proc_mock = self._proc_patch.start()
+
+    def tearDown(self):
+        """Clean up mocks."""
+        self._proc_patch.stop()
+        self._proc_mock = None
+        self.CMD_STDOUT = ""
+        self.CMD_STDERR = ""
+
+    def test_basic_get_cnf(self):
+        """Tests basic functionality of the get_cnf wrapper."""
+        self.CMD_STDOUT = dedent("""\
+            1 USER,1: "jake"
+            2    (1) USER_DISABLED,0: "0"
+            3    (1) USER_FULLNAME,0: "Jake"
+            4    (1) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
+            5    (1) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
+            6    (1) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
+            7    (1) USER_GROUP_MEMBER_REF,0: "100"
+            8    (1) USER_GROUP_MEMBER_REF,1: "2"
+            9    (1) USER_PASSWORD,0: "test1234"
+            11 USER,2: "jill"
+            12    (11) USER_DISABLED,0: "0"
+            13    (11) USER_FULLNAME,0: "Jill"
+            14    (11) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
+            15    (11) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
+            16    (11) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
+            17    (11) USER_GROUP_MEMBER_REF,0: "100"
+            18    (11) USER_GROUP_MEMBER_REF,1: "2"
+            19    (11) USER_PASSWORD,0: "test1234"
+            """)
+
+        output = CnfBinary.get_cnf("USER", 10)
+        self.assertEqual(self.CMD_STDOUT.splitlines(), output.splitlines())
+        self._proc_mock.assert_called_once_with(["/usr/intranator/bin/get_cnf", "USER", "10"],
+                                                input=ANY, check=ANY, capture_output=ANY,
+                                                encoding='latin1', timeout=ANY)
+
+    def test_get_cnf_error_handling(self):
+        """Test that the get cnf method does some sanity checks."""
+        # cannot pass an instance without a name
+        with self.assertRaises(ValueError):
+            CnfBinary.get_cnf(None, 10)
+
+        # cannot pass an instance of an invalid type
+        with self.assertRaises(TypeError):
+            CnfBinary.get_cnf("USER", 18.45)
+
+    def test_basic_set_cnf(self):
+        """Test basic set cnf operation."""
+        input_str = """\
+            1  FIREWALL_SERVICEGROUP,1: "http"
+            2     (1) FIREWALL_SERVICEGROUP_PREDEFINED_ID,0: "1"
+            3     (1) FIREWALL_SERVICEGROUP_TYPE,0: "TCP"
+            4        (3) FIREWALL_SERVICEGROUP_TYPE_COMMENT,0: ""
+            5        (3) FIREWALL_SERVICEGROUP_TYPE_TCPUDP_DST_PORT,0: "80"
+        """
+
+        CnfBinary.set_cnf(input_str)
+        self._proc_mock.assert_called_once_with(
+            ["/usr/intranator/bin/set_cnf"],
+            input=input_str, check=ANY, capture_output=ANY,
+            encoding="latin1", timeout=ANY)
+
+    def test_set_cnf_extra_args(self):
+        """Test that we can pass extra arguments to the set_cnf binary."""
+        input_str = """\
+        {
+            "cnf" :
+            [
+                {
+                    "data" : "0",
+                    "instance" : 0,
+                    "number" : 1,
+                    "varname" : "ACME_DEBUG_ENABLE"
+                }
+            ]
+        }
+        """
+
+        CnfBinary.set_cnf(input_str, as_json=True, fix_problems=True, discard_queue=True)
+        self._proc_mock.assert_called_once_with(
+            ["/usr/intranator/bin/set_cnf", "--json", "--fix-problems", "--discard-queue"],
+            input=input_str, check=ANY, capture_output=ANY,
+            encoding="utf8", timeout=ANY)
+
+    def _fake_subprocess_run(self, *args, **kwargs):
+        return SimpleNamespace(args=args[0], stdout=self.CMD_STDOUT,
+                               stderr=self.CMD_STDERR, returncode=0)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/cnfvar/test_model.py b/test/cnfvar/test_model.py
new file mode 100644 (file)
index 0000000..bbd2e63
--- /dev/null
@@ -0,0 +1,184 @@
+# The software in this package is distributed under the GNU General
+# Public License version 2 (with a special exception described below).
+#
+# A copy of GNU General Public License (GPL) is included in this distribution,
+# in the file COPYING.GPL.
+#
+# As a special exception, if other files instantiate templates or use macros
+# or inline functions from this file, or you compile this file and link it
+# with other works to produce a work based on this file, this file
+# does not by itself cause the resulting work to be covered
+# by the GNU General Public License.
+#
+# However the source code for this file must still be made available
+# in accordance with section (3) of the GNU General Public License.
+#
+# This exception does not invalidate any other reasons why a work based
+# on this file might be covered by the GNU General Public License.
+#
+# Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
+
+"""
+test_model.py: unit tests for cnfvar/model.py.
+
+Tests classes and functions in cnfvar/model.py
+
+For help see :py:mod:`unittest`
+"""
+
+import unittest
+import json
+from textwrap import dedent
+from tempfile import NamedTemporaryFile
+
+from src.cnfvar import CnfList
+
+CNF_TEST_DATA = dedent("""\
+    1 USER,1: "jake"
+    2    (1) USER_DISABLED,0: "0"
+    3    (1) USER_FULLNAME,0: "Jake"
+    4    (1) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
+    5    (1) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
+    6    (1) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
+    7    (1) USER_GROUP_MEMBER_REF,0: "100"
+    8    (1) USER_GROUP_MEMBER_REF,1: "2"
+    9    (1) USER_PASSWORD,0: "test1234"
+    11 USER,2: "jill"
+    12    (11) USER_DISABLED,0: "0"
+    13    (11) USER_FULLNAME,0: "Jill"
+    14    (11) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
+    15    (11) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
+    16    (11) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
+    17    (11) USER_GROUP_MEMBER_REF,0: "100"
+    18    (11) USER_GROUP_MEMBER_REF,1: "2"
+    19    (11) USER_PASSWORD,0: "test1234"
+    74 EMAILFILTER_BAN_FILTERLIST,0: "Vordefiniert: Alles verboten"
+    75    (74) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK"
+    76    (74) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: ""
+    77    (74) EMAILFILTER_BAN_FILTERLIST_FILTER_OFFICE_FILES,0: "BY_FILTERLIST"
+    78    (74) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: ""
+    79       (78) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "text/plain"
+    80       (78) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "text/html"
+    81    (74) EMAILFILTER_BAN_FILTERLIST_MODE,0: "ALLOW"
+    82    (74) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "1"
+    1196 UPDATE_EXPIRED,0: "0"
+    1197 UPDATE_URL_BASE,0: "https://update.intra2net.com/"
+    1198 UPDATE_VALIDATION_GROUP,0: "normal"
+    1199 UPS_LOCAL_KILLPOWER_ENABLE,0: "1"
+    """)
+
+
+EXPECTED_CNF_DATA = dedent("""\
+    1 USER,1: "jake"
+    2   (1) USER_DISABLED,0: "0"
+    3   (1) USER_FULLNAME,0: "Jake"
+    4   (1) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
+    5   (1) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
+    6   (1) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
+    7   (1) USER_GROUP_MEMBER_REF,0: "100"
+    8   (1) USER_GROUP_MEMBER_REF,1: "2"
+    9   (1) USER_PASSWORD,0: "test1234"
+    10 USER,2: "jane"
+    11   (10) USER_DISABLED,0: "0"
+    12   (10) USER_FULLNAME,0: "Jane"
+    13   (10) USER_GROUPWARE_FOLDER_DRAFTS,0: "INBOX/Entwürfe"
+    14   (10) USER_GROUPWARE_FOLDER_OUTBOX,0: "INBOX/Gesendete Objekte"
+    15   (10) USER_GROUPWARE_FOLDER_TRASH,0: "INBOX/Gelöschte Elemente"
+    16   (10) USER_GROUP_MEMBER_REF,0: "200"
+    17   (10) USER_GROUP_MEMBER_REF,1: "2"
+    18   (10) USER_PASSWORD,0: "test1234"
+    19   (10) USER_GROUP_MEMBER_REF,2: "5"
+    20 EMAILFILTER_BAN_FILTERLIST,0: "Vordefiniert: Alles verboten"
+    21   (20) EMAILFILTER_BAN_FILTERLIST_ENCRYPTED,0: "BLOCK"
+    22   (20) EMAILFILTER_BAN_FILTERLIST_EXTENSIONS,0: ""
+    23   (20) EMAILFILTER_BAN_FILTERLIST_FILTER_OFFICE_FILES,0: "BY_FILTERLIST"
+    24   (20) EMAILFILTER_BAN_FILTERLIST_MIMETYPES,0: "" # hey this is a comment
+    25     (24) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,0: "text/plain"
+    26     (24) EMAILFILTER_BAN_FILTERLIST_MIMETYPES_NAME,1: "text/html"
+    27   (20) EMAILFILTER_BAN_FILTERLIST_MODE,0: "ALLOW"
+    28   (20) EMAILFILTER_BAN_FILTERLIST_PREDEFINED_ID,0: "1"
+    29 UPDATE_EXPIRED,0: "1"
+    30 UPDATE_URL_BASE,0: "https://update.intra2net.com/"
+    31 UPDATE_VALIDATION_GROUP,0: "normal"
+    32 UPS_LOCAL_KILLPOWER_ENABLE,0: "1"
+    """)
+
+
+class TestModel(unittest.TestCase):
+    """Test the multiple capabilities of the CNF modules."""
+
+    def test_querying_and_serialization(self):
+        """Test deserializing, querying and serializing a CnfList."""
+        cnfs = CnfList.from_cnf_string(CNF_TEST_DATA)
+
+        self._modify(cnfs)
+
+        with NamedTemporaryFile() as tmpfile:
+            cnfs.to_cnf_file(tmpfile.name)
+
+            with open(tmpfile.name, "r", encoding="latin1") as f:
+                contents = f.read()
+
+        self.assertEqual(contents.splitlines(), EXPECTED_CNF_DATA.splitlines())
+
+        # make sure the result can be parsed again
+        CnfList.from_cnf_string(str(contents))
+
+    def test_querying_and_serialization_json(self):
+        """Test deserializing, querying and serializing a CnfList as JSON."""
+        # first serialize to JSON without renumbering to keep the structure
+        cnfs = CnfList.from_cnf_string(CNF_TEST_DATA)
+
+        with NamedTemporaryFile() as tmpfile:
+            cnfs.to_json_file(tmpfile.name, renumber=False)
+
+            with open(tmpfile.name, "r") as f:
+                contents = f.read()
+
+            # make sure the JSON structure is sane
+            json.loads(contents)
+
+        # now asserts that reading from JSON works
+        cnfs_2 = CnfList.from_json_string(contents)
+        self._modify(cnfs_2)
+        cnfs_2.renumber()
+
+        self.assertEqual(str(cnfs_2).splitlines(), EXPECTED_CNF_DATA.splitlines())
+
+        # make sure the result can be parsed again
+        CnfList.from_cnf_string(str(cnfs_2))
+
+    def _modify(self, cnfs):
+        """
+        Make test modifications on the list.
+
+        :param cnfs: list of cnfvars to modify
+        :type cnfs: :py:class:`CnfList`
+
+        .. note:: we unify this into a method so we can make sure that the test
+        using cnfvar strings and the test using JSON do the exactly same modifications.
+        """
+        self.assertFalse(cnfs.single_with_name("update_expired").is_enabled())
+        cnfs.single_with_name("update_expired") \
+            .enable()
+        self.assertTrue(cnfs.single_with_name("update_expired").is_enabled())
+
+        user_cnf = cnfs.first_with_value("jill")
+        user_cnf.value = "jane"
+        user_cnf.children[1].value = "Jane"
+        user_cnf.children.first_with_name("user_group_member_ref").value = "200"
+        user_cnf.add_children(("USER_GROUP_MEMBER_REF", 5))
+
+        # check correct types and equality
+        self.assertEqual(user_cnf.instance, 2)
+        self.assertEqual(user_cnf.lineno, 11)
+
+        other_cnf = cnfs.single_with_value("Vordefiniert: Alles verboten")
+        other_cnf.children \
+                 .where(lambda c: c.name == "emailfilter_ban_filterlist_mimetypes") \
+                 .single() \
+                 .comment = "hey this is a comment"
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/cnfvar/test_store.py b/test/cnfvar/test_store.py
new file mode 100644 (file)
index 0000000..e234c4d
--- /dev/null
@@ -0,0 +1,279 @@
+# The software in this package is distributed under the GNU General
+# Public License version 2 (with a special exception described below).
+#
+# A copy of GNU General Public License (GPL) is included in this distribution,
+# in the file COPYING.GPL.
+#
+# As a special exception, if other files instantiate templates or use macros
+# or inline functions from this file, or you compile this file and link it
+# with other works to produce a work based on this file, this file
+# does not by itself cause the resulting work to be covered
+# by the GNU General Public License.
+#
+# However the source code for this file must still be made available
+# in accordance with section (3) of the GNU General Public License.
+#
+# This exception does not invalidate any other reasons why a work based
+# on this file might be covered by the GNU General Public License.
+#
+# Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
+
+"""
+test_model.py: unit tests for cnfvar/model.py.
+
+Tests classes and functions in cnfvar/model.py
+
+For help see :py:mod:`unittest`
+"""
+
+import unittest
+import os
+import shutil
+from textwrap import dedent
+from tempfile import NamedTemporaryFile
+from unittest.mock import patch
+
+from src.cnfvar import binary, CnfList, CnfStore, BinaryCnfStore
+from src.arnied_api import Arnied, GetCnfRet
+
+
+TEST_CNF_FILE = os.path.join(os.path.dirname(__file__), "cnfs.cnf")
+
+
+class TestStore(unittest.TestCase):
+    """Test querying, commiting and deleting cnfvars using the stores."""
+
+    def test_querying_and_updating(self):
+        """Test querying and updating cnf variables."""
+        # To avoid creating long dummy objects, we can convert our test file
+        # to the arnied API structure using our API itself
+        fake_output = CnfList.from_cnf_file(TEST_CNF_FILE) \
+                             .to_api_structure()
+        fake_ret = GetCnfRet(vars=fake_output)
+
+        store = CnfStore()
+        with patch.object(Arnied, "get_cnf", return_value=fake_ret),\
+             patch.object(Arnied, "set_commit_cnf") as set_cnf_mock,\
+             patch.object(store, "_wait_for_generate"):
+            self._query_and_update(store)
+
+        cnf_api_input = set_cnf_mock.call_args.kwargs["vars"]
+        cnf_input = CnfList.from_api_structure(cnf_api_input)
+
+        expected_input = dedent("""\
+            1 NIC,0: ""
+            2   (1) NIC_COMMENT,0: "b0"
+            3   (1) NIC_DRIVER,0: "virtio_net"
+            4   (1) NIC_LAN_DNS_RELAYING_ALLOWED,0: "1"
+            5   (1) NIC_LAN_EMAIL_RELAYING_ALLOWED,0: "0"
+            6   (1) NIC_LAN_FIREWALL_RULESET_REF,0: "99"
+            7   (1) NIC_LAN_IP,0: "192.168.1.1"
+            8   (1) NIC_LAN_NAT_INTO,0: "0"
+            9   (1) NIC_LAN_NETMASK,0: "255.255.255.0"
+            10   (1) NIC_LAN_PROXY_PROFILE_REF,0: "-1"
+            11   (1) NIC_MAC,0: "02:00:00:00:01:01"
+            12   (1) NIC_TYPE,0: "NATLAN"
+            13   (1) NIC_LAN_PROXY_PROFILE_REF,1: "2"
+            14 NIC,1: ""
+            15   (14) NIC_COMMENT,0: "b18"
+            16   (14) NIC_DRIVER,0: "virtio_net"
+            17   (14) NIC_MAC,0: "02:00:00:00:01:02"
+            18   (14) NIC_TYPE,0: "DSLROUTER"
+            """)
+
+        self.assertEqual(str(cnf_input).splitlines(), expected_input.splitlines())
+
+    def test_deleting_cnfvars(self):
+        """Test that we can delete top-level cnfvars."""
+        # To avoid creating long dummy objects, we can convert our test file
+        # to the arnied API structure using our API itself
+        fake_output = CnfList.from_cnf_file(TEST_CNF_FILE) \
+                             .to_api_structure()
+        fake_ret = GetCnfRet(vars=fake_output)
+
+        store = CnfStore()
+        with patch.object(Arnied, "get_cnf", return_value=fake_ret):
+            cnfvars = store.query() \
+                           .where(lambda c: c.name == "THEME")
+
+        with patch.object(Arnied, "set_commit_cnf") as set_cnf_mock,\
+             patch.object(store, "_wait_for_generate"):
+            store.delete(cnfvars)
+
+        cnf_api_input = set_cnf_mock.call_args.kwargs["vars"]
+        cnf_input = CnfList.from_api_structure(cnf_api_input)
+
+        expected_input = dedent("""\
+            1 THEME,0: "Intra2net System"
+            2 THEME,1: "Xerberus"
+            """)
+
+        self.assertEqual([c.deleted for c in cnf_api_input],
+                         [True] * len(cnfvars))
+        self.assertEqual(str(cnf_input).splitlines(), expected_input.splitlines())
+
+    def _query_and_update(self, store):
+        """
+        Query the items, update a few variables and commit the changes.
+
+        :param store: the cnf store to use
+
+        The changes done were unified in this method so that we test the same
+        thing with different stores.
+        """
+        nics = store.query() \
+                    .where(lambda c: c.name == "nic")
+
+        self.assertEqual(len(nics), 4)
+
+        # test querying by instance and child
+        nic_0 = nics.single_with_instance(0)
+        nic_1 = nics.where_child(lambda c: c.name == "nic_comment" and c.value == "b1").single()
+
+        # test treating cnfvars as flags
+        self.assertFalse(nic_0.child_flag_enabled("NIC_LAN_DNS_RELAYING_ALLOWED"))
+        nic_0.enable_child_flag("nic_lan_dns_relaying_allowed")
+        self.assertTrue(nic_0.child_flag_enabled("NIC_LAN_DNS_RELAYING_ALLOWED"))
+
+        # test adding comments
+        nic_0.children.single_with_name("nic_comment").comment = "my cool nic"
+        nic_1.children.first_with_name("NIC_TYPE").comment = "a dsl router"
+
+        # test adding a reference from another cnfvar
+        proxy_profile = store.query().with_name("PROXY_PROFILE").first_with_instance(2)
+        nic_0.add_child(("nic_lan_proxy_profile_ref"), proxy_profile.instance)
+
+        # testing changing the value
+        nic_1.children.first(lambda c: c.name == "nic_comment") \
+                      .value = "b18"
+        store.commit(CnfList([nic_0, nic_1], renumber=True))
+
+
+class TestBinaryStore(TestStore):
+    """Test querying, commiting and deleting cnfvars using the binary store."""
+
+    def setUp(self):
+        """Set up mocks and replacements for the real cnf binaries."""
+        with NamedTemporaryFile(delete=False) as tmp:
+            self._get_cnf_output = tmp.name
+        with NamedTemporaryFile(delete=False) as tmp:
+            self._set_cnf_input = tmp.name
+
+        with NamedTemporaryFile(delete=False, mode="w") as tmp:
+            self._fake_get_cnf = tmp.name
+            os.chmod(tmp.name, 0o775)  # make file executable
+            tmp.write(dedent(f"""\
+                #!/bin/bash
+                cat "{self._get_cnf_output}"
+                """))
+            tmp.flush()
+
+        with NamedTemporaryFile(delete=False, mode="w") as tmp:
+            self._fake_set_cnf = tmp.name
+            os.chmod(tmp.name, 0o775)  # make file executable
+            tmp.write(dedent(f"""\
+                #!/bin/bash
+                input=$(</dev/stdin)
+                echo $@ > "{self._set_cnf_input}"
+                echo "$input" >> "{self._set_cnf_input}"
+                """))
+            tmp.flush()
+
+        get_cnf_mock = patch.object(binary, "BIN_GET_CNF", self._fake_get_cnf)
+        self._get_cnf_mock = (get_cnf_mock, get_cnf_mock.start())
+
+        set_cnf_mock = patch.object(binary, "BIN_SET_CNF", self._fake_set_cnf)
+        self._set_cnf_mock = (set_cnf_mock, set_cnf_mock.start())
+
+        self._arnied_mock = patch.object(BinaryCnfStore, "_call_arnied")
+        self._arnied_mock.start()
+
+    def tearDown(self) -> None:
+        """Drop mocks and clean up files."""
+        self._arnied_mock.stop()
+        self._get_cnf_mock[0].stop()
+        self._set_cnf_mock[0].stop()
+
+        os.unlink(self._get_cnf_output)
+        os.unlink(self._set_cnf_input)
+        os.unlink(self._fake_get_cnf)
+        os.unlink(self._fake_set_cnf)
+
+    def test_querying_and_updating(self):
+        """Test querying and updating cnf variables."""
+        # tell our fake get_cnf to return the whole file
+        shutil.copyfile(TEST_CNF_FILE, self._get_cnf_output)
+
+        store = BinaryCnfStore()
+
+        self._query_and_update(store)
+
+        expected_input = dedent("""\
+            1 NIC,0: ""
+            2   (1) NIC_COMMENT,0: "b0" # my cool nic
+            3   (1) NIC_DRIVER,0: "virtio_net"
+            4   (1) NIC_LAN_DNS_RELAYING_ALLOWED,0: "1"
+            5   (1) NIC_LAN_EMAIL_RELAYING_ALLOWED,0: "0"
+            6   (1) NIC_LAN_FIREWALL_RULESET_REF,0: "99"
+            7   (1) NIC_LAN_IP,0: "192.168.1.1"
+            8   (1) NIC_LAN_NAT_INTO,0: "0"
+            9   (1) NIC_LAN_NETMASK,0: "255.255.255.0"
+            10   (1) NIC_LAN_PROXY_PROFILE_REF,0: "-1"
+            11   (1) NIC_MAC,0: "02:00:00:00:01:01"
+            12   (1) NIC_TYPE,0: "NATLAN"
+            13   (1) NIC_LAN_PROXY_PROFILE_REF,1: "2"
+            14 NIC,1: ""
+            15   (14) NIC_COMMENT,0: "b18"
+            16   (14) NIC_DRIVER,0: "virtio_net"
+            17   (14) NIC_MAC,0: "02:00:00:00:01:02"
+            18   (14) NIC_TYPE,0: "DSLROUTER" # a dsl router
+            """)
+
+        with open(self._set_cnf_input, "r") as f:
+            set_cnf_args = f.readline()
+            set_cnf_input = f.read()
+
+        self.assertEqual(set_cnf_input.splitlines(), expected_input.splitlines())
+
+    def test_deleting_cnfvars(self):
+        """Test that we can delete top-level cnfvars."""
+        # tell our fake get_cnf to return the whole file
+        shutil.copyfile(TEST_CNF_FILE, self._get_cnf_output)
+
+        store = BinaryCnfStore()
+        cnfvars = store.query() \
+                       .where(lambda c: c.name == "THEME")
+
+        store.delete(cnfvars)
+
+        with open(self._set_cnf_input, "r") as f:
+            set_cnf_args = f.readline()
+            set_cnf_input = f.read()
+
+        expected_input = dedent("""\
+            1 THEME,0: "Intra2net System"
+            2   (1) THEME_ARROW_FILENAME,0: "arrow_intranator.gif"
+            3   (1) THEME_CSS_FILENAME,0: "intranator.css"
+            4   (1) THEME_DEFAULTVALUES_FILENAME,0: "defaultvalues-intranator.cnf"
+            5   (1) THEME_FAVICON_FILENAME,0: "favicon_intranator.ico"
+            6   (1) THEME_LICENSE_AGREEMENT_FILENAME,0: "license_agreement_intranator.html"
+            7   (1) THEME_LOGIN_FILENAME,0: "login_intranator.gif"
+            8   (1) THEME_LOGO_FILENAME,0: "intranator.gif"
+            9   (1) THEME_STATISTICS_FILENAME,0: "templates-intranator/arnielizer-config.xml"
+            10 THEME,1: "Xerberus"
+            11   (10) THEME_ARROW_FILENAME,0: "arrow_xerberus.gif"
+            12   (10) THEME_CSS_FILENAME,0: "xerberus.css"
+            13   (10) THEME_DEFAULTVALUES_FILENAME,0: "defaultvalues-xerberus.cnf"
+            14   (10) THEME_FAVICON_FILENAME,0: "favicon_xerberus.ico"
+            15   (10) THEME_LICENSE_AGREEMENT_FILENAME,0: "license_agreement_xerberus.html"
+            16   (10) THEME_LOGIN_FILENAME,0: "login_xerberus.gif"
+            17   (10) THEME_LOGO_FILENAME,0: "xerberus.gif"
+            18   (10) THEME_STATISTICS_FILENAME,0: "templates-xerberus/arnielizer-config.xml"
+            """)
+
+        self.assertIn("-x", set_cnf_args)
+        self.assertEqual(set_cnf_input.splitlines(), expected_input.splitlines())
+
+
+if __name__ == '__main__':
+    unittest.main()
index 4731472..03a951d 100644 (file)
@@ -18,7 +18,8 @@
 #
 # Copyright (c) 2016-2022 Intra2net AG <info@intra2net.com>
 
-""" test_arnied_api.py: unit tests for arnied_api.py
+"""
+test_arnied_api.py: unit tests for arnied_api.py.
 
 Tests classes and functions in arnied_api.py