# -*- coding: binary -*-
module Msf

module Exploit::Remote::VIMSoap
	include Msf::Exploit::Remote::HttpClient

	def vim_soap_envelope(body)
		soap_data = '<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
		soap_data << '<env:Body>'
		soap_data << body
		soap_data <<  '</env:Body></env:Envelope>'
	end



	def vim_soap_propset(type,path,all = false)
		soap_data = '<propSet xsi:type="PropertySpec">'
		soap_data << '<type>' + type + '</type>'
		if all
			soap_data << '<all>true</all>'
		else
			soap_data << '<pathSet>' + path + '</pathSet>'
		end
		soap_data << '</propSet>'
	end



	def vim_soap_objset(type, ref)
		soap_data = '<objectSet>'
		soap_data << '<obj type="' + type + '">' + ref + '</obj>'
		soap_data << '</objectSet>'
	end



	def vim_soap_specset(path,type,ref,all=false)
		soap_data = '<specSet>'
		soap_data << vim_soap_propset(type,path,all)
		soap_data << vim_soap_objset(type,ref)
		soap_data << '</specSet>'
	end



	def vim_soap_retrieve_properties(path,type,ref,all=false)
		soap_data = '<RetrieveProperties xmlns="urn:vim25">'
		soap_data << '<_this type="PropertyCollector">' + @server_objects['propertyCollector'] + '</_this>'
		soap_data << vim_soap_specset(path,type,ref,all)
		soap_data << '</RetrieveProperties>'
	end



	def vim_soap_retrieve_service_content
		soap_data = '<RetrieveServiceContent xmlns="urn:vim25">'
		soap_data << '<_this type="ServiceInstance">ServiceInstance</_this>'
		soap_data << '</RetrieveServiceContent>'
	end



	def vim_soap_login(user,pass)
		soap_data = '<Login xmlns="urn:vim25">'
		soap_data << '<_this type="SessionManager">' + @server_objects['sessionManager'] + '</_this>'
		soap_data << '<userName>' + user + '</userName>'
		soap_data << '<password>' + pass + '</password>'
		soap_data << '</Login>'
	end



	def vim_soap_session_active?(key, user)
		soap_data = '<SessionIsActive xmlns="urn:vim25">'
		soap_data << '<_this type="SessionManager">' + @server_objects['sessionManager'] + '</_this>'
		soap_data << '<sessionID>' + key+ '</sessionID>'
		soap_data << '<userName>' + user + '</userName>'
		soap_data << '</SessionIsActive>'
	end




	def vim_soap_terminate_session(key)
		soap_data = '<TerminateSession xmlns="urn:vim25">'
		soap_data << '<_this xsi:type="ManagedObjectReference" type="SessionManager" >' + @server_objects['sessionManager'] + '</_this>'
		soap_data << '<sessionId>' + key + '</sessionId>'
		soap_data << '</TerminateSession>'
	end



	def vim_soap_retrieve_usergroups(domain=nil)
		soap_data = '<RetrieveUserGroups xmlns="urn:internalvim25">'
		soap_data << '<_this xsi:type="ManagedObjectReference" type="UserDirectory">' + @server_objects['userDirectory'] + '</_this>'
		soap_data << '<domain>' + domain + '</domain>' if domain
		soap_data << '<searchStr></searchStr><exactMatch>false</exactMatch><findUsers>true</findUsers><findGroups>true</findGroups>'
		soap_data << '</RetrieveUserGroups>'
	end



	def vim_soap_log_user_event_vm(vm_ref,msg)
		soap_data = '<LogUserEvent xmlns="urn:vim25">'
		soap_data << '<_this type="EventManager">' + @server_objects['eventManager'] + '</_this>'
		soap_data << '<entity type="VirtualMachine">' + vm_ref + '</entity>'
		soap_data << '<msg>' + msg + '</msg>'
		soap_data << '</LogUserEvent>'
	end



	def vim_soap_retrieve_all_permissions
		soap_data = '<RetrieveAllPermissions xmlns="urn:vim25">'
		soap_data << '<_this type="AuthorizationManager">' + @server_objects['authorizationManager'] + '</_this>'
		soap_data << '</RetrieveAllPermissions>'
	end



	def vim_soap_find_child_byname(type,entity,name)
		soap_data = '<FindChild xmlns="urn:vim25">'
		soap_data << '<_this type="SearchIndex">' + @server_objects['searchIndex'] + '</_this>'
		soap_data << '<entity type="' + type + '">' + entity + '</entity>'
		soap_data << '<name>' + name + '</name>'
		soap_data << '</FindChild>'
	end



	def vim_soap_power_on_vm(vm_ref)
		soap_data = '<PowerOnVM_Task xmlns="urn:vim25">'
		soap_data << '<_this type="VirtualMachine">' + vm_ref + '</_this>'
		soap_data << '</PowerOnVM_Task>'
	end



	def vim_soap_power_off_vm(vm_ref)
		soap_data = '<PowerOffVM_Task xmlns="urn:vim25">'
		soap_data << '<_this type="VirtualMachine">' + vm_ref + '</_this>'
		soap_data << '</PowerOffVM_Task>'
	end



	def vim_soap_create_screenshot(vm_ref)
		soap_data = '<CreateScreenshot_Task xmlns="urn:vim25">'
		soap_data << '<_this type="VirtualMachine">' + vm_ref + '</_this>'
		soap_data << '</CreateScreenshot_Task>'
	end



	def vim_send_soap_request(soap_data)
		res = send_request_cgi({
			'uri'     => '/sdk',
			'method'  => 'POST',
			'agent'   => 'VMware VI Client',
			'cookie'  => @vim_cookie,
			'data' => soap_data,
			'headers' => { 'SOAPAction' => @soap_action}
		}, 25)
		return :noresponse unless res
		if res.body.include? "NotAuthenticatedFault"
			return :expired
		elsif res.body.include? "<faultstring>"
			@vim_soap_error = res.body.match(/<faultstring>([^\c ]+?)<\/faultstring>/)[1]
			return :error
		elsif res.code != 200
			@vim_soap_error = "An unknown error was encountered"
			return :error
		else
			return Hash.from_xml(res.body)['Envelope']['Body']
		end
	end

	def vim_get_session
		soap_data = vim_soap_envelope(vim_soap_retrieve_service_content)
		res = send_request_cgi({
			'uri'     => '/sdk',
			'method'  => 'POST',
			'agent'   => 'VMware VI Client',
			'data' => soap_data,
			'headers' => { 'SOAPAction' => @soap_action}
		}, 25)
		return false unless res and res.code == 200
		 @server_objects = Hash.from_xml(res.body)['Envelope']['Body']['RetrieveServiceContentResponse']['returnval']
		 @soap_action = "urn:vim25/#{@server_objects['about']['apiVersion']}"
		if res.headers['Set-Cookie']
			@vim_cookie = res.headers['Set-Cookie']
			return true
		else
			return false
		end
	end



	def vim_do_login(user, pass)
		unless vim_get_session
			return false
		end
		soap_data = vim_soap_envelope(vim_soap_login(user,pass))
		res = send_request_cgi({
				'uri'     => '/sdk',
				'method'  => 'POST',
				'agent'   => 'VMware VI Client',
				'cookie'  => @vim_cookie,
				'data' => soap_data,
				'headers' => { 'SOAPAction' => @soap_action}
				}, 25)
		if res.code == 200
			return :success
		else
			return :fail
		end
	end



	def vim_get_session_list
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('sessionList','SessionManager', @server_objects['sessionManager']))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			session_list = []
			session_list << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['UserSession']
			return session_list.flatten.compact
		else
			return res
		end
	end



	def vim_session_is_active(key, username)
		soap_data = vim_soap_envelope(vim_soap_session_active?(key,username))
		res = vim_send_soap_request(soap_data)
		print_status "Error: #{@vim_soap_error}"
		if res.class == Hash
			active = res['SessionIsActiveResponse']['returnval']
			return active
		else
			return res
		end
	end



	def vim_terminate_session(key)
		soap_data = vim_soap_envelope(vim_soap_terminate_session(key))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			return :success
		else
			return res
		end
	end



	def vim_get_domains
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('domainList', 'UserDirectory', @server_objects['userDirectory']))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			domains = []
			domains << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['string']
			return domains.flatten.compact
		else
			return res
		end
	end



	def vim_get_user_list(domain=nil)
		soap_data = vim_soap_envelope(vim_soap_retrieve_usergroups(domain))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			return nil unless res['RetrieveUserGroupsResponse']['returnval']
			user_list = []
			user_list <<  res['RetrieveUserGroupsResponse']['returnval']
			return user_list.flatten.compact
		else
			return res
		end
	end



	def vim_log_event_vm(vm_ref, msg)
		soap_data = vim_soap_envelope(vim_soap_log_user_event_vm(vm_ref,msg))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			return :success
		else
			return res
		end
	end



	def vim_get_all_permissions
		soap_data = vim_soap_envelope(vim_soap_retrieve_all_permissions)
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			permissions = []
			permissions << res['RetrieveAllPermissionsResponse']['returnval']
			return permissions.flatten.compact
		else
			return res
		end
	end



	def vim_get_roles
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('roleList', 'AuthorizationManager', @server_objects['authorizationManager']))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			roles = []
			roles << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['AuthorizationRole']
			return roles.flatten.compact
		else
			return res
		end
	end



	def vim_get_dc_name(dc)
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('name','Datacenter',dc))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			return res['RetrievePropertiesResponse']['returnval']['propSet']['val']
		else
			return res
		end
	end


	def vim_get_dcs
		soap_data = vim_soap_envelope(vim_soap_retrieve_service_content)
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			@server_objects.merge!(res['RetrieveServiceContentResponse']['returnval'])
		else
			return res
		end

		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('content', 'ServiceInstance', 'ServiceInstance'))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			hash = res['RetrievePropertiesResponse']['returnval']['propSet']['val']
			hash.delete('xsi:type')
			@server_objects.merge!(hash)
		else
			return res
		end

		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('childEntity', 'Folder', @server_objects['rootFolder']))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			tmp_dcs = []
			tmp_dcs << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
			tmp_dcs.flatten!
			tmp_dcs.each{|dc| @dcs << { 'name' => vim_get_dc_name(dc) , 'ref' => dc}}
		else
			return res
		end
	end



	def vim_get_hosts(datacenter)
		dc_hosts = []
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('hostFolder', 'Datacenter' , datacenter))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			host_folders = []
			host_folders << res['RetrievePropertiesResponse']['returnval']['propSet']['val']
			host_folders.flatten!
		else
			return res
		end

		compute_refs = []
		host_folders.each do |folder|
			soap_data = vim_soap_envelope(vim_soap_retrieve_properties('childEntity', 'Folder' , folder))
			res = vim_send_soap_request(soap_data)
			if res.class == Hash
				ref = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
				unless ref.nil?
					compute_refs << ref
				end
			else
				return res
			end
		end
		compute_refs.flatten!

		compute_refs.each do |ref|
			next if ref.start_with? "group-"
			soap_data = vim_soap_envelope(vim_soap_retrieve_properties('host', 'ComputeResource' , ref))
			res = vim_send_soap_request(soap_data)
			if res.class == Hash
				dc_hosts << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
			else
				return res
			end
		end
		dc_hosts.flatten!
		return dc_hosts
	end



	def vim_get_all_hosts
		@dcs.each{|dc| @hosts << vim_get_hosts(dc['ref'])}
		@hosts.flatten!
	end



	def vim_get_host_hw(host)
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('hardware', 'HostSystem' , host))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			return res['RetrievePropertiesResponse']['returnval']['propSet']['val']
		else
			return res
		end
	end

	def vim_get_all_host_summary(hw=false)
		vim_setup_references
		summaries = []
		@hosts.each do |host|
			details = {}
			details[host] = vim_get_host_summary(host)
			if details and hw
				details.merge!(vim_get_host_hw(host))
			end
			summaries << details
		end
		return summaries.flatten.compact
	end

	def vim_get_vm_datastore(vm)
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('datastore', 'VirtualMachine' , vm))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			datastore_refs = []
			datastore_refs << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
			datastore_refs.flatten!
			datastore_refs.compact!
			datastores = []
		else
			return res
		end

		datastore_refs.each do |datastore_ref|
			soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Datastore' , datastore_ref))
			res = vim_send_soap_request(soap_data)
			if res.class == Hash
				datastore_name = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['name']
				datastore = { 'name' => datastore_name, 'ref' => datastore_ref}
				datastores << datastore
			else
				return res
			end
		end
		return datastores

	end

	def vim_find_vm_by_name(name)
		vim_setup_references
		@dcs.each do |dc|
			soap_data = vim_soap_envelope(vim_soap_retrieve_properties('vmFolder', 'Datacenter' , dc['ref']))
			res = vim_send_soap_request(soap_data)
			if res.class == Hash
				vm_folders = []
				vm_folders << res['RetrievePropertiesResponse']['returnval']['propSet']['val']
				vm_folders.flatten!
				vm_folders.compact!
			else
				return res
			end


			vm_folders.each do |vm_folder|
				soap_data = vim_soap_envelope(vim_soap_find_child_byname('Folder', vm_folder, name))
				res = vim_send_soap_request(soap_data)
				if res.class == Hash
					vmref = res['FindChildResponse']['returnval']
					if vmref
						return vmref
					else
						next
					end
				else
					return res
				end
			end
		end
		return nil
	end



	def vim_powerON_vm(vm_ref)
		soap_data = vim_soap_envelope(vim_soap_power_on_vm(vm_ref))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			task_id = res['PowerOnVM_TaskResponse']['returnval']
		else
			return res
		end

		state= "running"
		while state == "running"
			soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Task', task_id))
			res = vim_send_soap_request(soap_data)
			if res.class == Hash
				state = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['state']
				case state
				when 'running'
					select(nil, nil, nil, 5)
				when 'error'
					if res['RetrievePropertiesResponse']['returnval']['propSet']['val']['error']['fault']['existingState'] == 'poweredOn'
						return 'alreadyON'
					end
				end
			else
				return res
			end
		end
		return state
	end



	def vim_powerOFF_vm(vm_ref)
		soap_data = vim_soap_envelope(vim_soap_power_off_vm(vm_ref))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			task_id = res['PowerOffVM_TaskResponse']['returnval']
		else
			return res
		end

		state= "running"
		while state == "running"
			soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Task', task_id))
			res = vim_send_soap_request(soap_data)
			if res.class == Hash
				state = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['state']
				case state
				when 'running'
					select(nil, nil, nil, 5)
				when 'error'
					if res['RetrievePropertiesResponse']['returnval']['propSet']['val']['error']['fault']['existingState'] == 'poweredOn'
						return 'alreadyON'
					end
				end
			else
				return res
			end
		end
		return state
	end



	def vim_take_screenshot(vm, user, pass)
		soap_data = vim_soap_envelope(vim_soap_create_screenshot(vm['ref']))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			task_id = res['CreateScreenshot_TaskResponse']['returnval']
		else
			return res
		end

		state= "running"
		while state == "running"
			soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Task', task_id))
			res = vim_send_soap_request(soap_data)
			if res.class == Hash
				state = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['state']
				screenshot_file = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['result']
			else
				return res
			end
		end
		unless screenshot_file
			return :error
		end
		(ss_folder, ss_file) = screenshot_file.split('/').last(2)
		ss_folder = Rex::Text.uri_encode(ss_folder)
		ss_file =  Rex::Text.uri_encode(ss_file)
		ss_path = "#{ss_folder}/#{ss_file}"
		datastores = vim_get_vm_datastore(vm['ref'])
		user_pass = Rex::Text.encode_base64(user + ":" + pass)
		datastores.each do |datastore|
			ss_uri = "/folder/#{ss_path}?dcPath=#{vm['dc_name']}&dsName=#{datastore['name']}"
			res = send_request_cgi({
				'uri'     => ss_uri,
				'method'  => 'GET',
				'agent'   => 'VMware VI Client',
				'cookie'  => @vim_cookie,
				'headers' => { 'Authorization' => "Basic #{user_pass}"}
			}, 25)
			next unless res
			if res.code == 200
				return res.body
			elsif res.code == 404
				next
			end
		end
		return :error
	end



	def vim_get_host_summary(host)
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('summary', 'HostSystem', host))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			hash = res['RetrievePropertiesResponse']['returnval']['propSet']['val']
			hash['runtime'].delete('healthSystemRuntime')
			hash.delete('xsi:type')
			hash.delete('host')
			return hash
		else
			return res
		end
	end



	def vim_get_vms
		vim_setup_references
		@vmrefs = []
		vmlist= []
		@dcs.each do |dc|
			dc_vm_refs = vim_get_dc_vms(dc['ref'])
			next if dc_vm_refs.nil? or dc_vm_refs.empty?
			dc_vm_refs.flatten!
			dc_vm_refs.compact!
			next if dc_vm_refs.nil? or dc_vm_refs.empty?
			print_status "#{datastore['RHOST']} - DataCenter: #{dc['name']} Found a Total of #{dc_vm_refs.length} VMs"
			print_status "#{datastore['RHOST']}  - DataCenter: #{dc['name']} Estimated Time: #{((dc_vm_refs.length * 7) /60)} Minutes"
			dc_vm_refs.each do |ref|
				print_status "#{datastore['RHOST']}  -  DataCenter: #{dc['name']} - Getting Data for VM: #{ref}..."
				details  = vim_get_vm_info(ref)
				if details
					details['ref'] = ref
					details['dc_ref'] = dc['ref']
					details['dc_name'] = dc['name']
					vmlist << details
				end
			end
		end
		return vmlist
	end



	def vim_get_dc_vms(datacenter)
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('vmFolder', 'Datacenter', datacenter))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			vmfolder = res['RetrievePropertiesResponse']['returnval']['propSet']['val']
		else
			return res
		end

		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('childEntity', 'Folder', vmfolder))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			vm_index_array = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
			vm_index_array.delete_if{|ref| ref.start_with? "group"} unless vm_index_array.nil? or vm_index_array.empty? or vm_index_array.class != Array
			return vm_index_array
		else
			return res
		end
	end

	def vim_get_vm_info(vm_ref)
		vim_setup_references
		soap_data = vim_soap_envelope(vim_soap_retrieve_properties('summary', 'VirtualMachine', vm_ref))
		res = vim_send_soap_request(soap_data)
		if res.class == Hash
			hash =  res['RetrievePropertiesResponse']['returnval']['propSet']['val']
			vm = hash['config']
			vm['runtime'] = hash['runtime']
			vm['guest'] = hash['guest']
			vm['quickStats'] = hash['quickStats']
			return vm
		else
			return res
		end
	end

	def vim_logged_in?
		return true if @vim_cookie
		return false
	end

	def vim_instance_vars_set?
		return false if @server_objects.nil? or @server_objects.empty?
		return false if @host.nil? or @host.empty?
		return true
	end

	def vim_setup_references
		unless vim_instance_vars_set?
			@dcs = []
			@hosts = []
			vim_get_dcs
			vim_get_all_hosts
			@hosts.flatten!
			@hosts.compact!
		end
	end

end
end
