From 86b67ca96a65a5def47329757b957755b9025585 Mon Sep 17 00:00:00 2001
From: Chris PeBenito <cpebenito@tresys.com>
Date: Fri, 22 Aug 2014 08:44:58 -0400
Subject: [PATCH] Initial constraints implementation.

---
 libapol/policyrep/__init__.py      |  21 ++-
 libapol/policyrep/constraint.py    | 197 ++++++++++++++++++++++++++++-
 libapol/policyrep/mlsconstraint.py |  32 -----
 3 files changed, 214 insertions(+), 36 deletions(-)
 delete mode 100644 libapol/policyrep/mlsconstraint.py

diff --git a/libapol/policyrep/__init__.py b/libapol/policyrep/__init__.py
index f42d81b..6297cab 100644
--- a/libapol/policyrep/__init__.py
+++ b/libapol/policyrep/__init__.py
@@ -50,7 +50,6 @@ import mlsrule
 
 # Constraints
 import constraint
-import mlsconstraint
 
 # In-policy Labeling
 import initsid
@@ -189,6 +188,26 @@ class SELinuxPolicy(object):
     # Constraints generators
     #
 
+    def constraints(self):
+        """Generator which yields all constraints."""
+
+        qiter = self.policy.get_constraint_iter()
+        while not qiter.end():
+            c = constraint.Constraint(self.policy, qpol.qpol_constraint_from_void(qiter.get_item()))
+            if not c.ismls:
+                yield c
+            qiter.next()
+
+    def mlsconstraints(self):
+        """Generator which yields all MLS constraints."""
+
+        qiter = self.policy.get_constraint_iter()
+        while not qiter.end():
+            c = constraint.Constraint(self.policy, qpol.qpol_constraint_from_void(qiter.get_item()))
+            if c.ismls:
+                yield c
+            qiter.next()
+
     #
     # In-policy Labeling statement generators
     #
diff --git a/libapol/policyrep/constraint.py b/libapol/policyrep/constraint.py
index 6e1755f..9562138 100644
--- a/libapol/policyrep/constraint.py
+++ b/libapol/policyrep/constraint.py
@@ -16,14 +16,205 @@
 # License along with SETools.  If not, see
 # <http://www.gnu.org/licenses/>.
 #
-import symbol
+import string
+
 import setools.qpol as qpol
 
+import symbol
+import objclass
+
 
 class Constraint(symbol.PolicySymbol):
 
-    """A constraint rule."""
-    pass
+    """A constraint rule (regular or MLS)."""
+
+    _expr_type_to_text = {
+        qpol.QPOL_CEXPR_TYPE_NOT: "not",
+        qpol.QPOL_CEXPR_TYPE_AND: "and",
+        qpol.QPOL_CEXPR_TYPE_OR: "\n\tor"}
+
+    _expr_op_to_text = {
+        qpol.QPOL_CEXPR_OP_EQ: "==",
+        qpol.QPOL_CEXPR_OP_NEQ: "!=",
+        qpol.QPOL_CEXPR_OP_DOM: "dom",
+        qpol.QPOL_CEXPR_OP_DOMBY: "domby",
+        qpol.QPOL_CEXPR_OP_INCOMP: "incomp"}
+
+    _sym_to_text = {
+        qpol.QPOL_CEXPR_SYM_USER: "u1",
+        qpol.QPOL_CEXPR_SYM_ROLE: "r1",
+        qpol.QPOL_CEXPR_SYM_TYPE: "t1",
+        qpol.QPOL_CEXPR_SYM_USER + qpol.QPOL_CEXPR_SYM_TARGET: "u2",
+        qpol.QPOL_CEXPR_SYM_ROLE + qpol.QPOL_CEXPR_SYM_TARGET: "r2",
+        qpol.QPOL_CEXPR_SYM_TYPE + qpol.QPOL_CEXPR_SYM_TARGET: "t2",
+        qpol.QPOL_CEXPR_SYM_L1L2: "l1",
+        qpol.QPOL_CEXPR_SYM_L1H2: "l1",
+        qpol.QPOL_CEXPR_SYM_H1L2: "h1",
+        qpol.QPOL_CEXPR_SYM_H1H2: "h1",
+        qpol.QPOL_CEXPR_SYM_L1H1: "l1",
+        qpol.QPOL_CEXPR_SYM_L2H2: "l2",
+        qpol.QPOL_CEXPR_SYM_L1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2",
+        qpol.QPOL_CEXPR_SYM_L1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2",
+        qpol.QPOL_CEXPR_SYM_H1L2 + qpol.QPOL_CEXPR_SYM_TARGET: "l2",
+        qpol.QPOL_CEXPR_SYM_H1H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2",
+        qpol.QPOL_CEXPR_SYM_L1H1 + qpol.QPOL_CEXPR_SYM_TARGET: "h1",
+        qpol.QPOL_CEXPR_SYM_L2H2 + qpol.QPOL_CEXPR_SYM_TARGET: "h2"}
+
+    _expr_type_to_precedence = {
+        qpol.QPOL_CEXPR_TYPE_NOT: 3,
+        qpol.QPOL_CEXPR_TYPE_AND: 2,
+        qpol.QPOL_CEXPR_TYPE_OR: 1}
+
+    # all operators have the same precedence
+    _expr_op_precedence = 4
+
+    def __str__(self):
+        if self.ismls:
+            rule_string = "mlsconstrain {0.tclass} ".format(self)
+        else:
+            rule_string = "constrain {0.tclass} ".format(self)
+
+        perms = self.perms
+        if len(perms) > 1:
+            rule_string += "{{ {0} }} (\n".format(string.join(perms))
+        else:
+            # convert to list since sets cannot be indexed
+            rule_string += "{0} (\n".format(list(perms)[0])
+
+        rule_string += "\t{0}\n);".format(self.__build_expression())
+
+        return rule_string
+
+    def __build_expression(self):
+        # qpol representation is in postfix notation.  This code
+        # converts it to infix notation.  Parentheses are added
+        # to ensure correct expressions, though they may end up
+        # being overused.  Set previous operator at start to the
+        # highest precedence (op) so if there is a single binary
+        # operator, no parentheses are output
+
+        expr_string = ""
+        qpol_iter = self.qpol_symbol.get_expr_iter(self.policy)
+
+        stack = []
+        prev_oper = self._expr_op_precedence
+        while not qpol_iter.end():
+            expr_node = qpol.qpol_constraint_expr_node_from_void(
+                qpol_iter.get_item())
+
+            op = expr_node.get_op(self.policy)
+            sym_type = expr_node.get_sym_type(self.policy)
+            expr_type = expr_node.get_expr_type(self.policy)
+
+            if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR:
+                stack.append([self._sym_to_text[sym_type],
+                              self._expr_op_to_text[op],
+                              self._sym_to_text[sym_type + qpol.QPOL_CEXPR_SYM_TARGET]])
+                prev_oper = self._expr_op_precedence
+            elif expr_type == qpol.QPOL_CEXPR_TYPE_NAMES:
+                names = []
+                names_iter = expr_node.get_names_iter(self.policy)
+                while not names_iter.end():
+                    names.append(qpol.to_str(names_iter.get_item()))
+                    names_iter.next()
+
+                if not names:
+                    names_str = "<empty set>"
+                elif len(names) == 1:
+                    names_str = names[0]
+                else:
+                    names_str = "{{ {0} }}".format(string.join(names))
+
+                stack.append([self._sym_to_text[sym_type],
+                              self._expr_op_to_text[op],
+                              names_str])
+                prev_oper = self._expr_op_precedence
+            elif expr_type == qpol.QPOL_CEXPR_TYPE_NOT:
+                # unary operator
+                operand = stack.pop()
+                stack.append([self._expr_type_to_text[expr_type],
+                              "(",
+                              operand,
+                              ")"])
+                prev_oper = self._expr_type_to_precedence[expr_type]
+            else:
+                operand1 = stack.pop()
+                operand2 = stack.pop()
+
+                # if previous operator is of higher precedence
+                # no parentheses are needed.
+                if self._expr_type_to_precedence[expr_type] < prev_oper:
+                    stack.append([operand1,
+                                  self._expr_type_to_text[expr_type],
+                                  operand2])
+                else:
+                    stack.append(["(",
+                                  operand1,
+                                  self._expr_type_to_text[expr_type],
+                                  operand2,
+                                  ")"])
+
+                prev_oper = self._expr_type_to_precedence[expr_type]
+
+            qpol_iter.next()
+
+        return self.__unwind_subexpression(stack)
+
+    def __unwind_subexpression(self, expr):
+        ret = []
+
+        # do a string.join on sublists (subexpressions)
+        for i in expr:
+            if isinstance(i, list):
+                ret.append(self.__unwind_subexpression(i))
+            else:
+                ret.append(i)
+
+        return string.join(ret)
+
+    @property
+    def ismls(self):
+        try:
+            return self._ismls
+        except AttributeError:
+            self._ismls = False
+
+            qpol_iter = self.qpol_symbol.get_expr_iter(self.policy)
+            while not qpol_iter.end():
+                expr_node = qpol.qpol_constraint_expr_node_from_void(
+                    qpol_iter.get_item())
+
+                sym_type = expr_node.get_sym_type(self.policy)
+                expr_type = expr_node.get_expr_type(self.policy)
+
+                if expr_type == qpol.QPOL_CEXPR_TYPE_ATTR and sym_type >= qpol.QPOL_CEXPR_SYM_L1L2:
+                    self._ismls = True
+                    break
+
+                qpol_iter.next()
+
+            return self._ismls
+
+    @property
+    def perms(self):
+        """The constraint's permission set."""
+
+        iter = self.qpol_symbol.get_perm_iter(self.policy)
+
+        p = set()
+        while not iter.end():
+            p.add(qpol.to_str(iter.get_item()))
+            iter.next()
+
+        return p
+
+    def statement(self):
+        return str(self)
+
+    @property
+    def tclass(self):
+        """Object class for this constraint."""
+        return objclass.ObjClass(self.policy, self.qpol_symbol.get_class(self.policy))
 
 
 class ValidateTrans(symbol.PolicySymbol):
diff --git a/libapol/policyrep/mlsconstraint.py b/libapol/policyrep/mlsconstraint.py
deleted file mode 100644
index a985b78..0000000
--- a/libapol/policyrep/mlsconstraint.py
+++ /dev/null
@@ -1,32 +0,0 @@
-# Copyright 2014, Tresys Technology, LLC
-#
-# This file is part of SETools.
-#
-# SETools is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation, either version 2.1 of
-# the License, or (at your option) any later version.
-#
-# SETools is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with SETools.  If not, see
-# <http://www.gnu.org/licenses/>.
-#
-import constraint
-import setools.qpol as qpol
-
-
-class MLSConstraint(constraint.Constraint):
-
-    """An MLS constraint rule."""
-    pass
-
-
-class MLSValidateTrans(constraint.ValidateTrans):
-
-    """An MLS validate transition rule."""
-    pass