| #!/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 <[email protected]> |
| # |
| # 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(_("%s is already in %s") % (f, 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(_("%s is not in %s") % (f, 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(_("%s is not in %s") % (f, 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) |