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