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 cmd = 'chcon -l %s' % sensitivity 119 for f in objects: 120 (rc, c) = selinux.getfilecon(f) 121 con = c.split(":")[3:] 122 clist = translate(con) 123 if sensitivity != clist[0]: 124 print(_("Can not modify sensitivity levels using '+' on %s") % f) 125 126 if len(clist) > 1: 127 if cat in clist[1:]: 128 print(_("%s is already in %s") % (f, orig)) 129 continue 130 clist.append(cat) 131 cats = clist[1:] 132 cats.sort() 133 cat_string = cats[0] 134 for c in cats[1:]: 135 cat_string = "%s,%s" % (cat_string, c) 136 else: 137 cat_string = cat 138 139 cmd = ["chcon", "-l", "%s:%s" % (sensitivity, cat_string), f] 140 try: 141 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 142 except subprocess.CalledProcessError: 143 errors += 1 144 return errors 145 146 147def chcat_user_remove(newcat, users): 148 errors = 0 149 logins = seobject.loginRecords() 150 seusers = logins.get_all() 151 add_ind = 0 152 verify_users(users) 153 for u in users: 154 if u in seusers.keys(): 155 user = seusers[u] 156 else: 157 add_ind = 1 158 user = seusers["__default__"] 159 serange = user[1].split("-") 160 cats = [] 161 top = ["s0"] 162 if len(serange) > 1: 163 top = serange[1].split(":") 164 if len(top) > 1: 165 cats = expandCats(top[1].split(',')) 166 167 for i in newcat[1:]: 168 if i in cats: 169 cats.remove(i) 170 171 if len(cats) > 0: 172 new_serange = "%s-%s:%s" % (serange[0], top[0], ",".join(cats)) 173 else: 174 new_serange = "%s-%s" % (serange[0], top[0]) 175 176 if add_ind: 177 cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u] 178 else: 179 cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u] 180 181 try: 182 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 183 except subprocess.CalledProcessError: 184 errors += 1 185 186 return errors 187 188 189def chcat_remove(orig, newcat, objects, login_ind): 190 if len(newcat) == 1: 191 raise ValueError(_("Requires at least one category")) 192 193 if login_ind == 1: 194 return chcat_user_remove(newcat, objects) 195 196 errors = 0 197 sensitivity = newcat[0] 198 cat = newcat[1] 199 200 for f in objects: 201 (rc, c) = selinux.getfilecon(f) 202 con = c.split(":")[3:] 203 clist = translate(con) 204 if sensitivity != clist[0]: 205 print(_("Can not modify sensitivity levels using '+' on %s") % f) 206 continue 207 208 if len(clist) > 1: 209 if cat not in clist[1:]: 210 print(_("%s is not in %s") % (f, orig)) 211 continue 212 clist.remove(cat) 213 if len(clist) > 1: 214 cat = clist[1] 215 for c in clist[2:]: 216 cat = "%s,%s" % (cat, c) 217 else: 218 cat = "" 219 else: 220 print(_("%s is not in %s") % (f, orig)) 221 continue 222 223 if len(cat) == 0: 224 new_serange = sensitivity 225 else: 226 new_serange = '%s:%s' % (sensitivity, cat) 227 228 cmd = ["chcon", "-l", new_serange, f] 229 try: 230 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 231 except subprocess.CalledProcessError: 232 errors += 1 233 return errors 234 235 236def chcat_user_replace(newcat, users): 237 errors = 0 238 logins = seobject.loginRecords() 239 seusers = logins.get_all() 240 add_ind = 0 241 verify_users(users) 242 for u in users: 243 if u in seusers.keys(): 244 user = seusers[u] 245 else: 246 add_ind = 1 247 user = seusers["__default__"] 248 serange = user[1].split("-") 249 new_serange = "%s-%s:%s" % (serange[0], newcat[0], ",".join(newcat[1:])) 250 if new_serange[-1:] == ":": 251 new_serange = new_serange[:-1] 252 253 if add_ind: 254 cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u] 255 else: 256 cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u] 257 try: 258 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 259 except subprocess.CalledProcessError: 260 errors += 1 261 return errors 262 263 264def chcat_replace(newcat, objects, login_ind): 265 if login_ind == 1: 266 return chcat_user_replace(newcat, objects) 267 errors = 0 268 # newcat[0] is the sensitivity level, newcat[1:] are the categories 269 if len(newcat) == 1: 270 new_serange = newcat[0] 271 else: 272 new_serange = "%s:%s" % (newcat[0], newcat[1]) 273 for cat in newcat[2:]: 274 new_serange = '%s,%s' % (new_serange, cat) 275 276 cmd = ["chcon", "-l", new_serange] + objects 277 try: 278 subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False) 279 except subprocess.CalledProcessError: 280 errors += 1 281 282 return errors 283 284 285def check_replace(cats): 286 plus_ind = 0 287 replace_ind = 0 288 for c in cats: 289 if len(c) > 0 and (c[0] == "+" or c[0] == "-"): 290 if replace_ind: 291 raise ValueError(_("Can not combine +/- with other types of categories")) 292 plus_ind = 1 293 else: 294 replace_ind = 1 295 if plus_ind: 296 raise ValueError(_("Can not combine +/- with other types of categories")) 297 return replace_ind 298 299 300def isSensitivity(sensitivity): 301 if sensitivity[0] == "s" and sensitivity[1:].isdigit() and int(sensitivity[1:]) in range(0, 16): 302 return 1 303 else: 304 return 0 305 306 307def expandCats(cats): 308 newcats = [] 309 for c in cats: 310 for i in c.split(","): 311 if i.find(".") != -1: 312 j = i.split(".") 313 for k in range(int(j[0][1:]), int(j[1][1:]) + 1): 314 x = ("c%d" % k) 315 if x not in newcats: 316 newcats.append(x) 317 else: 318 if i not in newcats: 319 newcats.append(i) 320 if len(newcats) > 25: 321 return cats 322 return newcats 323 324 325def translate(cats): 326 newcat = [] 327 if len(cats) == 0: 328 newcat.append("s0") 329 return newcat 330 for c in cats: 331 (rc, raw) = selinux.selinux_trans_to_raw_context("a:b:c:%s" % c) 332 rlist = raw.split(":")[3:] 333 tlist = [] 334 if isSensitivity(rlist[0]) == 0: 335 tlist.append("s0") 336 for i in expandCats(rlist): 337 tlist.append(i) 338 else: 339 tlist.append(rlist[0]) 340 for i in expandCats(rlist[1:]): 341 tlist.append(i) 342 if len(newcat) == 0: 343 newcat.append(tlist[0]) 344 else: 345 if newcat[0] != tlist[0]: 346 raise ValueError(_("Can not have multiple sensitivities")) 347 for i in tlist[1:]: 348 newcat.append(i) 349 return newcat 350 351 352def usage(): 353 print(_("Usage %s CATEGORY File ...") % sys.argv[0]) 354 print(_("Usage %s -l CATEGORY user ...") % sys.argv[0]) 355 print(_("Usage %s [[+|-]CATEGORY],...] File ...") % sys.argv[0]) 356 print(_("Usage %s -l [[+|-]CATEGORY],...] user ...") % sys.argv[0]) 357 print(_("Usage %s -d File ...") % sys.argv[0]) 358 print(_("Usage %s -l -d user ...") % sys.argv[0]) 359 print(_("Usage %s -L") % sys.argv[0]) 360 print(_("Usage %s -L -l user") % sys.argv[0]) 361 print(_("Use -- to end option list. For example")) 362 print(_("chcat -- -CompanyConfidential /docs/businessplan.odt")) 363 print(_("chcat -l +CompanyConfidential juser")) 364 sys.exit(1) 365 366 367def listcats(): 368 fd = open(selinux.selinux_translations_path()) 369 for l in fd.read().split("\n"): 370 if l.startswith("#"): 371 continue 372 if l.find("=") != -1: 373 rec = l.split("=") 374 print("%-30s %s" % tuple(rec)) 375 fd.close() 376 return 0 377 378 379def listusercats(users): 380 if len(users) == 0: 381 try: 382 users.append(os.getlogin()) 383 except OSError: 384 users.append(pwd.getpwuid(os.getuid()).pw_name) 385 386 verify_users(users) 387 for u in users: 388 cats = seobject.translate(selinux.getseuserbyname(u)[2]) 389 cats = cats.split("-") 390 if len(cats) > 1 and cats[1] != "s0": 391 print("%s: %s" % (u, cats[1])) 392 else: 393 print("%s: %s" % (u, cats[0])) 394 395 396def error(msg): 397 print("%s: %s" % (sys.argv[0], msg)) 398 sys.exit(1) 399 400 401if __name__ == '__main__': 402 if selinux.is_selinux_mls_enabled() != 1: 403 error("Requires a mls enabled system") 404 405 if selinux.is_selinux_enabled() != 1: 406 error("Requires an SELinux enabled system") 407 408 delete_ind = 0 409 list_ind = 0 410 login_ind = 0 411 try: 412 gopts, cmds = getopt.getopt(sys.argv[1:], 413 'dhlL', 414 ['list', 415 'login', 416 'help', 417 'delete']) 418 419 for o, a in gopts: 420 if o == "-h" or o == "--help": 421 usage() 422 if o == "-d" or o == "--delete": 423 delete_ind = 1 424 if o == "-L" or o == "--list": 425 list_ind = 1 426 if o == "-l" or o == "--login": 427 login_ind = 1 428 429 if list_ind == 0 and len(cmds) < 1: 430 usage() 431 432 except getopt.error as error: 433 errorExit(_("Options Error %s ") % error.msg) 434 435 except ValueError: 436 usage() 437 438 if delete_ind: 439 sys.exit(chcat_replace(["s0"], cmds, login_ind)) 440 441 if list_ind: 442 if login_ind: 443 sys.exit(listusercats(cmds)) 444 else: 445 if len(cmds) > 0: 446 usage() 447 sys.exit(listcats()) 448 449 if len(cmds) < 2: 450 usage() 451 452 set_ind = 0 453 cats = cmds[0].split(",") 454 mod_ind = 0 455 errors = 0 456 objects = cmds[1:] 457 try: 458 if check_replace(cats): 459 errors = chcat_replace(translate(cats), objects, login_ind) 460 else: 461 for c in cats: 462 l = [] 463 l.append(c[1:]) 464 if len(c) > 0 and c[0] == "+": 465 errors += chcat_add(c[1:], translate(l), objects, login_ind) 466 continue 467 if len(c) > 0 and c[0] == "-": 468 errors += chcat_remove(c[1:], translate(l), objects, login_ind) 469 continue 470 except ValueError as e: 471 error(e) 472 except OSError as e: 473 error(e) 474 475 sys.exit(errors) 476