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
20from socket import IPPROTO_TCP, IPPROTO_UDP
21
22from .mixins import MatchContext
23from .query import PolicyQuery
24from .policyrep import port_range, PortconProtocol
25from .util import match_range
26
27
28class PortconQuery(MatchContext, PolicyQuery):
29
30    """
31    Port context query.
32
33    Parameter:
34    policy          The policy to query.
35
36    Keyword Parameters/Class attributes:
37    protocol        The protocol to match (socket.IPPROTO_TCP for
38                    TCP or socket.IPPROTO_UDP for UDP)
39
40    ports           A 2-tuple of the port range to match. (Set both to
41                    the same value for a single port)
42    ports_subset    If true, the criteria will match if it is a subset
43                    of the portcon's range.
44    ports_overlap   If true, the criteria will match if it overlaps
45                    any of the portcon's range.
46    ports_superset  If true, the criteria will match if it is a superset
47                    of the portcon's range.
48    ports_proper    If true, use proper superset/subset operations.
49                    No effect if not using set operations.
50
51    user            The criteria to match the context's user.
52    user_regex      If true, regular expression matching
53                    will be used on the user.
54
55    role            The criteria to match the context's role.
56    role_regex      If true, regular expression matching
57                    will be used on the role.
58
59    type_           The criteria to match the context's type.
60    type_regex      If true, regular expression matching
61                    will be used on the type.
62
63    range_          The criteria to match the context's range.
64    range_subset    If true, the criteria will match if it is a subset
65                    of the context's range.
66    range_overlap   If true, the criteria will match if it overlaps
67                    any of the context's range.
68    range_superset  If true, the criteria will match if it is a superset
69                    of the context's range.
70    range_proper    If true, use proper superset/subset operations.
71                    No effect if not using set operations.
72    """
73
74    _protocol = None
75    _ports = None
76    ports_subset = False
77    ports_overlap = False
78    ports_superset = False
79    ports_proper = False
80
81    @property
82    def ports(self):
83        return self._ports
84
85    @ports.setter
86    def ports(self, value):
87        pending_ports = port_range(*value)
88
89        if all(pending_ports):
90            if pending_ports.low < 1 or pending_ports.high < 1:
91                raise ValueError("Port numbers must be positive: {0.low}-{0.high}".
92                                 format(pending_ports))
93
94            if pending_ports.low > pending_ports.high:
95                raise ValueError(
96                    "The low port must be smaller than the high port: {0.low}-{0.high}".
97                    format(pending_ports))
98
99            self._ports = pending_ports
100        else:
101            self._ports = None
102
103    @property
104    def protocol(self):
105        return self._protocol
106
107    @protocol.setter
108    def protocol(self, value):
109        if value:
110            self._protocol = PortconProtocol(value)
111        else:
112            self._protocol = None
113
114    def __init__(self, policy, **kwargs):
115        super(PortconQuery, self).__init__(policy, **kwargs)
116        self.log = logging.getLogger(__name__)
117
118    def results(self):
119        """Generator which yields all matching portcons."""
120        self.log.info("Generating portcon results from {0.policy}".format(self))
121        self.log.debug("Ports: {0.ports}, overlap: {0.ports_overlap}, "
122                       "subset: {0.ports_subset}, superset: {0.ports_superset}, "
123                       "proper: {0.ports_proper}".format(self))
124        self.log.debug("Protocol: {0.protocol!r}".format(self))
125        self._match_context_debug(self.log)
126
127        for portcon in self.policy.portcons():
128
129            if self.ports and not match_range(
130                    portcon.ports,
131                    self.ports,
132                    self.ports_subset,
133                    self.ports_overlap,
134                    self.ports_superset,
135                    self.ports_proper):
136                continue
137
138            if self.protocol and self.protocol != portcon.protocol:
139                continue
140
141            if not self._match_context(portcon.context):
142                continue
143
144            yield portcon
145