# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2018 Eduardo Aguiar
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation; either version 2, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
import pymobius.forensics.vfs
import pymobius.forensics.registry.installed_programs
from pymobius.forensics.registry import *
import pymobius.p2p
import pymobius.p2p.account
import pymobius.p2p.application
import pymobius.p2p.search
import pymobius.forensics.registry
import decoder_shareh_dat
import decoder_sharel_dat
import mobius
import binascii

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# References:
#    . Ares Galaxy 246 source code
#    . Forensic Analysis of Ares Galaxy Peer-to-Peer Network (Kolenbrander)
#
# Ares Galaxy main files:
#    . ShareH.dat - Shared file history
#    . ShareL.dat - Share file history
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Generic dataholder
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class dataholder (object):
  pass

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief iterate through Ares AppData folders
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def iter_ares_folders (vfs):
  for root in vfs:
    users_folder = root.get_child_by_name ('Users', False)

    if not users_folder:
      users_folder = root.get_child_by_name ('Documents and Settings', False)

    if users_folder:
      for entry in users_folder.get_children ():

        if entry.is_folder ():
          username = entry.name
          ares_folder = entry.get_child_by_path ('AppData/Local/Ares', False) or \
                        entry.get_child_by_path ('Application Data/Ares', False)

          if ares_folder:
            yield username, entry, ares_folder

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Retrieves Ares P2P activity data
# @author Eduardo Aguiar
# This function is planned to run in an independent thread. The idea here
# is to gather all activity data and only then write data to the model
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def retrieve (model):
  ant = Ant (model)
  ant.run ()
  
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief forensics: P2P Ares
# @author Eduardo Aguiar
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Ant (object):

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief initialize object
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __init__ (self, model):
    self.__model = model	# P2P model
    self.__item = model.item

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief run
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def run (self):

    # create local data model
    self.__data = dataholder ()
    self.__data.application = pymobius.p2p.application.application ()
    self.__data.application.id = 'ares'
    self.__data.application.name = 'Ares Galaxy'
    self.__data.item = self.__model.item

    self.__data.accounts = []
    self.__data.searches = []
    self.__data.local_files = []
    self.__data.remote_files = []

    # retrieve data
    self.__retrieve ()

    # update P2P model
    self.__model.applications.append (self.__data.application)
    self.__model.accounts += self.__data.accounts
    self.__model.searches += self.__data.searches
    self.__model.local_files += self.__data.local_files
    self.__model.remote_files += self.__data.remote_files

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve data
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve (self):
    ant = pymobius.forensics.registry.main.Ant (self.__item)

    for registry in ant.get_data ():
      self.__retrieve_registry_data (registry)

    self.__retrieve_app_data ()
    self.__normalize_data ()

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief retrieve registry data
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_registry_data (self, registry):

    # Application
    for program in pymobius.forensics.registry.installed_programs.get (registry):
      if program.display_name.startswith ('Ares '):
        self.__data.application.versions.add (program.version)

    # Search History
    for username, user_key in pymobius.forensics.registry.iter_hkey_users (registry):
      for subkey in user_key.get_key_by_mask ('Software\\Ares\\Search.History\\*'):
        for value in subkey.values:
          keyword = binascii.unhexlify (value.name)
          search = pymobius.p2p.search.search ()
          search.app_id = 'ares'
          search.app = 'Ares Galaxy'
          search.text = keyword
          search.username = username
          search.add_metadata ('Application', 'Ares Galaxy')
          search.add_metadata ('Search Term', search.text)
          search.add_metadata ('Category', subkey.name)
          self.__data.searches.append (search)

    # Accounts
    for username, user_key in pymobius.forensics.registry.iter_hkey_users (registry):
      ares_key = user_key.get_key_by_path ('Software\\Ares')

      if ares_key:
        guid = get_data_as_string (ares_key.get_data_by_name ('Personal.GUID'))
        account = pymobius.p2p.account.account ()
        account.username = username
        account.app_id = 'ares'
        account.app = 'Ares Galaxy'
        account.network = 'Ares'
        account.guid = guid
        self.__data.accounts.append (account)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief retrieve data from disk files
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_app_data (self):

    # get item VFS
    vfs = pymobius.forensics.vfs.get_item_vfs (self.__item)
    if not vfs:
      raise Exception, "Datasource is not available"

    # scan VFS root entries
    for username, user_folder, folder in iter_ares_folders (vfs):

     # 'Data' folder
     data_folder = folder.get_child_by_name ('Data', False)
     if data_folder:
       self.__retrieve_data_folder (data_folder, username)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief retrieve data from Data folder
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_data_folder (self, folder, username):

    for idx, f in enumerate (folder.get_children ()):
      fname = f.name.lower ()

      # Ignore reallocated entries
      if f.is_reallocated:
        pass

      # ShareH.dat
      elif fname == 'shareh.dat':
        decoder_shareh_dat.retrieve (self.__data, f, username)

      # ShareL.dat
      elif fname == 'sharel.dat':
        decoder_sharel_dat.retrieve (self.__data, f, username)

      # TorrentH.dat
      elif fname == 'torrenth.dat':
        pass # @todo handle

      # ChatroomIPs.dat
      elif fname == 'chatroomips.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # SNodes.dat
      elif fname == 'snodes.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # FailedSNodes.dat
      elif fname == 'failedsnodes.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # DHTnodes.dat
      elif fname == 'dhtnodes.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # MDHTnodes.dat
      elif fname == 'mdhtnodes.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # PHashIdx.dat
      elif fname == 'phashidx.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # PHashIdxTemp.dat
      elif fname == 'phashidxtemp.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # TempPHash.dat
      elif fname == 'tempphash.dat':
        pymobius.p2p.set_handled (self.__item, f)

      # TempDL
      elif fname == 'tempdl' and f.is_folder ():
        self.__retrieve_tempdl_folder (f, username)

      else:
        t = 'file' if f.is_file () else 'folder'
        mobius.core.log ('p2p.ares: Unhandled Data/%s %s' % (f.name, t))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief retrieve data from Data/TempDL folder
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_tempdl_folder (self, folder, username):

    for idx, f in enumerate (folder.get_children ()):
      fname = f.name.lower ()

      # PHash_XXXX.dat
      if fname.startswith ('phash_') and fname.endswith ('.dat'):
        pass # @todo handle

      # PBTHash_XXXX.dat
      elif fname.startswith ('pbthash_') and fname.endswith ('.dat'):
        pass # @todo handle

      # META_XXXX.dat
      elif fname.startswith ('meta_') and fname.endswith ('.dat'):
        pass # @todo handle

      else:
        mobius.core.log ('p2p.ares: Unhandled Data/TempDL/%s file' % f.name)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief normalize retrieved data
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __normalize_data (self):

    # merge ShareH and ShareL local files
    local_files = []
    shareh = {}
  
    for f in self.__data.local_files:
      hash_sha1 = f.get_hash ('sha1')

      if f.size == -1:		# ShareH
        shareh[hash_sha1] = f
        local_files.append (f)

      else:			# ShareL
        h = shareh.get (hash_sha1)
        if h:
          h.size = f.size
          h.path = f.path
	else:
          local_files.append (f)

    self.__data.local_files = local_files

    # sort and remove duplicated remote files
    remote_files = [ (f.timestamp, f) for f in self.__data.remote_files ]
    timestamp, peer_ip, size, name = None, None, None, None
    self.__data.remote_files = []

    for (timestamp, f) in sorted (remote_files):
      if (timestamp, peer_ip, size, name) != (f.timestamp, f.peer.ip, f.size, f.name):
        self.__data.remote_files.append (f)
        timestamp, peer_ip, size, name = f.timestamp, f.peer.ip, f.size, f.name
