require "xmlrpc/server"
require 'rex/service_manager'
require "rex"


module Msf
module RPC


module MonkeyPatcher
	def initialize(*args)
	
		# Enable Bigint processing (required for big file sizes,etc)
		if XMLRPC::Config.const_defined?(:ENABLE_BIGINT)
			XMLRPC::Config.send(:remove_const, :ENABLE_BIGINT)
			XMLRPC::Config.const_set(:ENABLE_BIGINT, true)
		end
		
		# Enable nils in requests
		if XMLRPC::Config.const_defined?(:ENABLE_NIL_CREATE)
			XMLRPC::Config.send(:remove_const, :ENABLE_NIL_CREATE)
			XMLRPC::Config.const_set(:ENABLE_NIL_CREATE, true)
		end
			
		# Enable nils in replies
		if XMLRPC::Config.const_defined?(:ENABLE_NIL_PARSER)
			XMLRPC::Config.send(:remove_const, :ENABLE_NIL_PARSER)
			XMLRPC::Config.const_set(:ENABLE_NIL_PARSER, true)						
		end
		
		super(*args)
	end
end

class Service < ::XMLRPC::BasicServer

	include MonkeyPatcher
	
	attr_accessor :service, :state, :on_input, :on_output, :on_error
	attr_accessor :dispatcher_timeout

	def initialize(srvhost, srvport, ssl=false, cert=nil, ckey=nil)
	
		self.dispatcher_timeout  = 0
	
		self.service = Rex::Socket::TcpServer.create(
			'LocalHost' => srvhost,
			'LocalPort' => srvport,
			'SSL'       => ssl
		)

		self.service.on_client_connect_proc = Proc.new { |client|
			on_client_connect(client)
		}
		self.service.on_client_data_proc = Proc.new { |client|	
			begin
				Timeout.timeout(self.dispatcher_timeout) do
					on_client_data(client)
				end
			rescue ::EOFError  => e
				raise e
			rescue ::Exception => e
				wlog("XMLRPC Server Error: #{client.inspect} #{e.class} #{e} #{e.backtrace}")
				raise e
			end
		}
		self.service.on_client_close_proc = Proc.new { |client|
			on_client_close(client)
		}

		self.state = {}
		super()
	end

	def start
		self.state = {}
		self.service.start
	end

	def stop
		self.state = {}
		self.service.stop
		self.service.close
	end

	def wait
		self.service.wait
	end

	def on_client_close(c)
		self.state.delete(c)
	end

	def on_client_connect(c)
		self.state[c] = ""
		self.clean_state_table
	end

	def on_client_data(c)
		data = c.get_once(-1)
		if(not data)
			c.close
			return
		end

		if not self.state[c]
			c.close
			return
		end

		self.state[c] << data
		procxml(c)
	end

	def clean_state_table
		self.state.keys.each do |s|
			self.state.delete(s) if s.closed?
		end
	end

	def procxml(c)
		while(self.state[c].index("\x00"))
			mesg,left = self.state[c].split("\x00", 2)
			self.state[c] = left
			begin
				self.on_input.call(mesg) if self.on_input

				res = process(mesg)

				self.on_output.call(res) if self.on_output

				c.put(res+"\x00")
			rescue ::Interrupt
				raise $!
			rescue ::Exception => e
				self.on_error.call(e) if self.on_error
			end
		end
	end

end

class WebService < ::XMLRPC::BasicServer

	include MonkeyPatcher
	
	attr_accessor :service, :state, :srvhost, :srvport, :uri


	def initialize(port, host, uri = "/RPC2")
		self.srvhost = host
		self.srvport = port
		self.uri = uri
		self.service = nil
		super()
	end

	def start
		self.state = {}
		self.service = Rex::ServiceManager.start(
			Rex::Proto::Http::Server,
			self.srvport,
			self.srvhost,
			{}
		)

		uopts = {
			'Proc' => Proc.new { |cli, req|
				on_request_uri(cli, req)
			},
			'Path' => self.uri
		}

		self.service.add_resource(self.uri,uopts)
	end

	def stop
		self.state = {}
		self.service.stop
	end

	def wait
		self.service.wait
	end

	def on_client_close(c)
		self.state.delete(c)
	end

	def on_client_connect(c)
		self.state[c] = ""
	end
	def on_request_uri(cli, req)
		begin
			res = Rex::Proto::Http::Response.new()
			res["Content-Type"] = "text/xml"
			res.body = process(req.body)
		rescue XMLRPC::FaultException => e
			res = Rex::Proto::Http::Response.new(e.faultCode,e.faultString)
		rescue
			res = Rex::Proto::Http::Response.new(404,"An Error Occured")
		end
		cli.send_response(res)
	end

end

end
end

