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