# -*- coding: binary -*-

module Msf
module Session

###
#
# This class implements the stubs that are needed to provide an interactive
# session.
#
###
module Interactive

  #
  # Interactive sessions by default may interact with the local user input
  # and output.
  #
  include Rex::Ui::Interactive

  #
  # Initializes the session.
  #
  def initialize(rstream, opts={})
    # A nil is passed in the case of non-stream interactive sessions (Meterpreter)
    if rstream
      self.rstream = rstream
      begin
        @peer_info = rstream.peerinfo
      rescue ::Exception
      end
    end
    super()
  end

  #
  # Returns that, yes, indeed, this session supports going interactive with
  # the user.
  #
  def interactive?
    true
  end

  #
  # Returns the local information.
  #
  def tunnel_local
    return @local_info if @local_info
    begin
      @local_info = rstream.localinfo
    rescue ::Exception => e
      elog('Interactive#tunnel_local error', error: e)
      @local_info = '127.0.0.1'
    end
  end

  #
  # Returns the remote peer information.
  #
  def tunnel_peer
    return @peer_info if @peer_info
    begin
      @peer_info = rstream.peerinfo
    rescue ::Exception => e
      elog('Interactive#tunnel_peer error', error: e)
      @peer_info = '127.0.0.1'
    end
  end

  def comm_channel
    return @comm_info if @comm_info
    if rstream.respond_to?(:channel) && rstream.channel.respond_to?(:client)
      @comm_info = "via session #{rstream.channel.client.sid}" if rstream.channel.client.respond_to?(:sid)
    end
  end

  #
  # Run an arbitrary command as if it came from user input.
  #
  def run_cmd(cmd)
  end

  #
  # Terminate the session
  #
  def kill
    self.reset_ui
    self.cleanup
    super()
  end

  #
  # Closes rstream.
  #
  def cleanup
    begin
      self.interacting = false if self.interactive?
      rstream.close if (rstream)
    rescue ::Exception
    end

    rstream = nil
    super
  end

  #
  # The remote stream handle.  Must inherit from Rex::IO::Stream.
  #
  attr_accessor :rstream

protected

  #
  # Stub method that is meant to handler interaction.
  #
  def _interact
    framework.events.on_session_interact(self)
  end

  #
  # Check to see if the user wants to abort.
  #
  def _interrupt
    begin
      intent = user_want_abort?
      # Judge the user wants to abort the reverse shell session
      # Or just want to abort the process running on the target machine
      # If the latter, just send ASCII Control Character \u0003 (End of Text) to the socket fd
      # The character will be handled by the line dicipline program of the pseudo-terminal on target machine
      # It will send the SEGINT singal to the foreground process
      if !intent
        # TODO: Check the shell is interactive or not
        # If the current shell is not interactive, the ASCII Control Character will not work
        if abort_foreground_supported
          print_status("Aborting foreground process in the shell session")
          abort_foreground
        end
        return
      end
    rescue Interrupt
      # The user hit ctrl-c while we were handling a ctrl-c. Ignore
    end
    true
  end

  def abort_foreground_supported
    true
  end

  def abort_foreground
    self.rstream.write("\u0003")
  end

  def _usr1
    # A simple signal to exit vim in reverse shell
    # Just for fun
    # Make sure you have already executed `shell` meta-shell command to pop up an interactive shell
    self.rstream.write("\x1B\x1B\x1B:q!\r")
  end

  #
  # Check to see if we should suspend.
  #
  def _suspend
    # Ask the user if they would like to background the session
    intent = prompt_yesno("Background session #{name}?")
    if !intent
      # User does not want to background the current session
      # Assuming the target is *nix, we'll forward CTRL-Z to the foreground process on the target
      if !(self.platform=="windows" && self.type =="shell")
        print_status("Backgrounding foreground process in the shell session")
        self.rstream.write("\u001A")
      end
      return
    end
    self.interacting = false
  end

  #
  # If the session reaches EOF, deregister it.
  #
  def _interact_complete
    framework.events.on_session_interact_completed()
    framework.sessions.deregister(self, "User exit")
  end

  #
  # Checks to see if the user wants to abort.
  #
  def user_want_abort?
    prompt_yesno("Abort session #{name}?")
  end

end

end
end
