1#!/usr/bin/python3 -EsI
2# Authors: Karl MacMillan <[email protected]>
3# Authors: Dan Walsh <[email protected]>
4#
5# Copyright (C) 2006-2013  Red Hat
6# see file 'COPYING' for use and warranty information
7#
8# This program is free software; you can redistribute it and/or
9# modify it under the terms of the GNU General Public License as
10# published by the Free Software Foundation; version 2 only
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20#
21
22import sys
23import os
24
25import sepolgen.audit as audit
26import sepolgen.policygen as policygen
27import sepolgen.interfaces as interfaces
28import sepolgen.output as output
29import sepolgen.objectmodel as objectmodel
30import sepolgen.defaults as defaults
31import sepolgen.module as module
32from sepolgen.sepolgeni18n import _
33import selinux.audit2why as audit2why
34import locale
35try:
36    locale.setlocale(locale.LC_ALL, '')
37except:
38    pass
39
40
41class AuditToPolicy:
42    VERSION = "%prog .1"
43    SYSLOG = "/var/log/messages"
44
45    def __init__(self):
46        self.__options = None
47        self.__parser = None
48        self.__avs = None
49
50    def __parse_options(self):
51        from optparse import OptionParser
52
53        parser = OptionParser(version=self.VERSION)
54        parser.add_option("-b", "--boot", action="store_true", dest="boot", default=False,
55                          help="audit messages since last boot conflicts with -i")
56        parser.add_option("-a", "--all", action="store_true", dest="audit", default=False,
57                          help="read input from audit log - conflicts with -i")
58        parser.add_option("-p", "--policy", dest="policy", default=None, help="Policy file to use for analysis")
59        parser.add_option("-d", "--dmesg", action="store_true", dest="dmesg", default=False,
60                          help="read input from dmesg - conflicts with --all and --input")
61        parser.add_option("-i", "--input", dest="input",
62                          help="read input from <input> - conflicts with -a")
63        parser.add_option("-l", "--lastreload", action="store_true", dest="lastreload", default=False,
64                          help="read input only after the last reload")
65        parser.add_option("-r", "--requires", action="store_true", dest="requires", default=False,
66                          help="generate require statements for rules")
67        parser.add_option("-m", "--module", dest="module",
68                          help="set the module name - implies --requires")
69        parser.add_option("-M", "--module-package", dest="module_package",
70                          help="generate a module package - conflicts with -o and -m")
71        parser.add_option("-o", "--output", dest="output",
72                          help="append output to <filename>, conflicts with -M")
73        parser.add_option("-D", "--dontaudit", action="store_true",
74                          dest="dontaudit", default=False,
75                          help="generate policy with dontaudit rules")
76        parser.add_option("-R", "--reference", action="store_true", dest="refpolicy",
77                          default=True, help="generate refpolicy style output")
78        parser.add_option("-C", "--cil", action="store_true", dest="cil", help="generate CIL output")
79
80        parser.add_option("-N", "--noreference", action="store_false", dest="refpolicy",
81                          default=False, help="do not generate refpolicy style output")
82        parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
83                          default=False, help="explain generated output")
84        parser.add_option("-e", "--explain", action="store_true", dest="explain_long",
85                          default=False, help="fully explain generated output")
86        parser.add_option("-t", "--type", help="only process messages with a type that matches this regex",
87                          dest="type")
88        parser.add_option("--perm-map", dest="perm_map", help="file name of perm map")
89        parser.add_option("--interface-info", dest="interface_info", help="file name of interface information")
90        parser.add_option("-x", "--xperms", action="store_true", dest="xperms",
91                          default=False, help="generate extended permission rules")
92        parser.add_option("-w", "--why", dest="audit2why", action="store_true", default=(os.path.basename(sys.argv[0]) == "audit2why"),
93                          help="Translates SELinux audit messages into a description of why the access was denied")
94
95        options, args = parser.parse_args()
96
97        # Make -d, -a, and -i conflict
98        if options.audit is True or options.boot:
99            if options.input is not None:
100                sys.stderr.write("error: --all/--boot conflicts with --input\n")
101            if options.dmesg is True:
102                sys.stderr.write("error: --all/--boot conflicts with --dmesg\n")
103        if options.input is not None and options.dmesg is True:
104            sys.stderr.write("error: --input conflicts with --dmesg\n")
105
106        # Turn on requires generation if a module name is given. Also verify
107        # the module name.
108        if options.module:
109            name = options.module
110        else:
111            name = options.module_package
112        if name:
113            options.requires = True
114            if not module.is_valid_name(name):
115                sys.stderr.write('error: module names must begin with a letter, optionally followed by letters, numbers, "-", "_", "."\n')
116                sys.exit(2)
117
118        # Make -M and -o or -C conflict
119        if options.module_package:
120            if options.output:
121                sys.stderr.write("error: --module-package conflicts with --output\n")
122                sys.exit(2)
123            if options.module:
124                sys.stderr.write("error: --module-package conflicts with --module\n")
125                sys.exit(2)
126            if options.cil:
127                sys.stderr.write("error: --module-package conflicts with --cil\n")
128                sys.exit(2)
129
130        self.__options = options
131
132    def __read_input(self):
133        parser = audit.AuditParser(last_load_only=self.__options.lastreload)
134
135        filename = None
136        messages = None
137        f = None
138
139        # Figure out what input we want
140        if self.__options.input is not None:
141            filename = self.__options.input
142        elif self.__options.dmesg:
143            messages = audit.get_dmesg_msgs()
144        elif self.__options.audit:
145            try:
146                messages = audit.get_audit_msgs()
147            except OSError as e:
148                sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
149                sys.exit(1)
150        elif self.__options.boot:
151            try:
152                messages = audit.get_audit_boot_msgs()
153            except OSError as e:
154                sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
155                sys.exit(1)
156        else:
157            # This is the default if no input is specified
158            f = sys.stdin
159
160        # Get the input
161        if filename is not None:
162            try:
163                f = open(filename)
164            except IOError as e:
165                sys.stderr.write('could not open file %s - "%s"\n' % (filename, str(e)))
166                sys.exit(1)
167
168        if f is not None:
169            parser.parse_file(f)
170            f.close()
171
172        if messages is not None:
173            parser.parse_string(messages)
174
175        self.__parser = parser
176
177    def __process_input(self):
178        if self.__options.type:
179            avcfilter = audit.AVCTypeFilter(self.__options.type)
180            self.__avs = self.__parser.to_access(avcfilter)
181            csfilter = audit.ComputeSidTypeFilter(self.__options.type)
182            self.__role_types = self.__parser.to_role(csfilter)
183        else:
184            self.__avs = self.__parser.to_access()
185            self.__role_types = self.__parser.to_role()
186
187    def __load_interface_info(self):
188        # Load interface info file
189        if self.__options.interface_info:
190            fn = self.__options.interface_info
191        else:
192            fn = defaults.interface_info()
193        try:
194            fd = open(fn)
195        except:
196            sys.stderr.write("could not open interface info [%s]\n" % fn)
197            sys.exit(1)
198
199        ifs = interfaces.InterfaceSet()
200        ifs.from_file(fd)
201        fd.close()
202
203        # Also load perm maps
204        if self.__options.perm_map:
205            fn = self.__options.perm_map
206        else:
207            fn = defaults.perm_map()
208        try:
209            fd = open(fn)
210        except:
211            sys.stderr.write("could not open perm map [%s]\n" % fn)
212            sys.exit(1)
213
214        perm_maps = objectmodel.PermMappings()
215        perm_maps.from_file(fd)
216
217        return (ifs, perm_maps)
218
219    def __output_modulepackage(self, writer, generator):
220        generator.set_module_name(self.__options.module_package)
221        filename = self.__options.module_package + ".te"
222        packagename = self.__options.module_package + ".pp"
223
224        try:
225            fd = open(filename, "w")
226        except IOError as e:
227            sys.stderr.write("could not write output file: %s\n" % str(e))
228            sys.exit(1)
229
230        writer.write(generator.get_module(), fd)
231        fd.close()
232
233        mc = module.ModuleCompiler()
234
235        try:
236            mc.create_module_package(filename, self.__options.refpolicy)
237        except RuntimeError as e:
238            print(e)
239            sys.exit(1)
240
241        sys.stdout.write(
242"""******************** {important} ***********************
243{text}
244
245semodule -i {packagename}
246
247""".format(
248    important=_("IMPORTANT"),
249    text=_("To make this policy package active, execute:"),
250    packagename=packagename
251))
252
253    def __output_audit2why(self):
254        import selinux
255        try:
256            import sepolicy
257        except (ImportError, ValueError):
258            sepolicy = None
259        for i in self.__parser.avc_msgs:
260            rc = i.type
261            data = i.data
262            if rc >= 0:
263                print("%s\n\tWas caused by:" % i.message)
264            if rc == audit2why.ALLOW:
265                print("\t\tUnknown - would be allowed by active policy")
266                print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
267                print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
268                continue
269            if rc == audit2why.DONTAUDIT:
270                print("\t\tUnknown - should be dontaudit'd by active policy")
271                print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
272                print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
273                continue
274            if rc == audit2why.BOOLEAN:
275                if len(data) > 1:
276                    print("\tOne of the following booleans was set incorrectly.")
277                    for b in data:
278                        if sepolicy is not None:
279                            print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(b[0]))
280                        print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (b[0], b[1]))
281                else:
282                    print("\tThe boolean %s was set incorrectly. " % (data[0][0]))
283                    if sepolicy is not None:
284                        print("\tDescription:\n\t%s\n" % sepolicy.boolean_desc(data[0][0]))
285                    print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (data[0][0], data[0][1]))
286                continue
287
288            if rc == audit2why.TERULE:
289                print("\t\tMissing type enforcement (TE) allow rule.\n")
290                print("\t\tYou can use audit2allow to generate a loadable module to allow this access.\n")
291                continue
292
293            if rc == audit2why.CONSTRAINT:
294                print()  # !!!! This avc is a constraint violation.  You would need to modify the attributes of either the source or target types to allow this access.\n"
295                print("#Constraint rule:")
296                print("\n#\t" + data[0])
297                for reason in data[1:]:
298                    print("#\tPossible cause is the source %s and target %s are different.\n" % reason)
299
300            if rc == audit2why.RBAC:
301                print("\t\tMissing role allow rule.\n")
302                print("\t\tAdd an allow rule for the role pair.\n")
303                continue
304
305            if rc == audit2why.BOUNDS:
306                print("\t\tTypebounds violation.\n")
307                print("\t\tAdd an allow rule for the parent type.\n")
308                continue
309
310        audit2why.finish()
311        return
312
313    def __output(self):
314
315        if self.__options.audit2why:
316            try:
317                return self.__output_audit2why()
318            except RuntimeError as e:
319                print(e)
320                sys.exit(1)
321
322        g = policygen.PolicyGenerator()
323
324        g.set_gen_dontaudit(self.__options.dontaudit)
325
326        if self.__options.module:
327            g.set_module_name(self.__options.module)
328
329        # Interface generation
330        if self.__options.refpolicy:
331            ifs, perm_maps = self.__load_interface_info()
332            g.set_gen_refpol(ifs, perm_maps)
333
334        # Extended permissions
335        if self.__options.xperms:
336            g.set_gen_xperms(True)
337
338        # Explanation
339        if self.__options.verbose:
340            g.set_gen_explain(policygen.SHORT_EXPLANATION)
341        if self.__options.explain_long:
342            g.set_gen_explain(policygen.LONG_EXPLANATION)
343
344        # Requires
345        if self.__options.requires:
346            g.set_gen_requires(True)
347
348        # CIL output
349        if self.__options.cil:
350            g.set_gen_cil(True)
351
352        # Generate the policy
353        g.add_access(self.__avs)
354        g.add_role_types(self.__role_types)
355
356        # Output
357        writer = output.ModuleWriter()
358
359        # CIL output
360        if self.__options.cil:
361            writer.set_gen_cil(True)
362
363        # Module package
364        if self.__options.module_package:
365            self.__output_modulepackage(writer, g)
366        else:
367            # File or stdout
368            if self.__options.module:
369                g.set_module_name(self.__options.module)
370
371            if self.__options.output:
372                fd = open(self.__options.output, "a")
373            else:
374                fd = sys.stdout
375            writer.write(g.get_module(), fd)
376
377    def main(self):
378        try:
379            self.__parse_options()
380            if self.__options.policy:
381                audit2why.init(self.__options.policy)
382            else:
383                audit2why.init()
384
385            self.__read_input()
386            self.__process_input()
387            self.__output()
388        except KeyboardInterrupt:
389            sys.exit(0)
390        except ValueError as e:
391            print(e)
392            sys.exit(1)
393        except IOError as e:
394            print(e)
395            sys.exit(1)
396
397if __name__ == "__main__":
398    app = AuditToPolicy()
399    app.main()
400