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