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