# -*- coding: binary -*-

require 'msf/core'
require 'msf/core/exploit/tcp'

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/
			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/
			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
