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/client.rb
Views: 11704
1
# -*- coding: binary -*-
2
require 'rex/socket'
3
4
require 'rex/text'
5
require 'digest'
6
7
8
module Rex
9
module Proto
10
module Http
11
12
###
13
#
14
# Acts as a client to an HTTP server, sending requests and receiving responses.
15
#
16
# See the RFC: http://www.w3.org/Protocols/rfc2616/rfc2616.html
17
#
18
###
19
class Client
20
21
#
22
# Creates a new client instance
23
# @param http_trace_proc_request [Proc] A proc object passed to log HTTP requests if HTTP-Trace is set
24
# @param http_trace_proc_response [Proc] A proc object passed to log HTTP responses if HTTP-Trace is set
25
#
26
def initialize(host, port = 80, context = {}, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '', kerberos_authenticator: nil, comm: nil, subscriber: nil)
27
self.hostname = host
28
self.port = port.to_i
29
self.context = context
30
self.ssl = ssl
31
self.ssl_version = ssl_version
32
self.proxies = proxies
33
self.username = username
34
self.password = password
35
self.kerberos_authenticator = kerberos_authenticator
36
self.comm = comm
37
self.subscriber = subscriber || HttpSubscriber.new
38
39
# Take ClientRequest's defaults, but override with our own
40
self.config = Http::ClientRequest::DefaultConfig.merge({
41
'read_max_data' => (1024*1024*1),
42
'vhost' => self.hostname,
43
'ssl_server_name_indication' => self.hostname,
44
})
45
self.config['agent'] ||= Rex::UserAgent.session_agent
46
47
# XXX: This info should all be controlled by ClientRequest
48
self.config_types = {
49
'uri_encode_mode' => ['hex-normal', 'hex-all', 'hex-random', 'hex-noslashes', 'u-normal', 'u-random', 'u-all'],
50
'uri_encode_count' => 'integer',
51
'uri_full_url' => 'bool',
52
'pad_method_uri_count' => 'integer',
53
'pad_uri_version_count' => 'integer',
54
'pad_method_uri_type' => ['space', 'tab', 'apache'],
55
'pad_uri_version_type' => ['space', 'tab', 'apache'],
56
'method_random_valid' => 'bool',
57
'method_random_invalid' => 'bool',
58
'method_random_case' => 'bool',
59
'version_random_valid' => 'bool',
60
'version_random_invalid' => 'bool',
61
'uri_dir_self_reference' => 'bool',
62
'uri_dir_fake_relative' => 'bool',
63
'uri_use_backslashes' => 'bool',
64
'pad_fake_headers' => 'bool',
65
'pad_fake_headers_count' => 'integer',
66
'pad_get_params' => 'bool',
67
'pad_get_params_count' => 'integer',
68
'pad_post_params' => 'bool',
69
'pad_post_params_count' => 'integer',
70
'shuffle_get_params' => 'bool',
71
'shuffle_post_params' => 'bool',
72
'uri_fake_end' => 'bool',
73
'uri_fake_params_start' => 'bool',
74
'header_folding' => 'bool',
75
'chunked_size' => 'integer',
76
'partial' => 'bool'
77
}
78
end
79
80
#
81
# Set configuration options
82
#
83
def set_config(opts = {})
84
opts.each_pair do |var,val|
85
# Default type is string
86
typ = self.config_types[var] || 'string'
87
88
# These are enum types
89
if typ.is_a?(Array)
90
if not typ.include?(val)
91
raise RuntimeError, "The specified value for #{var} is not one of the valid choices"
92
end
93
end
94
95
# The caller should have converted these to proper ruby types, but
96
# take care of the case where they didn't before setting the
97
# config.
98
99
if(typ == 'bool')
100
val = (val == true || val.to_s =~ /^(t|y|1)/i)
101
end
102
103
if(typ == 'integer')
104
val = val.to_i
105
end
106
107
self.config[var]=val
108
end
109
end
110
111
#
112
# Create an arbitrary HTTP request
113
#
114
# @param opts [Hash]
115
# @option opts 'agent' [String] User-Agent header value
116
# @option opts 'connection' [String] Connection header value
117
# @option opts 'cookie' [String] Cookie header value
118
# @option opts 'data' [String] HTTP data (only useful with some methods, see rfc2616)
119
# @option opts 'encode' [Bool] URI encode the supplied URI, default: false
120
# @option opts 'headers' [Hash] HTTP headers, e.g. <code>{ "X-MyHeader" => "value" }</code>
121
# @option opts 'method' [String] HTTP method to use in the request, not limited to standard methods defined by rfc2616, default: GET
122
# @option opts 'proto' [String] protocol, default: HTTP
123
# @option opts 'query' [String] raw query string
124
# @option opts 'raw_headers' [String] Raw HTTP headers
125
# @option opts 'uri' [String] the URI to request
126
# @option opts 'version' [String] version of the protocol, default: 1.1
127
# @option opts 'vhost' [String] Host header value
128
#
129
# @return [ClientRequest]
130
def request_raw(opts = {})
131
opts = self.config.merge(opts)
132
133
opts['cgi'] = false
134
opts['port'] = self.port
135
opts['ssl'] = self.ssl
136
137
ClientRequest.new(opts)
138
end
139
140
#
141
# Create a CGI compatible request
142
#
143
# @param (see #request_raw)
144
# @option opts (see #request_raw)
145
# @option opts 'ctype' [String] Content-Type header value, default for POST requests: +application/x-www-form-urlencoded+
146
# @option opts 'encode_params' [Bool] URI encode the GET or POST variables (names and values), default: true
147
# @option opts 'vars_get' [Hash] GET variables as a hash to be translated into a query string
148
# @option opts 'vars_post' [Hash] POST variables as a hash to be translated into POST data
149
# @option opts 'vars_form_data' [Hash] POST form_data variables as a hash to be translated into multi-part POST form data
150
#
151
# @return [ClientRequest]
152
def request_cgi(opts = {})
153
opts = self.config.merge(opts)
154
155
opts['cgi'] = true
156
opts['port'] = self.port
157
opts['ssl'] = self.ssl
158
159
ClientRequest.new(opts)
160
end
161
162
#
163
# Connects to the remote server if possible.
164
#
165
# @param t [Integer] Timeout
166
# @see Rex::Socket::Tcp.create
167
# @return [Rex::Socket::Tcp]
168
def connect(t = -1)
169
# If we already have a connection and we aren't pipelining, close it.
170
if (self.conn)
171
if !pipelining?
172
close
173
else
174
return self.conn
175
end
176
end
177
178
timeout = (t.nil? or t == -1) ? 0 : t
179
180
self.conn = Rex::Socket::Tcp.create(
181
'PeerHost' => self.hostname,
182
'PeerHostname' => self.config['ssl_server_name_indication'] || self.config['vhost'],
183
'PeerPort' => self.port.to_i,
184
'LocalHost' => self.local_host,
185
'LocalPort' => self.local_port,
186
'Context' => self.context,
187
'SSL' => self.ssl,
188
'SSLVersion' => self.ssl_version,
189
'Proxies' => self.proxies,
190
'Timeout' => timeout,
191
'Comm' => self.comm
192
)
193
end
194
195
#
196
# Closes the connection to the remote server.
197
#
198
def close
199
if self.conn && !self.conn.closed?
200
self.conn.shutdown
201
self.conn.close
202
end
203
204
self.conn = nil
205
self.ntlm_client = nil
206
end
207
208
#
209
# Sends a request and gets a response back
210
#
211
# If the request is a 401, and we have creds, it will attempt to complete
212
# authentication and return the final response
213
#
214
# @return (see #_send_recv)
215
def send_recv(req, t = -1, persist = false)
216
res = _send_recv(req, t, persist)
217
if res and res.code == 401 and res.headers['WWW-Authenticate']
218
res = send_auth(res, req.opts, t, persist)
219
end
220
res
221
end
222
223
#
224
# Transmit an HTTP request and receive the response
225
#
226
# If persist is set, then the request will attempt to reuse an existing
227
# connection.
228
#
229
# Call this directly instead of {#send_recv} if you don't want automatic
230
# authentication handling.
231
#
232
# @return (see #read_response)
233
def _send_recv(req, t = -1, persist = false)
234
@pipeline = persist
235
subscriber.on_request(req)
236
if req.respond_to?(:opts) && req.opts['ntlm_transform_request'] && self.ntlm_client
237
req = req.opts['ntlm_transform_request'].call(self.ntlm_client, req)
238
elsif req.respond_to?(:opts) && req.opts['krb_transform_request'] && self.krb_encryptor
239
req = req.opts['krb_transform_request'].call(self.krb_encryptor, req)
240
end
241
242
send_request(req, t)
243
244
res = read_response(t, :original_request => req)
245
if req.respond_to?(:opts) && req.opts['ntlm_transform_response'] && self.ntlm_client
246
req.opts['ntlm_transform_response'].call(self.ntlm_client, res)
247
elsif req.respond_to?(:opts) && req.opts['krb_transform_response'] && self.krb_encryptor
248
req = req.opts['krb_transform_response'].call(self.krb_encryptor, res)
249
end
250
res.request = req.to_s if res
251
res.peerinfo = peerinfo if res
252
subscriber.on_response(res)
253
res
254
end
255
256
#
257
# Send an HTTP request to the server
258
#
259
# @param req [Request,ClientRequest,#to_s] The request to send
260
# @param t (see #connect)
261
#
262
# @return [void]
263
def send_request(req, t = -1)
264
connect(t)
265
conn.put(req.to_s)
266
end
267
268
# Resends an HTTP Request with the proper authentication headers
269
# set. If we do not support the authentication type the server requires
270
# we return the original response object
271
#
272
# @param res [Response] the HTTP Response object
273
# @param opts [Hash] the options used to generate the original HTTP request
274
# @param t [Integer] the timeout for the request in seconds
275
# @param persist [Boolean] whether or not to persist the TCP connection (pipelining)
276
#
277
# @return [Response] the last valid HTTP response object we received
278
def send_auth(res, opts, t, persist)
279
if opts['username'].nil? or opts['username'] == ''
280
if self.username and not (self.username == '')
281
opts['username'] = self.username
282
opts['password'] = self.password
283
else
284
opts['username'] = nil
285
opts['password'] = nil
286
end
287
end
288
289
if opts[:kerberos_authenticator].nil?
290
opts[:kerberos_authenticator] = self.kerberos_authenticator
291
end
292
293
return res if (opts['username'].nil? or opts['username'] == '') and opts[:kerberos_authenticator].nil?
294
supported_auths = res.headers['WWW-Authenticate']
295
296
# if several providers are available, the client may want one in particular
297
preferred_auth = opts['preferred_auth']
298
299
if supported_auths.include?('Basic') && (preferred_auth.nil? || preferred_auth == 'Basic')
300
opts['headers'] ||= {}
301
opts['headers']['Authorization'] = basic_auth_header(opts['username'],opts['password'] )
302
req = request_cgi(opts)
303
res = _send_recv(req,t,persist)
304
return res
305
elsif supported_auths.include?('Digest') && (preferred_auth.nil? || preferred_auth == 'Digest')
306
temp_response = digest_auth(opts)
307
if temp_response.kind_of? Rex::Proto::Http::Response
308
res = temp_response
309
end
310
return res
311
elsif supported_auths.include?('NTLM') && (preferred_auth.nil? || preferred_auth == 'NTLM')
312
opts['provider'] = 'NTLM'
313
temp_response = negotiate_auth(opts)
314
if temp_response.kind_of? Rex::Proto::Http::Response
315
res = temp_response
316
end
317
return res
318
elsif supported_auths.include?('Negotiate') && (preferred_auth.nil? || preferred_auth == 'Negotiate')
319
opts['provider'] = 'Negotiate'
320
temp_response = negotiate_auth(opts)
321
if temp_response.kind_of? Rex::Proto::Http::Response
322
res = temp_response
323
end
324
return res
325
elsif supported_auths.include?('Negotiate') && (preferred_auth.nil? || preferred_auth == 'Kerberos')
326
opts['provider'] = 'Negotiate'
327
temp_response = kerberos_auth(opts)
328
if temp_response.kind_of? Rex::Proto::Http::Response
329
res = temp_response
330
end
331
return res
332
end
333
return res
334
end
335
336
# Converts username and password into the HTTP Basic authorization
337
# string.
338
#
339
# @return [String] A value suitable for use as an Authorization header
340
def basic_auth_header(username,password)
341
auth_str = username.to_s + ":" + password.to_s
342
auth_str = "Basic " + Rex::Text.encode_base64(auth_str)
343
end
344
345
346
def make_cnonce
347
Digest::MD5.hexdigest "%x" % (::Time.now.to_i + rand(65535))
348
end
349
350
# Send a series of requests to complete Digest Authentication
351
#
352
# @param opts [Hash] the options used to build an HTTP request
353
# @return [Response] the last valid HTTP response we received
354
def digest_auth(opts={})
355
cnonce = make_cnonce
356
nonce_count = 0
357
358
to = opts['timeout'] || 20
359
360
digest_user = opts['username'] || ""
361
digest_password = opts['password'] || ""
362
363
method = opts['method']
364
path = opts['uri']
365
iis = true
366
if (opts['DigestAuthIIS'] == false or self.config['DigestAuthIIS'] == false)
367
iis = false
368
end
369
370
begin
371
nonce_count += 1
372
373
resp = opts['response']
374
375
if not resp
376
# Get authentication-challenge from server, and read out parameters required
377
r = request_cgi(opts.merge({
378
'uri' => path,
379
'method' => method }))
380
resp = _send_recv(r, to)
381
unless resp.kind_of? Rex::Proto::Http::Response
382
return nil
383
end
384
385
if resp.code != 401
386
return resp
387
end
388
return resp unless resp.headers['WWW-Authenticate']
389
end
390
391
# Don't anchor this regex to the beginning of string because header
392
# folding makes it appear later when the server presents multiple
393
# WWW-Authentication options (such as is the case with IIS configured
394
# for Digest or NTLM).
395
resp['www-authenticate'] =~ /Digest (.*)/
396
397
parameters = {}
398
$1.split(/,[[:space:]]*/).each do |p|
399
k, v = p.split("=", 2)
400
parameters[k] = v.gsub('"', '')
401
end
402
403
qop = parameters['qop']
404
405
if parameters['algorithm'] =~ /(.*?)(-sess)?$/
406
algorithm = case $1
407
when 'MD5' then Digest::MD5
408
when 'SHA1' then Digest::SHA1
409
when 'SHA2' then Digest::SHA2
410
when 'SHA256' then Digest::SHA256
411
when 'SHA384' then Digest::SHA384
412
when 'SHA512' then Digest::SHA512
413
when 'RMD160' then Digest::RMD160
414
else raise Error, "unknown algorithm \"#{$1}\""
415
end
416
algstr = parameters["algorithm"]
417
sess = $2
418
else
419
algorithm = Digest::MD5
420
algstr = "MD5"
421
sess = false
422
end
423
424
a1 = if sess then
425
[
426
algorithm.hexdigest("#{digest_user}:#{parameters['realm']}:#{digest_password}"),
427
parameters['nonce'],
428
cnonce
429
].join ':'
430
else
431
"#{digest_user}:#{parameters['realm']}:#{digest_password}"
432
end
433
434
ha1 = algorithm.hexdigest(a1)
435
ha2 = algorithm.hexdigest("#{method}:#{path}")
436
437
request_digest = [ha1, parameters['nonce']]
438
request_digest.push(('%08x' % nonce_count), cnonce, qop) if qop
439
request_digest << ha2
440
request_digest = request_digest.join ':'
441
442
# Same order as IE7
443
auth = [
444
"Digest username=\"#{digest_user}\"",
445
"realm=\"#{parameters['realm']}\"",
446
"nonce=\"#{parameters['nonce']}\"",
447
"uri=\"#{path}\"",
448
"cnonce=\"#{cnonce}\"",
449
"nc=#{'%08x' % nonce_count}",
450
"algorithm=#{algstr}",
451
"response=\"#{algorithm.hexdigest(request_digest)[0, 32]}\"",
452
# The spec says the qop value shouldn't be enclosed in quotes, but
453
# some versions of IIS require it and Apache accepts it. Chrome
454
# and Firefox both send it without quotes but IE does it this way.
455
# Use the non-compliant-but-everybody-does-it to be as compatible
456
# as possible by default. The user can override if they don't like
457
# it.
458
if qop.nil? then
459
elsif iis then
460
"qop=\"#{qop}\""
461
else
462
"qop=#{qop}"
463
end,
464
if parameters.key? 'opaque' then
465
"opaque=\"#{parameters['opaque']}\""
466
end
467
].compact
468
469
headers ={ 'Authorization' => auth.join(', ') }
470
headers.merge!(opts['headers']) if opts['headers']
471
472
# Send main request with authentication
473
r = request_cgi(opts.merge({
474
'uri' => path,
475
'method' => method,
476
'headers' => headers }))
477
resp = _send_recv(r, to, true)
478
unless resp.kind_of? Rex::Proto::Http::Response
479
return nil
480
end
481
482
return resp
483
484
rescue ::Errno::EPIPE, ::Timeout::Error
485
end
486
end
487
488
def kerberos_auth(opts={})
489
to = opts['timeout'] || 20
490
auth_result = self.kerberos_authenticator.authenticate(mechanism: Rex::Proto::Gss::Mechanism::KERBEROS)
491
gss_data = auth_result[:security_blob]
492
gss_data_b64 = Rex::Text.encode_base64(gss_data)
493
494
# Separate options for the auth requests
495
auth_opts = opts.clone
496
auth_opts['headers'] = opts['headers'].clone
497
auth_opts['headers']['Authorization'] = "Kerberos #{gss_data_b64}"
498
499
if auth_opts['no_body_for_auth']
500
auth_opts.delete('data')
501
auth_opts.delete('krb_transform_request')
502
auth_opts.delete('krb_transform_response')
503
end
504
505
begin
506
# Send the auth request
507
r = request_cgi(auth_opts)
508
resp = _send_recv(r, to)
509
unless resp.kind_of? Rex::Proto::Http::Response
510
return nil
511
end
512
513
# Get the challenge and craft the response
514
response = resp.headers['WWW-Authenticate'].scan(/Kerberos ([A-Z0-9\x2b\x2f=]+)/ni).flatten[0]
515
return resp unless response
516
517
decoded = Rex::Text.decode_base64(response)
518
mutual_auth_result = self.kerberos_authenticator.parse_gss_init_response(decoded, auth_result[:session_key])
519
self.krb_encryptor = self.kerberos_authenticator.get_message_encryptor(mutual_auth_result[:ap_rep_subkey],
520
auth_result[:client_sequence_number],
521
mutual_auth_result[:server_sequence_number])
522
523
if opts['no_body_for_auth']
524
# If the body wasn't sent in the authentication, now do the actual request
525
r = request_cgi(opts)
526
resp = _send_recv(r, to, true)
527
end
528
return resp
529
530
rescue ::Errno::EPIPE, ::Timeout::Error
531
return nil
532
end
533
end
534
535
#
536
# Builds a series of requests to complete Negotiate Auth. Works essentially
537
# the same way as Digest auth. Same pipelining concerns exist.
538
#
539
# @option opts (see #send_request_cgi)
540
# @option opts provider ["Negotiate","NTLM"] What Negotiate provider to use
541
#
542
# @return [Response] the last valid HTTP response we received
543
def negotiate_auth(opts={})
544
545
to = opts['timeout'] || 20
546
opts['username'] ||= ''
547
opts['password'] ||= ''
548
549
if opts['provider'] and opts['provider'].include? 'Negotiate'
550
provider = "Negotiate "
551
else
552
provider = "NTLM "
553
end
554
555
opts['method']||= 'GET'
556
opts['headers']||= {}
557
558
workstation_name = Rex::Text.rand_text_alpha(rand(8)+6)
559
domain_name = self.config['domain']
560
561
ntlm_client = ::Net::NTLM::Client.new(
562
opts['username'],
563
opts['password'],
564
workstation: workstation_name,
565
domain: domain_name,
566
)
567
type1 = ntlm_client.init_context
568
569
begin
570
# Separate options for the auth requests
571
auth_opts = opts.clone
572
auth_opts['headers'] = opts['headers'].clone
573
auth_opts['headers']['Authorization'] = provider + type1.encode64
574
575
if auth_opts['no_body_for_auth']
576
auth_opts.delete('data')
577
auth_opts.delete('ntlm_transform_request')
578
auth_opts.delete('ntlm_transform_response')
579
end
580
581
# First request to get the challenge
582
r = request_cgi(auth_opts)
583
resp = _send_recv(r, to)
584
unless resp.kind_of? Rex::Proto::Http::Response
585
return nil
586
end
587
588
return resp unless resp.code == 401 && resp.headers['WWW-Authenticate']
589
590
# Get the challenge and craft the response
591
ntlm_challenge = resp.headers['WWW-Authenticate'].scan(/#{provider}([A-Z0-9\x2b\x2f=]+)/ni).flatten[0]
592
return resp unless ntlm_challenge
593
594
ntlm_message_3 = ntlm_client.init_context(ntlm_challenge, channel_binding)
595
596
self.ntlm_client = ntlm_client
597
# Send the response
598
auth_opts['headers']['Authorization'] = "#{provider}#{ntlm_message_3.encode64}"
599
r = request_cgi(auth_opts)
600
resp = _send_recv(r, to, true)
601
602
unless resp.kind_of? Rex::Proto::Http::Response
603
return nil
604
end
605
if opts['no_body_for_auth']
606
# If the body wasn't sent in the authentication, now do the actual request
607
r = request_cgi(opts)
608
resp = _send_recv(r, to, true)
609
end
610
return resp
611
612
rescue ::Errno::EPIPE, ::Timeout::Error
613
return nil
614
end
615
end
616
617
def channel_binding
618
if !self.conn.respond_to?(:peer_cert) or self.conn.peer_cert.nil?
619
nil
620
else
621
Net::NTLM::ChannelBinding.create(OpenSSL::X509::Certificate.new(self.conn.peer_cert))
622
end
623
end
624
625
# Read a response from the server
626
#
627
# Wait at most t seconds for the full response to be read in.
628
# If t is specified as a negative value, it indicates an indefinite wait cycle.
629
# If t is specified as nil or 0, it indicates no response parsing is required.
630
#
631
# @return [Response]
632
def read_response(t = -1, opts = {})
633
# Return a nil response if timeout is nil or 0
634
return if t.nil? || t == 0
635
636
resp = Response.new
637
resp.max_data = config['read_max_data']
638
639
original_request = opts.fetch(:original_request) { nil }
640
parse_opts = {}
641
unless original_request.nil?
642
parse_opts = { :orig_method => original_request.opts['method'] }
643
end
644
645
Timeout.timeout((t < 0) ? nil : t) do
646
647
rv = nil
648
while (
649
not conn.closed? and
650
rv != Packet::ParseCode::Completed and
651
rv != Packet::ParseCode::Error
652
)
653
654
begin
655
656
buff = conn.get_once(resp.max_data, 1)
657
rv = resp.parse(buff || '', parse_opts)
658
659
# Handle unexpected disconnects
660
rescue ::Errno::EPIPE, ::EOFError, ::IOError
661
case resp.state
662
when Packet::ParseState::ProcessingHeader
663
resp = nil
664
when Packet::ParseState::ProcessingBody
665
# truncated request, good enough
666
resp.error = :truncated
667
end
668
break
669
end
670
671
# This is a dirty hack for broken HTTP servers
672
if rv == Packet::ParseCode::Completed
673
rbody = resp.body
674
rbufq = resp.bufq
675
676
rblob = rbody.to_s + rbufq.to_s
677
tries = 0
678
begin
679
# XXX: This doesn't deal with chunked encoding
680
while tries < 1000 and resp.headers["Content-Type"] and resp.headers["Content-Type"].start_with?('text/html') and rblob !~ /<\/html>/i
681
buff = conn.get_once(-1, 0.05)
682
break if not buff
683
rblob += buff
684
tries += 1
685
end
686
rescue ::Errno::EPIPE, ::EOFError, ::IOError
687
end
688
689
resp.bufq = ""
690
resp.body = rblob
691
end
692
end
693
end
694
695
return resp if not resp
696
697
# As a last minute hack, we check to see if we're dealing with a 100 Continue here.
698
# Most of the time this is handled by the parser via check_100()
699
if resp.proto == '1.1' and resp.code == 100 and not opts[:skip_100]
700
# Read the real response from the body if we found one
701
# If so, our real response became the body, so we re-parse it.
702
if resp.body.to_s =~ /^HTTP/
703
body = resp.body
704
resp = Response.new
705
resp.max_data = config['read_max_data']
706
rv = resp.parse(body, parse_opts)
707
# We found a 100 Continue but didn't read the real reply yet
708
# Otherwise reread the reply, but don't try this hack again
709
else
710
resp = read_response(t, :skip_100 => true)
711
end
712
end
713
714
resp
715
rescue Timeout::Error
716
# Allow partial response due to timeout
717
resp if config['partial']
718
end
719
720
#
721
# Cleans up any outstanding connections and other resources.
722
#
723
def stop
724
close
725
end
726
727
#
728
# Returns whether or not the conn is valid.
729
#
730
def conn?
731
conn != nil
732
end
733
734
#
735
# Whether or not connections should be pipelined.
736
#
737
def pipelining?
738
pipeline
739
end
740
741
#
742
# Target host addr and port for this connection
743
#
744
def peerinfo
745
if self.conn
746
pi = self.conn.peerinfo || nil
747
if pi
748
return {
749
'addr' => pi.split(':')[0],
750
'port' => pi.split(':')[1].to_i
751
}
752
end
753
end
754
nil
755
end
756
757
#
758
# An optional comm to use for creating the underlying socket.
759
#
760
attr_accessor :comm
761
#
762
# The client request configuration
763
#
764
attr_accessor :config
765
#
766
# The client request configuration classes
767
#
768
attr_accessor :config_types
769
#
770
# Whether or not pipelining is in use.
771
#
772
attr_accessor :pipeline
773
#
774
# The local host of the client.
775
#
776
attr_accessor :local_host
777
#
778
# The local port of the client.
779
#
780
attr_accessor :local_port
781
#
782
# The underlying connection.
783
#
784
attr_accessor :conn
785
#
786
# The calling context to pass to the socket
787
#
788
attr_accessor :context
789
#
790
# The proxy list
791
#
792
attr_accessor :proxies
793
794
# Auth
795
attr_accessor :username, :password, :kerberos_authenticator
796
797
# When parsing the request, thunk off the first response from the server, since junk
798
attr_accessor :junk_pipeline
799
800
# @return [Rex::Proto::Http::HttpSubscriber] The HTTP subscriber
801
attr_accessor :subscriber
802
803
protected
804
805
# https
806
attr_accessor :ssl, :ssl_version # :nodoc:
807
808
attr_accessor :hostname, :port # :nodoc:
809
810
#
811
# The established NTLM connection info
812
#
813
attr_accessor :ntlm_client
814
815
#
816
# The established kerberos connection info
817
#
818
attr_accessor :krb_encryptor
819
end
820
821
end
822
end
823
end
824
825