1#! /usr/bin/python -Es
2# Copyright (C) 2012-2013 Red Hat
3# AUTHOR: Miroslav Grepl <mgrepl@redhat.com>
4# AUTHOR: David Quigley <selinux@davequigley.com>
5# see file 'COPYING' for use and warranty information
6#
7# semanage is a tool for managing SELinux configuration files
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#
25
26import argparse
27import seobject
28import sys
29import gettext
30PROGNAME="policycoreutils"
31try:
32       gettext.install(PROGNAME,
33                       localedir="/usr/share/locale",
34                       unicode=True,
35                       codeset = 'utf-8')
36except IOError:
37       import __builtin__
38       __builtin__.__dict__['_'] = unicode
39
40# define custom usages for selected main actions
41usage_login = "semanage login [-h] [-n] [-N] [-s STORE] ["
42usage_login_dict = {' --add':('-s SEUSER','-r RANGE','LOGIN',),' --modify':('-s SEUSER','-r RANGE','LOGIN',),' --delete':('LOGIN',), ' --list':('-C',),' --extract':('',), ' --deleteall':('',)}
43
44usage_fcontext = "semanage fcontext [-h] [-n] [-N] [-s STORE] ["
45usage_fcontext_dict = {' --add':('(','-t TYPE','-f FTYPE','-r RANGE','-s SEUSER', '|','-e EQUAL', ')','FILE_SPEC',')' ,),' --delete':('(','-t TYPE','-f FTYPE','|','-e EQUAL',')','FILE_SPEC', ')',),' --modify':('(','-t TYPE','-f FTYPE','-r RANGE','-s SEUSER','|','-e EQUAL',')','FILE_SPEC )',),' --list':('-C',), ' --extract':('',), ' --deleteall':('',)}
46
47usage_user = "semanage user [-h] [-n] [-N] [-s STORE] ["
48usage_user_dict = {' --add':('(','-L LEVEL','-R ROLES','-r RANGE','-s SEUSER','selinux_name'')'),' --delete':('selinux_name',),' --modify':('(','-L LEVEL','-R ROLES','-r RANGE','-s SEUSER','selinux_name',')'),' --list':('-C',), ' --extract':('',), ' --deleteall':('',)}
49
50usage_port = "semanage port [-h] [-n] [-N] [-s STORE] ["
51usage_port_dict = {' --add':('-t TYPE','-p PROTOCOL','-r RANGE','(','port_name','|','port_range',')'),' --modify':('-t TYPE','-p PROTOCOL','-r RANGE','(','port_name','|','port_range',')'), ' --delete':('-p PROTOCOL','(','port_name','|','port_range',')'),' --list':('-C',), ' --extract':('',), ' --deleteall':('',)}
52
53usage_node = "semanage node [-h] [-n] [-N] [-s STORE] ["
54usage_node_dict = {' --add':('-M NETMASK','-p PROTOCOL','-t TYPE','-r RANGE','node'),' --modify':('-M NETMASK','-p PROTOCOL','-t TYPE','-r RANGE','node'), ' --delete':('-M NETMASK','-p PROTOCOL','node'),' --list':('-C',), ' --extract':('',), ' --deleteall':('',)}
55
56usage_interface = "semanage interface [-h] [-n] [-N] [-s STORE] ["
57usage_interface_dict = {' --add':('-t TYPE','-r RANGE','interface'),' --modify':('-t TYPE','-r RANGE','interface'), ' --delete':('interface',),' --list':('-C',), ' --extract':('',), ' --deleteall':('',)}
58
59usage_boolean = "semanage boolean [-h] [-n] [-N] [-s STORE] ["
60usage_boolean_dict = {' --modify':('(','--on','|','--off',')','boolean'), ' --list':('-C',), '  --extract':('',), ' --deleteall':('',)}
61
62import sepolicy
63class CheckRole(argparse.Action):
64    def __call__(self, parser, namespace, value, option_string=None):
65        newval = getattr(namespace, self.dest)
66        if not newval:
67               newval = []
68        roles = sepolicy.get_all_roles()
69        for v in value.split():
70               if v not in roles:
71                      raise ValueError("%s must be an SELinux role:\nValid roles: %s" % (v, ", ".join(roles)))
72               newval.append(v)
73        setattr(namespace, self.dest, newval)
74
75store = ''
76class SetStore(argparse.Action):
77    def __call__(self, parser, namespace, values, option_string=None):
78        global store
79        store=values
80        setattr(namespace, self.dest, values)
81
82class seParser(argparse.ArgumentParser):
83    def error(self, message):
84        if len(sys.argv) == 2:
85            self.print_help()
86            sys.exit(2)
87        self.print_usage()
88        self.exit(2, ('%s: error: %s\n') % (self.prog, message))
89
90class SetExportFile(argparse.Action):
91    def __call__(self, parser, namespace, values, option_string=None):
92        if values:
93            if values is not "-":
94                try:
95                    sys.stdout = open(values, 'w')
96                except:
97                    sys.stderr.write("%s: %s\n" % (e.__class__.__name__, str(e)))
98                    sys.exit(1)
99        setattr(namespace, self.dest, values)
100
101class SetImportFile(argparse.Action):
102    def __call__(self, parser, namespace, values, option_string=None):
103        if values and values is not "-":
104               try:
105                      sys.stdin = open(values, 'r')
106               except IOError,e:
107                      sys.stderr.write("%s: %s\n" % (e.__class__.__name__, str(e)))
108                      sys.exit(1)
109        setattr(namespace, self.dest, values)
110
111# functions for OBJECT initialization
112def login_ini():
113    OBJECT = seobject.loginRecords(store)
114    return OBJECT
115
116def user_ini():
117    OBJECT = seobject.seluserRecords(store)
118    return OBJECT
119
120def port_ini():
121    OBJECT = seobject.portRecords(store)
122    return OBJECT
123
124def module_ini():
125    OBJECT = seobject.moduleRecords(store)
126    return OBJECT
127
128def interface_ini():
129    OBJECT = seobject.interfaceRecords(store)
130    return OBJECT
131
132def node_ini():
133    OBJECT = seobject.nodeRecords(store)
134    return OBJECT
135
136def fcontext_ini():
137    OBJECT = seobject.fcontextRecords(store)
138    return OBJECT
139
140def boolean_ini():
141    OBJECT = seobject.booleanRecords(store)
142    return OBJECT
143
144def permissive_ini():
145    OBJECT = seobject.permissiveRecords(store)
146    return OBJECT
147
148def dontaudit_ini():
149    OBJECT = seobject.dontauditClass(store)
150    return OBJECT
151
152# define dictonary for seobject OBEJCTS
153object_dict = {'login':login_ini, 'user':user_ini, 'port':port_ini, 'module':module_ini, 'interface':interface_ini, 'node':node_ini, 'fcontext':fcontext_ini, 'boolean':boolean_ini,'permissive':permissive_ini, 'dontaudit':dontaudit_ini}
154
155def generate_custom_usage(usage_text,usage_dict):
156    # generate custom usage from given text and dictonary
157    sorted_keys = []
158    for i in usage_dict.keys():
159        sorted_keys.append(i)
160    sorted_keys.sort()
161    for k in sorted_keys:
162        usage_text += "%s %s |" % (k,(" ".join(usage_dict[k])))
163    usage_text = usage_text[:-1] + "]"
164    usage_text = _(usage_text)
165
166    return usage_text
167
168def handle_opts(args,dict,target_key):
169    # handle conflict and required options for given dictonary
170    # {action:[conflict_opts,require_opts]}
171
172    # first we need to catch conflicts
173    for k in args.__dict__.keys():
174        try:
175            if k in dict[target_key][0] and args.__dict__[k]:
176                print("%s option can not be used with --%s" % (target_key,k))
177                sys.exit(2)
178        except KeyError:
179            continue
180
181    for k in args.__dict__.keys():
182        try:
183         if k in dict[target_key][1] and not args.__dict__[k]:
184            print("%s option is needed for %s" % (k,target_key))
185            sys.exit(2)
186        except KeyError:
187            continue
188
189def handleLogin(args):
190    # {action:[conflict_opts,require_opts]}
191    login_args = {'list':[('login','seuser'),('')],'add':[('locallist'),('seuser','login')],'modify':[('locallist'),('login')], 'delete':[('locallist'),('login')],'extract':[('locallist','login','seuser'),('')],'deleteall':[('locallist'),('')]}
192
193    handle_opts(args,login_args,args.action)
194
195    OBJECT = object_dict['login']()
196    OBJECT.set_reload(args.noreload)
197
198    if args.action is "add":
199        OBJECT.add(args.login, args.seuser, args.range)
200    if args.action is "modify":
201        OBJECT.modify(args.login, args.seuser, args.range)
202    if args.action is "delete":
203        OBJECT.delete(args.login)
204    if args.action is "list":
205        OBJECT.list(args.noheading, args.locallist)
206    if args.action is "deleteall":
207        OBJECT.deleteall()
208    if args.action is "extract":
209        for i in OBJECT.customized():
210            print "login %s" % (str(i))
211
212def parser_add_store(parser, name):
213    parser.add_argument('-S', '--store', action=SetStore, help=_("Select an alternate SELinux Policy Store to manage"))
214
215def parser_add_priority(parser, name):
216    parser.add_argument('-P', '--priority', type=int, default=400, help=_("Select a priority for module operations"))
217
218def parser_add_noheading(parser, name):
219    parser.add_argument('-n', '--noheading', action='store_false', default=True, help=_("Do not print heading when listing %s object types") % name  )
220
221def parser_add_noreload(parser, name):
222    parser.add_argument('-N', '--noreload', action='store_false', default=True, help=_('Do not reload policy after commit'))
223
224def parser_add_locallist(parser, name):
225    parser.add_argument('-C', '--locallist', action='store_true', default=False, help=_("List %s local customizations") % name  )
226
227def parser_add_add(parser, name):
228    parser.add_argument('-a', '--add', dest='action', action='store_const', const='add', help=_("Add a record of the %s object type") % name  )
229
230def parser_add_type(parser, name):
231    parser.add_argument('-t', '--type', help=_('SELinux Type for the object'))
232def parser_add_level(parser, name):
233    parser.add_argument('-L', '--level', default='s0', help=_('Default SELinux Level for SELinux user, s0 Default. (MLS/MCS Systems only)'))
234def parser_add_range(parser, name):
235    parser.add_argument('-r', '--range', default="s0",
236                             help=_('''
237MLS/MCS Security Range (MLS/MCS Systems only)
238SELinux Range  for SELinux login mapping
239defaults to the SELinux user record range.
240SELinux Range for SELinux user defaults to s0.
241'''))
242def parser_add_proto(parser, name):
243    parser.add_argument('-p', '--proto', help=_('''
244    Protocol  for  the specified port (tcp|udp) or internet protocol
245    version for the specified node (ipv4|ipv6).
246'''))
247
248def parser_add_modify(parser, name):
249    parser.add_argument('-m', '--modify', dest='action', action='store_const', const='modify', help=_("Modify a record of the %s object type") % name  )
250
251def parser_add_list(parser, name):
252    parser.add_argument('-l', '--list', dest='action', action='store_const', const='list', help=_("List records of the %s object type") % name  )
253
254def parser_add_delete(parser, name):
255    parser.add_argument('-d', '--delete', dest='action', action='store_const', const='delete', help=_("Delete a record of the %s object type") % name  )
256
257def parser_add_extract(parser, name):
258    parser.add_argument('-E', '--extract', dest='action', action='store_const', const='extract', help=_("Extract customizable commands, for use within a transaction"))
259
260def parser_add_deleteall(parser, name):
261    parser.add_argument('-D', '--deleteall', dest='action', action='store_const', const='deleteall', help=_('Remove all %s objects local customizations') % name )
262
263def parser_add_seuser(parser, name):
264    parser.add_argument('-s', '--seuser', default="", help=_("SELinux user name"))
265
266def setupLoginParser(subparsers):
267    generated_usage = generate_custom_usage(usage_login, usage_login_dict)
268    loginParser = subparsers.add_parser('login', usage=generated_usage, help=_("Manage login mappings between linux users and SELinux confined users"))
269    parser_add_locallist(loginParser, "login")
270    parser_add_noheading(loginParser, "login")
271    parser_add_noreload(loginParser, "login")
272    parser_add_store(loginParser, "login")
273    parser_add_range(loginParser, "login")
274
275    login_action = loginParser.add_mutually_exclusive_group(required=True)
276
277    parser_add_add(login_action, "login")
278    parser_add_delete(login_action, "login")
279    parser_add_modify(login_action, "login")
280    parser_add_list(login_action, "login")
281    parser_add_extract(login_action, "login")
282    parser_add_deleteall(login_action, "login")
283    parser_add_seuser(loginParser, "login")
284
285    loginParser.add_argument('login', nargs='?', default=None, help=_("login_name | %%groupname"))
286
287    loginParser.set_defaults(func=handleLogin)
288
289def handleFcontext(args):
290    fcontext_args = {'list':[('equal','ftype','seuser','type'),('')],'add':[('locallist'),('type','file_spec')],'modify':[('locallist'),('type','file_spec')], 'delete':[('locallist'), ('file_spec')],'extract':[('locallist','equal','ftype','seuser','type'),('')],'deleteall':[('locallist'),('')]}
291    # we can not use mutually for equal because we can define some actions together with equal
292    fcontext_equal_args = {'equal':[('list','locallist','type','ftype','seuser','deleteall','extract'),()]}
293
294    if args.action is None:
295        print("usage: "+"%s" % generate_custom_usage(usage_fcontext, usage_fcontext_dict))
296        sys.exit(2)
297    elif args.action and args.equal:
298        handle_opts(args, fcontext_equal_args, "equal")
299    else:
300        handle_opts(args, fcontext_args, args.action)
301
302    OBJECT = object_dict['fcontext']()
303    OBJECT.set_reload(args.noreload)
304
305    if args.action is "add":
306        if args.equal:
307            OBJECT.add_equal(args.file_spec, args.equal)
308        else:
309            OBJECT.add(args.file_spec, args.type, args.ftype, args.range, args.seuser)
310    if args.action is "modify":
311        if args.equal:
312            OBJECT.add_equal(args.file_spec, args.equal)
313        else:
314            OBJECT.modify(args.file_spec, args.type, args.ftype, args.range, args.seuser)
315    if args.action is "delete":
316        if args.equal:
317            OBJECT.delete(args.file_spec, args.equal)
318        else:
319            OBJECT.delete(args.file_spec,args.ftype)
320    if args.action is "list":
321        OBJECT.list(args.noheading, args.locallist)
322    if args.action is "deleteall":
323        OBJECT.deleteall()
324    if args.action is "extract":
325        for i in OBJECT.customized():
326            print "fcontext %s" % str(i)
327
328def setupFcontextParser(subparsers):
329    ftype_help = '''
330File Type.   This is used with fcontext.  Requires a  file  type
331as  shown  in  the  mode  field by ls, e.g. use -d to match only
332directories or -- to match only regular files. The following
333file type options can be passed:
334-- (regular file),-d (directory),-c (character device),
335-b (block device),-s (socket),-l (symbolic link),-p (named pipe)
336If you do not specify a file type, the file type will default to "all files".
337'''
338    generate_usage = generate_custom_usage(usage_fcontext, usage_fcontext_dict)
339    fcontextParser = subparsers.add_parser('fcontext',usage=generate_usage, help=_("Manage file context mapping definitions"))
340    parser_add_locallist(fcontextParser, "fcontext")
341    parser_add_noheading(fcontextParser, "fcontext")
342    parser_add_noreload(fcontextParser, "fcontext")
343    parser_add_store(fcontextParser, "fcontext")
344
345    fcontext_action = fcontextParser.add_mutually_exclusive_group(required=False)
346    parser_add_add(fcontext_action, "fcontext")
347    parser_add_delete(fcontext_action, "fcontext")
348    parser_add_modify(fcontext_action, "fcontext")
349    parser_add_list(fcontext_action, "fcontext")
350    parser_add_extract(fcontext_action, "fcontext")
351    parser_add_deleteall(fcontext_action, "fcontext")
352
353    fcontextParser.add_argument('-e', '--equal', help=_('''Substitute  target  path with sourcepath when generating default
354                                                                  label.  This is used with fcontext. Requires source  and  target
355                                                                  path  arguments.  The context labeling for the target subtree is
356                                                                  made equivalent to that defined for the source.'''))
357    fcontextParser.add_argument('-f', '--ftype', default="", choices=["a","f","d","c","b","s","l","p"], help=_(ftype_help))
358    parser_add_seuser(fcontextParser, "fcontext")
359    parser_add_type(fcontextParser, "fcontext")
360    parser_add_range(fcontextParser, "fcontext")
361    fcontextParser.add_argument('file_spec', nargs='?', default=None, help=_('file_spec'))
362    fcontextParser.set_defaults(func=handleFcontext)
363
364def handleUser(args):
365    user_args = {'list':[('selinux_name','seuser','roles'),('')],'add':[('locallist'),('roles','selinux_name')],'modify':[('locallist'),('selinux_name')], 'delete':[('locallist'),('selinux_name')],'extract':[('locallist','selinux_name','seuser','role'),('')],'deleteall':[('locallist'),('')]}
366
367    handle_opts(args,user_args,args.action)
368
369    OBJECT = object_dict['user']()
370    OBJECT.set_reload(args.noreload)
371
372    if args.action is "add":
373        OBJECT.add(args.selinux_name, args.roles,  args.level, args.range, args.prefix)
374    if args.action is "modify":
375        OBJECT.modify(args.selinux_name, args.roles,  args.level, args.range, args.prefix)
376    if args.action is "delete":
377        OBJECT.delete(args.selinux_name)
378    if args.action is "list":
379        OBJECT.list(args.noheading, args.locallist)
380    if args.action is "deleteall":
381        OBJECT.deleteall()
382    if args.action is "extract":
383        for i in OBJECT.customized():
384            print "user %s" % str(i)
385
386def setupUserParser(subparsers):
387    generated_usage = generate_custom_usage(usage_user, usage_user_dict)
388    userParser = subparsers.add_parser('user', usage=generated_usage,help=_('Manage SELinux confined users (Roles and levels for an SELinux user)'))
389    parser_add_locallist(userParser, "user")
390    parser_add_noheading(userParser, "user")
391    parser_add_noreload(userParser, "user")
392    parser_add_store(userParser, "user")
393
394    user_action = userParser.add_mutually_exclusive_group(required=True)
395    parser_add_add(user_action, "user")
396    parser_add_delete(user_action, "user")
397    parser_add_modify(user_action, "user")
398    parser_add_list(user_action, "user")
399    parser_add_extract(user_action, "user")
400    parser_add_deleteall(user_action, "user")
401
402    parser_add_level(userParser, "user")
403    parser_add_range(userParser, "user")
404    userParser.add_argument('-R', '--roles', default=[],
405                            action=CheckRole,
406                            help=_('''
407SELinux Roles.  You must enclose multiple roles within quotes,                  separate by spaces. Or specify -R multiple times.
408'''))
409    userParser.add_argument('-P', '--prefix', default="user",  help=argparse.SUPPRESS)
410    userParser.add_argument('selinux_name', nargs='?', default=None, help=_('selinux_name'))
411    userParser.set_defaults(func=handleUser)
412
413def handlePort(args):
414    port_args = {'list':[('port','type','proto'),('')],'add':[('locallist'),('type','port','proto')],'modify':[('localist'),('port','proto')], 'delete':[('locallist'),('port','proto')],'extract':[('locallist','port','type','proto'),('')],'deleteall':[('locallist'),('')]}
415
416    handle_opts(args,port_args,args.action)
417
418    OBJECT = object_dict['port']()
419    OBJECT.set_reload(args.noreload)
420
421    if args.action is "add":
422        OBJECT.add(args.port, args.proto, args.range, args.type)
423    if args.action is "modify":
424        OBJECT.modify(args.port, args.proto, args.range, args.type)
425    if args.action is "delete":
426        OBJECT.delete(args.port, args.proto)
427    if args.action is "list":
428        OBJECT.list(args.noheading, args.locallist)
429    if args.action is "deleteall":
430        OBJECT.deleteall()
431    if args.action is "extract":
432        for i in OBJECT.customized():
433            print "port %s" % str(i)
434
435def setupPortParser(subparsers):
436    generated_usage = generate_custom_usage(usage_port, usage_port_dict)
437    portParser = subparsers.add_parser('port', usage=generated_usage, help=_('Manage network port type definitions'))
438    parser_add_locallist(portParser, "port")
439    parser_add_noheading(portParser, "port")
440    parser_add_noreload(portParser, "port")
441    parser_add_store(portParser, "port")
442
443    port_action = portParser.add_mutually_exclusive_group(required=True)
444    parser_add_add(port_action, "port")
445    parser_add_delete(port_action, "port")
446    parser_add_modify(port_action, "port")
447    parser_add_list(port_action, "port")
448    parser_add_extract(port_action, "port")
449    parser_add_deleteall(port_action, "port")
450    parser_add_type(portParser, "port")
451    parser_add_range(portParser, "port")
452    parser_add_proto(portParser, "port")
453    portParser.add_argument('port', nargs='?', default=None, help=_('port | port_range'))
454    portParser.set_defaults(func=handlePort)
455
456def handleInterface(args):
457    interface_args = {'list':[('interface'),('')],'add':[('locallist'),('type','interface')],'modify':[('locallist'),('type','interface')], 'delete':[('locallist'),('interface')],'extract':[('locallist','interface','type'),('')],'deleteall':[('locallist'),('')]}
458
459    handle_opts(args,interface_args,args.action)
460
461    OBJECT = object_dict['interface']()
462    OBJECT.set_reload(args.noreload)
463
464    if args.action is "add":
465        OBJECT.add(args.interface, args.range, args.type)
466    if args.action is "modify":
467        OBJECT.add(args.interface, args.range, args.type)
468    if args.action is "delete":
469        OBJECT.delete(args.interface)
470    if args.action is "list":
471        OBJECT.list(args.noheading, args.locallist)
472    if args.action is "deleteall":
473        OBJECT.deleteall()
474    if args.action is "extract":
475        for i in OBJECT.customized():
476            print "interface %s" % str(i)
477
478def setupInterfaceParser(subparsers):
479    generated_usage = generate_custom_usage(usage_interface, usage_interface_dict)
480    interfaceParser = subparsers.add_parser('interface', usage=generated_usage, help=_('Manage network interface type definitions'))
481    parser_add_locallist(interfaceParser, "interface")
482    parser_add_noheading(interfaceParser, "interface")
483    parser_add_noreload(interfaceParser, "interface")
484    parser_add_store(interfaceParser, "interface")
485    parser_add_type(interfaceParser, "interface")
486    parser_add_range(interfaceParser, "interface")
487
488    interface_action = interfaceParser.add_mutually_exclusive_group(required=True)
489    parser_add_add(interface_action, "interface")
490    parser_add_delete(interface_action, "interface")
491    parser_add_modify(interface_action, "interface")
492    parser_add_list(interface_action, "interface")
493    parser_add_extract(interface_action, "interface")
494    parser_add_deleteall(interface_action, "interface")
495    interfaceParser.add_argument('interface', nargs='?', default=None, help=_('interface_spec'))
496    interfaceParser.set_defaults(func=handleInterface)
497
498def handleModule(args):
499    OBJECT = seobject.moduleRecords(store)
500    OBJECT.set_reload(args.noreload)
501    if args.action == "add":
502        OBJECT.add(args.module_name, args.priority)
503    if args.action == "enable":
504        OBJECT.set_enabled(args.module_name, True)
505    if args.action == "disable":
506        OBJECT.set_enabled(args.module_name, False)
507    if args.action == "remove":
508        OBJECT.delete(args.module_name, args.priority)
509    if args.action is "deleteall":
510        OBJECT.deleteall()
511    if args.action == "list":
512        OBJECT.list(args.noheading, args.locallist)
513    if args.action is "extract":
514        for i in OBJECT.customized():
515            print "module %s" % str(i)
516
517def setupModuleParser(subparsers):
518    moduleParser = subparsers.add_parser('module', help=_('Manage SELinux policy modules'))
519    parser_add_noheading(moduleParser, "module")
520    parser_add_noreload(moduleParser, "module")
521    parser_add_store(moduleParser, "module")
522    parser_add_locallist(moduleParser, "module")
523    parser_add_priority(moduleParser, "module")
524
525    mgroup = moduleParser.add_mutually_exclusive_group(required=True)
526    parser_add_add(mgroup, "module")
527    parser_add_list(mgroup, "module")
528    parser_add_extract(mgroup, "module")
529    parser_add_deleteall(mgroup, "module")
530    mgroup.add_argument('-r', '--remove', dest='action', action='store_const', const='remove', help=_("Remove a module"))
531    mgroup.add_argument('-d', '--disable', dest='action', action='store_const', const='disable', help=_("Disable a module"))
532    mgroup.add_argument('-e', '--enable', dest='action', action='store_const', const='enable', help=_("Enable a module"))
533    moduleParser.add_argument('module_name', nargs='?', default=None, help=_('Name of the module to act on'))
534    moduleParser.set_defaults(func=handleModule)
535
536def handleNode(args):
537    node_args = {'list':[('node','type','proto','netmask'),('')],'add':[('locallist'),('type','node','proto','netmask')],'modify':[('locallist'),('node','netmask','proto')], 'delete':[('locallist'),('node','netmask','prototype')],'extract':[('locallist','node','type','proto','netmask'),('')],'deleteall':[('locallist'),('')]}
538    handle_opts(args,node_args,args.action)
539
540    OBJECT = object_dict['node']()
541    OBJECT.set_reload(args.noreload)
542
543    if args.action is "add":
544        OBJECT.add(args.node, args.netmask, args.proto, args.range, args.type)
545    if args.action is "modify":
546        OBJECT.add(args.node, args.netmask, args.proto, args.range, args.type)
547    if args.action is "delete":
548        OBJECT.delete(args.node, args.netmask, args.proto)
549    if args.action is "list":
550        OBJECT.list(args.noheading, args.locallist)
551    if args.action is "deleteall":
552        OBJECT.deleteall()
553    if args.action is "extract":
554        for i in OBJECT.customized():
555            print "node %s" % str(i)
556
557def setupNodeParser(subparsers):
558    generated_usage = generate_custom_usage(usage_node, usage_node_dict)
559    nodeParser = subparsers.add_parser('node', usage=generated_usage, help=_('Manage network node type definitions'))
560    parser_add_locallist(nodeParser, "node")
561    parser_add_noheading(nodeParser, "node")
562    parser_add_noreload(nodeParser, "node")
563    parser_add_store(nodeParser, "node")
564
565    node_action = nodeParser.add_mutually_exclusive_group(required=True)
566    parser_add_add(node_action, "node")
567    parser_add_delete(node_action, "node")
568    parser_add_modify(node_action, "node")
569    parser_add_list(node_action, "node")
570    parser_add_extract(node_action, "node")
571    parser_add_deleteall(node_action, "node")
572
573    nodeParser.add_argument('-M', '--netmask',  help=_('Network Mask'))
574    parser_add_type(nodeParser, "node")
575    parser_add_range(nodeParser, "node")
576    parser_add_proto(nodeParser, "node")
577    nodeParser.add_argument('node', nargs='?', default=None, help=_('node'))
578    nodeParser.set_defaults(func=handleNode)
579
580def handleBoolean(args):
581    boolean_args = {'list':[('state','boolean'),('')],'modify':[('localist'),('')], 'extract':[('locallist','state','boolean'),('')],'deleteall':[('locallist'),('')],'state':[('locallist','list','extract','deleteall'),('modify')]}
582    if args.action is None:
583        print("Usage: "+"%s" % generate_custom_usage(usage_boolean, usage_boolean_dict))
584        sys.exit(2)
585    # TODO: should be added to handle_opts logic
586    elif args.action is "modify" and not args.boolean:
587        print "boolean name required "
588        sys.exit(1)
589    elif args.action is "modify" and args.boolean and not args.state:
590        print "state option is needed"
591        sys.exit(1)
592    else:
593        handle_opts(args,boolean_args,args.action)
594
595    OBJECT = object_dict['boolean']()
596    OBJECT.set_reload(args.noreload)
597
598    if args.action is "modify":
599        if args.boolean:
600             OBJECT.modify(args.boolean, args.state, False)
601    if args.action is "list":
602        OBJECT.list(args.noheading, args.locallist)
603    if args.action is "deleteall":
604        OBJECT.deleteall()
605    if args.action is "extract":
606        for i in OBJECT.customized():
607            print "boolean %s" % str(i)
608
609def setupBooleanParser(subparsers):
610    generated_usage = generate_custom_usage(usage_boolean, usage_boolean_dict)
611    booleanParser = subparsers.add_parser('boolean',usage=generated_usage, help=_('Manage booleans to selectively enable functionality'))
612    parser_add_locallist(booleanParser, "boolean")
613    parser_add_noheading(booleanParser, "boolean")
614    parser_add_noreload(booleanParser, "boolean")
615    parser_add_store(booleanParser, "boolean")
616    booleanParser.add_argument('boolean', nargs="?", default=None, help=_('boolean'))
617
618    boolean_action = booleanParser.add_mutually_exclusive_group(required=False)
619    #add_add(boolean_action)
620    parser_add_modify(boolean_action, "boolean")
621    parser_add_list(boolean_action, "boolean")
622    parser_add_extract(boolean_action, "boolean")
623    parser_add_deleteall(boolean_action, "boolean")
624
625    booleanGroup = booleanParser.add_mutually_exclusive_group(required=False)
626    booleanGroup.add_argument('-1', '--on', dest='state', action='store_const', const='on', help=_('Enable the boolean'))
627    booleanGroup.add_argument('-0', '--off', dest='state', action='store_const', const='off', help=_('Disable the boolean'))
628
629    booleanParser.set_defaults(func=handleBoolean)
630
631def handlePermissive(args):
632    OBJECT = object_dict['permissive']()
633    OBJECT.set_reload(args.noreload)
634
635    if args.action is "add":
636        OBJECT.add(args.type)
637    if args.action is "list":
638        OBJECT.list(args.noheading)
639    if args.action is "delete":
640        OBJECT.delete(args.type)
641
642def setupPermissiveParser(subparsers):
643    permissiveParser = subparsers.add_parser('permissive', help=_('Manage process type enforcement mode'))
644
645    pgroup = permissiveParser.add_mutually_exclusive_group(required=True)
646    parser_add_add(pgroup, "permissive")
647    parser_add_delete(pgroup, "permissive")
648    parser_add_list(pgroup, "permissive")
649    #TODO: probably should be also added => need to implement own option handling
650    #parser_add_deleteall(pgroup)
651
652    parser_add_noheading(permissiveParser, "permissive")
653    parser_add_noreload(permissiveParser, "permissive")
654    parser_add_store(permissiveParser, "permissive")
655    permissiveParser.add_argument('type', nargs='?', default=None, help=_('type'))
656    permissiveParser.set_defaults(func=handlePermissive)
657
658def handleDontaudit(args):
659    OBJECT = object_dict['dontaudit']()
660    OBJECT.set_reload(args.noreload)
661    OBJECT.toggle(args.action)
662
663def setupDontauditParser(subparsers):
664    dontauditParser = subparsers.add_parser('dontaudit', help=_('Disable/Enable dontaudit rules in policy'))
665    parser_add_noreload(dontauditParser, "dontaudit")
666    parser_add_store(dontauditParser, "dontaudit")
667    dontauditParser.add_argument('action', choices=["on", "off"])
668    dontauditParser.set_defaults(func=handleDontaudit)
669
670def handleExport(args):
671    manageditems=[ "boolean", "login", "interface", "user", "port", "node", "fcontext", "module"]
672    for i in manageditems:
673        print "%s -D" % i
674    for i in manageditems:
675        OBJECT = object_dict[i]()
676        for c in OBJECT.customized():
677            print "%s %s" % (i, str(c))
678
679    sys.exit(0)
680
681def setupExportParser(subparsers):
682    exportParser = subparsers.add_parser('export', help=_('Output local customizations'))
683    parser_add_store(exportParser, "export")
684    exportParser.add_argument('-f', '--output_file', dest='output_file', action=SetExportFile, help=_('Output file'))
685    exportParser.set_defaults(func=handleExport)
686
687import re
688def mkargv(line):
689    dquote = "\""
690    squote = "\'"
691    l = line.split()
692    ret = []
693    i = 0
694    while i < len(l):
695        cnt = len(re.findall(dquote, l[i]))
696        if cnt > 1:
697            ret.append(l[i].strip(dquote))
698            i = i + 1
699            continue
700        if cnt == 1:
701            quote = [ l[i].strip(dquote) ]
702            i = i + 1
703
704            while i < len(l) and  dquote not in l[i]:
705                quote.append(l[i])
706                i = i + 1
707            quote.append(l[i].strip(dquote))
708            ret.append(" ".join(quote))
709            i = i + 1
710            continue
711
712        cnt = len(re.findall(squote, l[i]))
713        if cnt > 1:
714            ret.append(l[i].strip(squote))
715            i = i + 1
716            continue
717        if cnt == 1:
718            quote = [ l[i].strip(squote) ]
719            i = i + 1
720            while i < len(l) and  squote not in l[i]:
721                quote.append(l[i])
722                i = i + 1
723
724            quote.append(l[i].strip(squote))
725            ret.append(" ".join(quote))
726            i = i + 1
727            continue
728
729        ret.append(l[i])
730        i = i + 1
731
732    return ret
733
734def handleImport(args):
735    trans = seobject.semanageRecords(store)
736    trans.start()
737
738    for l in sys.stdin.readlines():
739        if len(l.strip()) == 0:
740               continue
741
742        try:
743            commandParser = createCommandParser()
744            args = commandParser.parse_args(mkargv(l))
745            args.func(args)
746        except ValueError,e:
747            sys.stderr.write("%s: %s\n" % (e.__class__.__name__, str(e)))
748            sys.exit(1)
749        except IOError,e:
750            sys.stderr.write("%s: %s\n" % (e.__class__.__name__, str(e)))
751            sys.exit(1)
752        except KeyboardInterrupt:
753            sys.exit(0)
754
755    trans.set_reload(args.noreload)
756    trans.finish()
757
758def setupImportParser(subparsers):
759    importParser = subparsers.add_parser('import', help=_('Output local customizations'))
760    parser_add_noreload(importParser, "import")
761    parser_add_store(importParser, "import")
762    importParser.add_argument('-f', '--input_file', dest='input_file', action=SetImportFile, help=_('Input file'))
763    importParser.set_defaults(func=handleImport)
764
765def createCommandParser():
766    commandParser = seParser(prog='semanage',
767                                            formatter_class=argparse.ArgumentDefaultsHelpFormatter,
768                                            description='''semanage is used to configure certain elements
769                                                            of SELinux policy with-out requiring modification
770                                                            to or recompilation from policy source.''')
771
772    #To add a new subcommand define the parser for it in a function above and call it here.
773    subparsers = commandParser.add_subparsers(dest='subcommand')
774    setupImportParser(subparsers)
775    setupExportParser(subparsers)
776    setupLoginParser(subparsers)
777    setupUserParser(subparsers)
778    setupPortParser(subparsers)
779    setupInterfaceParser(subparsers)
780    setupModuleParser(subparsers)
781    setupNodeParser(subparsers)
782    setupFcontextParser(subparsers)
783    setupBooleanParser(subparsers)
784    setupPermissiveParser(subparsers)
785    setupDontauditParser(subparsers)
786
787    return commandParser
788
789def make_io_args(args):
790    # import/export backward compability
791    args_origin = ["-S", "-o", "-i", "targeted", "minumum", "mls"]
792    args_file = []
793    args_ie = []
794    args_subcommand = []
795
796    for i in args:
797        if i == "-o":
798            args_subcommand = ["export"]
799            continue
800        if i == "-i":
801            args_subcommand = ["import"]
802            continue
803        if i not in args_origin:
804            args_file = ["-f", i]
805            continue
806        args_ie.append(i)
807
808    return args_subcommand+args_ie+args_file
809
810def make_args(sys_args):
811    args = []
812    if "-o" in sys_args[1:] or "-i" in sys_args[1:]:
813        args=make_io_args(sys_args[1:])
814    else:
815        args=sys_args[1:]
816
817    return args
818
819def do_parser():
820    try:
821        commandParser = createCommandParser()
822        args = commandParser.parse_args(make_args(sys.argv))
823        args.func(args)
824        sys.exit(0)
825    except IOError,e:
826        sys.stderr.write("%s: %s\n" % (e.__class__.__name__, str(e)))
827        sys.exit(1)
828    except KeyboardInterrupt:
829        sys.exit(0)
830    except ValueError, e:
831        sys.stderr.write("%s: %s\n" % (e.__class__.__name__, e.args[0]))
832        sys.exit(1)
833    except KeyError, e:
834        sys.stderr.write("%s: %s\n" % (e.__class__.__name__, e.args[0]))
835        sys.exit(1)
836    except OSError, e:
837        sys.stderr.write("%s: %s\n" % (e.__class__.__name__, e.args[1]))
838        sys.exit(1)
839    except RuntimeError, e:
840        sys.stderr.write("%s: %s\n" % (e.__class__.__name__, e.args[0]))
841        sys.exit(1)
842
843if __name__ == '__main__':
844       do_parser()
845