476 lines
14 KiB
Python
Executable File
476 lines
14 KiB
Python
Executable File
#!/usr/bin/python3 -EsI
|
|
# Copyright (C) 2005 Red Hat
|
|
# see file 'COPYING' for use and warranty information
|
|
#
|
|
# chcat is a script that allows you modify the Security label on a file
|
|
#
|
|
# Author: Daniel Walsh <dwalsh@redhat.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation; either version 2 of
|
|
# the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
# 02111-1307 USA
|
|
#
|
|
#
|
|
import subprocess
|
|
import sys
|
|
import os
|
|
import pwd
|
|
import getopt
|
|
import selinux
|
|
import seobject
|
|
|
|
PROGNAME = "selinux-python"
|
|
try:
|
|
import gettext
|
|
kwargs = {}
|
|
if sys.version_info < (3,):
|
|
kwargs['unicode'] = True
|
|
t = gettext.translation(PROGNAME,
|
|
localedir="/usr/share/locale",
|
|
**kwargs,
|
|
fallback=True)
|
|
_ = t.gettext
|
|
except:
|
|
try:
|
|
import builtins
|
|
builtins.__dict__['_'] = str
|
|
except ImportError:
|
|
import __builtin__
|
|
__builtin__.__dict__['_'] = unicode
|
|
|
|
|
|
def errorExit(error):
|
|
sys.stderr.write("%s: " % sys.argv[0])
|
|
sys.stderr.write("%s\n" % error)
|
|
sys.stderr.flush()
|
|
sys.exit(1)
|
|
|
|
|
|
def verify_users(users):
|
|
for u in users:
|
|
try:
|
|
pwd.getpwnam(u)
|
|
except KeyError:
|
|
error("User %s does not exist" % u)
|
|
|
|
|
|
def chcat_user_add(newcat, users):
|
|
errors = 0
|
|
logins = seobject.loginRecords()
|
|
seusers = logins.get_all()
|
|
add_ind = 0
|
|
verify_users(users)
|
|
for u in users:
|
|
if u in seusers.keys():
|
|
user = seusers[u]
|
|
else:
|
|
add_ind = 1
|
|
user = seusers["__default__"]
|
|
serange = user[1].split("-")
|
|
cats = []
|
|
top = ["s0"]
|
|
if len(serange) > 1:
|
|
top = serange[1].split(":")
|
|
if len(top) > 1:
|
|
cats = expandCats(top[1].split(','))
|
|
|
|
for i in newcat[1:]:
|
|
if i not in cats:
|
|
cats.append(i)
|
|
|
|
if len(cats) > 0:
|
|
new_serange = "%s-%s:%s" % (serange[0], top[0], ",".join(cats))
|
|
else:
|
|
new_serange = "%s-%s" % (serange[0], top[0])
|
|
|
|
if add_ind:
|
|
cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u]
|
|
else:
|
|
cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u]
|
|
try:
|
|
subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False)
|
|
except subprocess.CalledProcessError:
|
|
errors += 1
|
|
|
|
return errors
|
|
|
|
|
|
def chcat_add(orig, newcat, objects, login_ind):
|
|
if len(newcat) == 1:
|
|
raise ValueError(_("Requires at least one category"))
|
|
|
|
if login_ind == 1:
|
|
return chcat_user_add(newcat, objects)
|
|
|
|
errors = 0
|
|
sensitivity = newcat[0]
|
|
cat = newcat[1]
|
|
for f in objects:
|
|
(rc, c) = selinux.getfilecon(f)
|
|
con = c.split(":")[3:]
|
|
clist = translate(con)
|
|
if sensitivity != clist[0]:
|
|
print(_("Can not modify sensitivity levels using '+' on %s") % f)
|
|
|
|
if len(clist) > 1:
|
|
if cat in clist[1:]:
|
|
print(_("{target} is already in {category}").format(target=f, category=orig))
|
|
continue
|
|
clist.append(cat)
|
|
cats = clist[1:]
|
|
cats.sort()
|
|
cat_string = cats[0]
|
|
for c in cats[1:]:
|
|
cat_string = "%s,%s" % (cat_string, c)
|
|
else:
|
|
cat_string = cat
|
|
|
|
cmd = ["chcon", "-l", "%s:%s" % (sensitivity, cat_string), f]
|
|
try:
|
|
subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False)
|
|
except subprocess.CalledProcessError:
|
|
errors += 1
|
|
return errors
|
|
|
|
|
|
def chcat_user_remove(newcat, users):
|
|
errors = 0
|
|
logins = seobject.loginRecords()
|
|
seusers = logins.get_all()
|
|
add_ind = 0
|
|
verify_users(users)
|
|
for u in users:
|
|
if u in seusers.keys():
|
|
user = seusers[u]
|
|
else:
|
|
add_ind = 1
|
|
user = seusers["__default__"]
|
|
serange = user[1].split("-")
|
|
cats = []
|
|
top = ["s0"]
|
|
if len(serange) > 1:
|
|
top = serange[1].split(":")
|
|
if len(top) > 1:
|
|
cats = expandCats(top[1].split(','))
|
|
|
|
for i in newcat[1:]:
|
|
if i in cats:
|
|
cats.remove(i)
|
|
|
|
if len(cats) > 0:
|
|
new_serange = "%s-%s:%s" % (serange[0], top[0], ",".join(cats))
|
|
else:
|
|
new_serange = "%s-%s" % (serange[0], top[0])
|
|
|
|
if add_ind:
|
|
cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u]
|
|
else:
|
|
cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u]
|
|
|
|
try:
|
|
subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False)
|
|
except subprocess.CalledProcessError:
|
|
errors += 1
|
|
|
|
return errors
|
|
|
|
|
|
def chcat_remove(orig, newcat, objects, login_ind):
|
|
if len(newcat) == 1:
|
|
raise ValueError(_("Requires at least one category"))
|
|
|
|
if login_ind == 1:
|
|
return chcat_user_remove(newcat, objects)
|
|
|
|
errors = 0
|
|
sensitivity = newcat[0]
|
|
cat = newcat[1]
|
|
|
|
for f in objects:
|
|
(rc, c) = selinux.getfilecon(f)
|
|
con = c.split(":")[3:]
|
|
clist = translate(con)
|
|
if sensitivity != clist[0]:
|
|
print(_("Can not modify sensitivity levels using '+' on %s") % f)
|
|
continue
|
|
|
|
if len(clist) > 1:
|
|
if cat not in clist[1:]:
|
|
print(_("{target} is not in {category}").format(target=f, category=orig))
|
|
continue
|
|
clist.remove(cat)
|
|
if len(clist) > 1:
|
|
cat = clist[1]
|
|
for c in clist[2:]:
|
|
cat = "%s,%s" % (cat, c)
|
|
else:
|
|
cat = ""
|
|
else:
|
|
print(_("{target} is not in {category}").format(target=f, category=orig))
|
|
continue
|
|
|
|
if len(cat) == 0:
|
|
new_serange = sensitivity
|
|
else:
|
|
new_serange = '%s:%s' % (sensitivity, cat)
|
|
|
|
cmd = ["chcon", "-l", new_serange, f]
|
|
try:
|
|
subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False)
|
|
except subprocess.CalledProcessError:
|
|
errors += 1
|
|
return errors
|
|
|
|
|
|
def chcat_user_replace(newcat, users):
|
|
errors = 0
|
|
logins = seobject.loginRecords()
|
|
seusers = logins.get_all()
|
|
add_ind = 0
|
|
verify_users(users)
|
|
for u in users:
|
|
if u in seusers.keys():
|
|
user = seusers[u]
|
|
else:
|
|
add_ind = 1
|
|
user = seusers["__default__"]
|
|
serange = user[1].split("-")
|
|
new_serange = "%s-%s:%s" % (serange[0], newcat[0], ",".join(newcat[1:]))
|
|
if new_serange[-1:] == ":":
|
|
new_serange = new_serange[:-1]
|
|
|
|
if add_ind:
|
|
cmd = ["semanage", "login", "-a", "-r", new_serange, "-s", user[0], u]
|
|
else:
|
|
cmd = ["semanage", "login", "-m", "-r", new_serange, "-s", user[0], u]
|
|
try:
|
|
subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False)
|
|
except subprocess.CalledProcessError:
|
|
errors += 1
|
|
return errors
|
|
|
|
|
|
def chcat_replace(newcat, objects, login_ind):
|
|
if login_ind == 1:
|
|
return chcat_user_replace(newcat, objects)
|
|
errors = 0
|
|
# newcat[0] is the sensitivity level, newcat[1:] are the categories
|
|
if len(newcat) == 1:
|
|
new_serange = newcat[0]
|
|
else:
|
|
new_serange = "%s:%s" % (newcat[0], newcat[1])
|
|
for cat in newcat[2:]:
|
|
new_serange = '%s,%s' % (new_serange, cat)
|
|
|
|
cmd = ["chcon", "-l", new_serange] + objects
|
|
try:
|
|
subprocess.check_call(cmd, stderr=subprocess.STDOUT, shell=False)
|
|
except subprocess.CalledProcessError:
|
|
errors += 1
|
|
|
|
return errors
|
|
|
|
|
|
def check_replace(cats):
|
|
plus_ind = 0
|
|
replace_ind = 0
|
|
for c in cats:
|
|
if len(c) > 0 and (c[0] == "+" or c[0] == "-"):
|
|
if replace_ind:
|
|
raise ValueError(_("Can not combine +/- with other types of categories"))
|
|
plus_ind = 1
|
|
else:
|
|
replace_ind = 1
|
|
if plus_ind:
|
|
raise ValueError(_("Can not combine +/- with other types of categories"))
|
|
return replace_ind
|
|
|
|
|
|
def isSensitivity(sensitivity):
|
|
if sensitivity[0] == "s" and sensitivity[1:].isdigit() and int(sensitivity[1:]) in range(0, 16):
|
|
return 1
|
|
else:
|
|
return 0
|
|
|
|
|
|
def expandCats(cats):
|
|
newcats = []
|
|
for c in cats:
|
|
for i in c.split(","):
|
|
if i.find(".") != -1:
|
|
j = i.split(".")
|
|
for k in range(int(j[0][1:]), int(j[1][1:]) + 1):
|
|
x = ("c%d" % k)
|
|
if x not in newcats:
|
|
newcats.append(x)
|
|
else:
|
|
if i not in newcats:
|
|
newcats.append(i)
|
|
if len(newcats) > 25:
|
|
return cats
|
|
return newcats
|
|
|
|
|
|
def translate(cats):
|
|
newcat = []
|
|
if len(cats) == 0:
|
|
newcat.append("s0")
|
|
return newcat
|
|
for c in cats:
|
|
(rc, raw) = selinux.selinux_trans_to_raw_context("a:b:c:%s" % c)
|
|
rlist = raw.split(":")[3:]
|
|
tlist = []
|
|
if isSensitivity(rlist[0]) == 0:
|
|
tlist.append("s0")
|
|
for i in expandCats(rlist):
|
|
tlist.append(i)
|
|
else:
|
|
tlist.append(rlist[0])
|
|
for i in expandCats(rlist[1:]):
|
|
tlist.append(i)
|
|
if len(newcat) == 0:
|
|
newcat.append(tlist[0])
|
|
else:
|
|
if newcat[0] != tlist[0]:
|
|
raise ValueError(_("Can not have multiple sensitivities"))
|
|
for i in tlist[1:]:
|
|
newcat.append(i)
|
|
return newcat
|
|
|
|
|
|
def usage():
|
|
print(_("Usage %s CATEGORY File ...") % sys.argv[0])
|
|
print(_("Usage %s -l CATEGORY user ...") % sys.argv[0])
|
|
print(_("Usage %s [[+|-]CATEGORY],...] File ...") % sys.argv[0])
|
|
print(_("Usage %s -l [[+|-]CATEGORY],...] user ...") % sys.argv[0])
|
|
print(_("Usage %s -d File ...") % sys.argv[0])
|
|
print(_("Usage %s -l -d user ...") % sys.argv[0])
|
|
print(_("Usage %s -L") % sys.argv[0])
|
|
print(_("Usage %s -L -l user") % sys.argv[0])
|
|
print(_("Use -- to end option list. For example"))
|
|
print(_("chcat -- -CompanyConfidential /docs/businessplan.odt"))
|
|
print(_("chcat -l +CompanyConfidential juser"))
|
|
sys.exit(1)
|
|
|
|
|
|
def listcats():
|
|
fd = open(selinux.selinux_translations_path())
|
|
for l in fd.read().split("\n"):
|
|
if l.startswith("#"):
|
|
continue
|
|
if l.find("=") != -1:
|
|
rec = l.split("=")
|
|
print("%-30s %s" % tuple(rec))
|
|
fd.close()
|
|
return 0
|
|
|
|
|
|
def listusercats(users):
|
|
if len(users) == 0:
|
|
try:
|
|
users.append(os.getlogin())
|
|
except OSError:
|
|
users.append(pwd.getpwuid(os.getuid()).pw_name)
|
|
|
|
verify_users(users)
|
|
for u in users:
|
|
cats = seobject.translate(selinux.getseuserbyname(u)[2])
|
|
cats = cats.split("-")
|
|
if len(cats) > 1 and cats[1] != "s0":
|
|
print("%s: %s" % (u, cats[1]))
|
|
else:
|
|
print("%s: %s" % (u, cats[0]))
|
|
|
|
|
|
def error(msg):
|
|
print("%s: %s" % (sys.argv[0], msg))
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
if selinux.is_selinux_mls_enabled() != 1:
|
|
error("Requires a mls enabled system")
|
|
|
|
if selinux.is_selinux_enabled() != 1:
|
|
error("Requires an SELinux enabled system")
|
|
|
|
delete_ind = 0
|
|
list_ind = 0
|
|
login_ind = 0
|
|
try:
|
|
gopts, cmds = getopt.getopt(sys.argv[1:],
|
|
'dhlL',
|
|
['list',
|
|
'login',
|
|
'help',
|
|
'delete'])
|
|
|
|
for o, a in gopts:
|
|
if o == "-h" or o == "--help":
|
|
usage()
|
|
if o == "-d" or o == "--delete":
|
|
delete_ind = 1
|
|
if o == "-L" or o == "--list":
|
|
list_ind = 1
|
|
if o == "-l" or o == "--login":
|
|
login_ind = 1
|
|
|
|
if list_ind == 0 and len(cmds) < 1:
|
|
usage()
|
|
|
|
except getopt.error as error:
|
|
errorExit(_("Options Error %s ") % error.msg)
|
|
|
|
except ValueError:
|
|
usage()
|
|
|
|
if delete_ind:
|
|
sys.exit(chcat_replace(["s0"], cmds, login_ind))
|
|
|
|
if list_ind:
|
|
if login_ind:
|
|
sys.exit(listusercats(cmds))
|
|
else:
|
|
if len(cmds) > 0:
|
|
usage()
|
|
sys.exit(listcats())
|
|
|
|
if len(cmds) < 2:
|
|
usage()
|
|
|
|
set_ind = 0
|
|
cats = cmds[0].split(",")
|
|
mod_ind = 0
|
|
errors = 0
|
|
objects = cmds[1:]
|
|
try:
|
|
if check_replace(cats):
|
|
errors = chcat_replace(translate(cats), objects, login_ind)
|
|
else:
|
|
for c in cats:
|
|
l = []
|
|
l.append(c[1:])
|
|
if len(c) > 0 and c[0] == "+":
|
|
errors += chcat_add(c[1:], translate(l), objects, login_ind)
|
|
continue
|
|
if len(c) > 0 and c[0] == "-":
|
|
errors += chcat_remove(c[1:], translate(l), objects, login_ind)
|
|
continue
|
|
except ValueError as e:
|
|
error(e)
|
|
except OSError as e:
|
|
error(e)
|
|
|
|
sys.exit(errors)
|