"""
SELinux
=======

Combiner for more complex handling of SELinux being disabled by any means
available to the users. It uses results of
:class:`insights.parsers.sestatus.SEStatus`, and
:class:`insights.parsers.selinux_config.SelinuxConfig` parsers and
:class:`insights.combiners.grub_conf.GrubConf` combiner.

It contains a dictionary ``problems`` in which it stores detected problems with
keys as follows and values are parsed lines with detected problem:

* ``sestatus_disabled`` - SELinux is disabled on runtime.
* ``sestatus_not_enforcing`` - SELinux is not in enforcing mode.
* ``grub_disabled`` - SELinux is set in Grub to be disabled.
* ``grub_not_enforcing`` - SELinux is set in Grub to not be in enforcing mode.
* ``selinux_conf_disabled`` - SELinux is set in configuration file to be disabled.
* ``sestatus_not_enforcing`` - SELinux is set in configuration file to not be in enforcing mode.

Examples:
    >>> selinux = shared[SELinux]
    >>> selinux.ok()
    False
    >>> selinux.problems
    {'grub_disabled': ['/vmlinuz-2.6.32-642.el6.x86_64 selinux=0 ro root= ...'],
     'selinux_conf_disabled': 'disabled',
     'sestatus_not_enforcing': 'permissive'}
"""

from ..core.plugins import combiner
from ..parsers.sestatus import SEStatus
from ..combiners.grub_conf import GrubConf
from ..parsers.selinux_config import SelinuxConfig

GRUB_DISABLED = 'grub_disabled'
GRUB_NOT_ENFORCING = 'grub_not_enforcing'
RUNTIME_DISABLED = 'sestatus_disabled'
RUNTIME_NOT_ENFORCING = 'sestatus_not_enforcing'
BOOT_DISABLED = 'selinux_conf_disabled'
BOOT_NOT_ENFORCING = 'selinux_conf_not_enforcing'


@combiner(SEStatus, SelinuxConfig, optional=[GrubConf])
class SELinux(object):
    """
    A combiner for detecting that SELinux is enabled and running and also enabled at boot time.
    """
    def __init__(self, se_status, selinux_config, grub_conf):
        self.problems = {}
        self.sestatus = se_status
        self.selinux_config = selinux_config
        self.grub_config = grub_conf.grub if grub_conf else None

        self._check_sestatus()
        self._check_boot_config()
        self._check_grub_config()

    def _check_sestatus(self):
        """
        Check runtime SELinux configuration from sestatus output.

        Values of output from sestatus command are always lowercase.
        """
        if self.sestatus.data['selinux_status'] != 'enabled':
            self.problems[RUNTIME_DISABLED] = self.sestatus.data['selinux_status']
        elif self.sestatus.data['current_mode'] != 'enforcing':
            self.problems[RUNTIME_NOT_ENFORCING] = self.sestatus.data['current_mode']

    def _check_boot_config(self):
        """
        Check that SELinux is not disabled in /etc/sysconfig/selinux.

        This file determines the boot configuration for SELinux.
        """
        opt_value = self.selinux_config.get('SELINUX')
        if opt_value is None:
            self.problems[BOOT_NOT_ENFORCING] = 'Missing in config (Permissive by default)'
        elif opt_value == 'disabled':
            self.problems[BOOT_DISABLED] = opt_value
        elif opt_value != 'enforcing':
            self.problems[BOOT_NOT_ENFORCING] = opt_value

    def _check_grub_config(self):
        """
        Check grub and grub 2 for kernel boot options if selinux settings is not overriden.

        Experiments confirmed that only lowercase is accepted in grub configuration.

        grub is in rhel-6 and the boot line looks usually like
            kernel /boot/vmlinuz-2.4.20-selinux-2003040709 ro root=/dev/hda1 nousb selinux=0
        grub 2 is in rhel-7 and rhel-8, the boot line of grub 2 in rhel-7 looks usually like
            linux16 /vmlinuz-0-rescue-9f20b35c9faa49aebe171f62a11b236f root=/dev/mapper/rhel-root ro crashkernel=auto rd.lvm.lv=rhel/root rd.lvm.lv=rhel/swap rhgb quiet
        the boot line of grub 2 in rhel-8 looks usually like
            options root=/dev/mapper/rhel_vm37--146-root ro crashkernel=auto resume=/dev/mapper/rhel_vm37--146-swap rd.lvm.lv=rhel_vm37-146/root rd.lvm.lv=rhel_vm37-146/swap $tuned_params
        """

        conf = self.grub_config.boot_entries if self.grub_config is not None else []
        se_dis = [e.cmdline for e in conf if 'selinux=0' in e.cmdline]
        if se_dis:
            self.problems[GRUB_DISABLED] = se_dis
        se_noe = [e.cmdline for e in conf if 'enforcing=0' in e.cmdline]
        if se_noe:
            self.problems[GRUB_NOT_ENFORCING] = se_noe

    def ok(self):
        """
        Checks if there are any problems with SELinux configuration.

        Returns
            bool: True if SELinux is enabled and functional, false otherwise.
        """
        return not bool(self.problems)
