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