#!/usr/bin/env python
# -*- coding: utf-8 -*-
# File: transferagent.py
#
# Copyright 2021 Vincent Schouten
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to
#  deal in the Software without restriction, including without limitation the
#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
#  DEALINGS IN THE SOFTWARE.
#

"""
Main code for transferagent.

.. _Google Python Style Guide:
   http://google.github.io/styleguide/pyguide.html

NOTE: The TransferAgent class is responsible to purge the stream (ie. index in stream is at COMMAND_PROMPT)

"""

import inspect
import os.path
import pexpect
from .logging import LoggerMixin

__author__ = '''Vincent Schouten <inquiry@intoreflection.co>'''
__docformat__ = '''google'''
__date__ = '''10-05-2019'''
__copyright__ = '''Copyright 2021, Vincent Schouten'''
__credits__ = ["Vincent Schouten"]
__license__ = '''MIT'''
__maintainer__ = '''Vincent Schouten'''
__email__ = '''<inquiry@intoreflection.co>'''
__status__ = '''Development'''  # "Prototype", "Development", "Production".

# Constant for Pexpect. This prompt is default for Fedora and CentOS.
COMMAND_PROMPT = '[#$] '


class TransferAgent(LoggerMixin):
    """Establishes a connection to the target destination host via one or more intermediaries."""

    def __init__(self, path_ssh_cfg, all_host_ips):
        """Initialize the TransferAgent object."""
        super().__init__()
        self.all_host_ips = all_host_ips
        self.last_host_ip = all_host_ips[-1]
        self.child = None
        self.path_ssh_cfg = path_ssh_cfg
        self.authenticated_hosts = []

    def __str__(self):
        return 'TransferAgent'

    @property
    def _path_to_agent_module(self):
        running_script = inspect.getframeinfo(inspect.currentframe()).filename
        running_script_dir = os.path.dirname(os.path.abspath(running_script))
        path_file = os.path.join(running_script_dir, 'payload', 'agent.py')
        self._logger.debug('agent.py resides in: %s', running_script_dir)
        return path_file

    def _generate_ssh_runtime_param(self):
        if len(self.all_host_ips) == 2:  # only 1 gateway + destination host
            # the result will be something in this format:
            # scp -F {} -o 'ProxyJump 10.10.1.72' /home/vincent/Pictures/andy_apollo_imdb.jpg 10.10.2.92:/tmp
            order_of_hosts = f'{self.all_host_ips[0]}'
        else:  # more than 1 gateway + destination host
            # the result will be something in this format:
            # scp -F {} -o 'ProxyJump 10.10.1.72,10.10.2.92' /home/vincent/Pictures/andy_apollo_imdb.jpg 10.10.3.82:/tmp
            order_of_hosts = ''
            for i, host in enumerate(self.all_host_ips):
                if i == 0:
                    order_of_hosts += f'{host}'
                elif i < len(self.all_host_ips) - 1:
                    order_of_hosts += f',{host}'
                else:
                    order_of_hosts += f' {host}'  # determine if this branch is still necessary.

        runtime_param = f"scp -v -F {self.path_ssh_cfg} -o 'ProxyJump {order_of_hosts}' " \
                        f"{self._path_to_agent_module} "
        runtime_param += f'{self.last_host_ip}:/tmp'
        self._logger.debug(runtime_param)
        return runtime_param

    def start(self):
        """Composes a SSH runtime param command and prints successful authentications."""
        result = False
        try:
            self.child = pexpect.spawn(self._generate_ssh_runtime_param(), env={"TERM": "dumb"}, timeout=10)
            # self.process.setecho(False)  # doesn't seem to have effect
            # self.process.waitnoecho()  # doesn't seem to have effect
            self._logger.debug('going through the stream to match patterns')
            for hostname in self.all_host_ips:
                # according to the documentation, "If you wish to read up to the end of the child's output -
                # without generating an EOF exception then use the expect(pexpect.EOF) method."
                # but apparently this doesn't work in a shell within a shell (SSH spawns a new shell)
                index = self.child.expect(
                    [f'Authenticated to {hostname}', 'Last failed login:', 'Last login:', 'socket error',
                     'not accessible', 'fingerprint', 'open failed: connect failed:', 'No such file', pexpect.TIMEOUT])
                if index == 0:
                    self._logger.info('authenticated to %s', hostname)  # logger level is "info" to inform user
                    self.authenticated_hosts.append(hostname)
                    result = True
                elif index == 1:
                    self._logger.debug('there were failed login attempts')
                    result = True
                elif index == 2:
                    self._logger.debug('there were no failed login attempts')
                    result = True
                elif index == 3:
                    self._logger.error('socket error. probable cause: SCP service on proxy or target machine disabled')
                    self.child.terminate()
                elif index == 4:
                    self._logger.error('the identity file is not accessible')
                    self.child.terminate()
                elif index == 5:
                    self._logger.warning('warning: hostname automatically added to list of known hosts')
                    self.child.sendline('yes')
                elif index == 6:
                    self._logger.error('SCP could not connect to %s', hostname)
                    self.child.terminate()
                elif index == 7:
                    pass
                elif index == 8:
                    self._logger.error('TIMEOUT exception was thrown. SCP could probably not connect to %s', hostname)
                    self.child.terminate()
                else:
                    self._logger.error('unknown state reached')
            self.child.expect(pexpect.EOF)  # the buffer has to be 'read' continuously, otherwise Pexpect deteriorates
        except pexpect.exceptions.ExceptionPexpect:
            self._logger.error('EOF is read; SCP has exited abnormally.')
            self.child.terminate()
        return result
