# -*- coding: binary -*-

module Msf

###
#
# This module provides methods for working with the RealPort protocol
#
###
module Exploit::Remote::RealPort
  include Msf::Exploit::Remote::Tcp

  #
  # Initializes an instance of an auxiliary module that uses RealPort
  #

  def initialize(info = {})
    super
    register_options( [
      Opt::RPORT(771)
    ], Msf::Exploit::Remote::RealPort )
  end

  @@REALPORT_BAUD_MAP = {
    '2400'   => "\x03\x00",
    '9600'   => "\x00\xc0",
    '19200'  => "\x00\x60",
    '38400'  => "\x00\x20",
    '57600'  => "\x00\x30",
    '76800'  => "\x00\x10",
    '115200' => "\x00\x10", # Yup, same as above
    '230400' => "\x00\x08",
    '460800' => "\x00\x04",
    '921600' => "\x00\x02",
  }

  # Connect to the RealPort service and send the initial handshake
  # This has the benefit of retrieving the port count and product
  # Returns true if it succeeds and nil otherwise
  def realport_connect
    connect
    sock.put("\xfb\x01\xfb\x02\xfb\x18")
    res = sock.get_once(12, 5)
    return unless (res and res.length == 12)

    unless res[0,2] == "\xfc\x01"
      vprint_error("#{rhost}:#{rport} Bad reply: #{res.inspect}")
      return
    end

    len = res[2,2].unpack("n").first
    return unless len > 0

    res = sock.get_once(len, 5)
    unless res.length == len
      vprint_error("#{rhost}:#{rport} Bad length: #{res.length} wanted #{len}")
      return
    end

    name,info = res.split("\xfc\x02", 2)
    fields = info.unpack("n*")

    @realport_port_count = fields[1].to_i
    @realport_name = name.gsub(/[\r\n]/, '')

    # The server also sends us an additional four-byte packet we can ignore here
    # This throws away a \xFC\x18\x00\x04 sequence
    sock.get_once(-1, 5)

    return true
  end

  def realport_disconnect
    disconnect
  end

  def realport_baud_to_speed(baud)
    @@REALPORT_BAUD_MAP[baud]
  end

  def realport_recv_banner(port=0, timeout=30, max_data=4096)
    #
    # Data is received here, header is:
    # a2   00 01      82   XX
    #  ^ [ counter ]   [ length ]  [ data ]
    #

    # Can also see f0 here (keep alive)

    banner = ""
    stime  = Time.now.to_f
    dcnt   = 0
    pcnt   = 0

    while banner.length < max_data and (Time.now.to_f - stime) < timeout

      res = sock.get_once(1, 1)
      unless res
        if banner.length == 0 or pcnt < 3
          # Send a new line to wake up the remote end
          realport_send(port, "\r")
          pcnt += 1
          next
        else
          # Allow three empty reads *after* we have sent at least one probe and have data
          dcnt += 1
          break if dcnt > 3
          next
        end
      end
      bit = res.unpack("C").first
      case bit
      when (0xA0 + port)
        # Read the packet sequence number (two bytes)
        res = sock.get_once(2, 1)
      when 0xF0
        # Skip this keep-alive response
      when (0x80 + port)
        # Read the one-byte length value
        res = sock.get_once(1, 1)
        if res
          len = res.unpack("C").first
          res = sock.get_once(len, 1)
          if res
            banner << res
          end
        end
      end
    end
    banner
  end

  def realport_send(port=0, data="")
    sock.put( [port].pack("C") + data )
  end

  def realport_close(port=0)
    cprt = [ 0xb0 + port ].pack("C")
    pkt  = cprt + "\x28\x00\xc0\x00\xb0\x00\x01\x00\x00\x00\x00" + cprt + "\x0a\x03"

    # Response
    # b2 0b 03 00 00 02

    # Send a close request
    sock.put(pkt)
    res = sock.get_once(-1, 5)

    vprint_status("#{target_host}:#{rport} Port:#{port} Close:#{ res.inspect }")
    return
  end

  def realport_open(port=0, baud='9600')

    @realport_banner = ''

    cprt = [ 0xb0 + port ].pack("C")
    aprt = [ 0xa0 + port ].pack("C")

    speed = realport_baud_to_speed(baud)

    # Open port
    pkt1 = "\xf0" + cprt + "\x0a"+ "\x00"

    # Response
    # b2 0b 00 00 00 02
    #  ^              ^ <- port number

    # Open the port
    sock.put(pkt1)
    res = sock.get_once(-1, 5)

    vprint_status("#{target_host}:#{rport} Port:#{port} Baud:#{baud} Open:#{ res.inspect }")

    # Access the port
    pkt2 =
      cprt + "\x0e" +
      cprt + "\x2a\x02\xc0\xf3" +
      cprt + "\x10" +
      cprt + "\x14" +
      cprt + "\x16" +
      cprt + "\x2c\x03\x00\x00"

    # Response (GOOD)
    # b2 0f 00 00 00 00 b2 15  0f ff 0f ff b2 11 00 00
    # 13 b2 17 01 02 00 2f 06  a8 00 1c 20 00 00 00 00
    # 00 f3 f3 00 00 00 00 00  00 00 00 00 00 00 00 00
    # 00

    # Response (BAD)
    # \xFF \x17 Access to unopened port\x00

    # Send negotiate request
    sock.put(pkt2)
    res = sock.get_once(-1, 5)
    if res.to_s =~ /^\xff/n
      vprint_status("#{target_host}:#{rport} Port:#{port} is closed: #{res.inspect}")
      return :closed
    end

    vprint_status("#{target_host}:#{rport} Port:#{port} Baud:#{baud} Negotiate:#{ res.inspect }")

    # Terminal settings
    pkt3 =
      cprt + "\x30\x03\xff\x00\x64" +
      cprt + "\x2d\x03\xff\x0b\xff" +
      cprt + "\x28" + speed + "\x04" +
      cprt + "\x00\x01\x00\x00\x00\x00" +
      cprt + "\x2c\x00\x12\x00" +
      cprt + "\x2e\x11\x13\x16\x00\x00" +
      cprt + "\x2f\x03\xff\x00\x64" +
      cprt + "\x40\x37" + aprt + "\x0f\xff"

    # Response
    # c2 12 00 00 f0
    #  ^

    # Send terminal settings request
    sock.put(pkt3)
    res = sock.get_once(-1, 5)

    if res.to_s =~ /^\xff/n
      vprint_status("#{target_host}:#{rport} Port:#{port} is closed: #{res.inspect}")
      return :closed
    end

    vprint_status("#{target_host}:#{rport} Port:#{port} Baud:#{baud} Settings:#{ res.inspect }")
    return :open
  end

end


end
