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