module Rex
module Proto
module DCERPC
class Client

require 'rex/proto/dcerpc/uuid'
require 'rex/proto/dcerpc/response'
require 'rex/proto/dcerpc/exceptions'
require 'rex/text'
require 'rex/proto/smb/exceptions'

	attr_accessor :handle, :socket, :options, :last_response, :context, :no_bind, :ispipe, :smb

	# initialize a DCE/RPC Function Call
	def initialize(handle, socket, useroptions = Hash.new)
		self.handle = handle
		self.socket = socket
		self.options = {
			'smb_user'   => '',
			'smb_pass'   => '',
			'smb_pipeio' => 'rw',
			'smb_name'   => nil,
			'read_timeout'    => 10,
			'connect_timeout' => 5
		}

		self.options.merge!(useroptions)

		# If the caller passed us a smb_client object, use it and
		# and skip the connect/login/ipc$ stages of the setup
		if (self.options['smb_client'])
			self.smb = self.options['smb_client']
		end

		# we must have a valid handle, regardless of everything else
		raise ArgumentError, 'handle is not a Rex::Proto::DCERPC::Handle' if !self.handle.is_a?(Rex::Proto::DCERPC::Handle)

		# we do this in case socket needs setup first, ie, socket = nil
		if !self.options['no_socketsetup']
			self.socket_check()
		end

		raise ArgumentError, 'socket can not read' if !self.socket.respond_to?(:read)
		raise ArgumentError, 'socket can not write' if !self.socket.respond_to?(:write)

		if !self.options['no_autobind']
			self.bind()
		end
	end

	def socket_check()
		if self.socket == nil
			self.socket_setup()
		end

		case self.handle.protocol
			when 'ncacn_ip_tcp'
				if self.socket.type? != 'tcp'
					raise "ack, #{self.handle.protocol} requires socket type tcp, not #{self.socket.type?}!"
				end
			when 'ncacn_np'
				if self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe
					self.ispipe = 1
				elsif self.socket.type? == 'tcp'
					self.smb_connect()
				else
					raise "ack, #{self.handle.protocol} requires socket type tcp, not #{self.socket.type?}!"
				end
				# No support ncacn_ip_udp (is it needed now that its ripped from Vista?)
			else
				raise "Unsupported protocol : #{self.handle.protocol}"
		end
	end

	# Create the appropriate socket based on protocol
	def socket_setup()
		ctx = { 'Msf' => self.options['Msf'], 'MsfExploit' => self.options['MsfExploit'] }
		self.socket = case self.handle.protocol

			when 'ncacn_ip_tcp'
				Rex::Socket.create_tcp(
					'PeerHost' => self.handle.address,
					'PeerPort' => self.handle.options[0],
					'Context' => ctx,
					'Timeout' => self.options['connect_timeout']
				)

			when 'ncacn_np'
				begin
					socket = Rex::Socket.create_tcp(
						'PeerHost' => self.handle.address,
						'PeerPort' => 445,
						'Context' => ctx,
						'Timeout' => self.options['connect_timeout']
					)
				rescue ::Timeout::Error, Rex::ConnectionRefused
					socket = Rex::Socket.create_tcp(
						'PeerHost' => self.handle.address,
						'PeerPort' => 139,
						'Context' => ctx,
						'Timeout' => self.options['connect_timeout']
					)
				end
				socket
			else nil
		end

		# Add this socket to the exploit's list of open sockets
		options['MsfExploit'].add_socket(self.socket) if (options['MsfExploit'])
	end

	def smb_connect()
		require 'rex/proto/smb/simpleclient'

		if(not self.smb)
			if self.socket.peerport == 139
				smb = Rex::Proto::SMB::SimpleClient.new(self.socket)
			else
				smb = Rex::Proto::SMB::SimpleClient.new(self.socket, true)
			end

			smb.login('*SMBSERVER', self.options['smb_user'], self.options['smb_pass'])
			smb.connect("\\\\#{self.handle.address}\\IPC$")
			self.smb = smb
			self.smb.read_timeout = self.options['read_timeout']
		end

		f = self.smb.create_pipe(self.handle.options[0])
		f.mode = self.options['smb_pipeio']
		self.socket = f
	end

	def read()

		max_read = self.options['pipe_read_max_size'] || 1024*1024
		min_read = self.options['pipe_read_min_size'] || max_read

		raw_response = ''

		# Are we reading from a remote pipe over SMB?
		if (self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe)
			begin

				# Max SMB read is 65535, cap it at 64000
				max_read = [64000, max_read].min
				min_read = [64000, min_read].min

				read_limit = nil

				while(true)
					# Random read offsets will not work on Windows NT 4.0 (thanks Dave!)

					read_cnt = (rand(max_read-min_read)+min_read)
					if(read_limit)
						if(read_cnt + raw_response.length > read_limit)
							read_cnt = raw_response.length - read_limit
						end
					end

					data = self.socket.read( read_cnt, rand(1024)+1)
					break if !(data and data.length > 0)
					raw_response += data

					# Keep reading until we have at least the DCERPC header
					next if raw_response.length < 10

					# We now have to process the raw_response and parse out the DCERPC fragment length
					# if we have read enough data. Once we have the length value, we need to make sure
					# that we don't read beyond this amount, or it can screw up the SMB state
					if (not read_limit)
						begin
							check = Rex::Proto::DCERPC::Response.new(raw_response)
							read_limit = check.frag_len
						rescue ::Rex::Proto::DCERPC::Exceptions::InvalidPacket
						end
					end
					break if (read_limit and read_limit <= raw_response.length)
				end

			rescue Rex::Proto::SMB::Exceptions::NoReply
				# I don't care if I didn't get a reply...
			rescue Rex::Proto::SMB::Exceptions::ErrorCode => exception
				if exception.error_code != 0xC000014B
					raise exception
				end
			end
		# This must be a regular TCP or UDP socket
		else
			if (self.socket.type? == 'tcp')
				if (false and max_read)
					while (true)
						data = self.socket.get_once((rand(max_read-min_read)+min_read), self.options['read_timeout'])
						break if not data
						break if not data.length
						raw_response << data
					end
				else
					# Just read the entire response in one go
					raw_response = self.socket.get_once(-1, self.options['read_timeout'])
				end
			else
				# No segmented read support for non-TCP sockets
				raw_response = self.socket.read(0xFFFFFFFF / 2 - 1)  # read max data
			end
		end

		raw_response
	end

	# Write data to the underlying socket, limiting the sizes of the writes based on
	# the pipe_write_min / pipe_write_max options.
	def write(data)

		max_write = self.options['pipe_write_max_size'] || data.length
		min_write = self.options['pipe_write_min_size'] || max_write

		if(min_write > max_write)
			max_write = min_write
		end

		idx = 0

		if (self.socket.class == Rex::Proto::SMB::SimpleClient::OpenPipe)
			while(idx < data.length)
				bsize = (rand(max_write-min_write)+min_write).to_i
				len = self.socket.write(data[idx, bsize], rand(1024)+1)
				idx += bsize
			end
		else
			self.socket.write(data)
		end

		data.length
	end

	def bind()
		require 'rex/proto/dcerpc/packet'
		bind = ''
		context = ''
		if self.options['fake_multi_bind']

			args = [ self.handle.uuid[0], self.handle.uuid[1] ]

			if (self.options['fake_multi_bind_prepend'])
				args << self.options['fake_multi_bind_prepend']
			end

			if (self.options['fake_multi_bind_append'])
				args << self.options['fake_multi_bind_append']
			end

			bind, context = Rex::Proto::DCERPC::Packet.make_bind_fake_multi(*args)
		else
			bind, context = Rex::Proto::DCERPC::Packet.make_bind(self.handle.uuid[0], self.handle.uuid[1])
		end

		raise 'make_bind failed' if !bind

		self.write(bind)
		raw_response = self.read()

		response = Rex::Proto::DCERPC::Response.new(raw_response)
		self.last_response = response
		if response.type == 12 or response.type == 15
			if self.last_response.ack_result[context] == 2
				raise "Could not bind to #{self.handle}"
			end
			self.context = context
		else
			raise "Could not bind to #{self.handle}"
		end
	end

	# Perform a DCE/RPC Function Call
	def call(function, data, do_recv = true)

		frag_size = data.length
		if options['frag_size']
			frag_size = options['frag_size']
		end
		object_id = ''
		if options['object_call']
			object_id = self.handle.uuid[0]
		end
		if options['random_object_id']
			object_id = Rex::Proto::DCERPC::UUID.uuid_unpack(Rex::Text.rand_text(16))
		end

		call_packets = Rex::Proto::DCERPC::Packet.make_request(function, data, frag_size, self.context, object_id)
		call_packets.each { |packet|
			self.write(packet)
		}

		return true if not do_recv

		raw_response = ''

		begin
			raw_response = self.read()
		rescue ::EOFError
			raise Rex::Proto::DCERPC::Exceptions::NoResponse
		end

		if (raw_response == nil or raw_response.length == 0)
			raise Rex::Proto::DCERPC::Exceptions::NoResponse
		end


		self.last_response = Rex::Proto::DCERPC::Response.new(raw_response)

		if self.last_response.type == 3
			e = Rex::Proto::DCERPC::Exceptions::Fault.new
			e.fault = self.last_response.status
			raise e
		end

		self.last_response.stub_data
	end

	# Process a DCERPC response packet from a socket
	def self.read_response(socket, timeout=self.options['read_timeout'])

		data = socket.get_once(-1, timeout)

		# We need at least 10 bytes to find the FragLen
		if (! data or data.length() < 10)
			return
		end

		# Pass the first 10 bytes to the constructor
		resp = Rex::Proto::DCERPC::Response.new(data.slice!(0, 10))

		# Something went wrong in the parser...
		if (! resp.frag_len)
			return resp
		end

		# Do we need to read more data?
		if (resp.frag_len > (data.length + 10))
			begin
				data << socket.timed_read(resp.frag_len - data.length - 10, timeout)
			rescue Timeout::Error
			end
		end

		# Still missing some data...
		if (data.length() != resp.frag_len - 10)
			# TODO: Bubble this up somehow
			# $stderr.puts "Truncated DCERPC response :-("
			return resp
		end

		resp.parse(data)
		return resp
	end

end
end
end
end

