1# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
2#
3# Copyright (C) 2006 Red Hat
4# see file 'COPYING' for use and warranty information
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2 only
9#
10# This program 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 General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19
20import string
21import selinux
22
23# OVERVIEW
24#
25# This file contains objects and functions used to represent the reference
26# policy (including the headers, M4 macros, and policy language statements).
27#
28# This representation is very different from the semantic representation
29# used in libsepol. Instead, it is a more typical abstract representation
30# used by the first stage of compilers. It is basically a parse tree.
31#
32# This choice is intentional as it allows us to handle the unprocessed
33# M4 statements - including the $1 style arguments - and to more easily generate
34# the data structures that we need for policy generation.
35#
36
37# Constans for referring to fields
38SRC_TYPE  = 0
39TGT_TYPE  = 1
40OBJ_CLASS = 2
41PERMS     = 3
42ROLE      = 4
43DEST_TYPE = 5
44
45# String represenations of the above constants
46field_to_str = ["source", "target", "object", "permission", "role", "destination" ]
47str_to_field = { "source" : SRC_TYPE, "target" : TGT_TYPE, "object" : OBJ_CLASS,
48                "permission" : PERMS, "role" : ROLE, "destination" : DEST_TYPE }
49
50# Base Classes
51
52class PolicyBase:
53    def __init__(self, parent=None):
54        self.parent = None
55        self.comment = None
56
57class Node(PolicyBase):
58    """Base class objects produced from parsing the reference policy.
59
60    The Node class is used as the base class for any non-leaf
61    object produced by parsing the reference policy. This object
62    should contain a reference to its parent (or None for a top-level
63    object) and 0 or more children.
64
65    The general idea here is to have a very simple tree structure. Children
66    are not separated out by type. Instead the tree structure represents
67    fairly closely the real structure of the policy statements.
68
69    The object should be iterable - by default over all children but
70    subclasses are free to provide additional iterators over a subset
71    of their childre (see Interface for example).
72    """
73
74    def __init__(self, parent=None):
75        PolicyBase.__init__(self, parent)
76        self.children = []
77
78    def __iter__(self):
79        return iter(self.children)
80
81    # Not all of the iterators will return something on all Nodes, but
82    # they won't explode either. Putting them here is just easier.
83
84    # Top level nodes
85
86    def nodes(self):
87        return filter(lambda x: isinstance(x, Node), walktree(self))
88
89    def modules(self):
90        return filter(lambda x: isinstance(x, Module), walktree(self))
91
92    def interfaces(self):
93        return filter(lambda x: isinstance(x, Interface), walktree(self))
94
95    def templates(self):
96        return filter(lambda x: isinstance(x, Template), walktree(self))
97
98    def support_macros(self):
99        return filter(lambda x: isinstance(x, SupportMacros), walktree(self))
100
101    # Common policy statements
102
103    def module_declarations(self):
104        return filter(lambda x: isinstance(x, ModuleDeclaration), walktree(self))
105
106    def interface_calls(self):
107        return filter(lambda x: isinstance(x, InterfaceCall), walktree(self))
108
109    def avrules(self):
110        return filter(lambda x: isinstance(x, AVRule), walktree(self))
111
112    def typerules(self):
113        return filter(lambda x: isinstance(x, TypeRule), walktree(self))
114
115    def typeattributes(self):
116        """Iterate over all of the TypeAttribute children of this Interface."""
117        return filter(lambda x: isinstance(x, TypeAttribute), walktree(self))
118
119    def roleattributes(self):
120        """Iterate over all of the RoleAttribute children of this Interface."""
121        return filter(lambda x: isinstance(x, RoleAttribute), walktree(self))
122
123    def requires(self):
124        return filter(lambda x: isinstance(x, Require), walktree(self))
125
126    def roles(self):
127        return filter(lambda x: isinstance(x, Role), walktree(self))
128
129    def role_allows(self):
130        return filter(lambda x: isinstance(x, RoleAllow), walktree(self))
131
132    def role_types(self):
133        return filter(lambda x: isinstance(x, RoleType), walktree(self))
134
135    def __str__(self):
136        if self.comment:
137            return str(self.comment) + "\n" + self.to_string()
138        else:
139            return self.to_string()
140
141    def __repr__(self):
142        return "<%s(%s)>" % (self.__class__.__name__, self.to_string())
143
144    def to_string(self):
145        return ""
146
147
148class Leaf(PolicyBase):
149    def __init__(self, parent=None):
150        PolicyBase.__init__(self, parent)
151
152    def __str__(self):
153        if self.comment:
154            return str(self.comment) + "\n" + self.to_string()
155        else:
156            return self.to_string()
157
158    def __repr__(self):
159        return "<%s(%s)>" % (self.__class__.__name__, self.to_string())
160
161    def to_string(self):
162        return ""
163
164
165
166# Utility functions
167
168def walktree(node, depthfirst=True, showdepth=False, type=None):
169    """Iterate over a Node and its Children.
170
171    The walktree function iterates over a tree containing Nodes and
172    leaf objects. The iteration can perform a depth first or a breadth
173    first traversal of the tree (controlled by the depthfirst
174    paramater. The passed in node will be returned.
175
176    This function will only work correctly for trees - arbitrary graphs
177    will likely cause infinite looping.
178    """
179    # We control depth first / versus breadth first by
180    # how we pop items off of the node stack.
181    if depthfirst:
182        index = -1
183    else:
184        index = 0
185
186    stack = [(node, 0)]
187    while len(stack) > 0:
188        cur, depth = stack.pop(index)
189        if showdepth:
190            yield cur, depth
191        else:
192            yield cur
193
194        # If the node is not a Node instance it must
195        # be a leaf - so no need to add it to the stack
196        if isinstance(cur, Node):
197            items = []
198            i = len(cur.children) - 1
199            while i >= 0:
200                if type is None or isinstance(cur.children[i], type):
201                    items.append((cur.children[i], depth + 1))
202                i -= 1
203
204            stack.extend(items)
205
206def walknode(node, type=None):
207    """Iterate over the direct children of a Node.
208
209    The walktree function iterates over the children of a Node.
210    Unlike walktree it does note return the passed in node or
211    the children of any Node objects (that is, it does not go
212    beyond the current level in the tree).
213    """
214    for x in node:
215        if type is None or isinstance(x, type):
216            yield x
217
218
219def list_to_space_str(s, cont=('{', '}')):
220    """Convert a set (or any sequence type) into a string representation
221    formatted to match SELinux space separated list conventions.
222
223    For example the list ['read', 'write'] would be converted into:
224    '{ read write }'
225    """
226    l = len(s)
227    str = ""
228    if l < 1:
229        raise ValueError("cannot convert 0 len set to string")
230    str = " ".join(s)
231    if l == 1:
232        return str
233    else:
234        return cont[0] + " " + str + " " + cont[1]
235
236def list_to_comma_str(s):
237    l = len(s)
238    if l < 1:
239        raise ValueError("cannot conver 0 len set to comma string")
240
241    return ", ".join(s)
242
243# Basic SELinux types
244
245class IdSet(set):
246    def __init__(self, list=None):
247        if list:
248            set.__init__(self, list)
249        else:
250            set.__init__(self)
251        self.compliment = False
252
253    def to_space_str(self):
254        return list_to_space_str(self)
255
256    def to_comma_str(self):
257        return list_to_comma_str(self)
258
259class SecurityContext(Leaf):
260    """An SELinux security context with optional MCS / MLS fields."""
261    def __init__(self, context=None, parent=None):
262        """Create a SecurityContext object, optionally from a string.
263
264        Parameters:
265           [context] - string representing a security context. Same format
266              as a string passed to the from_string method.
267        """
268        Leaf.__init__(self, parent)
269        self.user = ""
270        self.role = ""
271        self.type = ""
272        self.level = None
273        if context is not None:
274            self.from_string(context)
275
276    def from_string(self, context):
277        """Parse a string representing a context into a SecurityContext.
278
279        The string should be in the standard format - e.g.,
280        'user:role:type:level'.
281
282        Raises ValueError if the string is not parsable as a security context.
283        """
284        fields = context.split(":")
285        if len(fields) < 3:
286            raise ValueError("context string [%s] not in a valid format" % context)
287
288        self.user = fields[0]
289        self.role = fields[1]
290        self.type = fields[2]
291        if len(fields) > 3:
292            # FUTURE - normalize level fields to allow more comparisons to succeed.
293            self.level = ':'.join(fields[3:])
294        else:
295            self.level = None
296
297    def __eq__(self, other):
298        """Compare two SecurityContext objects - all fields must be exactly the
299        the same for the comparison to work. It is possible for the level fields
300        to be semantically the same yet syntactically different - in this case
301        this function will return false.
302        """
303        return self.user == other.user and \
304               self.role == other.role and \
305               self.type == other.type and \
306               self.level == other.level
307
308    def to_string(self, default_level=None):
309        """Return a string representing this security context.
310
311        By default, the string will contiain a MCS / MLS level
312        potentially from the default which is passed in if none was
313        set.
314
315        Arguments:
316           default_level - the default level to use if self.level is an
317             empty string.
318
319        Returns:
320           A string represening the security context in the form
321              'user:role:type:level'.
322        """
323        fields = [self.user, self.role, self.type]
324        if self.level is None:
325            if default_level is None:
326                if selinux.is_selinux_mls_enabled() == 1:
327                    fields.append("s0")
328            else:
329                fields.append(default_level)
330        else:
331            fields.append(self.level)
332        return ":".join(fields)
333
334class ObjectClass(Leaf):
335    """SELinux object class and permissions.
336
337    This class is a basic representation of an SELinux object
338    class - it does not represent separate common permissions -
339    just the union of the common and class specific permissions.
340    It is meant to be convenient for policy generation.
341    """
342    def __init__(self, name="", parent=None):
343        Leaf.__init__(self, parent)
344        self.name = name
345        self.perms = IdSet()
346
347# Basic statements
348
349class TypeAttribute(Leaf):
350    """SElinux typeattribute statement.
351
352    This class represents a typeattribute statement.
353    """
354    def __init__(self, parent=None):
355        Leaf.__init__(self, parent)
356        self.type = ""
357        self.attributes = IdSet()
358
359    def to_string(self):
360        return "typeattribute %s %s;" % (self.type, self.attributes.to_comma_str())
361
362class RoleAttribute(Leaf):
363    """SElinux roleattribute statement.
364
365    This class represents a roleattribute statement.
366    """
367    def __init__(self, parent=None):
368        Leaf.__init__(self, parent)
369        self.role = ""
370        self.roleattributes = IdSet()
371
372    def to_string(self):
373        return "roleattribute %s %s;" % (self.role, self.roleattributes.to_comma_str())
374
375
376class Role(Leaf):
377    def __init__(self, parent=None):
378        Leaf.__init__(self, parent)
379        self.role = ""
380        self.types = IdSet()
381
382    def to_string(self):
383        s = ""
384        for t in self.types:
385            s += "role %s types %s;\n" % (self.role, t)
386        return s
387
388class Type(Leaf):
389    def __init__(self, name="", parent=None):
390        Leaf.__init__(self, parent)
391        self.name = name
392        self.attributes = IdSet()
393        self.aliases = IdSet()
394
395    def to_string(self):
396        s = "type %s" % self.name
397        if len(self.aliases) > 0:
398            s = s + "alias %s" % self.aliases.to_space_str()
399        if len(self.attributes) > 0:
400            s = s + ", %s" % self.attributes.to_comma_str()
401        return s + ";"
402
403class TypeAlias(Leaf):
404    def __init__(self, parent=None):
405        Leaf.__init__(self, parent)
406        self.type = ""
407        self.aliases = IdSet()
408
409    def to_string(self):
410        return "typealias %s alias %s;" % (self.type, self.aliases.to_space_str())
411
412class Attribute(Leaf):
413    def __init__(self, name="", parent=None):
414        Leaf.__init__(self, parent)
415        self.name = name
416
417    def to_string(self):
418        return "attribute %s;" % self.name
419
420class Attribute_Role(Leaf):
421    def __init__(self, name="", parent=None):
422        Leaf.__init__(self, parent)
423        self.name = name
424
425    def to_string(self):
426        return "attribute_role %s;" % self.name
427
428
429# Classes representing rules
430
431class AVRule(Leaf):
432    """SELinux access vector (AV) rule.
433
434    The AVRule class represents all varieties of AV rules including
435    allow, dontaudit, and auditallow (indicated by the flags self.ALLOW,
436    self.DONTAUDIT, and self.AUDITALLOW respectively).
437
438    The source and target types, object classes, and perms are all represented
439    by sets containing strings. Sets are used to make it simple to add
440    strings repeatedly while avoiding duplicates.
441
442    No checking is done to make certain that the symbols are valid or
443    consistent (e.g., perms that don't match the object classes). It is
444    even possible to put invalid types like '$1' into the rules to allow
445    storage of the reference policy interfaces.
446    """
447    ALLOW = 0
448    DONTAUDIT = 1
449    AUDITALLOW = 2
450    NEVERALLOW = 3
451
452    def __init__(self, av=None, parent=None):
453        Leaf.__init__(self, parent)
454        self.src_types = IdSet()
455        self.tgt_types = IdSet()
456        self.obj_classes = IdSet()
457        self.perms = IdSet()
458        self.rule_type = self.ALLOW
459        if av:
460            self.from_av(av)
461
462    def __rule_type_str(self):
463        if self.rule_type == self.ALLOW:
464            return "allow"
465        elif self.rule_type == self.DONTAUDIT:
466            return "dontaudit"
467        else:
468            return "auditallow"
469
470    def from_av(self, av):
471        """Add the access from an access vector to this allow
472        rule.
473        """
474        self.src_types.add(av.src_type)
475        if av.src_type == av.tgt_type:
476            self.tgt_types.add("self")
477        else:
478            self.tgt_types.add(av.tgt_type)
479        self.obj_classes.add(av.obj_class)
480        self.perms.update(av.perms)
481
482    def to_string(self):
483        """Return a string representation of the rule
484        that is a valid policy language representation (assuming
485        that the types, object class, etc. are valie).
486        """
487        return "%s %s %s:%s %s;" % (self.__rule_type_str(),
488                                     self.src_types.to_space_str(),
489                                     self.tgt_types.to_space_str(),
490                                     self.obj_classes.to_space_str(),
491                                     self.perms.to_space_str())
492class TypeRule(Leaf):
493    """SELinux type rules.
494
495    This class is very similar to the AVRule class, but is for representing
496    the type rules (type_trans, type_change, and type_member). The major
497    difference is the lack of perms and only and sing destination type.
498    """
499    TYPE_TRANSITION = 0
500    TYPE_CHANGE = 1
501    TYPE_MEMBER = 2
502
503    def __init__(self, parent=None):
504        Leaf.__init__(self, parent)
505        self.src_types = IdSet()
506        self.tgt_types = IdSet()
507        self.obj_classes = IdSet()
508        self.dest_type = ""
509        self.rule_type = self.TYPE_TRANSITION
510
511    def __rule_type_str(self):
512        if self.rule_type == self.TYPE_TRANSITION:
513            return "type_transition"
514        elif self.rule_type == self.TYPE_CHANGE:
515            return "type_change"
516        else:
517            return "type_member"
518
519    def to_string(self):
520        return "%s %s %s:%s %s;" % (self.__rule_type_str(),
521                                     self.src_types.to_space_str(),
522                                     self.tgt_types.to_space_str(),
523                                     self.obj_classes.to_space_str(),
524                                     self.dest_type)
525
526class RoleAllow(Leaf):
527    def __init__(self, parent=None):
528        Leaf.__init__(self, parent)
529        self.src_roles = IdSet()
530        self.tgt_roles = IdSet()
531
532    def to_string(self):
533        return "allow %s %s;" % (self.src_roles.to_comma_str(),
534                                 self.tgt_roles.to_comma_str())
535
536class RoleType(Leaf):
537    def __init__(self, parent=None):
538        Leaf.__init__(self, parent)
539        self.role = ""
540        self.types = IdSet()
541
542    def to_string(self):
543        s = ""
544        for t in self.types:
545            s += "role %s types %s;\n" % (self.role, t)
546        return s
547
548class ModuleDeclaration(Leaf):
549    def __init__(self, parent=None):
550        Leaf.__init__(self, parent)
551        self.name = ""
552        self.version = ""
553        self.refpolicy = False
554
555    def to_string(self):
556        if self.refpolicy:
557            return "policy_module(%s, %s)" % (self.name, self.version)
558        else:
559            return "module %s %s;" % (self.name, self.version)
560
561class Conditional(Node):
562    def __init__(self, parent=None):
563        Node.__init__(self, parent)
564        self.cond_expr = []
565
566    def to_string(self):
567        return "[If %s]" % list_to_space_str(self.cond_expr, cont=("", ""))
568
569class Bool(Leaf):
570    def __init__(self, parent=None):
571        Leaf.__init__(self, parent)
572        self.name = ""
573        self.state = False
574
575    def to_string(self):
576        s = "bool %s " % self.name
577        if s.state:
578            return s + "true"
579        else:
580            return s + "false"
581
582class InitialSid(Leaf):
583    def __init(self, parent=None):
584        Leaf.__init__(self, parent)
585        self.name = ""
586        self.context = None
587
588    def to_string(self):
589        return "sid %s %s" % (self.name, str(self.context))
590
591class GenfsCon(Leaf):
592    def __init__(self, parent=None):
593        Leaf.__init__(self, parent)
594        self.filesystem = ""
595        self.path = ""
596        self.context = None
597
598    def to_string(self):
599        return "genfscon %s %s %s" % (self.filesystem, self.path, str(self.context))
600
601class FilesystemUse(Leaf):
602    XATTR = 1
603    TRANS = 2
604    TASK = 3
605
606    def __init__(self, parent=None):
607        Leaf.__init__(self, parent)
608        self.type = self.XATTR
609        self.filesystem = ""
610        self.context = None
611
612    def to_string(self):
613        s = ""
614        if self.type == XATTR:
615            s = "fs_use_xattr "
616        elif self.type == TRANS:
617            s = "fs_use_trans "
618        elif self.type == TASK:
619            s = "fs_use_task "
620
621        return "%s %s %s;" % (s, self.filesystem, str(self.context))
622
623class PortCon(Leaf):
624    def __init__(self, parent=None):
625        Leaf.__init__(self, parent)
626        self.port_type = ""
627        self.port_number = ""
628        self.context = None
629
630    def to_string(self):
631        return "portcon %s %s %s" % (self.port_type, self.port_number, str(self.context))
632
633class NodeCon(Leaf):
634    def __init__(self, parent=None):
635        Leaf.__init__(self, parent)
636        self.start = ""
637        self.end = ""
638        self.context = None
639
640    def to_string(self):
641        return "nodecon %s %s %s" % (self.start, self.end, str(self.context))
642
643class NetifCon(Leaf):
644    def __init__(self, parent=None):
645        Leaf.__init__(self, parent)
646        self.interface = ""
647        self.interface_context = None
648        self.packet_context = None
649
650    def to_string(self):
651        return "netifcon %s %s %s" % (self.interface, str(self.interface_context),
652                                   str(self.packet_context))
653class PirqCon(Leaf):
654    def __init__(self, parent=None):
655        Leaf.__init__(self, parent)
656        self.pirq_number = ""
657        self.context = None
658
659    def to_string(self):
660        return "pirqcon %s %s" % (self.pirq_number, str(self.context))
661
662class IomemCon(Leaf):
663    def __init__(self, parent=None):
664        Leaf.__init__(self, parent)
665        self.device_mem = ""
666        self.context = None
667
668    def to_string(self):
669        return "iomemcon %s %s" % (self.device_mem, str(self.context))
670
671class IoportCon(Leaf):
672    def __init__(self, parent=None):
673        Leaf.__init__(self, parent)
674        self.ioport = ""
675        self.context = None
676
677    def to_string(self):
678        return "ioportcon %s %s" % (self.ioport, str(self.context))
679
680class PciDeviceCon(Leaf):
681    def __init__(self, parent=None):
682        Leaf.__init__(self, parent)
683        self.device = ""
684        self.context = None
685
686    def to_string(self):
687        return "pcidevicecon %s %s" % (self.device, str(self.context))
688
689class DeviceTreeCon(Leaf):
690    def __init__(self, parent=None):
691        Leaf.__init__(self, parent)
692        self.path = ""
693        self.context = None
694
695    def to_string(self):
696        return "devicetreecon %s %s" % (self.path, str(self.context))
697
698# Reference policy specific types
699
700def print_tree(head):
701    for node, depth in walktree(head, showdepth=True):
702        s = ""
703        for i in range(depth):
704            s = s + "\t"
705        print(s + str(node))
706
707
708class Headers(Node):
709    def __init__(self, parent=None):
710        Node.__init__(self, parent)
711
712    def to_string(self):
713        return "[Headers]"
714
715
716class Module(Node):
717    def __init__(self, parent=None):
718        Node.__init__(self, parent)
719
720    def to_string(self):
721        return ""
722
723class Interface(Node):
724    """A reference policy interface definition.
725
726    This class represents a reference policy interface definition.
727    """
728    def __init__(self, name="", parent=None):
729        Node.__init__(self, parent)
730        self.name = name
731
732    def to_string(self):
733        return "[Interface name: %s]" % self.name
734
735class TunablePolicy(Node):
736    def __init__(self, parent=None):
737        Node.__init__(self, parent)
738        self.cond_expr = []
739
740    def to_string(self):
741        return "[Tunable Policy %s]" % list_to_space_str(self.cond_expr, cont=("", ""))
742
743class Template(Node):
744    def __init__(self, name="", parent=None):
745        Node.__init__(self, parent)
746        self.name = name
747
748    def to_string(self):
749        return "[Template name: %s]" % self.name
750
751class IfDef(Node):
752    def __init__(self, name="", parent=None):
753        Node.__init__(self, parent)
754        self.name = name
755
756    def to_string(self):
757        return "[Ifdef name: %s]" % self.name
758
759class InterfaceCall(Leaf):
760    def __init__(self, ifname="", parent=None):
761        Leaf.__init__(self, parent)
762        self.ifname = ifname
763        self.args = []
764        self.comments = []
765
766    def matches(self, other):
767        if self.ifname != other.ifname:
768            return False
769        if len(self.args) != len(other.args):
770            return False
771        for a,b in zip(self.args, other.args):
772            if a != b:
773                return False
774        return True
775
776    def to_string(self):
777        s = "%s(" % self.ifname
778        i = 0
779        for a in self.args:
780            if isinstance(a, list):
781                str = list_to_space_str(a)
782            else:
783                str = a
784
785            if i != 0:
786                s = s + ", %s" % str
787            else:
788                s = s + str
789            i += 1
790        return s + ")"
791
792class OptionalPolicy(Node):
793    def __init__(self, parent=None):
794        Node.__init__(self, parent)
795
796    def to_string(self):
797        return "[Optional Policy]"
798
799class SupportMacros(Node):
800    def __init__(self, parent=None):
801        Node.__init__(self, parent)
802        self.map = None
803
804    def to_string(self):
805        return "[Support Macros]"
806
807    def __expand_perm(self, perm):
808        # Recursive expansion - the assumption is that these
809        # are ordered correctly so that no macro is used before
810        # it is defined
811        s = set()
812        if perm in self.map:
813            for p in self.by_name(perm):
814                s.update(self.__expand_perm(p))
815        else:
816            s.add(perm)
817        return s
818
819    def __gen_map(self):
820        self.map = {}
821        for x in self:
822            exp_perms = set()
823            for perm in x.perms:
824                exp_perms.update(self.__expand_perm(perm))
825            self.map[x.name] = exp_perms
826
827    def by_name(self, name):
828        if not self.map:
829            self.__gen_map()
830        return self.map[name]
831
832    def has_key(self, name):
833        if not self.map:
834            self.__gen_map()
835        return name in self.map
836
837class Require(Leaf):
838    def __init__(self, parent=None):
839        Leaf.__init__(self, parent)
840        self.types = IdSet()
841        self.obj_classes = { }
842        self.roles = IdSet()
843        self.data = IdSet()
844        self.users = IdSet()
845
846    def add_obj_class(self, obj_class, perms):
847        p = self.obj_classes.setdefault(obj_class, IdSet())
848        p.update(perms)
849
850
851    def to_string(self):
852        s = []
853        s.append("require {")
854        for type in self.types:
855            s.append("\ttype %s;" % type)
856        for obj_class, perms in self.obj_classes.items():
857            s.append("\tclass %s %s;" % (obj_class, perms.to_space_str()))
858        for role in self.roles:
859            s.append("\trole %s;" % role)
860        for bool in self.data:
861            s.append("\tbool %s;" % bool)
862        for user in self.users:
863            s.append("\tuser %s;" % user)
864        s.append("}")
865
866        # Handle empty requires
867        if len(s) == 2:
868            return ""
869
870        return "\n".join(s)
871
872
873class ObjPermSet:
874    def __init__(self, name):
875        self.name = name
876        self.perms = set()
877
878    def to_string(self):
879        return "define(`%s', `%s')" % (self.name, self.perms.to_space_str())
880
881class ClassMap:
882    def __init__(self, obj_class, perms):
883        self.obj_class = obj_class
884        self.perms = perms
885
886    def to_string(self):
887        return self.obj_class + ": " + self.perms
888
889class Comment:
890    def __init__(self, l=None):
891        if l:
892            self.lines = l
893        else:
894            self.lines = []
895
896    def to_string(self):
897        # If there are no lines, treat this as a spacer between
898        # policy statements and return a new line.
899        if len(self.lines) == 0:
900            return ""
901        else:
902            out = []
903            for line in self.lines:
904                out.append("#" + line)
905            return "\n".join(out)
906
907    def merge(self, other):
908        if len(other.lines):
909            for line in other.lines:
910                if line != "":
911                    self.lines.append(line)
912
913    def __str__(self):
914        return self.to_string()
915
916
917