#! /usr/bin/python3
# ----------------------------------------------------------------------
#    Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
#    Copyright (C) 2014-2024 Christian Boltz <apparmor@cboltz.de>
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of version 2 of the GNU General Public
#    License as published by the Free Software Foundation.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
# ----------------------------------------------------------------------
import argparse

import apparmor.aa
import apparmor.cleanprofile as cleanprofile
import apparmor.ui as aaui
from apparmor.fail import enable_aa_exception_handler
from apparmor.translations import init_translation

enable_aa_exception_handler()  # setup exception handling
_ = init_translation()  # setup module translations

parser = argparse.ArgumentParser(description=_('Merge the given profiles into /etc/apparmor.d/ (or the directory specified with -d)'))
parser.add_argument('files', nargs='+', type=str, help=_('Profile(s) to merge'))
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
# parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts'))
parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS)
args = parser.parse_args()

args.other = None

apparmor.aa.init_aa(confdir=args.configdir, profiledir=args.dir)

profiles = args.files


def find_profiles_from_files(files):
    profile_to_filename = dict()
    for file_name in files:
        try:
            apparmor.aa.read_profile(file_name, True, read_error_fatal=True)
            for profile_name in apparmor.aa.active_profiles.profiles_in_file(file_name):
                profile_to_filename[profile_name] = file_name
        except IOError:
            pass
        apparmor.aa.reset_aa()

    return profile_to_filename


def find_files_from_profiles(profiles):
    profile_to_filename = dict()
    apparmor.aa.read_profiles()

    for profile_name in profiles:
        profile_to_filename[profile_name] = apparmor.aa.get_profile_filename_from_profile_name(profile_name, True)

    apparmor.aa.reset_aa()

    return profile_to_filename


def main():
    base_profile_to_file = find_profiles_from_files(profiles)

    profiles_to_merge = set(base_profile_to_file.keys())

    user_profile_to_file = find_files_from_profiles(profiles_to_merge)

    for profile_name in profiles_to_merge:
        aaui.UI_Info("\n\n" + _("Merging profile for %s" % profile_name))
        user_file = user_profile_to_file[profile_name]
        base_file = base_profile_to_file.get(profile_name, None)

        act(user_file, base_file, profile_name)

        apparmor.aa.reset_aa()


def act(user_file, base_file, merging_profile):
    mergeprofiles = Merge(user_file, base_file)
    # Get rid of common/superfluous stuff
    mergeprofiles.clear_common()

    mergeprofiles.ask_merge_questions()

    apparmor.aa.changed[merging_profile] = True  # force asking to save the profile
    apparmor.aa.save_profiles(True)


class Merge(object):
    def __init__(self, user, base):
        # Read and parse base profile and save profile data, include data from it and reset them
        apparmor.aa.read_profile(base, True)
        self.base = cleanprofile.Prof(base)

        apparmor.aa.reset_aa()

        # Read and parse user profile
        apparmor.aa.read_profile(user, True)
        self.user = cleanprofile.Prof(user)

    def clear_common(self):
        deleted = 0

        # Remove off the parts in base profile which are common/superfluous from user profile
        user_base = cleanprofile.CleanProf(False, self.user, self.base)
        deleted += user_base.compare_profiles()

    def ask_merge_questions(self):
        other = self.base
        log_dict = {'merge': other.active_profiles.get_all_profiles()}

        apparmor.aa.loadincludes()

        apparmor.aa.load_sev_db()

        # ask about preamble rules
        apparmor.aa.ask_rule_questions(
            other.active_profiles.files[other.filename],            # prof_events aka log_dict
            '[preamble]',                                           # displayed profile name
            self.user.active_profiles.files[self.user.filename],    # profile to update
            ['abi', 'inc_ie']                                       # rule types - TODO: don't hardcode
        )

        apparmor.aa.ask_the_questions(log_dict)


if __name__ == '__main__':
    main()
