#!/usr/bin/env python
from urllib.parse import urlparse
import click
import os
import time
import configparser
from subprocess import call
import re

GLOBAL_CONFIG_LOCATION = '~/.vcm'
DEFAULT_NMAP_SETTINGS = ["-sV", "-p-"]

global_config = None


# TODO: Stuff to automate later
#   brew install testssl
#   download openssl binary and store it in default location
#   brew install nmap
#   brew install nikto


class VcmGlobalConfig:
    open_ssl_binary = '/usr/bin/openssl'  # default - can be overridden in global config file.

    def __init__(self):
        pass

    def read_global_vcm(self):
        global global_config

        print(f"Reading global config from {GLOBAL_CONFIG_LOCATION}")

        read_config = configparser.RawConfigParser()
        global_config_filename = os.path.expanduser(GLOBAL_CONFIG_LOCATION)
        read_config.read(global_config_filename)

        self.open_ssl_binary = read_config.get('GlobalSettings', 'openssl_binary')

    def write_global_vcm(self):
        print(f"Creating global config file with defaults in {GLOBAL_CONFIG_LOCATION}")

        global global_config
        global_config = configparser.RawConfigParser()
        global_config.add_section('GlobalSettings')

        global_config.set('GlobalSettings', 'openssl_binary', self.open_ssl_binary)

        global_config_file = os.path.expanduser(GLOBAL_CONFIG_LOCATION)

        with open(global_config_file, 'w') as configfile:
            try:
                global_config.write(configfile)
            except configparser.Error as ex:
                print(f"Error writing config file: {global_config_file} : {ex.message}")
                return


class VcmProjectConfig:
    local_folder = ''
    remote_folder = ''
    project_name = ''
    targets = []
    target_urls = []

    # derived directories
    artifacts_folder = ''

    def __init__(self):
        pass

    def read_project_vcm(self):
        project_config = configparser.RawConfigParser()

        project_filename = os.path.join(os.getcwd(), '.vcm')

        cf = project_config.read(project_filename)

        if len(cf) == 0:
            raise Exception(f"Unable to read config file: {project_filename}")

        self.remote_folder = project_config.get('ProjectSettings', 'remote_path')
        self.local_folder = project_config.get('ProjectSettings', 'local_path')

        self.artifacts_folder = os.path.join(self.local_folder, 'artifacts')

        url_targets = re.split(",", project_config.get('ProjectSettings', 'url_targets'))

        for t in url_targets:
            stripped_target = t.strip()

            # The requirement is for targets to have a scheme - even if you're just
            # using nmap
            if len(stripped_target) > 0:
                target_url = urlparse(stripped_target)

                self.target_urls.append(target_url)

                if not bool(target_url.scheme):
                    raise ValueError(
                        f"URL found without scheme: {stripped_target}. Please note, schemes are required for all URLs")

                self.targets.append(t)

    def write_project_vcm(self, project_name, local_folder, remote_folder, url_targets):
        project_config = configparser.RawConfigParser()
        project_config.add_section('ProjectSettings')
        project_config.set('ProjectSettings', 'project_name', project_name)
        project_config.set('ProjectSettings', 'local_path', os.path.join(local_folder, ''))
        project_config.set('ProjectSettings', 'remote_path', os.path.join(remote_folder, ''))
        project_config.set('ProjectSettings', 'url_targets', url_targets)

        project_vmc_filename = os.path.join(local_folder, '.vcm')

        with open(project_vmc_filename, 'w') as configfile:
            try:
                project_config.write(configfile)
            except configparser.Error as ex:
                print(f"Error writing config file: {project_vmc_filename} : {ex.message}")
                return


@click.group()
def vcm():
    global global_config
    global_config = VcmGlobalConfig()

    if os.path.isfile(os.path.expanduser(GLOBAL_CONFIG_LOCATION)):
        global_config.read_global_vcm()
    else:
        global_config.write_global_vcm()
    pass


###
#   Folder and project management
###
def create_folder(folder):
    if not os.path.exists(folder):
        try:
            os.makedirs(folder)
        except OSError as ex:
            print(f"Error creating folder: {folder} : {ex.strerror}")
            return


@vcm.command()
def create():
    # create a config file .vcm and ask for: project name, root dir name, remote directory, urls (csv)
    project_name = click.prompt('Project Name', type=str)
    local_folder = click.prompt('Local Path', type=str, default=os.path.join(os.getcwd(), project_name))
    remote_folder = click.prompt('Remote Path', type=str)
    url_targets = click.prompt('URL Targets (CSV)', type=str)

    create_folder(local_folder)

    for folder in ['reports', 'artifacts', 'logistics']:
        create_folder(os.path.join(local_folder, folder))

    project_config = VcmProjectConfig()
    project_config.write_project_vcm(project_name, local_folder, remote_folder, url_targets)


@vcm.command()
def push():
    # ensure the remote dir is mounted
    project_config = VcmProjectConfig()
    project_config.read_project_vcm()

    # do an rsync -ah from local to remote
    if click.confirm('Sync local (%s) to remote (%s)?' % (project_config.local_folder, project_config.remote_folder)):
        args = ["rsync", "-ah", "--progress", project_config.local_folder, project_config.remote_folder]
        call(args)


@vcm.command()
def pull():
    # ensure the remote dir is mounted
    # do an rsync -ah from remote to local
    project_config = VcmProjectConfig()
    project_config.read_project_vcm()

    # do an rsync -ah from local to remote
    if click.confirm('Sync remote (%s) to local (%s)?' % (project_config.remote_folder, project_config.local_folder)):
        args = ["rsync", "-ah", "--progress", project_config.remote_folder, project_config.local_folder]
        call(args)


###
#   Running testing tools
###
@vcm.group()
def run():
    pass


@run.command()
def nmap():
    try:
        project_config = VcmProjectConfig()
        project_config.read_project_vcm()
    except ValueError as ex:
        print(ex)
        return

    # We only need the netloc of the full url - strip the rest out
    nmap_targets = []
    for t in project_config.targets:
        nmap_targets.append(urlparse(t).netloc)

    if not click.confirm('Run nmap against the following targets: %s' % ', '.join(nmap_targets)):
        return

    args = ["nmap"]
    args.extend(DEFAULT_NMAP_SETTINGS)

    for t in nmap_targets:
        args.append(t)

    args.append("-oA")
    args.append(os.path.join(project_config.artifacts_folder, f'nmap_{time.time()}'))
    call(args)


@run.command()
def nikto():
    try:
        project_config = VcmProjectConfig()
        project_config.read_project_vcm()
    except ValueError as ex:
        print(ex)
        return

    if not click.confirm('Run nikto against the following targets: %s' % ', '.join(project_config.targets)):
        return

    # Nikto takes multiple hosts from a file
    # BUT bear in mind advice from: https://github.com/sullo/nikto/wiki/Basic-Testing
    # ie run scans separately so that memory is freed each time.
    for t in project_config.targets:
        output_filename = os.path.join(project_config.artifacts_folder,
                                       f"nikto_{urlparse(t).netloc}_{time.time()}.html")
        try:
            # nikto -h https://www.test.com -ssl -Format html -output .
            args = ["nikto", "-h", t, '-ssl', '-Format', 'html', '-output', output_filename]

            print(args)
            call(args)
        except Exception as ex:
            print(f"Error writing nikto output to: {output_filename} : {ex}")


@run.command()
def testssl():
    try:
        project_config = VcmProjectConfig()
        project_config.read_project_vcm()
    except ValueError as ex:
        print(ex)
        return

    https_targets = []
    for t in project_config.targets:
        https_targets.append('https://' + urlparse(t).netloc)

    if not click.confirm('Run testssl against the following targets: %s' % ', '.join(https_targets)):
        return

    for t in https_targets:
        output_filename = os.path.join(project_config.artifacts_folder, f"testssl_{urlparse(t).netloc}_{time.time()}.txt")

        try:
            args = ["testssl.sh", "--openssl", global_config.open_ssl_binary, "--logfile", output_filename, t]

            print(args)
            call(args)

        except Exception as ex:
            print(f"Error writing testssl output to: {output_filename} : {ex}")


@run.command()
def dirb():
    try:
        project_config = VcmProjectConfig()
        project_config.read_project_vcm()
    except ValueError as ex:
        print(ex)
        return

    if not click.confirm('Run dirb against the following targets: %s' % ', '.join(project_config.targets)):
        return

    for t in project_config.targets:
        output_filename = os.path.join(project_config.artifacts_folder,
                                       'dirb_' + str(project_config.targets.index(t))) + '.txt'
        try:
            # dirb url -o output.txt
            args = ["dirb", t, '-o', output_filename]
            call(args)

        except Exception as ex:
            print(f"Error writing dirb output to: {output_filename} : {ex}")


if __name__ == '__main__':
    vcm()