enhanced http server crap

git-svn-id: file:///home/svn/incoming/trunk@3063 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
Matt Miller 2005-11-24 02:02:10 +00:00
parent 2f0b44adf6
commit fc9376d385
13 changed files with 361 additions and 35 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
foo <%= 2 + 2 %>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
#

View File

@ -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)

View File

@ -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 = ""

View File

@ -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)