1# Authors: Karl MacMillan <kmacmillan@mentalrootkit.com>
2#
3# Copyright (C) 2006 Red Hat
4# see file 'COPYING' for use and warranty information
5#
6# This program is free software; you can redistribute it and/or
7# modify it under the terms of the GNU General Public License as
8# published by the Free Software Foundation; version 2 only
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18#
19
20"""
21Utilities for dealing with the compilation of modules and creation
22of module tress.
23"""
24
25import defaults
26
27import selinux
28
29import re
30import tempfile
31import commands
32import os
33import os.path
34import subprocess
35import shutil
36
37def is_valid_name(modname):
38    """Check that a module name is valid.
39    """
40    m = re.findall("[^a-zA-Z0-9_\-\.]", modname)
41    if len(m) == 0 and modname[0].isalpha():
42        return True
43    else:
44        return False
45
46class ModuleTree:
47    def __init__(self, modname):
48        self.modname = modname
49        self.dirname = None
50
51    def dir_name(self):
52        return self.dirname
53
54    def te_name(self):
55        return self.dirname + "/" + self.modname + ".te"
56
57    def fc_name(self):
58        return self.dirname + "/" + self.modname + ".fc"
59
60    def if_name(self):
61        return self.dirname + "/" + self.modname + ".if"
62
63    def package_name(self):
64        return self.dirname + "/" + self.modname + ".pp"
65
66    def makefile_name(self):
67        return self.dirname + "/Makefile"
68
69    def create(self, parent_dirname, makefile_include=None):
70        self.dirname = parent_dirname + "/" + self.modname
71        os.mkdir(self.dirname)
72        fd = open(self.makefile_name(), "w")
73        if makefile_include:
74            fd.write("include " + makefile_include)
75        else:
76            fd.write("include " + defaults.refpolicy_makefile())
77        fd.close()
78
79        # Create empty files for the standard refpolicy
80        # module files
81        open(self.te_name(), "w").close()
82        open(self.fc_name(), "w").close()
83        open(self.if_name(), "w").close()
84
85def modname_from_sourcename(sourcename):
86    return os.path.splitext(os.path.split(sourcename)[1])[0]
87
88class ModuleCompiler:
89    """ModuleCompiler eases running of the module compiler.
90
91    The ModuleCompiler class encapsulates running the commandline
92    module compiler (checkmodule) and module packager (semodule_package).
93    You are likely interested in the create_module_package method.
94
95    Several options are controlled via paramaters (only effects the
96    non-refpol builds):
97
98     .mls          [boolean] Generate an MLS module (by passed -M to
99                   checkmodule). True to generate an MLS module, false
100                   otherwise.
101
102     .module       [boolean] Generate a module instead of a base module.
103                   True to generate a module, false to generate a base.
104
105     .checkmodule  [string] Fully qualified path to the module compiler.
106                   Default is /usr/bin/checkmodule.
107
108     .semodule_package [string] Fully qualified path to the module
109                   packager. Defaults to /usr/bin/semodule_package.
110     .output       [file object] File object used to write verbose
111                   output of the compililation and packaging process.
112    """
113    def __init__(self, output=None):
114        """Create a ModuleCompiler instance, optionally with an
115        output file object for verbose output of the compilation process.
116        """
117        self.mls = selinux.is_selinux_mls_enabled()
118        self.module = True
119        self.checkmodule = "/usr/bin/checkmodule"
120        self.semodule_package = "/usr/bin/semodule_package"
121        self.output = output
122        self.last_output = ""
123        self.refpol_makefile = defaults.refpolicy_makefile()
124        self.make = "/usr/bin/make"
125
126    def o(self, str):
127        if self.output:
128            self.output.write(str + "\n")
129        self.last_output = str
130
131    def run(self, command):
132        self.o(command)
133        rc, output = commands.getstatusoutput(command)
134        self.o(output)
135
136        return rc
137
138    def gen_filenames(self, sourcename):
139        """Generate the module and policy package filenames from
140        a source file name. The source file must be in the form
141        of "foo.te". This will generate "foo.mod" and "foo.pp".
142
143        Returns a tuple with (modname, policypackage).
144        """
145        splitname = sourcename.split(".")
146        if len(splitname) < 2:
147            raise RuntimeError("invalid sourcefile name %s (must end in .te)", sourcename)
148        # Handle other periods in the filename correctly
149        basename = ".".join(splitname[0:-1])
150        modname = basename + ".mod"
151        packagename = basename + ".pp"
152
153        return (modname, packagename)
154
155    def create_module_package(self, sourcename, refpolicy=True):
156        """Create a module package saved in a packagename from a
157        sourcename.
158
159        The create_module_package creates a module package saved in a
160        file named sourcename (.pp is the standard extension) from a
161        source file (.te is the standard extension). The source file
162        should contain SELinux policy statements appropriate for a
163        base or non-base module (depending on the setting of .module).
164
165        Only file names are accepted, not open file objects or
166        descriptors because the command line SELinux tools are used.
167
168        On error a RuntimeError will be raised with a descriptive
169        error message.
170        """
171        if refpolicy:
172            self.refpol_build(sourcename)
173        else:
174            modname, packagename = self.gen_filenames(sourcename)
175            self.compile(sourcename, modname)
176            self.package(modname, packagename)
177            os.unlink(modname)
178
179    def refpol_build(self, sourcename):
180        # Compile
181        command = self.make + " -f " + self.refpol_makefile
182        rc = self.run(command)
183
184        # Raise an error if the process failed
185        if rc != 0:
186            raise RuntimeError("compilation failed:\n%s" % self.last_output)
187
188    def compile(self, sourcename, modname):
189        s = [self.checkmodule]
190        if self.mls:
191            s.append("-M")
192        if self.module:
193            s.append("-m")
194        s.append("-o")
195        s.append(modname)
196        s.append(sourcename)
197
198        rc = self.run(" ".join(s))
199        if rc != 0:
200            raise RuntimeError("compilation failed:\n%s" % self.last_output)
201
202    def package(self, modname, packagename):
203        s = [self.semodule_package]
204        s.append("-o")
205        s.append(packagename)
206        s.append("-m")
207        s.append(modname)
208
209        rc = self.run(" ".join(s))
210        if rc != 0:
211            raise RuntimeError("packaging failed [%s]" % self.last_output)
212
213
214