# author Chris Gates - Uber
# additions Maus - Uber
# additions author Russ Nolen - Riot Games

# adversarial simulation engine
from __future__ import print_function

import datetime
import json
import logging
import os
import subprocess
import sys
import time
from argparse import ArgumentParser
from random import randint

import requests

import yaml
from reporting.log_to_file import *
from workers.vagranttasks import *

try:
    import configparser as ConfigParser  # Python 3
except ImportError:
    import ConfigParser                  # Python 2

# slack hook URL
hook = ""

# vagrant variables that get populated below
windows = " "
osx = " "
linux = " "
kali = " "

# banners for metta
banner = '''
   _____          __    __
  /     \   _____/  |__/  |______
 /  \ /  \_/ __ \   __\   __\__  \
/    Y    \  ___/|  |  |  |  / __ \_
\____|__  /\___  >__|  |__| (____  /
        \/     \/                \/
'''

banner2 = '''

 __   __  _______  _______  _______  _______
|  |_|  ||       ||       ||       ||   _   |
|       ||    ___||_     _||_     _||  |_|  |
|       ||   |___   |   |    |   |  |       |
|       ||    ___|  |   |    |   |  |       |
| ||_|| ||   |___   |   |    |   |  |   _   |
|_|   |_||_______|  |___|    |___|  |__| |__|

'''

# module to post to slack if you set the webhook in config.ini


def post_to_slack(hook, json):
    try:
        r = requests.post(hook, json=json)
    except Exception as e:
        print(e)


def run_scenario(ioc_filename):
    try:
        print("### Running the Scenario ###")
        raw_iocs = yaml.load_all(open(ioc_filename, 'r').read())

        timenow = datetime.datetime.utcnow()

        for raw_ioc in raw_iocs:
            scenario = raw_ioc.get('meta').get('scenario_actions')
            rule_name = raw_ioc.get('name')
            print("### {} ###".format(rule_name))

            scenario_actions = []
            # read the steps from purple_actions in yaml and load them into purple_actions
            for x in range(1, len(scenario)+1):
                scenario_actions.append(raw_ioc.get('meta').get('scenario_actions').get(x))

            for uuid_file in scenario_actions:
                run_uuid(uuid_file)

    except Exception as e:
        print(e)


def run_uuid(ioc_filename):
    try:
        print("\nRunning UUID actions inside:{}".format(ioc_filename))

        raw_iocs = yaml.load_all(open(ioc_filename, 'r').read())

        for raw_ioc in raw_iocs:
            rule_name = raw_ioc.get('name')
            rule_uuid = raw_ioc.get('uuid')
            rule_os = raw_ioc.get('os')
            mitre_phase = raw_ioc.get('meta').get('mitre_attack_phase')
            mitre_tech = raw_ioc.get('meta').get('mitre_attack_technique')
            purple = raw_ioc.get('meta').get('purple_actions')

            if not purple:
                print("No Purple Actions detected you've probably messed up your scenario.yml...")
                sys.exit(0)

            purple_actions = []
            # read the steps from purple_actions in yaml and load them into purple_actions
            for x in range(1, len(purple)+1):
                purple_actions.append(raw_ioc.get('meta').get('purple_actions').get(x))

            if rule_os == "windows":
                print("OS matched Windows...sending to the windows vagrant")
                for action in purple_actions:
                    print("Running: {}".format(action))
                    timenow = datetime.datetime.utcnow()
                    date = timenow.strftime('%Y-%m-%d')
                    hourminsec = timenow.strftime('%H:%M:%S')
                    time_to_log = date+" "+hourminsec
                    try:
                        vagrant = runcmd_nodb_win.delay(action, rule_name, rule_uuid, windows)
                        data = json.dumps({'time': time_to_log, 'rule_name': rule_name, 'action': action, 'mitre_attack_phase': mitre_phase, 'mitre_attack_technique': mitre_tech, 'host': windows})
                        logging.info(data)
                        write_row(time_to_log, rule_name, action, mitre_phase, mitre_tech, windows)

                        '''
                        # if you want to post to slack uncomment this and set the slack hook above
                        json = {'text': "Automated Purple Team --> Simulation: {} | Action: {}  | Host: {} | Execution Time: {} UTC".format(rule_name,action,windows,datetime.datetime.utcnow())}
                        post_to_slack(hook,json)
                        '''
                        time.sleep(randint(2, 30))
                    except Exception as e:
                        print(e)

            elif rule_os == "osx":
                print("OS matched OSX...sending to the OSX vagrant")
                for action in purple_actions:
                    print("Running: {}".format(action))
                    timenow = datetime.datetime.utcnow()
                    date = timenow.strftime('%Y-%m-%d')
                    hourminsec = timenow.strftime('%H:%M:%S')
                    time_to_log = date+" "+hourminsec
                    try:
                        vagrant = runcmd_nodb_osx.delay(action, rule_name, rule_uuid, osx)
                        data = json.dumps({'time': time_to_log, 'rule_name': rule_name, 'action': action, 'mitre_attack_phase': mitre_phase, 'mitre_attack_technique': mitre_tech, 'host': osx})
                        logging.info(data)
                        write_row(time_to_log, rule_name, action, mitre_phase, mitre_tech, osx)
                        '''
                        # if you want to post to slack uncomment this and set the slack hook above
                        json = {'text': "Automated Purple Team --> Simulation: {} | Action: {}  | Host: {} | Execution Time: {} UTC".format(rule_name,action,osx,datetime.datetime.utcnow())}
                        post_to_slack(hook,json)
                        '''
                        time.sleep(randint(2, 30))
                    except Exception as e:
                        print(e)

            elif rule_os == "linux":
                print("OS matched Linux...sending to the Linux vagrant")
                for action in purple_actions:
                    print("Running: {}".format(action))
                    timenow = datetime.datetime.utcnow()
                    date = timenow.strftime('%Y-%m-%d')
                    hourminsec = timenow.strftime('%H:%M:%S')
                    time_to_log = date+" "+hourminsec
                    try:
                        vagrant = runcmd_nodb_linux.delay(action, rule_name, rule_uuid, linux)
                        data = json.dumps({'time': time_to_log, 'rule_name': rule_name, 'action': action, 'mitre_attack_phase': mitre_phase, 'mitre_attack_technique': mitre_tech, 'host': linux})
                        logging.info(data)
                        write_row(time_to_log, rule_name, action, mitre_phase, mitre_tech, linux)
                        '''
                        # if you want to post to slack uncomment this and set the slack hook above
                        json = {'text': "Automated Purple Team --> Simulation: {} | Action: {}  | Host: {} | Execution Time: {} UTC".format(rule_name,action,osx,datetime.datetime.utcnow())}
                        post_to_slack(hook,json)
                        '''
                        time.sleep(randint(2, 30))
                    except Exception as e:
                        print(e)

            elif rule_os == "kali":
                print("OS matched Kali...sending to the Kali Linux vagrant")
                for action in purple_actions:
                    print("Running: {}".format(action))
                    timenow = datetime.datetime.utcnow()
                    date = timenow.strftime('%Y-%m-%d')
                    hourminsec = timenow.strftime('%H:%M:%S')
                    time_to_log = date+" "+hourminsec
                    try:
                        vagrant = runcmd_nodb_kali.delay(action, rule_name, rule_uuid, kali)
                        data = json.dumps({'time': time_to_log, 'rule_name': rule_name, 'action': action, 'mitre_attack_phase': mitre_phase, 'mitre_attack_technique': mitre_tech, 'host': kali})
                        logging.info(data)
                        write_row(time_to_log, rule_name, action, mitre_phase, mitre_tech, kali)
                        '''
                        #if you want to post to slack uncomment this and set the slack hook above
                        #json = {'text': "Automated Purple Team --> Simulation: {} | Action: {}  | Host: {} | Execution Time: {} UTC".format(rule_name,action,osx,datetime.datetime.utcnow())}
                        #post_to_slack(hook,json)
                        '''
                        time.sleep(randint(2, 30))
                    except Exception as e:
                        print(e)
            else:
                print("I received an unknown OS")
    except KeyboardInterrupt:
        print("CTRL-C received, exiting...")
    except Exception as e:
        print(e)


def parse_yaml(ioc_filename):
    print(banner2)
    print("YAML FILE: {}".format(ioc_filename))
    try:
        raw_iocs = yaml.load_all(open(ioc_filename, 'r').read())
        start_log("Adversarial Simulation", "1.0")

        for raw_ioc in raw_iocs:

            scenario = raw_ioc.get('meta').get('scenario')
            purple = raw_ioc.get('meta').get('purple_actions')
            # if we cant find the scenario tag, default to run_uuid
            if not scenario:
                run_uuid(ioc_filename)
            # if the scenario field is found and if it's true run the run_scenario function
            if scenario is True:
                run_scenario(ioc_filename)
        close_log()

    except KeyboardInterrupt:
        print("CTRL-C received, exiting...")

    except Exception as e:
        print(e)


def main():
    parser = ArgumentParser(description="adversarial-simulation ")
    parser.add_argument("-f", "--simfile", action="store", default=None, required=True, dest="simfile", help="Path to simulation file you want to run")
    args = parser.parse_args()
    config = ConfigParser.RawConfigParser()

    try:
        config.read('config.ini')
    except Exception as e:
        print(e)
        sys.exit(0)

    global windows
    windows = config.get('vms', 'windows')

    global osx
    osx = config.get('vms', 'osx')

    global linux
    linux = config.get('vms', 'linux')

    global kali
    kali = config.get('vms', 'kali')

    global console_output
    console_log_output = config.get('console_log_output', 'enabled')

    # logging function to log json to a file
    logging.basicConfig(level=logging.DEBUG, format='%(message)s', filename='simulation.log', filemode='w')

    if console_log_output == 'True' or console_log_output == 'true':
        # logging function to give info to the console
        console = logging.StreamHandler()
        console.setLevel(logging.INFO)
        # set a format which is simpler for console use
        formatter = logging.Formatter('%(levelname)-4s : %(message)s')
        # tell the handler to use this format
        console.setFormatter(formatter)
        # add the handler to the root logger
        logging.getLogger('').addHandler(console)
    else:
        ''

    parse_yaml(args.simfile)


if __name__ == '__main__':
    main()