# -*- coding: binary -*-
# Check Rex::Parser.nokogiri_loaded for status of the Nokogiri parsers
require 'rex/parser/nmap_nokogiri'
require 'rex/parser/nexpose_simple_nokogiri'
require 'rex/parser/nexpose_raw_nokogiri'
require 'rex/parser/foundstone_nokogiri'
require 'rex/parser/mbsa_nokogiri'
require 'rex/parser/acunetix_nokogiri'
require 'rex/parser/appscan_nokogiri'
require 'rex/parser/burp_session_nokogiri'
require 'rex/parser/ci_nokogiri'
require 'rex/parser/wapiti_nokogiri'
require 'rex/parser/openvas_nokogiri'
require 'rex/parser/fusionvm_nokogiri'

# Legacy XML parsers -- these will be converted some day

require 'rex/parser/nmap_xml'
require 'rex/parser/nexpose_xml'
require 'rex/parser/retina_xml'
require 'rex/parser/netsparker_xml'
require 'rex/parser/nessus_xml'
require 'rex/parser/ip360_xml'
require 'rex/parser/ip360_aspl_xml'

require 'rex/socket'
require 'zip'
require 'packetfu'
require 'uri'
require 'tmpdir'
require 'csv'


module Msf

###
#
# The states that a host can be in.
#
###
module HostState
	#
	# The host is alive.
	#
	Alive   = "alive"
	#
	# The host is dead.
	#
	Dead    = "down"
	#
	# The host state is unknown.
	#
	Unknown = "unknown"
end

###
#
# The states that a service can be in.
#
###
module ServiceState
	Open      = "open"
	Closed    = "closed"
	Filtered  = "filtered"
	Unknown   = "unknown"
end

###
#
# Events that can occur in the host/service database.
#
###
module DatabaseEvent

	#
	# Called when an existing host's state changes
	#
	def on_db_host_state(host, ostate)
	end

	#
	# Called when an existing service's state changes
	#
	def on_db_service_state(host, port, ostate)
	end

	#
	# Called when a new host is added to the database.  The host parameter is
	# of type Host.
	#
	def on_db_host(host)
	end

	#
	# Called when a new client is added to the database.  The client
	# parameter is of type Client.
	#
	def on_db_client(client)
	end

	#
	# Called when a new service is added to the database.  The service
	# parameter is of type Service.
	#
	def on_db_service(service)
	end

	#
	# Called when an applicable vulnerability is found for a service.  The vuln
	# parameter is of type Vuln.
	#
	def on_db_vuln(vuln)
	end

	#
	# Called when a new reference is created.
	#
	def on_db_ref(ref)
	end

end

class DBImportError < RuntimeError
end

###
#
# The DB module ActiveRecord definitions for the DBManager
#
###
class DBManager

	def rfc3330_reserved(ip)
		case ip.class.to_s
		when "PacketFu::Octets"
			ip_x = ip.to_x
			ip_i = ip.to_i
		when "String"
			if ipv46_validator(ip)
				ip_x = ip
				ip_i = Rex::Socket.addr_atoi(ip)
			else
				raise ArgumentError, "Invalid IP address: #{ip.inspect}"
			end
		when "Fixnum"
			if (0..2**32-1).include? ip
				ip_x = Rex::Socket.addr_itoa(ip)
				ip_i = ip
			else
				raise ArgumentError, "Invalid IP address: #{ip.inspect}"
			end
		else
			raise ArgumentError, "Invalid IP address: #{ip.inspect}"
		end
		return true if Rex::Socket::RangeWalker.new("0.0.0.0-0.255.255.255").include? ip_x
		return true if Rex::Socket::RangeWalker.new("127.0.0.0-127.255.255.255").include? ip_x
		return true if Rex::Socket::RangeWalker.new("169.254.0.0-169.254.255.255").include? ip_x
		return true if Rex::Socket::RangeWalker.new("224.0.0.0-239.255.255.255").include? ip_x
		return true if Rex::Socket::RangeWalker.new("255.255.255.255-255.255.255.255").include? ip_x
		return false
	end

	def ipv46_validator(addr)
		ipv4_validator(addr) or ipv6_validator(addr)
	end

	def ipv4_validator(addr)
		return false unless addr.kind_of? String
		Rex::Socket.is_ipv4?(addr)
	end

	def ipv6_validator(addr)
		Rex::Socket.is_ipv6?(addr)
	end

	# Takes a space-delimited set of ips and ranges, and subjects
	# them to RangeWalker for validation. Returns true or false.
	def validate_ips(ips)
		ret = true
		begin
			ips.split(/\s+/).each {|ip|
				unless Rex::Socket::RangeWalker.new(ip).ranges
					ret = false
					break
				end
				}
		rescue
			ret = false
		end
		return ret
	end


	#
	# Determines if the database is functional
	#
	def check
	::ActiveRecord::Base.connection_pool.with_connection {
		res = ::Mdm::Host.find(:first)
	}
	end


	def default_workspace
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::Workspace.default
	}
	end

	def find_workspace(name)
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::Workspace.find_by_name(name)
	}
	end

	#
	# Creates a new workspace in the database
	#
	def add_workspace(name)
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::Workspace.find_or_create_by_name(name)
	}
	end

	def workspaces
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::Workspace.find(:all)
	}
	end

	#
	# Wait for all pending write to finish
	#
	def sync
		# There is no more queue.
	end

	#
	# Find a host.  Performs no database writes.
	#
	def get_host(opts)
		if opts.kind_of? ::Mdm::Host
			return opts
		elsif opts.kind_of? String
			raise RuntimeError, "This invokation of get_host is no longer supported: #{caller}"
		else
			address = opts[:addr] || opts[:address] || opts[:host] || return
			return address if address.kind_of? ::Mdm::Host
		end
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		if wspace.kind_of? String
			wspace = find_workspace(wspace)
		end

		address = normalize_host(address)
		return wspace.hosts.find_by_address(address)
	}
	end

	#
	# Exactly like report_host but waits for the database to create a host and returns it.
	#
	def find_or_create_host(opts)
		report_host(opts)
	end

	#
	# Report a host's attributes such as operating system and service pack
	#
	# The opts parameter MUST contain
	# +:host+::         -- the host's ip address
	#
	# The opts parameter can contain:
	# +:state+::        -- one of the Msf::HostState constants
	# +:os_name+::      -- one of the Msf::OperatingSystems constants
	# +:os_flavor+::    -- something like "XP" or "Gentoo"
	# +:os_sp+::        -- something like "SP2"
	# +:os_lang+::      -- something like "English", "French", or "en-US"
	# +:arch+::         -- one of the ARCH_* constants
	# +:mac+::          -- the host's MAC address
	# +:scope+::        -- interface identifier for link-local IPv6
	# +:virtual_host+:: -- the name of the VM host software, eg "VMWare", "QEMU", "Xen", etc.
	#
	def report_host(opts)

		return if not active
		addr = opts.delete(:host) || return

		# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
		if addr.eql? "Remote Pipe"
			return
		end

	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		if wspace.kind_of? String
			wspace = find_workspace(wspace)
		end

		ret = { }

		if not addr.kind_of? ::Mdm::Host
			addr = normalize_host(addr)
			addr, scope = addr.split('%', 2)
			opts[:scope] = scope if scope

			unless ipv46_validator(addr)
				raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
			end

			if opts[:comm] and opts[:comm].length > 0
				host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
			else
				host = wspace.hosts.find_or_initialize_by_address(addr)
			end
		else
			host = addr
		end

		# Truncate the info field at the maximum field length
		if opts[:info]
			opts[:info] = opts[:info][0,65535]
		end

		# Truncate the name field at the maximum field length
		if opts[:name]
			opts[:name] = opts[:name][0,255]
		end

		opts.each { |k,v|
			if (host.attribute_names.include?(k.to_s))
				unless host.attribute_locked?(k.to_s)
					host[k] = v.to_s.gsub(/[\x00-\x1f]/, '')
				end
			else
				dlog("Unknown attribute for ::Mdm::Host: #{k}")
			end
		}
		host.info = host.info[0,::Mdm::Host.columns_hash["info"].limit] if host.info

		# Set default fields if needed
		host.state       = HostState::Alive if not host.state
		host.comm        = ''        if not host.comm
		host.workspace   = wspace    if not host.workspace

		if host.changed?
			msf_import_timestamps(opts,host)
			host.save!
		end

		host
	}
	end


	#
	# Update a host's attributes via semi-standardized sysinfo hash (Meterpreter)
	#
	# The opts parameter MUST contain the following entries
	# +:host+::           -- the host's ip address
	# +:info+::           -- the information hash
	# * 'Computer'        -- the host name
	# * 'OS'              -- the operating system string
	# * 'Architecture'    -- the hardware architecture
	# * 'System Language' -- the system language
	#
	# The opts parameter can contain:
	# +:workspace+::      -- the workspace for this host
	#
	def update_host_via_sysinfo(opts)

		return if not active
		addr = opts.delete(:host) || return
		info = opts.delete(:info) || return

		# Sometimes a host setup through a pivot will see the address as "Remote Pipe"
		if addr.eql? "Remote Pipe"
			return
		end

	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		if wspace.kind_of? String
			wspace = find_workspace(wspace)
		end

		if not addr.kind_of? ::Mdm::Host
			addr = normalize_host(addr)
			addr, scope = addr.split('%', 2)
			opts[:scope] = scope if scope

			unless ipv46_validator(addr)
				raise ::ArgumentError, "Invalid IP address in report_host(): #{addr}"
			end

			if opts[:comm] and opts[:comm].length > 0
				host = wspace.hosts.find_or_initialize_by_address_and_comm(addr, opts[:comm])
			else
				host = wspace.hosts.find_or_initialize_by_address(addr)
			end
		else
			host = addr
		end

		res = {}

		if info['Computer']
			res[:name] = info['Computer']
		end

		if info['Architecture']
			res[:arch] = info['Architecture'].split(/\s+/).first
		end

		if info['OS'] =~ /^Windows\s*([^\(]+)\(([^\)]+)\)/i
			res[:os_name]   = "Microsoft Windows"
			res[:os_flavor] = $1.strip
			build = $2.strip

			if build =~ /Service Pack (\d+)/
				res[:os_sp] = "SP" + $1
			else
				res[:os_sp] = "SP0"
			end
		end

		if info["System Language"]
			case info["System Language"]
				when /^en_/
					res[:os_lang] = "English"
			end
		end


		# Truncate the info field at the maximum field length
		if res[:info]
			res[:info] = res[:info][0,65535]
		end

		# Truncate the name field at the maximum field length
		if res[:name]
			res[:name] = res[:name][0,255]
		end

		res.each { |k,v|

			if (host.attribute_names.include?(k.to_s))
				unless host.attribute_locked?(k.to_s)
					host[k] = v.to_s.gsub(/[\x00-\x1f]/, '')
				end
			else
				dlog("Unknown attribute for Host: #{k}")
			end
		}

		# Set default fields if needed
		host.state       = HostState::Alive if not host.state
		host.comm        = ''        if not host.comm
		host.workspace   = wspace    if not host.workspace

		if host.changed?
			host.save!
		end

		host
	}
	end
	#
	# Iterates over the hosts table calling the supplied block with the host
	# instance of each entry.
	#
	def each_host(wspace=workspace, &block)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.hosts.each do |host|
			block.call(host)
		end
	}
	end

	#
	# Returns a list of all hosts in the database
	#
	def hosts(wspace = workspace, only_up = false, addresses = nil)
	::ActiveRecord::Base.connection_pool.with_connection {
		conditions = {}
		conditions[:state] = [Msf::HostState::Alive, Msf::HostState::Unknown] if only_up
		conditions[:address] = addresses if addresses
		wspace.hosts.where(conditions).order(:address)
	}
	end



	def find_or_create_service(opts)
		report_service(opts)
	end

	#
	# Record a service in the database.
	#
	# opts MUST contain
	# +:host+::  the host where this service is running
	# +:port+::  the port where this service listens
	# +:proto+:: the transport layer protocol (e.g. tcp, udp)
	#
	# opts may contain
	# +:name+::  the application layer protocol (e.g. ssh, mssql, smb)
	# +:sname+:: an alias for the above
	#
	def report_service(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection { |conn|
		addr  = opts.delete(:host) || return
		hname = opts.delete(:host_name)
		hmac  = opts.delete(:mac)
		host  = nil
		wspace = opts.delete(:workspace) || workspace
		hopts = {:workspace => wspace, :host => addr}
		hopts[:name] = hname if hname
		hopts[:mac]  = hmac  if hmac

		# Other report_* methods take :sname to mean the service name, so we
		# map it here to ensure it ends up in the right place despite not being
		# a real column.
		if opts[:sname]
			opts[:name] = opts.delete(:sname)
		end

		if addr.kind_of? ::Mdm::Host
			host = addr
			addr = host.address
		else
			host = report_host(hopts)
		end

		if opts[:port].to_i.zero?
			dlog("Skipping port zero for service '%s' on host '%s'" % [opts[:name],host.address])
			return nil
		end

		ret  = {}
=begin
		host = get_host(:workspace => wspace, :address => addr)
		if host
			host.updated_at = host.created_at
			host.state      = HostState::Alive
			host.save!
		end
=end

		proto = opts[:proto] || 'tcp'

		service = host.services.find_or_initialize_by_port_and_proto(opts[:port].to_i, proto)
		opts.each { |k,v|
			if (service.attribute_names.include?(k.to_s))
				service[k] = ((v and k == :name) ? v.to_s.downcase : v)
			else
				dlog("Unknown attribute for Service: #{k}")
			end
		}
		service.state ||= ServiceState::Open
		service.info  ||= ""

		if (service and service.changed?)
			msf_import_timestamps(opts,service)
			service.save!
		end
		ret[:service] = service
	}
	end

	def get_service(wspace, host, proto, port)
	::ActiveRecord::Base.connection_pool.with_connection {
		host = get_host(:workspace => wspace, :address => host)
		return if not host
		return host.services.find_by_proto_and_port(proto, port)
	}
	end

	#
	# Iterates over the services table calling the supplied block with the
	# service instance of each entry.
	#
	def each_service(wspace=workspace, &block)
	::ActiveRecord::Base.connection_pool.with_connection {
		services(wspace).each do |service|
			block.call(service)
		end
	}
	end

	#
	# Returns a list of all services in the database
	#
	def services(wspace = workspace, only_up = false, proto = nil, addresses = nil, ports = nil, names = nil)
	::ActiveRecord::Base.connection_pool.with_connection {
		conditions = {}
		conditions[:state] = [ServiceState::Open] if only_up
		conditions[:proto] = proto if proto
		conditions["hosts.address"] = addresses if addresses
		conditions[:port] = ports if ports
		conditions[:name] = names if names
		wspace.services.includes(:host).where(conditions).order("hosts.address, port")
	}
	end

	# Returns a session based on opened_time, host address, and workspace
	# (or returns nil)
	def get_session(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts[:workspace] || opts[:wspace] || workspace
		addr   = opts[:addr] || opts[:address] || opts[:host] || return
		host = get_host(:workspace => wspace, :host => addr)
		time = opts[:opened_at] || opts[:created_at] || opts[:time] || return
		::Mdm::Session.find_by_host_id_and_opened_at(host.id, time)
	}
	end

	# Record a new session in the database
	#
	# opts MUST contain either
	# +:session+:: the Msf::Session object we are reporting
	# +:host+::    the Host object we are reporting a session on.
	#
	def report_session(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		if opts[:session]
			raise ArgumentError.new("Invalid :session, expected Msf::Session") unless opts[:session].kind_of? Msf::Session
			session = opts[:session]
			wspace = opts[:workspace] || find_workspace(session.workspace)
			h_opts = { }
			h_opts[:host]      = normalize_host(session)
			h_opts[:arch]      = session.arch if session.respond_to?(:arch) and session.arch
			h_opts[:workspace] = wspace
			host = find_or_create_host(h_opts)
			sess_data = {
				:host_id => host.id,
				:stype => session.type,
				:desc => session.info,
				:platform => session.platform,
				:via_payload => session.via_payload,
				:via_exploit => session.via_exploit,
				:routes => [],
				:datastore => session.exploit_datastore.to_h,
				:opened_at => Time.now.utc,
				:last_seen => Time.now.utc,
				:local_id => session.sid
			}
		elsif opts[:host]
			raise ArgumentError.new("Invalid :host, expected Host object") unless opts[:host].kind_of? ::Mdm::Host
			host = opts[:host]
			sess_data = {
				:host_id => host.id,
				:stype => opts[:stype],
				:desc => opts[:desc],
				:platform => opts[:platform],
				:via_payload => opts[:via_payload],
				:via_exploit => opts[:via_exploit],
				:routes => opts[:routes] || [],
				:datastore => opts[:datastore],
				:opened_at => opts[:opened_at],
				:closed_at => opts[:closed_at],
				:last_seen => opts[:last_seen] || opts[:closed_at],
				:close_reason => opts[:close_reason],
			}
		else
			raise ArgumentError.new("Missing option :session or :host")
		end
		ret = {}

		# Truncate the session data if necessary
		if sess_data[:desc]
			sess_data[:desc] = sess_data[:desc][0,255]
		end

		s = ::Mdm::Session.new(sess_data)
		s.save!

		if opts[:session]
			session.db_record = s
		end

		# If this is a live session, we know the host is vulnerable to something.
		# If the exploit used was multi/handler, though, we don't know what
		# it's vulnerable to, so it isn't really useful to save it.
		if opts[:session] and session.via_exploit and session.via_exploit != "exploit/multi/handler"
			return unless host

			mod = framework.modules.create(session.via_exploit)
			vuln_info = {
				:host => host.address,
				:name => mod.name,
				:refs => mod.references,
				:workspace => wspace,
				:exploited_at => Time.now.utc,
				:info => "Exploited by #{mod.fullname} to create Session #{s.id}"
			}

			port    = session.exploit_datastore["RPORT"]
			service = (port ? host.services.find_by_port(port.to_i) : nil)

			vuln_info[:service] = service if service

			vuln = framework.db.report_vuln(vuln_info)
			
			attempt_info = {
				:timestamp   => Time.now.utc,
				:workspace   => wspace,
				:module      => session.via_exploit,
				:username    => session.username,
				:refs        => mod.references,
				:session_id  => s.id,
				:host        => host,
				:service     => service,
				:vuln        => vuln
			}

			framework.db.report_exploit_success(attempt_info)
			
		end

		s
	}
	end

	#
	# Record a session event in the database
	#
	# opts MUST contain one of:
	# +:session+:: the Msf::Session OR the ::Mdm::Session we are reporting
	# +:etype+::   event type, enum: command, output, upload, download, filedelete
	#
	# opts may contain
	# +:output+::      the data for an output event
	# +:command+::     the data for an command event
	# +:remote_path+:: path to the associated file for upload, download, and filedelete events
	# +:local_path+::  path to the associated file for upload, and download
	#
	def report_session_event(opts)
		return if not active
		raise ArgumentError.new("Missing required option :session") if opts[:session].nil?
		raise ArgumentError.new("Expected an :etype") unless opts[:etype]
		session = nil

	::ActiveRecord::Base.connection_pool.with_connection {
		if opts[:session].respond_to? :db_record
			session = opts[:session].db_record
			if session.nil?
				# The session doesn't have a db_record which means
				#  a) the database wasn't connected at session registration time
				# or
				#  b) something awful happened and the report_session call failed
				#
				# Either way, we can't do anything with this session as is, so
				# log a warning and punt.
				wlog("Warning: trying to report a session_event for a session with no db_record (#{opts[:session].sid})")
				return
			end
			event_data = { :created_at => Time.now }
		else
			session = opts[:session]
			event_data = { :created_at => opts[:created_at] }
		end

		event_data[:session_id] = session.id
		[:remote_path, :local_path, :output, :command, :etype].each do |attr|
			event_data[attr] = opts[attr] if opts[attr]
		end

		s = ::Mdm::SessionEvent.create(event_data)
	}
	end

	def report_session_route(session, route)
		return if not active
		if session.respond_to? :db_record
			s = session.db_record
		else
			s = session
		end
		unless s.respond_to?(:routes)
			raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
		end

	::ActiveRecord::Base.connection_pool.with_connection {

		subnet, netmask = route.split("/")
		s.routes.create(:subnet => subnet, :netmask => netmask)
	}
	end

	def report_session_route_remove(session, route)
		return if not active
		if session.respond_to? :db_record
			s = session.db_record
		else
			s = session
		end
		unless s.respond_to?(:routes)
			raise ArgumentError.new("Invalid :session, expected Session object got #{session.class}")
		end

	::ActiveRecord::Base.connection_pool.with_connection {
		subnet, netmask = route.split("/")
		r = s.routes.find_by_subnet_and_netmask(subnet, netmask)
		r.destroy if r
	}
	end


	def report_exploit_success(opts)
	::ActiveRecord::Base.connection_pool.with_connection {

		wspace = opts.delete(:workspace) || workspace
		mrefs  = opts.delete(:refs) || return
		host   = opts.delete(:host)
		port   = opts.delete(:port)
		prot   = opts.delete(:proto)
		svc    = opts.delete(:service)
		vuln   = opts.delete(:vuln)

		timestamp = opts.delete(:timestamp)
		username  = opts.delete(:username)
		mname     = opts.delete(:module)

		# Look up or generate the host as appropriate
		if not (host and host.kind_of? ::Mdm::Host)
			if svc.kind_of? ::Mdm::Service
				host = svc.host
			else
				host = report_host(:workspace => wspace, :address => host )
			end
		end

		# Bail if we dont have a host object
		return if not host

		# Look up or generate the service as appropriate
		if port and svc.nil?
			svc = report_service(:workspace => wspace, :host => host, :port => port, :proto => prot ) if port
		end

		if not vuln
			# Create a references map from the module list
			ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
				if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
					"#{ref.ctx_id}-#{ref.ctx_val}"
				else
					ref.to_s
				end
			})
		
			# Try find a matching vulnerability
			vuln = find_vuln_by_refs(ref_objs, host, svc)
		end

		# We have match, lets create a vuln_attempt record
		if vuln
			attempt_info = {
				:vuln_id      => vuln.id,
				:attempted_at => timestamp || Time.now.utc,
				:exploited    => true,
				:username     => username  || "unknown",
				:module       => mname
			}

			attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
			attempt_info[:loot_id]    = opts[:loot_id]    if opts[:loot_id]

			vuln.vuln_attempts.create(attempt_info)
	
			# Correct the vuln's associated service if necessary
			if svc and vuln.service_id.nil?
				vuln.service = svc
				vuln.save
			end
		end

		# Report an exploit attempt all the same
		attempt_info = {
			:attempted_at => timestamp || Time.now.utc,
			:exploited    => true,
			:username     => username  || "unknown",
			:module       => mname
		}

		attempt_info[:vuln_id]    = vuln.id           if vuln
		attempt_info[:session_id] = opts[:session_id] if opts[:session_id]
		attempt_info[:loot_id]    = opts[:loot_id]    if opts[:loot_id]
		
		if svc
			attempt_info[:port]  = svc.port
			attempt_info[:proto] = svc.proto
		end
		
		if port and svc.nil?
			attempt_info[:port]  = port
			attempt_info[:proto] = prot || "tcp"
		end

		host.exploit_attempts.create(attempt_info)
	}
	end

	def report_exploit_failure(opts)

	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		mrefs  = opts.delete(:refs) || return
		host   = opts.delete(:host)
		port   = opts.delete(:port)
		prot   = opts.delete(:proto)
		svc    = opts.delete(:service)
		vuln   = opts.delete(:vuln)

		timestamp  = opts.delete(:timestamp)
		freason    = opts.delete(:fail_reason)
		fdetail    = opts.delete(:fail_detail)		
		username   = opts.delete(:username)
		mname      = opts.delete(:module)

		# Look up the host as appropriate
		if not (host and host.kind_of? ::Mdm::Host)
			if svc.kind_of? ::Mdm::Service
				host = svc.host
			else
				host = get_host( :workspace => wspace, :address => host )
			end
		end

		# Bail if we dont have a host object
		return if not host

		# Look up the service as appropriate
		if port and svc.nil?
			prot ||= "tcp"
			svc = get_service(wspace, host, prot, port) if port
		end

		if not vuln
			# Create a references map from the module list
			ref_objs = ::Mdm::Ref.where(:name => mrefs.map { |ref|
				if ref.respond_to?(:ctx_id) and ref.respond_to?(:ctx_val)
					"#{ref.ctx_id}-#{ref.ctx_val}"
				else
					ref.to_s
				end
			})
		
			# Try find a matching vulnerability
			vuln = find_vuln_by_refs(ref_objs, host, svc)
		end

		# Report a vuln_attempt if we found a match
		if vuln
			attempt_info = {
				:attempted_at => timestamp || Time.now.utc,
				:exploited    => false,
				:fail_reason  => freason,
				:fail_detail  => fdetail,
				:username     => username  || "unknown",
				:module       => mname
			}

			vuln.vuln_attempts.create(attempt_info)
		end

		# Report an exploit attempt all the same
		attempt_info = {
			:attempted_at => timestamp || Time.now.utc,
			:exploited    => false,
			:username     => username  || "unknown",
			:module       => mname,
			:fail_reason  => freason,
			:fail_detail  => fdetail
		}

		attempt_info[:vuln_id] = vuln.id if vuln

		if svc
			attempt_info[:port]  = svc.port
			attempt_info[:proto] = svc.proto
		end
		
		if port and svc.nil?
			attempt_info[:port]  = port
			attempt_info[:proto] = prot || "tcp"
		end

		host.exploit_attempts.create(attempt_info)
	}
	end


	def report_vuln_attempt(vuln, opts)
	::ActiveRecord::Base.connection_pool.with_connection {
		return if not vuln
		info = {}
		
		# Opts can be keyed by strings or symbols
		::Mdm::VulnAttempt.column_names.each do |kn|
			k = kn.to_sym
			next if ['id', 'vuln_id'].include?(kn)
			info[k] = opts[kn] if opts[kn]
			info[k] = opts[k]  if opts[k]
		end

		return unless info[:attempted_at]

		vuln.vuln_attempts.create(info)
	}
	end

	def report_exploit_attempt(host, opts)
	::ActiveRecord::Base.connection_pool.with_connection {
		return if not host
		info = {}
		
		# Opts can be keyed by strings or symbols
		::Mdm::VulnAttempt.column_names.each do |kn|
			k = kn.to_sym
			next if ['id', 'host_id'].include?(kn)
			info[k] = opts[kn] if opts[kn]
			info[k] = opts[k]  if opts[k]
		end

		host.exploit_attempts.create(info)
	}
	end

	def get_client(opts)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		host   = get_host(:workspace => wspace, :host => opts[:host]) || return
		client = host.clients.where({:ua_string => opts[:ua_string]}).first()
		return client
	}
	end

	def find_or_create_client(opts)
		report_client(opts)
	end

	#
	# Report a client running on a host.
	#
	# opts MUST contain
	# +:ua_string+::  the value of the User-Agent header
	# +:host+::       the host where this client connected from, can be an ip address or a Host object
	#
	# opts can contain
	# +:ua_name+::    one of the Msf::HttpClients constants
	# +:ua_ver+::     detected version of the given client
	# +:campaign+::   an id or Campaign object
	#
	# Returns a Client.
	#
	def report_client(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		addr = opts.delete(:host) || return
		wspace = opts.delete(:workspace) || workspace
		report_host(:workspace => wspace, :host => addr)

		ret = {}

		host = get_host(:workspace => wspace, :host => addr)
		client = host.clients.find_or_initialize_by_ua_string(opts[:ua_string])

		opts[:ua_string] = opts[:ua_string].to_s

		campaign = opts.delete(:campaign)
		if campaign
			case campaign
			when Campaign
				opts[:campaign_id] = campaign.id
			else
				opts[:campaign_id] = campaign
			end
		end

		opts.each { |k,v|
			if (client.attribute_names.include?(k.to_s))
				client[k] = v
			else
				dlog("Unknown attribute for Client: #{k}")
			end
		}
		if (client and client.changed?)
			client.save!
		end
		ret[:client] = client
	}
	end

	#
	# This method iterates the vulns table calling the supplied block with the
	# vuln instance of each entry.
	#
	def each_vuln(wspace=workspace,&block)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.vulns.each do |vulns|
			block.call(vulns)
		end
	}
	end

	#
	# This methods returns a list of all vulnerabilities in the database
	#
	def vulns(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.vulns
	}
	end

	#
	# This methods returns a list of all credentials in the database
	#
	def creds(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		Mdm::Cred.includes({:service => :host}).where("hosts.workspace_id = ?", wspace.id)
	}
	end

	#
	# This method returns a list of all exploited hosts in the database.
	#
	def exploited_hosts(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.exploited_hosts
	}
	end

	#
	# This method iterates the notes table calling the supplied block with the
	# note instance of each entry.
	#
	def each_note(wspace=workspace, &block)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.notes.each do |note|
			block.call(note)
		end
	}
	end

	#
	# Find or create a note matching this type/data
	#
	def find_or_create_note(opts)
		report_note(opts)
	end

	#
	# Report a Note to the database.  Notes can be tied to a ::Mdm::Workspace, Host, or Service.
	#
	# opts MUST contain
	# +:data+::  whatever it is you're making a note of
	# +:type+::  The type of note, e.g. smb_peer_os
	#
	# opts can contain
	# +:workspace+::  the workspace to associate with this Note
	# +:host+::       an IP address or a Host object to associate with this Note
	# +:service+::    a Service object to associate with this Note
	# +:port+::       along with +:host+ and +:proto+, a service to associate with this Note
	# +:proto+::      along with +:host+ and +:port+, a service to associate with this Note
	# +:update+::     what to do in case a similar Note exists, see below
	#
	# The +:update+ option can have the following values:
	# +:unique+::       allow only a single Note per +:host+/+:type+ pair
	# +:unique_data+::  like +:uniqe+, but also compare +:data+
	# +:insert+::       always insert a new Note even if one with identical values exists
	#
	# If the provided +:host+ is an IP address and does not exist in the
	# database, it will be created.  If +:workspace+, +:host+ and +:service+
	# are all omitted, the new Note will be associated with the current
	# workspace.
	#
	def report_note(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		if wspace.kind_of? String
			wspace = find_workspace(wspace)
		end
		seen = opts.delete(:seen) || false
		crit = opts.delete(:critical) || false
		host = nil
		addr = nil
		# Report the host so it's there for the Proc to use below
		if opts[:host]
			if opts[:host].kind_of? ::Mdm::Host
				host = opts[:host]
			else
				addr = normalize_host(opts[:host])
				host = report_host({:workspace => wspace, :host => addr})
			end
			# Do the same for a service if that's also included.
			if (opts[:port])
				proto = nil
				sname = nil
				case opts[:proto].to_s.downcase # Catch incorrect usages
				when 'tcp','udp'
					proto = opts[:proto]
					sname = opts[:sname] if opts[:sname]
				when 'dns','snmp','dhcp'
					proto = 'udp'
					sname = opts[:proto]
				else
					proto = 'tcp'
					sname = opts[:proto]
				end
				sopts = {
					:workspace => wspace,
					:host  => host,
					:port  => opts[:port],
					:proto => proto
				}
				sopts[:name] = sname if sname
				report_service(sopts)
			end
		end
		# Update Modes can be :unique, :unique_data, :insert
		mode = opts[:update] || :unique

		ret = {}

		if addr and not host
			host = get_host(:workspace => wspace, :host => addr)
		end
		if host and (opts[:port] and opts[:proto])
			service = get_service(wspace, host, opts[:proto], opts[:port])
		elsif opts[:service] and opts[:service].kind_of? ::Mdm::Service
			service = opts[:service]
		end
=begin
		if host
			host.updated_at = host.created_at
			host.state      = HostState::Alive
			host.save!
		end
=end
		ntype  = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required")
		data   = opts[:data] || (raise RuntimeError, "Note :data is required")
		method = nil
		args   = []
		note   = nil

		conditions = { :ntype => ntype }
		conditions[:host_id] = host[:id] if host
		conditions[:service_id] = service[:id] if service

		case mode
		when :unique
			notes = wspace.notes.where(conditions)

			# Only one note of this type should exist, make a new one if it
			# isn't there. If it is, grab it and overwrite its data.
			if notes.empty?
				note = wspace.notes.new(conditions)
			else
				note = notes[0]
			end
			note.data = data
		when :unique_data
			notes = wspace.notes.where(conditions)

			# Don't make a new Note with the same data as one that already
			# exists for the given: type and (host or service)
			notes.each do |n|
				# Compare the deserialized data from the table to the raw
				# data we're looking for.  Because of the serialization we
				# can't do this easily or reliably in SQL.
				if n.data == data
					note = n
					break
				end
			end
			if not note
				# We didn't find one with the data we're looking for, make
				# a new one.
				note = wspace.notes.new(conditions.merge(:data => data))
			end
		else
			# Otherwise, assume :insert, which means always make a new one
			note = wspace.notes.new
			if host
				note.host_id = host[:id]
			end
			if opts[:service] and opts[:service].kind_of? ::Mdm::Service
				note.service_id = opts[:service][:id]
			end
			note.seen     = seen
			note.critical = crit
			note.ntype    = ntype
			note.data     = data
		end
		msf_import_timestamps(opts,note)
		note.save!
		ret[:note] = note
	}
	end

	#
	# This methods returns a list of all notes in the database
	#
	def notes(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.notes
	}
	end

	# This is only exercised by MSF3 XML importing for now. Needs the wait
	# conditions and return hash as well.
	def report_host_tag(opts)
		name = opts.delete(:name)
		raise DBImportError.new("Missing required option :name") unless name
		addr = opts.delete(:addr)
		raise DBImportError.new("Missing required option :addr") unless addr
		wspace = opts.delete(:wspace)
		raise DBImportError.new("Missing required option :wspace") unless wspace
	::ActiveRecord::Base.connection_pool.with_connection {
		if wspace.kind_of? String
			wspace = find_workspace(wspace)
		end

		host = nil
		report_host(:workspace => wspace, :address => addr)


		host = get_host(:workspace => wspace, :address => addr)
		desc = opts.delete(:desc)
		summary = opts.delete(:summary)
		detail = opts.delete(:detail)
		crit = opts.delete(:crit)
		possible_tags = Mdm::Tag.includes(:hosts).where("hosts.workspace_id = ? and tags.name = ?", wspace.id, name).order("tags.id DESC").limit(1)
		tag = (possible_tags.blank? ? Mdm::Tag.new : possible_tags.first)
		tag.name = name
		tag.desc = desc
		tag.report_summary = !!summary
		tag.report_detail = !!detail
		tag.critical = !!crit
		tag.hosts = tag.hosts | [host]
		tag.save! if tag.changed?
	}
	end

	#
	# Store a set of credentials in the database.
	#
	# report_auth_info used to create a note, now it creates
	# an entry in the creds table. It's much more akin to
	# report_vuln() now.
	#
	# opts MUST contain
	# +:host+::   an IP address or Host object reference
	# +:port+::   a port number
	#
	# opts can contain
	# +:user+::   the username
	# +:pass+::   the password, or path to ssh_key
	# +:ptype+::  the type of password (password(ish), hash, or ssh_key)
	# +:proto+::  a transport name for the port
	# +:sname+::  service name
	# +:active+:: by default, a cred is active, unless explicitly false
	# +:proof+::  data used to prove the account is actually active.
	#
	# Sources: Credentials can be sourced from another credential, or from
	# a vulnerability. For example, if an exploit was used to dump the
	# smb_hashes, and this credential comes from there, the source_id would
	# be the Vuln id (as reported by report_vuln) and the type would be "Vuln".
	#
	# +:source_id+::   The Vuln or Cred id of the source of this cred.
	# +:source_type+:: Either Vuln or Cred
	#
	# TODO: This is written somewhat host-centric, when really the
	# Service is the thing. Need to revisit someday.
	def report_auth_info(opts={})
		return if not active
		raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
		raise ArgumentError.new("Missing required option :port") if (opts[:port].nil? and opts[:service].nil?)

		if (not opts[:host].kind_of?(::Mdm::Host)) and (not validate_ips(opts[:host]))
			raise ArgumentError.new("Invalid address or object for :host (#{opts[:host].inspect})")
		end

		host = opts.delete(:host)
		ptype = opts.delete(:type) || "password"
		token = [opts.delete(:user), opts.delete(:pass)]
		sname = opts.delete(:sname)
		port = opts.delete(:port)
		proto = opts.delete(:proto) || "tcp"
		proof = opts.delete(:proof)
		source_id = opts.delete(:source_id)
		source_type = opts.delete(:source_type)
		duplicate_ok = opts.delete(:duplicate_ok)
		# Nil is true for active.
		active = (opts[:active] || opts[:active].nil?) ? true : false

		wspace = opts.delete(:workspace) || workspace

		# Service management; assume the user knows what
		# he's talking about.
		service = opts.delete(:service) || report_service(:host => host, :port => port, :proto => proto, :name => sname, :workspace => wspace)

		# Non-US-ASCII usernames are tripping up the database at the moment, this is a temporary fix until we update the tables
		( token[0] = token[0].gsub(/[\x00-\x1f\x7f-\xff]/){|m| "\\x%.2x" % m.unpack("C")[0] } ) if token[0]
		( token[1] = token[1].gsub(/[\x00-\x1f\x7f-\xff]/){|m| "\\x%.2x" % m.unpack("C")[0] } ) if token[1]

		ret = {}

		#Check to see if the creds already exist. We look also for a downcased username with the
		#same password because we can fairly safely assume they are not in fact two seperate creds.
		#this allows us to hedge against duplication of creds in the DB.

		if duplicate_ok
		# If duplicate usernames are okay, find by both user and password (allows
		# for actual duplicates to get modified updated_at, sources, etc)
			if token[0].nil? or token[0].empty?
				cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
			else
				cred = service.creds.find_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
				unless cred
					dcu = token[0].downcase
					cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
					unless cred
						cred = service.creds.find_or_initialize_by_user_and_ptype_and_pass(token[0] || "", ptype, token[1] || "")
					end
				end
			end
		else
			# Create the cred by username only (so we can change passwords)
			if token[0].nil? or token[0].empty?
				cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
			else
				cred = service.creds.find_by_user_and_ptype(token[0] || "", ptype)
				unless cred
					dcu = token[0].downcase
					cred = service.creds.find_by_user_and_ptype_and_pass( dcu || "", ptype, token[1] || "")
					unless cred
						cred = service.creds.find_or_initialize_by_user_and_ptype(token[0] || "", ptype)
					end
				end
			end
		end

		# Update with the password
		cred.pass = (token[1] || "")

		# Annotate the credential
		cred.ptype = ptype
		cred.active = active

		# Update the source ID only if there wasn't already one.
		if source_id and !cred.source_id
			cred.source_id = source_id
			cred.source_type = source_type if source_type
		end

		# Safe proof (lazy way) -- doesn't chop expanded
		# characters correctly, but shouldn't ever be a problem.
		unless proof.nil?
			proof = Rex::Text.to_hex_ascii(proof)
			proof = proof[0,4096]
		end
		cred.proof = proof

		# Update the timestamp
		if cred.changed?
			msf_import_timestamps(opts,cred)
			cred.save!
		end

		# Ensure the updated_at is touched any time report_auth_info is called
		# except when it's set explicitly (as it is for imports)
		unless opts[:updated_at] || opts["updated_at"]
			cred.updated_at = Time.now.utc
			cred.save!
		end

		ret[:cred] = cred
	end

	alias :report_cred :report_auth_info
	alias :report_auth :report_auth_info

	#
	# Find or create a credential matching this type/data
	#
	def find_or_create_cred(opts)
		report_auth_info(opts)
	end

	#
	# This method iterates the creds table calling the supplied block with the
	# cred instance of each entry.
	#
	def each_cred(wspace=workspace,&block)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.creds.each do |cred|
			block.call(cred)
		end
	}
	end

	def each_exploited_host(wspace=workspace,&block)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.exploited_hosts.each do |eh|
			block.call(eh)
		end
	}
	end

	#
	# Find or create a vuln matching this service/name
	#
	def find_or_create_vuln(opts)
		report_vuln(opts)
	end

	#
	# opts MUST contain
	# +:host+:: the host where this vulnerability resides
	# +:name+:: the friendly name for this vulnerability (title)
	#
	# opts can contain
	# +:info+::   a human readable description of the vuln, free-form text
	# +:refs+::   an array of Ref objects or string names of references
	# +:details:: a hash with :key pointed to a find criteria hash and the rest containing VulnDetail fields
	#
	def report_vuln(opts)
		return if not active
		raise ArgumentError.new("Missing required option :host") if opts[:host].nil?
		raise ArgumentError.new("Deprecated data column for vuln, use .info instead") if opts[:data]
		name = opts[:name] || return
		info = opts[:info]

	::ActiveRecord::Base.connection_pool.with_connection {

		wspace = opts.delete(:workspace) || workspace
		exploited_at = opts[:exploited_at] || opts["exploited_at"]
		details = opts.delete(:details)
		rids = opts.delete(:ref_ids)

		if opts[:refs]
			rids ||= []
			opts[:refs].each do |r|
				if (r.respond_to?(:ctx_id)) and (r.respond_to?(:ctx_val))
					r = "#{r.ctx_id}-#{r.ctx_val}"
				end
				rids << find_or_create_ref(:name => r)
			end
		end

		host = nil
		addr = nil
		if opts[:host].kind_of? ::Mdm::Host
			host = opts[:host]
		else
			host = report_host({:workspace => wspace, :host => opts[:host]})
			addr = normalize_host(opts[:host])
		end

		ret = {}

		# Truncate the info field at the maximum field length
		if info
			info = info[0,65535]
		end

		# Truncate the name field at the maximum field length
		name = name[0,255]

		# Placeholder for the vuln object
		vuln = nil

		# Identify the associated service
		service = opts.delete(:service)

		# Treat port zero as no service
		if service or opts[:port].to_i > 0

			if not service
				proto = nil
				case opts[:proto].to_s.downcase # Catch incorrect usages, as in report_note
				when 'tcp','udp'
					proto = opts[:proto]
				when 'dns','snmp','dhcp'
					proto = 'udp'
					sname = opts[:proto]
				else
					proto = 'tcp'
					sname = opts[:proto]
				end

				service = host.services.find_or_create_by_port_and_proto(opts[:port].to_i, proto)
			end

			# Try to find an existing vulnerability with the same service & references
			# If there are multiple matches, choose the one with the most matches
			# If a match is found on a vulnerability with no associated service,
			# update that vulnerability with our service information. This helps
			# prevent dupes of the same vuln found by both local patch and
			# service detection.		
			if rids and rids.length > 0
				vuln = find_vuln_by_refs(rids, host, service)
				vuln.service = service if vuln
			end
		else
			# Try to find an existing vulnerability with the same host & references
			# If there are multiple matches, choose the one with the most matches
			if rids and rids.length > 0
				vuln = find_vuln_by_refs(rids, host)
			end
		end

		# Try to match based on vuln_details records
		if not vuln and opts[:details_match]
			vuln = find_vuln_by_details(opts[:details_match], host, service)
			if vuln and service and not vuln.service
				vuln.service = service
			end
		end

		# No matches, so create a new vuln record
		unless vuln
			if service
				vuln = service.vulns.find_by_name(name)
			else
				vuln = host.vulns.find_by_name(name)
			end
			
			unless vuln

				vinf = {
					:host_id => host.id,
					:name    => name,
					:info    => info
				}

				vinf[:service_id] = service.id if service 
				vuln = Mdm::Vuln.create(vinf)
			end
		end

		# Set the exploited_at value if provided
		vuln.exploited_at = exploited_at if exploited_at

		# Merge the references
		if rids
			vuln.refs << (rids - vuln.refs)
		end

		# Finalize
		if vuln.changed?
			msf_import_timestamps(opts,vuln)
			vuln.save!
		end

		# Handle vuln_details parameters
		report_vuln_details(vuln, details) if details
		
		vuln
	}
	end

	def find_vuln_by_refs(refs, host, service=nil)

		vuln = nil

		# Try to find an existing vulnerability with the same service & references
		# If there are multiple matches, choose the one with the most matches
		if service
			refs_ids = refs.map{|x| x.id }
			vuln = service.vulns.find(:all, :include => [:refs], :conditions => { 'refs.id' => refs_ids }).sort { |a,b|
				( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
			}.first
		end

		# Return if we matched based on service
		return vuln if vuln

		# Try to find an existing vulnerability with the same host & references
		# If there are multiple matches, choose the one with the most matches
		refs_ids = refs.map{|x| x.id }
		vuln = host.vulns.find(:all, :include => [:refs], :conditions => { 'service_id' => nil, 'refs.id' => refs_ids }).sort { |a,b|
			( refs_ids - a.refs.map{|x| x.id } ).length <=> ( refs_ids - b.refs.map{|x| x.id } ).length
		}.first

		return vuln
	end


	def find_vuln_by_details(details_map, host, service=nil)

		# Create a modified version of the criteria in order to match against
		# the joined version of the fields

		crit = {}
		details_map.each_pair do |k,v|
			crit[ "vuln_details.#{k}" ] = v
		end

		vuln = nil

		if service
			vuln = service.vulns.find(:first, :include => [:vuln_details], :conditions => crit)
		end

		# Return if we matched based on service
		return vuln if vuln

		# Prevent matches against other services
		crit["vulns.service_id"] = nil if service
		vuln = host.vulns.find(:first, :include => [:vuln_details], :conditions => crit)

		return vuln
	end

	def get_vuln(wspace, host, service, name, data='')
		raise RuntimeError, "Not workspace safe: #{caller.inspect}"
	::ActiveRecord::Base.connection_pool.with_connection {
		vuln = nil
		if (service)
			vuln = ::Mdm::Vuln.find.where("name = ? and service_id = ? and host_id = ?", name, service.id, host.id).order("vulns.id DESC").first()
		else
			vuln = ::Mdm::Vuln.find.where("name = ? and host_id = ?", name, host.id).first()
		end

		return vuln
	}
	end

	#
	# Find or create a reference matching this name
	#
	def find_or_create_ref(opts)
		ret = {}
		ret[:ref] = get_ref(opts[:name])
		return ret[:ref] if ret[:ref]

	::ActiveRecord::Base.connection_pool.with_connection {
		ref = ::Mdm::Ref.find_or_initialize_by_name(opts[:name])
		if ref and ref.changed?
			ref.save!
		end
		ret[:ref] = ref
	}
	end

	def get_ref(name)
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::Ref.find_by_name(name)
	}
	end

	#
	# Populate the vuln_details table with additional
	# information, matched by a specific criteria
	#
	def report_vuln_details(vuln, details)
	::ActiveRecord::Base.connection_pool.with_connection {
		detail = ::Mdm::VulnDetail.where(( details.delete(:key) || {} ).merge(:vuln_id => vuln.id)).first
		if detail
			details.each_pair do |k,v|
				detail[k] = v
			end
			detail.save! if detail.changed?
			detail
		else
			detail = ::Mdm::VulnDetail.create(details.merge(:vuln_id => vuln.id))
		end
	}
	end

	#
	# Update vuln_details records en-masse based on specific criteria
	# Note that this *can* update data across workspaces
	#
	def update_vuln_details(details)
		criteria = details.delete(:key) || {}
		::Mdm::VulnDetail.update(key, details)
	end

	#
	# Populate the host_details table with additional
	# information, matched by a specific criteria
	#
	def report_host_details(host, details)
	::ActiveRecord::Base.connection_pool.with_connection {

		detail = ::Mdm::HostDetail.where(( details.delete(:key) || {} ).merge(:host_id => host.id)).first
		if detail
			details.each_pair do |k,v|
				detail[k] = v
			end
			detail.save! if detail.changed?
			detail
		else
			detail = ::Mdm::HostDetail.create(details.merge(:host_id => host.id))
		end
	}
	end

	# report_exploit() used to be used to track sessions and which modules
	# opened them. That information is now available with the session table
	# directly. TODO: kill this completely some day -- for now just warn if
	# some other UI is actually using it.
	def report_exploit(opts={})
		wlog("Deprecated method call: report_exploit()\n" +
			"report_exploit() options: #{opts.inspect}\n" +
			"report_exploit() call stack:\n\t#{caller.join("\n\t")}"
		)
	end

	#
	# Deletes a host and associated data matching this address/comm
	#
	def del_host(wspace, address, comm='')
	::ActiveRecord::Base.connection_pool.with_connection {
		address, scope = address.split('%', 2)
		host = wspace.hosts.find_by_address_and_comm(address, comm)
		host.destroy if host
	}
	end

	#
	# Deletes a port and associated vulns matching this port
	#
	def del_service(wspace, address, proto, port, comm='')

		host = get_host(:workspace => wspace, :address => address)
		return unless host

	::ActiveRecord::Base.connection_pool.with_connection {
		host.services.where({:proto => proto, :port => port}).each { |s| s.destroy }
	}
	end

	#
	# Find a reference matching this name
	#
	def has_ref?(name)
	::ActiveRecord::Base.connection_pool.with_connection {
		Mdm::Ref.find_by_name(name)
	}
	end

	#
	# Find a vulnerability matching this name
	#
	def has_vuln?(name)
	::ActiveRecord::Base.connection_pool.with_connection {
		Mdm::Vuln.find_by_name(name)
	}
	end

	#
	# Look for an address across all comms
	#
	def has_host?(wspace,addr)
	::ActiveRecord::Base.connection_pool.with_connection {
		address, scope = addr.split('%', 2)
		wspace.hosts.find_by_address(addr)
	}
	end

	def events(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.events.find :all, :order => 'created_at ASC'
	}
	end

	def report_event(opts = {})
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		return if not wspace # Temp fix?
		uname  = opts.delete(:username)

		if ! opts[:host].kind_of? ::Mdm::Host and opts[:host]
			opts[:host] = report_host(:workspace => wspace, :host => opts[:host])
		end

		::Mdm::Event.create(opts.merge(:workspace_id => wspace[:id], :username => uname))
	}
	end

	#
	# Loot collection
	#
	#
	# This method iterates the loot table calling the supplied block with the
	# instance of each entry.
	#
	def each_loot(wspace=workspace, &block)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.loots.each do |note|
			block.call(note)
		end
	}
	end

	#
	# Find or create a loot matching this type/data
	#
	def find_or_create_loot(opts)
		report_loot(opts)
	end

	def report_loot(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		path = opts.delete(:path) || (raise RuntimeError, "A loot :path is required")

		host = nil
		addr = nil

		# Report the host so it's there for the Proc to use below
		if opts[:host]
			if opts[:host].kind_of? ::Mdm::Host
				host = opts[:host]
			else
				host = report_host({:workspace => wspace, :host => opts[:host]})
				addr = normalize_host(opts[:host])
			end
		end

		ret = {}

		ltype  = opts.delete(:type) || opts.delete(:ltype) || (raise RuntimeError, "A loot :type or :ltype is required")
		ctype  = opts.delete(:ctype) || opts.delete(:content_type) || 'text/plain'
		name   = opts.delete(:name)
		info   = opts.delete(:info)
		data   = opts[:data]
		loot   = wspace.loots.new

		if host
			loot.host_id = host[:id]
		end
		if opts[:service] and opts[:service].kind_of? ::Mdm::Service
			loot.service_id = opts[:service][:id]
		end

		loot.path  = path
		loot.ltype = ltype
		loot.content_type = ctype
		loot.data  = data
		loot.name  = name if name
		loot.info  = info if info
		msf_import_timestamps(opts,loot)
		loot.save!

		if !opts[:created_at]
=begin
			if host
				host.updated_at = host.created_at
				host.state      = HostState::Alive
				host.save!
			end
=end
		end

		ret[:loot] = loot
	}
	end

	#
	# This methods returns a list of all loot in the database
	#
	def loots(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.loots
	}
	end

	#
	# Find or create a task matching this type/data
	#
	def find_or_create_task(opts)
		report_task(opts)
	end

	def report_task(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		path = opts.delete(:path) || (raise RuntimeError, "A task :path is required")

		ret = {}

		user      = opts.delete(:user)
		desc      = opts.delete(:desc)
		error     = opts.delete(:error)
		info      = opts.delete(:info)
		mod       = opts.delete(:mod)
		options   = opts.delete(:options)
		prog      = opts.delete(:prog)
		result    = opts.delete(:result)
		completed_at = opts.delete(:completed_at)
		task      = wspace.tasks.new

		task.created_by = user
		task.description = desc
		task.error = error if error
		task.info = info
		task.module = mod
		task.options = options
		task.path = path
		task.progress = prog
		task.result = result if result
		msf_import_timestamps(opts,task)
		# Having blank completed_ats, while accurate, will cause unstoppable tasks.
		if completed_at.nil? || completed_at.empty?
			task.completed_at = opts[:updated_at]
		else
			task.completed_at = completed_at
		end
		task.save!
		ret[:task] = task
	}
	end

	#
	# This methods returns a list of all tasks in the database
	#
	def tasks(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.tasks
	}
	end


	#
	# Find or create a task matching this type/data
	#
	def find_or_create_report(opts)
		report_report(opts)
	end

	def report_report(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace
		path = opts.delete(:path) || (raise RuntimeError, "A report :path is required")

		ret = {}
		user      = opts.delete(:user)
		options   = opts.delete(:options)
		rtype     = opts.delete(:rtype)
		report    = wspace.reports.new
		report.created_by = user
		report.options = options
		report.rtype = rtype
		report.path = path
		msf_import_timestamps(opts,report)
		report.save!

		ret[:task] = report
	}
	end

	#
	# This methods returns a list of all reports in the database
	#
	def reports(wspace=workspace)
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace.reports
	}
	end

	#
	# WMAP
	# Support methods
	#

	#
	# Report a Web Site to the database.  WebSites must be tied to an existing Service
	#
	# opts MUST contain
	# +:service+:: the service object this site should be associated with
	# +:vhost+::   the virtual host name for this particular web site`
	#
	# If +:service+ is NOT specified, the following values are mandatory
	# +:host+:: the ip address of the server hosting the web site
	# +:port+:: the port number of the associated web site
	# +:ssl+::  whether or not SSL is in use on this port
	#
	# These values will be used to create new host and service records
	#
	# opts can contain
	# +:options+:: a hash of options for accessing this particular web site
	# +:info+:: if present, report the service with this info
	#
	# Duplicate records for a given host, port, vhost combination will be overwritten
	#
	def report_web_site(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection { |conn|
		wspace = opts.delete(:workspace) || workspace
		vhost  = opts.delete(:vhost)

		addr = nil
		port = nil
		name = nil
		serv = nil
		info = nil

		if opts[:service] and opts[:service].kind_of?(::Mdm::Service)
			serv = opts[:service]
		else
			addr = opts[:host]
			port = opts[:port]
			name = opts[:ssl] ? 'https' : 'http'
			info = opts[:info]
			if not (addr and port)
				raise ArgumentError, "report_web_site requires service OR host/port/ssl"
			end

			# Force addr to be the address and not hostname
			addr = Rex::Socket.getaddress(addr, true)
		end

		ret = {}

		host = serv ? serv.host : find_or_create_host(
			:workspace => wspace,
			:host      => addr,
			:state     => Msf::HostState::Alive
		)

		if host.name.to_s.empty?
			host.name = vhost
			host.save!
		end

		serv = serv ? serv : find_or_create_service(
			:workspace => wspace,
			:host      => host,
			:port      => port,
			:proto     => 'tcp',
			:state     => 'open'
		)

		# Change the service name if it is blank or it has
		# been explicitly specified.
		if opts.keys.include?(:ssl) or serv.name.to_s.empty?
			name = opts[:ssl] ? 'https' : 'http'
			serv.name = name
		end
		# Add the info if it's there.
		unless info.to_s.empty?
			serv.info = info
		end
		serv.save! if serv.changed?
=begin
		host.updated_at = host.created_at
		host.state      = HostState::Alive
		host.save!
=end

		vhost ||= host.address
		site = ::Mdm::WebSite.find_or_initialize_by_vhost_and_service_id(vhost, serv[:id])
		site.options = opts[:options] if opts[:options]

		# XXX:
		msf_import_timestamps(opts, site)
		site.save!

		ret[:web_site] = site
	}
	end

	#
	# Report a Web Page to the database.  WebPage must be tied to an existing Web Site
	#
	# opts MUST contain
	# +:web_site+:: the web site object that this page should be associated with
	# +:path+::     the virtual host name for this particular web site
	# +:code+::     the http status code from requesting this page
	# +:headers+::  this is a HASH of headers (lowercase name as key) of ARRAYs of values
	# +:body+::     the document body of the server response
	# +:query+::    the query string after the path
	#
	# If web_site is NOT specified, the following values are mandatory
	# +:host+::  the ip address of the server hosting the web site
	# +:port+::  the port number of the associated web site
	# +:vhost+:: the virtual host for this particular web site
	# +:ssl+::   whether or not SSL is in use on this port
	#
	# These values will be used to create new host, service, and web_site records
	#
	# opts can contain
	# +:cookie+::   the Set-Cookie headers, merged into a string
	# +:auth+::     the Authorization headers, merged into a string
	# +:ctype+::    the Content-Type headers, merged into a string
	# +:mtime+::    the timestamp returned from the server of the last modification time
	# +:location+:: the URL that a redirect points to
	#
	# Duplicate records for a given web_site, path, and query combination will be overwritten
	#

	def report_web_page(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace

		path    = opts[:path]
		code    = opts[:code].to_i
		body    = opts[:body].to_s
		query   = opts[:query].to_s
		headers = opts[:headers]
		site    = nil

		if not (path and code and body and headers)
			raise ArgumentError, "report_web_page requires the path, query, code, body, and headers parameters"
		end

		if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite)
			site = opts.delete(:web_site)
		else
			site = report_web_site(
				:workspace => wspace,
				:host      => opts[:host], :port => opts[:port],
				:vhost     => opts[:host], :ssl  => opts[:ssl]
			)
			if not site
				raise ArgumentError, "report_web_page was unable to create the associated web site"
			end
		end

		ret = {}

		page = ::Mdm::WebPage.find_or_initialize_by_web_site_id_and_path_and_query(site[:id], path, query)
		page.code     = code
		page.body     = body
		page.headers  = headers
		page.cookie   = opts[:cookie] if opts[:cookie]
		page.auth     = opts[:auth]   if opts[:auth]
		page.mtime    = opts[:mtime]  if opts[:mtime]
		page.ctype    = opts[:ctype]  if opts[:ctype]
		page.location = opts[:location] if opts[:location]
		msf_import_timestamps(opts, page)
		page.save!

		ret[:web_page] = page
	}

	end


	#
	# Report a Web Form to the database.  WebForm must be tied to an existing Web Site
	#
	# opts MUST contain
	# +:web_site+:: the web site object that this page should be associated with
	# +:path+::     the virtual host name for this particular web site
	# +:query+::    the query string that is appended to the path (not valid for GET)
	# +:method+::   the form method, one of GET, POST, or PATH
	# +:params+::   an ARRAY of all parameters and values specified in the form
	#
	# If web_site is NOT specified, the following values are mandatory
	# +:host+::  the ip address of the server hosting the web site
	# +:port+::  the port number of the associated web site
	# +:vhost+:: the virtual host for this particular web site
	# +:ssl+::   whether or not SSL is in use on this port
	#
	# Duplicate records for a given web_site, path, method, and params combination will be overwritten
	#

	def report_web_form(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace

		path    = opts[:path]
		meth    = opts[:method].to_s.upcase
		para    = opts[:params]
		quer    = opts[:query].to_s
		site    = nil

		if not (path and meth)
			raise ArgumentError, "report_web_form requires the path and method parameters"
		end

		if not %W{GET POST PATH}.include?(meth)
			raise ArgumentError, "report_web_form requires the method to be one of GET, POST, PATH"
		end

		if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite)
			site = opts.delete(:web_site)
		else
			site = report_web_site(
				:workspace => wspace,
				:host      => opts[:host], :port => opts[:port],
				:vhost     => opts[:host], :ssl  => opts[:ssl]
			)
			if not site
				raise ArgumentError, "report_web_form was unable to create the associated web site"
			end
		end

		ret = {}

		# Since one of our serialized fields is used as a unique parameter, we must do the final
		# comparisons through ruby and not SQL.

		form = nil
		::Mdm::WebForm.find_all_by_web_site_id_and_path_and_method_and_query(site[:id], path, meth, quer).each do |xform|
			if xform.params == para
				form = xform
				break
			end
		end
		if not form
			form = ::Mdm::WebForm.new
			form.web_site_id = site[:id]
			form.path        = path
			form.method      = meth
			form.params      = para
			form.query       = quer
		end

		msf_import_timestamps(opts, form)
		form.save!
		ret[:web_form] = form
	}
	end


	#
	# Report a Web Vuln to the database.  WebVuln must be tied to an existing Web Site
	#
	# opts MUST contain
	# +:web_site+::  the web site object that this page should be associated with
	# +:path+::      the virtual host name for this particular web site
	# +:query+::     the query string appended to the path (not valid for GET method flaws)
	# +:method+::    the form method, one of GET, POST, or PATH
	# +:params+::    an ARRAY of all parameters and values specified in the form
	# +:pname+::     the specific field where the vulnerability occurs
	# +:proof+::     the string showing proof of the vulnerability
	# +:risk+::      an INTEGER value from 0 to 5 indicating the risk (5 is highest)
	# +:name+::      the string indicating the type of vulnerability
	#
	# If web_site is NOT specified, the following values are mandatory
	# +:host+::  the ip address of the server hosting the web site
	# +:port+::  the port number of the associated web site
	# +:vhost+:: the virtual host for this particular web site
	# +:ssl+::   whether or not SSL is in use on this port
	#
	#
	# Duplicate records for a given web_site, path, method, pname, and name combination will be overwritten
	#

	def report_web_vuln(opts)
		return if not active
	::ActiveRecord::Base.connection_pool.with_connection {
		wspace = opts.delete(:workspace) || workspace

		path    = opts[:path]
		meth    = opts[:method]
		para    = opts[:params] || []
		quer    = opts[:query].to_s
		pname   = opts[:pname]
		proof   = opts[:proof]
		risk    = opts[:risk].to_i
		name    = opts[:name].to_s.strip
		blame   = opts[:blame].to_s.strip
		desc    = opts[:description].to_s.strip
		conf    = opts[:confidence].to_i
		cat     = opts[:category].to_s.strip
		payload = opts[:payload].to_s
		owner   = opts[:owner] ? opts[:owner].shortname : nil


		site    = nil

		if not (path and meth and proof and pname)
			raise ArgumentError, "report_web_vuln requires the path, method, proof, risk, name, params, and pname parameters. Received #{opts.inspect}"
		end

		if not %W{GET POST PATH}.include?(meth)
			raise ArgumentError, "report_web_vuln requires the method to be one of GET, POST, PATH. Received '#{meth}'"
		end

		if risk < 0 or risk > 5
			raise ArgumentError, "report_web_vuln requires the risk to be between 0 and 5 (inclusive). Received '#{risk}'"
		end

		if conf < 0 or conf > 100
			raise ArgumentError, "report_web_vuln requires the confidence to be between 1 and 100 (inclusive). Received '#{conf}'"
		end

		if cat.empty?
			raise ArgumentError, "report_web_vuln requires the category to be a valid string"
		end

		if name.empty?
			raise ArgumentError, "report_web_vuln requires the name to be a valid string"
		end

		if opts[:web_site] and opts[:web_site].kind_of?(::Mdm::WebSite)
			site = opts.delete(:web_site)
		else
			site = report_web_site(
				:workspace => wspace,
				:host      => opts[:host], :port => opts[:port],
				:vhost     => opts[:host], :ssl  => opts[:ssl]
			)
			if not site
				raise ArgumentError, "report_web_form was unable to create the associated web site"
			end
		end

		ret = {}

		meth = meth.to_s.upcase

		vuln = ::Mdm::WebVuln.find_or_initialize_by_web_site_id_and_path_and_method_and_pname_and_name_and_category_and_query(site[:id], path, meth, pname, name, cat, quer)
		vuln.name     = name
		vuln.risk     = risk
		vuln.params   = para
		vuln.proof    = proof.to_s
		vuln.category = cat
		vuln.blame    = blame
		vuln.description = desc
		vuln.confidence  = conf
		vuln.payload = payload
		vuln.owner   = owner

		msf_import_timestamps(opts, vuln)
		vuln.save!

		ret[:web_vuln] = vuln
	}
	end

	#
	# WMAP
	# Selected host
	#
	def selected_host
	::ActiveRecord::Base.connection_pool.with_connection {
		selhost = ::Mdm::WmapTarget.where("selected != 0").first()
		if selhost
			return selhost.host
		else
			return
		end
	}
	end

	#
	# WMAP
	# Selected target
	#
	def selected_wmap_target
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::WmapTarget.find.where("selected != 0")
	}
	end

	#
	# WMAP
	# Selected port
	#
	def selected_port
		selected_wmap_target.port
	end

	#
	# WMAP
	# Selected ssl
	#
	def selected_ssl
		selected_wmap_target.ssl
	end

	#
	# WMAP
	# Selected id
	#
	def selected_id
		selected_wmap_target.object_id
	end

	#
	# WMAP
	# This method iterates the requests table identifiying possible targets
	# This method wiil be remove on second phase of db merging.
	#
	def each_distinct_target(&block)
		request_distinct_targets.each do |target|
			block.call(target)
		end
	end

	#
	# WMAP
	# This method returns a list of all possible targets available in requests
	# This method wiil be remove on second phase of db merging.
	#
	def request_distinct_targets
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::WmapRequest.select('DISTINCT host,address,port,ssl')
	}
	end

	#
	# WMAP
	# This method iterates the requests table returning a list of all requests of a specific target
	#
	def each_request_target_with_path(&block)
		target_requests('AND wmap_requests.path IS NOT NULL').each do |req|
			block.call(req)
		end
	end

	#
	# WMAP
	# This method iterates the requests table returning a list of all requests of a specific target
	#
	def each_request_target_with_query(&block)
		target_requests('AND wmap_requests.query IS NOT NULL').each do |req|
			block.call(req)
		end
	end

	#
	# WMAP
	# This method iterates the requests table returning a list of all requests of a specific target
	#
	def each_request_target_with_body(&block)
		target_requests('AND wmap_requests.body IS NOT NULL').each do |req|
			block.call(req)
		end
	end

	#
	# WMAP
	# This method iterates the requests table returning a list of all requests of a specific target
	#
	def each_request_target_with_headers(&block)
		target_requests('AND wmap_requests.headers IS NOT NULL').each do |req|
			block.call(req)
		end
	end

	#
	# WMAP
	# This method iterates the requests table returning a list of all requests of a specific target
	#
	def each_request_target(&block)
		target_requests('').each do |req|
			block.call(req)
		end
	end

	#
	# WMAP
	# This method returns a list of all requests from target
	#
	def target_requests(extra_condition)
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}",selected_host,selected_port)
	}
	end

	#
	# WMAP
	# This method iterates the requests table calling the supplied block with the
	# request instance of each entry.
	#
	def each_request(&block)
		requests.each do |request|
			block.call(request)
		end
	end

	#
	# WMAP
	# This method allows to query directly the requests table. To be used mainly by modules
	#
	def request_sql(host,port,extra_condition)
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::WmapRequest.where("wmap_requests.host = ? AND wmap_requests.port = ? #{extra_condition}", host , port)
	}
	end

	#
	# WMAP
	# This methods returns a list of all targets in the database
	#
	def requests
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::WmapRequest.find(:all)
	}
	end

	#
	# WMAP
	# This method iterates the targets table calling the supplied block with the
	# target instance of each entry.
	#
	def each_target(&block)
		targets.each do |target|
			block.call(target)
		end
	end

	#
	# WMAP
	# This methods returns a list of all targets in the database
	#
	def targets
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::WmapTarget.find(:all)
	}
	end

	#
	# WMAP
	# This methods deletes all targets from targets table in the database
	#
	def delete_all_targets
	::ActiveRecord::Base.connection_pool.with_connection {
		::Mdm::WmapTarget.delete_all
	}
	end

	#
	# WMAP
	# Find a target matching this id
	#
	def get_target(id)
	::ActiveRecord::Base.connection_pool.with_connection {
		target = ::Mdm::WmapTarget.where("id = ?", id).first()
		return target
	}
	end

	#
	# WMAP
	# Create a target
	#
	def create_target(host,port,ssl,sel)
	::ActiveRecord::Base.connection_pool.with_connection {
		tar = ::Mdm::WmapTarget.create(
				:host => host,
				:address => host,
				:port => port,
				:ssl => ssl,
				:selected => sel
			)
		#framework.events.on_db_target(rec)
	}
	end


	#
	# WMAP
	# Create a request (by hand)
	#
	def create_request(host,port,ssl,meth,path,headers,query,body,respcode,resphead,response)
	::ActiveRecord::Base.connection_pool.with_connection {
		req = ::Mdm::WmapRequest.create(
				:host => host,
				:address => host,
				:port => port,
				:ssl => ssl,
				:meth => meth,
				:path => path,
				:headers => headers,
				:query => query,
				:body => body,
				:respcode => respcode,
				:resphead => resphead,
				:response => response
			)
		#framework.events.on_db_request(rec)
	}
	end

	#
	# WMAP
	# Quick way to query the database (used by wmap_sql)
	#
	def sql_query(sqlquery)
	::ActiveRecord::Base.connection_pool.with_connection {
		ActiveRecord::Base.connection.select_all(sqlquery)
	}
	end


	# Returns a REXML::Document from the given data.
	def rexmlify(data)
		if data.kind_of?(REXML::Document)
			return data
		else
			# Make an attempt to recover from a REXML import fail, since
			# it's better than dying outright.
			begin
				return REXML::Document.new(data)
			rescue REXML::ParseException => e
				dlog("REXML error: Badly formatted XML, attempting to recover. Error was: #{e.inspect}")
				return REXML::Document.new(data.gsub(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xff])/){ |x| "\\x%.2x" % x.unpack("C*")[0] })
			end
		end
	end

	# Handles timestamps from Metasploit Express/Pro imports.
	def msf_import_timestamps(opts,obj)
		obj.created_at = opts["created_at"] if opts["created_at"]
		obj.created_at = opts[:created_at] if opts[:created_at]
		obj.updated_at = opts["updated_at"] ? opts["updated_at"] : obj.created_at
		obj.updated_at = opts[:updated_at] ? opts[:updated_at] : obj.created_at
		return obj
	end

	##
	#
	# Import methods
	#
	##

	#
	# Generic importer that automatically determines the file type being
	# imported.  Since this looks for vendor-specific strings in the given
	# file, there shouldn't be any false detections, but no guarantees.
	#
	def import_file(args={}, &block)
		filename = args[:filename] || args['filename']
		wspace = args[:wspace] || args['wspace'] || workspace
		@import_filedata            = {}
		@import_filedata[:filename] = filename

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(4)
		end

		case data[0,4]
		when "PK\x03\x04"
			data = Zip::ZipFile.open(filename)
		when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4"
			data = PacketFu::PcapFile.new(:filename => filename)
		else
			::File.open(filename, 'rb') do |f|
				sz = f.stat.size
				data = f.read(sz)
			end
		end
		if block
			import(args.merge(:data => data)) { |type,data| yield type,data }
		else
			import(args.merge(:data => data))
		end

	end

	# A dispatcher method that figures out the data's file type,
	# and sends it off to the appropriate importer. Note that
	# import_file_detect will raise an error if the filetype
	# is unknown.
	def import(args={}, &block)
		data = args[:data] || args['data']
		wspace = args[:wspace] || args['wspace'] || workspace
		ftype = import_filetype_detect(data)
		yield(:filetype, @import_filedata[:type]) if block
		self.send "import_#{ftype}".to_sym, args, &block
	end

	# Returns one of: :nexpose_simplexml :nexpose_rawxml :nmap_xml :openvas_xml
	# :nessus_xml :nessus_xml_v2 :qualys_scan_xml, :qualys_asset_xml, :msf_xml :nessus_nbe :amap_mlog
	# :amap_log :ip_list, :msf_zip, :libpcap, :foundstone_xml, :acunetix_xml, :appscan_xml
	# :burp_session, :ip360_xml_v3, :ip360_aspl_xml, :nikto_xml
	# If there is no match, an error is raised instead.
	def import_filetype_detect(data)

		if data and data.kind_of? Zip::ZipFile
			raise DBImportError.new("The zip file provided is empty.") if data.entries.empty?
			@import_filedata ||= {}
			@import_filedata[:zip_filename] = File.split(data.to_s).last
			@import_filedata[:zip_basename] = @import_filedata[:zip_filename].gsub(/\.zip$/,"")
			@import_filedata[:zip_entry_names] = data.entries.map {|x| x.name}
			begin
				@import_filedata[:zip_xml] = @import_filedata[:zip_entry_names].grep(/^(.*)_[0-9]+\.xml$/).first || raise
				@import_filedata[:zip_wspace] = @import_filedata[:zip_xml].to_s.match(/^(.*)_[0-9]+\.xml$/)[1]
				@import_filedata[:type] = "Metasploit ZIP Report"
				return :msf_zip
			rescue ::Interrupt
				raise $!
			rescue ::Exception
				raise DBImportError.new("The zip file provided is not a Metasploit ZIP report")
			end
		end

		if data and data.kind_of? PacketFu::PcapFile
			# Don't check for emptiness here because unlike other formats, we
			# haven't read any actual data in yet, only magic bytes to discover
			# that this is indeed a pcap file.
			#raise DBImportError.new("The pcap file provided is empty.") if data.body.empty?
			@import_filedata ||= {}
			@import_filedata[:type] = "Libpcap Packet Capture"
			return :libpcap
		end

		# This is a text string, lets make sure its treated as binary
		data = data.unpack("C*").pack("C*")
		if data and data.to_s.strip.length == 0
			raise DBImportError.new("The data provided to the import function was empty")
		end

		# Parse the first line or 4k of data from the file
		di = data.index("\n") || 4096

		firstline = data[0, di]
		@import_filedata ||= {}
		if (firstline.index("<NeXposeSimpleXML"))
			@import_filedata[:type] = "NeXpose Simple XML"
			return :nexpose_simplexml
		elsif (firstline.index("<FusionVM"))
			@import_filedata[:type] = "FusionVM XML"
			return :fusionvm_xml
		elsif (firstline.index("<NexposeReport"))
			@import_filedata[:type] = "NeXpose XML Report"
			return :nexpose_rawxml
		elsif (firstline.index("Name,Manufacturer,Device Type,Model,IP Address,Serial Number,Location,Operating System"))
			@import_filedata[:type] = "Spiceworks CSV Export"
			return :spiceworks_csv
		elsif (firstline.index("<scanJob>"))
			@import_filedata[:type] = "Retina XML"
			return :retina_xml
		elsif (firstline.index("<get_reports_response status=\"200\" status_text=\"OK\">"))
			@import_filedata[:type] = "OpenVAS XML"
			return :openvas_new_xml
		elsif (firstline.index("<report id=\""))
			@import_filedata[:type] = "OpenVAS XML"
			return :openvas_new_xml
		elsif (firstline.index("<NessusClientData>"))
			@import_filedata[:type] = "Nessus XML (v1)"
			return :nessus_xml
		elsif (firstline.index("<SecScan ID="))
			@import_filedata[:type] = "Microsoft Baseline Security Analyzer"
			return :mbsa_xml
		elsif (data[0,1024] =~ /<!ATTLIST\s+items\s+burpVersion/)
			@import_filedata[:type] = "Burp Session XML"
			return :burp_session_xml
		elsif (firstline.index("<?xml"))
			# it's xml, check for root tags we can handle
			line_count = 0
			data.each_line { |line|
				line =~ /<([a-zA-Z0-9\-\_]+)[ >]/

				case $1
				when "niktoscan"
					@import_filedata[:type] = "Nikto XML"
					return :nikto_xml
				when "nmaprun"
					@import_filedata[:type] = "Nmap XML"
					return :nmap_xml
				when "openvas-report"
					@import_filedata[:type] = "OpenVAS Report"
					return :openvas_xml
				when "NessusClientData"
					@import_filedata[:type] = "Nessus XML (v1)"
					return :nessus_xml
				when "NessusClientData_v2"
					@import_filedata[:type] = "Nessus XML (v2)"
					return :nessus_xml_v2
				when "SCAN"
					@import_filedata[:type] = "Qualys Scan XML"
					return :qualys_scan_xml
				when "report"
					@import_filedata[:type] = "Wapiti XML"
					return :wapiti_xml
				when "ASSET_DATA_REPORT"
					@import_filedata[:type] = "Qualys Asset XML"
					return :qualys_asset_xml
				when /MetasploitExpressV[1234]/
					@import_filedata[:type] = "Metasploit XML"
					return :msf_xml
				when /MetasploitV4/
					@import_filedata[:type] = "Metasploit XML"
					return :msf_xml
				when /netsparker/
					@import_filedata[:type] = "NetSparker XML"
					return :netsparker_xml
				when /audits?/ # <audit> and <audits> are both valid for nCircle. wtfmate.
					@import_filedata[:type] = "IP360 XML v3"
					return :ip360_xml_v3
				when /ontology/
					@import_filedata[:type] = "IP360 ASPL"
					return :ip360_aspl_xml
				when /ReportInfo/
					@import_filedata[:type] = "Foundstone"
					return :foundstone_xml
				when /ScanGroup/
					@import_filedata[:type] = "Acunetix"
					return :acunetix_xml
				when /AppScanInfo/ # Actually the second line
					@import_filedata[:type] = "Appscan"
					return :appscan_xml
				when "entities"
					if  line =~ /creator.*\x43\x4f\x52\x45\x20\x49\x4d\x50\x41\x43\x54/i
						@import_filedata[:type] = "CI"
						return :ci_xml
					end
				else
					# Give up if we haven't hit the root tag in the first few lines
					break if line_count > 10
				end
				line_count += 1
			}
		elsif (firstline.index("timestamps|||scan_start"))
			@import_filedata[:type] = "Nessus NBE Report"
			# then it's a nessus nbe
			return :nessus_nbe
		elsif (firstline.index("# amap v"))
			# then it's an amap mlog
			@import_filedata[:type] = "Amap Log -m"
			return :amap_mlog
		elsif (firstline.index("amap v"))
			# then it's an amap log
			@import_filedata[:type] = "Amap Log"
			return :amap_log
		elsif ipv46_validator(firstline)
			# then its an IP list
			@import_filedata[:type] = "IP Address List"
			return :ip_list
		elsif (data[0,1024].index("<netsparker"))
			@import_filedata[:type] = "NetSparker XML"
			return :netsparker_xml
		elsif (firstline.index("# Metasploit PWDump Export"))
			# then it's a Metasploit PWDump export
			@import_filedata[:type] = "msf_pwdump"
			return :msf_pwdump
		end

		raise DBImportError.new("Could not automatically determine file type")
	end

	# Boils down the validate_import_file to a boolean
	def validate_import_file(data)
		begin
			import_filetype_detect(data)
		rescue DBImportError
			return false
		end
		return true
	end

	#
	# Imports Nikto scan data from -Format xml as notes.
	#
	def import_nikto_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		doc = rexmlify(data)
		doc.elements.each do |f|
			f.elements.each('scandetails') do |host|
				# Get host information
				addr = host.attributes['targetip']
				next if not addr
				if bl.include? addr
					next
				else
					yield(:address,addr) if block
				end
				# Get service information
				port = host.attributes['targetport']
				next if port.to_i == 0
				uri = URI.parse(host.attributes['sitename']) rescue nil
				next unless uri and uri.scheme
				# Collect and report scan descriptions.
				host.elements.each do |item|
					if item.elements['description']
						desc_text = item.elements['description'].text
						next if desc_text.nil? or desc_text.empty?
						desc_data = {
							:workspace => wspace,
							:host => addr,
							:type => "service.nikto.scan.description",
							:data => desc_text,
							:proto => "tcp",
							:port => port.to_i,
							:sname => uri.scheme,
							:update => :unique_data
						}
						# Always report it as a note.
						report_note(desc_data)
						# Sometimes report it as a vuln, too.
						# XXX: There's a Vuln.info field but nothing reads from it? See Bug #5837
						if item.attributes['osvdbid'].to_i != 0
							desc_data[:refs] = ["OSVDB-#{item.attributes['osvdbid']}"]
							desc_data[:name] = "NIKTO-#{item.attributes['id']}"
							desc_data.delete(:data)
							desc_data.delete(:type)
							desc_data.delete(:update)
							report_vuln(desc_data)
						end
					end
				end
			end
		end
	end

	def import_wapiti_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
    		end
		import_wapiti_xml(args.merge(:data => data))
	end

	def import_wapiti_xml(args={}, &block)
		if block
			doc = Rex::Parser::WapitiDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::WapitiDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	def import_openvas_new_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_wapiti_xml(args.merge(:data => data))
	end

	def import_openvas_new_xml(args={}, &block)
		if block
			doc = Rex::Parser::OpenVASDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::OpenVASDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	def import_libpcap_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = PacketFu::PcapFile.new(:filename => filename)
		import_libpcap(args.merge(:data => data))
	end

	# The libpcap file format is handled by PacketFu for data
	# extraction. TODO: Make this its own mixin, and possibly
	# extend PacketFu to do better stream analysis on the fly.
	def import_libpcap(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		# seen_hosts is only used for determining when to yield an address. Once we get
		# some packet analysis going, the values will have all sorts of info. The plan
		# is to ru through all the packets as a first pass and report host and service,
		# then, once we have everything parsed, we can reconstruct sessions and ngrep
		# out things like authentication sequences, examine ttl's and window sizes, all
		# kinds of crazy awesome stuff like that.
		seen_hosts = {}
		decoded_packets = 0
		last_count = 0
		data.read_packet_bytes do |p|
			if (decoded_packets >= last_count + 1000) and block
				yield(:pcap_count, decoded_packets)
				last_count = decoded_packets
			end
			decoded_packets += 1

			pkt = PacketFu::Packet.parse(p) rescue next # Just silently skip bad packets

			next unless pkt.is_ip? # Skip anything that's not IP. Technically, not Ethernet::Ip
			next if pkt.is_tcp? && (pkt.tcp_src == 0 || pkt.tcp_dst == 0) # Skip port 0
			next if pkt.is_udp? && (pkt.udp_src == 0 || pkt.udp_dst == 0) # Skip port 0
			saddr = pkt.ip_saddr
			daddr = pkt.ip_daddr

			# Handle blacklists and obviously useless IP addresses, and report the host.
			next if (bl | [saddr,daddr]).size == bl.size # Both hosts are blacklisted, skip everything.
			unless( bl.include?(saddr) || rfc3330_reserved(saddr))
				yield(:address,saddr) if block and !seen_hosts.keys.include?(saddr)
				report_host(:workspace => wspace, :host => saddr, :state => Msf::HostState::Alive) unless seen_hosts[saddr]
				seen_hosts[saddr] ||= []

			end
			unless( bl.include?(daddr) || rfc3330_reserved(daddr))
				yield(:address,daddr) if block and !seen_hosts.keys.include?(daddr)
				report_host(:workspace => wspace, :host => daddr, :state => Msf::HostState::Alive) unless seen_hosts[daddr]
				seen_hosts[daddr] ||= []
			end

			if pkt.is_tcp? # First pass on TCP packets
				if (pkt.tcp_flags.syn == 1 and pkt.tcp_flags.ack == 1) or # Oh, this kills me
					pkt.tcp_src < 1024 # If it's a low port, assume it's a proper service.
					if seen_hosts[saddr]
						unless seen_hosts[saddr].include? [pkt.tcp_src,"tcp"]
							report_service(
								:workspace => wspace, :host => saddr,
								:proto => "tcp", :port => pkt.tcp_src,
								:state => Msf::ServiceState::Open
							)
							seen_hosts[saddr] << [pkt.tcp_src,"tcp"]
							yield(:service,"%s:%d/%s" % [saddr,pkt.tcp_src,"tcp"])
						end
					end
				end
			elsif pkt.is_udp? # First pass on UDP packets
				if pkt.udp_src == pkt.udp_dst # Very basic p2p detection.
					[saddr,daddr].each do |xaddr|
						if seen_hosts[xaddr]
							unless seen_hosts[xaddr].include? [pkt.udp_src,"udp"]
								report_service(
									:workspace => wspace, :host => xaddr,
									:proto => "udp", :port => pkt.udp_src,
									:state => Msf::ServiceState::Open
								)
								seen_hosts[xaddr] << [pkt.udp_src,"udp"]
								yield(:service,"%s:%d/%s" % [xaddr,pkt.udp_src,"udp"])
							end
						end
					end
				elsif pkt.udp_src < 1024 # Probably a service
					if seen_hosts[saddr]
						unless seen_hosts[saddr].include? [pkt.udp_src,"udp"]
							report_service(
								:workspace => wspace, :host => saddr,
								:proto => "udp", :port => pkt.udp_src,
								:state => Msf::ServiceState::Open
							)
							seen_hosts[saddr] << [pkt.udp_src,"udp"]
							yield(:service,"%s:%d/%s" % [saddr,pkt.udp_src,"udp"])
						end
					end
				end
			end # tcp or udp

			inspect_single_packet(pkt,wspace)

		end # data.body.map

		# Right about here, we should have built up some streams for some stream analysis.
		# Not sure what form that will take, but people like shoving many hundreds of
		# thousands of packets through this thing, so it'll need to be memory efficient.

	end

	# Do all the single packet analysis we can while churning through the pcap
	# the first time. Multiple packet inspection will come later, where we can
	# do stream analysis, compare requests and responses, etc.
	def inspect_single_packet(pkt,wspace)
		if pkt.is_tcp? or pkt.is_udp?
			inspect_single_packet_http(pkt,wspace)
		end
	end

	# Checks for packets that are headed towards port 80, are tcp, contain an HTTP/1.0
	# line, contains an Authorization line, contains a b64-encoded credential, and
	# extracts it. Reports this credential and solidifies the service as HTTP.
	def inspect_single_packet_http(pkt,wspace)
		# First, check the server side (data from port 80).
		if pkt.is_tcp? and pkt.tcp_src == 80 and !pkt.payload.nil? and !pkt.payload.empty?
			if pkt.payload =~ /^HTTP\x2f1\x2e[01]/
				http_server_match = pkt.payload.match(/\nServer:\s+([^\r\n]+)[\r\n]/)
				if http_server_match.kind_of?(MatchData) and http_server_match[1]
					report_service(
						:workspace => wspace,
						:host => pkt.ip_saddr,
						:port => pkt.tcp_src,
						:proto => "tcp",
						:name => "http",
						:info => http_server_match[1],
						:state => Msf::ServiceState::Open
					)
					# That's all we want to know from this service.
					return :something_significant
				end
			end
		end

		# Next, check the client side (data to port 80)
		if pkt.is_tcp? and pkt.tcp_dst == 80 and !pkt.payload.nil? and !pkt.payload.empty?
			if pkt.payload.match(/[\x00-\x20]HTTP\x2f1\x2e[10]/)
				auth_match = pkt.payload.match(/\nAuthorization:\s+Basic\s+([A-Za-z0-9=\x2b]+)/)
				if auth_match.kind_of?(MatchData) and auth_match[1]
					b64_cred = auth_match[1]
				else
					return false
				end
				# If we're this far, we can surmise that at least the client is a web browser,
				# he thinks the server is HTTP and he just made an authentication attempt. At
				# this point, we'll just believe everything the packet says -- validation ought
				# to come later.
				user,pass = b64_cred.unpack("m*").first.split(/:/,2)
				report_service(
					:workspace => wspace,
					:host => pkt.ip_daddr,
					:port => pkt.tcp_dst,
					:proto => "tcp",
					:name => "http"
				)
				report_auth_info(
					:workspace => wspace,
					:host => pkt.ip_daddr,
					:port => pkt.tcp_dst,
					:proto => "tcp",
					:type => "password",
					:active => true, # Once we can build a stream, determine if the auth was successful. For now, assume it is.
					:user => user,
					:pass => pass
				)
				# That's all we want to know from this service.
				return :something_significant
			end
		end
	end

	def import_spiceworks_csv(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		CSV.parse(data) do |row|
			next unless (["Name", "Manufacturer", "Device Type"] & row).empty? #header
			name = row[0]
			manufacturer = row[1]
			device = row[2]
			model = row[3]
			ip = row[4]
			serialno = row[5]
			location = row[6]
			os = row[7]

			next unless ip
			next if bl.include? ip

			conf = {
			:workspace => wspace,
			:host      => ip,
			:name      => name
			}

			conf[:os_name] = os if os

			info = []
			info << "Serial Number: #{serialno}" unless (serialno.blank? or serialno == name)
			info << "Location: #{location}" unless location.blank?
			conf[:info] = info.join(", ") unless info.empty?

			host = report_host(conf)
			report_import_note(wspace, host)
		end
	end

	#
	# Metasploit PWDump Export
	#
	# This file format is generated by the db_export -f pwdump and
	# the Metasploit Express and Pro report types of "PWDump."
	#
	# This particular block scheme is temporary, since someone is
	# bound to want to import gigantic lists, so we'll want a
	# stream parser eventually (just like the other non-nmap formats).
	#
	# The file format is:
	# # 1.2.3.4:23/tcp (telnet)
	# username password
	# user2 p\x01a\x02ss2
	# <BLANK> pass3
	# user3 <BLANK>
	# smbuser:sid:lmhash:nthash:::
	#
	# Note the leading hash for the host:port line. Note also all usernames
	# and passwords must be in 7-bit ASCII (character sequences of "\x01"
	# will be interpolated -- this includes spaces, which must be notated
	# as "\x20". Blank usernames or passwords should be <BLANK>.
	#
	def import_msf_pwdump(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		last_host = nil

		addr  = nil
		port  = nil
		proto = nil
		sname = nil
		ptype = nil
		active = false # Are there cases where imported creds are good? I just hate trusting the import right away.

		data.each_line do |line|
			case line
			when /^[\s]*#/ # Comment lines
				if line[/^#[\s]*([0-9.]+):([0-9]+)(\x2f(tcp|udp))?[\s]*(\x28([^\x29]*)\x29)?/]
					addr = $1
					port = $2
					proto = $4
					sname = $6
				end
			when /^[\s]*Warning:/
				next # Discard warning messages.
			when /^[\s]*([^\s:]+):[0-9]+:([A-Fa-f0-9]+:[A-Fa-f0-9]+):[^\s]*$/ # SMB Hash
				user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
				pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2
				ptype = "smb_hash"
			when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ # SMB Hash
				user = ([nil, "<BLANK>"].include?($1)) ? "" : $1
				pass = ""
				ptype = "smb_hash"
			when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/ # Must be a user pass
				user = ([nil, "<BLANK>"].include?($1)) ? "" : dehex($1)
				pass = ([nil, "<BLANK>"].include?($2)) ? "" : dehex($2)
				ptype = "password"
			else # Some unknown line not broken by a space.
				next
			end

			next unless [addr,port,user,pass].compact.size == 4
			next unless ipv46_validator(addr) # Skip Malformed addrs
			next unless port[/^[0-9]+$/] # Skip malformed ports
			if bl.include? addr
				next
			else
				yield(:address,addr) if block and addr != last_host
				last_host = addr
			end

			cred_info = {
				:host => addr,
				:port => port,
				:user => user,
				:pass => pass,
				:type => ptype,
				:workspace => wspace
			}
			cred_info[:proto] = proto if proto
			cred_info[:sname] = sname if sname
			cred_info[:active] = active

			report_auth_info(cred_info)
			user = pass = ptype = nil
		end

	end

	# If hex notation is present, turn them into a character.
	def dehex(str)
		hexen = str.scan(/\x5cx[0-9a-fA-F]{2}/)
		hexen.each { |h|
			str.gsub!(h,h[2,2].to_i(16).chr)
		}
		return str
	end


	#
	# Nexpose Simple XML
	#
	# XXX At some point we'll want to make this a stream parser for dealing
	# with large results files
	#
	def import_nexpose_simplexml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_nexpose_simplexml(args.merge(:data => data))
	end

	# Import a Metasploit XML file.
	def import_msf_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_msf_xml(args.merge(:data => data))
	end

	# Import a Metasploit Express ZIP file. Note that this requires
	# a fair bit of filesystem manipulation, and is very much tied
	# up with the Metasploit Express ZIP file format export (for
	# obvious reasons). In the event directories exist, they will
	# be reused. If target files exist, they will be overwritten.
	#
	# XXX: Refactor so it's not quite as sanity-blasting.
	def import_msf_zip(args={}, &block)
		data = args[:data]
		wpsace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename])
		if ::File.exists? new_tmp
			unless (::File.directory?(new_tmp) && ::File.writable?(new_tmp))
				raise DBImportError.new("Could not extract zip file to #{new_tmp}")
			end
		else
			FileUtils.mkdir_p(new_tmp)
		end
		@import_filedata[:zip_tmp] = new_tmp

		# Grab the list of unique basedirs over all entries.
		@import_filedata[:zip_tmp_subdirs] = @import_filedata[:zip_entry_names].map {|x| ::File.split(x)}.map {|x| x[0]}.uniq.reject {|x| x == "."}

		# mkdir all of the base directores we just pulled out, if they don't
		# already exist
		@import_filedata[:zip_tmp_subdirs].each {|sub|
			tmp_subdirs = ::File.join(@import_filedata[:zip_tmp],sub)
			if File.exists? tmp_subdirs
				unless (::File.directory?(tmp_subdirs) && File.writable?(tmp_subdirs))
					# if it exists but we can't write to it, give up
					raise DBImportError.new("Could not extract zip file to #{tmp_subdirs}")
				end
			else
				::FileUtils.mkdir(tmp_subdirs)
			end
		}


		data.entries.each do |e|
			target = ::File.join(@import_filedata[:zip_tmp],e.name)
			::File.unlink target if ::File.exists?(target) # Yep. Deleted.
			data.extract(e,target)
			if target =~ /^.*.xml$/
				target_data = ::File.open(target, "rb") {|f| f.read 1024}
				if import_filetype_detect(target_data) == :msf_xml
					@import_filedata[:zip_extracted_xml] = target
					#break
				end
			end
		end

		# This will kick the newly-extracted XML file through
		# the import_file process all over again.
		if @import_filedata[:zip_extracted_xml]
			new_args = args.dup
			new_args[:filename] = @import_filedata[:zip_extracted_xml]
			new_args[:data] = nil
			new_args[:ifd] = @import_filedata.dup
			if block
				import_file(new_args, &block)
			else
				import_file(new_args)
			end
		end

		# Kick down to all the MSFX ZIP specific items
		if block
			import_msf_collateral(new_args, &block)
		else
			import_msf_collateral(new_args)
		end
	end

	# Imports loot, tasks, and reports from an MSF ZIP report.
	# XXX: This function is stupidly long. It needs to be refactored.
	def import_msf_collateral(args={}, &block)
		data = ::File.open(args[:filename], "rb") {|f| f.read(f.stat.size)}
		wspace = args[:wspace] || args['wspace'] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		basedir = args[:basedir] || args['basedir'] || ::File.join(Msf::Config.install_root, "data", "msf")

		allow_yaml = false
		btag = nil

		doc = rexmlify(data)
		if doc.elements["MetasploitExpressV1"]
			m_ver = 1
			allow_yaml = true
			btag = "MetasploitExpressV1"
		elsif doc.elements["MetasploitExpressV2"]
			m_ver = 2
			allow_yaml = true
			btag = "MetasploitExpressV2"
		elsif doc.elements["MetasploitExpressV3"]
			m_ver = 3
			btag = "MetasploitExpressV3"
		elsif doc.elements["MetasploitExpressV4"]
			m_ver = 4
			btag = "MetasploitExpressV4"
		elsif doc.elements["MetasploitV4"]
			m_ver = 4
			btag = "MetasploitV4"
		else
			m_ver = nil
		end
		unless m_ver and btag
			raise DBImportError.new("Unsupported Metasploit XML document format")
		end

		host_info = {}
		doc.elements.each("/#{btag}/hosts/host") do |host|
			host_info[host.elements["id"].text.to_s.strip] = nils_for_nulls(host.elements["address"].text.to_s.strip)
		end

		# Import Loot
		doc.elements.each("/#{btag}/loots/loot") do |loot|
			next if bl.include? host_info[loot.elements["host-id"].text.to_s.strip]
			loot_info = {}
			loot_info[:host] = host_info[loot.elements["host-id"].text.to_s.strip]
			loot_info[:workspace] = args[:wspace]
			loot_info[:ctype] = nils_for_nulls(loot.elements["content-type"].text.to_s.strip)
			loot_info[:info] = nils_for_nulls(unserialize_object(loot.elements["info"], allow_yaml))
			loot_info[:ltype] = nils_for_nulls(loot.elements["ltype"].text.to_s.strip)
			loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip)
			loot_info[:created_at] = nils_for_nulls(loot.elements["created-at"].text.to_s.strip)
			loot_info[:updated_at] = nils_for_nulls(loot.elements["updated-at"].text.to_s.strip)
			loot_info[:name] = nils_for_nulls(loot.elements["name"].text.to_s.strip)
			loot_info[:orig_path] = nils_for_nulls(loot.elements["path"].text.to_s.strip)
			tmp = args[:ifd][:zip_tmp]
			loot_info[:orig_path].gsub!(/^\./,tmp) if loot_info[:orig_path]
			if !loot.elements["service-id"].text.to_s.strip.empty?
				unless loot.elements["service-id"].text.to_s.strip == "NULL"
					loot_info[:service] = loot.elements["service-id"].text.to_s.strip
				end
			end

			# Only report loot if we actually have it.
			# TODO: Copypasta. Seperate this out.
			if ::File.exists? loot_info[:orig_path]
				loot_dir = ::File.join(basedir,"loot")
				loot_file = ::File.split(loot_info[:orig_path]).last
				if ::File.exists? loot_dir
					unless (::File.directory?(loot_dir) && ::File.writable?(loot_dir))
						raise DBImportError.new("Could not move files to #{loot_dir}")
					end
				else
					::FileUtils.mkdir_p(loot_dir)
				end
				new_loot = ::File.join(loot_dir,loot_file)
				loot_info[:path] = new_loot
				if ::File.exists?(new_loot)
					::File.unlink new_loot # Delete it, and don't report it.
				else
					report_loot(loot_info) # It's new, so report it.
				end
				::FileUtils.copy(loot_info[:orig_path], new_loot)
				yield(:msf_loot, new_loot) if block
			end
		end

		# Import Tasks
		doc.elements.each("/#{btag}/tasks/task") do |task|
			task_info = {}
			task_info[:workspace] = args[:wspace]
			# Should user be imported (original) or declared (the importing user)?
			task_info[:user] = nils_for_nulls(task.elements["created-by"].text.to_s.strip)
			task_info[:desc] = nils_for_nulls(task.elements["description"].text.to_s.strip)
			task_info[:info] = nils_for_nulls(unserialize_object(task.elements["info"], allow_yaml))
			task_info[:mod] = nils_for_nulls(task.elements["module"].text.to_s.strip)
			task_info[:options] = nils_for_nulls(task.elements["options"].text.to_s.strip)
			task_info[:prog] = nils_for_nulls(task.elements["progress"].text.to_s.strip).to_i
			task_info[:created_at] = nils_for_nulls(task.elements["created-at"].text.to_s.strip)
			task_info[:updated_at] = nils_for_nulls(task.elements["updated-at"].text.to_s.strip)
			if !task.elements["completed-at"].text.to_s.empty?
				task_info[:completed_at] = nils_for_nulls(task.elements["completed-at"].text.to_s.strip)
			end
			if !task.elements["error"].text.to_s.empty?
				task_info[:error] = nils_for_nulls(task.elements["error"].text.to_s.strip)
			end
			if !task.elements["result"].text.to_s.empty?
				task_info[:result] = nils_for_nulls(task.elements["result"].text.to_s.strip)
			end
			task_info[:orig_path] = nils_for_nulls(task.elements["path"].text.to_s.strip)
			tmp = args[:ifd][:zip_tmp]
			task_info[:orig_path].gsub!(/^\./,tmp) if task_info[:orig_path]

			# Only report a task if we actually have it.
			# TODO: Copypasta. Seperate this out.
			if ::File.exists? task_info[:orig_path]
				tasks_dir = ::File.join(basedir,"tasks")
				task_file = ::File.split(task_info[:orig_path]).last
				if ::File.exists? tasks_dir
					unless (::File.directory?(tasks_dir) && ::File.writable?(tasks_dir))
						raise DBImportError.new("Could not move files to #{tasks_dir}")
					end
				else
					::FileUtils.mkdir_p(tasks_dir)
				end
				new_task = ::File.join(tasks_dir,task_file)
				task_info[:path] = new_task
				if ::File.exists?(new_task)
					::File.unlink new_task # Delete it, and don't report it.
				else
					report_task(task_info) # It's new, so report it.
				end
				::FileUtils.copy(task_info[:orig_path], new_task)
				yield(:msf_task, new_task) if block
			end
		end

		# Import Reports
		doc.elements.each("/#{btag}/reports/report") do |report|
			report_info = {}
			report_info[:workspace] = args[:wspace]
			# Should user be imported (original) or declared (the importing user)?
			report_info[:user] = nils_for_nulls(report.elements["created-by"].text.to_s.strip)
			report_info[:options] = nils_for_nulls(report.elements["options"].text.to_s.strip)
			report_info[:rtype] = nils_for_nulls(report.elements["rtype"].text.to_s.strip)
			report_info[:created_at] = nils_for_nulls(report.elements["created-at"].text.to_s.strip)
			report_info[:updated_at] = nils_for_nulls(report.elements["updated-at"].text.to_s.strip)

			report_info[:orig_path] = nils_for_nulls(report.elements["path"].text.to_s.strip)
			tmp = args[:ifd][:zip_tmp]
			report_info[:orig_path].gsub!(/^\./,tmp) if report_info[:orig_path]

			# Only report a report if we actually have it.
			# TODO: Copypasta. Seperate this out.
			if ::File.exists? report_info[:orig_path]
				reports_dir = ::File.join(basedir,"reports")
				report_file = ::File.split(report_info[:orig_path]).last
				if ::File.exists? reports_dir
					unless (::File.directory?(reports_dir) && ::File.writable?(reports_dir))
						raise DBImportError.new("Could not move files to #{reports_dir}")
					end
				else
					::FileUtils.mkdir_p(reports_dir)
				end
				new_report = ::File.join(reports_dir,report_file)
				report_info[:path] = new_report
				if ::File.exists?(new_report)
					::File.unlink new_report
				else
					report_report(report_info)
				end
				::FileUtils.copy(report_info[:orig_path], new_report)
				yield(:msf_report, new_report) if block
			end
		end

	end

	# For each host, step through services, notes, and vulns, and import
	# them.
	# TODO: loot, tasks, and reports
	def import_msf_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		allow_yaml = false
		btag       = nil

		doc = rexmlify(data)
		if doc.elements["MetasploitExpressV1"]
			m_ver = 1
			allow_yaml = true
			btag = "MetasploitExpressV1"
		elsif doc.elements["MetasploitExpressV2"]
			m_ver = 2
			allow_yaml = true
			btag = "MetasploitExpressV2"
		elsif doc.elements["MetasploitExpressV3"]
			m_ver = 3
			btag = "MetasploitExpressV3"
		elsif doc.elements["MetasploitExpressV4"]
			m_ver = 4
			btag = "MetasploitExpressV4"
		elsif doc.elements["MetasploitV4"]
			m_ver = 4
			btag = "MetasploitV4"
		else
			m_ver = nil
		end
		unless m_ver and btag
			raise DBImportError.new("Unsupported Metasploit XML document format")
		end

		doc.elements.each("/#{btag}/hosts/host") do |host|
			host_data = {}
			host_data[:workspace] = wspace
			host_data[:host] = nils_for_nulls(host.elements["address"].text.to_s.strip)
			if bl.include? host_data[:host]
				next
			else
				yield(:address,host_data[:host]) if block
			end
			host_data[:mac] = nils_for_nulls(host.elements["mac"].text.to_s.strip)
			if host.elements["comm"].text
				host_data[:comm] = nils_for_nulls(host.elements["comm"].text.to_s.strip)
			end
			%W{created-at updated-at name state os-flavor os-lang os-name os-sp purpose}.each { |datum|
				if host.elements[datum].text
					host_data[datum.gsub('-','_')] = nils_for_nulls(host.elements[datum].text.to_s.strip)
				end
			}
			host_address = host_data[:host].dup # Preserve after report_host() deletes
			hobj = report_host(host_data)

			host.elements.each("host_details/host_detail") do |hdet|
				hdet_data = {}
				hdet.elements.each do |det|
					next if ["id", "host-id"].include?(det.name)
					if det.text
						hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
					end
				end
				report_host_details(hobj, hdet_data)
			end

			host.elements.each("exploit_attempts/exploit_attempt") do |hdet|
				hdet_data = {}
				hdet.elements.each do |det|
					next if ["id", "host-id", "session-id", "vuln-id", "service-id", "loot-id"].include?(det.name)
					if det.text
						hdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
					end
				end
				report_exploit_attempt(hobj, hdet_data)
			end

			host.elements.each('services/service') do |service|
				service_data = {}
				service_data[:workspace] = wspace
				service_data[:host] = hobj
				service_data[:port] = nils_for_nulls(service.elements["port"].text.to_s.strip).to_i
				service_data[:proto] = nils_for_nulls(service.elements["proto"].text.to_s.strip)
				%W{created-at updated-at name state info}.each { |datum|
					if service.elements[datum].text
						if datum == "info"
							service_data["info"] = nils_for_nulls(unserialize_object(service.elements[datum], false))
						else
							service_data[datum.gsub("-","_")] = nils_for_nulls(service.elements[datum].text.to_s.strip)
						end
					end
				}
				report_service(service_data)
			end

			host.elements.each('notes/note') do |note|
				note_data = {}
				note_data[:workspace] = wspace
				note_data[:host] = hobj
				note_data[:type] = nils_for_nulls(note.elements["ntype"].text.to_s.strip)
				note_data[:data] = nils_for_nulls(unserialize_object(note.elements["data"], allow_yaml))

				if note.elements["critical"].text
					note_data[:critical] = true unless note.elements["critical"].text.to_s.strip == "NULL"
				end
				if note.elements["seen"].text
					note_data[:seen] = true unless note.elements["critical"].text.to_s.strip == "NULL"
				end
				%W{created-at updated-at}.each { |datum|
					if note.elements[datum].text
						note_data[datum.gsub("-","_")] = nils_for_nulls(note.elements[datum].text.to_s.strip)
					end
				}
				report_note(note_data)
			end

			host.elements.each('tags/tag') do |tag|
				tag_data = {}
				tag_data[:addr] = host_address
				tag_data[:wspace] = wspace
				tag_data[:name] = tag.elements["name"].text.to_s.strip
				tag_data[:desc] = tag.elements["desc"].text.to_s.strip
				if tag.elements["report-summary"].text
					tag_data[:summary] = tag.elements["report-summary"].text.to_s.strip
				end
				if tag.elements["report-detail"].text
					tag_data[:detail] = tag.elements["report-detail"].text.to_s.strip
				end
				if tag.elements["critical"].text
					tag_data[:crit] = true unless tag.elements["critical"].text.to_s.strip == "NULL"
				end
				report_host_tag(tag_data)
			end

			host.elements.each('vulns/vuln') do |vuln|
				vuln_data = {}
				vuln_data[:workspace] = wspace
				vuln_data[:host] = hobj
				vuln_data[:info] = nils_for_nulls(unserialize_object(vuln.elements["info"], allow_yaml))
				vuln_data[:name] = nils_for_nulls(vuln.elements["name"].text.to_s.strip)
				%W{created-at updated-at exploited-at}.each { |datum|
					if vuln.elements[datum] and vuln.elements[datum].text
						vuln_data[datum.gsub("-","_")] = nils_for_nulls(vuln.elements[datum].text.to_s.strip)
					end
				}
				if vuln.elements["refs"]
					vuln_data[:refs] = []
					vuln.elements.each("refs/ref") do |ref|
						vuln_data[:refs] << nils_for_nulls(ref.text.to_s.strip)
					end
				end

				vobj = report_vuln(vuln_data)

				vuln.elements.each("vuln_details/vuln_detail") do |vdet|
					vdet_data = {}
					vdet.elements.each do |det|
						next if ["id", "vuln-id"].include?(det.name)
						if det.text
							vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
						end
					end
					report_vuln_details(vobj, vdet_data)
				end

				vuln.elements.each("vuln_attempts/vuln_attempt") do |vdet|
					vdet_data = {}
					vdet.elements.each do |det|
						next if ["id", "vuln-id", "loot-id", "session-id"].include?(det.name)
						if det.text
							vdet_data[det.name.gsub('-','_')] = nils_for_nulls(det.text.to_s.strip)
						end
					end
					report_vuln_attempt(vobj, vdet_data)
				end
			end

			host.elements.each('creds/cred') do |cred|
				cred_data = {}
				cred_data[:workspace] = wspace
				cred_data[:host] = hobj
				%W{port ptype sname proto proof active user pass}.each {|datum|
					if cred.elements[datum].respond_to? :text
						cred_data[datum.intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
					end
				}
				%W{created-at updated-at}.each { |datum|
					if cred.elements[datum].respond_to? :text
						cred_data[datum.gsub("-","_")] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
					end
				}
				%W{source-type source-id}.each { |datum|
					if cred.elements[datum].respond_to? :text
						cred_data[datum.gsub("-","_").intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip)
					end
				}
				if cred_data[:pass] == "<masked>"
					cred_data[:pass] = ""
					cred_data[:active] = false
				elsif cred_data[:pass] == "*BLANK PASSWORD*"
					cred_data[:pass] = ""
				end
				report_cred(cred_data)
			end

			host.elements.each('sessions/session') do |sess|
				sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i)
				sess_data = {}
				sess_data[:host] = hobj
				%W{desc platform port stype}.each {|datum|
					if sess.elements[datum].respond_to? :text
						sess_data[datum.intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
					end
				}
				%W{opened-at close-reason closed-at via-exploit via-payload}.each {|datum|
					if sess.elements[datum].respond_to? :text
						sess_data[datum.gsub("-","_").intern] = nils_for_nulls(sess.elements[datum].text.to_s.strip)
					end
				}
				sess_data[:datastore] = nils_for_nulls(unserialize_object(sess.elements["datastore"], allow_yaml))
				if sess.elements["routes"]
					sess_data[:routes] = nils_for_nulls(unserialize_object(sess.elements["routes"], allow_yaml)) || []
				end
				if not sess_data[:closed_at] # Fake a close if we don't already have one
					sess_data[:closed_at] = Time.now.utc
					sess_data[:close_reason] = "Imported at #{Time.now.utc}"
				end

				existing_session = get_session(
					:workspace => sess_data[:host].workspace,
					:addr => sess_data[:host].address,
					:time => sess_data[:opened_at]
				)
				this_session = existing_session || report_session(sess_data)
				next if existing_session
				sess.elements.each('events/event') do |sess_event|
					sess_event_data = {}
					sess_event_data[:session] = this_session
					%W{created-at etype local-path remote-path}.each {|datum|
						if sess_event.elements[datum].respond_to? :text
							sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(sess_event.elements[datum].text.to_s.strip)
						end
					}
					%W{command output}.each {|datum|
						if sess_event.elements[datum].respond_to? :text
							sess_event_data[datum.gsub("-","_").intern] = nils_for_nulls(unserialize_object(sess_event.elements[datum], allow_yaml))
						end
					}
					report_session_event(sess_event_data)
				end
			end
		end

		# Import web sites
		doc.elements.each("/#{btag}/web_sites/web_site") do |web|
			info = {}
			info[:workspace] = wspace

			%W{host port vhost ssl comments}.each do |datum|
				if web.elements[datum].respond_to? :text
					info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
				end
			end

			info[:options]   = nils_for_nulls(unserialize_object(web.elements["options"], allow_yaml)) if web.elements["options"].respond_to?(:text)
			info[:ssl]       = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false

			%W{created-at updated-at}.each { |datum|
				if web.elements[datum].text
					info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip)
				end
			}

			report_web_site(info)
			yield(:web_site, "#{info[:host]}:#{info[:port]} (#{info[:vhost]})") if block
		end

		%W{page form vuln}.each do |wtype|
			doc.elements.each("/#{btag}/web_#{wtype}s/web_#{wtype}") do |web|
				info = {}
				info[:workspace] = wspace
				info[:host]      = nils_for_nulls(web.elements["host"].text.to_s.strip)  if web.elements["host"].respond_to?(:text)
				info[:port]      = nils_for_nulls(web.elements["port"].text.to_s.strip)  if web.elements["port"].respond_to?(:text)
				info[:ssl]       = nils_for_nulls(web.elements["ssl"].text.to_s.strip)   if web.elements["ssl"].respond_to?(:text)
				info[:vhost]     = nils_for_nulls(web.elements["vhost"].text.to_s.strip) if web.elements["vhost"].respond_to?(:text)

				info[:ssl] = (info[:ssl] and info[:ssl].to_s.strip.downcase == "true") ? true : false

				case wtype
				when "page"
					%W{path code body query cookie auth ctype mtime location}.each do |datum|
						if web.elements[datum].respond_to? :text
							info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
						end
					end
					info[:headers] = nils_for_nulls(unserialize_object(web.elements["headers"], allow_yaml))
				when "form"
					%W{path query method}.each do |datum|
						if web.elements[datum].respond_to? :text
							info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
						end
					end
					info[:params] = nils_for_nulls(unserialize_object(web.elements["params"], allow_yaml))
				when "vuln"
					%W{path query method pname proof risk name blame description category confidence}.each do |datum|
						if web.elements[datum].respond_to? :text
							info[datum.intern] = nils_for_nulls(web.elements[datum].text.to_s.strip)
						end
					end
					info[:params] = nils_for_nulls(unserialize_object(web.elements["params"], allow_yaml))
					info[:risk]   = info[:risk].to_i
					info[:confidence] = info[:confidence].to_i
				end

				%W{created-at updated-at}.each { |datum|
					if web.elements[datum].text
						info[datum.gsub("-","_")] = nils_for_nulls(web.elements[datum].text.to_s.strip)
					end
				}
				self.send("report_web_#{wtype}", info)

				yield("web_#{wtype}".intern, info[:path]) if block
			end
		end
	end

	# Convert the string "NULL" to actual nil
	def nils_for_nulls(str)
		str == "NULL" ? nil : str
	end

	def import_nexpose_simplexml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_nexpose_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_nexpose_noko_stream(noko_args)
			end
			return true
		end
		data = args[:data]

		doc = rexmlify(data)
		doc.elements.each('/NeXposeSimpleXML/devices/device') do |dev|
			addr = dev.attributes['address'].to_s
			if bl.include? addr
				next
			else
				yield(:address,addr) if block
			end

			fprint = {}

			dev.elements.each('fingerprint/description') do |str|
				fprint[:desc] = str.text.to_s.strip
			end
			dev.elements.each('fingerprint/vendor') do |str|
				fprint[:vendor] = str.text.to_s.strip
			end
			dev.elements.each('fingerprint/family') do |str|
				fprint[:family] = str.text.to_s.strip
			end
			dev.elements.each('fingerprint/product') do |str|
				fprint[:product] = str.text.to_s.strip
			end
			dev.elements.each('fingerprint/version') do |str|
				fprint[:version] = str.text.to_s.strip
			end
			dev.elements.each('fingerprint/architecture') do |str|
				fprint[:arch] = str.text.to_s.upcase.strip
			end

			conf = {
				:workspace => wspace,
				:host      => addr,
				:state     => Msf::HostState::Alive
			}

			host = report_host(conf)
			report_import_note(wspace, host)

			report_note(
				:workspace => wspace,
				:host      => host,
				:type      => 'host.os.nexpose_fingerprint',
				:data      => fprint
			)

			# Load vulnerabilities not associated with a service
			dev.elements.each('vulnerabilities/vulnerability') do |vuln|
				vid  = vuln.attributes['id'].to_s.downcase
				refs = process_nexpose_data_sxml_refs(vuln)
				next if not refs
				report_vuln(
					:workspace => wspace,
					:host      => host,
					:name      => 'NEXPOSE-' + vid,
					:info      => vid,
					:refs      => refs
				)
			end

			# Load the services
			dev.elements.each('services/service') do |svc|
				sname = svc.attributes['name'].to_s
				sprot = svc.attributes['protocol'].to_s.downcase
				sport = svc.attributes['port'].to_s.to_i
				next if sport == 0

				name = sname.split('(')[0].strip
				info = ''

				svc.elements.each('fingerprint/description') do |str|
					info = str.text.to_s.strip
				end

				if(sname.downcase != '<unknown>')
					report_service(:workspace => wspace, :host => host, :proto => sprot, :port => sport, :name => name, :info => info)
				else
					report_service(:workspace => wspace, :host => host, :proto => sprot, :port => sport, :info => info)
				end

				# Load vulnerabilities associated with this service
				svc.elements.each('vulnerabilities/vulnerability') do |vuln|
					vid  = vuln.attributes['id'].to_s.downcase
					refs = process_nexpose_data_sxml_refs(vuln)
					next if not refs
					report_vuln(
						:workspace => wspace,
						:host => host,
						:port => sport,
						:proto => sprot,
						:name => 'NEXPOSE-' + vid,
						:info => vid,
						:refs => refs
					)
				end
			end
		end
	end


	#
	# Nexpose Raw XML
	#
	def import_nexpose_rawxml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_nexpose_rawxml(args.merge(:data => data))
	end

	def import_nexpose_rawxml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_nexpose_raw_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_nexpose_raw_noko_stream(noko_args)
			end
			return true
		end
		data = args[:data]

		# Use a stream parser instead of a tree parser so we can deal with
		# huge results files without running out of memory.
		parser = Rex::Parser::NexposeXMLStreamParser.new

		# Since all the Refs have to be in the database before we can use them
		# in a Vuln, we store all the hosts until we finish parsing and only
		# then put everything in the database.  This is memory-intensive for
		# large files, but should be much less so than a tree parser.
		#
		# This method is also considerably faster than parsing through the tree
		# looking for references every time we hit a vuln.
		hosts = []
		vulns = []

		# The callback merely populates our in-memory table of hosts and vulns
		parser.callback = Proc.new { |type, value|
			case type
			when :host
				# XXX: Blacklist should be checked here instead of saving a
				# host we're just going to throw away later
				hosts.push(value)
			when :vuln
				value["id"] = value["id"].downcase if value["id"]
				vulns.push(value)
			end
		}

		REXML::Document.parse_stream(data, parser)

		vuln_refs = nexpose_refs_to_struct(vulns)
		hosts.each do |host|
			if bl.include? host["addr"]
				next
			else
				yield(:address,host["addr"]) if block
			end
			nexpose_host_from_rawxml(host, vuln_refs, wspace)
		end
	end

	#
	# Takes an array of vuln hashes, as returned by the NeXpose rawxml stream
	# parser, like:
	#   [
	#		{"id"=>"winreg-notes-protocol-handler", severity="8", "refs"=>[{"source"=>"BID", "value"=>"10600"}, ...]}
	#		{"id"=>"windows-zotob-c", severity="8", "refs"=>[{"source"=>"BID", "value"=>"14513"}, ...]}
	#	]
	# and transforms it into a struct, containing :id, :refs, :title, and :severity
	#
	# Other attributes can be added later, as needed.
	def nexpose_refs_to_struct(vulns)
		ret = []
		vulns.each do |vuln|
			next if ret.map {|v| v.id}.include? vuln["id"]
			vstruct = Struct.new(:id, :refs, :title, :severity).new
			vstruct.id = vuln["id"]
			vstruct.title = vuln["title"]
			vstruct.severity = vuln["severity"]
			vstruct.refs = []
			vuln["refs"].each do |ref|
				if ref['source'] == 'BID'
					vstruct.refs.push('BID-' + ref["value"])
				elsif ref['source'] == 'CVE'
					# value is CVE-$ID
					vstruct.refs.push(ref["value"])
				elsif ref['source'] == 'MS'
					vstruct.refs.push('MSB-' + ref["value"])
				elsif ref['source'] == 'URL'
					vstruct.refs.push('URL-' + ref["value"])
				end
			end
			ret.push vstruct
		end
		return ret
	end

	# Takes a Host object, an array of vuln structs (generated by nexpose_refs_to_struct()),
	# and a workspace, and reports the vulns on that host.
	def nexpose_host_from_rawxml(h, vstructs, wspace)
		hobj = nil
		data = {:workspace => wspace}
		if h["addr"]
			addr = h["addr"]
		else
			# Can't report it if it doesn't have an IP
			return
		end
		data[:host] = addr
		if (h["hardware-address"])
			# Put colons between each octet of the MAC address
			data[:mac] = h["hardware-address"].gsub(':', '').scan(/../).join(':')
		end
		data[:state] = (h["status"] == "alive") ? Msf::HostState::Alive : Msf::HostState::Dead

		# Since we only have one name field per host in the database, just
		# take the first one.
		if (h["names"] and h["names"].first)
			data[:name] = h["names"].first
		end

		if (data[:state] != Msf::HostState::Dead)
			hobj = report_host(data)
			report_import_note(wspace, hobj)
		end

		if h["notes"]
			note = {
				:workspace => wspace,
				:host => (hobj || addr),
				:type => "host.vuln.nexpose_keys",
				:data => {},
				:mode => :unique_data
			}
			h["notes"].each do |v,k|
				note[:data][v] ||= []
				next if note[:data][v].include? k
				note[:data][v] << k
			end
			report_note(note)
		end

		if h["os_family"]
			note = {
				:workspace => wspace,
				:host => hobj || addr,
				:type => 'host.os.nexpose_fingerprint',
				:data => {
					:family    => h["os_family"],
					:certainty => h["os_certainty"]
				}
			}
			note[:data][:vendor]  = h["os_vendor"]  if h["os_vendor"]
			note[:data][:product] = h["os_product"] if h["os_product"]
			note[:data][:version] = h["os_version"] if h["os_version"]
			note[:data][:arch]    = h["arch"]       if h["arch"]

			report_note(note)
		end

		h["endpoints"].each { |p|
			extra = ""
			extra << p["product"] + " " if p["product"]
			extra << p["version"] + " " if p["version"]

			# Skip port-0 endpoints
			next if p["port"].to_i == 0

			# XXX This should probably be handled in a more standard way
			# extra << "(" + p["certainty"] + " certainty) " if p["certainty"]

			data = {}
			data[:workspace] = wspace
			data[:proto] = p["protocol"].downcase
			data[:port]  = p["port"].to_i
			data[:state] = p["status"]
			data[:host]  = hobj || addr
			data[:info]  = extra if not extra.empty?
			if p["name"] != "<unknown>"
				data[:name] = p["name"]
			end
			report_service(data)
		}

		h["vulns"].each_pair { |k,v|

			next if v["status"] !~ /^vulnerable/
			vstruct = vstructs.select {|vs| vs.id.to_s.downcase == v["id"].to_s.downcase}.first
			next unless vstruct
			data = {}
			data[:workspace] = wspace
			data[:host] = hobj || addr
			data[:proto] = v["protocol"].downcase if v["protocol"]
			data[:port] = v["port"].to_i if v["port"]
			data[:name] = "NEXPOSE-" + v["id"]
			data[:info] = vstruct.title
			data[:refs] = vstruct.refs
			report_vuln(data)
		}
	end


	#
	# Retina XML
	#

	# Process a Retina XML file
	def import_retina_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_retina_xml(args.merge(:data => data))
	end

	# Process Retina XML
	def import_retina_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		msg =  "Warning: The Retina XML format does not associate vulnerabilities with the\n"
		msg << "specific service on which they were found.\n"
		msg << "This makes it impossible to correlate exploits to discovered vulnerabilities\n"
		msg << "in a reliable fashion."

		yield(:warning,msg) if block

		parser = Rex::Parser::RetinaXMLStreamParser.new
		parser.on_found_host = Proc.new do |host|
			hobj = nil
			data = {:workspace => wspace}
			addr = host['address']
			next if not addr

			next if bl.include? addr
			data[:host] = addr

			if host['mac']
				data[:mac] = host['mac']
			end

			data[:state] = Msf::HostState::Alive

			if host['hostname']
				data[:name] = host['hostname']
			end

			if host['netbios']
				data[:name] = host['netbios']
			end

			yield(:address, data[:host]) if block

			# Import Host
			hobj = report_host(data)
			report_import_note(wspace, hobj)

			# Import OS fingerprint
			if host["os"]
				note = {
					:workspace => wspace,
					:host => addr,
					:type => 'host.os.retina_fingerprint',
					:data => {
						:os => host["os"]
					}
				}
				report_note(note)
			end

			# Import vulnerabilities
			host['vulns'].each do |vuln|
				refs = vuln['refs'].map{|v| v.join("-")}
				refs << "RETINA-#{vuln['rthid']}" if vuln['rthid']

				vuln_info = {
					:workspace => wspace,
					:host => addr,
					:name => vuln['name'],
					:info => vuln['description'],
					:refs => refs
				}

				report_vuln(vuln_info)
			end
		end

		REXML::Document.parse_stream(data, parser)
	end

	#
	# NetSparker XML
	#

	# Process a NetSparker XML file
	def import_netsparker_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_netsparker_xml(args.merge(:data => data))
	end

	# Process NetSparker XML
	def import_netsparker_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		addr = nil
		parser = Rex::Parser::NetSparkerXMLStreamParser.new
		parser.on_found_vuln = Proc.new do |vuln|
			data = {:workspace => wspace}

			# Parse the URL
			url  = vuln['url']
			return if not url

			# Crack the URL into a URI
			uri = URI(url) rescue nil
			return if not uri

			# Resolve the host and cache the IP
			if not addr
				baddr = Rex::Socket.addr_aton(uri.host) rescue nil
				if baddr
					addr = Rex::Socket.addr_ntoa(baddr)
					yield(:address, addr) if block
				end
			end

			# Bail early if we have no IP address
			if not addr
				raise Interrupt, "Not a valid IP address"
			end

			if bl.include?(addr)
				raise Interrupt, "IP address is on the blacklist"
			end

			data[:host]  = addr
			data[:vhost] = uri.host
			data[:port]  = uri.port
			data[:ssl]   = (uri.scheme == "ssl")

			body = nil
			# First report a web page
			if vuln['response']
				headers = {}
				code    = 200
				head,body = vuln['response'].to_s.split(/\r?\n\r?\n/, 2)
				if body

					if head =~ /^HTTP\d+\.\d+\s+(\d+)\s*/
						code = $1.to_i
					end

					headers = {}
					head.split(/\r?\n/).each do |line|
						hname,hval = line.strip.split(/\s*:\s*/, 2)
						next if hval.to_s.strip.empty?
						headers[hname.downcase] ||= []
						headers[hname.downcase] << hval
					end

					info = {
						:path     => uri.path,
						:query    => uri.query,
						:code     => code,
						:body     => body,
						:headers  => headers
					}
					info.merge!(data)

					if headers['content-type']
						info[:ctype] = headers['content-type'][0]
					end

					if headers['set-cookie']
						info[:cookie] = headers['set-cookie'].join("\n")
					end

					if headers['authorization']
						info[:auth] = headers['authorization'].join("\n")
					end

					if headers['location']
						info[:location] = headers['location'][0]
					end

					if headers['last-modified']
						info[:mtime] = headers['last-modified'][0]
					end

					# Report the web page to the database
					report_web_page(info)

					yield(:web_page, url) if block
				end
			end # End web_page reporting


			details = netsparker_vulnerability_map(vuln)

			method = netsparker_method_map(vuln)
			pname  = netsparker_pname_map(vuln)
			params = netsparker_params_map(vuln)

			proof  = ''

			if vuln['info'] and vuln['info'].length > 0
				proof << vuln['info'].map{|x| "#{x[0]}: #{x[1]}\n" }.join + "\n"
			end

			if proof.empty?
				if body
					proof << body + "\n"
				else
					proof << vuln['response'].to_s + "\n"
				end
			end

			if params.empty? and pname
				params = [[pname, vuln['vparam_name'].to_s]]
			end

			info = {
				:path     => uri.path,
				:query    => uri.query,
				:method   => method,
				:params   => params,
				:pname    => pname.to_s,
				:proof    => proof,
				:risk     => details[:risk],
				:name     => details[:name],
				:blame    => details[:blame],
				:category => details[:category],
				:description => details[:description],
				:confidence  => details[:confidence],
			}
			info.merge!(data)

			next if vuln['type'].to_s.empty?

			report_web_vuln(info)
			yield(:web_vuln, url) if block
		end

		# We throw interrupts in our parser when the job is hopeless
		begin
			REXML::Document.parse_stream(data, parser)
		rescue ::Interrupt => e
			wlog("The netsparker_xml_import() job was interrupted: #{e}")
		end
	end

	def netsparker_method_map(vuln)
		case vuln['vparam_type']
		when "FullQueryString"
			"GET"
		when "Querystring"
			"GET"
		when "Post"
			"POST"
		when "RawUrlInjection"
			"GET"
		else
			"GET"
		end
	end

	def netsparker_pname_map(vuln)
		case vuln['vparam_name']
		when "URI-BASED", "Query Based"
			"PATH"
		else
			vuln['vparam_name']
		end
	end

	def netsparker_params_map(vuln)
		[]
	end

	def netsparker_vulnerability_map(vuln)
		res = {
			:risk => 1,
			:name  => 'Information Disclosure',
			:blame => 'System Administrator',
			:category => 'info',
			:description => "This is an information leak",
			:confidence => 100
		}

		# Risk is a value from 1-5 indicating the severity of the issue
		#	Examples: 1, 4, 5

		# Name is a descriptive name for this vulnerability.
		#	Examples: XSS, ReflectiveXSS, PersistentXSS

		# Blame indicates who is at fault for the vulnerability
		#	Examples: App Developer, Server Developer, System Administrator

		# Category indicates the general class of vulnerability
		#	Examples: info, xss, sql, rfi, lfi, cmd

		# Description is a textual summary of the vulnerability
		#	Examples: "A reflective cross-site scripting attack"
		#             "The web server leaks the internal IP address"
		#             "The cookie is not set to HTTP-only"

		#
		# Confidence is a value from 1 to 100 indicating how confident the
		# software is that the results are valid.
		#	Examples: 100, 90, 75, 15, 10, 0

		case vuln['type'].to_s
		when "ApacheDirectoryListing"
			res = {
				:risk => 1,
				:name  => 'Directory Listing',
				:blame => 'System Administrator',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "ApacheMultiViewsEnabled"
			res = {
				:risk => 1,
				:name  => 'Apache MultiViews Enabled',
				:blame => 'System Administrator',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "ApacheVersion"
			res = {
				:risk => 1,
				:name  => 'Web Server Version',
				:blame => 'System Administrator',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "PHPVersion"
			res = {
				:risk => 1,
				:name  => 'PHP Module Version',
				:blame => 'System Administrator',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "AutoCompleteEnabled"
			res = {
				:risk => 1,
				:name  => 'Form AutoComplete Enabled',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "CookieNotMarkedAsHttpOnly"
			res = {
				:risk => 1,
				:name  => 'Cookie Not HttpOnly',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "EmailDisclosure"
			res = {
				:risk => 1,
				:name  => 'Email Address Disclosure',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "ForbiddenResource"
			res = {
				:risk => 1,
				:name  => 'Forbidden Resource',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "FileUploadFound"
			res = {
				:risk => 1,
				:name  => 'File Upload Form',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "PasswordOverHTTP"
			res = {
				:risk => 2,
				:name  => 'Password Over HTTP',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "MySQL5Identified"
			res = {
				:risk => 1,
				:name  => 'MySQL 5 Identified',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "PossibleInternalWindowsPathLeakage"
			res = {
				:risk => 1,
				:name  => 'Path Leakage - Windows',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "PossibleInternalUnixPathLeakage"
			res = {
				:risk => 1,
				:name  => 'Path Leakage - Unix',
				:blame => 'App Developer',
				:category => 'info',
				:description => "",
				:confidence => 100
			}
		when "PossibleXSS", "LowPossibilityPermanentXSS", "XSS", "PermanentXSS"
			conf = 100
			conf = 25  if vuln['type'].to_s == "LowPossibilityPermanentXSS"
			conf = 50  if vuln['type'].to_s == "PossibleXSS"
			res = {
				:risk => 3,
				:name  => 'Cross-Site Scripting',
				:blame => 'App Developer',
				:category => 'xss',
				:description => "",
				:confidence => conf
			}

		when "ConfirmedBlindSQLInjection", "ConfirmedSQLInjection", "HighlyPossibleSqlInjection", "DatabaseErrorMessages"
			conf = 100
			conf = 90  if vuln['type'].to_s == "HighlyPossibleSqlInjection"
			conf = 25  if vuln['type'].to_s == "DatabaseErrorMessages"
			res = {
				:risk => 5,
				:name  => 'SQL Injection',
				:blame => 'App Developer',
				:category => 'sql',
				:description => "",
				:confidence => conf
			}
		else
		conf = 100
		res = {
			:risk => 1,
			:name  => vuln['type'].to_s,
			:blame => 'App Developer',
			:category => 'info',
			:description => "",
			:confidence => conf
		}
		end

		res
	end

	def import_fusionvm_xml(args={})
		args[:wspace] ||= workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		doc = Rex::Parser::FusionVMDocument.new(args,self)
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end


	#
	# Import Nmap's -oX xml output
	#
	def import_nmap_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_nmap_xml(args.merge(:data => data))
	end

	def import_nexpose_raw_noko_stream(args, &block)
		if block
			doc = Rex::Parser::NexposeRawDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::NexposeRawDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	def import_nexpose_noko_stream(args, &block)
		if block
			doc = Rex::Parser::NexposeSimpleDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::NexposeSimpleDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	def import_nmap_noko_stream(args, &block)
		if block
			doc = Rex::Parser::NmapDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::NmapDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	# If you have Nokogiri installed, you'll be shunted over to
	# that. Otherwise, you'll hit the old NmapXMLStreamParser.
	def import_nmap_xml(args={}, &block)
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		if Rex::Parser.nokogiri_loaded
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, "Nokogiri v#{::Nokogiri::VERSION}")
				import_nmap_noko_stream(noko_args) {|type, data| yield type,data }
			else
				import_nmap_noko_stream(noko_args)
			end
			return true
		end

		# XXX: Legacy nmap xml parser starts here.

		fix_services = args[:fix_services]
		data = args[:data]

		# Use a stream parser instead of a tree parser so we can deal with
		# huge results files without running out of memory.
		parser = Rex::Parser::NmapXMLStreamParser.new
		yield(:parser, parser.class.name) if block

		# Whenever the parser pulls a host out of the nmap results, store
		# it, along with any associated services, in the database.
		parser.on_found_host = Proc.new { |h|
			hobj = nil
			data = {:workspace => wspace}
			if (h["addrs"].has_key?("ipv4"))
				addr = h["addrs"]["ipv4"]
			elsif (h["addrs"].has_key?("ipv6"))
				addr = h["addrs"]["ipv6"]
			else
				# Can't report it if it doesn't have an IP
				raise RuntimeError, "At least one IPv4 or IPv6 address is required"
			end
			next if bl.include? addr
			data[:host] = addr
			if (h["addrs"].has_key?("mac"))
				data[:mac] = h["addrs"]["mac"]
			end
			data[:state] = (h["status"] == "up") ? Msf::HostState::Alive : Msf::HostState::Dead

			if ( h["reverse_dns"] )
				data[:name] = h["reverse_dns"]
			end

			# Only report alive hosts with ports to speak of.
			if(data[:state] != Msf::HostState::Dead)
				if h["ports"].size > 0
					if fix_services
						port_states = h["ports"].map {|p| p["state"]}.reject {|p| p == "filtered"}
						next if port_states.compact.empty?
					end
					yield(:address,data[:host]) if block
					hobj = report_host(data)
					report_import_note(wspace,hobj)
				end
			end

			if( h["os_vendor"] )
				note = {
					:workspace => wspace,
					:host => hobj || addr,
					:type => 'host.os.nmap_fingerprint',
					:data => {
						:os_vendor   => h["os_vendor"],
						:os_family   => h["os_family"],
						:os_version  => h["os_version"],
						:os_accuracy => h["os_accuracy"]
					}
				}

				if(h["os_match"])
					note[:data][:os_match] = h['os_match']
				end

				report_note(note)
			end

			if (h["last_boot"])
				report_note(
					:workspace => wspace,
					:host => hobj || addr,
					:type => 'host.last_boot',
					:data => {
						:time => h["last_boot"]
					}
				)
			end

			if (h["trace"])
				hops = []
				h["trace"]["hops"].each do |hop|
					hops << {
						"ttl"     => hop["ttl"].to_i,
						"address" => hop["ipaddr"].to_s,
						"rtt"     => hop["rtt"].to_f,
						"name"    => hop["host"].to_s
					}
				end
				report_note(
					:workspace => wspace,
					:host => hobj || addr,
					:type => 'host.nmap.traceroute',
					:data => {
						'port'  => h["trace"]["port"].to_i,
						'proto' => h["trace"]["proto"].to_s,
						'hops'  => hops
					}
				)
			end


			# Put all the ports, regardless of state, into the db.
			h["ports"].each { |p|
				# Localhost port results are pretty unreliable -- if it's
				# unknown, it's no good (possibly Windows-only)
				if (
					p["state"] == "unknown" &&
					h["status_reason"] == "localhost-response"
				)
					next
				end
				extra = ""
				extra << p["product"]   + " " if p["product"]
				extra << p["version"]   + " " if p["version"]
				extra << p["extrainfo"] + " " if p["extrainfo"]

				data = {}
				data[:workspace] = wspace
				if fix_services
					data[:proto] = nmap_msf_service_map(p["protocol"])
				else
					data[:proto] = p["protocol"].downcase
				end
				data[:port]  = p["portid"].to_i
				data[:state] = p["state"]
				data[:host]  = hobj || addr
				data[:info]  = extra if not extra.empty?
				if p["name"] != "unknown"
					data[:name] = p["name"]
				end
				report_service(data)
			}
			#Parse the scripts output
			if h["scripts"]
				h["scripts"].each do |key,val|
					if key == "smb-check-vulns"
						if val =~ /MS08-067: VULNERABLE/
							vuln_info = {
								:workspace => wspace,
								:host =>  hobj || addr,
								:port => 445,
								:proto => 'tcp',
								:name => 'MS08-067',
								:info => 'Microsoft Windows Server Service Crafted RPC Request Handling Unspecified Remote Code Execution',
								:refs =>['CVE-2008-4250',
									'BID-31874',
									'OSVDB-49243',
									'CWE-94',
									'MSFT-MS08-067',
									'MSF-Microsoft Server Service Relative Path Stack Corruption',
									'NSS-34476']
							}
							report_vuln(vuln_info)
						end
						if val =~ /MS06-025: VULNERABLE/
							vuln_info = {
								:workspace => wspace,
								:host =>  hobj || addr,
								:port => 445,
								:proto => 'tcp',
								:name => 'MS06-025',
								:info => 'Vulnerability in Routing and Remote Access Could Allow Remote Code Execution',
								:refs =>['CVE-2006-2370',
									'CVE-2006-2371',
									'BID-18325',
									'BID-18358',
									'BID-18424',
									'OSVDB-26436',
									'OSVDB-26437',
									'MSFT-MS06-025',
									'MSF-Microsoft RRAS Service RASMAN Registry Overflow',
									'NSS-21689']
							}
							report_vuln(vuln_info)
						end
						# This one has NOT been  Tested , remove this comment if confirmed working
						if val =~ /MS07-029: VULNERABLE/
							vuln_info = {
								:workspace => wspace,
								:host =>  hobj || addr,
								:port => 445,
								:proto => 'tcp',
								:name => 'MS07-029',
								:info => 'Vulnerability in Windows DNS RPC Interface Could Allow Remote Code Execution',
								# Add more refs based on nessus/nexpose .. results
								:refs =>['CVE-2007-1748',
									'OSVDB-34100',
									'MSF-Microsoft DNS RPC Service extractQuotedChar()',
									'NSS-25168']
							}
							report_vuln(vuln_info)
						end
					end
				end
			end
		}

		# XXX: Legacy nmap xml parser ends here.

		REXML::Document.parse_stream(data, parser)
	end

	def nmap_msf_service_map(proto)
		service_name_map(proto)
	end

	#
	# This method normalizes an incoming service name to one of the
	# the standard ones recognized by metasploit
	# 
	def service_name_map(proto)
		return proto unless proto.kind_of? String
		case proto.downcase
		when "msrpc", "nfs-or-iis", "dce endpoint resolution"
			"dcerpc"
		when "ms-sql-s", "tds"
			"mssql"
		when "ms-sql-m","microsoft sql monitor"
			"mssql-m"
		when "postgresql";                  "postgres"
		when "http-proxy";                  "http"
		when "iiimsf";                      "db2"
		when "oracle-tns";                  "oracle"
		when "quickbooksrds";               "metasploit"
		when "microsoft remote display protocol"
			"rdp"
		when "vmware authentication daemon"
			"vmauthd"
		when "netbios-ns", "cifs name service"
			"netbios"
		when "netbios-ssn", "microsoft-ds", "cifs"
			"smb"
		when "remote shell"
			"shell"
		when "remote login"
			"login"
		when "nfs lockd"
			"lockd"
		when "hp jetdirect"
			"jetdirect"
		when "dhcp server"
			"dhcp"
		when /^dns-(udp|tcp)$/;             "dns"
		when /^dce[\s+]rpc$/;               "dcerpc"
		else
			proto.downcase.gsub(/\s*\(.*/, '')   # "service (some service)"
		end
	end

	def report_import_note(wspace,addr)
		if @import_filedata.kind_of?(Hash) && @import_filedata[:filename] && @import_filedata[:filename] !~ /msfe-nmap[0-9]{8}/
		report_note(
			:workspace => wspace,
			:host => addr,
			:type => 'host.imported',
			:data => @import_filedata.merge(:time=> Time.now.utc)
		)
		end
	end

	#
	# Import Nessus NBE files
	#
	def import_nessus_nbe_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_nessus_nbe(args.merge(:data => data))
	end

	# There is no place the NBE actually stores the plugin name used to
	# scan. You get "Security Note" or "Security Warning," and that's it.
	def import_nessus_nbe(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		nbe_copy = data.dup
		# First pass, just to build the address map.
		addr_map = {}

		# Cache host objects before passing into handle_nessus()
		hobj_map = {}

		nbe_copy.each_line do |line|
			r = line.split('|')
			next if r[0] != 'results'
			next if r[4] != "12053"
			data = r[6]
			addr,hname = data.match(/([0-9\x2e]+) resolves as (.+)\x2e\\n/)[1,2]
			addr_map[hname] = addr
		end

		data.each_line do |line|
			r = line.split('|')
			next if r[0] != 'results'
			hname = r[2]
			if addr_map[hname]
				addr = addr_map[hname]
			else
				addr = hname # Must be unresolved, probably an IP address.
			end
			port = r[3]
			nasl = r[4]
			type = r[5]
			data = r[6]

			# If there's no resolution, or if it's malformed, skip it.
			next unless ipv46_validator(addr)

			if bl.include? addr
				next
			else
				yield(:address,addr) if block
			end

			hobj_map[ addr ] ||= report_host(:host => addr, :workspace => wspace)

			# Match the NBE types with the XML severity ratings
			case type
			# log messages don't actually have any data, they are just
			# complaints about not being able to perform this or that test
			# because such-and-such was missing
			when "Log Message"; next
			when "Security Hole"; severity = 3
			when "Security Warning"; severity = 2
			when "Security Note"; severity = 1
			# a severity 0 means there's no extra data, it's just an open port
			else; severity = 0
			end
			if nasl == "11936"
				os = data.match(/The remote host is running (.*)\\n/)[1]
				report_note(
					:workspace => wspace,
					:host => hobj_map[ addr ],
					:type => 'host.os.nessus_fingerprint',
					:data => {
						:os => os.to_s.strip
					}
				)
			end

			next if nasl.to_s.strip.empty?
			plugin_name = nil # NBE doesn't ever populate this
			handle_nessus(wspace, hobj_map[ addr ], port, nasl, plugin_name, severity, data)
		end
	end

	#
	# Of course they had to change the nessus format.
	#
	def import_openvas_xml(args={}, &block)
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		raise DBImportError.new("No OpenVAS XML support. Please submit a patch to msfdev[at]metasploit.com")
	end

	#
	# Import IP360 XML v3 output
	#
	def import_ip360_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_ip360_xml_v3(args.merge(:data => data))
	end

	#
	# Import Nessus XML v1 and v2 output
	#
	# Old versions of openvas exported this as well
	#
	def import_nessus_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end

		if data.index("NessusClientData_v2")
			import_nessus_xml_v2(args.merge(:data => data))
		else
			import_nessus_xml(args.merge(:data => data))
		end
	end

	def import_nessus_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		doc = rexmlify(data)
		doc.elements.each('/NessusClientData/Report/ReportHost') do |host|
			hobj = nil
			addr = nil
			hname = nil
			os = nil
			# If the name is resolved, the Nessus plugin for DNS
			# resolution should be there. If not, fall back to the
			# HostName
			host.elements.each('ReportItem') do |item|
				next unless item.elements['pluginID'].text == "12053"
				addr = item.elements['data'].text.match(/([0-9\x2e]+) resolves as/)[1]
				hname = host.elements['HostName'].text
			end
			addr ||= host.elements['HostName'].text
			next unless ipv46_validator(addr) # Skip resolved names and SCAN-ERROR.
			if bl.include? addr
				next
			else
				yield(:address,addr) if block
			end

			hinfo = {
				:workspace => wspace,
				:host => addr
			}

			# Record the hostname
			hinfo.merge!(:name => hname.to_s.strip) if hname
			hobj = report_host(hinfo)
			report_import_note(wspace,hobj)

			# Record the OS
			os ||= host.elements["os_name"]
			if os
				report_note(
					:workspace => wspace,
					:host => hobj,
					:type => 'host.os.nessus_fingerprint',
					:data => {
						:os => os.text.to_s.strip
					}
				)
			end

			host.elements.each('ReportItem') do |item|
				nasl = item.elements['pluginID'].text
				plugin_name = item.elements['pluginName'].text
				port = item.elements['port'].text
				data = item.elements['data'].text
				severity = item.elements['severity'].text

				handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data)
			end
		end
	end

	def import_nessus_xml_v2(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		#@host = {
				#'hname'             => nil,
				#'addr'              => nil,
				#'mac'               => nil,
				#'os'                => nil,
				#'ports'             => [ 'port' => {    'port'              	=> nil,
				#					'svc_name'              => nil,
				#					'proto'              	=> nil,
				#					'severity'              => nil,
				#					'nasl'              	=> nil,
				#					'description'           => nil,
				#					'cve'                   => [],
				#					'bid'                   => [],
				#					'xref'                  => []
				#				}
				#			]
				#}
		parser = Rex::Parser::NessusXMLStreamParser.new
		parser.on_found_host = Proc.new { |host|

			hobj = nil
			addr = host['addr'] || host['hname']

			next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others.

			if bl.include? addr
				next
			else
				yield(:address,addr) if block
			end

			os = host['os']
			hname = host['hname']
			mac = host['mac']

			host_info = {
				:workspace => wspace,
				:host => addr,
			}
			host_info[:name] = hname.to_s.strip if hname
			# Short mac, protect against Nessus's habit of saving multiple macs
			# We can't use them anyway, so take just the first.
			host_info[:mac]  = mac.to_s.strip.upcase.split(/\s+/).first if mac

			hobj = report_host(host_info)
			report_import_note(wspace,hobj)

			os = host['os']
			yield(:os,os) if block
			if os
				report_note(
					:workspace => wspace,
					:host => hobj,
					:type => 'host.os.nessus_fingerprint',
					:data => {
						:os => os.to_s.strip
					}
				)
			end

			host['ports'].each do |item|
				next if item['port'] == 0
				msf = nil
				nasl = item['nasl'].to_s
				nasl_name = item['nasl_name'].to_s
				port = item['port'].to_s
				proto = item['proto'] || "tcp"
				sname = item['svc_name']
				severity = item['severity']
				description = item['description']
				cve = item['cve']
				bid = item['bid']
				xref = item['xref']
				msf = item['msf']

				yield(:port,port) if block

				handle_nessus_v2(wspace, hobj, port, proto, sname, nasl, nasl_name, severity, description, cve, bid, xref, msf)

			end
			yield(:end,hname) if block
		}

		REXML::Document.parse_stream(data, parser)

	end

	def import_mbsa_xml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_mbsa_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_mbsa_noko_stream(noko_args)
			end
			return true
		else # Sorry
			raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
		end
	end

	def import_mbsa_noko_stream(args={},&block)
		if block
			doc = Rex::Parser::MbsaDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::MbsaDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	def import_foundstone_xml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_foundstone_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_foundstone_noko_stream(noko_args)
			end
			return true
		else # Sorry
			raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
		end
	end

	def import_foundstone_noko_stream(args={},&block)
		if block
			doc = Rex::Parser::FoundstoneDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::FoundstoneDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	def import_acunetix_xml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_acunetix_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_acunetix_noko_stream(noko_args)
			end
			return true
		else # Sorry
			raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
		end
	end

	def import_ci_xml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_ci_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_ci_noko_stream(noko_args)
			end
			return true
		else # Sorry
			raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
		end
	end

	def import_acunetix_noko_stream(args={},&block)
		if block
			doc = Rex::Parser::AcunetixDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::AcunetixFoundstoneDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end


	def import_appscan_xml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_appscan_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_appscan_noko_stream(noko_args)
			end
			return true
		else # Sorry
			raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
		end
	end

	def import_appscan_noko_stream(args={},&block)
		if block
			doc = Rex::Parser::AppscanDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::AppscanDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end

	def import_burp_session_xml(args={}, &block)
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		wspace = args[:wspace] || workspace
		if Rex::Parser.nokogiri_loaded
			# Rex::Parser.reload("burp_session_nokogiri.rb")
			parser = "Nokogiri v#{::Nokogiri::VERSION}"
			noko_args = args.dup
			noko_args[:blacklist] = bl
			noko_args[:wspace] = wspace
			if block
				yield(:parser, parser)
				import_burp_session_noko_stream(noko_args) {|type, data| yield type,data}
			else
				import_burp_session_noko_stream(noko_args)
			end
			return true
		else # Sorry
			raise DBImportError.new("Could not import due to missing Nokogiri parser. Try 'gem install nokogiri'.")
		end
	end

	def import_burp_session_noko_stream(args={},&block)
		if block
			doc = Rex::Parser::BurpSessionDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::BurpSessionDocument.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end


	#
	# Import IP360's ASPL database
	#
	def import_ip360_aspl_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		if not data.index("<ontology")
			raise DBImportError.new("The ASPL file does not appear to be valid or may still be compressed")
		end

		base = ::File.join(Msf::Config.config_directory, "data", "ncircle")
		::FileUtils.mkdir_p(base)
		::File.open(::File.join(base, "ip360.aspl"), "wb") do |fd|
			fd.write(data)
		end
		yield(:notice, "Saved the IP360 ASPL database to #{base}...")
	end


	#
	# Import IP360's xml output
	#
	def import_ip360_xml_v3(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		# @aspl = {'vulns' => {'name' => { }, 'cve' => { }, 'bid' => { } }
		# 'oses' => {'name' } }

		aspl_path  = nil
		aspl_paths = [
			::File.join(Msf::Config.config_directory, "data", "ncircle", "ip360.aspl"),
			::File.join(Msf::Config.data_directory, "ncircle", "ip360.aspl")
		]

		aspl_paths.each do |tpath|
			next if not (::File.exist?(tpath) and ::File.readable?(tpath))
			aspl_path = tpath
			break
		end

		if not aspl_path
			raise DBImportError.new("The nCircle IP360 ASPL file is not present.\n    Download ASPL from nCircle VNE | Administer | Support | Resources, unzip it, and import it first")
		end

		# parse nCircle ASPL file
		aspl = ""
		::File.open(aspl_path, "rb") do |f|
			aspl = f.read(f.stat.size)
		end

		@asplhash = nil
		parser = Rex::Parser::IP360ASPLXMLStreamParser.new
		parser.on_found_aspl = Proc.new { |asplh|
			@asplhash = asplh
		}
		REXML::Document.parse_stream(aspl, parser)

		# nCircle has some quotes escaped which causes the parser to break
		# we don't need these lines so just replace \" with "
		data.gsub!(/\\"/,'"')

		# parse nCircle Scan Output
		parser = Rex::Parser::IP360XMLStreamParser.new
		parser.on_found_host = Proc.new { |host|
			hobj = nil
			addr = host['addr'] || host['hname']

			next unless ipv46_validator(addr) # Catches SCAN-ERROR, among others.

			if bl.include? addr
				next
			else
				yield(:address,addr) if block
			end

			os = host['os']
			hname = host['hname']
			mac = host['mac']

			host_hash = {
				:workspace => wspace,
				:host => addr,
			}
			host_hash[:name] = hname.to_s.strip if hname
			host_hash[:mac]  = mac.to_s.strip.upcase if mac

			hobj = report_host(host_hash)

			yield(:os, os) if block
			if os
				report_note(
					:workspace => wspace,
					:host => hobj,
					:type => 'host.os.ip360_fingerprint',
					:data => {
						:os => @asplhash['oses'][os].to_s.strip
					}
				)
			end

			host['apps'].each do |item|
				port = item['port'].to_s
				proto = item['proto'].to_s

				handle_ip360_v3_svc(wspace, hobj, port, proto, hname)
			end


			host['vulns'].each do |item|
				vulnid = item['vulnid'].to_s
				port = item['port'].to_s
				proto = item['proto'] || "tcp"
				vulnname = @asplhash['vulns']['name'][vulnid]
				cves = @asplhash['vulns']['cve'][vulnid]
				bids = @asplhash['vulns']['bid'][vulnid]

				yield(:port, port) if block

				handle_ip360_v3_vuln(wspace, hobj, port, proto, hname, vulnid, vulnname, cves, bids)

			end

			yield(:end, hname) if block
		}

		REXML::Document.parse_stream(data, parser)
	end

	def find_qualys_asset_vuln_refs(doc)
		vuln_refs = {}
		doc.elements.each("/ASSET_DATA_REPORT/GLOSSARY/VULN_DETAILS_LIST/VULN_DETAILS") do |vuln|
			next unless vuln.elements['QID'] && vuln.elements['QID'].first
			qid = vuln.elements['QID'].first.to_s
			vuln_refs[qid] ||= []
			vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref|
				vuln_refs[qid].push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1])
			end
			vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref|
				vuln_refs[qid].push('BID-' + ref.elements['ID'].text.to_s)
			end
		end
		return vuln_refs
	end

	# Pull out vulnerabilities that have at least one matching
	# ref -- many "vulns" are not vulns, just audit information.
	def find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,&block)
		host.elements.each("VULN_INFO_LIST/VULN_INFO") do |vi|
			next unless vi.elements["QID"]
			vi.elements.each("QID") do |qid|
				next if vuln_refs[qid.text].nil? || vuln_refs[qid.text].empty?
				handle_qualys(wspace, hobj, nil, nil, qid.text, nil, vuln_refs[qid.text], nil)
			end
		end
	end

	# Takes QID numbers and finds the discovered services in
	# a qualys_asset_xml.
	def find_qualys_asset_ports(i,host,wspace,hobj)
		return unless (i == 82023 || i == 82004)
		proto = i == 82023 ? 'tcp' : 'udp'
		qid = host.elements["VULN_INFO_LIST/VULN_INFO/QID[@id='qid_#{i}']"]
		qid_result = qid.parent.elements["RESULT[@format='table']"] if qid
		hports = qid_result.first.to_s if qid_result
		if hports
			hports.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
				if match[2] == nil or match[2].strip == 'unknown'
					name = match[1].strip
				else
					name = match[2].strip
				end
				handle_qualys(wspace, hobj, match[0].to_s, proto, 0, nil, nil, name)
			end
		end
	end

	#
	# Import Qualys's Asset Data Report format
	#
	def import_qualys_asset_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []
		doc = rexmlify(data)
		vuln_refs = find_qualys_asset_vuln_refs(doc)

		# 2nd pass, actually grab the hosts.
		doc.elements.each("/ASSET_DATA_REPORT/HOST_LIST/HOST") do |host|
			hobj = nil
			addr = host.elements["IP"].text if host.elements["IP"]
			next unless validate_ips(addr)
			if bl.include? addr
				next
			else
				yield(:address,addr) if block
			end
			hname = ( # Prefer NetBIOS over DNS
				(host.elements["NETBIOS"].text if host.elements["NETBIOS"]) ||
			 	(host.elements["DNS"].text if host.elements["DNS"]) ||
			 	"" )
			hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive)
			report_import_note(wspace,hobj)

			if host.elements["OPERATING_SYSTEM"]
				hos = host.elements["OPERATING_SYSTEM"].text
				report_note(
					:workspace => wspace,
					:host => hobj,
					:type => 'host.os.qualys_fingerprint',
					:data => { :os => hos }
				)
			end

			# Report open ports.
			find_qualys_asset_ports(82023,host,wspace,hobj) # TCP
			find_qualys_asset_ports(82004,host,wspace,hobj) # UDP

			# Report vulns
			find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,&block)

		end # host

	end

	#
	# Import Qualys' Scan xml output
	#
	def import_qualys_scan_xml_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_qualys_scan_xml(args.merge(:data => data))
	end

	def import_qualys_scan_xml(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []


		doc = rexmlify(data)
		doc.elements.each('/SCAN/IP') do |host|
			hobj = nil
			addr  = host.attributes['value']
			if bl.include? addr
				next
			else
				yield(:address,addr) if block
			end
			hname = host.attributes['name'] || ''

			hobj = report_host(:workspace => wspace, :host => addr, :name => hname, :state => Msf::HostState::Alive)
			report_import_note(wspace,hobj)

			if host.elements["OS"]
				hos = host.elements["OS"].text
				report_note(
					:workspace => wspace,
					:host => hobj,
					:type => 'host.os.qualys_fingerprint',
					:data => {
						:os => hos
					}
				)
			end

			# Open TCP Services List (Qualys ID 82023)
			services_tcp = host.elements["SERVICES/CAT/SERVICE[@number='82023']/RESULT"]
			if services_tcp
				services_tcp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
					if match[2] == nil or match[2].strip == 'unknown'
						name = match[1].strip
					else
						name = match[2].strip
					end
					handle_qualys(wspace, hobj, match[0].to_s, 'tcp', 0, nil, nil, name)
				end
			end
			# Open UDP Services List (Qualys ID 82004)
			services_udp = host.elements["SERVICES/CAT/SERVICE[@number='82004']/RESULT"]
			if services_udp
				services_udp.text.scan(/([0-9]+)\t(.*?)\t.*?\t([^\t\n]*)/) do |match|
					if match[2] == nil or match[2].strip == 'unknown'
						name = match[1].strip
					else
						name = match[2].strip
					end
					handle_qualys(wspace, hobj, match[0].to_s, 'udp', 0, nil, nil, name)
				end
			end

			# VULNS are confirmed, PRACTICES are unconfirmed vulnerabilities
			host.elements.each('VULNS/CAT | PRACTICES/CAT') do |cat|
				port = cat.attributes['port']
				protocol = cat.attributes['protocol']
				cat.elements.each('VULN | PRACTICE') do |vuln|
					refs = []
					qid = vuln.attributes['number']
					severity = vuln.attributes['severity']
					title = vuln.elements['TITLE'].text.to_s
					vuln.elements.each('VENDOR_REFERENCE_LIST/VENDOR_REFERENCE') do |ref|
						refs.push(ref.elements['ID'].text.to_s)
					end
					vuln.elements.each('CVE_ID_LIST/CVE_ID') do |ref|
						refs.push('CVE-' + /C..-([0-9\-]{9})/.match(ref.elements['ID'].text.to_s)[1])
					end
					vuln.elements.each('BUGTRAQ_ID_LIST/BUGTRAQ_ID') do |ref|
						refs.push('BID-' + ref.elements['ID'].text.to_s)
					end

					handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, nil,title)
				end
			end
		end
	end

	def import_ip_list_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace

		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end
		import_ip_list(args.merge(:data => data))
	end

	def import_ip_list(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		data.each_line do |ip|
			ip.strip!
			if bl.include? ip
				next
			else
				yield(:address,ip) if block
			end
			host = find_or_create_host(:workspace => wspace, :host=> ip, :state => Msf::HostState::Alive)
		end
	end

	def import_amap_log_file(args={})
		filename = args[:filename]
		wspace = args[:wspace] || workspace
		data = ""
		::File.open(filename, 'rb') do |f|
			data = f.read(f.stat.size)
		end

		case import_filetype_detect(data)
		when :amap_log
			import_amap_log(args.merge(:data => data))
		when :amap_mlog
			import_amap_mlog(args.merge(:data => data))
		else
			raise DBImportError.new("Could not determine file type")
		end
	end

	def import_amap_log(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		data.each_line do |line|
			next if line =~ /^#/
			next if line !~ /^Protocol on ([^:]+):([^\x5c\x2f]+)[\x5c\x2f](tcp|udp) matches (.*)$/
			addr   = $1
			next if bl.include? addr
			port   = $2.to_i
			proto  = $3.downcase
			name   = $4
			host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
			next if not host
			yield(:address,addr) if block
			info = {
				:workspace => wspace,
				:host => host,
				:proto => proto,
				:port => port
			}
			if name != "unidentified"
				info[:name] = name
			end
			service = find_or_create_service(info)
		end
	end

	def import_amap_mlog(args={}, &block)
		data = args[:data]
		wspace = args[:wspace] || workspace
		bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : []

		data.each_line do |line|
			next if line =~ /^#/
			r = line.split(':')
			next if r.length < 6

			addr   = r[0]
			next if bl.include? addr
			port   = r[1].to_i
			proto  = r[2].downcase
			status = r[3]
			name   = r[5]
			next if status != "open"

			host = find_or_create_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
			next if not host
			yield(:address,addr) if block
			info = {
				:workspace => wspace,
				:host => host,
				:proto => proto,
				:port => port
			}
			if name != "unidentified"
				info[:name] = name
			end
			service = find_or_create_service(info)
		end
	end

	def import_ci_noko_stream(args, &block)
		if block
			doc = Rex::Parser::CIDocument.new(args,framework.db) {|type, data| yield type,data }
		else
			doc = Rex::Parser::CI.new(args,self)
		end
		parser = ::Nokogiri::XML::SAX::Parser.new(doc)
		parser.parse(args[:data])
	end


	def unserialize_object(xml_elem, allow_yaml = false)
		return nil unless xml_elem
		string = xml_elem.text.to_s.strip
		return string unless string.is_a?(String)
		return nil if (string.empty? || string.nil?)

		begin
			# Validate that it is properly formed base64 first
			if string.gsub(/\s+/, '') =~ /^([a-z0-9A-Z\+\/=]+)$/
				Marshal.load($1.unpack("m")[0])
			else
				if allow_yaml
					begin
						YAML.load(string)
					rescue
						dlog("Badly formatted YAML: '#{string}'")
						string
					end
				else
					string
				end
			end
		rescue ::Exception => e
			if allow_yaml
				YAML.load(string) rescue string
			else
				string
			end
		end
	end

	#
	# Returns something suitable for the +:host+ parameter to the various report_* methods
	#
	# Takes a Host object, a Session object, an Msf::Session object or a String
	# address
	#
	def normalize_host(host)
		return host if host.kind_of? ::Mdm::Host
		norm_host = nil

		if (host.kind_of? String)

			if Rex::Socket.is_ipv4?(host)
				# If it's an IPv4 addr with a port on the end, strip the port
				if host =~ /((\d{1,3}\.){3}\d{1,3}):\d+/
					norm_host = $1
				else
					norm_host = host
				end
			elsif Rex::Socket.is_ipv6?(host)
				# If it's an IPv6 addr, drop the scope
				address, scope = host.split('%', 2)
				norm_host = address
			else
				norm_host = Rex::Socket.getaddress(host, true)
			end
		elsif host.kind_of? ::Mdm::Session
			norm_host = host.host
		elsif host.respond_to?(:session_host)
			# Then it's an Msf::Session object
			thost = host.session_host
			norm_host = thost
		end

		# If we got here and don't have a norm_host yet, it could be a
		# Msf::Session object with an empty or nil tunnel_host and tunnel_peer;
		# see if it has a socket and use its peerhost if so.
		if (
				norm_host.nil? and
				host.respond_to?(:sock) and
				host.sock.respond_to?(:peerhost) and
				host.sock.peerhost.to_s.length > 0
			)
			norm_host = session.sock.peerhost
		end
		# If We got here and still don't have a real host, there's nothing left
		# to try, just log it and return what we were given
		if not norm_host
			dlog("Host could not be normalized: #{host.inspect}")
			norm_host = host
		end

		norm_host
	end

	# A way to sneak the yield back into the db importer.
	# Used by the SAX parsers.
	def emit(sym,data,&block)
		yield(sym,data)
	end

protected

	#
	# This holds all of the shared parsing/handling used by the
	# Nessus NBE and NESSUS v1 methods
	#
	def handle_nessus(wspace, hobj, port, nasl, plugin_name, severity, data)
		addr = hobj.address
		# The port section looks like:
		#   http (80/tcp)
		p = port.match(/^([^\(]+)\((\d+)\/([^\)]+)\)/)
		return if not p

		# Unnecessary as the caller should already have reported this host
		#report_host(:workspace => wspace, :host => addr, :state => Msf::HostState::Alive)
		name = p[1].strip
		port = p[2].to_i
		proto = p[3].downcase

		info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }
		if name != "unknown" and name[-1,1] != "?"
			info[:name] = name
		end
		report_service(info)

		if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0"
			return
		end

		data.gsub!("\\n", "\n")

		refs = []

		if (data =~ /^CVE : (.*)$/)
			$1.gsub(/C(VE|AN)\-/, '').split(',').map { |r| r.strip }.each do |r|
				refs.push('CVE-' + r)
			end
		end

		if (data =~ /^BID : (.*)$/)
			$1.split(',').map { |r| r.strip }.each do |r|
				refs.push('BID-' + r)
			end
		end

		if (data =~ /^Other references : (.*)$/)
			$1.split(',').map { |r| r.strip }.each do |r|
				ref_id, ref_val = r.split(':')
				ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id)
			end
		end

		nss = 'NSS-' + nasl.to_s.strip
		refs << nss

		unless plugin_name.to_s.strip.empty?
			vuln_name = plugin_name
		else
			vuln_name = nss
		end

		vuln_info = {
			:workspace => wspace,
			:host => hobj,
			:port => port,
			:proto => proto,
			:name => vuln_name,
			:info => data,
			:refs => refs
		}
		report_vuln(vuln_info)
	end

	#
	# NESSUS v2 file format has a dramatically different layout
	# for ReportItem data
	#
	def handle_nessus_v2(wspace,hobj,port,proto,name,nasl,nasl_name,severity,description,cve,bid,xref,msf)
		addr = hobj.address

		info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }

		unless name =~ /^unknown$|\?$/
			info[:name] = name
		end

		if port.to_i != 0
			report_service(info)
		end

		if nasl.nil? || nasl.empty? || nasl == 0 || nasl == "0"
			return
		end

		refs = []

		cve.each do |r|
			r.to_s.gsub!(/C(VE|AN)\-/, '')
			refs.push('CVE-' + r.to_s)
		end if cve

		bid.each do |r|
			refs.push('BID-' + r.to_s)
		end if bid

		xref.each do |r|
			ref_id, ref_val = r.to_s.split(':')
			ref_val ? refs.push(ref_id + '-' + ref_val) : refs.push(ref_id)
		end if xref

		msfref = "MSF-" << msf if msf
		refs.push msfref if msfref

		nss = 'NSS-' + nasl
		if nasl_name.nil? || nasl_name.empty?
			vuln_name = nss
		else
			vuln_name = nasl_name
		end

		refs << nss.strip

		vuln = {
			:workspace => wspace,
			:host => hobj,
			:name => vuln_name,
			:info => description ? description : "",
			:refs => refs
		}

		if port.to_i != 0
			vuln[:port]  = port
			vuln[:proto] = proto
		end

		report_vuln(vuln)
	end

	#
	# IP360 v3 vuln
	#
	def handle_ip360_v3_svc(wspace,hobj,port,proto,hname)
		addr = hobj.address
		report_host(:workspace => wspace, :host => hobj, :state => Msf::HostState::Alive)

		info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }
		if hname != "unknown" and hname[-1,1] != "?"
			info[:name] = hname
		end

		if port.to_i != 0
			report_service(info)
		end
	end  #handle_ip360_v3_svc

	#
	# IP360 v3 vuln
	#
	def handle_ip360_v3_vuln(wspace,hobj,port,proto,hname,vulnid,vulnname,cves,bids)
		info = { :workspace => wspace, :host => hobj, :port => port, :proto => proto }
		if hname != "unknown" and hname[-1,1] != "?"
			info[:name] = hname
		end

		if port.to_i != 0
			report_service(info)
		end

		refs = []

		cves.split(/,/).each do |cve|
			refs.push(cve.to_s)
		end if cves

		bids.split(/,/).each do |bid|
			refs.push('BID-' + bid.to_s)
		end if bids

		description = nil   # not working yet
		vuln = {
			:workspace => wspace,
			:host => hobj,
			:name => vulnname,
			:info => description ? description : "",
			:refs => refs
		}

		if port.to_i != 0
			vuln[:port]  = port
			vuln[:proto] = proto
		end

		report_vuln(vuln)
	end  #handle_ip360_v3_vuln

	#
	# Qualys report parsing/handling
	#
	def handle_qualys(wspace, hobj, port, protocol, qid, severity, refs, name=nil, title=nil)
		addr = hobj.address
		port = port.to_i if port

		info = { :workspace => wspace, :host => hobj, :port => port, :proto => protocol }
		if name and name != 'unknown' and name != 'No registered hostname'
			info[:name] = name
		end

		if info[:host] && info[:port] && info[:proto]
			report_service(info)
		end

		fixed_refs = []
		if refs
			refs.each do |ref|
				case ref
				when /^MS[0-9]{2}-[0-9]{3}/
					fixed_refs << "MSB-#{ref}"
				else
					fixed_refs << ref
				end
			end
		end

		return if qid == 0
		title = 'QUALYS-' + qid if title.nil? or title.empty?
		if addr
			report_vuln(
				:workspace => wspace,
				:host => hobj,
				:port => port,
				:proto => protocol,
				:name =>  title,
				:refs => fixed_refs
			)
		end
	end

	def process_nexpose_data_sxml_refs(vuln)
		refs = []
		vid = vuln.attributes['id'].to_s.downcase
		vry = vuln.attributes['resultCode'].to_s.upcase

		# Only process vuln-exploitable and vuln-version statuses
		return if vry !~ /^V[VE]$/

		refs = []
		vuln.elements.each('id') do |ref|
			rtyp = ref.attributes['type'].to_s.upcase
			rval = ref.text.to_s.strip
			case rtyp
			when 'CVE'
				refs << rval.gsub('CAN', 'CVE')
			when 'MS' # obsolete?
				refs << "MSB-MS-#{rval}"
			else
				refs << "#{rtyp}-#{rval}"
			end
		end

		refs << "NEXPOSE-#{vid}"
		refs
	end

end

end
