# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Mobius Forensic Toolkit
# Copyright (C) 2008,2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,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 mobius
import traceback
import pymobius.operating_system
import pymobius.registry.installed_programs
from pymobius.registry import *

import pymobius.p2p
import pymobius.p2p.application
from pymobius.p2p.local_file import *

from . import decoder_bt_fastresume
from . import decoder_ed2k_fastresume
from . import decoder_emuletorrent_ini

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# Versions examined: libed2k v0.0.1
#
# EmuleTorrent main forensic files:
#
# . AppDATALOCAL\Emuletorrent\BT_backup\*.fastresume: metadata for
#   downloading files (BitTorrent network)
#
# . AppDATALOCAL\Emuletorrent\ED2K_backup\*.fastresume: metadata for
#   downloading files (ED2K network)
#
# . AppDATAROAMING\mulehome\emuletorrent.ini: configuration file
#
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

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

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief get data as UNIX datetime
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
def get_unix_datetime (timestamp):

  if timestamp > 0:
    delta = datetime.timedelta (seconds=timestamp)
    date = datetime.datetime (1970, 1, 1, 0, 0, 0) + delta
  else:
    date = None

  return date

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief Retrieves EmuleTorrent 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):
  try:
    ant = Ant (model)
    ant.run ()
  except Exception as e:
    mobius.core.logf ('WRN p2p.emuletorrent: %s' % str (e))

# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
# @brief forensics: P2P Emule
# @author Eduardo Aguiar
# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
class Ant (object):

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

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

    # create local data model
    self.__data = dataholder ()
    self.__data.accounts = []
    self.__data.searches = []
    self.__data.local_files = []
    self.__data.remote_files = []

    # create app
    self.__data.application = pymobius.p2p.application.application ()
    self.__data.application.id = 'emuletorrent'
    self.__data.application.name = 'EmuleTorrent'

    # retrieve data
    self.__retrieve_registry_data ()
    self.__retrieve_app_data ()
    self.__normalize_data ()

    # 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 registry data
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_registry_data (self):
    ant = pymobius.registry.main.Ant (self.__item)

    for registry in ant.get_data ():
      for program in pymobius.registry.installed_programs.get (registry):
        program_name = program.display_name.lower ()

        if program_name == 'emuletorrent':
          self.__data.application.versions.add (program.version)

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

    # decode "<USER_FOLDER>/"
    for opsys in pymobius.operating_system.scan (self.__item):
      for user_profile in opsys.get_profiles ():
        try:

          # AppData/Local/eMuleTorrent
          folder = user_profile.get_entry_by_path ('%localappdata%/eMuleTorrent')

          if folder:
            self.__retrieve_appdatalocal (user_profile.username, folder)

          # AppData/Roaming/mulehome
          folder = user_profile.get_entry_by_path ('%appdata%/mulehome')

          if folder:
            self.__retrieve_appdataroaming (user_profile.username, folder)

        except Exception as e:
          mobius.core.logf ('WRN p2p.emuletorrent: ' + str (e))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve data from APPDATALOCAL folder
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_appdatalocal (self, username, folder):

    for child in folder.get_children ():
      lname = child.name.lower ()

      if child.is_reallocated ():
        pass

      # BT_backup folder
      elif lname == 'bt_backup':
        self.__retrieve_bt_backup_folder (username, child)

      # ED2K_backup folder
      elif lname == 'ed2k_backup':
        self.__retrieve_ed2k_backup_folder (username, child)

      # Unhandled file/folder
      else:
        t = 'file' if child.is_file () else 'folder'
        mobius.core.logf ('p2p.emuletorrent: Unhandled AppData/Local/%s %s' % (child.name, t))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve data from APPDATAROAMING folder
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_appdataroaming (self, username, folder):
    idx = 1

    for child in folder.get_children ():
      lname = child.name.lower ()

      if child.is_reallocated ():
        pass

      # emuleTorrent.ini
      elif lname == 'emuletorrent.ini':
        self.__retrieve_emuletorrent_ini (self.__item, self.__data, child, username)

      # Unhandled file/folder
      else:
        t = 'file' if child.is_file () else 'folder'
        mobius.core.logf ('p2p.emuletorrent: Unhandled AppData/Roaming/mulehome/%s %s' % (child.name, t))

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve data from BT_backup folder
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_bt_backup_folder (self, username, folder):
   for child in folder.get_children ():
     if child.is_file () and child.name.endswith ('.fastresume'):
       self.__retrieve_bt_fastresume (self.__item, self.__data, child, username)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve data from ED2K_backup folder
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_ed2k_backup_folder (self, username, folder):
   for child in folder.get_children ():
     if child.is_file () and child.name.endswith ('.fastresume'):
       self.__retrieve_ed2k_fastresume (self.__item, self.__data, child, username)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve BT data from .fastresume file
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_bt_fastresume (self, item, model, f, username):
    data = decoder_bt_fastresume.decode (f)
    if not data:
      return

    # get metadata
    info = data.metadata

    metadata = []
    metadata.append (('File added date/time', data.added_time))
    metadata.append (('Last seen complete date/time', data.last_seen_complete))
    metadata.append (('Download completed date/time', data.completed_time))

    for name, value in info.items ():
      if name not in ('pieces', 'piece_priority', 'trackers', 'peers', 'file_priority', 'info-hash'):
        metadata.append ((name.capitalize (), value))

    # create local files
    for btf in data.files:
      lf = local_file ()
      lf.app_id = 'emuletorrent'
      lf.app = 'EmuleTorrent'
      lf.name = btf.name
      lf.path = None 	# @todo data.path
      lf.username = username
      lf.size = btf.size

      # flags
      #lf.flag_shared = STATE_ALWAYS
      lf.flag_corrupted = STATE_NO
      lf.flag_downloaded = STATE_YES if info.get ('total_downloaded', 0) > 0 else STATE_NO
      lf.flag_uploaded = STATE_YES if info.get ('total_uploaded', 0) > 0 else STATE_NO
      lf.flag_completed = STATE_YES if info.get ('completed_time', 0) > 0 else STATE_NO

      # metadata
      lf.add_metadata ('Last modification date/time', btf.last_modification_time)

      for name, value in metadata:
        lf.add_metadata (name, value)

      # add local file to model
      model.local_files.append (lf)

    # create remote files
    peers = info.get ('peers')
    if peers:
      mobius.core.logf ('DEV p2p.emuletorrent remote files found')
      mobius.core.logf ('DEV ' + pymobius.dump (peers))

    # set file handled
    pymobius.p2p.set_handled (item, f)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve ED2K data from .fastresume file
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_ed2k_fastresume (self, item, model, f, username):
    entry = decoder_ed2k_fastresume.decode (f)
    if not entry:
      return

    info = entry.metadata

    # exam pieces
    # @see transfer::write_resume_data (transfer.cpp - libed2k)
    pieces = info.get ('pieces')

    if pieces:
      flag_completed = all (ord (p) != 0 for p in pieces)
      pieces_count = len (pieces)
      pieces_downloaded = len ([p for p in pieces if ord (p) != 0])

    else:
      flag_completed = False
      pieces_count = 'unknown'
      pieces_downloaded = 0

      bytes_to_download = entry.size
      bytes_downloaded = 0

    # create local file
    lf = local_file ()
    lf.app_id = 'emuletorrent'
    lf.app = 'EmuleTorrent'
    lf.name = entry.name
    lf.path = entry.path
    lf.size = entry.size
    lf.username = username

    # flags
    #lf.flag_shared = STATE_ALWAYS
    lf.flag_corrupted = STATE_NO
    lf.flag_downloaded = STATE_YES if info.get ('total_downloaded', 0) > 0 else STATE_NO
    lf.flag_uploaded = STATE_YES if info.get ('total_uploaded', 0) > 0 else STATE_NO
    lf.flag_completed = STATE_YES if flag_completed else STATE_NO

    # hash (ED2K)
    lf.set_hash ('ed2k', entry.hash_ed2k)

    # Last modification date/time
    file_sizes = info.get ('file sizes', [])

    if len (file_sizes) == 1:
      size, mtime = file_sizes[0]
      # @todo maybe "Last save size" (file size[0]) and "Last metadata save time" (file size[1])
      lf.add_metadata ('File size[0]', size)
      lf.add_metadata ('Last modification date/time', get_unix_datetime (mtime))

    # metadata
    lf.add_metadata ('DUMMY VALUE', '0x%08x' % entry.dummy)
    lf.add_metadata ('Pieces', pieces_count)
    lf.add_metadata ('Pieces downloaded', pieces_downloaded)

    for name, value in info.items ():
      if name not in ('hashset-values', 'pieces', 'piece_priority', 'max_uploads', 'max_connections', 'file sizes'):
        lf.add_metadata (name.capitalize (), value)

    lf.add_metadata ('.fastresume path', entry.fastresume_path)
    lf.add_metadata ('.fastresume creation time (UTC)', entry.fastresume_creation_time)
    lf.add_metadata ('.fastresume last modification time (UTC)', entry.fastresume_last_modification_time)

    # add local file to model
    model.local_files.append (lf)

    # set file handled
    pymobius.p2p.set_handled (item, f)

  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  # @brief Retrieve data from emuletorrent.ini file
  # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  def __retrieve_emuletorrent_ini (self, item, model, f, username):
    data = decoder_emuletorrent_ini.decode (f)
    if not data:
      return

    # edonkey account
    account = pymobius.p2p.account.account ()
    account.username = username
    account.app_id = 'emuletorrent'
    account.app = 'EmuleTorrent'
    account.network = 'eDonkey'
    account.guid = data.guid.upper ()
    model.accounts.append (account)

    # set file handled
    pymobius.p2p.set_handled (item, f)

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