• 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    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