1#!/usr/bin/python3 -Es 2# Copyright (C) 2005 Red Hat 3# see file 'COPYING' for use and warranty information 4# 5# chcat is a script that allows you modify the Security label on a file 6# 7# Author: Daniel Walsh <dwalsh@redhat.com> 8# 9# This program is free software; you can redistribute it and/or 10# modify it under the terms of the GNU General Public License as 11# published by the Free Software Foundation; either version 2 of 12# the License, or (at your option) any later version. 13# 14# This program is distributed in the hope that it will be useful, 15# but WITHOUT ANY WARRANTY; without even the implied warranty of 16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17# GNU General Public License for more details. 18# 19# You should have received a copy of the GNU General Public License 20# along with this program; if not, write to the Free Software 21# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 22# 02111-1307 USA 23# 24# 25import subprocess 26import sys 27import os 28import pwd 29import getopt 30import selinux 31import seobject 32 33PROGNAME = "policycoreutils" 34try: 35 import gettext 36 kwargs = {} 37 if sys.version_info < (3,): 38 kwargs['unicode'] = True 39 gettext.install(PROGNAME, 40 localedir="/usr/share/locale", 41 codeset='utf-8', 42 **kwargs) 43except ImportError: 44 try: 45 import builtins 46 builtins.__dict__['_'] = str 47 except ImportError: 48 import __builtin__ 49 __builtin__.__dict__['_'] = unicode 50 51 52def errorExit(error): 53 sys.stderr.write("%s: " % sys.argv[0]) 54 sys.stderr.write("%s\n" % error) 55 sys.stderr.flush() 56 sys.exit(1) 57 58 59def verify_users(users): 60 for u in users: 61 try: 62 pwd.getpwnam(u) 63 except KeyError: 64 error("User %s does not exist" % u) 65 66 67def chcat_user_add(newcat, users): 68 errors = 0 69 logins = seobject.loginRecords() 70 seusers = logins.get_all() 71 add_ind = 0 72 verify_users(users) 73 for u in users: 74 if u in seusers.keys(): 75 user = seusers[u] 76 else: 77 add_ind = 1 78 user = seusers["__default__"] 79 serange = user[1].split("-") 80 cats = [] 81 top = ["s0"] 82 if len(serange) > 1: 83 top = serange[1].split(":") 84 if len(top) > 1: 85 cats = expandCats(top[1].split(',')) 86 87 for i in newcat[1:]: 88 if i not in cats: 89 cats.append(i) 90 91 if len(cats) > 0: 92 new_serange = "%s-%s:%s" % (serange[0], top[0], ",".join(cats)) 93 else: 94 new_serange = "%s-%s" % (serange[0], top[0]) 95 96 if add_ind: 97 cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u] 98 else: 99 cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u] 100 try: 101 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 102 except subprocess.CalledProcessError: 103 errors += 1 104 105 return errors 106 107 108def chcat_add(orig, newcat, objects, login_ind): 109 if len(newcat) == 1: 110 raise ValueError(_("Requires at least one category")) 111 112 if login_ind == 1: 113 return chcat_user_add(newcat, objects) 114 115 errors = 0 116 sensitivity = newcat[0] 117 cat = newcat[1] 118 for f in objects: 119 (rc, c) = selinux.getfilecon(f) 120 con = c.split(":")[3:] 121 clist = translate(con) 122 if sensitivity != clist[0]: 123 print(_("Can not modify sensitivity levels using '+' on %s") % f) 124 125 if len(clist) > 1: 126 if cat in clist[1:]: 127 print(_("%s is already in %s") % (f, orig)) 128 continue 129 clist.append(cat) 130 cats = clist[1:] 131 cats.sort() 132 cat_string = cats[0] 133 for c in cats[1:]: 134 cat_string = "%s,%s" % (cat_string, c) 135 else: 136 cat_string = cat 137 138 cmd = ["chcon", "-l", "%s:%s" % (sensitivity, cat_string), f] 139 try: 140 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 141 except subprocess.CalledProcessError: 142 errors += 1 143 return errors 144 145 146def chcat_user_remove(newcat, users): 147 errors = 0 148 logins = seobject.loginRecords() 149 seusers = logins.get_all() 150 add_ind = 0 151 verify_users(users) 152 for u in users: 153 if u in seusers.keys(): 154 user = seusers[u] 155 else: 156 add_ind = 1 157 user = seusers["__default__"] 158 serange = user[1].split("-") 159 cats = [] 160 top = ["s0"] 161 if len(serange) > 1: 162 top = serange[1].split(":") 163 if len(top) > 1: 164 cats = expandCats(top[1].split(',')) 165 166 for i in newcat[1:]: 167 if i in cats: 168 cats.remove(i) 169 170 if len(cats) > 0: 171 new_serange = "%s-%s:%s" % (serange[0], top[0], ",".join(cats)) 172 else: 173 new_serange = "%s-%s" % (serange[0], top[0]) 174 175 if add_ind: 176 cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u] 177 else: 178 cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u] 179 180 try: 181 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 182 except subprocess.CalledProcessError: 183 errors += 1 184 185 return errors 186 187 188def chcat_remove(orig, newcat, objects, login_ind): 189 if len(newcat) == 1: 190 raise ValueError(_("Requires at least one category")) 191 192 if login_ind == 1: 193 return chcat_user_remove(newcat, objects) 194 195 errors = 0 196 sensitivity = newcat[0] 197 cat = newcat[1] 198 199 for f in objects: 200 (rc, c) = selinux.getfilecon(f) 201 con = c.split(":")[3:] 202 clist = translate(con) 203 if sensitivity != clist[0]: 204 print(_("Can not modify sensitivity levels using '+' on %s") % f) 205 continue 206 207 if len(clist) > 1: 208 if cat not in clist[1:]: 209 print(_("%s is not in %s") % (f, orig)) 210 continue 211 clist.remove(cat) 212 if len(clist) > 1: 213 cat = clist[1] 214 for c in clist[2:]: 215 cat = "%s,%s" % (cat, c) 216 else: 217 cat = "" 218 else: 219 print(_("%s is not in %s") % (f, orig)) 220 continue 221 222 if len(cat) == 0: 223 new_serange = sensitivity 224 else: 225 new_serange = '%s:%s' % (sensitivity, cat) 226 227 cmd = ["chcon", "-l", new_serange, f] 228 try: 229 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 230 except subprocess.CalledProcessError: 231 errors += 1 232 return errors 233 234 235def chcat_user_replace(newcat, users): 236 errors = 0 237 logins = seobject.loginRecords() 238 seusers = logins.get_all() 239 add_ind = 0 240 verify_users(users) 241 for u in users: 242 if u in seusers.keys(): 243 user = seusers[u] 244 else: 245 add_ind = 1 246 user = seusers["__default__"] 247 serange = user[1].split("-") 248 new_serange = "%s-%s:%s" % (serange[0], newcat[0], ",".join(newcat[1:])) 249 if new_serange[-1:] == ":": 250 new_serange = new_serange[:-1] 251 252 if add_ind: 253 cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u] 254 else: 255 cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u] 256 try: 257 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 258 except subprocess.CalledProcessError: 259 errors += 1 260 return errors 261 262 263def chcat_replace(newcat, objects, login_ind): 264 if login_ind == 1: 265 return chcat_user_replace(newcat, objects) 266 errors = 0 267 # newcat[0] is the sensitivity level, newcat[1:] are the categories 268 if len(newcat) == 1: 269 new_serange = newcat[0] 270 else: 271 new_serange = "%s:%s" % (newcat[0], newcat[1]) 272 for cat in newcat[2:]: 273 new_serange = '%s,%s' % (new_serange, cat) 274 275 cmd = ["chcon", "-l", new_serange] + objects 276 try: 277 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 278 except subprocess.CalledProcessError: 279 errors += 1 280 281 return errors 282 283 284def check_replace(cats): 285 plus_ind = 0 286 replace_ind = 0 287 for c in cats: 288 if len(c) > 0 and (c[0] == "+" or c[0] == "-"): 289 if replace_ind: 290 raise ValueError(_("Can not combine +/- with other types of categories")) 291 plus_ind = 1 292 else: 293 replace_ind = 1 294 if plus_ind: 295 raise ValueError(_("Can not combine +/- with other types of categories")) 296 return replace_ind 297 298 299def isSensitivity(sensitivity): 300 if sensitivity[0] == "s" and sensitivity[1:].isdigit() and int(sensitivity[1:]) in range(0, 16): 301 return 1 302 else: 303 return 0 304 305 306def expandCats(cats): 307 newcats = [] 308 for c in cats: 309 for i in c.split(","): 310 if i.find(".") != -1: 311 j = i.split(".") 312 for k in range(int(j[0][1:]), int(j[1][1:]) + 1): 313 x = ("c%d" % k) 314 if x not in newcats: 315 newcats.append(x) 316 else: 317 if i not in newcats: 318 newcats.append(i) 319 if len(newcats) > 25: 320 return cats 321 return newcats 322 323 324def translate(cats): 325 newcat = [] 326 if len(cats) == 0: 327 newcat.append("s0") 328 return newcat 329 for c in cats: 330 (rc, raw) = selinux.selinux_trans_to_raw_context("a:b:c:%s" % c) 331 rlist = raw.split(":")[3:] 332 tlist = [] 333 if isSensitivity(rlist[0]) == 0: 334 tlist.append("s0") 335 for i in expandCats(rlist): 336 tlist.append(i) 337 else: 338 tlist.append(rlist[0]) 339 for i in expandCats(rlist[1:]): 340 tlist.append(i) 341 if len(newcat) == 0: 342 newcat.append(tlist[0]) 343 else: 344 if newcat[0] != tlist[0]: 345 raise ValueError(_("Can not have multiple sensitivities")) 346 for i in tlist[1:]: 347 newcat.append(i) 348 return newcat 349 350 351def usage(): 352 print(_("Usage %s CATEGORY File ...") % sys.argv[0]) 353 print(_("Usage %s -l CATEGORY user ...") % sys.argv[0]) 354 print(_("Usage %s [[+|-]CATEGORY],...] File ...") % sys.argv[0]) 355 print(_("Usage %s -l [[+|-]CATEGORY],...] user ...") % sys.argv[0]) 356 print(_("Usage %s -d File ...") % sys.argv[0]) 357 print(_("Usage %s -l -d user ...") % sys.argv[0]) 358 print(_("Usage %s -L") % sys.argv[0]) 359 print(_("Usage %s -L -l user") % sys.argv[0]) 360 print(_("Use -- to end option list. For example")) 361 print(_("chcat -- -CompanyConfidential /docs/businessplan.odt")) 362 print(_("chcat -l +CompanyConfidential juser")) 363 sys.exit(1) 364 365 366def listcats(): 367 fd = open(selinux.selinux_translations_path()) 368 for l in fd.read().split("\n"): 369 if l.startswith("#"): 370 continue 371 if l.find("=") != -1: 372 rec = l.split("=") 373 print("%-30s %s" % tuple(rec)) 374 fd.close() 375 return 0 376 377 378def listusercats(users): 379 if len(users) == 0: 380 try: 381 users.append(os.getlogin()) 382 except OSError: 383 users.append(pwd.getpwuid(os.getuid()).pw_name) 384 385 verify_users(users) 386 for u in users: 387 cats = seobject.translate(selinux.getseuserbyname(u)[2]) 388 cats = cats.split("-") 389 if len(cats) > 1 and cats[1] != "s0": 390 print("%s: %s" % (u, cats[1])) 391 else: 392 print("%s: %s" % (u, cats[0])) 393 394 395def error(msg): 396 print("%s: %s" % (sys.argv[0], msg)) 397 sys.exit(1) 398 399 400if __name__ == '__main__': 401 if selinux.is_selinux_mls_enabled() != 1: 402 error("Requires a mls enabled system") 403 404 if selinux.is_selinux_enabled() != 1: 405 error("Requires an SELinux enabled system") 406 407 delete_ind = 0 408 list_ind = 0 409 login_ind = 0 410 try: 411 gopts, cmds = getopt.getopt(sys.argv[1:], 412 'dhlL', 413 ['list', 414 'login', 415 'help', 416 'delete']) 417 418 for o, a in gopts: 419 if o == "-h" or o == "--help": 420 usage() 421 if o == "-d" or o == "--delete": 422 delete_ind = 1 423 if o == "-L" or o == "--list": 424 list_ind = 1 425 if o == "-l" or o == "--login": 426 login_ind = 1 427 428 if list_ind == 0 and len(cmds) < 1: 429 usage() 430 431 except getopt.error as error: 432 errorExit(_("Options Error %s ") % error.msg) 433 434 except ValueError: 435 usage() 436 437 if delete_ind: 438 sys.exit(chcat_replace(["s0"], cmds, login_ind)) 439 440 if list_ind: 441 if login_ind: 442 sys.exit(listusercats(cmds)) 443 else: 444 if len(cmds) > 0: 445 usage() 446 sys.exit(listcats()) 447 448 if len(cmds) < 2: 449 usage() 450 451 set_ind = 0 452 cats = cmds[0].split(",") 453 mod_ind = 0 454 errors = 0 455 objects = cmds[1:] 456 try: 457 if check_replace(cats): 458 errors = chcat_replace(translate(cats), objects, login_ind) 459 else: 460 for c in cats: 461 l = [] 462 l.append(c[1:]) 463 if len(c) > 0 and c[0] == "+": 464 errors += chcat_add(c[1:], translate(l), objects, login_ind) 465 continue 466 if len(c) > 0 and c[0] == "-": 467 errors += chcat_remove(c[1:], translate(l), objects, login_ind) 468 continue 469 except ValueError as e: 470 error(e) 471 except OSError as e: 472 error(e) 473 474 sys.exit(errors) 475