1#! /usr/bin/python -Es
2# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
3# Authors: Dan Walsh <dwalsh@redhat.com>
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
79        parser.add_option("-N", "--noreference", action="store_false", dest="refpolicy",
80                          default=False, help="do not generate refpolicy style output")
81        parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
82                          default=False, help="explain generated output")
83        parser.add_option("-e", "--explain", action="store_true", dest="explain_long",
84                          default=False, help="fully explain generated output")
85        parser.add_option("-t", "--type", help="only process messages with a type that matches this regex",
86                          dest="type")
87        parser.add_option("--perm-map", dest="perm_map", help="file name of perm map")
88        parser.add_option("--interface-info", dest="interface_info", help="file name of interface information")
89        parser.add_option("--debug", dest="debug", action="store_true", default=False,
90                          help="leave generated modules for -M")
91        parser.add_option("-w", "--why", dest="audit2why", action="store_true", default=(os.path.basename(sys.argv[0]) == "audit2why"),
92                          help="Translates SELinux audit messages into a description of why the access was denied")
93
94        options, args = parser.parse_args()
95
96        # Make -d, -a, and -i conflict
97        if options.audit is True or options.boot:
98            if options.input is not None:
99                sys.stderr.write("error: --all/--boot conflicts with --input\n")
100            if options.dmesg is True:
101                sys.stderr.write("error: --all/--boot conflicts with --dmesg\n")
102        if options.input is not None and options.dmesg is True:
103            sys.stderr.write("error: --input conflicts with --dmesg\n")
104
105        # Turn on requires generation if a module name is given. Also verify
106        # the module name.
107        if options.module:
108            name = options.module
109        else:
110            name = options.module_package
111        if name:
112            options.requires = True
113            if not module.is_valid_name(name):
114                sys.stderr.write('error: module names must begin with a letter, optionally followed by letters, numbers, "-", "_", "."\n')
115                sys.exit(2)
116
117        # Make -M and -o conflict
118        if options.module_package:
119            if options.output:
120                sys.stderr.write("error: --module-package conflicts with --output\n")
121                sys.exit(2)
122            if options.module:
123                sys.stderr.write("error: --module-package conflicts with --module\n")
124                sys.exit(2)
125
126        self.__options = options
127
128    def __read_input(self):
129        parser = audit.AuditParser(last_load_only=self.__options.lastreload)
130
131        filename = None
132        messages = None
133        f = None
134
135        # Figure out what input we want
136        if self.__options.input is not None:
137            filename = self.__options.input
138        elif self.__options.dmesg:
139            messages = audit.get_dmesg_msgs()
140        elif self.__options.audit:
141            try:
142                messages = audit.get_audit_msgs()
143            except OSError as e:
144                sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
145                sys.exit(1)
146        elif self.__options.boot:
147            try:
148                messages = audit.get_audit_boot_msgs()
149            except OSError as e:
150                sys.stderr.write('could not run ausearch - "%s"\n' % str(e))
151                sys.exit(1)
152        else:
153            # This is the default if no input is specified
154            f = sys.stdin
155
156        # Get the input
157        if filename is not None:
158            try:
159                f = open(filename)
160            except IOError as e:
161                sys.stderr.write('could not open file %s - "%s"\n' % (filename, str(e)))
162                sys.exit(1)
163
164        if f is not None:
165            parser.parse_file(f)
166            f.close()
167
168        if messages is not None:
169            parser.parse_string(messages)
170
171        self.__parser = parser
172
173    def __process_input(self):
174        if self.__options.type:
175            avcfilter = audit.AVCTypeFilter(self.__options.type)
176            self.__avs = self.__parser.to_access(avcfilter)
177            csfilter = audit.ComputeSidTypeFilter(self.__options.type)
178            self.__role_types = self.__parser.to_role(csfilter)
179        else:
180            self.__avs = self.__parser.to_access()
181            self.__role_types = self.__parser.to_role()
182
183    def __load_interface_info(self):
184        # Load interface info file
185        if self.__options.interface_info:
186            fn = self.__options.interface_info
187        else:
188            fn = defaults.interface_info()
189        try:
190            fd = open(fn)
191        except:
192            sys.stderr.write("could not open interface info [%s]\n" % fn)
193            sys.exit(1)
194
195        ifs = interfaces.InterfaceSet()
196        ifs.from_file(fd)
197        fd.close()
198
199        # Also load perm maps
200        if self.__options.perm_map:
201            fn = self.__options.perm_map
202        else:
203            fn = defaults.perm_map()
204        try:
205            fd = open(fn)
206        except:
207            sys.stderr.write("could not open perm map [%s]\n" % fn)
208            sys.exit(1)
209
210        perm_maps = objectmodel.PermMappings()
211        perm_maps.from_file(fd)
212
213        return (ifs, perm_maps)
214
215    def __output_modulepackage(self, writer, generator):
216        generator.set_module_name(self.__options.module_package)
217        filename = self.__options.module_package + ".te"
218        packagename = self.__options.module_package + ".pp"
219
220        try:
221            fd = open(filename, "w")
222        except IOError as e:
223            sys.stderr.write("could not write output file: %s\n" % str(e))
224            sys.exit(1)
225
226        writer.write(generator.get_module(), fd)
227        fd.close()
228
229        mc = module.ModuleCompiler()
230
231        try:
232            mc.create_module_package(filename, self.__options.refpolicy)
233        except RuntimeError as e:
234            print(e)
235            sys.exit(1)
236
237        sys.stdout.write(_("******************** IMPORTANT ***********************\n"))
238        sys.stdout.write((_("To make this policy package active, execute:" +
239                            "\n\nsemodule -i %s\n\n") % packagename))
240
241    def __output_audit2why(self):
242        import selinux
243        import seobject
244        for i in self.__parser.avc_msgs:
245            rc = i.type
246            data = i.data
247            if rc >= 0:
248                print("%s\n\tWas caused by:" % i.message)
249            if rc == audit2why.ALLOW:
250                print("\t\tUnknown - would be allowed by active policy")
251                print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
252                print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
253                continue
254            if rc == audit2why.DONTAUDIT:
255                print("\t\tUnknown - should be dontaudit'd by active policy")
256                print("\t\tPossible mismatch between this policy and the one under which the audit message was generated.\n")
257                print("\t\tPossible mismatch between current in-memory boolean settings vs. permanent ones.\n")
258                continue
259            if rc == audit2why.BOOLEAN:
260                if len(data) > 1:
261                    print("\tOne of the following booleans was set incorrectly.")
262                    for b in data:
263                        print("\tDescription:\n\t%s\n" % seobject.boolean_desc(b[0]))
264                        print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (b[0], b[1]))
265                else:
266                    print("\tThe boolean %s was set incorrectly. " % (data[0][0]))
267                    print("\tDescription:\n\t%s\n" % seobject.boolean_desc(data[0][0]))
268                    print("\tAllow access by executing:\n\t# setsebool -P %s %d" % (data[0][0], data[0][1]))
269                continue
270
271            if rc == audit2why.TERULE:
272                print("\t\tMissing type enforcement (TE) allow rule.\n")
273                print("\t\tYou can use audit2allow to generate a loadable module to allow this access.\n")
274                continue
275
276            if rc == audit2why.CONSTRAINT:
277                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"
278                print("#Constraint rule:")
279                print("\n#\t" + data[0])
280                for reason in data[1:]:
281                    print("#\tPossible cause is the source %s and target %s are different.\n" % reason)
282
283            if rc == audit2why.RBAC:
284                print("\t\tMissing role allow rule.\n")
285                print("\t\tAdd an allow rule for the role pair.\n")
286                continue
287
288        audit2why.finish()
289        return
290
291    def __output(self):
292
293        if self.__options.audit2why:
294            try:
295                return self.__output_audit2why()
296            except RuntimeError as e:
297                print(e)
298                sys.exit(1)
299
300        g = policygen.PolicyGenerator()
301
302        g.set_gen_dontaudit(self.__options.dontaudit)
303
304        if self.__options.module:
305            g.set_module_name(self.__options.module)
306
307        # Interface generation
308        if self.__options.refpolicy:
309            ifs, perm_maps = self.__load_interface_info()
310            g.set_gen_refpol(ifs, perm_maps)
311
312        # Explanation
313        if self.__options.verbose:
314            g.set_gen_explain(policygen.SHORT_EXPLANATION)
315        if self.__options.explain_long:
316            g.set_gen_explain(policygen.LONG_EXPLANATION)
317
318        # Requires
319        if self.__options.requires:
320            g.set_gen_requires(True)
321
322        # Generate the policy
323        g.add_access(self.__avs)
324        g.add_role_types(self.__role_types)
325
326        # Output
327        writer = output.ModuleWriter()
328
329        # Module package
330        if self.__options.module_package:
331            self.__output_modulepackage(writer, g)
332        else:
333            # File or stdout
334            if self.__options.module:
335                g.set_module_name(self.__options.module)
336
337            if self.__options.output:
338                fd = open(self.__options.output, "a")
339            else:
340                fd = sys.stdout
341            writer.write(g.get_module(), fd)
342
343    def main(self):
344        try:
345            self.__parse_options()
346            if self.__options.policy:
347                audit2why.init(self.__options.policy)
348            else:
349                audit2why.init()
350
351            self.__read_input()
352            self.__process_input()
353            self.__output()
354        except KeyboardInterrupt:
355            sys.exit(0)
356        except ValueError as e:
357            print(e)
358            sys.exit(1)
359        except IOError as e:
360            print(e)
361            sys.exit(1)
362
363if __name__ == "__main__":
364    app = AuditToPolicy()
365    app.main()
366