#!/usr/bin/python3
#
# LibreOffice and VLC macros blocker
#

import os
import sys
import pwd
import grp
import shutil
import subprocess
import xml.etree.ElementTree as ET

# FILES structure: use, mode, file name, param1, param2, param3
# use :  0 - skip file, 1 - proceed with file
# mode : 1 - change file permissions for LibreOffice; 2 - modify menubar.xml file;
#        3 - move file; 4 - change file permissions for VLC
FILES = (
    [1, 1, '/share/config/soffice.cfg/uui/ui/macrowarnmedium.ui', '000', 'root', 'root'],
    [1, 1, '/share/config/soffice.cfg/xmlsec/ui/macrosecuritydialog.ui', '644', 'root', 'root'],
    [1, 1, '/share/config/soffice.cfg/cui/ui/macroselectordialog.ui', '000', 'root', 'root'],
    [1, 1, '/share/config/soffice.cfg/cui/ui/macroassigndialog.ui', '000', 'root', 'root'],
    [1, 1, '/share/config/soffice.cfg/cui/ui/macroassignpage.ui', '000', 'root', 'root'],
    [1, 1, '/share/config/soffice.cfg/modules/BasicIDE', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/StartModule/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/dbapp/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/dbquery/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/dbrelation/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/dbreport/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/dbtable/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/dbtdata/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/sbibliography/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/scalc/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/schart/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/sdraw/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/sglobal/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/simpress/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/smath/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/sweb/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/swform/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/swreport/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/swriter/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 2, '/share/config/soffice.cfg/modules/swxform/menubar/menubar.xml', '000', 'root', 'root'],
    [1, 3, '/program/services/scriptproviderforpython.rdb', '/program/scriptproviderforpython.rdb'],
    [1, 3, '/program/services/scriptproviderforbeanshell.rdb', '/program/scriptproviderforbeanshell.rdb'],
    [1, 3, '/program/services/scriptproviderforjavascript.rdb', '/program/scriptproviderforjavascript.rdb'],
    [1, 4, '/vlc/plugins/lua/liblua_plugin.so', '000', 'root', 'root'],
)
# XML_MENU_ENTRIES tuple
# elem[0] - root of 'Tools' menu
# elem[1] - root element of 'Tools->Macros' menu entry
# elem[2..n] - menu entries to be disabled (removed) if found in current 'menubar.xml' file
XML_MENU_ENTRIES = (
    '.uno:ToolsMenu',
    '.uno:MacrosMenu',
    '.uno:RunMacro',
    '.uno:ScriptOrganizer',
    '.uno:MacroOrganizer?TabId:short=1',
    '.uno:MacroRecorder',
    '.uno:RunMacro',
    '.uno:BasicIDEAppear',
    '.uno:ScriptOrganizer',
    '.uno:MacroSignature',
)
VLC_PKG_NAME = 'vlc-mini'
OFFICE_EXTENSION_FILE = 'alterator-secsetup/config_level_macros.oxt'
OFFICE_PACKAGES = ('LibreOffice-common', 'LibreOffice-still-common')

office_pkg_name = ''
office_ext_file = ''
office_path = ''

def set_files_paths():
    """ Set absolute paths for files to be processed.
    Exits with error code 1 if no LibreOffcie package
    or installation directory found in system.
    """

    global office_pkg_name
    global office_ext_file
    global office_path
    libdir = ''
    office_name = ''

    sp = subprocess.Popen('rpm --eval="%_libdir"', shell=True,
                            stdout=subprocess.PIPE, encoding='utf-8')
    libdir = sp.stdout.read().strip()

    sp = subprocess.Popen('rpm --eval="%_datadir"', shell=True,
                            stdout=subprocess.PIPE, encoding='utf-8')
    datadir = sp.stdout.read().strip()

    office_ext_file = os.path.join(datadir, OFFICE_EXTENSION_FILE)

    if not os.path.isfile(office_ext_file):
        print("LibreOffice extension not found")
        sys.exit(1)

    for pkg in OFFICE_PACKAGES:
        if (subprocess.run(['rpm', '-q', pkg], capture_output=True).returncode == 0):
            office_name = pkg.replace('-common', '')
            office_pkg_name = pkg

    if office_name == '':
        print("LibreOffcie package not installed")
        sys.exit(1)

    office_path = os.path.join(libdir, office_name)

    if not os.path.isdir(office_path):
        print("LibreOffice directory location not found")
        sys.exit(1)

    # store absolute file paths
    for file in FILES:
        if file[0] == 1:
            if file[1] == 1 or file[1] == 2:
                file[2] = office_path + file[2]
            elif file[1] == 3:
                file[2] = office_path + file[2]
                file[3] = office_path + file[3]
            elif file[1] == 4:
                file[2] = libdir + file[2]

def rpm_query_file_attrs(pkg_name):
    """ Query files permission, user and group for
    given package name from rpm utility.

    Parameters
    ----------
    pkg_name : str
        Package name

    Returns
    -------
    dict
        Dictionary with file name as {key} and
        permissions, user, group tuple as {value}
    """

    pkg_files = {}
    sp = subprocess.Popen(f'rpm -ql --dump {pkg_name}', shell=True,
                            stdout=subprocess.PIPE, encoding='utf-8')
    res = sp.stdout.readlines()

    for line in res:
        tmp = line.strip().split()
        pkg_files[tmp[0]] = (tmp[4][-3:], tmp[5], tmp[6])

    return pkg_files

def xml_menu_file_modify(fname, entries):
    """ Find and remove specified entries in xml menu file.

    Parameters
    ----------
    fname : str
        xml filename
    entries : tuple
        list of {id} attribute values for xml elements
        to be deleted

    Returns
    -------
    xml_str : str
        string representation of xml
    """

    ns = {'menu': 'http://openoffice.org/2001/menu'}
    ns_uri_str = '{' + ns['menu'] + '}'

    tree = ET.parse(fname)

    for prefix, uri in ns.items():
        ET.register_namespace(prefix, uri)

    root = tree.getroot()
    t = root.find(f"menu:menu[@menu:id='{entries[0]}']", ns)
    if t is not None:
        for chld in list(t):
            m = chld.find(f"menu:menu[@menu:id='{entries[1]}']", ns)

        if m is not None:
            for chld in list(m):
                for node in list(chld):
                    if node.get(f"{ns_uri_str}id") in entries:
                        chld.remove(node)

    return ET.tostring(root)

def file_set_attrs(fname, perms, user, group):
    """ Set file permissions and owner.

    Parameters
    ----------
    fname : str
        file name
    perms : str
        file permissions (ex. '644')
    user  : str
        file owner
    group : str
        file group
    """
    shutil.chown(fname, user, group)
    rwx = int(perms, 8)
    os.chmod(fname, rwx)

def file_get_attrs(fname):
    """ Get file permissions and owner.

    Parameters
    ----------
    fname : str
        file name

    Returns
    -------
    attrs : tuple
        tuple contains file permissions, owner and group
    """
    st = os.stat(fname)
    return(oct(st.st_mode)[-3:],
            pwd.getpwuid(st.st_uid)[0],
            grp.getgrgid(st.st_gid)[0])


def files_modify(restore = 0):
    """ Perfom all necessary actions on files:
    cnage attrs, move and modify.

    Parameters
    ----------
    restore : int
        operation mode: 0 - modify files, 1 - restore original condition
    """

    if restore == 0:
        # change files attributes, modify and move files
        for file in FILES:
            use, mode, fname = file[0], file[1], file[2]
            if use == 1:
                if mode == 1:
                    # just change office file attrs
                    file_set_attrs(fname, file[3], file[4], file[5])
                elif mode == 2:
                    # copy original office file and modify xml
                    fname2 = fname + '_1'
                    if os.path.isfile(fname) and not os.path.isfile(fname2):
                        res_xml = xml_menu_file_modify(fname, XML_MENU_ENTRIES)
                        old_attrs = file_get_attrs(fname)
                        shutil.move(fname, fname2)
                        file_set_attrs(fname2, file[3], file[4], file[5])
                        with open(fname, 'wb') as f:
                            f.write(res_xml)
                        file_set_attrs(fname, *old_attrs)
                elif mode == 3:
                    # move office file
                    fname2 = file[3]
                    if os.path.isfile(fname):
                        shutil.move(fname, fname2)
                elif mode == 4:
                    # change vlc file attrs
                    if os.path.isfile(fname):
                        file_set_attrs(fname, file[3], file[4], file[5])
    else:
        # restore original files with permissions
        rpm_pkg_files_office = rpm_query_file_attrs(office_pkg_name)
        rpm_pkg_files_vlc = rpm_query_file_attrs(VLC_PKG_NAME)
        for file in FILES:
            use, mode, fname = file[0], file[1], file[2]
            if use == 1:
                if mode == 1:
                    # get initial file attrs from rpm
                    if not fname in rpm_pkg_files_office:
                        continue
                    else:
                        file_set_attrs(fname, *rpm_pkg_files_office[fname])
                elif mode == 2:
                    # remove modified office xml files and restore original
                    fname2 = fname + '_1'
                    if os.path.isfile(fname):
                        if os.path.isfile(fname2):
                            os.remove(fname)
                            shutil.move(fname2, fname)
                        if not fname in rpm_pkg_files_office:
                            continue
                        else:
                            file_set_attrs(fname, *rpm_pkg_files_office[fname])
                elif mode == 3:
                    # move office file
                    fname2 = file[3]
                    if os.path.isfile(fname2):
                        shutil.move(fname2, fname)
                elif mode == 4:
                    # change vlc file attrs
                    if not fname in rpm_pkg_files_vlc:
                        continue
                    else:
                        file_set_attrs(fname, *rpm_pkg_files_vlc[fname])

def install_office_extension(remove = 0):
    """ Install or remove {config_level_macros.oxt} LibreOffice extension
    for all system users with unopkg utility.

    Parameters
    ----------
    remove : int
        operation mode: 0 - install extension, 1 - remove extension
    """

    unopkg_bin = os.path.join(office_path, 'program/unopkg')

    if remove == 0:
        # install LibreOffice extention
        subprocess.run([unopkg_bin, 'add', '-f', '-s', '--shared',
                        office_ext_file], capture_output=True)
    else:
        #remove LibreOffice extention
        office_ext_fname = os.path.basename(office_ext_file)
        subprocess.run([unopkg_bin, 'remove', '-f', '-s', '--shared',
                        office_ext_fname], capture_output=True)

if len(sys.argv) > 1:
    # process packages and paths
    set_files_paths()
    # process args
    if sys.argv[1] == 'enable':
        # block macros
        files_modify(0)
        install_office_extension(0)
    elif sys.argv[1] == 'disable':
        # unblock macros
        files_modify(1)
        install_office_extension(1)
    else:
        print("Option not recognized\nUsage: secsetup-macrosblocker <enable|disable>")
        sys.exit(0)
else:
    print("Usage: secsetup-macrosblocker <enable|disable>")
    sys.exit(0)
