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