CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/proto/http/server.rb
Views: 11704
1
# -*- coding: binary -*-
2
require 'rex/socket'
3
4
5
module Rex
6
module Proto
7
module Http
8
9
###
10
#
11
# Acts as an HTTP server, processing requests and dispatching them to
12
# registered procs. Some of this server was modeled after webrick.
13
#
14
###
15
class Server
16
17
include Proto
18
19
#
20
# A hash that associated a file extension with a mime type for use as the
21
# content type of responses.
22
#
23
ExtensionMimeTypes =
24
{
25
"rhtml" => "text/html",
26
"html" => "text/html",
27
"htm" => "text/htm",
28
"jpg" => "image/jpeg",
29
"jpeg" => "image/jpeg",
30
"gif" => "image/gif",
31
"png" => "image/png",
32
"bmp" => "image/bmp",
33
"txt" => "text/plain",
34
"css" => "text/css",
35
"ico" => "image/x-icon",
36
}
37
38
#
39
# The default server name that will be returned in the Server attribute of
40
# a response.
41
#
42
DefaultServer = "Rex"
43
44
#
45
# Initializes an HTTP server as listening on the provided port and
46
# hostname.
47
#
48
def initialize(port = 80, listen_host = '0.0.0.0', ssl = false, context = {},
49
comm = nil, ssl_cert = nil, ssl_compression = false,
50
ssl_cipher = nil, ssl_version = nil)
51
self.listen_host = listen_host
52
self.listen_port = port
53
self.ssl = ssl
54
self.context = context
55
self.comm = comm
56
self.ssl_cert = ssl_cert
57
self.ssl_compression = ssl_compression
58
self.ssl_cipher = ssl_cipher
59
self.ssl_version = ssl_version
60
self.listener = nil
61
self.resources = {}
62
self.server_name = DefaultServer
63
end
64
65
# More readable inspect that only shows the url and resources
66
# @return [String]
67
def inspect
68
resources_str = resources.keys.map{|r| r.inspect }.join ", "
69
70
"#<#{self.class} http#{ssl ? "s" : ""}://#{listen_host}:#{listen_port} [ #{resources_str} ]>"
71
end
72
73
#
74
# Returns the hardcore alias for the HTTP service
75
#
76
def self.hardcore_alias(*args)
77
"#{(args[0] || '')}-#{(args[1] || '')}-#{args[4] || ''}"
78
end
79
80
#
81
# HTTP server.
82
#
83
def alias
84
super || "HTTP Server"
85
end
86
87
#
88
# Listens on the defined port and host and starts monitoring for clients.
89
#
90
def start
91
92
self.listener = Rex::Socket::TcpServer.create(
93
'LocalHost' => self.listen_host,
94
'LocalPort' => self.listen_port,
95
'Context' => self.context,
96
'SSL' => self.ssl,
97
'SSLCert' => self.ssl_cert,
98
'SSLCompression' => self.ssl_compression,
99
'SSLCipher' => self.ssl_cipher,
100
'SSLVersion' => self.ssl_version,
101
'Comm' => self.comm
102
)
103
104
# Register callbacks
105
self.listener.on_client_connect_proc = Proc.new { |cli|
106
on_client_connect(cli)
107
}
108
self.listener.on_client_data_proc = Proc.new { |cli|
109
on_client_data(cli)
110
}
111
112
self.listener.start
113
end
114
115
#
116
# Terminates the monitor thread and turns off the listener.
117
#
118
def stop
119
self.listener.stop
120
self.listener.close
121
end
122
123
124
#
125
# Waits for the HTTP service to terminate
126
#
127
def wait
128
self.listener.wait if self.listener
129
end
130
131
#
132
# Closes the supplied client, if valid.
133
#
134
def close_client(cli)
135
listener.close_client(cli)
136
end
137
138
#
139
# Mounts a directory or resource as being serviced by the supplied handler.
140
#
141
def mount(root, handler, long_call = false, *args)
142
resources[root] = [ handler, long_call, args ]
143
end
144
145
#
146
# Remove the mount point.
147
#
148
def unmount(root)
149
resources.delete(root)
150
end
151
152
#
153
# Adds a resource handler, such as one for /, which will be called whenever
154
# the resource is requested. The ``opts'' parameter can have any of the
155
# following:
156
#
157
# Proc (proc) - The procedure to call when a request comes in for this resource.
158
# LongCall (bool) - Hints to the server that this resource may have long
159
# request processing times.
160
#
161
def add_resource(name, opts)
162
if (resources[name])
163
raise RuntimeError,
164
"The supplied resource '#{name}' is already added.", caller
165
end
166
167
# If a procedure was passed, mount the resource with it.
168
if (opts['Proc'])
169
mount(name, Handler::Proc, false, opts['Proc'], opts['VirtualDirectory'])
170
else
171
raise ArgumentError, "You must specify a procedure."
172
end
173
end
174
175
#
176
# Removes the supplied resource handler.
177
#
178
def remove_resource(name)
179
self.resources.delete(name)
180
end
181
182
#
183
# Adds Server headers and stuff.
184
#
185
def add_response_headers(resp)
186
resp['Server'] = self.server_name if not resp['Server']
187
end
188
189
#
190
# Returns the mime type associated with the supplied file. Right now the
191
# set of mime types is fairly limited.
192
#
193
def mime_type(file)
194
type = nil
195
196
if (file =~ /\.(.+?)$/)
197
type = ExtensionMimeTypes[$1.downcase]
198
end
199
200
type || "text/plain"
201
end
202
203
#
204
# Sends a 404 error to the client for a given request.
205
#
206
def send_e404(cli, request)
207
resp = Response::E404.new
208
209
resp['Content-Type'] = 'text/html'
210
211
resp.body =
212
"<html><head>" +
213
"<title>404 Not Found</title>" +
214
"</head><body>" +
215
"<h1>Not found</h1>" +
216
"The requested URL #{html_escape(request.resource)} was not found on this server.<p><hr>" +
217
"</body></html>"
218
219
# Send the response to the client like what
220
cli.send_response(resp)
221
end
222
223
attr_accessor :listen_port, :listen_host, :server_name, :context, :comm
224
attr_accessor :ssl, :ssl_cert, :ssl_compression, :ssl_cipher, :ssl_version
225
attr_accessor :listener, :resources
226
227
protected
228
229
#
230
# Extends new clients with the ServerClient module and initializes them.
231
#
232
def on_client_connect(cli)
233
cli.extend(ServerClient)
234
235
cli.init_cli(self)
236
end
237
238
#
239
# Processes data coming in from a client.
240
#
241
def on_client_data(cli)
242
begin
243
data = cli.read(65535)
244
245
raise ::EOFError if not data
246
raise ::EOFError if data.empty?
247
248
case cli.request.parse(data)
249
when Packet::ParseCode::Completed
250
dispatch_request(cli, cli.request)
251
cli.reset_cli
252
253
when Packet::ParseCode::Partial
254
# Return and wait for the on_client_data handler to be called again
255
# The Request object tracks the state of the request for us
256
return
257
258
when Packet::ParseCode::Error
259
close_client(cli)
260
end
261
rescue EOFError
262
if (cli.request.completed?)
263
dispatch_request(cli, cli.request)
264
265
cli.reset_cli
266
end
267
268
close_client(cli)
269
end
270
end
271
272
#
273
# Dispatches the supplied request for a given connection.
274
#
275
def dispatch_request(cli, request)
276
# Is the client requesting keep-alive?
277
if ((request['Connection']) and
278
(request['Connection'].downcase == 'Keep-Alive'.downcase))
279
cli.keepalive = true
280
end
281
282
# Search for the resource handler for the requested URL. This is pretty
283
# inefficient right now, but we can spruce it up later.
284
p = nil
285
len = 0
286
root = nil
287
288
resources.each_pair { |k, val|
289
if (request.resource =~ /^#{k}/ and k.length > len)
290
p = val
291
len = k.length
292
root = k
293
end
294
}
295
296
if (p)
297
# Create an instance of the handler for this resource
298
handler = p[0].new(self, *p[2])
299
300
# If the handler class requires a relative resource...
301
if (handler.relative_resource_required?)
302
# Substituted the mount point root in the request to make things
303
# relative to the mount point.
304
request.relative_resource = request.resource.gsub(/^#{root}/, '')
305
request.relative_resource = '/' + request.relative_resource if (request.relative_resource !~ /^\//)
306
end
307
308
309
# If we found the resource handler for this resource, call its
310
# procedure.
311
if (p[1] == true)
312
Rex::ThreadFactory.spawn("HTTPServerRequestHandler", false) {
313
handler.on_request(cli, request)
314
}
315
else
316
handler.on_request(cli, request)
317
end
318
else
319
elog("Failed to find handler for resource: #{request.resource}", LogSource)
320
321
send_e404(cli, request)
322
end
323
324
# If keep-alive isn't enabled for this client, close the connection
325
if (cli.keepalive == false)
326
close_client(cli)
327
end
328
end
329
330
end
331
332
end
333
end
334
end
335
336
337