# -*- coding: binary -*-
require 'set'
require 'rex/post/meterpreter'
require 'rex'

module Rex
module Post
module Meterpreter
module Ui

###
#
# Core meterpreter client commands that provide only the required set of
# commands for having a functional meterpreter client<->server instance.
#
###
class Console::CommandDispatcher::Core

  include Console::CommandDispatcher

  #
  # Initializes an instance of the core command set using the supplied shell
  # for interactivity.
  #
  def initialize(shell)
    super

    self.extensions = []
    self.bgjobs     = []
    self.bgjob_id   = 0

    # keep a lookup table to refer to transports by index
    @transport_map = {}
  end

  @@irb_opts = Rex::Parser::Arguments.new(
    '-h' => [false, 'Help menu.'             ],
    '-e' => [true,  'Expression to evaluate.']
  )

  @@load_opts = Rex::Parser::Arguments.new(
    '-h' => [false, 'Help menu.'                    ],
    '-l' => [false, 'List all available extensions.']
  )

  #
  # List of supported commands.
  #
  def commands
    cmds = {
      '?'                        => 'Help menu',
      'background'               => 'Backgrounds the current session',
      'bg'                       => 'Alias for background',
      'close'                    => 'Closes a channel',
      'channel'                  => 'Displays information or control active channels',
      'exit'                     => 'Terminate the meterpreter session',
      'help'                     => 'Help menu',
      'irb'                      => 'Open an interactive Ruby shell on the current session',
      'pry'                      => 'Open the Pry debugger on the current session',
      'use'                      => 'Deprecated alias for "load"',
      'load'                     => 'Load one or more meterpreter extensions',
      'machine_id'               => 'Get the MSF ID of the machine attached to the session',
      'secure'                   => '(Re)Negotiate TLV packet encryption on the session',
      'guid'                     => 'Get the session GUID',
      'quit'                     => 'Terminate the meterpreter session',
      'resource'                 => 'Run the commands stored in a file',
      'uuid'                     => 'Get the UUID for the current session',
      'read'                     => 'Reads data from a channel',
      'run'                      => 'Executes a meterpreter script or Post module',
      'bgrun'                    => 'Executes a meterpreter script as a background thread',
      'bgkill'                   => 'Kills a background meterpreter script',
      'sessions'                 => 'Quickly switch to another session',
      'bglist'                   => 'Lists running background scripts',
      'write'                    => 'Writes data to a channel',
      'enable_unicode_encoding'  => 'Enables encoding of unicode strings',
      'disable_unicode_encoding' => 'Disables encoding of unicode strings',
      'migrate'                  => 'Migrate the server to another process',
      'pivot'                    => 'Manage pivot listeners',
      # transport related commands
      'detach'                   => 'Detach the meterpreter session (for http/https)',
      'sleep'                    => 'Force Meterpreter to go quiet, then re-establish session',
      'transport'                => 'Manage the transport mechanisms',
      'get_timeouts'             => 'Get the current session timeout values',
      'set_timeouts'             => 'Set the current session timeout values',
      'ssl_verify'               => 'Modify the SSL certificate verification setting'
    }

    if msf_loaded?
      cmds['info'] = 'Displays information about a Post module'
    end

    reqs = {
      'load'         => [COMMAND_ID_CORE_LOADLIB],
      'machine_id'   => [COMMAND_ID_CORE_MACHINE_ID],
      'migrate'      => [COMMAND_ID_CORE_MIGRATE],
      'pivot'        => [COMMAND_ID_CORE_PIVOT_ADD, COMMAND_ID_CORE_PIVOT_REMOVE],
      'secure'       => [COMMAND_ID_CORE_NEGOTIATE_TLV_ENCRYPTION],
      # channel related commands
      'read'         => [COMMAND_ID_CORE_CHANNEL_READ],
      'write'        => [COMMAND_ID_CORE_CHANNEL_WRITE],
      'close'        => [COMMAND_ID_CORE_CHANNEL_CLOSE],
      # transport related commands
      'sleep'        => [COMMAND_ID_CORE_TRANSPORT_SLEEP],
      'ssl_verify'   => [COMMAND_ID_CORE_TRANSPORT_GETCERTHASH, COMMAND_ID_CORE_TRANSPORT_SETCERTHASH],
      'transport'    => [
        COMMAND_ID_CORE_TRANSPORT_ADD,
        COMMAND_ID_CORE_TRANSPORT_CHANGE,
        COMMAND_ID_CORE_TRANSPORT_LIST,
        COMMAND_ID_CORE_TRANSPORT_NEXT,
        COMMAND_ID_CORE_TRANSPORT_PREV,
        COMMAND_ID_CORE_TRANSPORT_REMOVE
      ],
      'get_timeouts' => [COMMAND_ID_CORE_TRANSPORT_SET_TIMEOUTS],
      'set_timeouts' => [COMMAND_ID_CORE_TRANSPORT_SET_TIMEOUTS],
    }

    # XXX: Remove this line once the payloads gem has had another major version bump from 2.x to 3.x and
    # rapid7/metasploit-payloads#451 has been landed to correct the `enumextcmd` behavior on Windows. Until then, skip
    # filtering for Windows which supports all the filtered commands anyways. This is not the only instance of this
    # workaround.
    reqs.clear if client.base_platform == 'windows'

    filter_commands(cmds, reqs)
  end

  #
  # Core baby.
  #
  def name
    'Core'
  end

  @@pivot_opts = Rex::Parser::Arguments.new(
    '-t' => [true, 'Pivot listener type'],
    '-i' => [true, 'Identifier of the pivot to remove'],
    '-l' => [true, 'Host address to bind to (if applicable)'],
    '-n' => [true, 'Name of the listener entity (if applicable)'],
    '-a' => [true, 'Architecture of the stage to generate'],
    '-p' => [true, 'Platform of the stage to generate'],
    '-h' => [false, 'View help']
  )

  @@pivot_supported_archs = [Rex::Arch::ARCH_X64, Rex::Arch::ARCH_X86]
  @@pivot_supported_platforms = ['windows']

  def cmd_pivot_help
    print_line('Usage: pivot <list|add|remove> [options]')
    print_line
    print_line('Manage pivot listeners on the target.')
    print_line
    print_line(@@pivot_opts.usage)
    print_line
    print_line('Supported pivot types:')
    print_line('     - pipe (using named pipes over SMB)')
    print_line('Supported architectures:')
    @@pivot_supported_archs.each do |a|
      print_line('     - ' + a)
    end
    print_line('Supported platforms:')
    print_line('     - windows')
    print_line
    print_line("eg.    pivot add -t pipe -l 192.168.0.1 -n msf-pipe -a #{@@pivot_supported_archs.first} -p windows")
    print_line("       pivot list")
    print_line("       pivot remove -i 1")
    print_line
  end

  def cmd_pivot_tabs(str, words)
    return %w[list add remove] + @@pivot_opts.option_keys if words.length == 1

    case words[-1]
    when '-a'
      return @@pivot_supported_archs
    when '-i'
      matches = []
      client.pivot_listeners.each_value { |v| matches << v.id.unpack('H*')[0] }
      return matches
    when '-p'
      return @@pivot_supported_platforms
    when '-t'
      return ['pipe']
    when 'add', 'remove'
      return @@pivot_opts.option_keys
    end

    []
  end

  def cmd_pivot(*args)
    if args.length == 0 || args.include?('-h')
      cmd_pivot_help
      return true
    end

    opts = {}
    @@pivot_opts.parse(args) { |opt, idx, val|
      case opt
      when '-t'
        opts[:type] = val
      when '-i'
        opts[:guid] = val
      when '-l'
        opts[:lhost] = val
      when '-n'
        opts[:name] = val
      when '-a'
        opts[:arch] = val
      when '-p'
        opts[:platform] = val
      end
    }

    # first parameter is the command
    case args[0]
    when 'remove', 'del', 'delete', 'rm'
      unless opts[:guid]
        print_error('Pivot listener ID must be specified (-i)')
        return false
      end

      unless opts[:guid] =~ /^[0-9a-f]{32}/i && opts[:guid].length == 32
        print_error("Invalid pivot listener ID: #{opts[:guid]}")
        return false
      end

      listener_id = [opts[:guid]].pack('H*')
      unless client.find_pivot_listener(listener_id)
        print_error("Unknown pivot listener ID: #{opts[:guid]}")
        return false
      end

      Pivot.remove_listener(client, listener_id)
      print_good("Successfully removed pivot: #{opts[:guid]}")
    when 'list', 'show', 'print'
      if client.pivot_listeners.length > 0
        tbl = Rex::Text::Table.new(
          'Header'  => 'Currently active pivot listeners',
          'Indent'  => 4,
          'Columns' => ['Id', 'URL', 'Stage'])

        client.pivot_listeners.each do |k, v|
          tbl << v.to_row
        end
        print_line
        print_line(tbl.to_s)
      else
        print_status('There are no active pivot listeners')
      end
    when 'add'
      unless opts[:type]
        print_error('Pivot type must be specified (-t)')
        return false
      end

      unless opts[:arch]
        print_error('Architecture must be specified (-a)')
        return false
      end
      unless @@pivot_supported_archs.include?(opts[:arch])
        print_error("Unknown or unsupported architecture: #{opts[:arch]}")
        return false
      end

      unless opts[:platform]
        print_error('Platform must be specified (-p)')
        return false
      end
      unless @@pivot_supported_platforms.include?(opts[:platform])
        print_error("Unknown or unsupported platform: #{opts[:platform]}")
        return false
      end

      # currently only one pivot type supported, more to come we hope
      case opts[:type]
      when 'pipe'
        pivot_add_named_pipe(opts)
      else
        print_error("Unknown pivot type: #{opts[:type]}")
        return false
      end
    else
      print_error("Unknown command: #{args[0]}")
    end
  end

  def pivot_add_named_pipe(opts)
    unless opts[:lhost]
      print_error('Pipe host must be specified (-l)')
      return false
    end

    unless opts[:name]
      print_error('Pipe name must be specified (-n)')
      return false
    end

    # reconfigure the opts so that they can be passed to the setup function
    opts[:pipe_host] = opts[:lhost]
    opts[:pipe_name] = opts[:name]
    Pivot.create_named_pipe_listener(client, opts)
    print_good("Successfully created #{opts[:type]} pivot.")
  end

  def cmd_sessions_help
    print_line('Usage: sessions <id>')
    print_line
    print_line('Interact with a different session Id.')
    print_line('This works the same as calling this from the MSF shell: sessions -i <session id>')
    print_line
  end

  def cmd_sessions(*args)
    if args.length == 0 || args[0].to_i == 0
      cmd_sessions_help
    elsif args[0].to_s == client.name.to_s
      print_status("Session #{client.name} is already interactive.")
    else
      print_status("Backgrounding session #{client.name}...")
      # store the next session id so that it can be referenced as soon
      # as this session is no longer interacting
      client.next_session = args[0]
      client.interacting = false
    end
  end

  def cmd_secure
    print_status('Negotiating new encryption key ...')
    client.core.secure
    print_good('Done.')
  end

  def cmd_background_help
    print_line('Usage: background')
    print_line
    print_line('Stop interacting with this session and return to the parent prompt')
    print_line
  end

  def cmd_background
    print_status("Backgrounding session #{client.name}...")
    client.interacting = false
  end

  alias cmd_bg cmd_background
  alias cmd_bg_help cmd_background_help

  #
  # Displays information about active channels
  #
  @@channel_opts = Rex::Parser::Arguments.new(
    '-c' => [ true,  'Close the given channel.' ],
    '-k' => [ true,  'Close the given channel.' ],
    '-K' => [ false, 'Close all channels.' ],
    '-i' => [ true,  'Interact with the given channel.' ],
    '-l' => [ false, 'List active channels.' ],
    '-r' => [ true,  'Read from the given channel.' ],
    '-w' => [ true,  'Write to the given channel.' ],
    '-h' => [ false, 'Help menu.' ])

  def cmd_channel_help
    print_line('Usage: channel [options]')
    print_line
    print_line('Displays information about active channels.')
    print_line(@@channel_opts.usage)
  end

  #
  # Performs operations on the supplied channel.
  #
  def cmd_channel(*args)
    if args.empty? || args.include?('-h')
      cmd_channel_help
      return
    end

    mode = nil
    chan = nil

    # Parse options
    @@channel_opts.parse(args) { |opt, idx, val|
      case opt
      when '-l'
        mode = :list
      when '-c', '-k'
        mode = :close
        chan = val
      when '-i'
        mode = :interact
        chan = val
      when '-r'
        mode = :read
        chan = val
      when '-w'
        mode = :write
        chan = val
      when '-K'
        mode = :kill_all
      end

      if @@channel_opts.arg_required?(opt)
        unless chan
          print_error('Channel ID required')
          return
        end
      end
    }

    case mode
    when :list
      tbl = Rex::Text::Table.new(
        'Indent'  => 4,
        'Columns' => ['Id', 'Class', 'Type'])
      items = 0

      client.channels.each_pair { |cid, channel|
        tbl << [ cid, channel.class.cls, channel.type ]
        items += 1
      }

      if (items == 0)
        print_line('No active channels.')
      else
        print("\n" + tbl.to_s + "\n")
      end
    when :close
      cmd_close(chan)
    when :interact
      cmd_interact(chan)
    when :read
      cmd_read(chan)
    when :write
      cmd_write(chan)
    when :kill_all
      if client.channels.empty?
        print_line('No active channels.')
        return
      end

      print_line('Killing all channels...')
      client.channels.each_pair do |id, channel|
        channel._close
      rescue ::StandardError
        print_error("Failed when trying to kill channel: #{id}")
      end
      print_line('Killed all channels.')
    else
      # No mode, no service.
      return true
    end
  end

  def cmd_channel_tabs(str, words)
    case words.length
    when 1
      @@channel_opts.option_keys
    when 2
      case words[1]
      when '-k', '-c', '-i', '-r', '-w'
        tab_complete_channels
      else
        []
      end
    else
      []
    end
  end

  def cmd_close_help
    print_line('Usage: close <channel_id>')
    print_line
    print_line('Closes the supplied channel.')
    print_line
  end

  #
  # Closes a supplied channel.
  #
  def cmd_close(*args)
    if args.empty? || args.include?('-h')
      cmd_close_help
      return true
    end

    cid = args[0].to_i
    channel = client.find_channel(cid)

    unless channel
      print_error('Invalid channel identifier specified.')
      return true
    end

    channel._close # Issue #410

    print_status("Closed channel #{cid}.")
  end

  def cmd_close_tabs(str, words)
    return [] if words.length > 1

    return tab_complete_channels
  end

  #
  # Terminates the meterpreter session.
  #
  def cmd_exit(*args)
    print_status('Shutting down Meterpreter...')
    client.core.shutdown rescue nil
    client.shutdown_passive_dispatcher
    shell.stop
  end

  alias cmd_quit cmd_exit

  def cmd_detach_help
    print_line('Detach from the victim. Only possible for non-stream sessions (http/https)')
    print_line
    print_line('The victim will continue to attempt to call back to the handler until it')
    print_line('successfully connects (which may happen immediately if you have a handler')
    print_line('running in the background), or reaches its expiration.')
    print_line
    print_line("This session may #{client.passive_service ? "" : "NOT"} be detached.")
    print_line
  end

  #
  # Disconnects the session
  #
  def cmd_detach(*args)
    unless client.passive_service
      print_error('The detach command is not applicable with the current transport')
      return
    end
    client.shutdown_passive_dispatcher
    shell.stop
  end

  def cmd_interact_help
    print_line('Usage: interact <channel_id>')
    print_line
    print_line('Interacts with the supplied channel.')
    print_line
  end

  #
  # Interacts with a channel.
  #
  def cmd_interact(*args)
    if args.empty? || args.include?('-h')
      cmd_info_help
      return true
    end

    cid = args[0].to_i
    channel = client.find_channel(cid)

    if channel
      print_line("Interacting with channel #{cid}...\n")

      shell.interact_with_channel(channel)
    else
      print_error('Invalid channel identifier specified.')
    end
  end

  alias cmd_interact_tabs cmd_close_tabs

  def cmd_irb_help
    print_line('Usage: irb')
    print_line
    print_line('Open an interactive Ruby shell on the current session.')
    print @@irb_opts.usage
  end

  def cmd_irb_tabs(str, words)
    return [] if words.length > 1
    @@irb_opts.option_keys
  end

  #
  # Open an interactive Ruby shell on the current session
  #
  def cmd_irb(*args)
    expressions = []

    # Parse the command options
    @@irb_opts.parse(args) do |opt, idx, val|
      case opt
      when '-e'
        expressions << val
      when '-h'
        return cmd_irb_help
      end
    end

    session = client
    framework = client.framework

    if expressions.empty?
      print_status('Starting IRB shell...')
      print_status("You are in the \"client\" (session) object\n")
      Rex::Ui::Text::Shell::HistoryManager.with_context(name: :irb) do
        Rex::Ui::Text::IrbShell.new(client).run
      end
    else
      # XXX: No vprint_status here
      if framework.datastore['VERBOSE'].to_s == 'true'
        print_status("You are executing expressions in #{binding.receiver}")
      end

      expressions.each { |expression| eval(expression, binding) }
    end
  end

  def cmd_pry_help
    print_line 'Usage: pry'
    print_line
    print_line 'Open the Pry debugger on the current session.'
    print_line
  end

  #
  # Open the Pry debugger on the current session
  #
  def cmd_pry(*args)
    if args.include?('-h')
      cmd_pry_help
      return
    end

    begin
      require 'pry'
    rescue LoadError
      print_error('Failed to load Pry, try "gem install pry"')
      return
    end

    print_status('Starting Pry shell...')
    print_status("You are in the \"client\" (session) object\n")

    Pry.config.history_load = false
    Rex::Ui::Text::Shell::HistoryManager.with_context(history_file: Msf::Config.pry_history, name: :pry) do
      client.pry
    end
  end

  @@set_timeouts_opts = Rex::Parser::Arguments.new(
    '-c' => [true, 'Comms timeout (seconds)'],
    '-x' => [true, 'Expiration timout (seconds)'],
    '-t' => [true, 'Retry total time (seconds)'],
    '-w' => [true, 'Retry wait time (seconds)'],
    '-h' => [false, 'Help menu'])

  def cmd_set_timeouts_help
    print_line('Usage: set_timeouts [options]')
    print_line
    print_line('Set the current timeout options.')
    print_line('Any or all of these can be set at once.')
    print_line(@@set_timeouts_opts.usage)
  end

  def cmd_set_timeouts_tabs(str, words)
    return [] if words.length > 1
    @@set_timeouts_opts.option_keys
  end

  def cmd_set_timeouts(*args)
    if args.length == 0 || args.include?('-h')
      cmd_set_timeouts_help
      return
    end

    opts = {}

    @@set_timeouts_opts.parse(args) do |opt, idx, val|
      case opt
      when '-c'
        opts[:comm_timeout] = val.to_i if val
      when '-x'
        opts[:session_exp] = val.to_i if val
      when '-t'
        opts[:retry_total] = val.to_i if val
      when '-w'
        opts[:retry_wait] = val.to_i if val
      end
    end

    if opts.keys.length == 0
      print_error('No options set')
    else
      timeouts = client.core.set_transport_timeouts(opts)
      print_timeouts(timeouts)
    end
  end

  def cmd_get_timeouts(*args)
    # Calling set without passing values is the same as
    # getting all the current timeouts
    timeouts = client.core.set_transport_timeouts
    print_timeouts(timeouts)
  end

  def print_timeouts(timeouts)
    if timeouts[:session_exp]
      print_line("Session Expiry  : @ #{(::Time.now + timeouts[:session_exp]).strftime('%Y-%m-%d %H:%M:%S')}")
    end
    if timeouts[:comm_timeout]
      print_line("Comm Timeout    : #{timeouts[:comm_timeout]} seconds")
    end
    if timeouts[:retry_total]
      print_line("Retry Total Time: #{timeouts[:retry_total]} seconds")
    end
    if timeouts[:retry_wait]
      print_line("Retry Wait Time : #{timeouts[:retry_wait]} seconds")
    end
  end

  #
  # Get the machine ID of the target
  #
  def cmd_machine_id(*args)
    client.machine_id = client.core.machine_id unless client.machine_id
    print_good("Machine ID: #{client.machine_id}")
  end

  #
  # Get the session GUID
  #
  def cmd_guid(*args)
    parts = client.session_guid.unpack('H*')[0]
    guid = [parts[0, 8], parts[8, 4], parts[12, 4], parts[16, 4], parts[20, 12]].join('-')
    print_good("Session GUID: #{guid}")
  end

  #
  # Get the machine ID of the target (should always be up to date locally)
  #
  def cmd_uuid(*args)
    print_good("UUID: #{client.payload_uuid}")
  end

  #
  # Arguments for ssl verification
  #
  @@ssl_verify_opts = Rex::Parser::Arguments.new(
    '-e' => [ false, 'Enable SSL certificate verification' ],
    '-d' => [ false, 'Disable SSL certificate verification' ],
    '-q' => [ false, 'Query the status of SSL certificate verification' ],
    '-h' => [ false, 'Help menu' ])

  #
  # Help for ssl verification
  #
  def cmd_ssl_verify_help
    print_line('Usage: ssl_verify [options]')
    print_line
    print_line('Change and query the current setting for SSL verification')
    print_line('Only one of the following options can be used at a time')
    print_line(@@ssl_verify_opts.usage)
  end

  #
  # Handle the SSL verification querying and setting function.
  #
  def cmd_ssl_verify(*args)
    if ( args.length == 0 or args.include?("-h") )
      cmd_ssl_verify_help
      return
    end

    unless client.passive_service && client.sock.type? == 'tcp-ssl'
      print_error('The ssl_verify command is not applicable with the current transport')
      return
    end

    query = false
    enable = false
    disable = false

    settings = 0

    @@ssl_verify_opts.parse(args) do |opt, idx, val|
      case opt
      when '-q'
        query = true
        settings += 1
      when '-e'
        enable = true
        settings += 1
      when '-d'
        disable = true
        settings += 1
      end
    end

    # Make sure only one action has been chosen
    if settings != 1
      cmd_ssl_verify_help
      return
    end

    if query
      hash = client.core.get_ssl_hash_verify
      if hash
        print_good("SSL verification is enabled. SHA1 Hash: #{hash.unpack("H*")[0]}")
      else
        print_good('SSL verification is disabled.')
      end

    elsif enable
      hash = client.core.enable_ssl_hash_verify
      if hash
        print_good("SSL verification has been enabled. SHA1 Hash: #{hash.unpack("H*")[0]}")
      else
        print_error('Failed to enable SSL verification')
      end

    else
      if client.core.disable_ssl_hash_verify
        print_good('SSL verification has been disabled')
      else
        print_error('Failed to disable SSL verification')
      end
    end

  end

  #
  # Display help for the sleep.
  #
  def cmd_sleep_help
    print_line('Usage: sleep <time>')
    print_line
    print_line('  time: Number of seconds to wait (positive integer)')
    print_line
    print_line('  This command tells Meterpreter to go to sleep for the specified')
    print_line('  number of seconds. Sleeping will result in the transport being')
    print_line('  shut down and restarted after the designated timeout.')
  end

  #
  # Handle the sleep command.
  #
  def cmd_sleep(*args)
    if args.empty? || args.include?('-h')
      cmd_sleep_help
      return
    end

    seconds = args.shift.to_i

    if seconds <= 0
      cmd_sleep_help
      return
    end

    print_status("Telling the target instance to sleep for #{seconds} seconds ...")
    if client.core.transport_sleep(seconds)
      print_good("Target instance has gone to sleep, terminating current session.")
      client.shutdown_passive_dispatcher
      shell.stop
    else
      print_error("Target instance failed to go to sleep.")
    end
  end

  #
  # Arguments for transport switching
  #
  @@transport_opts = Rex::Parser::Arguments.new(
    '-t' => [true, "Transport type: #{Rex::Post::Meterpreter::ClientCore::VALID_TRANSPORTS.keys.join(', ')}"],
    '-l' => [true, 'LHOST parameter (for reverse transports)'],
    '-p' => [true, 'LPORT parameter'],
    '-i' => [true, 'Specify transport by index (currently supported: remove)'],
    '-u' => [true, 'Local URI for HTTP/S transports (used when adding/changing transports with a custom LURI)'],
    '-c' => [true, 'SSL certificate path for https transport verification (optional)'],
    '-A' => [true, 'User agent for HTTP/S transports (optional)'],
    '-H' => [true, 'Proxy host for HTTP/S transports (optional)'],
    '-P' => [true, 'Proxy port for HTTP/S transports (optional)'],
    '-U' => [true, 'Proxy username for HTTP/S transports (optional)'],
    '-N' => [true, 'Proxy password for HTTP/S transports (optional)'],
    '-B' => [true, 'Proxy type for HTTP/S transports (optional: http, socks; default: http)'],
    '-C' => [true, 'Comms timeout (seconds) (default: same as current session)'],
    '-X' => [true, 'Expiration timout (seconds) (default: same as current session)'],
    '-T' => [true, 'Retry total time (seconds) (default: same as current session)'],
    '-W' => [true, 'Retry wait time (seconds) (default: same as current session)'],
    '-v' => [false, 'Show the verbose format of the transport list'],
    '-h' => [false, 'Help menu'])

  #
  # Display help for transport management.
  #
  def cmd_transport_help
    print_line('Usage: transport <list|change|add|next|prev|remove> [options]')
    print_line
    print_line('   list: list the currently active transports.')
    print_line('    add: add a new transport to the transport list.')
    print_line(' change: same as add, but changes directly to the added entry.')
    print_line('   next: jump to the next transport in the list (no options).')
    print_line('   prev: jump to the previous transport in the list (no options).')
    print_line(' remove: remove an existing, non-active transport.')
    print_line(@@transport_opts.usage)
  end

  def cmd_transport_tabs(str, words)
    return %w[list change add next prev remove] + @@transport_opts.option_keys if words.length == 1

    case words[-1]
    when '-c'
      return tab_complete_filenames(str, words)
    when '-i'
      return (1..client.core.transport_list[:transports].length).to_a.map!(&:to_s)
    when '-l'
      return tab_complete_source_address
    when '-t'
      return %w[reverse_tcp reverse_http reverse_https bind_tcp]
    when 'add', 'remove', 'change'
      return @@transport_opts.option_keys
    end

    []
  end

  def update_transport_map
    result = client.core.transport_list
    @transport_map.clear
    sorted_by_url = result[:transports].sort_by { |k| k[:url] }
    sorted_by_url.each_with_index { |t, i| @transport_map[i+1] = t }
  end

  #
  # Manage transports
  #
  def cmd_transport(*args)
    if ( args.length == 0 or args.include?("-h") )
      cmd_transport_help
      return
    end

    command = args.shift
    unless ['list', 'add', 'change', 'prev', 'next', 'remove'].include?(command)
      cmd_transport_help
      return
    end

    opts = {
      :uuid         => client.payload_uuid,
      :transport    => nil,
      :lhost        => nil,
      :lport        => nil,
      :ua           => nil,
      :proxy_host   => nil,
      :proxy_port   => nil,
      :proxy_type   => nil,
      :proxy_user   => nil,
      :proxy_pass   => nil,
      :comm_timeout => nil,
      :session_exp  => nil,
      :retry_total  => nil,
      :retry_wait   => nil,
      :cert         => nil,
      :verbose      => false
    }

    valid = true
    transport_index = 0
    @@transport_opts.parse(args) do |opt, idx, val|
      case opt
      when '-c'
        opts[:cert] = val
      when '-i'
        transport_index = val.to_i
      when '-u'
        opts[:luri] = val
      when '-H'
        opts[:proxy_host] = val
      when '-P'
        opts[:proxy_port] = val.to_i
      when '-B'
        opts[:proxy_type] = val
      when '-U'
        opts[:proxy_user] = val
      when '-N'
        opts[:proxy_pass] = val
      when '-A'
        opts[:ua] = val
      when '-C'
        opts[:comm_timeout] = val.to_i if val
      when '-X'
        opts[:session_exp] = val.to_i if val
      when '-T'
        opts[:retry_total] = val.to_i if val
      when '-W'
        opts[:retry_wait] = val.to_i if val
      when '-p'
        opts[:lport] = val.to_i if val
      when '-l'
        opts[:lhost] = val
      when '-v'
        opts[:verbose] = true
      when '-t'
        unless client.core.valid_transport?(val)
          cmd_transport_help
          return
        end
        opts[:transport] = val
      else
        valid = false
      end
    end

    unless valid
      cmd_transport_help
      return
    end

    update_transport_map

    case command
    when 'list'
      result = client.core.transport_list

      current_transport_url = result[:transports].first[:url]

      sorted_by_url = result[:transports].sort_by { |k| k[:url] }

      # this will output the session timeout first
      print_timeouts(result)

      columns = ['ID', 'Curr', 'URL', 'Comms T/O', 'Retry Total', 'Retry Wait']

      if opts[:verbose]
        columns << 'User Agent'
        columns << 'Proxy Host'
        columns << 'Proxy User'
        columns << 'Proxy Pass'
        columns << 'Cert Hash'
      end

      # next draw up a table of transport entries
      tbl = Rex::Text::Table.new(
        'SortIndex' => 0, # sort by ID
        'Indent'    => 4,
        'Columns'   => columns)

      sorted_by_url.each_with_index do |t, i|
        entry = [i + 1, current_transport_url == t[:url] ? '*' : '', t[:url],
                  t[:comm_timeout], t[:retry_total], t[:retry_wait]]

        if opts[:verbose]
          entry << t[:ua]
          entry << t[:proxy_host]
          entry << t[:proxy_user]
          entry << t[:proxy_pass]
          entry << (t[:cert_hash] || '').unpack("H*")[0]
        end

        tbl << entry
      end

      print("\n" + tbl.to_s + "\n")
    when 'next'
      print_status('Changing to next transport ...')
      if client.core.transport_next
        print_good('Successfully changed to the next transport, killing current session.')
        client.shutdown_passive_dispatcher
        shell.stop
      else
        print_error('Failed to change transport, please check the parameters')
      end
    when 'prev'
      print_status('Changing to previous transport ...')
      if client.core.transport_prev
        print_good('Successfully changed to the previous transport, killing current session.')
        client.shutdown_passive_dispatcher
        shell.stop
      else
        print_error('Failed to change transport, please check the parameters')
      end
    when 'change'
      print_status('Changing to new transport ...')
      if client.core.transport_change(opts)
        print_good("Successfully added #{opts[:transport]} transport, killing current session.")
        client.shutdown_passive_dispatcher
        shell.stop
      else
        print_error('Failed to change transport, please check the parameters')
      end
    when 'add'
      print_status('Adding new transport ...')
      if client.core.transport_add(opts)
        print_good("Successfully added #{opts[:transport]} transport.")
      else
        print_error('Failed to add transport, please check the parameters')
      end
    when 'remove'
      if opts[:transport] && !opts[:transport].end_with?('_tcp') && opts[:uri].nil?
        print_error('HTTP/S transport specified without session URI')
        return
      end

      if !transport_index.zero? && @transport_map.has_key?(transport_index)
        # validate the URL
        url_to_delete = @transport_map[transport_index][:url]
        begin
          uri = URI.parse(url_to_delete)
          opts[:transport] = "reverse_#{uri.scheme}"
          opts[:lhost]     = uri.host
          opts[:lport]     = uri.port
          opts[:uri]       = uri.path[1..-2] if uri.scheme.include?('http')

        rescue URI::InvalidURIError
          print_error("Failed to parse URL: #{url_to_delete}")
          return
        end
      end

      print_status('Removing transport ...')
      if client.core.transport_remove(opts)
        print_good("Successfully removed #{opts[:transport]} transport.")
      else
        print_error('Failed to remove transport, please check the parameters')
      end
    end
  end

  @@migrate_opts = Rex::Parser::Arguments.new(
    '-P' => [true, 'PID to migrate to.'],
    '-N' => [true, 'Process name to migrate to.'],
    '-p' => [true, 'Writable path - Linux only (eg. /tmp).'],
    '-t' => [true, 'The number of seconds to wait for migration to finish (default: 60).'],
    '-h' => [false, 'Help menu.']
  )

  def cmd_migrate_help
    if client.platform == 'linux'
      print_line('Usage: migrate <<pid> | -P <pid> | -N <name>> [-p writable_path] [-t timeout]')
    else
      print_line('Usage: migrate <<pid> | -P <pid> | -N <name>> [-t timeout]')
    end
    print_line
    print_line('Migrates the server instance to another process.')
    print_line('NOTE: Any open channels or other dynamic state will be lost.')
    print_line
  end

  #
  # Migrates the server to the supplied process identifier.
  #
  # @param args [Array<String>] Commandline arguments, -h or a pid. On linux
  #   platforms a path for the unix domain socket used for IPC.
  # @return [void]
  def cmd_migrate(*args)
    if args.length == 0 || args.any? { |arg| %w(-h --pid --name).include? arg }
      cmd_migrate_help
      return true
    end

    pid = nil
    writable_dir = nil
    opts = {
      timeout: nil
    }

    @@migrate_opts.parse(args) do |opt, idx, val|
      case opt
      when '-t'
        opts[:timeout] = val.to_i
      when '-p'
        writable_dir = val
      when '-P'
        unless val =~ /^\d+$/
          print_error("Not a PID: #{val}")
          return
        end
        pid = val.to_i
      when '-N'
        if val.to_s.empty?
          print_error('No process name provided')
          return
        end
        # this will migrate to the first process with a matching name
        unless (process = client.sys.process.processes.find { |p| p['name'] == val })
          print_error("Could not find process name #{val}")
          return
        end
        pid = process['pid']
      end
    end

    # we cannot migrate to another process until loaded stdapi
    unless extensions.include?('stdapi')
      print_error('Stdapi extension must be loaded.')
      return
    end

    unless pid
      unless (pid = args.first)
        print_error('A process ID or name argument must be provided')
        return
      end
      unless pid =~ /^\d+$/
        print_error("Not a PID: #{pid}")
        return
      end
      pid = pid.to_i
    end

    begin
      server = client.sys.process.open
    rescue Rex::TimeoutError, ::Timeout::Error => e
      elog('Server Timeout', error: e)
    rescue RequestError => e
      elog('Request Error', error: e)
    end

    service = client.pfservice

    # If we have any open port forwards, we need to close them down
    # otherwise we'll end up with local listeners which aren't connected
    # to valid channels in the migrated meterpreter instance.
    existing_relays = []

    if service
      service.each_tcp_relay do |lhost, lport, rhost, rport, opts|
        next unless opts['MeterpreterRelay']
        if existing_relays.empty?
          print_status('Removing existing TCP relays...')
        end
        if (service.stop_tcp_relay(lport, lhost))
          print_status("Successfully stopped TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
          existing_relays << {
            :lport => lport,
            :opts => opts
          }
        else
          print_error("Failed to stop TCP relay on #{lhost || '0.0.0.0'}:#{lport}")
          next
        end
      end
      unless existing_relays.empty?
        print_status("#{existing_relays.length} TCP relay(s) removed.")
      end
    end

    if pid == server.pid
      print_error("Process already running at PID #{pid}")
      return
    end

    server ? print_status("Migrating from #{server.pid} to #{pid}...") : print_status("Migrating to #{pid}")

    # Do this thang.
    client.core.migrate(pid, writable_dir, opts)

    print_status('Migration completed successfully.')

    # Update session info (we may have a new username)
    client.update_session_info

    unless existing_relays.empty?
      print_status('Recreating TCP relay(s)...')
      existing_relays.each do |r|
        client.pfservice.start_tcp_relay(r[:lport], r[:opts])
        print_status("Local TCP relay recreated: #{r[:opts]['LocalHost'] || '0.0.0.0'}:#{r[:lport]} <-> #{r[:opts]['PeerHost']}:#{r[:opts]['PeerPort']}")
      end
    end

  end

  def cmd_load_help
    print_line('Usage: load ext1 ext2 ext3 ...')
    print_line
    print_line('Loads a meterpreter extension module or modules.')
    print_line(@@load_opts.usage)
  end

  #
  # Loads one or more meterpreter extensions.
  #
  def cmd_load(*args)
    if args.length == 0
      args.unshift('-h')
    end

    @@load_opts.parse(args) { |opt, idx, val|
      case opt
      when '-l'
        exts = Set.new
        if extensions.include?('stdapi') && !client.sys.config.sysinfo['BuildTuple'].blank?
          # Use API to get list of extensions from the gem
          exts.merge(MetasploitPayloads::Mettle.available_extensions(client.sys.config.sysinfo['BuildTuple']))
        else
          exts.merge(client.binary_suffix.map { |suffix| MetasploitPayloads.list_meterpreter_extensions(suffix) }.flatten)
        end
        exts = exts.sort.uniq
        print(exts.to_a.join("\n") + "\n")

        return true
      when '-h'
        cmd_load_help
        return true
      end
    }

    # Load each of the modules
    args.each { |m|
      md = m.downcase

      # Temporary hack to pivot mimikatz over to kiwi until
      # everyone remembers to do it themselves
      if md == 'mimikatz'
        print_warning('The "mimikatz" extension has been replaced by "kiwi". Please use this in future.')
        md = 'kiwi'
      end

      modulenameprovided = md

      if client.binary_suffix and client.binary_suffix.size > 1
        client.binary_suffix.each { |s|
          if (md =~ /(.*)\.#{s}/ )
            md = $1
            break
          end
        }
      end

      if (extensions.include?(md))
        print_warning("The \"#{md}\" extension has already been loaded.")
        next
      end

      print("Loading extension #{md}...")

      begin
        # Use the remote side, then load the client-side
        if (client.core.use(modulenameprovided) == true)
          add_extension_client(md)

          if md == 'stdapi' && (client.exploit_datastore && !client.exploit_datastore['AutoLoadStdapi'] && client.exploit_datastore['AutoSystemInfo'])
            client.load_session_info
          end
        end
      rescue => ex
        print_line
        log_error("Failed to load extension: #{ex.message}")
        elog(ex)
        if ex.kind_of?(ExtensionLoadError) && ex.name
          # MetasploitPayloads and MetasploitPayloads::Mettle do things completely differently, build an array of
          # suggestion keys (binary_suffixes and Mettle build-tuples)
          suggestion_keys = MetasploitPayloads.list_meterpreter_extension_suffixes(ex.name) + MetasploitPayloads::Mettle.available_platforms(ex.name)
          suggestion_map = {
            # Extension Suffixes
            'jar' => 'java',
            'php' => 'php',
            'py' => 'python',
            'x64.dll' => 'windows/x64',
            'x86.dll' => 'windows',
            # Mettle Platforms
            'aarch64-iphone-darwin' => 'apple_ios/aarch64',
            'aarch64-linux-musl' => 'linux/aarch64',
            'arm-iphone-darwin' => 'apple_ios/armle',
            'armv5b-linux-musleabi' => 'linux/armbe',
            'armv5l-linux-musleabi' => 'linux/armle',
            'i486-linux-musl' => 'linux/x86',
            'mips64-linux-muslsf' => 'linux/mips64',
            'mipsel-linux-muslsf' => 'linux/mipsle',
            'mips-linux-muslsf' => 'linux/mipsbe',
            'powerpc64le-linux-musl' => 'linux/ppc64le',
            'powerpc-e500v2-linux-musl' => 'linux/ppce500v2',
            'powerpc-linux-muslsf' => 'linux/ppc',
            's390x-linux-musl' => 'linux/zarch',
            'x86_64-apple-darwin' => 'osx/x64',
            'x86_64-linux-musl' => 'linux/x64',
          }
          suggestions = suggestion_map.select { |k,_v| suggestion_keys.include?(k) }.values
          unless suggestions.empty?
            log_error("The \"#{ex.name}\" extension is supported by the following Meterpreter payloads:")
            suggestions.each do |suggestion|
              log_error("  - #{suggestion}/meterpreter*")
            end
          end
        end

        next
      end

      print_line('Success.')
    }

    return true
  end

  def cmd_load_tabs(str, words)
    tabs = Set.new
    if extensions.include?('stdapi') && !client.sys.config.sysinfo['BuildTuple'].blank?
      tabs.merge(MetasploitPayloads::Mettle.available_extensions(client.sys.config.sysinfo['BuildTuple']))
    else
      tabs.merge(client.binary_suffix.map { |suffix| MetasploitPayloads.list_meterpreter_extensions(suffix) }.flatten)
    end
    tabs = tabs.sort.uniq
    return tabs.to_a
  end

  def cmd_use(*args)
    #print_error("Warning: The 'use' command is deprecated in favor of 'load'")
    cmd_load(*args)
  end
  alias cmd_use_help cmd_load_help
  alias cmd_use_tabs cmd_load_tabs

  def cmd_read_help
    print_line('Usage: read <channel_id> [length]')
    print_line
    print_line('Reads data from the supplied channel.')
    print_line
  end

  #
  # Reads data from a channel.
  #
  def cmd_read(*args)
    if args.empty? || args.include?('-h')
      cmd_read_help
      return true
    end

    cid     = args[0].to_i
    length  = (args.length >= 2) ? args[1].to_i : 16384
    channel = client.find_channel(cid)

    unless channel
      print_error("Channel #{cid} is not valid.")
      return true
    end

    data = channel.read(length)

    if data && data.length
      print("Read #{data.length} bytes from #{cid}:\n\n#{data}\n")
    else
      print_error('No data was returned.')
    end

    return true
  end

  alias cmd_read_tabs cmd_close_tabs

  def cmd_run_help
    print_line('Usage: run <script> [arguments]')
    print_line
    print_line('Executes a ruby script or Metasploit Post module in the context of the')
    print_line('meterpreter session.  Post modules can take arguments in var=val format.')
    print_line('Example: run post/foo/bar BAZ=abcd')
    print_line
  end

  #
  # Executes a script in the context of the meterpreter session.
  #
  def cmd_run(*args)
    if args.empty? || args.first == '-h'
      cmd_run_help
      return true
    end

    # Get the script name
    begin
      script_name = args.shift
      # First try it as a Post module if we have access to the Metasploit
      # Framework instance.  If we don't, or if no such module exists,
      # fall back to using the scripting interface.
      if msf_loaded? && mod = client.framework.modules.create(script_name)
        original_mod = mod
        reloaded_mod = client.framework.modules.reload_module(original_mod)

        unless reloaded_mod
          error = client.framework.modules.module_load_error_by_path[original_mod.file_path]
          print_error("Failed to reload module: #{error}")

          return
        end

        opts = ''
        if reloaded_mod.is_a?(Msf::Exploit)
          # set the payload as one of the first options, allowing it to be overridden by the user
          opts << "PAYLOAD=#{client.via_payload.delete_prefix('payload/')}," if client.via_payload
        end

        opts  << (args + [ "SESSION=#{client.sid}" ]).join(',')
        result = reloaded_mod.run_simple(
          #'RunAsJob' => true,
          'LocalInput'  => shell.input,
          'LocalOutput' => shell.output,
          'OptionStr'   => opts
        )

        print_status("Session #{result.sid} created in the background.") if result.is_a?(Msf::Session)
      else
        # the rest of the arguments get passed in through the binding
        client.execute_script(script_name, args)
      end
    rescue => e
      print_error("Error in script: #{script_name}")
      elog("Error in script: #{script_name}", error: e)
    end
  end

  def cmd_run_tabs(str, words)
    tabs = []
    unless words[1] && words[1].match(/^\//)
      begin
        tabs += tab_complete_modules(str, words) if msf_loaded?
        [
          ::Msf::Sessions::Meterpreter.script_base,
          ::Msf::Sessions::Meterpreter.user_script_base
        ].each do |dir|
          next if not ::File.exist? dir
          tabs += ::Dir.new(dir).find_all { |e|
            path = dir + ::File::SEPARATOR + e
            ::File.file?(path) and ::File.readable?(path)
          }
        end
      rescue Exception
      end
    end

    tabs.map { |e| e.sub(/\.rb$/, '') }
  end


  #
  # Executes a script in the context of the meterpreter session in the background
  #
  def cmd_bgrun(*args)
    if args.empty? || args.first == '-h'
      print_line('Usage: bgrun <script> [arguments]')
      print_line
      print_line('Executes a ruby script in the context of the meterpreter session.')
      print_line

      return true
    end

    jid = self.bgjob_id
    self.bgjob_id += 1

    # Get the script name
    self.bgjobs[jid] = Rex::ThreadFactory.spawn("MeterpreterBGRun(#{args[0]})-#{jid}", false, jid, args) do |myjid,xargs|
      ::Thread.current[:args] = xargs.dup
      begin
        # the rest of the arguments get passed in through the binding
        script_name = args.shift
        client.execute_script(script_name, args)
      rescue ::Exception => e
        print_error("Error in script: #{script_name}")
        elog("Error in script: #{script_name}", error: e)
      end
      self.bgjobs[myjid] = nil
      print_status("Background script with Job ID #{myjid} has completed (#{::Thread.current[:args].inspect})")
    end

    print_status("Executed Meterpreter with Job ID #{jid}")
  end

  #
  # Map this to the normal run command tab completion
  #
  def cmd_bgrun_tabs(*args)
    cmd_run_tabs(*args)
  end

  #
  # Kill a background job
  #
  def cmd_bgkill(*args)
    if args.empty? || args.include?('-h')
      print_line('Usage: bgkill [id]')
      return
    end

    args.each do |jid|
      jid = jid.to_i
      if self.bgjobs[jid]
        print_status("Killing background job #{jid}...")
        self.bgjobs[jid].kill
        self.bgjobs[jid] = nil
      else
        print_error("Job #{jid} was not running")
      end
    end
  end

  #
  # List background jobs
  #
  def cmd_bglist(*args)
    self.bgjobs.each_index do |jid|
      if self.bgjobs[jid]
        print_status("Job #{jid}: #{self.bgjobs[jid][:args].inspect}")
      end
    end
  end

  def cmd_info_help
    print_line('Usage: info <module>')
    print_line
    print_line('Prints information about a post-exploitation module')
    print_line
  end

  #
  # Show info for a given Post module.
  #
  # See also +cmd_info+ in lib/msf/ui/console/command_dispatcher/core.rb
  #
  def cmd_info(*args)
    return unless msf_loaded?

    if args.length != 1 or args.include?('-h')
      cmd_info_help
      return
    end

    module_name = args.shift
    mod = client.framework.modules.create(module_name);

    if mod.nil?
      print_error("Invalid module: #{module_name}")
    end

    if (mod)
      print_line(::Msf::Serializer::ReadableText.dump_module(mod))
      mod_opt = ::Msf::Serializer::ReadableText.dump_options(mod, '   ')
      print_line("\nModule options (#{mod.fullname}):\n\n#{mod_opt}") if (mod_opt and mod_opt.length > 0)
    end
  end

  def cmd_info_tabs(str, words)
    tab_complete_modules(str, words) if msf_loaded?
  end

  #
  # Writes data to a channel.
  #
  @@write_opts = Rex::Parser::Arguments.new(
    '-f' => [true, 'Write the contents of a file on disk'],
    '-h' => [false, 'Help menu.'])

  def cmd_write_help
    print_line('Usage: write [options] channel_id')
    print_line
    print_line('Writes data to the supplied channel.')
    print_line(@@write_opts.usage)
  end

  def cmd_write_tabs(str, words)
    return tab_complete_filenames(str, words) if words[-1] == '-f'
    tab_complete_channels
  end

  def cmd_write(*args)
    if args.length == 0 || args.include?("-h")
      cmd_write_help
      return
    end

    src_file = nil
    cid      = nil

    @@write_opts.parse(args) { |opt, idx, val|
      case opt
      when "-f"
        src_file = val
      else
        cid = val.to_i
      end
    }

    # Find the channel associated with this cid, assuming the cid is valid.
    unless cid && channel = client.find_channel(cid)
      print_error('Invalid channel identifier specified.')
      return true
    end

    # If they supplied a source file, read in its contents and write it to
    # the channel
    if src_file
      begin
        data = ''

        ::File.open(src_file, 'rb') { |f|
          data = f.read(f.stat.size)
        }

      rescue Errno::ENOENT
        print_error("Invalid source file specified: #{src_file}")
        return true
      end

      if data && data.length > 0
        channel.write(data)
        print_status("Wrote #{data.length} bytes to channel #{cid}.")
      else
        print_error("No data to send from file #{src_file}")
        return true
      end
    # Otherwise, read from the input descriptor until we're good to go.
    else
      print_line('Enter data followed by a "." on an empty line:')
      print_line
      print_line

      data = ''

      # Keep truckin'
      while s = shell.input.gets
        break if s =~ /^\.\r?\n?$/
        data += s
      end

      if !data || data.length == 0
        print_error('No data to send.')
      else
        channel.write(data)
        print_status("Wrote #{data.length} bytes to channel #{cid}.")
      end
    end

    return true
  end

  def cmd_resource_help
    print_line "Usage: resource path1 [path2 ...]"
    print_line
    print_line "Run the commands stored in the supplied files. (- for stdin, press CTRL+D to end input from stdin)"
    print_line "Resource files may also contain ERB or Ruby code between <ruby></ruby> tags."
    print_line
  end

  def cmd_resource(*args)
    if args.empty?
      cmd_resource_help
      return false
    end

    args.each do |res|
      good_res = nil
      if res == '-'
        good_res = res
      elsif ::File.exist?(res)
        good_res = res
      elsif
        # let's check to see if it's in the scripts/resource dir (like when tab completed)
        [
          ::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
          ::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter'
        ].each do |dir|
          res_path = dir + ::File::SEPARATOR + res
          if ::File.exist?(res_path)
            good_res = res_path
            break
          end
        end
      end
      if good_res
        client.console.load_resource(good_res)
      else
        print_error("#{res} is not a valid resource file")
        next
      end
    end
  end

  def cmd_resource_tabs(str, words)
    tabs = []
    #return tabs if words.length > 1
    if ( str and str =~ /^#{Regexp.escape(::File::SEPARATOR)}/ )
      # then you are probably specifying a full path so let's just use normal file completion
      return tab_complete_filenames(str,words)
    elsif (not words[1] or not words[1].match(/^\//))
      # then let's start tab completion in the scripts/resource directories
      begin
        [
          ::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
          ::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
          '.'
        ].each do |dir|
          next if not ::File.exist? dir
          tabs += ::Dir.new(dir).find_all { |e|
            path = dir + ::File::SEPARATOR + e
            ::File.file?(path) and ::File.readable?(path)
          }
        end
      rescue Exception
      end
    else
      tabs += tab_complete_filenames(str,words)
    end
    return tabs
  end

  def cmd_enable_unicode_encoding
    client.encode_unicode = true
    print_status('Unicode encoding is enabled')
  end

  def cmd_disable_unicode_encoding
    client.encode_unicode = false
    print_status('Unicode encoding is disabled')
  end

  @@client_extension_search_paths = [::File.join(Rex::Root, 'post', 'meterpreter', 'ui', 'console', 'command_dispatcher')]

  def self.add_client_extension_search_path(path)
    @@client_extension_search_paths << path unless @@client_extension_search_paths.include?(path)
  end

  def self.client_extension_search_paths
    @@client_extension_search_paths
  end

  def unknown_command(cmd, line)
    status = super

    if status.nil?
      # Check to see if we can find this command in another extension. This relies on the core extension being the last
      # in the dispatcher stack which it should be since it's the first loaded.
      Rex::Post::Meterpreter::ExtensionMapper.get_extension_names.each do |ext_name|
        next if extensions.include?(ext_name)
        ext_klass = get_extension_client_class(ext_name)
        next if ext_klass.nil?

        if ext_klass.has_command?(cmd)
          print_error("The \"#{cmd}\" command requires the \"#{ext_name}\" extension to be loaded (run: `load #{ext_name}`)")
          return :handled
        end
      end
    end

    status
  end

protected

  attr_accessor :extensions # :nodoc:
  attr_accessor :bgjobs, :bgjob_id # :nodoc:

  CommDispatcher = Console::CommandDispatcher

  #
  # Loads the client extension specified in mod
  #
  def add_extension_client(mod)
    klass = get_extension_client_class(mod)

    if klass.nil?
      print_error("Failed to load client portion of #{mod}.")
      return false
    end

    # Enstack the dispatcher
    self.shell.enstack_dispatcher(klass)

    # Insert the module into the list of extensions
    self.extensions << mod
  end

  def get_extension_client_class(mod)
    self.class.client_extension_search_paths.each do |path|
      path = ::File.join(path, "#{mod}.rb")
      klass = CommDispatcher.check_hash(path)
      return klass unless klass.nil?

      old = CommDispatcher.constants
      next unless ::File.exist? path

      return nil unless require(path)

      new  = CommDispatcher.constants
      diff = new - old

      next if (diff.empty?)

      klass = CommDispatcher.const_get(diff[0])

      CommDispatcher.set_hash(path, klass)
      return klass
    end
  end

  def tab_complete_modules(str, words)
    tabs = []
    client.framework.modules.post.map do |name,klass|
      tabs << 'post/' + name
    end
    client.framework.modules.module_names('exploit').
      grep(/(multi|#{Regexp.escape(client.platform)})\/local\//).each do |name|
      tabs << 'exploit/' + name
    end
    return tabs.sort
  end

  def tab_complete_channels
    client.channels.keys.map { |k| k.to_s }
  end

end

end
end
end
end
