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 sepolicy
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" % sepolicy.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" % sepolicy.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            if rc == audit2why.BOUNDS:
289                print("\t\tTypebounds violation.\n")
290                print("\t\tAdd an allow rule for the parent type.\n")
291                continue
292
293        audit2why.finish()
294        return
295
296    def __output(self):
297
298        if self.__options.audit2why:
299            try:
300                return self.__output_audit2why()
301            except RuntimeError as e:
302                print(e)
303                sys.exit(1)
304
305        g = policygen.PolicyGenerator()
306
307        g.set_gen_dontaudit(self.__options.dontaudit)
308
309        if self.__options.module:
310            g.set_module_name(self.__options.module)
311
312        # Interface generation
313        if self.__options.refpolicy:
314            ifs, perm_maps = self.__load_interface_info()
315            g.set_gen_refpol(ifs, perm_maps)
316
317        # Explanation
318        if self.__options.verbose:
319            g.set_gen_explain(policygen.SHORT_EXPLANATION)
320        if self.__options.explain_long:
321            g.set_gen_explain(policygen.LONG_EXPLANATION)
322
323        # Requires
324        if self.__options.requires:
325            g.set_gen_requires(True)
326
327        # Generate the policy
328        g.add_access(self.__avs)
329        g.add_role_types(self.__role_types)
330
331        # Output
332        writer = output.ModuleWriter()
333
334        # Module package
335        if self.__options.module_package:
336            self.__output_modulepackage(writer, g)
337        else:
338            # File or stdout
339            if self.__options.module:
340                g.set_module_name(self.__options.module)
341
342            if self.__options.output:
343                fd = open(self.__options.output, "a")
344            else:
345                fd = sys.stdout
346            writer.write(g.get_module(), fd)
347
348    def main(self):
349        try:
350            self.__parse_options()
351            if self.__options.policy:
352                audit2why.init(self.__options.policy)
353            else:
354                audit2why.init()
355
356            self.__read_input()
357            self.__process_input()
358            self.__output()
359        except KeyboardInterrupt:
360            sys.exit(0)
361        except ValueError as e:
362            print(e)
363            sys.exit(1)
364        except IOError as e:
365            print(e)
366            sys.exit(1)
367
368if __name__ == "__main__":
369    app = AuditToPolicy()
370    app.main()
371