• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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