1#! /usr/bin/python -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 commands, sys, os, pwd, string, getopt, selinux
26import seobject
27import gettext
28
29try:
30    gettext.install('policycoreutils')
31except IOError:
32       import __builtin__
33       __builtin__.__dict__['_'] = unicode
34
35def errorExit(error):
36    sys.stderr.write("%s: " % sys.argv[0])
37    sys.stderr.write("%s\n" % error)
38    sys.stderr.flush()
39    sys.exit(1)
40
41def verify_users(users):
42    for u in users:
43        try:
44            pwd.getpwnam(u)
45        except KeyError:
46            error( "User %s does not exist" % u)
47
48def chcat_user_add(newcat, users):
49    errors = 0
50    logins = seobject.loginRecords()
51    seusers = logins.get_all()
52    add_ind = 0
53    verify_users(users)
54    for u in users:
55        if u in seusers.keys():
56            user = seusers[u]
57        else:
58            add_ind = 1
59            user = seusers["__default__"]
60        serange = user[1].split("-")
61        cats = []
62        top = ["s0"]
63        if len(serange) > 1:
64            top = serange[1].split(":")
65            if len(top) > 1:
66                cats.append(top[1])
67                cats = expandCats(cats)
68
69        for i in newcat[1:]:
70            if i not in cats:
71                cats.append(i)
72
73            
74        if len(cats) > 0:
75            new_serange = "%s-%s:%s" % (serange[0], top[0], ",".join(cats))
76        else:
77            new_serange = "%s-%s" % (serange[0], top[0])
78            
79        if add_ind:
80            cmd = "semanage login -a -r %s -s %s %s" % (new_serange, user[0], u)
81        else:
82            cmd = "semanage login -m -r %s -s %s %s" % (new_serange, user[0], u)
83        rc = commands.getstatusoutput(cmd)
84        if rc[0] != 0:
85            print rc[1]
86            errors += 1
87
88    return errors
89        
90def chcat_add(orig, newcat, objects,login_ind):
91    if len(newcat) == 1:
92        raise ValueError(_("Requires at least one category"))
93
94    if login_ind == 1:
95        return chcat_user_add(newcat, objects)
96    
97    errors = 0
98    sensitivity = newcat[0]
99    cat = newcat[1]
100    cmd = 'chcon -l %s' % sensitivity
101    for f in objects:
102        (rc, c) = selinux.getfilecon(f)
103        con = c.split(":")[3:]
104        clist  =  translate(con)
105        if sensitivity != clist[0]:
106                print(_("Can not modify sensitivity levels using '+' on %s") % f)
107
108        if len(clist) > 1:
109            if cat in clist[1:]:
110                print _("%s is already in %s") % (f, orig)
111                continue
112            clist.append(cat)
113            cats = clist[1:]
114            cats.sort()
115            cat_string = cats[0]
116            for c in cats[1:]:
117                cat_string = "%s,%s" % (cat_string, c)
118        else:
119            cat_string = cat
120        cmd = 'chcon -l %s:%s %s' % (sensitivity, cat_string, f)
121        rc = commands.getstatusoutput(cmd)
122        if rc[0] != 0:
123            print rc[1]
124            errors += 1
125    return errors
126
127def chcat_user_remove(newcat, users):
128    errors = 0
129    logins = seobject.loginRecords()
130    seusers = logins.get_all()
131    add_ind = 0
132    verify_users(users)
133    for u in users:
134        if u in seusers.keys():
135            user = seusers[u]
136        else:
137            add_ind = 1
138            user = seusers["__default__"]
139        serange = user[1].split("-")
140        cats = []
141        top = ["s0"]
142        if len(serange) > 1:
143            top = serange[1].split(":")
144            if len(top) > 1:
145                cats.append(top[1])
146                cats = expandCats(cats)
147
148        for i in newcat[1:]:
149            if i in cats:
150                cats.remove(i)
151
152        if len(cats) > 0:
153            new_serange = "%s-%s:%s" % (serange[0], top[0], ",".join(cats))
154        else:
155            new_serange = "%s-%s" % (serange[0], top[0])
156            
157        if add_ind:
158            cmd = "semanage login -a -r %s -s %s %s" % (new_serange, user[0], u)
159        else:
160            cmd = "semanage login -m -r %s -s %s %s" % (new_serange, user[0], u)
161        rc = commands.getstatusoutput(cmd)
162        if rc[0] != 0:
163            print rc[1]
164            errors += 1
165    return errors
166        
167def chcat_remove(orig, newcat, objects, login_ind):
168    if len(newcat) == 1:
169        raise ValueError(_("Requires at least one category"))
170
171    if login_ind == 1:
172        return chcat_user_remove(newcat, objects)
173
174    errors = 0
175    sensitivity = newcat[0]
176    cat = newcat[1]
177
178    for f in objects:
179        (rc, c) = selinux.getfilecon(f)
180        con = c.split(":")[3:]
181        clist = translate(con)
182        if sensitivity != clist[0]:
183                print(_("Can not modify sensitivity levels using '+' on %s") % f)
184                continue
185            
186        if len(clist) > 1:
187            if cat not in clist[1:]:
188                print _("%s is not in %s") % (f, orig)
189                continue
190            clist.remove(cat)
191            if len(clist) > 1:
192                cat = clist[1]
193                for c in clist[2:]:
194                    cat = "%s,%s" % (cat, c)
195            else:
196                cat = ""
197        else:
198                print _("%s is not in %s") % (f, orig)
199                continue
200        
201        if len(cat) == 0: 
202            cmd = 'chcon -l %s %s' % (sensitivity, f)
203        else:
204            cmd = 'chcon -l %s:%s %s' % (sensitivity,cat, f)
205        rc = commands.getstatusoutput(cmd)
206        if rc[0] != 0:
207            print rc[1]
208            errors += 1
209    return errors
210
211def chcat_user_replace(newcat, users):
212    errors = 0
213    logins = seobject.loginRecords()
214    seusers = logins.get_all()
215    add_ind = 0
216    verify_users(users)
217    for u in users:
218        if u in seusers.keys():
219            user = seusers[u]
220        else:
221            add_ind = 1
222            user = seusers["__default__"]
223        serange = user[1].split("-")
224        new_serange = "%s-%s:%s" % (serange[0],newcat[0], string.join(newcat[1:], ","))
225        if new_serange[-1:] == ":":
226            new_serange = new_serange[:-1]
227
228        if add_ind:
229            cmd = "semanage login -a -r %s -s %s %s" % (new_serange, user[0], u)
230        else:
231            cmd = "semanage login -m -r %s -s %s %s" % (new_serange, user[0], u)
232        rc = commands.getstatusoutput(cmd)
233        if rc[0] != 0:
234            print rc[1]
235            errors += 1
236    return errors
237    
238def chcat_replace(newcat, objects, login_ind):
239    if login_ind == 1:
240        return chcat_user_replace(newcat, objects)
241    errors = 0
242    if len(newcat) == 1:
243        sensitivity = newcat[0]
244        cmd = 'chcon -l %s ' % newcat[0]
245    else:
246        sensitivity = newcat[0]
247        cmd = 'chcon -l %s:%s' % (sensitivity, newcat[1])
248        for cat in newcat[2:]:
249            cmd = '%s,%s' % (cmd, cat)
250        
251    for f in objects:
252        cmd = "%s %s" % (cmd, f)
253
254    rc = commands.getstatusoutput(cmd)
255    if rc[0] != 0:
256        print rc[1]
257        errors += 1
258
259    return errors
260
261def check_replace(cats):
262    plus_ind = 0
263    replace_ind = 0
264    for c in cats:
265        if len(c) > 0 and ( c[0] == "+" or c[0] == "-" ):
266            if replace_ind:
267                raise ValueError(_("Can not combine +/- with other types of categories"))
268            plus_ind = 1
269        else:
270            replace_ind = 1
271            if plus_ind:
272                raise ValueError(_("Can not combine +/- with other types of categories"))
273    return replace_ind
274
275def isSensitivity(sensitivity):
276    if sensitivity[0] == "s" and sensitivity[1:].isdigit() and int(sensitivity[1:]) in range(0,16):
277        return 1
278    else:
279        return 0
280    
281def expandCats(cats):
282    newcats = []
283    for c in cats:
284        for i in c.split(","):
285            if i.find(".") != -1:
286                j = i.split(".")
287                for k in range(int(j[0][1:]), int(j[1][1:]) + 1):
288                    x = ("c%d" % k)
289                    if x not in newcats:
290                        newcats.append(x)
291            else:
292                if i not in newcats:
293                    newcats.append(i)
294    if len(newcats) > 25:
295        return cats
296    return newcats
297
298def translate(cats):
299    newcat = []
300    if len(cats) == 0:
301        newcat.append("s0")
302        return newcat
303    for c in cats:
304        (rc, raw) = selinux.selinux_trans_to_raw_context("a:b:c:%s" % c)
305        rlist = raw.split(":")[3:]
306        tlist = []
307        if isSensitivity(rlist[0]) == 0:
308            tlist.append("s0")
309            for i in expandCats(rlist):
310                tlist.append(i)
311        else:
312            tlist.append(rlist[0])
313            for i in expandCats(rlist[1:]):
314                tlist.append(i)
315        if len(newcat) == 0:
316            newcat.append(tlist[0])
317        else:
318            if newcat[0] != tlist[0]:
319                raise ValueError(_("Can not have multiple sensitivities"))
320        for i in tlist[1:]:
321            newcat.append(i)
322    return newcat
323    
324def usage():
325	print _("Usage %s CATEGORY File ...") % sys.argv[0]
326	print _("Usage %s -l CATEGORY user ...") % sys.argv[0]
327	print _("Usage %s [[+|-]CATEGORY],...]q File ...") % sys.argv[0]
328	print _("Usage %s -l [[+|-]CATEGORY],...]q user ...") % sys.argv[0]
329	print _("Usage %s -d File ...") % sys.argv[0]
330	print _("Usage %s -l -d user ...") % sys.argv[0]
331	print _("Usage %s -L") % sys.argv[0]
332	print _("Usage %s -L -l user") % sys.argv[0]
333        print _("Use -- to end option list.  For example")
334        print _("chcat -- -CompanyConfidential /docs/businessplan.odt")
335        print _("chcat -l +CompanyConfidential juser")
336	sys.exit(1)
337
338def listcats():
339    fd = open(selinux.selinux_translations_path())
340    for l in fd.read().split("\n"):
341        if l.startswith("#"):
342            continue
343        if l.find("=") != -1:
344            rec = l.split("=")
345            print "%-30s %s" % tuple(rec)
346    fd.close()
347    return 0
348    
349
350def listusercats(users):
351    if len(users) == 0:
352        users.append(os.getlogin())
353
354    verify_users(users)
355    for u in users:
356        cats = seobject.translate(selinux.getseuserbyname(u)[2])
357        cats = cats.split("-")
358        if len(cats) > 1 and cats[1] != "s0":
359            print "%s: %s" % (u, cats[1])
360        else:
361            print "%s: %s" % (u, cats[0])
362            
363def error(msg):
364    print "%s: %s" % (sys.argv[0], msg)
365    sys.exit(1)
366    
367if __name__ == '__main__':
368    if selinux.is_selinux_mls_enabled() != 1:
369        error("Requires a mls enabled system")
370        
371    if selinux.is_selinux_enabled() != 1:
372        error("Requires an SELinux enabled system")
373        
374    delete_ind = 0
375    list_ind = 0
376    login_ind = 0
377    try:
378        gopts, cmds = getopt.getopt(sys.argv[1:],
379                                    'dhlL',
380                                    ['list',
381                                     'login',
382                                     'help',
383                                     'delete'])
384
385        for o,a in gopts:
386            if o == "-h" or o == "--help":
387                usage()
388            if o == "-d" or o == "--delete":
389                delete_ind = 1
390            if o == "-L" or o == "--list":
391                list_ind = 1
392            if o == "-l" or o == "--login":
393                login_ind = 1
394
395        if list_ind == 0 and len(cmds) < 1:
396            usage()
397
398    except getopt.error, error:
399        errorExit(_("Options Error %s ") % error.msg)
400
401    except ValueError, e:
402        usage()
403
404    if delete_ind:
405        sys.exit(chcat_replace(["s0"], cmds, login_ind))
406
407    if list_ind:
408        if login_ind:
409            sys.exit(listusercats(cmds))
410        else:
411            if len(cmds) > 0:
412                usage()
413            sys.exit(listcats())
414
415    if len(cmds) < 2:
416        usage()
417    
418    set_ind = 0
419    cats = cmds[0].split(",")
420    mod_ind = 0
421    errors = 0
422    objects = cmds[1:]
423    try:
424        if check_replace(cats):
425            errors = chcat_replace(translate(cats), objects, login_ind)
426        else:
427            for c in cats:
428                l = []
429                l.append(c[1:])
430                if len(c) > 0 and c[0] == "+":
431                    errors += chcat_add(c[1:],translate(l), objects, login_ind)
432                    continue
433                if len(c) > 0 and c[0] == "-":
434                    errors += chcat_remove(c[1:],translate(l), objects, login_ind)
435                    continue
436    except ValueError, e:
437        error(e)
438    except OSError, e:
439        error(e)
440    
441    sys.exit(errors)
442    
443
444
445