Merge branch 'bug/read-module-content-errno-enoent' into rapid7

Really [Closes #1025]
This commit is contained in:
James Lee 2012-11-07 19:25:39 -06:00
commit 8a4fb07a0c
21 changed files with 846 additions and 99 deletions

2
.gitignore vendored
View File

@ -6,6 +6,8 @@
.yardoc
# Mac OS X files
.DS_Store
# simplecov coverage data
coverage
data/meterpreter/ext_server_pivot.dll
data/meterpreter/ext_server_pivot.x64.dll
doc

View File

@ -24,4 +24,7 @@ end
group :test do
# testing framework
gem 'rspec'
# code coverage for tests
# any version newer than 0.5.4 gives an Encoding error when trying to read the source files.
gem 'simplecov', '0.5.4', :require => false
end

View File

@ -45,6 +45,10 @@ GEM
rspec-expectations (2.11.3)
diff-lcs (~> 1.1.3)
rspec-mocks (2.11.3)
simplecov (0.5.4)
multi_json (~> 1.0.3)
simplecov-html (~> 0.5.3)
simplecov-html (0.5.3)
slop (3.3.3)
tzinfo (0.3.33)
yard (0.8.2.1)
@ -60,4 +64,5 @@ DEPENDENCIES
rake
redcarpet
rspec
simplecov (= 0.5.4)
yard

View File

@ -109,4 +109,4 @@ module Msf::ModuleManager::Loading
count_by_type
end
end
end

View File

@ -0,0 +1,38 @@
# Base error class for all error under {Msf::Modules}
class Msf::Modules::Error < StandardError
def initialize(attributes={})
@module_path = attributes[:module_path]
@module_reference_name = attributes[:module_reference_name]
message_parts = []
message_parts << "Failed to load module"
if module_reference_name or module_path
clause_parts = []
if module_reference_name
clause_parts << module_reference_name
end
if module_path
clause_parts << "from #{module_path}"
end
clause = clause_parts.join(' ')
message_parts << "(#{clause})"
end
causal_message = attributes[:causal_message]
if causal_message
message_parts << "due to #{causal_message}"
end
message = message_parts.join(' ')
super(message)
end
attr_reader :module_reference_name
attr_reader :module_path
end

View File

@ -3,6 +3,7 @@
#
require 'msf/core/modules/loader'
require 'msf/core/modules/namespace'
require 'msf/core/modules/metasploit_class_compatibility_error'
require 'msf/core/modules/version_compatibility_error'
# Responsible for loading modules for {Msf::ModuleManager}.
@ -117,12 +118,17 @@ class Msf::Modules::Loader::Base
metasploit_class = nil
module_content = read_module_content(parent_path, type, module_reference_name)
if module_content.empty?
# read_module_content is responsible for calling {#load_error}, so just return here.
return false
end
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
namespace_module.parent_path = parent_path
module_content = read_module_content(parent_path, type, module_reference_name)
begin
namespace_module.module_eval_with_lexical_scope(module_content, module_path)
# handle interrupts as pass-throughs unlike other Exceptions so users can bail with Ctrl+C
@ -133,45 +139,33 @@ class Msf::Modules::Loader::Base
begin
namespace_module.version_compatible!(module_path, module_reference_name)
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
error_message = "Failed to load module (#{module_path}) due to error and #{version_compatibility_error}"
load_error(module_path, version_compatibility_error)
else
error_message = "#{error.class} #{error}"
load_error(module_path, error)
end
# record the error message without the backtrace for the console
module_manager.module_load_error_by_path[module_path] = error_message
error_message_with_backtrace = "#{error_message}:\n#{error.backtrace.join("\n")}"
elog(error_message_with_backtrace)
return false
end
begin
namespace_module.version_compatible!(module_path, module_reference_name)
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
error_message = version_compatibility_error.to_s
elog(error_message)
module_manager.module_load_error_by_path[module_path] = error_message
load_error(module_path, version_compatibility_error)
return false
end
metasploit_class = namespace_module.metasploit_class
begin
metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
rescue Msf::Modules::MetasploitClassCompatibilityError => error
load_error(module_path, error)
unless metasploit_class
error_message = "Missing Metasploit class constant"
elog(error_message)
module_manager.module_load_error_by_path[module_path] = error_message
return false
return false
end
unless usable?(metasploit_class)
ilog(
"Skipping module #{module_reference_name} under #{parent_path} because is_usable returned false.",
"Skipping module (#{module_reference_name} from #{module_path}) because is_usable returned false.",
'core',
LEV_1
)
@ -409,6 +403,29 @@ class Msf::Modules::Loader::Base
raise ::NotImplementedError
end
# Records the load error to {Msf::ModuleManager::Loading#module_load_error_by_path} and the log.
#
# @param [String] module_path Path to the module as returned by {#module_path}.
# @param [Exception, #class, #to_s, #backtrace] error the error that cause the module not to load.
# @return [void]
#
# @see #module_path
def load_error(module_path, error)
# module_load_error_by_path does not get the backtrace because the value is echoed to the msfconsole where
# backtraces should not appear.
module_manager.module_load_error_by_path[module_path] = "#{error.class} #{error}"
log_lines = []
log_lines << "#{module_path} failed to load due to the following error:"
log_lines << error.class.to_s
log_lines << error.to_s
log_lines << "Call stack:"
log_lines += error.backtrace
log_message = log_lines.join("\n")
elog(log_message)
end
# @return [Msf::ModuleManager] The module manager for which this loader is loading modules.
attr_reader :module_manager

View File

@ -75,13 +75,17 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
module_content = ''
# force to read in binary mode so Pro modules won't be truncated on Windows
File.open(full_path, 'rb') do |f|
# Pass the size of the file as it leads to faster reads due to fewer buffer resizes. Greatest effect on Windows.
# @see http://www.ruby-forum.com/topic/209005
# @see https://github.com/ruby/ruby/blob/ruby_1_8_7/io.c#L1205
# @see https://github.com/ruby/ruby/blob/ruby_1_9_3/io.c#L2038
module_content = f.read(f.stat.size)
begin
# force to read in binary mode so Pro modules won't be truncated on Windows
File.open(full_path, 'rb') do |f|
# Pass the size of the file as it leads to faster reads due to fewer buffer resizes. Greatest effect on Windows.
# @see http://www.ruby-forum.com/topic/209005
# @see https://github.com/ruby/ruby/blob/ruby_1_8_7/io.c#L1205
# @see https://github.com/ruby/ruby/blob/ruby_1_9_3/io.c#L2038
module_content = f.read(f.stat.size)
end
rescue Errno::ENOENT => error
load_error(full_path, error)
end
module_content

View File

@ -0,0 +1,13 @@
require 'msf/core/modules/error'
# Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant
# with {Msf::Framework::Major} or lower as a number after 'Metasploit', which indicates a compatible Msf::Module.
class Msf::Modules::MetasploitClassCompatibilityError < Msf::Modules::Error
def initialize(attributes={})
super_attributes = {
:causal_message => 'Missing compatible Metasploit<major_version> class constant',
}.merge(attributes)
super(super_attributes)
end
end

View File

@ -10,8 +10,6 @@ module Msf::Modules::Namespace
# @return [nil] if such as class is not defined.
def metasploit_class
metasploit_class = nil
# don't search ancestors for the metasploit_class
#inherit = false
::Msf::Framework::Major.downto(1) do |major|
# Since we really only care about the deepest namespace, we don't
@ -29,6 +27,19 @@ module Msf::Modules::Namespace
metasploit_class
end
def metasploit_class!(module_path, module_reference_name)
metasploit_class = self.metasploit_class
unless metasploit_class
raise Msf::Modules::MetasploitClassCompatibilityError.new(
:module_path => module_path,
:module_reference_name => module_reference_name
)
end
metasploit_class
end
# Raises an error unless {Msf::Framework::VersionCore} and {Msf::Framework::VersionAPI} meet the minimum required
# versions defined in RequiredVersions in the module content.
#

View File

@ -1,20 +1,43 @@
require 'msf/core/modules/error'
# Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module}
# if the API or Core version does not meet the minimum requirements defined in the RequiredVersions constant in the
# {Msf::Modules::Loader::Base#read_module_content module content}.
class Msf::Modules::VersionCompatibilityError < StandardError
class Msf::Modules::VersionCompatibilityError < Msf::Modules::Error
# @param [Hash{Symbol => Float}] attributes
# @option attributes [Float] :minimum_api_version The minimum {Msf::Framework::VersionAPI} as defined in
# RequiredVersions.
# @option attributes [Float] :minimum_core_version The minimum {Msf::Framework::VersionCore} as defined in
# RequiredVersions.
def initialize(attributes={})
@module_path = attributes[:module_path]
@module_reference_name = attributes[:module_reference_name]
@minimum_api_version = attributes[:minimum_api_version]
@minimum_core_version = attributes[:minimum_core_version]
super("Failed to reload module (#{module_reference_name} from #{module_path}) due to version check " \
"(requires API:#{minimum_api_version} Core:#{minimum_core_version})")
message_parts = []
message_parts << 'version check'
if minimum_api_version or minimum_core_version
clause_parts = []
if minimum_api_version
clause_parts << "API >= #{minimum_api_version}"
end
if minimum_core_version
clause_parts << "Core >= #{minimum_core_version}"
end
clause = clause_parts.join(' and ')
message_parts << "(requires #{clause})"
end
causal_message = message_parts.join(' ')
super_attributes = {
:causal_message => causal_message
}.merge(attributes)
super(super_attributes)
end
# @return [Float] The minimum value of {Msf::Framework::VersionAPI} for the module to be compatible.

View File

@ -0,0 +1,101 @@
require 'spec_helper'
describe Msf::Modules::Error do
context 'instance methods' do
context '#initialize' do
include_context 'Msf::Modules::Error attributes'
context 'with :causal_message' do
subject do
described_class.new(:causal_message => causal_message)
end
it 'should include causal_message in error' do
subject.to_s.should == "Failed to load module due to #{causal_message}"
end
end
context 'with :causal_message and :module_path' do
subject do
described_class.new(
:causal_message => causal_message,
:module_path => module_path
)
end
it 'should include causal_message and module_path in error' do
subject.to_s.should == "Failed to load module (from #{module_path}) due to #{causal_message}"
end
end
context 'with :causal_message and :module_reference_name' do
subject do
described_class.new(
:causal_message => causal_message,
:module_reference_name => module_reference_name
)
end
it 'should include causal_message and module_reference_name in error' do
subject.to_s.should == "Failed to load module (#{module_reference_name}) due to #{causal_message}"
end
end
context 'with :causal_message, :module_path, and :module_reference_nam' do
subject do
described_class.new(
:causal_message => causal_message,
:module_path => module_path,
:module_reference_name => module_reference_name
)
end
it 'should include causal_message, module_path, and module_reference_name in error' do
subject.to_s.should == "Failed to load module (#{module_reference_name} from #{module_path}) due to #{causal_message}"
end
end
context 'with :module_path' do
subject do
described_class.new(:module_path => module_path)
end
it 'should use :module_path for module_path' do
subject.module_path.should == module_path
end
it 'should include module_path in error' do
subject.to_s.should == "Failed to load module (from #{module_path})"
end
end
context 'with :module_path and :module_reference_name' do
subject do
described_class.new(
:module_path => module_path,
:module_reference_name => module_reference_name
)
end
it 'should include module_path and module_reference_name in error' do
subject.to_s.should == "Failed to load module (#{module_reference_name} from #{module_path})"
end
end
context 'with :module_reference_name' do
subject do
described_class.new(:module_reference_name => module_reference_name)
end
it 'should use :module_reference_name for module_reference_name' do
subject.module_reference_name.should == module_reference_name
end
it 'should include module_reference_name in error' do
subject.to_s.should == "Failed to load module (#{module_reference_name})"
end
end
end
end
end

View File

@ -3,6 +3,8 @@ require 'spec_helper'
require 'msf/core'
describe Msf::Modules::Loader::Base do
include_context 'Msf::Modules::Loader::Base'
let(:described_class_pathname) do
root_pathname.join('lib', 'msf', 'core', 'modules', 'loader', 'base.rb')
end
@ -37,18 +39,6 @@ describe Msf::Modules::Loader::Base 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
@ -230,7 +220,7 @@ describe Msf::Modules::Loader::Base do
context 'instance methods' do
let(:module_manager) do
mock('Module Manager')
mock('Module Manager', :module_load_error_by_path => {})
end
subject do
@ -323,13 +313,14 @@ describe Msf::Modules::Loader::Base do
end
it 'should call #namespace_module_transaction with the module full name and :reload => true' do
subject.stub(:read_module_content => module_content)
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)
@ -339,7 +330,6 @@ describe Msf::Modules::Loader::Base do
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)
@ -348,7 +338,6 @@ describe Msf::Modules::Loader::Base do
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
@ -356,13 +345,29 @@ describe Msf::Modules::Loader::Base do
subject.load_module(parent_path, type, module_reference_name, :reload => true).should be_false
end
context 'with empty module content' do
before(:each) do
subject.stub(:read_module_content).with(parent_path, type, module_reference_name).and_return('')
end
it 'should return false' do
subject.load_module(parent_path, type, module_reference_name).should be_false
end
it 'should not attempt to make a new namespace_module' do
subject.should_not_receive(:namespace_module_transaction)
subject.load_module(parent_path, type, module_reference_name).should be_false
end
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)
module_content = mock('Module Content', :empty? => false)
subject.stub(:read_module_content).and_return(module_content)
end
context 'with Interrupt' do
@ -409,16 +414,8 @@ describe Msf::Modules::Loader::Base 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]}"
)
it 'should record the load error using the original error' do
subject.should_receive(:load_error).with(module_path, error)
subject.load_module(parent_path, type, module_reference_name).should be_false
end
end
@ -448,18 +445,8 @@ describe Msf::Modules::Loader::Base do
)
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}/)
it 'should record the load error using the Msf::Modules::VersionCompatibilityError' do
subject.should_receive(:load_error).with(module_path, version_compatibility_error)
subject.load_module(parent_path, type, module_reference_name).should be_false
end
end
@ -479,7 +466,7 @@ describe Msf::Modules::Loader::Base do
@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)
@namespace_module.stub(:metasploit_class! => metasploit_class)
subject.stub(:namespace_module_transaction).and_yield(@namespace_module)
@ -521,13 +508,8 @@ describe Msf::Modules::Loader::Base do
)
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)
it 'should record the load error' do
subject.should_receive(:load_error).with(module_path, version_compatibility_error)
subject.load_module(parent_path, type, module_reference_name).should be_false
end
@ -548,24 +530,27 @@ describe Msf::Modules::Loader::Base do
end
context 'without metasploit_class' do
let(:error) do
Msf::Modules::MetasploitClassCompatibilityError.new(
:module_path => module_path,
:module_reference_name => module_reference_name
)
end
before(:each) do
@namespace_module.stub(:metasploit_class).and_return(nil)
@namespace_module.stub(:metasploit_class!).with(module_path, module_reference_name).and_raise(error)
end
let(:error_message) do
'Missing Metasploit class constant'
end
it 'should log missing Metasploit class' do
subject.should_receive(:elog).with(error_message)
it 'should record load error' do
subject.should_receive(
:load_error
).with(
module_path,
kind_of(Msf::Modules::MetasploitClassCompatibilityError)
)
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
@ -583,7 +568,7 @@ describe Msf::Modules::Loader::Base do
end
before(:each) do
@namespace_module.stub(:metasploit_class => metasploit_class)
@namespace_module.stub(:metasploit_class! => metasploit_class)
end
it 'should check if it is usable' do

View File

@ -2,6 +2,126 @@ require 'spec_helper'
require 'msf/core'
require 'msf/core/modules/loader/directory'
describe Msf::Modules::Loader::Directory do
require 'msf/core'
describe Msf::Modules::Loader::Directory do
context 'instance methods' do
include_context 'Msf::Modules::Loader::Base'
let(:module_manager) do
mock('Module Manager')
end
let(:module_path) do
"#{parent_path}/exploits/#{module_reference_name}.rb"
end
let(:type) do
'exploit'
end
subject do
described_class.new(module_manager)
end
context '#load_module' do
context 'with existent module_path' do
let(:framework) do
framework = mock('Msf::Framework', :datastore => {})
events = mock('Events')
events.stub(:on_module_load)
events.stub(:on_module_created)
framework.stub(:events => events)
framework
end
let(:module_full_name) do
"#{type}/#{module_reference_name}"
end
let(:module_manager) do
Msf::ModuleManager.new(framework)
end
let(:module_reference_name) do
'windows/smb/ms08_067_netapi'
end
it 'should load a module that can be created' do
subject.load_module(parent_path, type, module_reference_name).should be_true
created_module = module_manager.create(module_full_name)
created_module.name.should == 'Microsoft Server Service Relative Path Stack Corruption'
end
end
context 'without existent module_path' do
let(:module_reference_name) do
'osx/armle/safari_libtiff'
end
let(:error) do
Errno::ENOENT.new(module_path)
end
before(:each) do
module_manager.stub(:file_changed? => true)
module_manager.stub(:module_load_error_by_path => {})
end
it 'should not raise an error' do
File.exist?(module_path).should be_false
expect {
subject.load_module(parent_path, type, module_reference_name)
}.to_not raise_error
end
it 'should return false' do
File.exist?(module_path).should be_false
subject.load_module(parent_path, type, module_reference_name).should be_false
end
end
end
context '#read_module_content' do
context 'with non-existent module_path' do
let(:module_reference_name) do
'osx/armle/safari_libtiff'
end
before(:each) do
subject.stub(:load_error).with(module_path, kind_of(Errno::ENOENT))
end
# this ensures that the File.exist?(module_path) checks are checking the same path as the code under test
it 'should attempt to open the expected module_path' do
File.should_receive(:open).with(module_path, 'rb')
File.exist?(module_path).should be_false
subject.send(:read_module_content, parent_path, type, module_reference_name)
end
it 'should not raise an error' do
expect {
subject.send(:read_module_content, parent_path, type, module_reference_name)
}.to_not raise_error
end
it 'should return an empty string' do
subject.send(:read_module_content, parent_path, type, module_reference_name).should == ''
end
it 'should record the load error' do
subject.should_receive(:load_error).with(module_path, kind_of(Errno::ENOENT))
subject.send(:read_module_content, parent_path, type, module_reference_name).should == ''
end
end
end
end
end

View File

@ -0,0 +1,7 @@
require 'spec_helper'
require 'msf/core/modules/metasploit_class_compatibility_error'
describe Msf::Modules::MetasploitClassCompatibilityError do
it_should_behave_like 'Msf::Modules::Error subclass #initialize'
end

View File

@ -0,0 +1,267 @@
require 'spec_helper'
require 'msf/core'
require 'msf/core/modules/namespace'
describe Msf::Modules::Namespace do
let(:module_path) do
"parent/path/type_directory/#{module_reference_name}.rb"
end
let(:module_reference_name) do
'module/reference/name'
end
subject do
mod = Module.new
mod.extend described_class
mod
end
context 'metasploit_class' do
before(:each) do
if major
subject.const_set("Metasploit#{major}", Class.new)
end
end
context 'without Metasploit<n> constant defined' do
let(:major) do
nil
end
it 'should not be defined' do
metasploit_constants = subject.constants.select { |constant|
constant.to_s =~ /Metasploit/
}
metasploit_constants.should be_empty
end
end
context 'with Metasploit1 constant defined' do
let(:major) do
1
end
it 'should be defined' do
subject.const_defined?('Metasploit1').should be_true
end
it 'should return the class' do
subject.metasploit_class.should be_a Class
end
end
context 'with Metasploit2 constant defined' do
let(:major) do
2
end
it 'should be defined' do
subject.const_defined?('Metasploit2').should be_true
end
it 'should return the class' do
subject.metasploit_class.should be_a Class
end
end
context 'with Metasploit3 constant defined' do
let(:major) do
3
end
it 'should be defined' do
subject.const_defined?('Metasploit3').should be_true
end
it 'should return the class' do
subject.metasploit_class.should be_a Class
end
end
context 'with Metasploit4 constant defined' do
let(:major) do
4
end
it 'should be defined' do
subject.const_defined?('Metasploit4').should be_true
end
it 'should return the class' do
subject.metasploit_class.should be_a Class
end
end
context 'with Metasploit5 constant defined' do
let(:major) do
5
end
it 'should be defined' do
subject.const_defined?('Metasploit5').should be_true
end
it 'should be newer than Msf::Framework::Major' do
major.should > Msf::Framework::Major
end
it 'should return nil' do
subject.metasploit_class.should be_nil
end
end
end
context 'metasploit_class!' do
it 'should call metasploit_class' do
subject.should_receive(:metasploit_class).and_return(Class.new)
subject.metasploit_class!(module_path, module_reference_name)
end
context 'with metasploit_class' do
let(:metasploit_class) do
Class.new
end
before(:each) do
subject.stub(:metasploit_class => metasploit_class)
end
it 'should return the metasploit_class' do
subject.metasploit_class!(module_path, module_reference_name).should == metasploit_class
end
end
context 'without metasploit_class' do
before(:each) do
subject.stub(:metasploit_class => nil)
end
it 'should raise a Msf::Modules::MetasploitClassCompatibilityError' do
expect {
subject.metasploit_class!(module_path, module_reference_name)
}.to raise_error(Msf::Modules::MetasploitClassCompatibilityError)
end
context 'the Msf::Modules::MetasploitClassCompatibilityError' do
it 'should include the module path' do
error = nil
begin
subject.metasploit_class!(module_path, module_reference_name)
rescue Msf::Modules::MetasploitClassCompatibilityError => error
end
error.should_not be_nil
error.to_s.should include(module_path)
end
it 'should include the module reference name' do
error = nil
begin
subject.metasploit_class!(module_path, module_reference_name)
rescue Msf::Modules::MetasploitClassCompatibilityError => error
end
error.should_not be_nil
error.to_s.should include(module_reference_name)
end
end
end
end
context 'version_compatible!' do
context 'without RequiredVersions' do
it 'should not be defined' do
subject.const_defined?('RequiredVersions').should be_false
end
it 'should not raise an error' do
expect {
subject.version_compatible!(module_path, module_reference_name)
}.to_not raise_error
end
end
context 'with RequiredVersions defined' do
let(:minimum_api_version) do
1
end
let(:minimum_core_version) do
1
end
before(:each) do
subject.const_set(
:RequiredVersions,
[
minimum_core_version,
minimum_api_version
]
)
end
context 'with minimum Core version' do
it 'should be <= Msf::Framework::VersionCore' do
minimum_core_version.should <= Msf::Framework::VersionCore
end
context 'without minimum API version' do
let(:minimum_api_version) do
2
end
it 'should be > Msf::Framework::VersionAPI' do
minimum_api_version.should > Msf::Framework::VersionAPI
end
it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
end
context 'with minimum API version' do
it 'should not raise an error' do
expect {
subject.version_compatible!(module_path, module_reference_name)
}.to_not raise_error(Msf::Modules::VersionCompatibilityError)
end
end
end
context 'without minimum Core version' do
let(:minimum_core_version) do
5
end
it 'should be > Msf::Framework::VersionCore' do
minimum_core_version.should > Msf::Framework::VersionCore
end
context 'without minimum API version' do
let(:minimum_api_version) do
2
end
it 'should be > Msf::Framework::VersionAPI' do
minimum_api_version.should > Msf::Framework::VersionAPI
end
it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
end
context 'with minimum API version' do
it 'should be <= Msf::Framework::VersionAPI' do
minimum_api_version <= Msf::Framework::VersionAPI
end
it_should_behave_like 'Msf::Modules::VersionCompatibilityError'
end
end
end
end
end

View File

@ -0,0 +1,62 @@
require 'spec_helper'
describe Msf::Modules::VersionCompatibilityError do
it_should_behave_like 'Msf::Modules::Error subclass #initialize' do
let(:minimum_api_version) do
1
end
let(:minimum_core_version) do
2
end
it 'should say cause was version check' do
subject.to_s.should match(/due to version check/)
end
context 'with :minimum_api_version' do
subject do
described_class.new(
:minimum_api_version => minimum_api_version
)
end
it 'should set minimum_api_version' do
subject.minimum_api_version.should == minimum_api_version
end
it 'should include minimum_api_version in error' do
subject.to_s.should match(/due to version check \(requires API >= #{minimum_api_version}\)/)
end
end
context 'with :minimum_api_version and :minimum_core_version' do
subject do
described_class.new(
:minimum_api_version => minimum_api_version,
:minimum_core_version => minimum_core_version
)
end
it 'should include minimum_api_version and minimum_core_version in error' do
subject.to_s.should match(/due to version check \(requires API >= #{minimum_api_version} and Core >= #{minimum_core_version}\)/)
end
end
context 'with :minimum_core_version' do
subject do
described_class.new(
:minimum_core_version => minimum_core_version
)
end
it 'should set minimum_core_version' do
subject.minimum_core_version.should == minimum_core_version
end
it 'should include minimum_core_version in error' do
subject.to_s.should match(/due to version check \(requires Core >= #{minimum_core_version}\)/)
end
end
end
end

View File

@ -8,6 +8,11 @@ root_pathname = spec_pathname.join('..').expand_path
lib_pathname = root_pathname.join('lib')
$LOAD_PATH.unshift(lib_pathname.to_s)
# must be first require and started before any other requires so that it can measure coverage of all following required
# code. It is after the rubygems and bundler only because Bundler.setup supplies the LOAD_PATH to simplecov.
require 'simplecov'
SimpleCov.start
require 'rspec/core'
# Requires supporting ruby files with custom matchers and macros, etc,

View File

@ -0,0 +1,13 @@
shared_context 'Msf::Modules::Error attributes' do
let(:causal_message) do
'rspec'
end
let(:module_path) do
"parent/path/type/#{module_reference_name}.rb"
end
let(:module_reference_name) do
'module/reference/name'
end
end

View File

@ -0,0 +1,13 @@
shared_context "Msf::Modules::Loader::Base" do
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
end

View File

@ -0,0 +1,26 @@
shared_examples_for 'Msf::Modules::Error subclass #initialize' do
context 'instance methods' do
context '#initialize' do
include_context 'Msf::Modules::Error attributes'
subject do
described_class.new(
:module_path => module_path,
:module_reference_name => module_reference_name
)
end
it 'should include causal message in error' do
subject.to_s.should match(/due to .*/)
end
it 'should set module_path' do
subject.module_path.should == module_path
end
it 'should set module_reference_name' do
subject.module_reference_name.should == module_reference_name
end
end
end
end

View File

@ -0,0 +1,32 @@
shared_examples_for 'Msf::Modules::VersionCompatibilityError' do
let(:error) do
begin
subject.version_compatible!(module_path, module_reference_name)
rescue Msf::Modules::VersionCompatibilityError => error
end
error
end
it 'should be raised' do
expect {
subject.version_compatible!(module_path, module_reference_name)
}.to raise_error(Msf::Modules::VersionCompatibilityError)
end
it 'should include minimum API version' do
error.to_s.should include(minimum_api_version.to_s)
end
it 'should include minimum Core version' do
error.to_s.should include(minimum_core_version.to_s)
end
it 'should include module path' do
error.to_s.should include(module_path)
end
it 'should include module reference name' do
error.to_s.should include(module_reference_name)
end
end