#!/usr/bin/env python3
# Copyright 2026 SigmaSRC, Inc. All Rights Reserved.

"""A  command line utility to create policy benmarks from a base Policy Fuelpack.

%prog [options]
"""

import os
import optparse
import pprint
import queue as Queue
import sys

#from sigma.util import ostypes, xmltime, paramtypes, uuid
#from sigma.policies import xmldefs, widgets, treewidget, framework
#
from sigma.util import ostypes, xmltime
from sigma.policies import xmldefs

#
# the policy queue contains the policy ids to be processed.
#
# the queue entries are defined as a map consisting of
#   target_policy_id - An id from the original target policy, for policies which already
#                      defined.
#   subpolicies - array of policy ids from the base policy file.
#
policyQueue = Queue.Queue()

#
# command line option parsing definitions
#
parser = optparse.OptionParser(usage=__doc__.strip())
parser.add_option("-o", "--ostype", dest="ostype", help="ostype for the new policy file")
parser.add_option("-f", "--fuelpack", dest="fuelpack", help="name of the new Fuelpack file")
parser.add_option("-a", "--annotation", dest="annotation", help="the annotation name to use. example: CISWIN10")
parser.add_option("-p", "--policy-base-file", dest="base_policy", help="the policy file to use", default="../fuelpacks/espwindows.xml")

pp = pprint.PrettyPrinter(indent=4,depth=3)

annotation = None
fpBasePolicy = None


class FPEDocument(object):

    def __init__(self):
        """Create out document."""
        self.fp = xmldefs.new_fuelpack()

    def readFromFile(self, filename):
        try:
            f = open(filename, "r")
        except IOError as err:
            return "Can't open %s: %s" % (filename, err)
        try:
            try:
                self.fp = xmldefs.load_fuelpack(f)
                #print "Read policy file %s" % (filename)
            except Exception as err:
                return "Error reading %s: %s" % (filename, err)
        finally:
            f.close()


commonRules = FPEDocument()
targetBenchmark = FPEDocument()
originalTargetBenchmark = FPEDocument()


def main(args=None):
    opts, args = parser.parse_args(args)
    hasError = False
    if opts.ostype is None:
        print("Error: OSTYPE is missing")
        hasError = True
    if opts.fuelpack is None:
        print("Error: FUELPACK name is missing")
        hasError = True
    if opts.annotation is None:
        print("Error: ANNOTATION is missing")
        hasError = True

    # print the help options
    if hasError:
        print
        parser.print_help()
    # print opts

    #
    # now to read the policy base file
    #

    if os.path.isfile(opts.fuelpack):
        originalTargetBenchmark.readFromFile(opts.fuelpack)
        #
        # copy over the description, version, ...
        #
        # displayFP(originalTargetBenchmark.fp)
        targetBenchmark.fp.dependencies = originalTargetBenchmark.fp.dependencies
        targetBenchmark.fp.id           = originalTargetBenchmark.fp.id
        targetBenchmark.fp.modules      = originalTargetBenchmark.fp.modules
        targetBenchmark.fp.name         = originalTargetBenchmark.fp.name
        targetBenchmark.fp.parent       = originalTargetBenchmark.fp.parent
        targetBenchmark.fp.short        = originalTargetBenchmark.fp.short
        targetBenchmark.fp.timestamp    = xmltime.now()
        targetBenchmark.fp.version      = originalTargetBenchmark.fp.version + 1

    else:
        print("Target benchmark does not exist. Please create a minimal fuel pack for the benchmark.")
        
    error = commonRules.readFromFile(opts.base_policy)
    if error is not None:
        print("Unable to read base policy file ({0!s})".format(opts.base_policy))
        sys.exit()
        
    # print "\nNow the fun starts..."

    # print "\n############# Source Benchmark ######################################\n"
    # displayFP(commonRules.fp)
    # print "\n########################################################\n"
    # print "\n############# Target Benchmark ######################################\n"
    # displayFP(targetBenchmark.fp)
    # print targetBenchmark.fp

    targetBasePolicyId = None
    
    #
    # for the target fuelpack, we need to find the starting policy defined at the root
    # level, which is not the Trash folder. This is the root element policy to which the
    # policies from the base policy file are added.
    #
    for subPolicy in originalTargetBenchmark.fp._root.subpolicies:
        policy_id = subPolicy.policy_id
        if policy_id == xmldefs.TRASH_POLICY_ID:
            continue

        targetBasePolicyId = policy_id

    if targetBasePolicyId is None:
        print("Target Fuelpack is incorrectly configured. Needs to have 1 policy defined.")
        print("For example a policy named like \"CIS Windows 10 Enterprise\"")
        os._exit(-1)
    
    #
    # create a new policy from the original
    #
    targetPolicy = originalTargetBenchmark.fp.getpolicy(targetBasePolicyId)

    newPolicy = targetBenchmark.fp.newpolicy(targetPolicy.name, targetPolicy.id)
    newPolicy.documentation = targetPolicy.documentation
    newPolicy.description = targetPolicy.description
    newPolicy.notes = targetPolicy.notes

    targetBenchmark.fp.getpolicy(xmldefs.ROOT_POLICY_ID).subpolicies.append(xmldefs.Subpolicy(policy_id = newPolicy.id))

    #
    # start off processing the root definition. There should only be a single policy
    # that is not the "trash" node. This is where we want to start processing the policies
    # and rules.
    #
    # So to that end, the subpolicies defined get added to the queue for processing.
    basePolicyId = None

    for subPolicy in commonRules.fp._root.subpolicies:
        policy_id = subPolicy.policy_id
        #print "queuing policy_id: " + str(policy_id)
        if policy_id == xmldefs.TRASH_POLICY_ID:
            continue
        basePolicyId = policy_id

    if basePolicyId is None:
        print("The base policy class does not have a policy defined at the root level")
        os.exit()

    subpollist = []
    policy = commonRules.fp.policies.getpolicy(basePolicyId)
    for subPolicy in policy.subpolicies:
        subpollist.append(subPolicy.policy_id)

    entry = dict()
    entry['target_policy_id'] = targetBasePolicyId
    entry['subpolicies'] = subpollist

    policyQueue.put(entry)
    
    #
    # annotations are defined like [@xxxx, page #, section #(.x)+]
    # so we will use the annotation against the documentation string of a profile
    # to determine if the policy is applicable.
    #
    annotation = '[@' + opts.annotation
    #print "looking for annotation: " + annotation

    #
    # now to process the policy queue
    #
    while not policyQueue.empty():
        entry = policyQueue.get()

        # pp.pprint(entry)
        # print
        targetPolicyId = entry['target_policy_id']
        #
        # targetPolicy contains existing policy definition if it is not None. Otherwise, the
        # policy has to be created.
        #
        targetPolicy = targetBenchmark.fp.getpolicy(targetPolicyId)
        if targetPolicy is None:
            print("Lookup oftargetPolicy failed: " + targetBasePolicyId)
            print("Did it get created and added to the target benchmark before being added to the processing queue?")
            os.exit()

        for sub_policy_id in entry['subpolicies']:
            # get the subpolicy
            sub_policy = None  # targetBenchmark.fp.getpolicy(sub_policy_id)
            if sub_policy is None:
                sub_policy = commonRules.fp.getpolicy(sub_policy_id)
                if sub_policy is None:
                    print("error getting subpolicy " + str(sub_policy_id))
                    os.exit(-1)

            # TODO: need to check for annotation tag...
            if sub_policy.documentation is None:
                continue

            doc = sub_policy.documentation.text
            start = doc.find(annotation)
            # print "start: " + str(start)
            if start >= 0:
                doc = doc[start:]
                # print "doc: " + doc
                end = doc.find("]")
                # print end
                if end < 0:
                    print("policy annotation documentation error, no \"]\": policy id: " + sub_policy_id + " " + doc[start:])
                    os.exit(-1)
            else:
                continue

            if end >= 0:
                annot = doc[0:end]
            else:
                annot = doc
            # print "sub_poliicy name: " + sub_policy.name + " annot: " + annot
            section = annot.split(", ")[2].split(" ")[1]
            if len(section) == 1:
                section = " " + section

            policy = targetBenchmark.fp.newpolicy("%s %s" % (section, sub_policy.name), id=None)
            policy.description = sub_policy.description
            policy.documentation = sub_policy.documentation
            policy.notes = sub_policy.notes

            # print "#### annotation: " + annot + " section: " + section + " name: " + policy.name

            targetPolicy.subpolicies.append(xmldefs.Subpolicy(policy_id = policy.id))

            #
            # Note: This is the code, that needs to be changed to support deep copy of the rules.
            #       Just need to create a template, and copy the template from the source
            if sub_policy.rules is not None and len(sub_policy.rules) > 0:
                for r in sub_policy.rules:
                    template = commonRules.fp.gettemplate(r.template_id)
                    targetBenchmark.fp.newrule(policy, template, commonRules.fp)

            ids = []
            for subPolicy in sub_policy.subpolicies:
                ids.append(subPolicy.policy_id)

            entry = dict()
            entry['target_policy_id'] = policy.id
            entry['subpolicies'] = ids

            # print "### queue ###"
            # pp.pprint(entry)
            policyQueue.put(entry)
            #


    # print "######################################"
    print(targetBenchmark.fp)



def displayFP(fp):
    pp = pprint.PrettyPrinter(indent=4,depth=3)

    pp.pprint(vars(fp))
    print("==== Dependencies=================================")
    if fp.dependencies is not None:
        pp.pprint(vars(fp.dependencies))
    else:
        print("-- No dependencies")
    print("=====================================")
    deps = fp.dependencies
    if deps is not None:
        deplist = deps.dependencies
    else:
        deplist = []
    for dep in deplist:
        pp.pprint(vars(dep))
        print("=====================================")
    pp.pprint(vars(fp.policies))
    print("==== Policies =================================")
    pp.pprint(vars(fp.policies.policies[0]))
    print("*description*")
    pp.pprint(fp.policies.policies[0].description.text)
    print("**************")

    print("vvvvvvvvvvvvvvvvvvvvvv")
    #print "    ---------------------"

    for subpolicy in fp.policies.policies[0].subpolicies:
        print("policy_id: " + str(subpolicy.policy_id))
        policy = fp.policies.getpolicy(subpolicy.policy_id)
        pp.pprint(vars(policy))
        print("    *description*")
        pp.pprint(policy.description.text)
        print("    **************")
        print(policy.documentation)  #pp.pprint(policy.documentation)
        print("    ---------------------")
    print("^^^^^^^^^^^^^^^^^^^^^^")
    


# Main program

if __name__ == "__main__":
    main()
