• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#! /usr/bin/python -Es
2# Copyright (C) 2012-2013 Red Hat
3# AUTHOR: Dan Walsh <dwalsh@redhat.com>
4# AUTHOR: Miroslav Grepl <mgrepl@redhat.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__all__ = ['ManPage', 'HTMLManPages', 'manpage_domains', 'manpage_roles', 'gen_domains']
26
27import string
28import selinux
29import sepolicy
30import os
31import time
32
33typealias_types = {
34"antivirus_t":("amavis_t", "clamd_t", "clamscan_t", "freshclam_t"),
35"cluster_t":("rgmanager_t", "corosync_t", "aisexec_t", "pacemaker_t"),
36"svirt_t":("qemu_t"),
37"httpd_t":("phpfpm_t"),
38}
39
40equiv_dict = {"smbd": ["samba"], "httpd": ["apache"], "virtd": ["virt", "libvirt"], "named": ["bind"], "fsdaemon": ["smartmon"], "mdadm": ["raid"]}
41
42equiv_dirs = ["/var"]
43modules_dict = None
44
45
46def gen_modules_dict(path="/usr/share/selinux/devel/policy.xml"):
47    global modules_dict
48    if modules_dict:
49        return modules_dict
50
51    import xml.etree.ElementTree
52    modules_dict = {}
53    try:
54        tree = xml.etree.ElementTree.fromstring(sepolicy.policy_xml(path))
55        for l in tree.findall("layer"):
56            for m in l.findall("module"):
57                name = m.get("name")
58                if name == "user" or name == "unconfined":
59                    continue
60                if name == "unprivuser":
61                    name = "user"
62                if name == "unconfineduser":
63                    name = "unconfined"
64                for b in m.findall("summary"):
65                    modules_dict[name] = b.text
66    except IOError:
67        pass
68    return modules_dict
69
70users = None
71users_range = None
72
73
74def get_all_users_info():
75    global users
76    global users_range
77    if users and users_range:
78        return users, users_range
79
80    users = []
81    users_range = {}
82    allusers = []
83    allusers_info = sepolicy.info(sepolicy.USER)
84
85    for d in allusers_info:
86        allusers.append(d['name'])
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 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        for f in flist:
811            self.fd.write("""
812
813.EX
814.PP
815.B %s
816.EE
817
818- %s
819""" % (f, sepolicy.get_description(f)))
820
821            if f in self.fcdict:
822                plural = ""
823                if len(self.fcdict[f]["regex"]) > 1:
824                    plural = "s"
825                    self.fd.write("""
826.br
827.TP 5
828Path%s:
829%s""" % (plural, self.fcdict[f]["regex"][0]))
830                    for x in self.fcdict[f]["regex"][1:]:
831                        self.fd.write(", %s" % x)
832
833        self.fd.write("""
834
835.PP
836Note: File context can be temporarily modified with the chcon command.  If you want to permanently change the file context you need to use the
837.B semanage fcontext
838command.  This will modify the SELinux labeling database.  You will need to use
839.B restorecon
840to apply the labels.
841""")
842
843    def _see_also(self):
844        ret = ""
845        for d in self.domains:
846            if d == self.domainname:
847                continue
848            if d.startswith(self.short_name):
849                ret += ", %s_selinux(8)" % d
850            if d.startswith(self.domainname + "_"):
851                ret += ", %s_selinux(8)" % d
852        self.fd.write(ret)
853
854    def _public_content(self):
855        if len(self.anon_list) > 0:
856            self.fd.write("""
857.SH SHARING FILES
858If 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.
859.TP
860Allow %(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.
861.PP
862.B
863semanage fcontext -a -t public_content_t "/var/%(domainname)s(/.*)?"
864.br
865.B restorecon -F -R -v /var/%(domainname)s
866.pp
867.TP
868Allow %(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.
869.PP
870.B
871semanage fcontext -a -t public_content_rw_t "/var/%(domainname)s/incoming(/.*)?"
872.br
873.B restorecon -F -R -v /var/%(domainname)s/incoming
874.br
875.B setsebool -P %(domainname)s_anon_write 1
876""" % {'domainname': self.domainname})
877            for b in self.anon_list:
878                desc = self.booleans_dict[b][2][0].lower() + self.booleans_dict[b][2][1:]
879                self.fd.write("""
880.PP
881If you want to %s, you must turn on the %s boolean.
882
883.EX
884.B setsebool -P %s 1
885.EE
886""" % (desc, b, b))
887
888    def _footer(self):
889        self.fd.write("""
890.SH "COMMANDS"
891.B semanage fcontext
892can also be used to manipulate default file context mappings.
893.PP
894.B semanage permissive
895can also be used to manipulate whether or not a process type is permissive.
896.PP
897.B semanage module
898can also be used to enable/disable/install/remove policy modules.
899""")
900
901        if len(self.ports) > 0:
902            self.fd.write("""
903.B semanage port
904can also be used to manipulate the port definitions
905""")
906
907        if self.booltext != "":
908            self.fd.write("""
909.B semanage boolean
910can also be used to manipulate the booleans
911""")
912
913        self.fd.write("""
914.PP
915.B system-config-selinux
916is a GUI tool available to customize SELinux policy settings.
917
918.SH AUTHOR
919This manual page was auto-generated using
920.B "sepolicy manpage".
921
922.SH "SEE ALSO"
923selinux(8), %s(8), semanage(8), restorecon(8), chcon(1), sepolicy(8)
924""" % (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 sepolicy.search([sepolicy.ALLOW], {'source': self.type, 'permlist': ['entrypoint'], 'class': 'file'})]
942        if len(entrypoints) == 0:
943            return
944
945        self.fd.write("""
946.SH "ENTRYPOINTS"
947""")
948        if len(entrypoints) > 1:
949            entrypoints_str = "\\fB%s\\fP file types" % ", ".join(entrypoints)
950        else:
951            entrypoints_str = "\\fB%s\\fP file type" % entrypoints[0]
952
953        self.fd.write("""
954The %s_t SELinux type can be entered via the %s.
955
956The default entrypoint paths for the %s_t domain are the following:
957""" % (self.domainname, entrypoints_str, self.domainname))
958        if "bin_t" in entrypoints:
959            entrypoints.remove("bin_t")
960            self.fd.write("""
961All executeables with the default executable label, usually stored in /usr/bin and /usr/sbin.""")
962
963        paths = []
964        for entrypoint in entrypoints:
965            if entrypoint in self.fcdict:
966                paths += self.fcdict[entrypoint]["regex"]
967
968        self.fd.write("""
969%s""" % ", ".join(paths))
970
971    def _mcs_types(self):
972        mcs_constrained_type = next(sepolicy.info(sepolicy.ATTRIBUTE, "mcs_constrained_type"))
973        if self.type not in mcs_constrained_type['types']:
974            return
975        self.fd.write ("""
976.SH "MCS Constrained"
977The 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.
978
979For 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
980""" % {'type': self.domainname})
981
982    def _writes(self):
983        permlist = sepolicy.search([sepolicy.ALLOW], {'source': self.type, 'permlist': ['open', 'write'], 'class': 'file'})
984        if permlist is None or len(permlist) == 0:
985            return
986
987        all_writes = []
988        attributes = ["proc_type", "sysctl_type"]
989        for i in permlist:
990            if not i['target'].endswith("_t"):
991                attributes.append(i['target'])
992
993        for i in permlist:
994            if self._valid_write(i['target'], attributes):
995                if i['target'] not in all_writes:
996                    all_writes.append(i['target'])
997
998        if len(all_writes) == 0:
999            return
1000        self.fd.write("""
1001.SH "MANAGED FILES"
1002""")
1003        self.fd.write("""
1004The 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.
1005""" % self.domainname)
1006
1007        all_writes.sort()
1008        if "file_type" in all_writes:
1009            all_writes = ["file_type"]
1010        for f in all_writes:
1011            self.fd.write("""
1012.br
1013.B %s
1014
1015""" % f)
1016            if f in self.fcdict:
1017                for path in self.fcdict[f]["regex"]:
1018                    self.fd.write("""\t%s
1019.br
1020""" % path)
1021
1022    def _get_users_range(self):
1023        if self.domainname in self.all_users_range:
1024            return self.all_users_range[self.domainname]
1025        return "s0"
1026
1027    def _user_header(self):
1028        self.fd.write('.TH  "%(type)s_selinux"  "8"  "%(type)s" "mgrepl@redhat.com" "%(type)s SELinux Policy documentation"'
1029                      % {'type': self.domainname})
1030
1031        self.fd.write(r"""
1032.SH "NAME"
1033%(user)s_u \- \fB%(desc)s\fP - Security Enhanced Linux Policy
1034
1035.SH DESCRIPTION
1036
1037\fB%(user)s_u\fP is an SELinux User defined in the SELinux
1038policy. SELinux users have default roles, \fB%(user)s_r\fP.  The
1039default role has a default type, \fB%(user)s_t\fP, associated with it.
1040
1041The SELinux user will usually login to a system with a context that looks like:
1042
1043.B %(user)s_u:%(user)s_r:%(user)s_t:%(range)s
1044
1045Linux users are automatically assigned an SELinux users at login.
1046Login programs use the SELinux User to assign initial context to the user's shell.
1047
1048SELinux policy uses the context to control the user's access.
1049
1050By default all users are assigned to the SELinux user via the \fB__default__\fP flag
1051
1052On Targeted policy systems the \fB__default__\fP user is assigned to the \fBunconfined_u\fP SELinux user.
1053
1054You can list all Linux User to SELinux user mapping using:
1055
1056.B semanage login -l
1057
1058If you wanted to change the default user mapping to use the %(user)s_u user, you would execute:
1059
1060.B semanage login -m -s %(user)s_u __default__
1061
1062""" % {'desc': self.desc, 'type': self.type, 'user': self.domainname, 'range': self._get_users_range()})
1063
1064        if "login_userdomain" in self.attributes and "login_userdomain" in self.all_attributes:
1065            self.fd.write("""
1066If you want to map the one Linux user (joe) to the SELinux user %(user)s, you would execute:
1067
1068.B $ semanage login -a -s %(user)s_u joe
1069
1070""" % {'user': self.domainname})
1071
1072    def _can_sudo(self):
1073        sudotype = "%s_sudo_t" % self.domainname
1074        self.fd.write("""
1075.SH SUDO
1076""")
1077        if sudotype in self.types:
1078            role = self.domainname + "_r"
1079            self.fd.write("""
1080The SELinux user %(user)s can execute sudo.
1081
1082You can set up sudo to allow %(user)s to transition to an administrative domain:
1083
1084Add one or more of the following record to sudoers using visudo.
1085
1086""" % {'user': self.domainname})
1087            for adminrole in self.role_allows[role]:
1088                self.fd.write("""
1089USERNAME ALL=(ALL) ROLE=%(admin)s_r TYPE=%(admin)s_t COMMAND
1090.br
1091sudo will run COMMAND as %(user)s_u:%(admin)s_r:%(admin)s_t:LEVEL
1092""" % {'admin': adminrole[:-2], 'user': self.domainname})
1093
1094                self.fd.write("""
1095You might also need to add one or more of these new roles to your SELinux user record.
1096
1097List the SELinux roles your SELinux user can reach by executing:
1098
1099.B $ semanage user -l |grep selinux_name
1100
1101Modify the roles list and add %(user)s_r to this list.
1102
1103.B $ semanage user -m -R '%(roles)s' %(user)s_u
1104
1105For more details you can see semanage man page.
1106
1107""" % {'user': self.domainname, "roles": " ".join([role] + self.role_allows[role])})
1108            else:
1109                self.fd.write("""
1110The SELinux type %s_t is not allowed to execute sudo.
1111""" % self.domainname)
1112
1113    def _user_attribute(self):
1114        self.fd.write("""
1115.SH USER DESCRIPTION
1116""")
1117        if "unconfined_usertype" in self.attributes:
1118            self.fd.write("""
1119The 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.
1120""" % self.domainname)
1121
1122        if "unpriv_userdomain" in self.attributes:
1123            self.fd.write("""
1124The 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.
1125""" % self.domainname)
1126
1127        if "admindomain" in self.attributes:
1128            self.fd.write("""
1129The 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.
1130""" % self.domainname)
1131
1132    def _xwindows_login(self):
1133        if "x_domain" in self.all_attributes:
1134            self.fd.write("""
1135.SH X WINDOWS LOGIN
1136""")
1137            if "x_domain" in self.attributes:
1138                self.fd.write("""
1139The SELinux user %s_u is able to X Windows login.
1140""" % self.domainname)
1141            else:
1142                self.fd.write("""
1143The SELinux user %s_u is not able to X Windows login.
1144""" % self.domainname)
1145
1146    def _terminal_login(self):
1147        if "login_userdomain" in self.all_attributes:
1148            self.fd.write("""
1149.SH TERMINAL LOGIN
1150""")
1151            if "login_userdomain" in self.attributes:
1152                self.fd.write("""
1153The SELinux user %s_u is able to terminal login.
1154""" % self.domainname)
1155            else:
1156                self.fd.write("""
1157The SELinux user %s_u is not able to terminal login.
1158""" % self.domainname)
1159
1160    def _network(self):
1161        from sepolicy import network
1162        self.fd.write("""
1163.SH NETWORK
1164""")
1165        for net in ("tcp", "udp"):
1166            portdict = network.get_network_connect(self.type, net, "name_bind")
1167            if len(portdict) > 0:
1168                self.fd.write("""
1169.TP
1170The SELinux user %s_u is able to listen on the following %s ports.
1171""" % (self.domainname, net))
1172                for p in portdict:
1173                    for t, ports in portdict[p]:
1174                        self.fd.write("""
1175.B %s
1176""" % ",".join(ports))
1177            portdict = network.get_network_connect(self.type, "tcp", "name_connect")
1178            if len(portdict) > 0:
1179                self.fd.write("""
1180.TP
1181The SELinux user %s_u is able to connect to the following tcp ports.
1182""" % (self.domainname))
1183                for p in portdict:
1184                    for t, ports in portdict[p]:
1185                        self.fd.write("""
1186.B %s
1187""" % ",".join(ports))
1188
1189    def _home_exec(self):
1190        permlist = sepolicy.search([sepolicy.ALLOW], {'source': self.type, 'target': 'user_home_type', 'class': 'file', 'permlist': ['ioctl', 'read', 'getattr', 'execute', 'execute_no_trans', 'open']})
1191        self.fd.write("""
1192.SH HOME_EXEC
1193""")
1194        if permlist is not None:
1195            self.fd.write("""
1196The SELinux user %s_u is able execute home content files.
1197""" % self.domainname)
1198
1199        else:
1200            self.fd.write("""
1201The SELinux user %s_u is not able execute home content files.
1202""" % self.domainname)
1203
1204    def _transitions(self):
1205        self.fd.write(r"""
1206.SH TRANSITIONS
1207
1208Three things can happen when %(type)s attempts to execute a program.
1209
1210\fB1.\fP SELinux Policy can deny %(type)s from executing the program.
1211
1212.TP
1213
1214\fB2.\fP SELinux Policy can allow %(type)s to execute the program in the current user type.
1215
1216Execute the following to see the types that the SELinux user %(type)s can execute without transitioning:
1217
1218.B sesearch -A -s %(type)s -c file -p execute_no_trans
1219
1220.TP
1221
1222\fB3.\fP SELinux can allow %(type)s to execute the program and transition to a new type.
1223
1224Execute the following to see the types that the SELinux user %(type)s can execute and transition:
1225
1226.B $ sesearch -A -s %(type)s -c process -p transition
1227
1228""" % {'user': self.domainname, 'type': self.type})
1229
1230    def _role_header(self):
1231        self.fd.write('.TH  "%(user)s_selinux"  "8"  "%(user)s" "mgrepl@redhat.com" "%(user)s SELinux Policy documentation"'
1232                      % {'user': self.domainname})
1233
1234        self.fd.write(r"""
1235.SH "NAME"
1236%(user)s_r \- \fB%(desc)s\fP - Security Enhanced Linux Policy
1237
1238.SH DESCRIPTION
1239
1240SELinux supports Roles Based Access Control (RBAC), some Linux roles are login roles, while other roles need to be transition into.
1241
1242.I Note:
1243Examples in this man page will use the
1244.B staff_u
1245SELinux user.
1246
1247Non 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.
1248
1249The default type for the %(user)s_r role is %(user)s_t.
1250
1251The
1252.B newrole
1253program to transition directly to this role.
1254
1255.B newrole -r %(user)s_r -t %(user)s_t
1256
1257.B sudo
1258is 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.
1259
1260USERNAME ALL=(ALL) ROLE=%(user)s_r TYPE=%(user)s_t COMMAND
1261
1262.br
1263sudo will run COMMAND as staff_u:%(user)s_r:%(user)s_t:LEVEL
1264
1265When using a a non login role, you need to setup SELinux so that your SELinux user can reach %(user)s_r role.
1266
1267Execute the following to see all of the assigned SELinux roles:
1268
1269.B semanage user -l
1270
1271You 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:
1272
1273.B $ semanage user -m -R 'staff_r system_r %(user)s_r' staff_u
1274
1275""" % {'desc': self.desc, 'user': self.domainname})
1276        troles = []
1277        for i in self.role_allows:
1278            if self.domainname + "_r" in self.role_allows[i]:
1279                troles.append(i)
1280        if len(troles) > 0:
1281            plural = ""
1282            if len(troles) > 1:
1283                plural = "s"
1284
1285                self.fd.write("""
1286
1287SELinux policy also controls which roles can transition to a different role.
1288You can list these rules using the following command.
1289
1290.B search --role_allow
1291
1292SELinux policy allows the %s role%s can transition to the %s_r role.
1293
1294""" % (", ".join(troles), plural, self.domainname))
1295