diff --git a/lib/msf/ui/web/driver.rb b/lib/msf/ui/web/driver.rb index 1e289d91de..69561484cc 100644 --- a/lib/msf/ui/web/driver.rb +++ b/lib/msf/ui/web/driver.rb @@ -68,7 +68,8 @@ class Driver < Msf::Ui::Driver ilog("Web server started on #{host}:#{port}", LogSource) service.add_resource( - opts['ServerRoot'] || DefaultRoot, + server_root, + 'Directory' => true, 'Proc' => Proc.new { |cli, req| on_request(cli, req) }) @@ -91,6 +92,13 @@ class Driver < Msf::Ui::Driver term_event.set end + # + # Returns the root resource name, such as '/msfweb' + # + def server_root + opts['ServerRoot'] || DefaultRoot + end + # # The framework instance associated with this driver. # @@ -125,7 +133,9 @@ protected # dispatched. # def on_request(cli, req) - dispatch_request(cli, req) + parts = req.resource.gsub(server_root, '').split(/\//) + + end end diff --git a/lib/rex.rb.ts.rb b/lib/rex.rb.ts.rb index 1f666313e7..e501cfa7ad 100644 --- a/lib/rex.rb.ts.rb +++ b/lib/rex.rb.ts.rb @@ -27,6 +27,8 @@ require 'rex/socket/comm/local.rb.ut' require 'rex/socket/switch_board.rb.ut' require 'rex/socket/subnet_walker.rb.ut' +require 'rex/proto/http' + require 'rex/parser/arguments.rb.ut' require 'rex/ui/text/color.rb.ut' @@ -67,6 +69,15 @@ class Rex::TestSuite suite << Rex::Socket::SwitchBoard::UnitTest.suite suite << Rex::Socket::SubnetWalker::UnitTest.suite + # Protocols + suite << Rex::Proto::Http::Client::UnitTest.suite + suite << Rex::Proto::Http::Server::UnitTest.suite + suite << Rex::Proto::Http::Packet::UnitTest.suite + suite << Rex::Proto::Http::Request::UnitTest.suite + suite << Rex::Proto::Http::Response::UnitTest.suite + suite << Rex::Proto::Http::Handler::Erb::UnitTest.suite + suite << Rex::Proto::Http::Handler::Proc::UnitTest.suite + # Parsers suite << Rex::Parser::Arguments::UnitTest.suite diff --git a/lib/rex/proto/http/handler.rb b/lib/rex/proto/http/handler.rb new file mode 100644 index 0000000000..b62303f2d5 --- /dev/null +++ b/lib/rex/proto/http/handler.rb @@ -0,0 +1,39 @@ +module Rex +module Proto +module Http + +### +# +# This class acts as the base class for all handlers. +# +### +class Handler + + require 'rex/proto/http/handler/erb' + require 'rex/proto/http/handler/proc' + + # + # Initializes the handler instance as being associated with the supplied + # server. + # + def initialize(server) + self.server = server + end + + # + # By default, handlers do not require a relative resource. + # + def self.relative_resource_required? + false + end + +protected + + attr_accessor :server # :nodoc: + +end + + +end +end +end diff --git a/lib/rex/proto/http/handler/erb.rb b/lib/rex/proto/http/handler/erb.rb new file mode 100644 index 0000000000..3200f06b21 --- /dev/null +++ b/lib/rex/proto/http/handler/erb.rb @@ -0,0 +1,105 @@ +require 'erb' + +module Rex +module Proto +module Http + +### +# +# This class implements a handler for ERB (.rhtml) template files. This is +# based off the webrick handler. +# +### +class Handler::Erb < Handler + + # + # ERB handlers required a relative resource so that the full path name can + # be computed. + # + def self.relative_resource_required? + true + end + + # + # Initializes the ERB handler + # + def initialize(server, root_path, opts = {}) + super(server) + + self.root_path = root_path + self.opts = opts + + self.opts['MimeType'] = "text/html" unless self.opts['MimeType'] + end + + # + # Called when a request arrives. + # + def on_request(cli, req) + resource = req.relative_resource + + # Make sure directory traversals aren't happening + if (resource =~ /\.\./) + wlog("Erb::on_request: Dangerous request performed: #{resource}", + LogSource) + return + end + + begin + resp = Response.new + + # Calculate the actual file path on disk. + file_path = root_path + resource + + puts "file path is #{file_path}" + + # Serialize the contents of the file + data = ::IO.readlines(file_path).join + + # Evaluate the data and set the output as the response body. + resp.body = evaluate(ERB.new(data), cli, req, resp) + + # Set the content-type to text/html by default. + resp['Content-Type'] = opts['MimeType'] + rescue + elog("Erb::on_request: #{$!}\n\n#{$@.join("\n")}", LogSource) + + puts "exception: #{$!} #{$@.join("\n")}" + + resp = Response::E404.new + end + + # Send the response to the + if (cli and resp) + cli.send_response(resp) + end + + resp + end + + # + # Evaulates the ERB context in a specific binding context. + # + def evaluate(erb, cli, request, response) + # If the thing that created this handler wanted us to use a callback + # instead of the default behavior, then let's do that. + if (opts['Callback']) + opts['Callback'].call(erb, cli, request, response) + else + Module.new.module_eval { + query_string = request.qstring + meta_vars = request.meta_vars + erb.result(binding) + } + end + end + +protected + + attr_accessor :root_path, :opts # :nodoc: + +end + +end +end +end diff --git a/lib/rex/proto/http/handler/erb.rb.ut.rb b/lib/rex/proto/http/handler/erb.rb.ut.rb new file mode 100644 index 0000000000..8f5985fde8 --- /dev/null +++ b/lib/rex/proto/http/handler/erb.rb.ut.rb @@ -0,0 +1,21 @@ +#!/usr/bin/ruby + +$:.unshift(File.join(File.dirname(__FILE__), '..', '..', '..', '..')) + +require 'test/unit' +require 'rex/proto/http' + +class Rex::Proto::Http::Handler::Erb::UnitTest < Test::Unit::TestCase + + Klass = Rex::Proto::Http::Handler::Erb + Request = Rex::Proto::Http::Request + + def test_erb + k = Klass.new(nil, File.dirname(__FILE__)) + r = k.on_request(nil, Request::Get.new("/erb.rb.ut.rb.rhtml")) + + assert_not_nil(r) + assert_equal("foo 4\n", r.body) + end + +end diff --git a/lib/rex/proto/http/handler/erb.rb.ut.rb.rhtml b/lib/rex/proto/http/handler/erb.rb.ut.rb.rhtml new file mode 100644 index 0000000000..7f6479f936 --- /dev/null +++ b/lib/rex/proto/http/handler/erb.rb.ut.rb.rhtml @@ -0,0 +1 @@ +foo <%= 2 + 2 %> diff --git a/lib/rex/proto/http/handler/proc.rb b/lib/rex/proto/http/handler/proc.rb new file mode 100644 index 0000000000..0dde29afa5 --- /dev/null +++ b/lib/rex/proto/http/handler/proc.rb @@ -0,0 +1,43 @@ +require 'erb' + +module Rex +module Proto +module Http + +### +# +# This class is used to wrapper the calling of a procedure when a request +# arrives. +# +### +class Handler::Proc < Handler + + # + # Initializes the proc handler with the supplied procedure + # + def initialize(server, procedure) + super(server) + + self.procedure = procedure + end + + # + # Called when a request arrives. + # + def on_request(cli, req) + begin + procedure.call(cli, req) + rescue + elog("Proc::on_request: #{$!}\n\n#{$@.join("\n")}", LogSource) + end + end + +protected + + attr_accessor :procedure # :nodoc: + +end + +end +end +end diff --git a/lib/rex/proto/http/handler/proc.rb.ut.rb b/lib/rex/proto/http/handler/proc.rb.ut.rb new file mode 100644 index 0000000000..5960e6dfb1 --- /dev/null +++ b/lib/rex/proto/http/handler/proc.rb.ut.rb @@ -0,0 +1,24 @@ +#!/usr/bin/ruby + +$:.unshift(File.join(File.dirname(__FILE__), '..', '..', '..', '..')) + +require 'test/unit' +require 'rex/proto/http' + +class Rex::Proto::Http::Handler::Proc::UnitTest < Test::Unit::TestCase + + Klass = Rex::Proto::Http::Handler::Proc + Request = Rex::Proto::Http::Request + + def test_proc + cool = 0 + k = Klass.new(nil, Proc.new { |cli, req| + cool = 1 + }) + + r = k.on_request(nil, Request::Get.new("/erb.rb.ut.rb.rhtml")) + + assert_equal(1, cool) + end + +end diff --git a/lib/rex/proto/http/request.rb b/lib/rex/proto/http/request.rb index 66f18bdd96..de82aab2cd 100644 --- a/lib/rex/proto/http/request.rb +++ b/lib/rex/proto/http/request.rb @@ -57,6 +57,7 @@ class Request < Packet self.uri_parts = {} self.proto = proto || DefaultProtocol + update_uri_parts end # @@ -68,21 +69,31 @@ class Request < Packet self.uri = URI.decode(md[2]) self.proto = md[3] - # If it has a query string, get the parts. - if ((self.uri) and (md = self.uri.match(/(.+?)\?(.*)$/))) - self.uri_parts['QueryString'] = parse_cgi_qstring(md[2]) - self.uri_parts['Resource'] = md[1] - # Otherwise, just assume that the URI is equal to the resource being - # requested. - else - self.uri_parts['QueryString'] = nil - self.uri_parts['Resource'] = self.uri - end + update_uri_parts else raise RuntimeError, "Invalid request command string", caller end end + # + # Split the URI into the resource being requested and its query string. + # + def update_uri_parts + # If it has a query string, get the parts. + if ((self.uri) and (md = self.uri.match(/(.+?)\?(.*)$/))) + self.uri_parts['QueryString'] = parse_cgi_qstring(md[2]) + self.uri_parts['Resource'] = md[1] + # Otherwise, just assume that the URI is equal to the resource being + # requested. + else + self.uri_parts['QueryString'] = nil + self.uri_parts['Resource'] = self.uri + end + + # Set the relative resource to the actual resource. + self.relative_resource = resource + end + # # Returns the command string derived from the three values. # @@ -97,6 +108,14 @@ class Request < Packet self.uri_parts['Resource'] end + # + # Changes the resource URI. This is used when making a request relative to + # a given mount point. + # + def resource=(rsrc) + self.uri_parts['Resource'] = rsrc + end + # # If there were CGI parameters in the URI, this will hold a hash of each # variable to value. If there is more than one value for a given variable, @@ -106,6 +125,15 @@ class Request < Packet self.uri_parts['QueryString'] end + # + # Returns a hash of variables that contain information about the request, + # such as the remote host information. + # + # TODO + # + def meta_vars + end + # # The method being used for the request (e.g. GET). # @@ -122,6 +150,10 @@ class Request < Packet # The protocol to be sent with the request. # attr_accessor :proto + # + # The resource path relative to the root of a server mount point. + # + attr_accessor :relative_resource protected diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb index 7a6a7a1839..91760fa5ec 100644 --- a/lib/rex/proto/http/server.rb +++ b/lib/rex/proto/http/server.rb @@ -1,5 +1,6 @@ require 'rex/socket' require 'rex/proto/http' +require 'rex/proto/http/handler' module Rex module Proto @@ -61,7 +62,7 @@ end ### # # Acts as an HTTP server, processing requests and dispatching them to -# registered procs. +# registered procs. Some of this server was modeled after webrick. # ### class Server @@ -118,6 +119,20 @@ class Server listener.close_client(cli) end + # + # Mounts a directory or resource as being serviced by the supplied handler. + # + def mount(root, handler, long_call = false, *args) + resources[root] = [ handler, long_call, args ] + end + + # + # Remove the mount point. + # + def unmount(root) + resources.delete(root) + end + # # Adds a resource handler, such as one for /, which will be called whenever # the resource is requested. The ``opts'' parameter can have any of the @@ -126,15 +141,19 @@ class Server # Proc (proc) - The procedure to call when a request comes in for this resource. # LongCall (bool) - Hints to the server that this resource may have long # request processing times. - # Directory (bool) - Whether or not this resource symbolizes a directory. # def add_resource(name, opts) - if (self.resources[name]) + if (resources[name]) raise RuntimeError, "The supplied resource '#{name}' is already added.", caller end - self.resources[name] = opts + # If a procedure was passed, mount the resource with it. + if (opts['Proc']) + mount(name, Handler::Proc, false, opts['Proc']) + else + raise ArgumentError, "You must specify a procedure." + end end # @@ -199,29 +218,47 @@ protected (request['Connection'].downcase == 'Keep-Alive'.downcase)) cli.keepalive = true end - - # If we fail to find the resource by full name, check if - # it's a directory resource - if ((p = self.resources[request.resource]) == nil) - resources.each_pair { |k, val| - if (val['Directory'] and request.resource =~ /^#{k}/) - p = val - break - end - } - end - # If we found the resource handler for this resource, call its - # procedure. + # Search for the resource handler for the requested URL. This is pretty + # inefficient right now, but we can spruce it up later. + p = nil + len = 0 + root = nil + + resources.each_pair { |k, val| + if (request.resource =~ /^#{k}/ and k.length > len) + p = val + len = k.length + root = k + end + } + +begin if (p) - if (p['LongCall'] == true) + # Create an instance of the handler for this resource + handler = p[0].new(self, *p[2]) + + # If the handler class requires a relative resource... + if (p[0].relative_resource_required?) + # Substituted the mount point root in the request to make things + # relative to the mount point. + request.relative_resource = request.resource.gsub(root, '') + request.relative_resource = '/' + request.relative_resource if (request.relative_resource !~ /^\//) + end + + # If we found the resource handler for this resource, call its + # procedure. + if (p[1] == true) Thread.new { - p['Proc'].call(cli, request) + handler.on_request(cli, request) } else - p['Proc'].call(cli, request) + handler.on_request(cli, request) end else + elog("Failed to find handler for resource: #{request.resource}", + LogSource) + send_e404(cli, request) end @@ -229,6 +266,9 @@ protected if (cli.keepalive == false) close_client(cli) end +rescue + puts "bleh #{$!} #{$@.join("\n")}" +end end # diff --git a/lib/rex/proto/http/server.rb.ut.rb b/lib/rex/proto/http/server.rb.ut.rb index 72b8910b4a..0943e821c8 100644 --- a/lib/rex/proto/http/server.rb.ut.rb +++ b/lib/rex/proto/http/server.rb.ut.rb @@ -52,7 +52,7 @@ class Rex::Proto::Http::Server::UnitTest < Test::Unit::TestCase } s.remove_resource('/foo') - + req = Rex::Proto::Http::Request::Get.new('/foo') res = c.send_request(req) assert_not_nil(res) diff --git a/lib/rex/socket/ssl_tcp.rb.ut.rb b/lib/rex/socket/ssl_tcp.rb.ut.rb index 48c5877a78..e50d5f8c24 100644 --- a/lib/rex/socket/ssl_tcp.rb.ut.rb +++ b/lib/rex/socket/ssl_tcp.rb.ut.rb @@ -21,7 +21,7 @@ class Rex::Socket::SslTcp::UnitTest < Test::Unit::TestCase # Send a HEAD request and make sure we get some kind of response head_request = "HEAD / HTTP/1.0\r\n\r\n" - assert_equal(true, t.put(head_request), "sending head request") + assert_equal(19, t.put(head_request), "sending head request") head_response = "" diff --git a/lib/rex/socket/tcp_server.rb.ut.rb b/lib/rex/socket/tcp_server.rb.ut.rb index a194a2e57d..0add424e1b 100644 --- a/lib/rex/socket/tcp_server.rb.ut.rb +++ b/lib/rex/socket/tcp_server.rb.ut.rb @@ -29,7 +29,7 @@ class Rex::Socket::TcpServer::UnitTest < Test::Unit::TestCase scli = serv.accept assert_kind_of(Rex::Socket::Tcp, scli, "valid server client Tcp") - assert_equal(true, scli.put("Yo"), "scli: put Yo") + assert_equal(2, scli.put("Yo"), "scli: put Yo") assert_equal("Yo", ccli.get(), "ccli: get Yo") ensure ccli.close if (ccli)