• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (C) 2012-2013 Red Hat
2# AUTHOR: Dan Walsh <dwalsh@redhat.com>
3# AUTHOR: Miroslav Grepl <mgrepl@redhat.com>
4# see file 'COPYING' for use and warranty information
5#
6# semanage is a tool for managing SELinux configuration files
7#
8#    This program is free software; you can redistribute it and/or
9#    modify it under the terms of the GNU General Public License as
10#    published by the Free Software Foundation; either version 2 of
11#    the License, or (at your option) any later version.
12#
13#    This program is distributed in the hope that it will be useful,
14#    but WITHOUT ANY WARRANTY; without even the implied warranty of
15#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16#    GNU General Public License for more details.
17#
18#    You should have received a copy of the GNU General Public License
19#    along with this program; if not, write to the Free Software
20#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21#                                        02111-1307  USA
22#
23#
24__all__ = ['ManPage', 'HTMLManPages', 'manpage_domains', 'manpage_roles', 'gen_domains']
25
26import string
27import selinux
28import sepolicy
29import os
30import time
31
32typealias_types = {
33"antivirus_t":("amavis_t", "clamd_t", "clamscan_t", "freshclam_t"),
34"cluster_t":("rgmanager_t", "corosync_t", "aisexec_t", "pacemaker_t"),
35"svirt_t":("qemu_t"),
36"httpd_t":("phpfpm_t"),
37}
38
39equiv_dict = {"smbd": ["samba"], "httpd": ["apache"], "virtd": ["virt", "libvirt"], "named": ["bind"], "fsdaemon": ["smartmon"], "mdadm": ["raid"]}
40
41equiv_dirs = ["/var"]
42modules_dict = None
43
44
45def gen_modules_dict(path="/usr/share/selinux/devel/policy.xml"):
46    global modules_dict
47    if modules_dict:
48        return modules_dict
49
50    import xml.etree.ElementTree
51    modules_dict = {}
52    try:
53        tree = xml.etree.ElementTree.fromstring(sepolicy.policy_xml(path))
54        for l in tree.findall("layer"):
55            for m in l.findall("module"):
56                name = m.get("name")
57                if name == "user" or name == "unconfined":
58                    continue
59                if name == "unprivuser":
60                    name = "user"
61                if name == "unconfineduser":
62                    name = "unconfined"
63                for b in m.findall("summary"):
64                    modules_dict[name] = b.text
65    except IOError:
66        pass
67    return modules_dict
68
69users = None
70users_range = None
71
72
73def get_all_users_info():
74    global users
75    global users_range
76    if users and users_range:
77        return users, users_range
78
79    users = []
80    users_range = {}
81    allusers = []
82    allusers_info = sepolicy.info(sepolicy.USER)
83
84    for d in allusers_info:
85        allusers.append(d['name'])
86        if 'range' in d:
87            users_range[d['name'].split("_")[0]] = d['range']
88
89    for u in allusers:
90        if u not in ["system_u", "root", "unconfined_u"]:
91            users.append(u.replace("_u", ""))
92    users.sort()
93    return users, users_range
94
95all_entrypoints = None
96
97def get_entrypoints():
98    global all_entrypoints
99    if not all_entrypoints:
100        all_entrypoints = next(sepolicy.info(sepolicy.ATTRIBUTE, "entry_type"))["types"]
101    return all_entrypoints
102
103domains = None
104
105
106def gen_domains():
107    global domains
108    if domains:
109        return domains
110    domains = []
111    for d in sepolicy.get_all_domains():
112        found = False
113        domain = d[:-2]
114#		if domain + "_exec_t" not in get_entrypoints():
115#			continue
116        if domain in domains:
117            continue
118        domains.append(domain)
119
120    for role in sepolicy.get_all_roles():
121        if role[:-2] in domains or role == "system_r":
122            continue
123        domains.append(role[:-2])
124
125    domains.sort()
126    return domains
127
128types = None
129
130
131def _gen_types():
132    global types
133    if types:
134        return types
135    all_types = sepolicy.info(sepolicy.TYPE)
136    types = {}
137    for rec in all_types:
138        try:
139            types[rec["name"]] = rec["attributes"]
140        except:
141            types[rec["name"]] = []
142    return types
143
144
145def prettyprint(f, trim):
146    return " ".join(f[:-len(trim)].split("_"))
147
148# for HTML man pages
149manpage_domains = []
150manpage_roles = []
151
152fedora_releases = ["Fedora17", "Fedora18"]
153rhel_releases = ["RHEL6", "RHEL7"]
154
155
156def get_alphabet_manpages(manpage_list):
157    alphabet_manpages = dict.fromkeys(string.ascii_letters, [])
158    for i in string.ascii_letters:
159        temp = []
160        for j in manpage_list:
161            if j.split("/")[-1][0] == i:
162                temp.append(j.split("/")[-1])
163
164        alphabet_manpages[i] = temp
165
166    return alphabet_manpages
167
168
169def convert_manpage_to_html(html_manpage, manpage):
170    try:
171        from commands import getstatusoutput
172    except ImportError:
173        from subprocess import getstatusoutput
174    rc, output = getstatusoutput("/usr/bin/groff -man -Thtml %s 2>/dev/null" % manpage)
175    if rc == 0:
176        print(html_manpage, "has been created")
177        fd = open(html_manpage, 'w')
178        fd.write(output)
179        fd.close()
180
181
182class HTMLManPages:
183
184    """
185            Generate a HHTML Manpages on an given SELinux domains
186    """
187
188    def __init__(self, manpage_roles, manpage_domains, path, os_version):
189        self.manpage_roles = get_alphabet_manpages(manpage_roles)
190        self.manpage_domains = get_alphabet_manpages(manpage_domains)
191        self.os_version = os_version
192        self.old_path = path + "/"
193        self.new_path = self.old_path + self.os_version + "/"
194
195        if self.os_version in fedora_releases or self.os_version in rhel_releases:
196            self.__gen_html_manpages()
197        else:
198            print("SELinux HTML man pages can not be generated for this %s" % os_version)
199            exit(1)
200
201    def __gen_html_manpages(self):
202        self._write_html_manpage()
203        self._gen_index()
204        self._gen_body()
205        self._gen_css()
206
207    def _write_html_manpage(self):
208        if not os.path.isdir(self.new_path):
209            os.mkdir(self.new_path)
210
211        for domain in self.manpage_domains.values():
212            if len(domain):
213                for d in domain:
214                    convert_manpage_to_html((self.new_path + d.rsplit("_selinux", 1)[0] + ".html"), self.old_path + d)
215
216        for role in self.manpage_roles.values():
217            if len(role):
218                for r in role:
219                    convert_manpage_to_html((self.new_path + r.rsplit("_selinux", 1)[0] + ".html"), self.old_path + r)
220
221    def _gen_index(self):
222        index = self.old_path + "index.html"
223        fd = open(index, 'w')
224        fd.write("""
225<html>
226<head>
227    <link rel=stylesheet type="text/css" href="style.css" title="style">
228    <title>SELinux man pages online</title>
229</head>
230<body>
231<h1>SELinux man pages</h1>
232<br></br>
233Fedora or Red Hat Enterprise Linux Man Pages.</h2>
234<br></br>
235<hr>
236<h3>Fedora</h3>
237<table><tr>
238<td valign="middle">
239</td>
240</tr></table>
241<pre>
242""")
243        for f in fedora_releases:
244            fd.write("""
245<a href=%s/%s.html>%s</a> - SELinux man pages for %s """ % (f, f, f, f))
246
247        fd.write("""
248</pre>
249<hr>
250<h3>RHEL</h3>
251<table><tr>
252<td valign="middle">
253</td>
254</tr></table>
255<pre>
256""")
257        for r in rhel_releases:
258            fd.write("""
259<a href=%s/%s.html>%s</a> - SELinux man pages for %s """ % (r, r, r, r))
260
261        fd.write("""
262</pre>
263	""")
264        fd.close()
265        print("%s has been created" % index)
266
267    def _gen_body(self):
268        html = self.new_path + self.os_version + ".html"
269        fd = open(html, 'w')
270        fd.write("""
271<html>
272<head>
273	<link rel=stylesheet type="text/css" href="../style.css" title="style">
274	<title>Linux man-pages online for Fedora18</title>
275</head>
276<body>
277<h1>SELinux man pages for Fedora18</h1>
278<hr>
279<table><tr>
280<td valign="middle">
281<h3>SELinux roles</h3>
282""")
283        for letter in self.manpage_roles:
284            if len(self.manpage_roles[letter]):
285                fd.write("""
286<a href=#%s_role>%s</a>"""
287                         % (letter, letter))
288
289        fd.write("""
290</td>
291</tr></table>
292<pre>
293""")
294        rolename_body = ""
295        for letter in self.manpage_roles:
296            if len(self.manpage_roles[letter]):
297                rolename_body += "<p>"
298                for r in self.manpage_roles[letter]:
299                    rolename = r.rsplit("_selinux", 1)[0]
300                    rolename_body += "<a name=%s_role></a><a href=%s.html>%s_selinux(8)</a> - Security Enhanced Linux Policy for the %s SELinux user\n" % (letter, rolename, rolename, rolename)
301
302        fd.write("""%s
303</pre>
304<hr>
305<table><tr>
306<td valign="middle">
307<h3>SELinux domains</h3>"""
308                 % rolename_body)
309
310        for letter in self.manpage_domains:
311            if len(self.manpage_domains[letter]):
312                fd.write("""
313<a href=#%s_domain>%s</a>
314			""" % (letter, letter))
315
316        fd.write("""
317</td>
318</tr></table>
319<pre>
320""")
321        domainname_body = ""
322        for letter in self.manpage_domains:
323            if len(self.manpage_domains[letter]):
324                domainname_body += "<p>"
325                for r in self.manpage_domains[letter]:
326                    domainname = r.rsplit("_selinux", 1)[0]
327                    domainname_body += "<a name=%s_domain></a><a href=%s.html>%s_selinux(8)</a> - Security Enhanced Linux Policy for the %s SELinux processes\n" % (letter, domainname, domainname, domainname)
328
329        fd.write("""%s
330</pre>
331</body>
332</html>
333""" % domainname_body)
334
335        fd.close()
336        print("%s has been created" % html)
337
338    def _gen_css(self):
339        style_css = self.old_path + "style.css"
340        fd = open(style_css, 'w')
341        fd.write("""
342html, body {
343    background-color: #fcfcfc;
344    font-family: arial, sans-serif;
345    font-size: 110%;
346    color: #333;
347}
348
349h1, h2, h3, h4, h5, h5 {
350	color: #2d7c0b;
351	font-family: arial, sans-serif;
352	margin-top: 25px;
353}
354
355a {
356    color: #336699;
357    text-decoration: none;
358}
359
360a:visited {
361    color: #4488bb;
362}
363
364a:hover, a:focus, a:active {
365    color: #07488A;
366    text-decoration: none;
367}
368
369a.func {
370    color: red;
371    text-decoration: none;
372}
373a.file {
374    color: red;
375    text-decoration: none;
376}
377
378pre.code {
379    background-color: #f4f0f4;
380//    font-family: monospace, courier;
381    font-size: 110%;
382    margin-left: 0px;
383    margin-right: 60px;
384    padding-top: 5px;
385    padding-bottom: 5px;
386    padding-left: 8px;
387    padding-right: 8px;
388    border: 1px solid #AADDAA;
389}
390
391.url {
392    font-family: serif;
393    font-style: italic;
394    color: #440064;
395}
396""")
397
398        fd.close()
399        print("%s has been created" % style_css)
400
401
402class ManPage:
403
404    """
405        Generate a Manpage on an SELinux domain in the specified path
406    """
407    modules_dict = None
408    enabled_str = ["Disabled", "Enabled"]
409
410    def __init__(self, domainname, path="/tmp", root="/", source_files=False, html=False):
411        self.html = html
412        self.source_files = source_files
413        self.root = root
414        self.portrecs = sepolicy.gen_port_dict()[0]
415        self.domains = gen_domains()
416        self.all_domains = sepolicy.get_all_domains()
417        self.all_attributes = sepolicy.get_all_attributes()
418        self.all_bools = sepolicy.get_all_bools()
419        self.all_port_types = sepolicy.get_all_port_types()
420        self.all_roles = sepolicy.get_all_roles()
421        self.all_users = get_all_users_info()[0]
422        self.all_users_range = get_all_users_info()[1]
423        self.all_file_types = sepolicy.get_all_file_types()
424        self.role_allows = sepolicy.get_all_role_allows()
425        self.types = _gen_types()
426
427        if self.source_files:
428            self.fcpath = self.root + "file_contexts"
429        else:
430            self.fcpath = self.root + selinux.selinux_file_context_path()
431
432        self.fcdict = sepolicy.get_fcdict(self.fcpath)
433
434        if not os.path.exists(path):
435            os.makedirs(path)
436
437        self.path = path
438
439        if self.source_files:
440            self.xmlpath = self.root + "policy.xml"
441        else:
442            self.xmlpath = self.root + "/usr/share/selinux/devel/policy.xml"
443        self.booleans_dict = sepolicy.gen_bool_dict(self.xmlpath)
444
445        self.domainname, self.short_name = sepolicy.gen_short_name(domainname)
446
447        self.type = self.domainname + "_t"
448        self._gen_bools()
449        self.man_page_path = "%s/%s_selinux.8" % (path, self.domainname)
450        self.fd = open(self.man_page_path, 'w')
451        if self.domainname + "_r" in self.all_roles:
452            self.__gen_user_man_page()
453            if self.html:
454                manpage_roles.append(self.man_page_path)
455        else:
456            if self.html:
457                manpage_domains.append(self.man_page_path)
458            self.__gen_man_page()
459        self.fd.close()
460
461        for k in equiv_dict.keys():
462            if k == self.domainname:
463                for alias in equiv_dict[k]:
464                    self.__gen_man_page_link(alias)
465
466    def _gen_bools(self):
467        self.bools = []
468        self.domainbools = []
469        types = [self.type]
470        if self.domainname in equiv_dict:
471            for t in equiv_dict[self.domainname]:
472                if t + "_t" in self.all_domains:
473                    types.append(t + "_t")
474
475        for t in types:
476            domainbools, bools = sepolicy.get_bools(t)
477            self.bools += bools
478            self.domainbools += domainbools
479
480        self.bools.sort()
481        self.domainbools.sort()
482
483    def get_man_page_path(self):
484        return self.man_page_path
485
486    def __gen_user_man_page(self):
487        self.role = self.domainname + "_r"
488        if not self.modules_dict:
489            self.modules_dict = gen_modules_dict(self.xmlpath)
490
491        try:
492            self.desc = self.modules_dict[self.domainname]
493        except:
494            self.desc = "%s user role" % self.domainname
495
496        if self.domainname in self.all_users:
497            self.attributes = next(sepolicy.info(sepolicy.TYPE, (self.type)))["attributes"]
498            self._user_header()
499            self._user_attribute()
500            self._can_sudo()
501            self._xwindows_login()
502            # until a new policy build with login_userdomain attribute
503        #self.terminal_login()
504            self._network()
505            self._booleans()
506            self._home_exec()
507            self._transitions()
508        else:
509            self._role_header()
510            self._booleans()
511
512        self._port_types()
513        self._mcs_types()
514        self._writes()
515        self._footer()
516
517    def __gen_man_page_link(self, alias):
518        path = "%s/%s_selinux.8" % (self.path, alias)
519        self.fd = open("%s/%s_selinux.8" % (self.path, alias), 'w')
520        self.fd.write(".so man8/%s_selinux.8" % self.domainname)
521        self.fd.close()
522        print(path)
523
524    def __gen_man_page(self):
525        self.anon_list = []
526
527        self.attributes = {}
528        self.ptypes = []
529        self._get_ptypes()
530
531        for domain_type in self.ptypes:
532            try:
533                if typealias_types[domain_type]:
534                    fd = self.fd
535                    man_page_path =  self.man_page_path
536                    for t in typealias_types[domain_type]:
537                        self._typealias_gen_man(t)
538                    self.fd = fd
539                    self.man_page_path = man_page_path
540            except KeyError:
541                continue
542            self.attributes[domain_type] = next(sepolicy.info(sepolicy.TYPE, ("%s") % domain_type))["attributes"]
543
544        self._header()
545        self._entrypoints()
546        self._process_types()
547        self._mcs_types()
548        self._booleans()
549        self._nsswitch_domain()
550        self._port_types()
551        self._writes()
552        self._file_context()
553        self._public_content()
554        self._footer()
555
556    def _get_ptypes(self):
557        for f in self.all_domains:
558            if f.startswith(self.short_name) or f.startswith(self.domainname):
559                self.ptypes.append(f)
560
561    def _typealias_gen_man(self, t):
562        self.man_page_path = "%s/%s_selinux.8" % (self.path, t[:-2])
563        self.ports = []
564        self.booltext = ""
565        self.fd = open(self.man_page_path, 'w')
566        self._typealias(t[:-2])
567        self._footer()
568        self.fd.close()
569
570    def _typealias(self,typealias):
571        self.fd.write('.TH  "%(typealias)s_selinux"  "8"  "%(date)s" "%(typealias)s" "SELinux Policy %(typealias)s"'
572                 % {'typealias':typealias, 'date': time.strftime("%y-%m-%d")})
573        self.fd.write(r"""
574.SH "NAME"
575%(typealias)s_selinux \- Security Enhanced Linux Policy for the %(typealias)s processes
576.SH "DESCRIPTION"
577
578%(typealias)s_t SELinux domain type is now associated with %(domainname)s domain type (%(domainname)s_t).
579""" % {'typealias':typealias, 'domainname':self.domainname})
580
581        self.fd.write(r"""
582Please see
583
584.B %(domainname)s_selinux
585
586man page for more details.
587"""  % {'domainname':self.domainname})
588
589    def _header(self):
590        self.fd.write('.TH  "%(domainname)s_selinux"  "8"  "%(date)s" "%(domainname)s" "SELinux Policy %(domainname)s"'
591                      % {'domainname': self.domainname, 'date': time.strftime("%y-%m-%d")})
592        self.fd.write(r"""
593.SH "NAME"
594%(domainname)s_selinux \- Security Enhanced Linux Policy for the %(domainname)s processes
595.SH "DESCRIPTION"
596
597Security-Enhanced Linux secures the %(domainname)s processes via flexible mandatory access control.
598
599The %(domainname)s processes execute with the %(domainname)s_t SELinux type. You can check if you have these processes running by executing the \fBps\fP command with the \fB\-Z\fP qualifier.
600
601For example:
602
603.B ps -eZ | grep %(domainname)s_t
604
605""" % {'domainname': self.domainname})
606
607    def _format_boolean_desc(self, b):
608        desc = self.booleans_dict[b][2][0].lower() + self.booleans_dict[b][2][1:]
609        if desc[-1] == ".":
610            desc = desc[:-1]
611        return desc
612
613    def _gen_bool_text(self):
614        booltext = ""
615        for b, enabled in self.domainbools + self.bools:
616            if b.endswith("anon_write") and b not in self.anon_list:
617                self.anon_list.append(b)
618            else:
619                if b not in self.booleans_dict:
620                    continue
621                booltext += """
622.PP
623If you want to %s, you must turn on the %s boolean. %s by default.
624
625.EX
626.B setsebool -P %s 1
627
628.EE
629""" % (self._format_boolean_desc(b), b, self.enabled_str[enabled], b)
630        return booltext
631
632    def _booleans(self):
633        self.booltext = self._gen_bool_text()
634
635        if self.booltext != "":
636            self.fd.write("""
637.SH BOOLEANS
638SELinux policy is customizable based on least access required.  %s policy is extremely flexible and has several booleans that allow you to manipulate the policy and run %s with the tightest access possible.
639
640""" % (self.domainname, self.domainname))
641
642            self.fd.write(self.booltext)
643
644    def _nsswitch_domain(self):
645        nsswitch_types = []
646        nsswitch_booleans = ['authlogin_nsswitch_use_ldap', 'kerberos_enabled']
647        nsswitchbooltext = ""
648        for k in self.attributes.keys():
649            if "nsswitch_domain" in self.attributes[k]:
650                nsswitch_types.append(k)
651
652        if len(nsswitch_types):
653            self.fd.write("""
654.SH NSSWITCH DOMAIN
655""")
656            for b in nsswitch_booleans:
657                nsswitchbooltext += """
658.PP
659If you want to %s for the %s, you must turn on the %s boolean.
660
661.EX
662.B setsebool -P %s 1
663.EE
664""" % (self._format_boolean_desc(b), (", ".join(nsswitch_types)), b, b)
665
666        self.fd.write(nsswitchbooltext)
667
668    def _process_types(self):
669        if len(self.ptypes) == 0:
670            return
671        self.fd.write(r"""
672.SH PROCESS TYPES
673SELinux defines process types (domains) for each process running on the system
674.PP
675You can see the context of a process using the \fB\-Z\fP option to \fBps\bP
676.PP
677Policy governs the access confined processes have to files.
678SELinux %(domainname)s policy is very flexible allowing users to setup their %(domainname)s processes in as secure a method as possible.
679.PP
680The following process types are defined for %(domainname)s:
681""" % {'domainname': self.domainname})
682        self.fd.write("""
683.EX
684.B %s
685.EE""" % ", ".join(self.ptypes))
686        self.fd.write("""
687.PP
688Note:
689.B semanage permissive -a %(domainname)s_t
690can be used to make the process type %(domainname)s_t permissive. SELinux does not deny access to permissive process types, but the AVC (SELinux denials) messages are still generated.
691""" % {'domainname': self.domainname})
692
693    def _port_types(self):
694        self.ports = []
695        for f in self.all_port_types:
696            if f.startswith(self.short_name) or f.startswith(self.domainname):
697                self.ports.append(f)
698
699        if len(self.ports) == 0:
700            return
701        self.fd.write("""
702.SH PORT TYPES
703SELinux defines port types to represent TCP and UDP ports.
704.PP
705You can see the types associated with a port by using the following command:
706
707.B semanage port -l
708
709.PP
710Policy governs the access confined processes have to these ports.
711SELinux %(domainname)s policy is very flexible allowing users to setup their %(domainname)s processes in as secure a method as possible.
712.PP
713The following port types are defined for %(domainname)s:""" % {'domainname': self.domainname})
714
715        for p in self.ports:
716            self.fd.write("""
717
718.EX
719.TP 5
720.B %s
721.TP 10
722.EE
723""" % p)
724            once = True
725            for prot in ("tcp", "udp"):
726                if (p, prot) in self.portrecs:
727                    if once:
728                        self.fd.write("""
729
730Default Defined Ports:""")
731                    once = False
732                    self.fd.write(r"""
733%s %s
734.EE""" % (prot, ",".join(self.portrecs[(p, prot)])))
735
736    def _file_context(self):
737        flist = []
738        mpaths = []
739        for f in self.all_file_types:
740            if f.startswith(self.domainname):
741                flist.append(f)
742                if f in self.fcdict:
743                    mpaths = mpaths + self.fcdict[f]["regex"]
744        if len(mpaths) == 0:
745            return
746        mpaths.sort()
747        mdirs = {}
748        for mp in mpaths:
749            found = False
750            for md in mdirs:
751                if mp.startswith(md):
752                    mdirs[md].append(mp)
753                    found = True
754                    break
755            if not found:
756                for e in equiv_dirs:
757                    if mp.startswith(e) and mp.endswith('(/.*)?'):
758                        mdirs[mp[:-6]] = []
759                        break
760
761        equiv = []
762        for m in mdirs:
763            if len(mdirs[m]) > 0:
764                equiv.append(m)
765
766        self.fd.write(r"""
767.SH FILE CONTEXTS
768SELinux requires files to have an extended attribute to define the file type.
769.PP
770You can see the context of a file using the \fB\-Z\fP option to \fBls\bP
771.PP
772Policy governs the access confined processes have to these files.
773SELinux %(domainname)s policy is very flexible allowing users to setup their %(domainname)s processes in as secure a method as possible.
774.PP
775""" % {'domainname': self.domainname})
776
777        if len(equiv) > 0:
778            self.fd.write(r"""
779.PP
780.B EQUIVALENCE DIRECTORIES
781""")
782            for e in equiv:
783                self.fd.write(r"""
784.PP
785%(domainname)s policy stores data with multiple different file context types under the %(equiv)s directory.  If you would like to store the data in a different directory you can use the semanage command to create an equivalence mapping.  If you wanted to store this data under the /srv dirctory you would execute the following command:
786.PP
787.B semanage fcontext -a -e %(equiv)s /srv/%(alt)s
788.br
789.B restorecon -R -v /srv/%(alt)s
790.PP
791""" % {'domainname': self.domainname, 'equiv': e, 'alt': e.split('/')[-1]})
792
793        self.fd.write(r"""
794.PP
795.B STANDARD FILE CONTEXT
796
797SELinux defines the file context types for the %(domainname)s, if you wanted to
798store files with these types in a diffent paths, you need to execute the semanage command to sepecify alternate labeling and then use restorecon to put the labels on disk.
799
800.B semanage fcontext -a -t %(type)s '/srv/%(domainname)s/content(/.*)?'
801.br
802.B restorecon -R -v /srv/my%(domainname)s_content
803
804Note: SELinux often uses regular expressions to specify labels that match multiple files.
805""" % {'domainname': self.domainname, "type": flist[0]})
806
807        self.fd.write(r"""
808.I The following file types are defined for %(domainname)s:
809""" % {'domainname': self.domainname})
810        flist.sort()
811        for f in flist:
812            self.fd.write("""
813
814.EX
815.PP
816.B %s
817.EE
818
819- %s
820""" % (f, sepolicy.get_description(f)))
821
822            if f in self.fcdict:
823                plural = ""
824                if len(self.fcdict[f]["regex"]) > 1:
825                    plural = "s"
826                    self.fd.write("""
827.br
828.TP 5
829Path%s:
830%s""" % (plural, self.fcdict[f]["regex"][0]))
831                    for x in self.fcdict[f]["regex"][1:]:
832                        self.fd.write(", %s" % x)
833
834        self.fd.write("""
835
836.PP
837Note: File context can be temporarily modified with the chcon command.  If you want to permanently change the file context you need to use the
838.B semanage fcontext
839command.  This will modify the SELinux labeling database.  You will need to use
840.B restorecon
841to apply the labels.
842""")
843
844    def _see_also(self):
845        ret = ""
846        for d in self.domains:
847            if d == self.domainname:
848                continue
849            if d.startswith(self.short_name):
850                ret += ", %s_selinux(8)" % d
851            if d.startswith(self.domainname + "_"):
852                ret += ", %s_selinux(8)" % d
853        self.fd.write(ret)
854
855    def _public_content(self):
856        if len(self.anon_list) > 0:
857            self.fd.write("""
858.SH SHARING FILES
859If you want to share files with multiple domains (Apache, FTP, rsync, Samba), you can set a file context of public_content_t and public_content_rw_t.  These context allow any of the above domains to read the content.  If you want a particular domain to write to the public_content_rw_t domain, you must set the appropriate boolean.
860.TP
861Allow %(domainname)s servers to read the /var/%(domainname)s directory by adding the public_content_t file type to the directory and by restoring the file type.
862.PP
863.B
864semanage fcontext -a -t public_content_t "/var/%(domainname)s(/.*)?"
865.br
866.B restorecon -F -R -v /var/%(domainname)s
867.pp
868.TP
869Allow %(domainname)s servers to read and write /var/%(domainname)s/incoming by adding the public_content_rw_t type to the directory and by restoring the file type.  You also need to turn on the %(domainname)s_anon_write boolean.
870.PP
871.B
872semanage fcontext -a -t public_content_rw_t "/var/%(domainname)s/incoming(/.*)?"
873.br
874.B restorecon -F -R -v /var/%(domainname)s/incoming
875.br
876.B setsebool -P %(domainname)s_anon_write 1
877""" % {'domainname': self.domainname})
878            for b in self.anon_list:
879                desc = self.booleans_dict[b][2][0].lower() + self.booleans_dict[b][2][1:]
880                self.fd.write("""
881.PP
882If you want to %s, you must turn on the %s boolean.
883
884.EX
885.B setsebool -P %s 1
886.EE
887""" % (desc, b, b))
888
889    def _footer(self):
890        self.fd.write("""
891.SH "COMMANDS"
892.B semanage fcontext
893can also be used to manipulate default file context mappings.
894.PP
895.B semanage permissive
896can also be used to manipulate whether or not a process type is permissive.
897.PP
898.B semanage module
899can also be used to enable/disable/install/remove policy modules.
900""")
901
902        if len(self.ports) > 0:
903            self.fd.write("""
904.B semanage port
905can also be used to manipulate the port definitions
906""")
907
908        if self.booltext != "":
909            self.fd.write("""
910.B semanage boolean
911can also be used to manipulate the booleans
912""")
913
914        self.fd.write("""
915.PP
916.B system-config-selinux
917is a GUI tool available to customize SELinux policy settings.
918
919.SH AUTHOR
920This manual page was auto-generated using
921.B "sepolicy manpage".
922
923.SH "SEE ALSO"
924selinux(8), %s(8), semanage(8), restorecon(8), chcon(1), sepolicy(8)""" % (self.domainname))
925
926        if self.booltext != "":
927            self.fd.write(", setsebool(8)")
928
929        self._see_also()
930
931    def _valid_write(self, check, attributes):
932        if check in [self.type, "domain"]:
933            return False
934        if check.endswith("_t"):
935            for a in attributes:
936                if a in self.types[check]:
937                    return False
938        return True
939
940    def _entrypoints(self):
941        entrypoints = [x['target'] for x in filter(lambda y:
942            y['source'] == self.type and y['class'] == 'file' and 'entrypoint' in y['permlist'],
943            sepolicy.get_all_allow_rules()
944        )]
945
946        if len(entrypoints) == 0:
947            return
948
949        self.fd.write("""
950.SH "ENTRYPOINTS"
951""")
952        if len(entrypoints) > 1:
953            entrypoints_str = "\\fB%s\\fP file types" % ", ".join(entrypoints)
954        else:
955            entrypoints_str = "\\fB%s\\fP file type" % entrypoints[0]
956
957        self.fd.write("""
958The %s_t SELinux type can be entered via the %s.
959
960The default entrypoint paths for the %s_t domain are the following:
961""" % (self.domainname, entrypoints_str, self.domainname))
962        if "bin_t" in entrypoints:
963            entrypoints.remove("bin_t")
964            self.fd.write("""
965All executeables with the default executable label, usually stored in /usr/bin and /usr/sbin.""")
966
967        paths = []
968        for entrypoint in entrypoints:
969            if entrypoint in self.fcdict:
970                paths += self.fcdict[entrypoint]["regex"]
971
972        self.fd.write("""
973%s""" % ", ".join(paths))
974
975    def _mcs_types(self):
976        try:
977            mcs_constrained_type = next(sepolicy.info(sepolicy.ATTRIBUTE, "mcs_constrained_type"))
978        except StopIteration:
979            return
980        if self.type not in mcs_constrained_type['types']:
981            return
982        self.fd.write ("""
983.SH "MCS Constrained"
984The SELinux process type %(type)s_t is an MCS (Multi Category Security) constrained type.  Sometimes this separation is referred to as sVirt. These types are usually used for securing multi-tenant environments, such as virtualization, containers or separation of users.  The tools used to launch MCS types, pick out a different MCS label for each process group.
985
986For example one process might be launched with %(type)s_t:s0:c1,c2, and another process launched with %(type)s_t:s0:c3,c4. The SELinux kernel only allows these processes can only write to content with a matching MCS label, or a MCS Label of s0. A process running with the MCS level of s0:c1,c2 is not allowed to write to content with the MCS label of s0:c3,c4
987""" % {'type': self.domainname})
988
989    def _writes(self):
990        # add assigned attributes
991        src_list = [self.type]
992        try:
993            src_list += list(filter(lambda x: x['name'] == self.type, sepolicy.get_all_types_info()))[0]['attributes']
994        except:
995            pass
996
997        permlist = list(filter(lambda x:
998            x['source'] in src_list and
999            set(['open', 'write']).issubset(x['permlist']) and
1000            x['class'] == 'file',
1001            sepolicy.get_all_allow_rules()))
1002        if permlist is None or len(permlist) == 0:
1003            return
1004
1005        all_writes = []
1006        attributes = ["proc_type", "sysctl_type"]
1007
1008        for i in permlist:
1009            if self._valid_write(i['target'], attributes):
1010                if i['target'] not in all_writes:
1011                    all_writes.append(i['target'])
1012
1013        if len(all_writes) == 0:
1014            return
1015        self.fd.write("""
1016.SH "MANAGED FILES"
1017""")
1018        self.fd.write("""
1019The SELinux process type %s_t can manage files labeled with the following file types.  The paths listed are the default paths for these file types.  Note the processes UID still need to have DAC permissions.
1020""" % self.domainname)
1021
1022        all_writes.sort()
1023        if "file_type" in all_writes:
1024            all_writes = ["file_type"]
1025        for f in all_writes:
1026            self.fd.write("""
1027.br
1028.B %s
1029
1030""" % f)
1031            if f in self.fcdict:
1032                for path in self.fcdict[f]["regex"]:
1033                    self.fd.write("""\t%s
1034.br
1035""" % path)
1036
1037    def _get_users_range(self):
1038        if self.domainname in self.all_users_range:
1039            return self.all_users_range[self.domainname]
1040        return "s0"
1041
1042    def _user_header(self):
1043        self.fd.write('.TH  "%(type)s_selinux"  "8"  "%(type)s" "mgrepl@redhat.com" "%(type)s SELinux Policy documentation"'
1044                      % {'type': self.domainname})
1045
1046        self.fd.write(r"""
1047.SH "NAME"
1048%(user)s_u \- \fB%(desc)s\fP - Security Enhanced Linux Policy
1049
1050.SH DESCRIPTION
1051
1052\fB%(user)s_u\fP is an SELinux User defined in the SELinux
1053policy. SELinux users have default roles, \fB%(user)s_r\fP.  The
1054default role has a default type, \fB%(user)s_t\fP, associated with it.
1055
1056The SELinux user will usually login to a system with a context that looks like:
1057
1058.B %(user)s_u:%(user)s_r:%(user)s_t:%(range)s
1059
1060Linux users are automatically assigned an SELinux users at login.
1061Login programs use the SELinux User to assign initial context to the user's shell.
1062
1063SELinux policy uses the context to control the user's access.
1064
1065By default all users are assigned to the SELinux user via the \fB__default__\fP flag
1066
1067On Targeted policy systems the \fB__default__\fP user is assigned to the \fBunconfined_u\fP SELinux user.
1068
1069You can list all Linux User to SELinux user mapping using:
1070
1071.B semanage login -l
1072
1073If you wanted to change the default user mapping to use the %(user)s_u user, you would execute:
1074
1075.B semanage login -m -s %(user)s_u __default__
1076
1077""" % {'desc': self.desc, 'type': self.type, 'user': self.domainname, 'range': self._get_users_range()})
1078
1079        if "login_userdomain" in self.attributes and "login_userdomain" in self.all_attributes:
1080            self.fd.write("""
1081If you want to map the one Linux user (joe) to the SELinux user %(user)s, you would execute:
1082
1083.B $ semanage login -a -s %(user)s_u joe
1084
1085""" % {'user': self.domainname})
1086
1087    def _can_sudo(self):
1088        sudotype = "%s_sudo_t" % self.domainname
1089        self.fd.write("""
1090.SH SUDO
1091""")
1092        if sudotype in self.types:
1093            role = self.domainname + "_r"
1094            self.fd.write("""
1095The SELinux user %(user)s can execute sudo.
1096
1097You can set up sudo to allow %(user)s to transition to an administrative domain:
1098
1099Add one or more of the following record to sudoers using visudo.
1100
1101""" % {'user': self.domainname})
1102            for adminrole in self.role_allows[role]:
1103                self.fd.write("""
1104USERNAME ALL=(ALL) ROLE=%(admin)s_r TYPE=%(admin)s_t COMMAND
1105.br
1106sudo will run COMMAND as %(user)s_u:%(admin)s_r:%(admin)s_t:LEVEL
1107""" % {'admin': adminrole[:-2], 'user': self.domainname})
1108
1109                self.fd.write("""
1110You might also need to add one or more of these new roles to your SELinux user record.
1111
1112List the SELinux roles your SELinux user can reach by executing:
1113
1114.B $ semanage user -l |grep selinux_name
1115
1116Modify the roles list and add %(user)s_r to this list.
1117
1118.B $ semanage user -m -R '%(roles)s' %(user)s_u
1119
1120For more details you can see semanage man page.
1121
1122""" % {'user': self.domainname, "roles": " ".join([role] + self.role_allows[role])})
1123            else:
1124                self.fd.write("""
1125The SELinux type %s_t is not allowed to execute sudo.
1126""" % self.domainname)
1127
1128    def _user_attribute(self):
1129        self.fd.write("""
1130.SH USER DESCRIPTION
1131""")
1132        if "unconfined_usertype" in self.attributes:
1133            self.fd.write("""
1134The SELinux user %s_u is an unconfined user. It means that a mapped Linux user to this SELinux user is supposed to be allow all actions.
1135""" % self.domainname)
1136
1137        if "unpriv_userdomain" in self.attributes:
1138            self.fd.write("""
1139The SELinux user %s_u is defined in policy as a unprivileged user. SELinux prevents unprivileged users from doing administration tasks without transitioning to a different role.
1140""" % self.domainname)
1141
1142        if "admindomain" in self.attributes:
1143            self.fd.write("""
1144The SELinux user %s_u is an admin user. It means that a mapped Linux user to this SELinux user is intended for administrative actions. Usually this is assigned to a root Linux user.
1145""" % self.domainname)
1146
1147    def _xwindows_login(self):
1148        if "x_domain" in self.all_attributes:
1149            self.fd.write("""
1150.SH X WINDOWS LOGIN
1151""")
1152            if "x_domain" in self.attributes:
1153                self.fd.write("""
1154The SELinux user %s_u is able to X Windows login.
1155""" % self.domainname)
1156            else:
1157                self.fd.write("""
1158The SELinux user %s_u is not able to X Windows login.
1159""" % self.domainname)
1160
1161    def _terminal_login(self):
1162        if "login_userdomain" in self.all_attributes:
1163            self.fd.write("""
1164.SH TERMINAL LOGIN
1165""")
1166            if "login_userdomain" in self.attributes:
1167                self.fd.write("""
1168The SELinux user %s_u is able to terminal login.
1169""" % self.domainname)
1170            else:
1171                self.fd.write("""
1172The SELinux user %s_u is not able to terminal login.
1173""" % self.domainname)
1174
1175    def _network(self):
1176        from sepolicy import network
1177        self.fd.write("""
1178.SH NETWORK
1179""")
1180        for net in ("tcp", "udp"):
1181            portdict = network.get_network_connect(self.type, net, "name_bind")
1182            if len(portdict) > 0:
1183                self.fd.write("""
1184.TP
1185The SELinux user %s_u is able to listen on the following %s ports.
1186""" % (self.domainname, net))
1187                for p in portdict:
1188                    for t, ports in portdict[p]:
1189                        self.fd.write("""
1190.B %s
1191""" % ",".join(ports))
1192            portdict = network.get_network_connect(self.type, "tcp", "name_connect")
1193            if len(portdict) > 0:
1194                self.fd.write("""
1195.TP
1196The SELinux user %s_u is able to connect to the following tcp ports.
1197""" % (self.domainname))
1198                for p in portdict:
1199                    for t, ports in portdict[p]:
1200                        self.fd.write("""
1201.B %s
1202""" % ",".join(ports))
1203
1204    def _home_exec(self):
1205        permlist = list(filter(lambda x:
1206            x['source'] == self.type and
1207            x['target'] == 'user_home_type' and
1208            x['class'] == 'file' and
1209            set(['ioctl', 'read', 'getattr', 'execute', 'execute_no_trans', 'open']).issubset(set(x['permlist'])),
1210            sepolicy.get_all_allow_rules()))
1211        self.fd.write("""
1212.SH HOME_EXEC
1213""")
1214        if permlist is not None:
1215            self.fd.write("""
1216The SELinux user %s_u is able execute home content files.
1217""" % self.domainname)
1218
1219        else:
1220            self.fd.write("""
1221The SELinux user %s_u is not able execute home content files.
1222""" % self.domainname)
1223
1224    def _transitions(self):
1225        self.fd.write(r"""
1226.SH TRANSITIONS
1227
1228Three things can happen when %(type)s attempts to execute a program.
1229
1230\fB1.\fP SELinux Policy can deny %(type)s from executing the program.
1231
1232.TP
1233
1234\fB2.\fP SELinux Policy can allow %(type)s to execute the program in the current user type.
1235
1236Execute the following to see the types that the SELinux user %(type)s can execute without transitioning:
1237
1238.B sesearch -A -s %(type)s -c file -p execute_no_trans
1239
1240.TP
1241
1242\fB3.\fP SELinux can allow %(type)s to execute the program and transition to a new type.
1243
1244Execute the following to see the types that the SELinux user %(type)s can execute and transition:
1245
1246.B $ sesearch -A -s %(type)s -c process -p transition
1247
1248""" % {'user': self.domainname, 'type': self.type})
1249
1250    def _role_header(self):
1251        self.fd.write('.TH  "%(user)s_selinux"  "8"  "%(user)s" "mgrepl@redhat.com" "%(user)s SELinux Policy documentation"'
1252                      % {'user': self.domainname})
1253
1254        self.fd.write(r"""
1255.SH "NAME"
1256%(user)s_r \- \fB%(desc)s\fP - Security Enhanced Linux Policy
1257
1258.SH DESCRIPTION
1259
1260SELinux supports Roles Based Access Control (RBAC), some Linux roles are login roles, while other roles need to be transition into.
1261
1262.I Note:
1263Examples in this man page will use the
1264.B staff_u
1265SELinux user.
1266
1267Non login roles are usually used for administrative tasks. For example, tasks that require root privileges.  Roles control which types a user can run processes with. Roles often have default types assigned to them.
1268
1269The default type for the %(user)s_r role is %(user)s_t.
1270
1271The
1272.B newrole
1273program to transition directly to this role.
1274
1275.B newrole -r %(user)s_r -t %(user)s_t
1276
1277.B sudo
1278is the preferred method to do transition from one role to another.  You setup sudo to transition to %(user)s_r by adding a similar line to the /etc/sudoers file.
1279
1280USERNAME ALL=(ALL) ROLE=%(user)s_r TYPE=%(user)s_t COMMAND
1281
1282.br
1283sudo will run COMMAND as staff_u:%(user)s_r:%(user)s_t:LEVEL
1284
1285When using a non login role, you need to setup SELinux so that your SELinux user can reach %(user)s_r role.
1286
1287Execute the following to see all of the assigned SELinux roles:
1288
1289.B semanage user -l
1290
1291You need to add %(user)s_r to the staff_u user.  You could setup the staff_u user to be able to use the %(user)s_r role with a command like:
1292
1293.B $ semanage user -m -R 'staff_r system_r %(user)s_r' staff_u
1294
1295""" % {'desc': self.desc, 'user': self.domainname})
1296        troles = []
1297        for i in self.role_allows:
1298            if self.domainname + "_r" in self.role_allows[i]:
1299                troles.append(i)
1300        if len(troles) > 0:
1301            plural = ""
1302            if len(troles) > 1:
1303                plural = "s"
1304
1305                self.fd.write("""
1306
1307SELinux policy also controls which roles can transition to a different role.
1308You can list these rules using the following command.
1309
1310.B search --role_allow
1311
1312SELinux policy allows the %s role%s can transition to the %s_r role.
1313
1314""" % (", ".join(troles), plural, self.domainname))
1315