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