diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index ac440dd470..1435d0282d 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -36,6 +36,9 @@ module Exploit::Remote::HttpServer ], Exploit::Remote::HttpServer ) + # Used to keep track of resources added to the service manager by + # this module. see #add_resource and #cleanup + @my_resources = [] @service_path = nil end @@ -202,6 +205,39 @@ module Exploit::Remote::HttpServer add_resource(uopts) end + # Set {#on_request_uri} to handle the given +uri+ in addition to the one + # specified by the user in URIPATH. + # + # @note This MUST be called from {#primer} so that the service has been set + # up but we have not yet entered the listen/accept loop. + # + # @param uri [String] The resource URI that should be handled by + # {#on_request_uri}. + # @return [void] + def hardcoded_uripath(uri) + proc = Proc.new do |cli, req| + on_request_uri(cli, req) + end + + vprint_status("Adding hardcoded uri #{uri}") + begin + add_resource({'Path' => uri, 'Proc' => proc}) + rescue RuntimeError => e + print_error("This module requires a hardcoded uri at #{uri}. Can't run while other modules are using it.") + raise e + end + end + + # Take care of removing any resources that we created + def cleanup + # Must dup here because remove_resource modifies @my_resources + @my_resources.dup.each do |resource| + remove_resource(resource) + end + + super + end + # # Return a Hash containing a best guess at the actual browser and operating # system versions, based on the User-Agent header. @@ -358,9 +394,16 @@ module Exploit::Remote::HttpServer # NOTE: Calling #add_resource will change the results of subsequent calls # to #get_resource! # + # @return (see Rex::Service#add_resource) def add_resource(opts) @service_path = opts['Path'] - service.add_resource(opts['Path'], opts) + res = service.add_resource(opts['Path'], opts) + + # This has to go *after* the call to service.add_resource in case + # the service manager doesn't like it for some reason and raises. + @my_resources.push(opts['Path']) + + res end # @@ -455,7 +498,11 @@ module Exploit::Remote::HttpServer # Removes a URI resource. # def remove_resource(name) - service.remove_resource(name) + # Guard against removing resources added by other modules + if @my_resources.include?(name) + @my_resources.delete(name) + service.remove_resource(name) + end end # diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index 77a2bf0fd7..2fbafdc1c1 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -173,6 +173,7 @@ class OptString < OptBase def valid?(value=self.value) value = normalize(value) + return false unless value.kind_of?(String) or value.kind_of?(NilClass) return false if empty_required_value?(value) return super end @@ -263,6 +264,7 @@ class OptEnum < OptBase def valid?(value=self.value) return false if empty_required_value?(value) + return true if value.nil? and !required? (value and self.enums.include?(value.to_s)) end @@ -330,10 +332,17 @@ class OptAddress < OptBase def valid?(value) return false if empty_required_value?(value) + return false unless value.kind_of?(String) or value.kind_of?(NilClass) if (value != nil and value.empty? == false) begin - ::Rex::Socket.getaddress(value, true) + getaddr_result = ::Rex::Socket.getaddress(value, true) + # Covers a wierdcase where an incomplete ipv4 address will have it's + # missing octets filled in with 0's. (e.g 192.168 become 192.0.0.168) + # which does not feel like a legit behaviour + if value =~ /^\d{1,3}(\.\d{1,3}){1,3}$/ + return false unless value =~ Rex::Socket::MATCH_IPV4 + end rescue return false end @@ -354,6 +363,7 @@ class OptAddressRange < OptBase end def normalize(value) + return nil unless value.kind_of?(String) if (value =~ /^file:(.*)/) path = $1 return false if not File.exists?(path) or File.directory?(path) @@ -373,9 +383,12 @@ class OptAddressRange < OptBase def valid?(value) return false if empty_required_value?(value) + return false unless value.kind_of?(String) or value.kind_of?(NilClass) if (value != nil and value.empty? == false) - walker = Rex::Socket::RangeWalker.new(normalize(value)) + normalized = normalize(value) + return false if normalized.nil? + walker = Rex::Socket::RangeWalker.new(normalized) if (not walker or not walker.valid?) return false end @@ -474,7 +487,7 @@ class OptRegexp < OptBase Regexp.compile(value) return true - rescue RegexpError + rescue RegexpError, TypeError return false end end diff --git a/lib/rex/compat.rb b/lib/rex/compat.rb index cd01eb7072..e594c55a17 100644 --- a/lib/rex/compat.rb +++ b/lib/rex/compat.rb @@ -131,9 +131,11 @@ def self.open_browser(url='http://metasploit.com/') # Search through the PATH variable (if it exists) and chose a browser # We are making an assumption about the nature of "PATH" so tread lightly if defined? ENV['PATH'] - # "sensible-browser" opens the "default" browser in Ubuntu and others, so try that first - # but also provide fallbacks - ['sensible-browser', 'firefox', 'opera', 'chromium-browser', 'konqueror'].each do |browser| + # "xdg-open" is more general than "sensible-browser" and can be useful for lots of + # file types -- text files, pcaps, or URLs. It's nearly always + # going to use the application the user is expecting. If we're not + # on something Debian-based, fall back to likely browsers. + ['xdg-open', 'sensible-browser', 'firefox', 'firefox-bin', 'opera', 'konqueror', 'chromium-browser'].each do |browser| ENV['PATH'].split(':').each do |path| # Does the browser exists? if File.exists?("#{path}/#{browser}") @@ -143,9 +145,6 @@ def self.open_browser(url='http://metasploit.com/') end end end - - # If nothing else worked, default to firefox - system("firefox #{url} &") end end diff --git a/lib/rex/file.rb b/lib/rex/file.rb index 99e82c40c0..51337657da 100644 --- a/lib/rex/file.rb +++ b/lib/rex/file.rb @@ -14,7 +14,54 @@ module Rex module FileUtils # - # This methods cleans the supplied path of directory traversal sequences + # This method joins the paths together in Unix format. + # + def self.normalize_unix_path(*strs) + new_str = strs * '/' + new_str = new_str.gsub!("//", "/") while new_str.index("//") + + new_str + end + + # + # This method joins the paths together in Windows format. + # All reserved characters will be filtered out, including: + # " * : < > ? \ / | + # + def self.normalize_win_path(*strs) + # Convert to the same format so the parsing is easier + s = strs * '\\' + + # Filter out double slashes + s = s.gsub(/\\\\/, '\\') while s.index('\\\\') + + # Keep the trailing slash if exists + trailing_s = ('\\' if s =~ /\\$/) || '' + + # Check the items (fie/dir) individually + s = s.split(/\\/) + + # Parse the path prefix + prefix = (s[0] || '').gsub(/[\*<>\?\/]/, '') + + # Delete the original prefix. We want the new one later. + s.delete_at(0) + + # Filter out all the reserved characters + s.map! {|e| e.gsub(/["\*:<>\?\\\/|]/, '') } + + # Put the modified prefix back + s.insert(0, prefix) + + # And then safely join the items + s *= '\\' + + # Add the trailing slash back if exists + s << trailing_s + end + + # + # This method cleans the supplied path of directory traversal sequences # It must accept path/with/..a/folder../starting/or/ending/in/two/dots # but clean ../something as well as path/with/..\traversal # diff --git a/lib/rex/random_identifier_generator.rb b/lib/rex/random_identifier_generator.rb new file mode 100644 index 0000000000..b85ae55d50 --- /dev/null +++ b/lib/rex/random_identifier_generator.rb @@ -0,0 +1,168 @@ + +# A quick way to produce unique random strings that follow the rules of +# identifiers, i.e., begin with a letter and contain only alphanumeric +# characters and underscore. +# +# The advantage of using this class over, say, {Rex::Text.rand_text_alpha} +# each time you need a new identifier is that it ensures you don't have +# collisions. +# +# @example +# vars = Rex::RandomIdentifierGenerator.new +# asp_code = <<-END_CODE +# Sub #{vars[:func]}() +# Dim #{vars[:fso]} +# Set #{vars[:fso]} = CreateObject("Scripting.FileSystemObject") +# ... +# End Sub +# #{vars[:func]} +# END_CODE +# +class Rex::RandomIdentifierGenerator + + # Raised when a RandomIdentifierGenerator cannot create any more + # identifiers without collisions. + class ExhaustedSpaceError < StandardError; end + + # Default options + DefaultOpts = { + # Arbitrary + :max_length => 12, + :min_length => 3, + # This should be pretty universal for identifier rules + :char_set => Rex::Text::AlphaNumeric+"_", + :first_char_set => Rex::Text::LowerAlpha + } + + # @param opts [Hash] Options, see {DefaultOpts} for default values + # @option opts :max_length [Fixnum] + # @option opts :min_length [Fixnum] + # @option opts :char_set [String] + def initialize(opts={}) + # Holds all identifiers. + @value_by_name = {} + # Inverse of value_by_name so we can ensure uniqueness without + # having to search through the whole list of values + @name_by_value = {} + + @opts = DefaultOpts.merge(opts) + if @opts[:min_length] < 1 || @opts[:max_length] < 1 || @opts[:max_length] < @opts[:min_length] + raise ArgumentError, "Invalid length options" + end + + # This is really just the maximum number of shortest names. This + # will still be a pretty big number most of the time, so don't + # bother calculating the real one, which will potentially be + # expensive, since we're talking about a 36-digit decimal number to + # represent the total possibilities for the range of 10- to + # 20-character identifiers. + # + # 26 because the first char is lowercase alpha, (min_length - 1) and + # not just min_length because it includes that first alpha char. + @max_permutations = 26 * (@opts[:char_set].length ** (@opts[:min_length]-1)) + # The real number of permutations could be calculated thusly: + #((@opts[:min_length]-1) .. (@opts[:max_length]-1)).reduce(0) { |a, e| + # a + (26 * @opts[:char_set].length ** e) + #} + end + + # Return a unique random identifier for +name+, generating a new one + # if necessary. + # + # @param name [Symbol] A descriptive, intention-revealing name for an + # identifier. This is what you would normally call the variable if + # you weren't generating it. + # @return [String] + def get(name) + return @value_by_name[name] if @value_by_name[name] + + @value_by_name[name] = generate + @name_by_value[@value_by_name[name]] = name + + @value_by_name[name] + end + alias [] get + + # Add a new identifier. Its name will be checked for uniqueness among + # previously-generated names. + # + # @note This should be called *before* any calls to {#get} to avoid + # potential collisions. If you do hit a collision, this method will + # raise. + # + # @param name (see #get) + # @param value [String] The identifier that will be returned by + # subsequent calls to {#get} with the sane +name+. + # @raise RuntimeError if +value+ already exists + # @return [void] + def store(name, value) + + case @name_by_value[value] + when name + # we already have this value and it is associated with this name + # nothing to do here + when nil + # don't have this value yet, so go ahead and just insert + @value_by_name[name] = value + @name_by_value[value] = name + else + # then the caller is trying to insert a duplicate + raise RuntimeError, "Value is not unique!" + end + + self + end + + # Create a random string that satisfies most languages' requirements + # for identifiers. In particular, with a default configuration, the + # first character will always be lowercase alpha (unless modified by a + # block), and the whole thing will contain only a-zA-Z0-9_ characters. + # + # If called with a block, the block will be given the identifier before + # uniqueness checks. The block's return value will be the new + # identifier. Note that the block may be called multiple times if it + # returns a non-unique value. + # + # @note Calling this method with a block that returns only values that + # this generator already contains will result in an infinite loop. + # + # @example + # rig = Rex::RandomIdentifierGenerator.new + # const = rig.generate { |val| val.capitalize } + # rig.insert(:SOME_CONSTANT, const) + # ruby_code = <<-EOC + # #{rig[:SOME_CONSTANT]} = %q^generated ruby constant^ + # def #{rig[:my_method]}; ...; end + # EOC + # + # @param len [Fixnum] Avoid setting this unless a specific size is + # necessary. Default is random within range of min .. max + # @return [String] A string that matches [a-z][a-zA-Z0-9_]* + # @yield [String] The identifier before uniqueness checks. This allows + # you to modify the value and still avoid collisions. + def generate(len=nil) + raise ArgumentError, "len must be positive integer" if len && len < 1 + raise ExhaustedSpaceError if @value_by_name.length >= @max_permutations + + # pick a random length within the limits + len ||= rand(@opts[:min_length] .. (@opts[:max_length])) + + ident = "" + + # XXX: Infinite loop if block returns only values we've already + # generated. + loop do + ident = Rex::Text.rand_base(1, "", @opts[:first_char_set]) + ident << Rex::Text.rand_base(len-1, "", @opts[:char_set]) + if block_given? + ident = yield ident + end + # Try to make another one if it collides with a previously + # generated one. + break unless @name_by_value.key?(ident) + end + + ident + end + +end diff --git a/lib/rex/socket/range_walker.rb b/lib/rex/socket/range_walker.rb index f8c57e95fc..baa0b60d76 100644 --- a/lib/rex/socket/range_walker.rb +++ b/lib/rex/socket/range_walker.rb @@ -95,9 +95,12 @@ class RangeWalker return false if ip_part.nil? or ip_part.empty? or mask_part.nil? or mask_part.empty? return false if mask_part !~ /^[0-9]{1,2}$/ # Illegal mask -- numerals only return false if mask_part.to_i > 32 # This too -- between 0 and 32. + if ip_part =~ /^\d{1,3}(\.\d{1,3}){1,3}$/ + return false unless ip_part =~ Rex::Socket::MATCH_IPV4 + end begin - Rex::Socket.addr_atoi(ip_part) # This allows for "www.metasploit.com/24" which is fun. - rescue Resolv::ResolvError + Rex::Socket.getaddress(ip_part) # This allows for "www.metasploit.com/24" which is fun. + rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT return false # Can't resolve the ip_part, so bail. end diff --git a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb index 7d9e23a81a..d960dd9ace 100644 --- a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'DLink DIR 645 Password Extractor', + 'Name' => 'D-Link DIR 645 Password Extractor', 'Description' => %q{ This module exploits an authentication bypass vulnerability in DIR 645 < v1.03. With this vulnerability you are able to extract the password for the remote diff --git a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb index 9f792dffc2..a152d4868b 100644 --- a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb @@ -14,9 +14,9 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'DLink DSL 320B Password Extractor', + 'Name' => 'D-Link DSL 320B Password Extractor', 'Description' => %q{ - This module exploits an authentication bypass vulnerability in DLink DSL 320B + This module exploits an authentication bypass vulnerability in D-Link DSL 320B <=v1.23. This vulnerability allows to extract the credentials for the remote management interface. }, diff --git a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb index 1d79e8371a..ddc8b1213d 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb @@ -18,9 +18,9 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'DLink DIR-300A / DIR-320 / DIR-615D HTTP Login Utility', + 'Name' => 'D-Link DIR-300A / DIR-320 / DIR-615D HTTP Login Utility', 'Description' => %q{ - This module attempts to authenticate to different DLink HTTP management + This module attempts to authenticate to different D-Link HTTP management services. It has been tested on D-Link DIR-300 Hardware revision A, D-Link DIR-615 Hardware revision D and D-Link DIR-320 devices. It is possible that this module also works with other models. @@ -71,9 +71,9 @@ class Metasploit3 < Msf::Auxiliary @uri = "/login.php" if is_dlink? - vprint_good("#{target_url} - DLink device detected") + vprint_good("#{target_url} - D-Link device detected") else - vprint_error("#{target_url} - Dlink device doesn't detected") + vprint_error("#{target_url} - D-Link device doesn't detected") return end @@ -100,7 +100,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? 'https' : 'http'), :user => user, :pass => pass, - :proof => "WEBAPP=\"DLink Management Interface\", PROOF=#{response.to_s}", + :proof => "WEBAPP=\"D-Link Management Interface\", PROOF=#{response.to_s}", :active => true ) diff --git a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb index 294e1d3981..c3893aed0e 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb @@ -18,9 +18,9 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'DLink DIR-615H HTTP Login Utility', + 'Name' => 'D-Link DIR-615H HTTP Login Utility', 'Description' => %q{ - This module attempts to authenticate to different DLink HTTP management + This module attempts to authenticate to different D-Link HTTP management services. It has been tested successfully on D-Link DIR-615 Hardware revision H devices. It is possible that this module also works with other models. }, @@ -56,9 +56,9 @@ class Metasploit3 < Msf::Auxiliary @uri = "/login.htm" if is_dlink? - vprint_good("#{target_url} - DLink device detected") + vprint_good("#{target_url} - D-Link device detected") else - vprint_error("#{target_url} - Dlink device doesn't detected") + vprint_error("#{target_url} - D-Link device doesn't detected") return end @@ -109,7 +109,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? 'https' : 'http'), :user => user, :pass => pass, - :proof => "WEBAPP=\"Dlink Management Interface\", PROOF=#{response.to_s}", + :proof => "WEBAPP=\"D-Link Management Interface\", PROOF=#{response.to_s}", :active => true ) diff --git a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb index 15f4e0b6e4..71d46964f4 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb @@ -18,9 +18,9 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'DLink DIR-300B / DIR-600B / DIR-815 / DIR-645 HTTP Login Utility', + 'Name' => 'D-Link DIR-300B / DIR-600B / DIR-815 / DIR-645 HTTP Login Utility', 'Description' => %q{ - This module attempts to authenticate to different DLink HTTP management + This module attempts to authenticate to different D-Link HTTP management services. It has been tested successfully on D-Link DIR-300 Hardware revision B, D-Link DIR-600 Hardware revision B, D-Link DIR-815 Hardware revision A and DIR-645 Hardware revision A devices.It is possible that this module also works with other @@ -72,9 +72,9 @@ class Metasploit3 < Msf::Auxiliary @uri = "/session.cgi" if is_dlink? - vprint_good("#{target_url} - DLink device detected") + vprint_good("#{target_url} - D-Link device detected") else - vprint_error("#{target_url} - Dlink device doesn't detected") + vprint_error("#{target_url} - D-Link device doesn't detected") return end @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Auxiliary :sname => (ssl ? 'https' : 'http'), :user => user, :pass => pass, - :proof => "WEBAPP=\"Dlink Management Interface\", PROOF=#{response.to_s}", + :proof => "WEBAPP=\"D-Link Management Interface\", PROOF=#{response.to_s}", :active => true ) diff --git a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb index 3f07b9fa28..aaf9a51a88 100644 --- a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb @@ -17,12 +17,12 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'DLink DIR-645 / DIR-815 diagnostic.php Command Execution', + 'Name' => 'D-Link DIR-645 / DIR-815 diagnostic.php Command Execution', 'Description' => %q{ - Some DLink Routers are vulnerable to OS Command injection in the web interface. + Some D-Link Routers are vulnerable to OS Command injection in the web interface. On DIR-645 versions prior 1.03 authentication isn't needed to exploit it. On version 1.03 authentication is needed in order to trigger the vulnerability, which - has been fixed definitely on version 1.04. Other DLink products, like DIR-300 rev B + has been fixed definitely on version 1.04. Other D-Link products, like DIR-300 rev B and DIR-600, are also affected by this vulnerability. Not every device includes wget which we need for deploying our payload. On such devices you could use the cmd generic payload and try to start telnetd or execute other commands. Since it is a @@ -155,7 +155,7 @@ class Metasploit3 < Msf::Exploit::Remote # # download payload # - print_status("#{rhost}:#{rport} - Asking the DLink device to download #{service_url}") + print_status("#{rhost}:#{rport} - Asking the D-Link device to download #{service_url}") #this filename is used to store the payload on the device filename = rand_text_alpha_lower(8) @@ -168,7 +168,7 @@ class Metasploit3 < Msf::Exploit::Remote # wait for payload download if (datastore['DOWNHOST']) - print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the DLink device to download the payload") + print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the D-Link device to download the payload") select(nil, nil, nil, datastore['HTTP_DELAY']) else wait_linux_payload @@ -179,7 +179,7 @@ class Metasploit3 < Msf::Exploit::Remote # chmod # cmd = "chmod 777 /tmp/#{filename}" - print_status("#{rhost}:#{rport} - Asking the DLink device to chmod #{downfile}") + print_status("#{rhost}:#{rport} - Asking the D-Link device to chmod #{downfile}") res = request(cmd,uri) if (!res) fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") @@ -189,7 +189,7 @@ class Metasploit3 < Msf::Exploit::Remote # execute # cmd = "/tmp/#{filename}" - print_status("#{rhost}:#{rport} - Asking the DLink device to execute #{downfile}") + print_status("#{rhost}:#{rport} - Asking the D-Link device to execute #{downfile}") res = request(cmd,uri) if (!res) fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") diff --git a/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb b/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb index 877220d1e1..134592891b 100644 --- a/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb @@ -212,7 +212,7 @@ class Metasploit3 < Msf::Exploit::Remote # # download payload # - print_status("#{rhost}:#{rport} - Asking the DLink device to take and execute #{service_url}") + print_status("#{rhost}:#{rport} - Asking the D-Link device to take and execute #{service_url}") #this filename is used to store the payload on the device filename = rand_text_alpha_lower(8) @@ -225,7 +225,7 @@ class Metasploit3 < Msf::Exploit::Remote # wait for payload download if (datastore['DOWNHOST']) - print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the DLink device to download the payload") + print_status("#{rhost}:#{rport} - Giving #{datastore['HTTP_DELAY']} seconds to the D-Link device to download the payload") select(nil, nil, nil, datastore['HTTP_DELAY']) else wait_linux_payload diff --git a/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb b/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb index 8e772bc6b9..2612ff32ce 100644 --- a/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb +++ b/modules/exploits/windows/browser/adobe_flash_mp4_cprt.rb @@ -170,18 +170,14 @@ class Metasploit3 < Msf::Exploit::Remote end end + def primer + # "/test.mp4" is currently hard-coded in the swf file, so we need to add to resource + hardcoded_uripath("/test.mp4") + end + def exploit @swf = create_swf super - - # - # "/test.mp4" is currently hard-coded in the swf file, so we need to add to resource - # - proc = Proc.new do |cli, req| - self.add_resource({'Path' => "/test.mp4", 'Proc' => proc}) rescue nil - on_request_uri(cli, req) - end - end def on_request_uri(cli, request) @@ -275,12 +271,6 @@ pluginspage="http://www.macromedia.com/go/getflashplayer"> send_response(cli, html, {'Content-Type'=>'text/html'}) end - def cleanup - print_status("Removing mp4 resource") - remove_resource('/test.mp4') rescue nil - super - end - def create_swf path = ::File.join( Msf::Config.install_root, "data", "exploits", "CVE-2012-0754.swf" ) fd = ::File.open( path, "rb" ) diff --git a/modules/exploits/windows/browser/adobe_flash_otf_font.rb b/modules/exploits/windows/browser/adobe_flash_otf_font.rb index 30df7f47f3..71033b3ca8 100644 --- a/modules/exploits/windows/browser/adobe_flash_otf_font.rb +++ b/modules/exploits/windows/browser/adobe_flash_otf_font.rb @@ -204,16 +204,15 @@ class Metasploit3 < Msf::Exploit::Remote html = html.gsub(/^\t\t/, '') - # we need to handle direct /pay.txt requests - proc = Proc.new do |cli, req| - on_request_uri(cli, req) - end - add_resource({'Path' => "/#{@resource_name}.txt", 'Proc' => proc}) rescue nil - print_status("Sending HTML") send_response(cli, html, {'Content-Type'=>'text/html'}) end + def primer + # we need to handle direct /pay.txt requests + hardcoded_uripath("/#{@resource_name}.txt") + end + def exploit @swf = create_swf @resource_name = Rex::Text.rand_text_alpha(5) @@ -235,10 +234,4 @@ class Metasploit3 < Msf::Exploit::Remote return swf end - def cleanup - vprint_status("Removing txt resource") - remove_resource("/#{@resource_name}.txt") rescue nil - super - end - end diff --git a/modules/exploits/windows/browser/citrix_gateway_actx.rb b/modules/exploits/windows/browser/citrix_gateway_actx.rb index 68d0d03894..58ad77d6c1 100644 --- a/modules/exploits/windows/browser/citrix_gateway_actx.rb +++ b/modules/exploits/windows/browser/citrix_gateway_actx.rb @@ -60,6 +60,10 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultTarget' => 0)) end + def primer + hardcoded_uripath("/epaq") + end + def exploit @ocx = ::File.read(::File.join(Msf::Config.install_root, 'data', 'exploits', 'CVE-2011-2882', 'nsepa.ocx')) super @@ -184,12 +188,6 @@ class Metasploit3 < Msf::Exploit::Remote html = html.gsub(/^\t\t/, '') - # we need to handle direct /epaq requests - proc = Proc.new do |cli, req| - on_request_uri(cli, req) - end - - add_resource({'Path' => "/epaq", 'Proc' => proc}) rescue nil print_status("Sending #{self.name} HTML") send_response(cli, html, { 'Content-Type' => 'text/html' }) end diff --git a/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb b/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb index 799193ab1e..28b2921c72 100644 --- a/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb +++ b/modules/exploits/windows/browser/honeywell_hscremotedeploy_exec.rb @@ -55,6 +55,10 @@ class Metasploit3 < Msf::Exploit::Remote 'DefaultTarget' => 0)) end + def primer + hardcoded_uripath("/SystemDisplays/RemoteInstallWelcome.hta") + end + def exploit @var_exename = rand_text_alpha(5 + rand(5)) + ".exe" @dropped_files = [ @@ -198,13 +202,6 @@ SetLocale(#{var_origLoc})|) EOS - # we need to handle direct /SystemDisplays/RemoteInstallWelcome.hta requests - proc = Proc.new do |cli, req| - on_request_uri(cli, req) - end - - add_resource({'Path' => "/SystemDisplays/RemoteInstallWelcome.hta", 'Proc' => proc}) rescue nil - print_status("Sending html") send_response(cli, html, {'Content-Type'=>'text/html'}) diff --git a/msfcli b/msfcli index fd88f6b28a..02c0f6c493 100755 --- a/msfcli +++ b/msfcli @@ -16,15 +16,7 @@ while File.symlink?(msfbase) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) -require 'fastlib' -require 'msfenv' - - -$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] - require 'rex' -require 'msf/ui' -require 'msf/base' Indent = ' ' @@ -61,6 +53,12 @@ module_class = "exploit" if(exploit_name == "-h") usage() +else + $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] + require 'fastlib' + require 'msfenv' + require 'msf/ui' + require 'msf/base' end # Initialize the simplified framework instance. diff --git a/spec/file_fixtures/short_address_list.txt b/spec/file_fixtures/short_address_list.txt new file mode 100644 index 0000000000..7e38d1f4b4 --- /dev/null +++ b/spec/file_fixtures/short_address_list.txt @@ -0,0 +1,5 @@ +192.168.1.1 +192.168.1.2 +192.168.1.3 +192.168.1.4 +192.168.1.5 \ No newline at end of file diff --git a/spec/file_fixtures/string_list.txt b/spec/file_fixtures/string_list.txt new file mode 100644 index 0000000000..98a86f9e2c --- /dev/null +++ b/spec/file_fixtures/string_list.txt @@ -0,0 +1,3 @@ +foo +bar +baz \ No newline at end of file diff --git a/spec/lib/msf/core/exploit/http/server_spec.rb b/spec/lib/msf/core/exploit/http/server_spec.rb new file mode 100644 index 0000000000..4aed0c7d0e --- /dev/null +++ b/spec/lib/msf/core/exploit/http/server_spec.rb @@ -0,0 +1,89 @@ + +# -*- coding:binary -*- +require 'spec_helper' + +require 'msf/core' +require 'msf/core/exploit/http/server' + +describe Msf::Exploit::Remote::HttpServer do + subject(:server_module) do + mod = Msf::Exploit.allocate + mod.extend described_class + mod.send(:initialize, {}) + + mod + end + + let(:mock_service) do + mock_service = mock("service") + mock_service.stub(:server_name=) + mock_service.stub(:add_resource) + + mock_service + end + + before do + Rex::ServiceManager.stub(:start => mock_service) + end + + describe "#add_resource" do + it "should call the ServiceManager's add_resource" do + server_module.start_service + + mock_service.should_receive(:add_resource) + server_module.add_resource('Path' => 'foo') + end + + it "should re-raise if the resource has already been added" do + server_module.start_service + + mock_service.should_receive(:add_resource).ordered + mock_service.should_receive(:add_resource).ordered.and_raise(RuntimeError) + + server_module.add_resource('Path' => 'foo') + + expect { server_module.add_resource('Path' => 'foo') }.to raise_error + end + + end + + describe "#cleanup" do + it "should not remove resources if none were successfully added" do + server_module.should_not_receive(:remove_resource) + server_module.cleanup + end + + it "should remove successfully-added resources" do + # setup + server_module.start_service + resources = [ 'a', 'b', 'c' ] + resources.each { |r| server_module.add_resource('Path' => r) } + + # The service will add one resource as part of #start_service, so + # add that to the number that we added manually + server_module.should_receive(:remove_resource).exactly(resources.count + 1).times + server_module.cleanup + end + + end + + describe "#hardcoded_uripath" do + it "should call the ServiceManager's add_resource" do + server_module.start_service + + mock_service.should_receive(:add_resource) + server_module.hardcoded_uripath('foo') + end + + it "should re-raise if the resource has already been added" do + server_module.start_service + + mock_service.should_receive(:add_resource).ordered.and_raise(RuntimeError) + + expect { server_module.hardcoded_uripath('foo') }.to raise_error + end + end + +end + + diff --git a/spec/lib/msf/core/options/opt_address_range_spec.rb b/spec/lib/msf/core/options/opt_address_range_spec.rb index 68f16489ac..721dfa0630 100644 --- a/spec/lib/msf/core/options/opt_address_range_spec.rb +++ b/spec/lib/msf/core/options/opt_address_range_spec.rb @@ -11,27 +11,49 @@ describe Msf::OptAddressRange do { :value => "192.0.2.0,1-255", :normalized => "192.0.2.0,1-255" }, { :value => "192.0.2.*", :normalized => "192.0.2.*" }, { :value => "192.0.2.0-192.0.2.255", :normalized => "192.0.2.0-192.0.2.255" }, + { :value => "file:#{File.expand_path('short_address_list.txt',FILE_FIXTURES_PATH)}", :normalized => '192.168.1.1 192.168.1.2 192.168.1.3 192.168.1.4 192.168.1.5'}, ] invalid_values = [ # Too many dots { :value => "192.0.2.0.0" }, { :value => "192.0.2.0.0,1" }, { :value => "192.0.2.0.0,1-2" }, - { :pending => "Redmine #7536", :value => "192.0.2.0.0/24" }, + { :value => "192.0.2.0.0/24" }, # Not enough dots { :value => "192.0.2" }, { :value => "192.0.2,1" }, { :value => "192.0.2,1-2" }, - { :pending => "Redmine #7536", :value => "192.0.2/24" }, + { :value => "192.0.2/24" }, # Can't mix ranges and CIDR { :value => "192.0.2.0,1/24" }, { :value => "192.0.2.0-1/24" }, { :value => "192.0.2.0,1-2/24" }, { :value => "192.0.2.0/1-24" }, - { :value => "192.0.2.0-192.0.2.1-255", }, + { :value => "192.0.2.0-192.0.2.1-255" }, + # Non-string values + { :value => true}, + { :value => 5 }, + { :value => []}, + { :value => [1,2]}, + { :value => {}}, ] - it_behaves_like "an option", valid_values, invalid_values + it_behaves_like "an option", valid_values, invalid_values, 'addressrange' + + let(:required_opt) { Msf::OptAddressRange.new('RHOSTS', [true, 'The target addresses', '']) } + + context 'the normalizer' do + it 'should handle a call for random IPs' do + random_addresses = required_opt.normalize('rand:5') + random_addresses.kind_of?(String).should == true + ips = random_addresses.split(' ') + ips.count.should == 5 + ips.each do |ip| + (ip =~ Rex::Socket::MATCH_IPV4).should == 0 + end + end + end + end diff --git a/spec/lib/msf/core/options/opt_address_spec.rb b/spec/lib/msf/core/options/opt_address_spec.rb index fa362df6ab..b14d385990 100644 --- a/spec/lib/msf/core/options/opt_address_spec.rb +++ b/spec/lib/msf/core/options/opt_address_spec.rb @@ -13,10 +13,19 @@ describe Msf::OptAddress do # Too many dots { :value => "192.0.2.0.0" }, # Not enough - { :pending => "Redmine #7537", :value => "192.0.2" } + { :value => "192.0.2" }, + # Non-string values + { :value => true}, + { :value => 5 }, + { :value => []}, + { :value => [1,2]}, + { :value => {}}, ] - it_behaves_like "an option", valid_values, invalid_values + it_behaves_like "an option", valid_values, invalid_values, 'address' + + + end diff --git a/spec/lib/msf/core/options/opt_bool_spec.rb b/spec/lib/msf/core/options/opt_bool_spec.rb index 0ad386e023..2b4b3c8c65 100644 --- a/spec/lib/msf/core/options/opt_bool_spec.rb +++ b/spec/lib/msf/core/options/opt_bool_spec.rb @@ -17,6 +17,7 @@ describe Msf::OptBool do { :value => "012" }, { :value => "123" }, ] - it_behaves_like "an option", valid_values, invalid_values + it_behaves_like "an option", valid_values, invalid_values, 'bool' + end diff --git a/spec/lib/msf/core/options/opt_enum_spec.rb b/spec/lib/msf/core/options/opt_enum_spec.rb new file mode 100644 index 0000000000..247b58d7ea --- /dev/null +++ b/spec/lib/msf/core/options/opt_enum_spec.rb @@ -0,0 +1,23 @@ +# -*- coding:binary -*- + +require 'spec_helper' +require 'msf/core/option_container' + +describe Msf::OptEnum do + + it_behaves_like "an option", [], [], 'enum' + + subject do + Msf::OptEnum.new('name',[true, 'A Boolean Value', 'Foo', ['Foo', 'Bar', 'Baz']]) + end + + context 'the validator' do + it 'should return false for a value not in the list' do + subject.valid?('Snap').should == false + end + + it 'should return true for a value in the list' do + subject.valid?('Bar').should == true + end + end +end \ No newline at end of file diff --git a/spec/lib/msf/core/options/opt_int_spec.rb b/spec/lib/msf/core/options/opt_int_spec.rb index 5b7918518f..b9a4d4e5b2 100644 --- a/spec/lib/msf/core/options/opt_int_spec.rb +++ b/spec/lib/msf/core/options/opt_int_spec.rb @@ -21,7 +21,7 @@ describe Msf::OptInt do { :value => "FF", }, ] - it_behaves_like "an option", valid_values, invalid_values + it_behaves_like "an option", valid_values, invalid_values, 'integer' end diff --git a/spec/lib/msf/core/options/opt_path_spec.rb b/spec/lib/msf/core/options/opt_path_spec.rb index 430c042044..9a7a793e43 100644 --- a/spec/lib/msf/core/options/opt_path_spec.rb +++ b/spec/lib/msf/core/options/opt_path_spec.rb @@ -15,6 +15,6 @@ describe Msf::OptPath do { :value => "$", }, ] - it_behaves_like "an option", valid_values, invalid_values + it_behaves_like "an option", valid_values, invalid_values, 'path' end diff --git a/spec/lib/msf/core/options/opt_port_spec.rb b/spec/lib/msf/core/options/opt_port_spec.rb index 8164927544..e90f5133a2 100644 --- a/spec/lib/msf/core/options/opt_port_spec.rb +++ b/spec/lib/msf/core/options/opt_port_spec.rb @@ -16,6 +16,6 @@ describe Msf::OptPort do { :value => "65536", }, ] - it_behaves_like "an option", valid_values, invalid_values + it_behaves_like "an option", valid_values, invalid_values, 'port' end diff --git a/spec/lib/msf/core/options/opt_raw_spec.rb b/spec/lib/msf/core/options/opt_raw_spec.rb new file mode 100644 index 0000000000..68b2cceb8c --- /dev/null +++ b/spec/lib/msf/core/options/opt_raw_spec.rb @@ -0,0 +1,15 @@ +# -*- coding:binary -*- + +require 'spec_helper' +require 'msf/core/option_container' + +describe Msf::OptRaw do + + valid_values = [ + { :value => 'foo', :normalized => 'foo' }, + { :value => "file:#{File.expand_path('string_list.txt',FILE_FIXTURES_PATH)}",:normalized => "foo\nbar\nbaz" }, + ] + invalid_values = [] + + it_behaves_like "an option", valid_values, invalid_values, 'raw' +end \ No newline at end of file diff --git a/spec/lib/msf/core/options/opt_regexp_spec.rb b/spec/lib/msf/core/options/opt_regexp_spec.rb new file mode 100644 index 0000000000..682f7c9330 --- /dev/null +++ b/spec/lib/msf/core/options/opt_regexp_spec.rb @@ -0,0 +1,17 @@ +# -*- coding:binary -*- + +require 'spec_helper' +require 'msf/core/option_container' + +describe Msf::OptRegexp do + + valid_values = [ + { :value => '^foo$', :normalized => /^foo$/ }, + ] + invalid_values = [ + { :value => 123 }, + { :value => 'foo('} + ] + + it_behaves_like "an option", valid_values, invalid_values, 'regexp' +end \ No newline at end of file diff --git a/spec/lib/msf/core/options/opt_string_spec.rb b/spec/lib/msf/core/options/opt_string_spec.rb new file mode 100644 index 0000000000..33c81eb011 --- /dev/null +++ b/spec/lib/msf/core/options/opt_string_spec.rb @@ -0,0 +1,21 @@ +# -*- coding:binary -*- + +require 'spec_helper' +require 'msf/core/option_container' + +describe Msf::OptString do + valid_values = [ + { :value => 'foo', :normalized => 'foo' }, + { :value => "file:#{File.expand_path('string_list.txt',FILE_FIXTURES_PATH)}",:normalized => "foo\nbar\nbaz" }, + ] + invalid_values = [ + # Non-string values + { :value => true}, + { :value => 5 }, + { :value => []}, + { :value => [1,2]}, + { :value => {}}, + ] + + it_behaves_like "an option", valid_values, invalid_values, 'string' +end \ No newline at end of file diff --git a/spec/lib/rex/file_utils_spec.rb b/spec/lib/rex/file_utils_spec.rb new file mode 100644 index 0000000000..a589896831 --- /dev/null +++ b/spec/lib/rex/file_utils_spec.rb @@ -0,0 +1,60 @@ +require 'rex/file' + +describe Rex::FileUtils do + context "Class methods" do + + context ".normalize_win_path" do + it "should convert an absolute path as an array into Windows format" do + described_class.normalize_win_path('C:\\', 'hello', 'world').should eq("C:\\hello\\world") + end + + it "should convert an absolute path as a string into Windows format" do + described_class.normalize_win_path('C:\\hello\\world').should eq("C:\\hello\\world") + end + + it "should convert a relative path" do + described_class.normalize_win_path('/', 'test', 'me').should eq("\\test\\me") + described_class.normalize_win_path('\\temp').should eq("\\temp") + described_class.normalize_win_path('temp').should eq("temp") + end + + it "should keep the trailing slash if exists" do + described_class.normalize_win_path('/', 'test', 'me\\').should eq("\\test\\me\\") + described_class.normalize_win_path('\\temp\\').should eq("\\temp\\") + end + + it "should convert a path without reserved characters" do + described_class.normalize_win_path('C:\\', 'Windows:').should eq("C:\\Windows") + described_class.normalize_win_path('C:\\Windows???\\test').should eq("C:\\Windows\\test") + end + + it "should convert a path without double slashes" do + described_class.normalize_win_path('C:\\\\\\', 'Windows').should eq("C:\\Windows") + described_class.normalize_win_path('C:\\\\\\Hello World\\\\whatever.txt').should eq("C:\\Hello World\\whatever.txt") + described_class.normalize_win_path('C:\\\\').should eq("C:\\") + described_class.normalize_win_path('\\test\\\\test\\\\').should eq("\\test\\test\\") + end + end + + context ".normalize_unix_path" do + it "should convert an absolute path as an array into Unix format" do + described_class.normalize_unix_path('/etc', '/passwd').should eq("/etc/passwd") + end + + it "should convert an absolute path as a string into Unix format" do + described_class.normalize_unix_path('/etc/passwd').should eq('/etc/passwd') + end + + it "should still give me a trailing slash if I have it" do + described_class.normalize_unix_path('/etc/folder/').should eq("/etc/folder/") + end + + it "should convert a path without double slashes" do + described_class.normalize_unix_path('//etc////passwd').should eq("/etc/passwd") + described_class.normalize_unix_path('/etc////', 'passwd').should eq('/etc/passwd') + end + end + + end +end + diff --git a/spec/lib/rex/random_identifier_generator_spec.rb b/spec/lib/rex/random_identifier_generator_spec.rb new file mode 100644 index 0000000000..1660f4898c --- /dev/null +++ b/spec/lib/rex/random_identifier_generator_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' +require 'rex/random_identifier_generator' + +describe Rex::RandomIdentifierGenerator do + let(:options) do + { :min_length => 10, :max_length => 20 } + end + + subject(:rig) { described_class.new(options) } + + it { should respond_to(:generate) } + it { should respond_to(:[]) } + it { should respond_to(:get) } + + describe "#generate" do + it "should respect :min_length" do + 1000.times do + rig.generate.length.should >= options[:min_length] + end + end + + it "should respect :max_length" do + 1000.times do + rig.generate.length.should <= options[:max_length] + end + end + + it "should allow mangling in a block" do + ident = rig.generate { |identifier| identifier.upcase } + ident.should match(/\A[A-Z0-9_]*\Z/) + + ident = subject.generate { |identifier| identifier.downcase } + ident.should match(/\A[a-z0-9_]*\Z/) + + ident = subject.generate { |identifier| identifier.gsub("A","B") } + ident.should_not include("A") + end + end + + describe "#get" do + let(:options) do + { :min_length=>3, :max_length=>3 } + end + it "should return the same thing for subsequent calls" do + rig.get(:rspec).should == rig.get(:rspec) + end + it "should not return the same for different names" do + # Statistically... + count = 1000 + a = Set.new + count.times do |n| + a.add rig.get(n) + end + a.size.should == count + end + + context "with an exhausted set" do + let(:options) do + { :char_set => "abcd", :min_length=>2, :max_length=>2 } + end + let(:max_permutations) do + # 26 because first char is hardcoded to be lowercase alpha + 26 * (options[:char_set].length ** options[:min_length]) + end + + it "doesn't infinite loop" do + Timeout.timeout(1) do + expect { + (max_permutations + 1).times { |i| rig.get(i) } + }.to raise_error(Rex::RandomIdentifierGenerator::ExhaustedSpaceError) + # don't rescue TimeoutError here because we want that to be a + # failure case + end + end + + end + + end + + describe "#store" do + let(:options) do + { :char_set => "abcd", :min_length=>8, :max_length=>20 } + end + + it "should allow smaller than minimum length" do + value = "a"*(options[:min_length]-1) + rig.store(:spec, value) + rig.get(:spec).should == value + end + + it "should allow bigger than maximum length" do + value = "a"*(options[:max_length]+1) + rig.store(:spec, value) + rig.get(:spec).should == value + end + + it "should raise if value is not unique" do + value = "a"*(options[:max_length]+1) + rig.store(:spec0, value) + rig.get(:spec0).should == value + expect { rig.store(:spec1, value) }.to raise_error + end + + it "should overwrite a previously stored value" do + orig_value = "a"*(options[:max_length]) + rig.store(:spec, orig_value) + rig.get(:spec).should == orig_value + + new_value = "b"*(options[:max_length]) + rig.store(:spec, new_value) + rig.get(:spec).should == new_value + end + + it "should overwrite a previously generated value" do + rig.get(:spec) + + new_value = "a"*(options[:max_length]) + rig.store(:spec, new_value) + rig.get(:spec).should == new_value + end + + end +end diff --git a/spec/lib/rex/socket/range_walker_spec.rb b/spec/lib/rex/socket/range_walker_spec.rb index 2b17421998..108c6b682d 100644 --- a/spec/lib/rex/socket/range_walker_spec.rb +++ b/spec/lib/rex/socket/range_walker_spec.rb @@ -28,6 +28,16 @@ describe Rex::Socket::RangeWalker do walker.should include("10.1.3.5") end + it 'should reject CIDR ranges with missing octets' do + walker = Rex::Socket::RangeWalker.new('192.168/24') + walker.should_not be_valid + end + + it 'should reject a CIDR range with too many octets' do + walker = Rex::Socket::RangeWalker.new('192.168.1.2.0/24') + walker.should_not be_valid + end + it "should default the lower bound of a range to 0" do walker = Rex::Socket::RangeWalker.new("10.1.3.-17") walker.should be_valid diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 08c4475ff3..910228f14b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,8 @@ require 'rubygems' require 'bundler' Bundler.require(:default, :test, :db) +FILE_FIXTURES_PATH = File.expand_path(File.dirname(__FILE__)) + "/file_fixtures/" + # add project lib directory to load path spec_pathname = Pathname.new(__FILE__).dirname root_pathname = spec_pathname.join('..').expand_path diff --git a/spec/support/shared/examples/options.rb b/spec/support/shared/examples/options.rb index c6c2c8a775..f26f94e89f 100644 --- a/spec/support/shared/examples/options.rb +++ b/spec/support/shared/examples/options.rb @@ -1,10 +1,29 @@ # -*- coding:binary -*- -shared_examples_for "an option" do |valid_values, invalid_values| +shared_examples_for "an option" do |valid_values, invalid_values, type| subject do described_class.new("name") end + let(:required) { described_class.new('name', [true, 'A description here'])} + let(:optional) { described_class.new('name', [false, 'A description here'])} + + it "should return a type of #{type}" do + subject.type.should == type + end + + context 'when required' do + it 'should not be valid for nil' do + required.valid?(nil).should == false + end + end + + context 'when not required' do + it 'it should be valid for nil' do + optional.valid?(nil).should == true + end + end + context "with valid values" do valid_values.each do |vhash| valid_value = vhash[:value]