require 'msf/core'

module Msf

###
#
# The module base class is responsible for providing the common interface
# that is used to interact with modules at the most basic levels, such as
# by inspecting a given module's attributes (name, dsecription, version,
# authors, etc) and by managing the module's data store.
#
###
class Module

	# Modules can subscribe to a user-interface, and as such they include the
	# UI subscriber module.  This provides methods like print, print_line, etc.
	# User interfaces are designed to be medium independent, and as such the
	# user interface subscribes are designed to provide a flexible way of
	# interacting with the user, n stuff.
	include Rex::Ui::Subscriber

	# Make include public so we can runtime extend
	public_class_method :include

	class << self
		include Framework::Offspring

		#
		# Class method to figure out what type of module this is
		#
		def type
			raise NotImplementedError
		end

		def fullname
			return type + '/' + refname
		end

		def shortname
			return refname.split('/')[-1]
		end

		#
		# Returns this module's ranking.
		#
		def rank
			(const_defined?('Rank')) ? const_get('Rank') : NormalRanking
		end

		#
		# Returns this module's ranking as a string representation.
		#
		def rank_to_s
			RankingName[rank]
		end

		#
		# Returns this module's ranking as a string for display.
		#
		def rank_to_h
			rank_to_s.gsub('Rank', '').downcase
		end
		#
		# The module's name that is assigned it it by the framework
		# or derived from the path that the module is loaded from.
		#
		attr_accessor :refname

		#
		# This attribute holds the non-duplicated copy of the module
		# implementation.  This attribute is used for reloading purposes so that
		# it can be re-duplicated.
		#
		attr_accessor :orig_cls

		#
		# The path from which the module was loaded.
		#
		attr_accessor :file_path
	end

	#
	# Returns the class reference to the framework
	#
	def framework
		return self.class.framework
	end

	#
	# This method allows modules to tell the framework if they are usable
	# on the system that they are being loaded on in a generic fashion.
	# By default, all modules are indicated as being usable.  An example of
	# where this is useful is if the module depends on something external to
	# ruby, such as a binary.
	#
	def self.is_usable
		true
	end

	require 'msf/core/module/author'
	require 'msf/core/module/platform_list'
	require 'msf/core/module/reference'
	require 'msf/core/module/target'
	require 'msf/core/module/auxiliary_action'

	#
	# Creates an instance of an abstract module using the supplied information
	# hash.
	#
	def initialize(info = {})

		@module_info_copy = info.dup


		self.module_info = info
		generate_uuid

		set_defaults

		# Initialize module compatibility hashes
		init_compat

		# Fixup module fields as needed
		info_fixups

		# Transform some of the fields to arrays as necessary
		self.author = Author.transform(module_info['Author'])
		self.arch = Rex::Transformer.transform(module_info['Arch'], Array, [ String ], 'Arch')
		self.platform = PlatformList.transform(module_info['Platform'])
		self.references = Rex::Transformer.transform(module_info['References'], Array, [ SiteReference, Reference ], 'Ref')

		# Create and initialize the option container for this module
		self.options = OptionContainer.new
		self.options.add_options(info['Options'], self.class)
		self.options.add_advanced_options(info['AdvancedOptions'], self.class)
		self.options.add_evasion_options(info['EvasionOptions'], self.class)

		# Create and initialize the data store for this module
		self.datastore = ModuleDataStore.new(self)

		# Import default options into the datastore
		import_defaults

		self.privileged = module_info['Privileged'] || false
		self.license = module_info['License'] || MSF_LICENSE

		# Allow all modules to track their current workspace
		register_advanced_options(
			[
				OptString.new('WORKSPACE', [ false, "Specify the workspace for this module" ])
			], Msf::Module)
	end

	#
	# Creates a fresh copy of an instantiated module
	#
	def replicant

		obj = self.class.new
		self.instance_variables.each { |k|
			v = instance_variable_get(k)
			v = v.dup rescue v
			obj.instance_variable_set(k, v)
		}

		obj.datastore    = self.datastore.copy
		obj.user_input   = self.user_input
		obj.user_output  = self.user_output
		obj.module_store = self.module_store.clone
		obj
	end

	#
	# Overwrite the Subscriber print_line to do time stamps
	#

	def print_prefix
		if(
			datastore['TimestampOutput'] =~ /^(t|y|1)/i or
			framework.datastore['TimestampOutput'] =~ /^(t|y|1)/i
		)
			prefix = "[#{Time.now.strftime("%Y.%m.%d-%H:%M:%S")}] "

			xn ||= datastore['ExploitNumber']
			xn ||= framework.datastore['ExploitNumber']
			if xn.is_a?(Fixnum)
				prefix << "[%04d] " % xn
			end

			return prefix
		end
		""
	end

	def print_status(msg='')
		super(print_prefix + msg)
	end

	def print_error(msg='')
		super(print_prefix + msg)
	end

	def print_good(msg='')
		super(print_prefix + msg)
	end
	#
	# Returns the module's framework full reference name.  This is the
	# short name that end-users work with (refname) plus the type
	# of module prepended.  Ex:
	#
	# payloads/windows/shell/reverse_tcp
	#
	def fullname
		return self.class.fullname
	end

	#
	# Returns the module's framework reference name.  This is the
	# short name that end-users work with.  Ex:
	#
	# windows/shell/reverse_tcp
	#
	def refname
		return self.class.refname
	end

	#
	# Returns the module's rank.
	#
	def rank
		return self.class.rank
	end

	#
	# Returns the module's rank in string format.
	#
	def rank_to_s
		return self.class.rank_to_s
	end

	#
	# Returns the module's rank in display format.
	#
	def rank_to_h
		return self.class.rank_to_h
	end

	#
	# Returns the module's framework short name.  This is a
	# possibly conflicting name used for things like console
	# prompts.
	#
	# reverse_tcp
	#
	def shortname
		return self.class.shortname
	end

	#
	# Returns the unduplicated class associated with this module.
	#
	def orig_cls
		return self.class.orig_cls
	end

	#
	# The path to the file in which the module can be loaded from.
	#
	def file_path
		self.class.file_path
	end

	#
	# Return the module's name from the module information hash.
	#
	def name
		module_info['Name']
	end

	#
	# Returns the module's alias, if it has one.  Otherwise, the module's
	# name is returned.
	#
	def alias
		module_info['Alias']
	end

	#
	# Return the module's description.
	#
	def description
		module_info['Description']
	end

	#
	# Return the module's version information.
	#
	def version
		module_info['Version'].split(/,/).map { |ver|
			ver.gsub(/\$Rev.s.on:\s+|\s+\$$/, '')
		}.join(',')
	end

	#
	# Returns the disclosure date, if known.
	#
	def disclosure_date
		date_str = module_info['DisclosureDate']
		date_str ? Date.parse(date_str) : nil
	end

	#
	# Returns the hash that describes this module's compatibilities.
	#
	def compat
		module_info['Compat'] || {}
	end

	#
	# Returns the address of the last target host (rough estimate)
	#
	def target_host
		if(self.respond_to?('rhost'))
			return rhost()
		end

		if(self.datastore['RHOST'])
			return self.datastore['RHOST']
		end

		nil
	end

	#
	# Returns the current workspace
	#
	def workspace
		self.datastore['WORKSPACE'] ||
			(framework.db and framework.db.active and framework.db.workspace and framework.db.workspace.name)
	end

	#
	# Returns the username that instantiated this module, this tries a handful of methods
	# to determine what actual user ran this module. 
	#
	def owner
		# Generic method to configure a module owner
		username = self.datastore['MODULE_OWNER'].to_s.strip
		
		# Specific method used by the commercial products
		if username.empty?
			username = self.datastore['PROUSER'].to_s.strip
		end
		
		# Fallback when neither prior method is available, common for msfconsole
		if username.empty?
			username = (ENV['LOGNAME'] || ENV['USERNAME'] || ENV['USER'] || "unknown").to_s.strip
		end

		username
	end

	#
	# Returns whether or not this module is compatible with the supplied
	# module.
	#
	def compatible?(mod)
		ch = nil

		# Invalid module?  Shoot, we can't compare that.
		return true if (mod == nil)

		# Determine which hash to used based on the supplied module type
		if (mod.type == MODULE_ENCODER)
			ch = self.compat['Encoder']
		elsif (mod.type == MODULE_NOP)
			ch = self.compat['Nop']
		elsif (mod.type == MODULE_PAYLOAD)
			ch = self.compat['Payload']
		else
			return true
		end

		# Enumerate each compatibility item in our hash to find out
		# if we're compatible with this sucker.
		ch.each_pair do |k,v|

			# Get the value of the current key from the module, such as
			# the ConnectionType for a stager (ws2ord, for instance).
			mval = mod.module_info[k]

			# Reject a filled compat item on one side, but not the other
			return false if (v and not mval)

			# Track how many of our values matched the module
			mcnt = 0

			# Values are whitespace separated
			sv = v.split(/\s+/)
			mv = mval.split(/\s+/)

			sv.each do |x|

				dlog("Checking compat [#{mod.refname} with #{self.refname}]: #{x} to #{mv.join(", ")}", 'core', LEV_3)

				# Verify that any negate values are not matched
				if (x[0,1] == '-' and mv.include?(x[1, x.length-1]))
					dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{x}, value was #{mval}", 'core', LEV_1)
					return false
				end

				mcnt += 1 if mv.include?(x)
			end

			# No values matched, reject this module
			if (mcnt == 0)
				dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}, value was #{mval}", 'core', LEV_1)
				return false
			end

		end

		# If we get here, we're compatible.
		return true
	end

	#
	# Return the module's abstract type.
	#
	def type
		raise NotImplementedError
	end

	#
	# Return a comma separated list of author for this module.
	#
	def author_to_s
		return author.collect { |author| author.to_s }.join(", ")
	end

	#
	# Enumerate each author.
	#
	def each_author(&block)
		author.each(&block)
	end

	#
	# Return a comma separated list of supported architectures, if any.
	#
	def arch_to_s
		return arch.join(", ")
	end

	#
	# Enumerate each architecture.
	#
	def each_arch(&block)
		arch.each(&block)
	end

	#
	# Return whether or not the module supports the supplied architecture.
	#
	def arch?(what)
		return true if (what == ARCH_ANY)

		return arch.index(what) != nil
	end

	#
	# Return a comma separated list of supported platforms, if any.
	#
	def platform_to_s
		return ((platform.all?) ? [ "All" ] : platform.names).join(", ")
	end

	#
	# Checks to see if this module is compatible with the supplied platform
	#
	def platform?(what)
		(platform & what).empty? == false
	end

	#
	# Returns whether or not the module requires or grants high privileges.
	#
	def privileged?
		return (privileged == true)
	end

	#
	# The default communication subsystem for this module.  We may need to move
	# this somewhere else.
	#
	def comm
		return Rex::Socket::Comm::Local
	end

	#
	# Overrides the class' own datastore with the one supplied.  This is used
	# to allow modules to share datastores, such as a payload sharing an
	# exploit module's datastore.
	#
	def share_datastore(ds)
		self.datastore = ds
		self.datastore.import_options(self.options)
	end

	#
	# Imports default options into the module's datastore, optionally clearing
	# all of the values currently set in the datastore.
	#
	def import_defaults(clear_datastore = true)
		# Clear the datastore if the caller asked us to
		self.datastore.clear if clear_datastore

		self.datastore.import_options(self.options, 'self', true)

		# If there are default options, import their values into the datastore
		if (module_info['DefaultOptions'])
			self.datastore.import_options_from_hash(module_info['DefaultOptions'], true, 'self')
		end
	end

	#
	# This method ensures that the options associated with this module all
	# have valid values according to each required option in the option
	# container.
	#
	def validate
		self.options.validate(self.datastore)
	end

	#
	# Returns true if this module is being debugged.  The debug flag is set
	# by setting datastore['DEBUG'] to 1|true|yes
	#
	def debugging?
		(datastore['DEBUG'] || '') =~ /^(1|t|y)/i
	end

	##
	#
	# Just some handy quick checks
	#
	##

	#
	# Returns true if this module is an exploit module.
	#
	def exploit?
		return (type == MODULE_EXPLOIT)
	end

	#
	# Returns true if this module is a payload module.
	#
	def payload?
		return (type == MODULE_PAYLOAD)
	end

	#
	# Returns true if this module is an encoder module.
	#
	def encoder?
		return (type == MODULE_ENCODER)
	end

	#
	# Returns true if this module is a nop module.
	#
	def nop?
		return (type == MODULE_NOP)
	end

	#
	# Returns true if this module is an auxiliary module.
	#
	def auxiliary?
		return (type == MODULE_AUX)
	end

	#
	# Returns false since this is the real module
	#
	def self.cached?
		false
	end

	#
	# Read a value from the module store
	#
	def [](k)
		self.module_store[k]
	end

	#
	# Store a value into the module
	#
	def []=(k,v)
		self.module_store[k] = v
	end

	#
	# The array of zero or more authors.
	#
	attr_reader   :author
	#
	# The array of zero or more architectures.
	#
	attr_reader   :arch
	#
	# The array of zero or more platforms.
	#
	attr_reader   :platform
	#
	# The reference count for the module.
	#
	attr_reader   :references
	#
	# The module-specific datastore instance.
	#
	attr_reader   :datastore
	#
	# The module-specific options.
	#
	attr_reader   :options
	#
	# Whether or not this module requires privileged access.
	#
	attr_reader   :privileged
	#
	# The license under which this module is provided.
	#
	attr_reader   :license

	#
	# The job identifier that this module is running as, if any.
	#
	attr_accessor :job_id

	#
	# A generic hash used for passing additional information to modules
	#
	attr_accessor :module_store

	#
	# The last exception to occur using this module
	#
	attr_accessor :error

	#
	# A unique identifier for this module instance
	#
	attr_reader :uuid

protected
	attr_writer :uuid
	def generate_uuid
		self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase
	end
	#
	# The list of options that support merging in an information hash.
	#
	UpdateableOptions = [ "Name", "Description", "Alias", "PayloadCompat" ]

	#
	# Sets the modules unsupplied info fields to their default values.
	#
	def set_defaults
		self.module_info = {
			'Name'        => 'No module name',
			'Description' => 'No module description',
			'Version'     => '0',
			'Author'      => nil,
			'Arch'        => nil, # No architectures by default.
			'Platform'    => [],  # No platforms by default.
			'Ref'         => nil,
			'Privileged'  => false,
			'License'     => MSF_LICENSE,
		}.update(self.module_info)
		self.module_store = {}
	end

	#
	# This method initializes the module's compatibility hashes by normalizing
	# them into one single hash.  As it stands, modules can define
	# compatibility in their supplied info hash through:
	#
	#   Compat        - direct compat definitions
	#   PayloadCompat - payload compatibilities
	#   EncoderCompat - encoder compatibilities
	#   NopCompat     - nop compatibilities
	#
	# In the end, the module specific compatibilities are merged as sub-hashes
	# of the primary Compat hash key to make checks more uniform.
	#
	def init_compat
		c = module_info['Compat']

		if (c == nil)
			c = module_info['Compat'] = Hash.new
		end

		# Initialize the module sub compatibilities
		c['Payload'] = Hash.new if (c['Payload'] == nil)
		c['Encoder'] = Hash.new if (c['Encoder'] == nil)
		c['Nop']     = Hash.new if (c['Nop'] == nil)

		# Update the compat-derived module specific compatibilities from
		# the specific ones to make a uniform view of compatibilities
		c['Payload'].update(module_info['PayloadCompat'] || {})
		c['Encoder'].update(module_info['EncoderCompat'] || {})
		c['Nop'].update(module_info['NopCompat'] || {})
	end

	#
	# Register options with a specific owning class.
	#
	def info_fixups
		# Each reference should be an array consisting of two elements
		refs = module_info['References']
		if(refs and not refs.empty?)
			refs.each_index do |i|
				if !(refs[i].respond_to?('[]') and refs[i].length == 2)
					refs[i] = nil
				end
			end

			# Purge invalid references
			refs.delete(nil)
		end
	end

	#
	# Register options with a specific owning class.
	#
	def register_options(options, owner = self.class)
		self.options.add_options(options, owner)
		self.datastore.import_options(self.options, 'self', true)
		import_defaults(false)
	end

	#
	# Register advanced options with a specific owning class.
	#
	def register_advanced_options(options, owner = self.class)
		self.options.add_advanced_options(options, owner)
		self.datastore.import_options(self.options, 'self', true)
		import_defaults(false)
	end

	#
	# Register evasion options with a specific owning class.
	#
	def register_evasion_options(options, owner = self.class)
		self.options.add_evasion_options(options, owner)
		self.datastore.import_options(self.options, 'self', true)
		import_defaults(false)
	end

	#
	# Removes the supplied options from the module's option container
	# and data store.
	#
	def deregister_options(*names)
		names.each { |name|
			self.options.remove_option(name)
			self.datastore.delete(name)
		}
	end

	#
	# Checks to see if a derived instance of a given module implements a method
	# beyond the one that is provided by a base class.  This is a pretty lame
	# way of doing it, but I couldn't find a better one, so meh.
	#
	def derived_implementor?(parent, method_name)
		(self.method(method_name).to_s.match(/#{parent}[^:]/)) ? false : true
	end

	#
	# Merges options in the info hash in a sane fashion, as some options
	# require special attention.
	#
	def merge_info(info, opts)
		opts.each_pair { |name, val|
			merge_check_key(info, name, val)
		}

		return info
	end

	#
	# Updates information in the supplied info hash and merges other
	# information.  This method is used to override things like Name, Version,
	# and Description without losing the ability to merge architectures,
	# platforms, and options.
	#
	def update_info(info, opts)
		opts.each_pair { |name, val|
			# If the supplied option name is one of the ones that we should
			# override by default
			if (UpdateableOptions.include?(name) == true)
				# Only if the entry is currently nil do we use our value
				if (info[name] == nil)
					info[name] = val
				end
			# Otherwise, perform the merge operation like normal
			else
				merge_check_key(info, name, val)
			end
		}

		return info
	end

	#
	# Checks and merges the supplied key/value pair in the supplied hash.
	#
	def merge_check_key(info, name, val)
		if (self.respond_to?("merge_info_#{name.downcase}"))
			eval("merge_info_#{name.downcase}(info, val)")
		else
			# If the info hash already has an entry for this name
			if (info[name])
				# If it's not an array, convert it to an array and merge the
				# two
				if (info[name].kind_of?(Array) == false)
					curr       = info[name]
					info[name] = [ curr ]
				end

				# If the value being merged is an array, add each one
				if (val.kind_of?(Array) == true)
					val.each { |v|
						if (info[name].include?(v) == false)
							info[name] << v
						end
					}
				# Otherwise just add the value
				elsif (info[name].include?(val) == false)
					info[name] << val
				end
			# Otherwise, just set the value equal if no current value
			# exists
			else
				info[name] = val
			end
		end
	end

	#
	# Merge aliases with an underscore delimiter.
	#
	def merge_info_alias(info, val)
		merge_info_string(info, 'Alias', val, '_')
	end

	#
	# Merges the module name.
	#
	def merge_info_name(info, val)
		merge_info_string(info, 'Name', val, ', ', true)
	end

	#
	# Merges the module description.
	#
	def merge_info_description(info, val)
		merge_info_string(info, 'Description', val)
	end

	#
	# Merge the module version.
	#
	def merge_info_version(info, val)
		merge_info_string(info, 'Version', val)
	end

	#
	# Merges a given key in the info hash with a delimiter.
	#
	def merge_info_string(info, key, val, delim = ', ', inverse = false)
		if (info[key])
			if (inverse == true)
				info[key] = info[key] + delim + val
			else
				info[key] = val + delim + info[key]
			end
		else
			info[key] = val
		end
	end

	#
	# Merges options.
	#
	def merge_info_options(info, val, advanced = false, evasion = false)

		key_name = ((advanced) ? 'Advanced' : (evasion) ? 'Evasion' : '') + 'Options'

		new_cont = OptionContainer.new
		new_cont.add_options(val, advanced, evasion)
		cur_cont = OptionContainer.new
		cur_cont.add_options(info[key_name] || [], advanced, evasion)

		new_cont.each_option { |name, option|
			next if (cur_cont.get(name))

			info[key_name]  = [] if (!info[key_name])
			info[key_name] << option
		}
	end

	#
	# Merges advanced options.
	#
	def merge_info_advanced_options(info, val)
		merge_info_options(info, val, true, false)
	end

	#
	# Merges advanced options.
	#
	def merge_info_evasion_options(info, val)
		merge_info_options(info, val, false, true)
	end

	attr_accessor :module_info # :nodoc:
	attr_writer   :author, :arch, :platform, :references, :datastore, :options # :nodoc:
	attr_writer   :privileged # :nodoc:
	attr_writer   :license # :nodoc:

end

#
# Alias the data types so people can reference them just by Msf:: and not
# Msf::Module::
#
Author = Msf::Module::Author
Reference = Msf::Module::Reference
SiteReference = Msf::Module::SiteReference
Platform = Msf::Module::Platform
Target = Msf::Module::Target

end

