Merge branch 'bug/wrong-file_changed-argument' into rapid7
[Closes #965]
This commit is contained in:
+7
-5
@@ -182,12 +182,14 @@ class FastLib
|
||||
end
|
||||
|
||||
|
||||
# This method provides a way to create a FASTLIB archive programatically.
|
||||
#
|
||||
# This method provides a way to create a FASTLIB archive programatically,
|
||||
# the key arguments are the name of the destination archive, the base
|
||||
# directory that should be excluded from the archived path, and finally
|
||||
# the list of specific files and directories to include in the archive.
|
||||
#
|
||||
# @param [String] lib the output path for the archive
|
||||
# @param [String] flag a string containing the hex values for the flags ({FLAG_COMPRESS} and {FLAG_ENCRYPT}).
|
||||
# @param [String] bdir the path to the base directory which will be stripped from all paths included in the archive
|
||||
# @param [Array<String>] dirs list of directories/files to pack into the archive. All dirs should be under bdir so
|
||||
# that the paths are stripped correctly.
|
||||
# @return [void]
|
||||
def self.dump(lib, flag, bdir, *dirs)
|
||||
head = ""
|
||||
data = ""
|
||||
|
||||
@@ -26,11 +26,11 @@ module Msf::ModuleManager::Loading
|
||||
def file_changed?(path)
|
||||
changed = false
|
||||
|
||||
module_details = self.module_info_by_path[path]
|
||||
module_info = self.module_info_by_path[path]
|
||||
|
||||
# if uncached then it counts as changed
|
||||
# Payloads can't be cached due to stage/stager matching
|
||||
if module_details.nil? or module_details[:mtype] == Msf::MODULE_PAYLOAD
|
||||
if module_info.nil? or module_info[:type] == Msf::MODULE_PAYLOAD
|
||||
changed = true
|
||||
else
|
||||
begin
|
||||
@@ -39,7 +39,7 @@ module Msf::ModuleManager::Loading
|
||||
# if the file does not exist now, that's a change
|
||||
changed = true
|
||||
else
|
||||
cached_modification_time = module_details[:mtime].to_i
|
||||
cached_modification_time = module_info[:modification_time].to_i
|
||||
|
||||
# if the file's modification time's different from the cache, then it's changed
|
||||
if current_modification_time != cached_modification_time
|
||||
|
||||
@@ -41,7 +41,7 @@ class Msf::Modules::Loader::Archive < Msf::Modules::Loader::Base
|
||||
type = entry.split('/', 2)[0]
|
||||
type = type.singularize
|
||||
|
||||
unless module_manager.enablement_by_type[type]
|
||||
unless module_manager.type_enabled?(type)
|
||||
next
|
||||
end
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#
|
||||
require 'msf/core/modules/loader'
|
||||
require 'msf/core/modules/namespace'
|
||||
require 'msf/core/modules/version_compatibility_error'
|
||||
|
||||
# Responsible for loading modules for {Msf::ModuleManager}.
|
||||
#
|
||||
@@ -77,7 +78,6 @@ class Msf::Modules::Loader::Base
|
||||
raise ::NotImplementedError
|
||||
end
|
||||
|
||||
|
||||
# Loads a module from the supplied path and module_reference_name.
|
||||
#
|
||||
# @param [String] parent_path The path under which the module exists. This is not necessarily the same path as passed
|
||||
@@ -104,14 +104,18 @@ class Msf::Modules::Loader::Base
|
||||
force = options[:force] || false
|
||||
reload = options[:reload] || false
|
||||
|
||||
unless force or module_manager.file_changed?(parent_path)
|
||||
dlog("Cached module from #{parent_path} has not changed.", 'core', LEV_2)
|
||||
module_path = self.module_path(parent_path, type, module_reference_name)
|
||||
file_changed = module_manager.file_changed?(module_path)
|
||||
|
||||
unless force or file_changed
|
||||
dlog("Cached module from #{module_path} has not changed.", 'core', LEV_2)
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
reload ||= force || file_changed
|
||||
|
||||
metasploit_class = nil
|
||||
module_path = self.module_path(parent_path, type, module_reference_name)
|
||||
|
||||
loaded = namespace_module_transaction(type + "/" + module_reference_name, :reload => reload) { |namespace_module|
|
||||
# set the parent_path so that the module can be reloaded with #load_module
|
||||
@@ -121,7 +125,7 @@ class Msf::Modules::Loader::Base
|
||||
|
||||
begin
|
||||
namespace_module.module_eval_with_lexical_scope(module_content, module_path)
|
||||
# handle interrupts as pass-throughs unlike other Exceptions
|
||||
# handle interrupts as pass-throughs unlike other Exceptions so users can bail with Ctrl+C
|
||||
rescue ::Interrupt
|
||||
raise
|
||||
rescue ::Exception => error
|
||||
@@ -191,6 +195,11 @@ class Msf::Modules::Loader::Base
|
||||
# not trigger an ambiguous name warning, which would cause the reloaded module to not be stored in the
|
||||
# ModuleManager.
|
||||
module_manager.delete(module_reference_name)
|
||||
|
||||
# Delete the original copy of the module in the type-specific module set stores the reloaded module and doesn't
|
||||
# trigger an ambiguous name warning
|
||||
module_set = module_manager.module_set(type)
|
||||
module_set.delete(module_reference_name)
|
||||
end
|
||||
|
||||
# Do some processing on the loaded module to get it into the right associations
|
||||
@@ -290,7 +299,6 @@ class Msf::Modules::Loader::Base
|
||||
|
||||
dlog("Reloading module #{module_reference_name}...", 'core')
|
||||
|
||||
|
||||
if load_module(parent_path, type, module_reference_name, :force => true, :reload => true)
|
||||
# Create a new instance of the module
|
||||
reloaded_module_instance = module_manager.create(module_reference_name)
|
||||
@@ -486,20 +494,18 @@ class Msf::Modules::Loader::Base
|
||||
elog("Reloading namespace_module #{previous_namespace_module} when :reload => false")
|
||||
end
|
||||
|
||||
relative_name = namespace_module_names.last
|
||||
|
||||
if previous_namespace_module
|
||||
parent_module = previous_namespace_module.parent
|
||||
relative_name = namespace_module_names.last
|
||||
|
||||
# remove_const is private, so invoke in instance context
|
||||
parent_module.instance_eval do
|
||||
remove_const relative_name
|
||||
end
|
||||
else
|
||||
parent_module = nil
|
||||
relative_name = namespace_module_names.last
|
||||
# remove_const is private, so use send to bypass
|
||||
parent_module.send(:remove_const, relative_name)
|
||||
end
|
||||
|
||||
namespace_module = create_namespace_module(namespace_module_names)
|
||||
# Get the parent module from the created module so that restore_namespace_module can remove namespace_module's
|
||||
# constant if needed.
|
||||
parent_module = namespace_module.parent
|
||||
|
||||
begin
|
||||
loaded = block.call(namespace_module)
|
||||
@@ -534,19 +540,29 @@ class Msf::Modules::Loader::Base
|
||||
#
|
||||
# @param [Module] parent_module The .parent of namespace_module before it was removed from the constant tree.
|
||||
# @param [String] relative_name The name of the constant under parent_module where namespace_module was attached.
|
||||
# @param [Module] namespace_module The previous namespace module containing the old module content.
|
||||
# @param [Module, nil] namespace_module The previous namespace module containing the old module content. If `nil`,
|
||||
# then the relative_name constant is removed from parent_module, but nothing is set as the new constant.
|
||||
# @return [void]
|
||||
def restore_namespace_module(parent_module, relative_name, namespace_module)
|
||||
if parent_module and namespace_module
|
||||
# the const may have been redefined by {#create_namespace_module}, in which case that new namespace_module needs
|
||||
# to be removed so the original can replace it.
|
||||
if parent_module.const_defined? relative_name
|
||||
parent_module.instance_eval do
|
||||
remove_const relative_name
|
||||
end
|
||||
end
|
||||
if parent_module
|
||||
# If there is a current module with relative_name
|
||||
if parent_module.const_defined?(relative_name)
|
||||
# if the current value isn't the value to be restored.
|
||||
if parent_module.const_get(relative_name) != namespace_module
|
||||
# remove_const is private, so use send to bypass
|
||||
parent_module.send(:remove_const, relative_name)
|
||||
|
||||
parent_module.const_set(relative_name, namespace_module)
|
||||
# if there was a previous module, not set it to the name
|
||||
if namespace_module
|
||||
parent_module.const_set(relative_name, namespace_module)
|
||||
end
|
||||
end
|
||||
else
|
||||
# if there was a previous module, but there isn't a current module, then restore the previous module
|
||||
if namespace_module
|
||||
parent_module.const_set(relative_name, namespace_module)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
describe FastLib do
|
||||
let(:archived_paths) do
|
||||
[
|
||||
File.join('auxiliary', 'scanner', 'portscan', 'xmas.rb'),
|
||||
File.join('exploits', 'windows', 'smb', 'ms08_067_netapi.rb')
|
||||
]
|
||||
end
|
||||
|
||||
let(:base_path) do
|
||||
File.join(Msf::Config.install_root, 'modules')
|
||||
end
|
||||
|
||||
let(:extension) do
|
||||
'.fastlib'
|
||||
end
|
||||
|
||||
let(:flag_compress) do
|
||||
0x01
|
||||
end
|
||||
|
||||
let(:flag_encrypt) do
|
||||
0x02
|
||||
end
|
||||
|
||||
let(:unarchived_paths) do
|
||||
archived_paths.collect { |archived_path|
|
||||
File.join(base_path, archived_path)
|
||||
}
|
||||
end
|
||||
|
||||
context 'CONSTANTS' do
|
||||
context 'flags' do
|
||||
it 'should have compression' do
|
||||
described_class::FLAG_COMPRESS.should == flag_compress
|
||||
end
|
||||
|
||||
it 'should have encryption' do
|
||||
described_class::FLAG_ENCRYPT.should == flag_encrypt
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'class methods' do
|
||||
context 'dump' do
|
||||
let(:flag_string) do
|
||||
flags.to_s(16)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
FastLib.cache.clear
|
||||
end
|
||||
|
||||
around(:each) do |example|
|
||||
Dir.mktmpdir do |directory|
|
||||
@destination_path = File.join(directory, "rspec#{extension}")
|
||||
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
context 'without compression and without encryption' do
|
||||
let(:flags) do
|
||||
0x0
|
||||
end
|
||||
|
||||
it 'should create an archive' do
|
||||
File.exist?(@destination_path).should be_false
|
||||
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
|
||||
File.exist?(@destination_path).should be_true
|
||||
end
|
||||
|
||||
context 'cache' do
|
||||
it 'should populate' do
|
||||
FastLib.cache[@destination_path].should be_nil
|
||||
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
|
||||
FastLib.cache[@destination_path].should be_a Hash
|
||||
end
|
||||
|
||||
it 'should include flags' do
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
|
||||
FastLib.cache[@destination_path][:fastlib_flags].should == flags
|
||||
end
|
||||
|
||||
pending "Fix https://www.pivotaltracker.com/story/show/38730815" do
|
||||
it 'should include header' do
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
header = FastLib.cache[@destination_path][:fastlib_header]
|
||||
modification_time = File.mtime(@destination_path).utc.to_i
|
||||
|
||||
header.should be_a Array
|
||||
# @todo figure out why 12 before header length
|
||||
header[0].should == 12
|
||||
# @todo figure out why header length is 0
|
||||
header[1].should == 0
|
||||
header[2].should == modification_time
|
||||
end
|
||||
|
||||
it 'should include archived paths' do
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
cache = FastLib.cache[@destination_path]
|
||||
|
||||
archived_path = File.join('exploits', 'windows', 'smb', 'ms08_067_netapi.rb')
|
||||
unarchived_path = File.join(base_path, archived_path)
|
||||
|
||||
# make sure that the unarchived module exists and hasn't be deleted or renamed before expecting it to be
|
||||
# in the archive.
|
||||
File.exist?(unarchived_path).should be_true
|
||||
cache[archived_path].should_not be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'with compression and without encryption' do
|
||||
let(:flags) do
|
||||
flag_compress
|
||||
end
|
||||
|
||||
it 'should create an archive' do
|
||||
File.exist?(@destination_path).should be_false
|
||||
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
|
||||
File.exist?(@destination_path).should be_true
|
||||
end
|
||||
|
||||
it 'should be smaller than the uncompressed archive' do
|
||||
uncompressed_path = "#{@destination_path}.uncompressed"
|
||||
compressed_path = "#{@destination_path}.compressed"
|
||||
|
||||
File.exist?(uncompressed_path).should be_false
|
||||
File.exist?(compressed_path).should be_false
|
||||
|
||||
described_class.dump(uncompressed_path, '', base_path, *unarchived_paths)
|
||||
described_class.dump(compressed_path, flag_string, base_path, *unarchived_paths)
|
||||
|
||||
File.exist?(uncompressed_path).should be_true
|
||||
File.exist?(compressed_path).should be_true
|
||||
|
||||
File.size(compressed_path).should < File.size(uncompressed_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without compression and with encryption' do
|
||||
let(:flags) do
|
||||
flag_encrypt
|
||||
end
|
||||
|
||||
it 'should create an archive' do
|
||||
File.exist?(@destination_path).should be_false
|
||||
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
|
||||
File.exist?(@destination_path).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with compression and with encryption' do
|
||||
let(:flags) do
|
||||
flag_compress | flag_encrypt
|
||||
end
|
||||
|
||||
it 'should create an archive' do
|
||||
File.exist?(@destination_path).should be_false
|
||||
|
||||
described_class.dump(@destination_path, flag_string, base_path, *unarchived_paths)
|
||||
|
||||
File.exist?(@destination_path).should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'list' do
|
||||
around(:each) do |example|
|
||||
Dir.mktmpdir do |directory|
|
||||
@destination_path = File.join(directory, "rspec#{extension}")
|
||||
|
||||
FastLib.dump(@destination_path, FastLib::FLAG_COMPRESS.to_s, base_path, *unarchived_paths)
|
||||
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
# ensure modules expected to be listed actually exist
|
||||
it 'should use existent unarchived modules' do
|
||||
unarchived_paths.each do |unarchived_path|
|
||||
File.exist?(unarchived_path).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cached dump', :pending => "Fix https://www.pivotaltracker.com/story/show/38730815" do
|
||||
it 'should have dump cached' do
|
||||
FastLib.cache[@destination_path].should_not be_nil
|
||||
end
|
||||
|
||||
it 'should list archived paths' do
|
||||
paths = FastLib.list(@destination_path)
|
||||
|
||||
paths.length.should == archived_paths.length
|
||||
paths.should == archived_paths
|
||||
end
|
||||
end
|
||||
|
||||
context 'without cached dump' do
|
||||
before(:each) do
|
||||
FastLib.cache.clear
|
||||
end
|
||||
|
||||
it 'should not have dump cache' do
|
||||
FastLib.cache[@destination_path].should be_nil
|
||||
end
|
||||
|
||||
it 'should list archived paths' do
|
||||
paths = FastLib.list(@destination_path)
|
||||
|
||||
paths.length.should == archived_paths.length
|
||||
paths.should == archived_paths
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -107,4 +107,82 @@ describe Msf::ModuleManager do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#file_changed?' do
|
||||
let(:module_basename) do
|
||||
[basename_prefix, '.rb']
|
||||
end
|
||||
|
||||
it 'should return true if module info is not cached' do
|
||||
Tempfile.open(module_basename) do |tempfile|
|
||||
module_path = tempfile.path
|
||||
|
||||
subject.send(:module_info_by_path)[module_path].should be_nil
|
||||
subject.file_changed?(module_path).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return true if the cached type is Msf::MODULE_PAYLOAD' do
|
||||
Tempfile.open(module_basename) do |tempfile|
|
||||
module_path = tempfile.path
|
||||
modification_time = File.mtime(module_path)
|
||||
|
||||
subject.send(:module_info_by_path)[module_path] = {
|
||||
# :modification_time must match so that it is the :type that is causing the `true` and not the
|
||||
# :modification_time causing the `true`.
|
||||
:modification_time => modification_time,
|
||||
:type => Msf::MODULE_PAYLOAD
|
||||
}
|
||||
|
||||
subject.file_changed?(module_path).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with cache module info and not a payload module' do
|
||||
it 'should return true if the file does not exist on the file system' do
|
||||
tempfile = Tempfile.new(module_basename)
|
||||
module_path = tempfile.path
|
||||
modification_time = File.mtime(module_path).to_i
|
||||
|
||||
subject.send(:module_info_by_path)[module_path] = {
|
||||
:modification_time => modification_time
|
||||
}
|
||||
|
||||
tempfile.unlink
|
||||
|
||||
File.exist?(module_path).should be_false
|
||||
subject.file_changed?(module_path).should be_true
|
||||
end
|
||||
|
||||
it 'should return true if modification time does not match the cached modification time' do
|
||||
Tempfile.open(module_basename) do |tempfile|
|
||||
module_path = tempfile.path
|
||||
modification_time = File.mtime(module_path).to_i
|
||||
cached_modification_time = (modification_time * rand).to_i
|
||||
|
||||
subject.send(:module_info_by_path)[module_path] = {
|
||||
:modification_time => cached_modification_time
|
||||
}
|
||||
|
||||
cached_modification_time.should_not == modification_time
|
||||
subject.file_changed?(module_path).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return false if modification time does match the cached modification time' do
|
||||
Tempfile.open(module_basename) do |tempfile|
|
||||
module_path = tempfile.path
|
||||
modification_time = File.mtime(module_path).to_i
|
||||
cached_modification_time = modification_time
|
||||
|
||||
subject.send(:module_info_by_path)[module_path] = {
|
||||
:modification_time => cached_modification_time
|
||||
}
|
||||
|
||||
cached_modification_time.should == modification_time
|
||||
subject.file_changed?(module_path).should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,275 @@
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
describe Msf::Modules::Loader::Archive do
|
||||
let(:archive_extension) do
|
||||
'.fastlib'
|
||||
end
|
||||
|
||||
context 'CONSTANTS' do
|
||||
it 'should have extension' do
|
||||
described_class::ARCHIVE_EXTENSION.should == archive_extension
|
||||
end
|
||||
end
|
||||
|
||||
context 'instance methods' do
|
||||
let(:enabled_type) do
|
||||
'exploit'
|
||||
end
|
||||
|
||||
let(:enabled_type_directory) do
|
||||
'exploits'
|
||||
end
|
||||
|
||||
let(:framework) do
|
||||
mock('Framework')
|
||||
end
|
||||
|
||||
let(:module_extension) do
|
||||
'.rb'
|
||||
end
|
||||
|
||||
let(:module_manager) do
|
||||
# DO NOT mock module_manager to ensure that no protected methods are being called.
|
||||
Msf::ModuleManager.new(framework, [enabled_type])
|
||||
end
|
||||
|
||||
let(:module_reference_name) do
|
||||
'module/reference/name'
|
||||
end
|
||||
|
||||
subject do
|
||||
described_class.new(module_manager)
|
||||
end
|
||||
|
||||
context '#each_module_reference_name' do
|
||||
let(:disabled_module_content) do
|
||||
<<-EOS
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
let(:disabled_type) do
|
||||
'auxiliary'
|
||||
end
|
||||
|
||||
let(:disabled_type_directory) do
|
||||
'auxiliary'
|
||||
end
|
||||
|
||||
let(:enabled_module_content) do
|
||||
<<-EOS
|
||||
class Metasploit3 < Msf::Exploit::Remote
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
around(:each) do |example|
|
||||
Dir.mktmpdir do |directory|
|
||||
@base_path = directory
|
||||
|
||||
# make a .svn directory to be ignored
|
||||
subversion_path = File.join(@base_path, '.svn')
|
||||
FileUtils.mkdir_p subversion_path
|
||||
|
||||
# make a type directory that should be ignored because it's not enabled
|
||||
disabled_type_path = File.join(@base_path, disabled_type_directory)
|
||||
FileUtils.mkdir_p disabled_type_path
|
||||
|
||||
#
|
||||
# create a valid module in the disabled type directory to make sure it's the enablement that's preventing the
|
||||
# yield
|
||||
#
|
||||
|
||||
disabled_module_path = File.join(disabled_type_path, "#{disabled_type}#{module_extension}")
|
||||
|
||||
File.open(disabled_module_path, 'wb') do |f|
|
||||
f.write(disabled_module_content)
|
||||
end
|
||||
|
||||
# make a type directory that should not be ignored because it is enabled
|
||||
enabled_module_path = File.join(
|
||||
@base_path,
|
||||
enabled_type_directory,
|
||||
"#{module_reference_name}#{module_extension}"
|
||||
)
|
||||
enabled_module_directory = File.dirname(enabled_module_path)
|
||||
FileUtils.mkdir_p enabled_module_directory
|
||||
|
||||
File.open(enabled_module_path, 'wb') do |f|
|
||||
f.write(enabled_module_content)
|
||||
end
|
||||
|
||||
Dir.mktmpdir do |archive_directory|
|
||||
@archive_path = File.join(archive_directory, "rspec#{archive_extension}")
|
||||
FastLib.dump(@archive_path, FastLib::FLAG_COMPRESS.to_s(16), @base_path, @base_path)
|
||||
|
||||
# @todo Fix https://www.pivotaltracker.com/story/show/38730815 and the cache won't need to be cleared as a work-around
|
||||
FastLib.cache.clear
|
||||
|
||||
example.run
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# this checks that the around(:each) is working
|
||||
it 'should have an existent FastLib' do
|
||||
File.exist?(@archive_path).should be_true
|
||||
end
|
||||
|
||||
it 'should ignore .svn directories' do
|
||||
subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name|
|
||||
parent_path.should_not include('.svn')
|
||||
end
|
||||
end
|
||||
|
||||
it 'should ignore types that are not enabled' do
|
||||
module_manager.type_enabled?(disabled_type).should be_false
|
||||
|
||||
subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name|
|
||||
type.should_not == disabled_type
|
||||
end
|
||||
end
|
||||
|
||||
it 'should yield (parent_path, type, module_reference_name) with parent_path equal to the archive path' do
|
||||
subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name|
|
||||
parent_path.should == @archive_path
|
||||
end
|
||||
end
|
||||
|
||||
it 'should yield (parent_path, type, module_reference_name) with type equal to enabled type' do
|
||||
module_manager.type_enabled?(enabled_type).should be_true
|
||||
|
||||
subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name|
|
||||
type.should == enabled_type
|
||||
end
|
||||
end
|
||||
|
||||
it 'should yield (path, type, module_reference_name) with module_reference_name without extension' do
|
||||
subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name|
|
||||
module_reference_name.should_not match(/#{Regexp.escape(module_extension)}$/)
|
||||
module_reference_name.should == module_reference_name
|
||||
end
|
||||
end
|
||||
|
||||
# ensure that the block is actually being run so that shoulds in the block aren't just being skipped
|
||||
it 'should yield the correct number of tuples' do
|
||||
actual_count = 0
|
||||
|
||||
subject.send(:each_module_reference_name, @archive_path) do |parent_path, type, module_reference_name|
|
||||
actual_count += 1
|
||||
end
|
||||
|
||||
actual_count.should == 1
|
||||
end
|
||||
end
|
||||
|
||||
context '#loadable?' do
|
||||
it 'should return true if the path has ARCHIVE_EXTENSION as file extension' do
|
||||
path = "path/to/archive#{archive_extension}"
|
||||
|
||||
File.extname(path).should == described_class::ARCHIVE_EXTENSION
|
||||
subject.loadable?(path).should be_true
|
||||
end
|
||||
|
||||
it 'should return false if the path contains ARCHIVE_EXTENSION, but it is not the file extension' do
|
||||
path = "path/to/archive#{archive_extension}.bak"
|
||||
|
||||
path.should include(described_class::ARCHIVE_EXTENSION)
|
||||
File.extname(path).should_not == described_class::ARCHIVE_EXTENSION
|
||||
subject.loadable?(path).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context '#module_path' do
|
||||
let(:parent_path) do
|
||||
"path/to/archive#{archive_extension}"
|
||||
end
|
||||
|
||||
let(:type) do
|
||||
'exploit'
|
||||
end
|
||||
|
||||
let(:type_directory) do
|
||||
'exploits'
|
||||
end
|
||||
|
||||
it 'should use typed_path to convert the type name to a type directory' do
|
||||
subject.should_receive(:typed_path).with(type, module_reference_name)
|
||||
|
||||
subject.send(:module_path, parent_path, type, module_reference_name)
|
||||
end
|
||||
|
||||
it "should separate the archive path from the entry path with '::'" do
|
||||
module_path = subject.send(:module_path, parent_path, type, module_reference_name)
|
||||
|
||||
module_path.should == "#{parent_path}::#{type_directory}/#{module_reference_name}.rb"
|
||||
end
|
||||
end
|
||||
|
||||
context '#read_module_path' do
|
||||
let(:module_reference_name) do
|
||||
'windows/smb/ms08_067_netapi'
|
||||
end
|
||||
|
||||
let(:type) do
|
||||
enabled_type
|
||||
end
|
||||
|
||||
let(:type_directory) do
|
||||
enabled_type_directory
|
||||
end
|
||||
|
||||
let(:archived_path) do
|
||||
File.join(type_directory, "#{module_reference_name}#{module_extension}")
|
||||
end
|
||||
|
||||
let(:base_path) do
|
||||
File.join(Msf::Config.install_root, 'modules')
|
||||
end
|
||||
|
||||
let(:flag_string) do
|
||||
flags.to_s(16)
|
||||
end
|
||||
|
||||
let(:flags) do
|
||||
0x0
|
||||
end
|
||||
|
||||
let(:unarchived_path) do
|
||||
File.join(base_path, archived_path)
|
||||
end
|
||||
|
||||
it 'should read modules that exist' do
|
||||
File.exist?(unarchived_path).should be_true
|
||||
end
|
||||
|
||||
around(:each) do |example|
|
||||
Dir.mktmpdir do |directory|
|
||||
@parent_path = File.join(directory, 'rspec.fastlib')
|
||||
|
||||
FastLib.dump(@parent_path, flag_string, base_path, unarchived_path)
|
||||
|
||||
# @todo Fix https://www.pivotaltracker.com/story/show/38730815 so cache from dump is correct
|
||||
FastLib.cache.clear
|
||||
|
||||
example.run
|
||||
end
|
||||
end
|
||||
|
||||
context 'with uncompressed archive' do
|
||||
it_should_behave_like 'Msf::Modules::Loader::Archive#read_module_content'
|
||||
end
|
||||
|
||||
context 'with compressed archive' do
|
||||
let(:flags) do
|
||||
FastLib::FLAG_COMPRESS
|
||||
end
|
||||
|
||||
it_should_behave_like 'Msf::Modules::Loader::Archive#read_module_content'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,1423 @@
|
||||
require 'spec_helper'
|
||||
|
||||
require 'msf/core'
|
||||
|
||||
describe Msf::Modules::Loader::Base do
|
||||
let(:described_class_pathname) do
|
||||
root_pathname.join('lib', 'msf', 'core', 'modules', 'loader', 'base.rb')
|
||||
end
|
||||
|
||||
let(:malformed_module_content) do
|
||||
<<-EOS
|
||||
class Metasploit3
|
||||
# purposeful typo to check that module path is used in backtrace
|
||||
inclde Exploit::Remote::Tcp
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
let(:module_content) do
|
||||
<<-EOS
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
# fully-qualified name is Msf::GoodRanking, so this will failing if lexical scope is not captured
|
||||
Rank = GoodRanking
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
let(:module_full_name) do
|
||||
"#{type}/#{module_reference_name}"
|
||||
end
|
||||
|
||||
let(:module_path) do
|
||||
parent_pathname.join('auxiliary', 'rspec', 'mock.rb').to_s
|
||||
end
|
||||
|
||||
let(:module_reference_name) do
|
||||
'rspec/mock'
|
||||
end
|
||||
|
||||
let(:parent_path) do
|
||||
parent_pathname.to_s
|
||||
end
|
||||
|
||||
let(:parent_pathname) do
|
||||
root_pathname.join('modules')
|
||||
end
|
||||
|
||||
let(:root_pathname) do
|
||||
Pathname.new(Msf::Config.install_root)
|
||||
end
|
||||
|
||||
let(:type) do
|
||||
Msf::MODULE_AUX
|
||||
end
|
||||
|
||||
context 'CONSTANTS' do
|
||||
|
||||
context 'DIRECTORY_BY_TYPE' do
|
||||
let(:directory_by_type) do
|
||||
described_class::DIRECTORY_BY_TYPE
|
||||
end
|
||||
|
||||
it 'should be defined' do
|
||||
described_class.const_defined?(:DIRECTORY_BY_TYPE).should be_true
|
||||
end
|
||||
|
||||
it 'should map Msf::MODULE_AUX to auxiliary' do
|
||||
directory_by_type[Msf::MODULE_AUX].should == 'auxiliary'
|
||||
end
|
||||
|
||||
it 'should map Msf::MODULE_ENCODER to encoders' do
|
||||
directory_by_type[Msf::MODULE_ENCODER].should == 'encoders'
|
||||
end
|
||||
|
||||
it 'should map Msf::MODULE_EXPLOIT to exploits' do
|
||||
directory_by_type[Msf::MODULE_EXPLOIT].should == 'exploits'
|
||||
end
|
||||
|
||||
it 'should map Msf::MODULE_NOP to nops' do
|
||||
directory_by_type[Msf::MODULE_NOP].should == 'nops'
|
||||
end
|
||||
|
||||
it 'should map Msf::MODULE_PAYLOAD to payloads' do
|
||||
directory_by_type[Msf::MODULE_PAYLOAD].should == 'payloads'
|
||||
end
|
||||
|
||||
it 'should map Msf::MODULE_POST to post' do
|
||||
directory_by_type[Msf::MODULE_POST].should == 'post'
|
||||
end
|
||||
end
|
||||
|
||||
context 'NAMESPACE_MODULE_LINE' do
|
||||
it 'should be line number for first line of NAMESPACE_MODULE_CONTENT' do
|
||||
file_lines = []
|
||||
|
||||
described_class_pathname.open do |f|
|
||||
file_lines = f.to_a
|
||||
end
|
||||
|
||||
# -1 because file lines are 1-based, but array is 0-based
|
||||
file_line = file_lines[described_class::NAMESPACE_MODULE_LINE - 1]
|
||||
|
||||
constant_lines = described_class::NAMESPACE_MODULE_CONTENT.lines.to_a
|
||||
constant_line = constant_lines.first
|
||||
|
||||
file_line.should == constant_line
|
||||
end
|
||||
end
|
||||
|
||||
context 'NAMESPACE_MODULE_CONTENT' do
|
||||
context 'derived module' do
|
||||
let(:namespace_module_names) do
|
||||
['Msf', 'Modules', 'Mod617578696c696172792f72737065632f6d6f636b']
|
||||
end
|
||||
|
||||
let(:namespace_module) do
|
||||
Object.module_eval(
|
||||
<<-EOS
|
||||
module #{namespace_module_names[0]}
|
||||
module #{namespace_module_names[1]}
|
||||
module #{namespace_module_names[2]}
|
||||
#{described_class::NAMESPACE_MODULE_CONTENT}
|
||||
end
|
||||
end
|
||||
end
|
||||
EOS
|
||||
)
|
||||
|
||||
namespace_module_names.join('::').constantize
|
||||
end
|
||||
|
||||
context 'loader' do
|
||||
it 'should be a read/write attribute' do
|
||||
loader = mock('Loader')
|
||||
namespace_module.loader = loader
|
||||
|
||||
namespace_module.loader.should == loader
|
||||
end
|
||||
end
|
||||
|
||||
context 'module_eval_with_lexical_scope' do
|
||||
it 'should capture the lexical scope' do
|
||||
expect {
|
||||
namespace_module.module_eval_with_lexical_scope(module_content, module_path)
|
||||
}.to_not raise_error(NameError)
|
||||
end
|
||||
|
||||
context 'with malformed module content' do
|
||||
it 'should use module path in module_eval' do
|
||||
error = nil
|
||||
|
||||
begin
|
||||
namespace_module.module_eval_with_lexical_scope(malformed_module_content, module_path)
|
||||
rescue NoMethodError => error
|
||||
# don't put the should in the rescue because if there is no error, then the example will still be
|
||||
# successful.
|
||||
end
|
||||
|
||||
error.should_not be_nil
|
||||
error.backtrace[0].should include(module_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'parent_path' do
|
||||
it 'should be a read/write attribute' do
|
||||
parent_path = mock('Parent Path')
|
||||
namespace_module.parent_path = parent_path
|
||||
|
||||
namespace_module.parent_path.should == parent_path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'MODULE_EXTENSION' do
|
||||
it 'should only support ruby source modules' do
|
||||
described_class::MODULE_EXTENSION.should == '.rb'
|
||||
end
|
||||
end
|
||||
|
||||
context 'MODULE_SEPARATOR' do
|
||||
it 'should make valid module names' do
|
||||
name = ['Msf', 'Modules'].join(described_class::MODULE_SEPARATOR)
|
||||
name.constantize.should == Msf::Modules
|
||||
end
|
||||
end
|
||||
|
||||
context 'NAMESPACE_MODULE_NAMES' do
|
||||
it 'should be under Msf so that Msf constants resolve from lexical scope' do
|
||||
described_class::NAMESPACE_MODULE_NAMES.should include('Msf')
|
||||
end
|
||||
|
||||
it "should not be directly under Msf so that modules don't collide with core namespaces" do
|
||||
direct_index = described_class::NAMESPACE_MODULE_NAMES.index('Msf')
|
||||
last_index = described_class::NAMESPACE_MODULE_NAMES.length - 1
|
||||
|
||||
last_index.should > direct_index
|
||||
end
|
||||
end
|
||||
|
||||
context 'UNIT_TEST_REGEX' do
|
||||
it 'should match test suite files' do
|
||||
described_class::UNIT_TEST_REGEX.should match('rb.ts.rb')
|
||||
end
|
||||
|
||||
it 'should match unit test files' do
|
||||
described_class::UNIT_TEST_REGEX.should match('rb.ut.rb')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'class methods' do
|
||||
context 'typed_path' do
|
||||
it 'should have MODULE_EXTENSION for the extension name' do
|
||||
typed_path = described_class.typed_path(Msf::MODULE_AUX, module_reference_name)
|
||||
|
||||
File.extname(typed_path).should == described_class::MODULE_EXTENSION
|
||||
end
|
||||
|
||||
# Don't iterate over a Hash here as that would too closely mirror the actual implementation and not test anything
|
||||
it_should_behave_like 'typed_path', 'Msf::MODULE_AUX' => 'auxiliary'
|
||||
it_should_behave_like 'typed_path', 'Msf::MODULE_ENCODER' => 'encoders'
|
||||
it_should_behave_like 'typed_path', 'Msf::MODULE_EXPLOIT' => 'exploits'
|
||||
it_should_behave_like 'typed_path', 'Msf::MODULE_NOP' => 'nops'
|
||||
it_should_behave_like 'typed_path', 'Msf::MODULE_PAYLOAD' => 'payloads'
|
||||
it_should_behave_like 'typed_path', 'Msf::MODULE_POST' => 'post'
|
||||
end
|
||||
end
|
||||
|
||||
context 'instance methods' do
|
||||
let(:module_manager) do
|
||||
mock('Module Manager')
|
||||
end
|
||||
|
||||
subject do
|
||||
described_class.new(module_manager)
|
||||
end
|
||||
|
||||
context '#initialize' do
|
||||
it 'should set @module_manager' do
|
||||
loader = described_class.new(module_manager)
|
||||
loader.instance_variable_get(:@module_manager).should == module_manager
|
||||
end
|
||||
end
|
||||
|
||||
context '#loadable?' do
|
||||
it 'should be abstract' do
|
||||
expect {
|
||||
subject.loadable?(parent_pathname.to_s)
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
context '#load_module' do
|
||||
let(:parent_path) do
|
||||
parent_pathname.to_s
|
||||
end
|
||||
|
||||
let(:type) do
|
||||
Msf::MODULE_AUX
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
subject.stub(:module_path => module_path)
|
||||
end
|
||||
|
||||
it 'should return false if :force is false and the file has not been changed' do
|
||||
module_manager.stub(:file_changed? => false)
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name, :force => false).should be_false
|
||||
end
|
||||
|
||||
it 'should call file_changed? with the module_path' do
|
||||
module_manager.should_receive(:file_changed?).with(module_path).and_return(false)
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name, :force => false)
|
||||
end
|
||||
|
||||
context 'with file changed' do
|
||||
let(:module_full_name) do
|
||||
File.join('auxiliary', module_reference_name)
|
||||
end
|
||||
|
||||
let(:namespace_module) do
|
||||
Msf::Modules.const_get(relative_name)
|
||||
end
|
||||
|
||||
let(:relative_name) do
|
||||
'Mod617578696c696172792f72737065632f6d6f636b'
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
# capture in a local so that instance_eval can access it
|
||||
relative_name = self.relative_name
|
||||
|
||||
# remove module from previous examples so reload error aren't logged
|
||||
if Msf::Modules.const_defined? relative_name
|
||||
Msf::Modules.instance_eval do
|
||||
remove_const relative_name
|
||||
end
|
||||
end
|
||||
|
||||
# create an namespace module that can be restored
|
||||
module Msf
|
||||
module Modules
|
||||
module Mod617578696c696172792f72737065632f6d6f636b
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@original_namespace_module = Msf::Modules::Mod617578696c696172792f72737065632f6d6f636b
|
||||
|
||||
module_manager.stub(:delete).with(module_reference_name)
|
||||
module_manager.stub(:file_changed?).with(module_path).and_return(true)
|
||||
|
||||
module_set = mock('Module Set')
|
||||
module_set.stub(:delete).with(module_reference_name)
|
||||
module_manager.stub(:module_set).with(type).and_return(module_set)
|
||||
end
|
||||
|
||||
it 'should call #namespace_module_transaction with the module full name and :reload => true' do
|
||||
subject.should_receive(:namespace_module_transaction).with(module_full_name, hash_including(:reload => true))
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name)
|
||||
end
|
||||
|
||||
it 'should set the parent_path on the namespace_module to match the parent_path passed to #load_module' do
|
||||
module_manager.stub(:module_load_error_by_path => {})
|
||||
module_manager.stub(:on_module_load)
|
||||
|
||||
subject.stub(:read_module_content => module_content)
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
namespace_module.parent_path.should == parent_path
|
||||
end
|
||||
|
||||
it 'should call #read_module_content to get the module content so that #read_module_content can be overridden to change loading behavior' do
|
||||
module_manager.stub(:module_load_error_by_path => {})
|
||||
module_manager.stub(:on_module_load)
|
||||
|
||||
subject.should_receive(:read_module_content).with(parent_path, type, module_reference_name).and_return(module_content)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
end
|
||||
|
||||
it 'should call namespace_module.module_eval_with_lexical_scope with the module_path' do
|
||||
subject.stub(:read_module_content => malformed_module_content)
|
||||
module_manager.stub(:module_load_error_by_path => {})
|
||||
module_manager.stub(:on_module_load)
|
||||
|
||||
# if the module eval error includes the module_path then the module_path was passed along correctly
|
||||
subject.should_receive(:elog).with(/#{Regexp.escape(module_path)}/)
|
||||
subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_false
|
||||
end
|
||||
|
||||
context 'with errors from namespace_module_eval_with_lexical_scope' do
|
||||
before(:each) do
|
||||
@namespace_module = mock('Namespace Module')
|
||||
@namespace_module.stub(:parent_path=)
|
||||
|
||||
subject.stub(:namespace_module_transaction).and_yield(@namespace_module)
|
||||
subject.stub(:read_module_content)
|
||||
end
|
||||
|
||||
context 'with Interrupt' do
|
||||
it 'should re-raise' do
|
||||
@namespace_module.stub(:module_eval_with_lexical_scope).and_raise(Interrupt)
|
||||
|
||||
expect {
|
||||
subject.load_module(parent_path, type, module_reference_name)
|
||||
}.to raise_error(Interrupt)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with other Exception' do
|
||||
let(:backtrace) do
|
||||
[
|
||||
'Backtrace Line 1',
|
||||
'Backtrace Line 2'
|
||||
]
|
||||
end
|
||||
|
||||
let(:error) do
|
||||
error_class.new(error_message)
|
||||
end
|
||||
|
||||
let(:error_class) do
|
||||
ArgumentError
|
||||
end
|
||||
|
||||
let(:error_message) do
|
||||
'This is rspec. Your argument is invalid.'
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@namespace_module.stub(:module_eval_with_lexical_scope).and_raise(error)
|
||||
|
||||
@module_load_error_by_path = {}
|
||||
module_manager.stub(:module_load_error_by_path => @module_load_error_by_path)
|
||||
|
||||
error.stub(:backtrace => backtrace)
|
||||
end
|
||||
|
||||
context 'with version compatibility' do
|
||||
before(:each) do
|
||||
@namespace_module.stub(:version_compatible!).with(module_path, module_reference_name)
|
||||
end
|
||||
|
||||
it 'should report error class and string in module_manager.module_load_error_by_path' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
@module_load_error_by_path[module_path].should == "#{error_class} #{error}"
|
||||
end
|
||||
|
||||
it 'should report error class, string, and backtrace in the log' do
|
||||
subject.should_receive(:elog).with(
|
||||
# don't use join on backtrace as that will match implementation too closely
|
||||
"#{error_class} #{error}:\n#{backtrace[0]}\n#{backtrace[1]}"
|
||||
)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context 'without version compatibility' do
|
||||
let(:version_compatibility_error) do
|
||||
Msf::Modules::VersionCompatibilityError.new(
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name,
|
||||
:minimum_api_version => infinity,
|
||||
:minimum_core_version => infinity
|
||||
)
|
||||
end
|
||||
|
||||
let(:infinity) do
|
||||
0.0 / 0.0
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@namespace_module.stub(
|
||||
:version_compatible!
|
||||
).with(
|
||||
module_path,
|
||||
module_reference_name
|
||||
).and_raise(
|
||||
version_compatibility_error
|
||||
)
|
||||
end
|
||||
|
||||
it 'should report module_path and version compatibility error string in module_manager.module_load_error_by_path' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
|
||||
@module_load_error_by_path[module_path].should include(module_path)
|
||||
@module_load_error_by_path[module_path].should include(version_compatibility_error.to_s)
|
||||
end
|
||||
|
||||
it 'should report backtrace of original error in the log' do
|
||||
formatted_backtrace = "\n#{backtrace[0]}\n#{backtrace[1]}"
|
||||
escaped_backtrace = Regexp.escape(formatted_backtrace)
|
||||
|
||||
subject.should_receive(:elog).with(/#{escaped_backtrace}/)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
@namespace_module.stub(:version_compatible!).with(module_path, module_reference_name)
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without module_eval errors' do
|
||||
before(:each) do
|
||||
@namespace_module = mock('Namespace Module')
|
||||
@namespace_module.stub(:parent_path=)
|
||||
@namespace_module.stub(:module_eval_with_lexical_scope).with(module_content, module_path)
|
||||
|
||||
metasploit_class = mock('Metasploit Class', :parent => @namespace_module)
|
||||
@namespace_module.stub(:metasploit_class => metasploit_class)
|
||||
|
||||
subject.stub(:namespace_module_transaction).and_yield(@namespace_module)
|
||||
|
||||
subject.stub(:read_module_content).with(parent_path, type, module_reference_name).and_return(module_content)
|
||||
|
||||
@module_load_error_by_path = {}
|
||||
module_manager.stub(:module_load_error_by_path => @module_load_error_by_path)
|
||||
end
|
||||
|
||||
it 'should check for version compatibility' do
|
||||
module_manager.stub(:on_module_load)
|
||||
|
||||
@namespace_module.should_receive(:version_compatible!).with(module_path, module_reference_name)
|
||||
subject.load_module(parent_path, type, module_reference_name)
|
||||
end
|
||||
|
||||
context 'without version compatibility' do
|
||||
let(:version_compatibility_error) do
|
||||
Msf::Modules::VersionCompatibilityError.new(
|
||||
:module_path => module_path,
|
||||
:module_reference_name => module_reference_name,
|
||||
:minimum_api_version => infinity,
|
||||
:minimum_core_version => infinity
|
||||
)
|
||||
end
|
||||
|
||||
let(:infinity) do
|
||||
0.0 / 0.0
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@namespace_module.stub(
|
||||
:version_compatible!
|
||||
).with(
|
||||
module_path,
|
||||
module_reference_name
|
||||
).and_raise(
|
||||
version_compatibility_error
|
||||
)
|
||||
end
|
||||
|
||||
it 'should report error in module_manage.module_load_error_by_path' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
@module_load_error_by_path[module_path].should == version_compatibility_error.to_s
|
||||
end
|
||||
|
||||
it 'should log error' do
|
||||
subject.should_receive(:elog).with(version_compatibility_error.to_s)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
|
||||
it 'should restore the old namespace module' do
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
context 'with version compatibility' do
|
||||
before(:each) do
|
||||
@namespace_module.stub(:version_compatible!).with(module_path, module_reference_name)
|
||||
|
||||
module_manager.stub(:on_module_load)
|
||||
end
|
||||
|
||||
context 'without metasploit_class' do
|
||||
before(:each) do
|
||||
@namespace_module.stub(:metasploit_class).and_return(nil)
|
||||
end
|
||||
|
||||
let(:error_message) do
|
||||
'Missing Metasploit class constant'
|
||||
end
|
||||
|
||||
it 'should log missing Metasploit class' do
|
||||
subject.should_receive(:elog).with(error_message)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
|
||||
it 'should record error in module_manager.module_load_error_by_path' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
@module_load_error_by_path[module_path].should == error_message
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
|
||||
it 'should restore the old namespace module' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
Msf::Modules.const_defined?(relative_name).should be_true
|
||||
Msf::Modules.const_get(relative_name).should == @original_namespace_module
|
||||
end
|
||||
end
|
||||
|
||||
context 'with metasploit_class' do
|
||||
let(:metasploit_class) do
|
||||
mock('Metasploit Class')
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
@namespace_module.stub(:metasploit_class => metasploit_class)
|
||||
end
|
||||
|
||||
it 'should check if it is usable' do
|
||||
subject.should_receive(:usable?).with(metasploit_class).and_return(true)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
end
|
||||
|
||||
context 'without usable metasploit_class' do
|
||||
before(:each) do
|
||||
subject.stub(:usable? => false)
|
||||
end
|
||||
|
||||
it 'should log information' do
|
||||
subject.should_receive(:ilog).with(/#{module_reference_name}/, 'core', LEV_1)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
end
|
||||
|
||||
it 'should restore the old namespace module' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_false
|
||||
Msf::Modules.const_defined?(relative_name).should be_true
|
||||
Msf::Modules.const_get(relative_name).should == @original_namespace_module
|
||||
end
|
||||
end
|
||||
|
||||
context 'with usable metasploit_class' do
|
||||
before(:each) do
|
||||
# remove the mocked namespace_module since happy-path/real loading is occurring in this context
|
||||
subject.unstub(:namespace_module_transaction)
|
||||
end
|
||||
|
||||
it 'should log load information' do
|
||||
subject.should_receive(:ilog).with(/#{module_reference_name}/, 'core', LEV_2)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
end
|
||||
|
||||
it 'should delete any pre-existing load errors from module_manager.module_load_error_by_path' do
|
||||
original_load_error = "Back in my day this module didn't load"
|
||||
module_manager.module_load_error_by_path[module_path] = original_load_error
|
||||
|
||||
module_manager.module_load_error_by_path[module_path].should == original_load_error
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
module_manager.module_load_error_by_path[module_path].should be_nil
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
end
|
||||
|
||||
context 'with module_reference_name already in module_manager' do
|
||||
let(:framework) do
|
||||
framework = mock('Framework', :datastore => {})
|
||||
framework.stub_chain(:events, :on_module_load)
|
||||
|
||||
framework
|
||||
end
|
||||
|
||||
let(:metasploit_class) do
|
||||
@original_namespace_module::Metasploit3
|
||||
end
|
||||
|
||||
let(:module_manager) do
|
||||
Msf::ModuleManager.new(framework)
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
# remove the stub from before(:each) in context 'with version compatibility'
|
||||
module_manager.unstub(:on_module_load)
|
||||
|
||||
# remove the stubs from before(:each) in context 'with file changed'
|
||||
module_manager.unstub(:delete)
|
||||
module_manager.unstub(:module_set)
|
||||
end
|
||||
|
||||
it 'should not cause an ambiguous module_reference_name in the module_manager' do
|
||||
module_manager[module_reference_name] = metasploit_class
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
module_manager.send(:ambiguous_module_reference_name_set).should be_empty
|
||||
end
|
||||
|
||||
it 'should not cause an ambiguous module_reference_name in the type module_set' do
|
||||
module_set = module_manager.module_set(type)
|
||||
module_set[module_reference_name] = metasploit_class
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
module_set.send(:ambiguous_module_reference_name_set).should be_empty
|
||||
end
|
||||
|
||||
context 'without file changed' do
|
||||
before(:each) do
|
||||
module_manager.stub(:file_changed => false)
|
||||
end
|
||||
|
||||
context 'with :force => true' do
|
||||
it 'should not cause an ambiguous module_reference_name in the module_manager' do
|
||||
module_manager[module_reference_name] = metasploit_class
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name, :force => true).should be_true
|
||||
module_manager.send(:ambiguous_module_reference_name_set).should be_empty
|
||||
end
|
||||
|
||||
it 'should not cause an ambiguous module_reference_name in the type module_set' do
|
||||
module_set = module_manager.module_set(type)
|
||||
module_set[module_reference_name] = metasploit_class
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name, :force => true).should be_true
|
||||
module_set.send(:ambiguous_module_reference_name_set).should be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :reload => true' do
|
||||
it 'should not cause an ambiguous module_reference_name in the module_manager' do
|
||||
module_manager[module_reference_name] = metasploit_class
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_true
|
||||
module_manager.send(:ambiguous_module_reference_name_set).should be_empty
|
||||
end
|
||||
|
||||
it 'should not cause an ambiguous module_reference_name in the type module_set' do
|
||||
module_set = module_manager.module_set(type)
|
||||
module_set[module_reference_name] = metasploit_class
|
||||
|
||||
subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_true
|
||||
module_set.send(:ambiguous_module_reference_name_set).should be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should call module_manager.on_module_load' do
|
||||
module_manager.should_receive(:on_module_load)
|
||||
subject.load_module(parent_path, type, module_reference_name).should be_true
|
||||
end
|
||||
|
||||
context 'with :recalculate_by_type' do
|
||||
it 'should set the type to be recalculated' do
|
||||
recalculate_by_type = {}
|
||||
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:recalculate_by_type => recalculate_by_type
|
||||
).should be_true
|
||||
recalculate_by_type[type].should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with :count_by_type' do
|
||||
it 'should set the count to 1 if it does not exist' do
|
||||
count_by_type = {}
|
||||
|
||||
count_by_type.has_key?(type).should be_false
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:count_by_type => count_by_type
|
||||
).should be_true
|
||||
count_by_type[type].should == 1
|
||||
end
|
||||
|
||||
it 'should increment the count if it does exist' do
|
||||
original_count = 1
|
||||
count_by_type = {
|
||||
type => original_count
|
||||
}
|
||||
|
||||
subject.load_module(
|
||||
parent_path,
|
||||
type,
|
||||
module_reference_name,
|
||||
:count_by_type => count_by_type
|
||||
).should be_true
|
||||
|
||||
incremented_count = original_count + 1
|
||||
count_by_type[type].should == incremented_count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#create_namespace_module' do
|
||||
let(:namespace_module_names) do
|
||||
[
|
||||
'Msf',
|
||||
'Modules',
|
||||
relative_name
|
||||
]
|
||||
end
|
||||
|
||||
let(:relative_name) do
|
||||
'Mod0'
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
# capture in local variable so it works in instance_eval
|
||||
relative_name = self.relative_name
|
||||
|
||||
if Msf::Modules.const_defined? relative_name
|
||||
Msf::Modules.instance_eval do
|
||||
remove_const relative_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should wrap NAMESPACE_MODULE_CONTENT with module declarations matching namespace_module_names' do
|
||||
Object.should_receive(
|
||||
:module_eval
|
||||
).with(
|
||||
"module #{namespace_module_names[0]}\n" \
|
||||
"module #{namespace_module_names[1]}\n" \
|
||||
"module #{namespace_module_names[2]}\n" \
|
||||
"#{described_class::NAMESPACE_MODULE_CONTENT}\n" \
|
||||
"end\n" \
|
||||
"end\n" \
|
||||
"end",
|
||||
anything,
|
||||
anything
|
||||
)
|
||||
|
||||
namespace_module = mock('Namespace Module')
|
||||
namespace_module.stub(:loader=)
|
||||
subject.stub(:current_module => namespace_module)
|
||||
|
||||
subject.send(:create_namespace_module, namespace_module_names)
|
||||
end
|
||||
|
||||
it "should set the module_eval path to the loader's __FILE__" do
|
||||
Object.should_receive(
|
||||
:module_eval
|
||||
).with(
|
||||
anything,
|
||||
described_class_pathname.to_s,
|
||||
anything
|
||||
)
|
||||
|
||||
namespace_module = mock('Namespace Module')
|
||||
namespace_module.stub(:loader=)
|
||||
subject.stub(:current_module => namespace_module)
|
||||
|
||||
subject.send(:create_namespace_module, namespace_module_names)
|
||||
end
|
||||
|
||||
it 'should set the module_eval line to compensate for the wrapping module declarations' do
|
||||
Object.should_receive(
|
||||
:module_eval
|
||||
).with(
|
||||
anything,
|
||||
anything,
|
||||
described_class::NAMESPACE_MODULE_LINE - namespace_module_names.length
|
||||
)
|
||||
|
||||
namespace_module = mock('Namespace Module')
|
||||
namespace_module.stub(:loader=)
|
||||
subject.stub(:current_module => namespace_module)
|
||||
|
||||
subject.send(:create_namespace_module, namespace_module_names)
|
||||
end
|
||||
|
||||
it "should set the namespace_module's module loader to itself" do
|
||||
namespace_module = mock('Namespace Module')
|
||||
|
||||
namespace_module.should_receive(:loader=).with(subject)
|
||||
|
||||
subject.stub(:current_module => namespace_module)
|
||||
|
||||
subject.send(:create_namespace_module, namespace_module_names)
|
||||
end
|
||||
end
|
||||
|
||||
context '#current_module' do
|
||||
let(:module_names) do
|
||||
[
|
||||
'Msf',
|
||||
'Modules',
|
||||
relative_name
|
||||
]
|
||||
end
|
||||
|
||||
let(:relative_name) do
|
||||
'Mod0'
|
||||
end
|
||||
|
||||
before(:each) do
|
||||
# copy to local variable so it is accessible in instance_eval
|
||||
relative_name = self.relative_name
|
||||
|
||||
if Msf::Modules.const_defined? relative_name
|
||||
Msf::Modules.instance_eval do
|
||||
remove_const relative_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should return nil if the module is not defined' do
|
||||
Msf::Modules.const_defined?(relative_name).should be_false
|
||||
subject.send(:current_module, module_names).should be_nil
|
||||
end
|
||||
|
||||
it 'should return the module if it is defined' do
|
||||
module Msf
|
||||
module Modules
|
||||
module Mod0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
subject.send(:current_module, module_names).should == Msf::Modules::Mod0
|
||||
end
|
||||
end
|
||||
|
||||
context '#each_module_reference_name' do
|
||||
it 'should be abstract' do
|
||||
expect {
|
||||
subject.send(:each_module_reference_name, parent_path)
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
context '#module_path' do
|
||||
it 'should be abstract' do
|
||||
expect {
|
||||
subject.send(:module_path, parent_path, Msf::MODULE_AUX, module_reference_name)
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
context '#module_path?' do
|
||||
it 'should return false if path is hidden' do
|
||||
hidden_path = '.hidden/path/file.rb'
|
||||
|
||||
subject.send(:module_path?, hidden_path).should be_false
|
||||
end
|
||||
|
||||
it 'should return false if the file extension is not MODULE_EXTENSION' do
|
||||
non_module_extension = '.c'
|
||||
path = "path/with/wrong/extension#{non_module_extension}"
|
||||
|
||||
non_module_extension.should_not == described_class::MODULE_EXTENSION
|
||||
subject.send(:module_path?, path).should be_false
|
||||
end
|
||||
|
||||
it 'should return false if the file is a unit test' do
|
||||
unit_test_extension = '.rb.ut.rb'
|
||||
path = "path/to/unit_test#{unit_test_extension}"
|
||||
|
||||
subject.send(:module_path?, path).should be_false
|
||||
end
|
||||
|
||||
it 'should return false if the file is a test suite' do
|
||||
test_suite_extension = '.rb.ts.rb'
|
||||
path = "path/to/test_suite#{test_suite_extension}"
|
||||
|
||||
subject.send(:module_path?, path).should be_false
|
||||
end
|
||||
|
||||
it 'should return true otherwise' do
|
||||
subject.send(:module_path?, module_path).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context '#module_reference_name_from_path' do
|
||||
it 'should strip MODULE_EXTENSION from the end of the path' do
|
||||
path_without_extension = "a#{described_class::MODULE_EXTENSION}.dir/a"
|
||||
path = "#{path_without_extension}#{described_class::MODULE_EXTENSION}"
|
||||
|
||||
subject.send(:module_reference_name_from_path, path).should == path_without_extension
|
||||
end
|
||||
end
|
||||
|
||||
context '#namespace_module_name' do
|
||||
it 'should prefix the name with Msf::Modules::' do
|
||||
subject.send(:namespace_module_name, module_full_name).should start_with('Msf::Modules::')
|
||||
end
|
||||
|
||||
it 'should prefix the relative name with Mod' do
|
||||
namespace_module_name = subject.send(:namespace_module_name, module_full_name)
|
||||
relative_name = namespace_module_name.gsub(/^.*::/, '')
|
||||
|
||||
relative_name.should start_with('Mod')
|
||||
end
|
||||
|
||||
it 'should be reversible' do
|
||||
namespace_module_name = subject.send(:namespace_module_name, module_full_name)
|
||||
unpacked_name = namespace_module_name.gsub(/^.*::Mod/, '')
|
||||
|
||||
[unpacked_name].pack('H*').should == module_full_name
|
||||
end
|
||||
end
|
||||
|
||||
context '#namespace_module_names' do
|
||||
it "should prefix the array with ['Msf', 'Modules']" do
|
||||
subject.send(:namespace_module_names, module_full_name).should start_with(['Msf', 'Modules'])
|
||||
end
|
||||
|
||||
it 'should prefix the relative name with Mod' do
|
||||
namespace_module_names = subject.send(:namespace_module_names, module_full_name)
|
||||
|
||||
namespace_module_names.last.should start_with('Mod')
|
||||
end
|
||||
|
||||
it 'should be reversible' do
|
||||
namespace_module_names = subject.send(:namespace_module_names, module_full_name)
|
||||
relative_name = namespace_module_names.last
|
||||
unpacked_name = relative_name.gsub(/^Mod/, '')
|
||||
|
||||
[unpacked_name].pack('H*').should == module_full_name
|
||||
end
|
||||
end
|
||||
|
||||
context '#namespace_module_transaction' do
|
||||
let(:relative_name) do
|
||||
'Mod617578696c696172792f72737065632f6d6f636b'
|
||||
end
|
||||
|
||||
context 'with pre-existing namespace module' do
|
||||
before(:each) do
|
||||
module Msf
|
||||
module Modules
|
||||
module Mod617578696c696172792f72737065632f6d6f636b
|
||||
class Metasploit3
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@existent_namespace_module = Msf::Modules::Mod617578696c696172792f72737065632f6d6f636b
|
||||
end
|
||||
|
||||
context 'with :reload => false' do
|
||||
it 'should log an error' do
|
||||
subject.should_receive(:elog).with(/Reloading.*when :reload => false/)
|
||||
|
||||
subject.send(:namespace_module_transaction, module_full_name, :reload => false) do |namespace_module|
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'should remove the pre-existing namespace module' do
|
||||
Msf::Modules.should_receive(:remove_const).with(relative_name)
|
||||
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create a new namespace module for the block' do
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
namespace_module.should_not == @existent_namespace_module
|
||||
|
||||
expect {
|
||||
namespace_module::Metasploit3
|
||||
}.to raise_error(NameError)
|
||||
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an Exception from the block' do
|
||||
let(:error_class) do
|
||||
NameError
|
||||
end
|
||||
|
||||
let(:error_message) do
|
||||
"SayMyName"
|
||||
end
|
||||
|
||||
it 'should restore the previous namespace module' do
|
||||
Msf::Modules.const_get(relative_name).should == @existent_namespace_module
|
||||
|
||||
begin
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
current_constant = Msf::Modules.const_get(relative_name)
|
||||
|
||||
current_constant.should == namespace_module
|
||||
current_constant.should_not == @existent_namespace_module
|
||||
|
||||
raise error_class, error_message
|
||||
end
|
||||
rescue error_class => error
|
||||
end
|
||||
|
||||
Msf::Modules.const_get(relative_name).should == @existent_namespace_module
|
||||
end
|
||||
|
||||
it 'should re-raise the error' do
|
||||
expect {
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
raise error_class, error_message
|
||||
end
|
||||
}.to raise_error(error_class, error_message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the block returning false' do
|
||||
it 'should restore the previous namespace module' do
|
||||
Msf::Modules.const_get(relative_name).should == @existent_namespace_module
|
||||
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
current_constant = Msf::Modules.const_get(relative_name)
|
||||
|
||||
current_constant.should == namespace_module
|
||||
current_constant.should_not == @existent_namespace_module
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
Msf::Modules.const_get(relative_name).should == @existent_namespace_module
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
subject.send(:namespace_module_transaction, module_full_name) { |namespace_module|
|
||||
false
|
||||
}.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the block returning true' do
|
||||
it 'should not restore the previous namespace module' do
|
||||
Msf::Modules.const_get(relative_name).should == @existent_namespace_module
|
||||
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
true
|
||||
end
|
||||
|
||||
current_constant = Msf::Modules.const_get(relative_name)
|
||||
|
||||
current_constant.should_not be_nil
|
||||
current_constant.should_not == @existent_namespace_module
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
subject.send(:namespace_module_transaction, module_full_name) { |namespace_module|
|
||||
true
|
||||
}.should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without pre-existing namespace module' do
|
||||
before(:each) do
|
||||
relative_name = self.relative_name
|
||||
|
||||
if Msf::Modules.const_defined? relative_name
|
||||
Msf::Modules.send(:remove_const, relative_name)
|
||||
end
|
||||
end
|
||||
|
||||
it 'should create a new namespace module' do
|
||||
expect {
|
||||
Msf::Modules.const_get(relative_name)
|
||||
}.to raise_error(NameError)
|
||||
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
Msf::Modules.const_get(relative_name).should == namespace_module
|
||||
end
|
||||
end
|
||||
|
||||
context 'with an Exception from the block' do
|
||||
let(:error_class) do
|
||||
Exception
|
||||
end
|
||||
|
||||
let(:error_message) do
|
||||
'Error Message'
|
||||
end
|
||||
|
||||
it 'should remove the created namespace module' do
|
||||
Msf::Modules.const_defined?(relative_name).should be_false
|
||||
|
||||
begin
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
Msf::Module.const_defined?(relative_name).should be_true
|
||||
|
||||
raise error_class, error_message
|
||||
end
|
||||
rescue error_class
|
||||
end
|
||||
|
||||
Msf::Modules.const_defined?(relative_name).should be_false
|
||||
end
|
||||
|
||||
it 'should re-raise the error' do
|
||||
expect {
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
raise error_class, error_message
|
||||
end
|
||||
}.to raise_error(error_class, error_message)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the block returning false' do
|
||||
it 'should remove the created namespace module' do
|
||||
Msf::Modules.const_defined?(relative_name).should be_false
|
||||
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
Msf::Modules.const_defined?(relative_name).should be_true
|
||||
|
||||
false
|
||||
end
|
||||
|
||||
Msf::Modules.const_defined?(relative_name).should be_false
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
subject.send(:namespace_module_transaction, module_full_name) { |namespace_module|
|
||||
false
|
||||
}.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
context 'with the block returning true' do
|
||||
it 'should not restore the non-existent previous namespace module' do
|
||||
Msf::Modules.const_defined?(relative_name).should be_false
|
||||
|
||||
created_namespace_module = nil
|
||||
|
||||
subject.send(:namespace_module_transaction, module_full_name) do |namespace_module|
|
||||
Msf::Modules.const_defined?(relative_name).should be_true
|
||||
|
||||
created_namespace_module = namespace_module
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
Msf::Modules.const_defined?(relative_name).should be_true
|
||||
Msf::Modules.const_get(relative_name).should == created_namespace_module
|
||||
end
|
||||
|
||||
it 'should return true' do
|
||||
subject.send(:namespace_module_transaction, module_full_name) { |namespace_module|
|
||||
true
|
||||
}.should be_true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#read_module_content' do
|
||||
it 'should be abstract' do
|
||||
type = Msf::MODULE_AUX
|
||||
|
||||
expect {
|
||||
subject.send(:read_module_content, parent_pathname.to_s, type, module_reference_name)
|
||||
}.to raise_error(NotImplementedError)
|
||||
end
|
||||
end
|
||||
|
||||
context '#restore_namespace_module' do
|
||||
let(:parent_module) do
|
||||
Msf::Modules
|
||||
end
|
||||
|
||||
let(:relative_name) do
|
||||
'Mod0'
|
||||
end
|
||||
|
||||
it 'should do nothing if parent_module is nil' do
|
||||
parent_module = nil
|
||||
|
||||
allow_message_expectations_on_nil
|
||||
parent_module.should_not_receive(:remove_const)
|
||||
parent_module.should_not_receive(:const_set)
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
|
||||
end
|
||||
|
||||
context 'with namespace_module nil' do
|
||||
let(:namespace_module) do
|
||||
nil
|
||||
end
|
||||
|
||||
it 'should remove relative_name' do
|
||||
parent_module.should_receive(:remove_const).with(relative_name)
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, namespace_module)
|
||||
end
|
||||
|
||||
it 'should not set the relative_name constant to anything' do
|
||||
parent_module.should_not_receive(:const_set)
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, namespace_module)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with parent_module and namespace_module' do
|
||||
before(:each) do
|
||||
module Msf
|
||||
module Modules
|
||||
module Mod0
|
||||
class Metasploit3
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@original_namespace_module = Msf::Modules::Mod0
|
||||
|
||||
Msf::Modules.send(:remove_const, relative_name)
|
||||
end
|
||||
|
||||
context 'with relative_name being a defined constant' do
|
||||
before(:each) do
|
||||
module Msf
|
||||
module Modules
|
||||
module Mod0
|
||||
class Metasploit2
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@current_namespace_module = Msf::Modules::Mod0
|
||||
end
|
||||
|
||||
context 'with the current constant being the namespace_module' do
|
||||
it 'should not change the constant' do
|
||||
parent_module.const_defined?(relative_name).should be_true
|
||||
|
||||
current_module = parent_module.const_get(relative_name)
|
||||
current_module.should == @current_namespace_module
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, @current_namespace_module)
|
||||
|
||||
parent_module.const_defined?(relative_name).should be_true
|
||||
restored_module = parent_module.const_get(relative_name)
|
||||
restored_module.should == current_module
|
||||
restored_module.should == @current_namespace_module
|
||||
end
|
||||
|
||||
it 'should not remove the constant and then set it' do
|
||||
parent_module.should_not_receive(:remove_const).with(relative_name)
|
||||
parent_module.should_not_receive(:const_set).with(relative_name, @current_namespace_module)
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, @current_namespace_module)
|
||||
end
|
||||
end
|
||||
|
||||
context 'without the current constant being the namespace_module' do
|
||||
it 'should remove relative_name from parent_module' do
|
||||
parent_module.const_defined?(relative_name).should be_true
|
||||
parent_module.should_receive(:remove_const).with(relative_name)
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
|
||||
end
|
||||
|
||||
it 'should restore the module to the constant' do
|
||||
parent_module.const_get(relative_name).should_not == @original_namespace_module
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
|
||||
|
||||
parent_module.const_get(relative_name).should == @original_namespace_module
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without relative_name being a defined constant' do
|
||||
it 'should set relative_name on parent_module to namespace_module' do
|
||||
parent_module.const_defined?(relative_name).should be_false
|
||||
|
||||
subject.send(:restore_namespace_module, parent_module, relative_name, @original_namespace_module)
|
||||
|
||||
parent_module.const_defined?(relative_name).should be_true
|
||||
parent_module.const_get(relative_name).should == @original_namespace_module
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#typed_path' do
|
||||
it 'should delegate to the class method' do
|
||||
type = Msf::MODULE_EXPLOIT
|
||||
|
||||
described_class.should_receive(:typed_path).with(type, module_reference_name)
|
||||
subject.send(:typed_path, type, module_reference_name)
|
||||
end
|
||||
end
|
||||
|
||||
context '#usable?' do
|
||||
context 'without metasploit_class responding to is_usable' do
|
||||
it 'should return true' do
|
||||
metasploit_class = mock('Metasploit Class')
|
||||
metasploit_class.should_not respond_to(:is_usable)
|
||||
|
||||
subject.send(:usable?, metasploit_class).should be_true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with metasploit_class responding to is_usable' do
|
||||
it 'should delegate to metasploit_class.is_usable' do
|
||||
# not a proper return, but guarantees that delegation is actually happening
|
||||
usability = 'maybe'
|
||||
metasploit_class = mock('Metasploit Class', :is_usable => usability)
|
||||
|
||||
subject.send(:usable?, metasploit_class).should == usability
|
||||
end
|
||||
|
||||
context 'with error from metasploit_class.is_usable' do
|
||||
let(:error) do
|
||||
'Expected error'
|
||||
end
|
||||
|
||||
let(:metasploit_class) do
|
||||
metasploit_class = mock('Metasploit Class')
|
||||
|
||||
metasploit_class.stub(:is_usable).and_raise(error)
|
||||
|
||||
metasploit_class
|
||||
end
|
||||
|
||||
it 'should log error' do
|
||||
subject.should_receive(:elog).with(/#{error}/)
|
||||
|
||||
subject.send(:usable?, metasploit_class)
|
||||
end
|
||||
|
||||
it 'should return false' do
|
||||
subject.send(:usable?, metasploit_class).should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
require 'spec_helper'
|
||||
require 'msf/core'
|
||||
require 'msf/core/modules/loader/directory'
|
||||
|
||||
describe Msf::Modules::Loader::Directory do
|
||||
|
||||
end
|
||||
@@ -0,0 +1,13 @@
|
||||
shared_examples_for 'Msf::Modules::Loader::Archive#read_module_content' do
|
||||
it 'should be able to read the module content' do
|
||||
archived_module_content = subject.send(:read_module_content, @parent_path, type, module_reference_name)
|
||||
unarchived_module_content = ''
|
||||
|
||||
File.open(unarchived_path) do |f|
|
||||
unarchived_module_content = f.read
|
||||
end
|
||||
|
||||
unarchived_module_content.should_not be_empty
|
||||
archived_module_content.should == unarchived_module_content
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
shared_examples_for 'typed_path' do |map={}|
|
||||
if map.length < 1
|
||||
raise ArgumentError,
|
||||
"type_path shared example requires a hash mapping the type constant name to the directory name: " \
|
||||
"it_should_behave_like 'type_path', 'Msf::Auxiliary' => 'auxiliary'"
|
||||
end
|
||||
|
||||
if map.length > 1
|
||||
raise ArgumentError,
|
||||
"only one constant to directory mapping should be passed to each shared example, not #{map.length}"
|
||||
end
|
||||
|
||||
type_constant_name, directory = map.shift
|
||||
|
||||
context "with #{type_constant_name} type" do
|
||||
let(:type_constant) do
|
||||
type_constant_name.constantize
|
||||
end
|
||||
|
||||
it "should start with #{directory} directory" do
|
||||
typed_path = described_class.typed_path(type_constant, module_reference_name)
|
||||
first_directory = typed_path.split(File::SEPARATOR).first
|
||||
|
||||
first_directory.should == directory
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user