1# Copyright 2014-2015, Tresys Technology, LLC
2#
3# This file is part of SETools.
4#
5# SETools is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License as
7# published by the Free Software Foundation, either version 2.1 of
8# the License, or (at your option) any later version.
9#
10# SETools is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public
16# License along with SETools.  If not, see
17# <http://www.gnu.org/licenses/>.
18#
19import logging
20
21
22class PolicyQuery(object):
23
24    """Base class for SELinux policy queries."""
25
26    def __init__(self, policy, **kwargs):
27        self.log = logging.getLogger(self.__class__.__name__)
28
29        self.policy = policy
30
31        # keys are sorted in reverse order so regex settings
32        # are set before the criteria, e.g. name_regex
33        # is set before name.  This ensures correct behavior
34        # since the criteria descriptors are sensitve to
35        # regex settings.
36        for name in sorted(kwargs.keys(), reverse=True):
37            attr = getattr(self, name, None)  # None is not callable
38            if callable(attr):
39                raise ValueError("Keyword parameter {0} conflicts with a callable.".format(name))
40
41            setattr(self, name, kwargs[name])
42
43    @staticmethod
44    def _match_regex(obj, criteria, regex):
45        """
46        Match the object with optional regular expression.
47
48        Parameters:
49        obj         The object to match.
50        criteria    The criteria to match.
51        regex       If regular expression matching should be used.
52        """
53
54        if regex:
55            return bool(criteria.search(str(obj)))
56        else:
57            return obj == criteria
58
59    @staticmethod
60    def _match_set(obj, criteria, equal):
61        """
62        Match the object (a set) with optional set equality.
63
64        Parameters:
65        obj         The object to match. (a set)
66        criteria    The criteria to match. (a set)
67        equal       If set equality should be used. Otherwise
68                    any set intersection will match.
69        """
70
71        if equal:
72            return obj == criteria
73        else:
74            return bool(obj.intersection(criteria))
75
76    @staticmethod
77    def _match_in_set(obj, criteria, regex):
78        """
79        Match if the criteria is in the list, with optional
80        regular expression matching.
81
82        Parameters:
83        obj         The object to match.
84        criteria    The criteria to match.
85        regex       If regular expression matching should be used.
86        """
87
88        if regex:
89            return [m for m in obj if criteria.search(str(m))]
90        else:
91            return criteria in obj
92
93    @staticmethod
94    def _match_indirect_regex(obj, criteria, indirect, regex):
95        """
96        Match the object with optional regular expression and indirection.
97
98        Parameters:
99        obj         The object to match.
100        criteria    The criteria to match.
101        regex       If regular expression matching should be used.
102        indirect    If object indirection should be used, e.g.
103                    expanding an attribute.
104        """
105
106        if indirect:
107            return PolicyQuery._match_in_set((obj.expand()), criteria, regex)
108        else:
109            return PolicyQuery._match_regex(obj, criteria, regex)
110
111    @staticmethod
112    def _match_regex_or_set(obj, criteria, equal, regex):
113        """
114        Match the object (a set) with either set comparisons
115        (equality or intersection) or by regex matching of the
116        set members.  Regular expression matching will override
117        the set equality option.
118
119        Parameters:
120        obj         The object to match. (a set)
121        criteria    The criteria to match.
122        equal       If set equality should be used.  Otherwise
123                    any set intersection will match. Ignored
124                    if regular expression matching is used.
125        regex       If regular expression matching should be used.
126        """
127
128        if regex:
129            return [m for m in obj if criteria.search(str(m))]
130        else:
131            return PolicyQuery._match_set(obj, set(criteria), equal)
132
133    @staticmethod
134    def _match_range(obj, criteria, subset, overlap, superset, proper):
135        """
136        Match ranges of objects.
137
138        obj         An object with attributes named "low" and "high", representing the range.
139        criteria    An object with attributes named "low" and "high", representing the criteria.
140        subset      If true, the criteria will match if it is a subset obj's range.
141        overlap     If true, the criteria will match if it overlaps any of the obj's range.
142        superset    If true, the criteria will match if it is a superset of the obj's range.
143        proper      If true, use proper superset/subset operations.
144                    No effect if not using set operations.
145        """
146
147        if overlap:
148            return ((obj.low <= criteria.low <= obj.high) or (
149                     obj.low <= criteria.high <= obj.high) or (
150                     criteria.low <= obj.low and obj.high <= criteria.high))
151        elif subset:
152            if proper:
153                return ((obj.low < criteria.low and criteria.high <= obj.high) or (
154                         obj.low <= criteria.low and criteria.high < obj.high))
155            else:
156                return obj.low <= criteria.low and criteria.high <= obj.high
157        elif superset:
158            if proper:
159                return ((criteria.low < obj.low and obj.high <= criteria.high) or (
160                         criteria.low <= obj.low and obj.high < criteria.high))
161            else:
162                return (criteria.low <= obj.low and obj.high <= criteria.high)
163        else:
164            return criteria.low == obj.low and obj.high == criteria.high
165
166    @staticmethod
167    def _match_level(obj, criteria, dom, domby, incomp):
168        """
169        Match the an MLS level.
170
171        obj         The level to match.
172        criteria    The criteria to match. (a level)
173        dom         If true, the criteria will match if it dominates obj.
174        domby       If true, the criteria will match if it is dominated by obj.
175        incomp      If true, the criteria will match if it is incomparable to obj.
176        """
177
178        if dom:
179            return (criteria >= obj)
180        elif domby:
181            return (criteria <= obj)
182        elif incomp:
183            return (criteria ^ obj)
184        else:
185            return (criteria == obj)
186
187    def results(self):
188        """
189        Generator which returns the matches for the query.  This method
190        should be overridden by subclasses.
191        """
192        raise NotImplementedError
193