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