librex 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. data/README +4 -0
  2. data/lib/rex/exploitation/cmdstager.rb +9 -133
  3. data/lib/rex/exploitation/cmdstager/base.rb +170 -0
  4. data/lib/rex/exploitation/cmdstager/debug_asm.rb +142 -0
  5. data/lib/rex/exploitation/cmdstager/debug_write.rb +136 -0
  6. data/lib/rex/exploitation/cmdstager/tftp.rb +63 -0
  7. data/lib/rex/exploitation/cmdstager/vbs.rb +128 -0
  8. data/lib/rex/io/stream.rb +2 -2
  9. data/lib/rex/io/stream_server.rb +1 -1
  10. data/lib/rex/job_container.rb +7 -6
  11. data/lib/rex/mime/header.rb +12 -10
  12. data/lib/rex/mime/message.rb +57 -26
  13. data/lib/rex/ole/directory.rb +5 -4
  14. data/lib/rex/ole/samples/create_ole.rb +0 -0
  15. data/lib/rex/ole/samples/dir.rb +0 -0
  16. data/lib/rex/ole/samples/dump_stream.rb +1 -1
  17. data/lib/rex/ole/samples/ole_info.rb +0 -0
  18. data/lib/rex/parser/nexpose_xml.rb +131 -0
  19. data/lib/rex/parser/nmap_xml.rb +1 -0
  20. data/lib/rex/peparsey/pe.rb +21 -3
  21. data/lib/rex/post/meterpreter/client.rb +6 -1
  22. data/lib/rex/post/meterpreter/client_core.rb +2 -2
  23. data/lib/rex/post/meterpreter/extensions/priv/priv.rb +19 -18
  24. data/lib/rex/post/meterpreter/packet.rb +68 -0
  25. data/lib/rex/post/meterpreter/packet_dispatcher.rb +2 -2
  26. data/lib/rex/post/meterpreter/packet_response_waiter.rb +5 -5
  27. data/lib/rex/post/meterpreter/ui/console.rb +2 -0
  28. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +5 -2
  29. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +2 -2
  30. data/lib/rex/proto/dcerpc/client.rb.ut.rb +0 -0
  31. data/lib/rex/proto/http/client.rb +8 -3
  32. data/lib/rex/proto/http/packet.rb +11 -1
  33. data/lib/rex/proto/smb/client.rb +1 -1
  34. data/lib/rex/proto/smb/utils.rb +72 -24
  35. data/lib/rex/proto/tftp.rb +3 -0
  36. data/lib/rex/proto/tftp/constants.rb +37 -0
  37. data/lib/rex/proto/tftp/server.rb +249 -0
  38. data/lib/rex/proto/tftp/server.rb.ut.rb +28 -0
  39. data/lib/rex/script/meterpreter.rb +6 -0
  40. data/lib/rex/services/local_relay.rb +2 -2
  41. data/lib/rex/socket/ip.rb +9 -8
  42. data/lib/rex/socket/range_walker.rb +43 -5
  43. data/lib/rex/socket/udp.rb +11 -4
  44. data/lib/rex/text.rb +42 -19
  45. data/lib/rex/ui/interactive.rb +24 -22
  46. data/lib/rex/ui/text/irb_shell.rb +4 -2
  47. data/lib/rex/ui/text/output/file.rb +6 -0
  48. data/lib/rex/ui/text/shell.rb +14 -18
  49. data/lib/rex/zip/samples/comment.rb +0 -0
  50. data/lib/rex/zip/samples/mkwar.rb +0 -0
  51. data/lib/rex/zip/samples/mkzip.rb +0 -0
  52. data/lib/rex/zip/samples/recursive.rb +0 -0
  53. metadata +20 -5
@@ -24,6 +24,17 @@ TLV_META_TYPE_COMPRESSED = (1 << 29)
24
24
  TLV_META_TYPE_GROUP = (1 << 30)
25
25
  TLV_META_TYPE_COMPLEX = (1 << 31)
26
26
 
27
+ # Exclude compressed from the mask since other meta types (e.g. RAW) can also
28
+ # be compressed
29
+ TLV_META_MASK = (
30
+ TLV_META_TYPE_STRING |
31
+ TLV_META_TYPE_UINT |
32
+ TLV_META_TYPE_RAW |
33
+ TLV_META_TYPE_BOOL |
34
+ TLV_META_TYPE_GROUP |
35
+ TLV_META_TYPE_COMPLEX
36
+ )
37
+
27
38
  #
28
39
  # TLV base starting points
29
40
  #
@@ -114,6 +125,63 @@ class Tlv
114
125
  end
115
126
  end
116
127
 
128
+ def inspect
129
+ utype = type ^ TLV_META_TYPE_COMPRESSED
130
+ meta = case (utype & TLV_META_MASK)
131
+ when TLV_META_TYPE_STRING; "STRING"
132
+ when TLV_META_TYPE_UINT; "INT"
133
+ when TLV_META_TYPE_RAW; "RAW"
134
+ when TLV_META_TYPE_BOOL; "BOOL"
135
+ when TLV_META_TYPE_GROUP; "GROUP"
136
+ when TLV_META_TYPE_COMPLEX; "COMPLEX"
137
+ else; 'unknown-meta-type'
138
+ end
139
+ stype = case type
140
+ when TLV_TYPE_REQUEST_ID; "REQUEST-ID"
141
+ when TLV_TYPE_METHOD; "METHOD"
142
+ when TLV_TYPE_RESULT; "RESULT"
143
+ when TLV_TYPE_EXCEPTION; "EXCEPTION"
144
+ when TLV_TYPE_STRING; "STRING"
145
+ when TLV_TYPE_UINT; "UINT"
146
+ when TLV_TYPE_BOOL; "BOOL"
147
+
148
+ when TLV_TYPE_LENGTH; "LENGTH"
149
+ when TLV_TYPE_DATA; "DATA"
150
+ when TLV_TYPE_FLAGS; "FLAGS"
151
+
152
+ when TLV_TYPE_CHANNEL_ID; "CHANNEL-ID"
153
+ when TLV_TYPE_CHANNEL_TYPE; "CHANNEL-TYPE"
154
+ when TLV_TYPE_CHANNEL_DATA; "CHANNEL-DATA"
155
+ when TLV_TYPE_CHANNEL_DATA_GROUP; "CHANNEL-DATA-GROUP"
156
+ when TLV_TYPE_CHANNEL_CLASS; "CHANNEL-CLASS"
157
+ when TLV_TYPE_CHANNEL_PARENTID; "CHANNEL-PARENTID"
158
+
159
+ when TLV_TYPE_SEEK_WHENCE; "SEEK-WHENCE"
160
+ when TLV_TYPE_SEEK_OFFSET; "SEEK-OFFSET"
161
+ when TLV_TYPE_SEEK_POS; "SEEK-POS"
162
+
163
+ when TLV_TYPE_EXCEPTION_CODE; "EXCEPTION-CODE"
164
+ when TLV_TYPE_EXCEPTION_STRING; "EXCEPTION-STRING"
165
+
166
+ when TLV_TYPE_LIBRARY_PATH; "LIBRARY-PATH"
167
+ when TLV_TYPE_TARGET_PATH; "TARGET-PATH"
168
+ when TLV_TYPE_MIGRATE_PID; "MIGRATE-PID"
169
+ when TLV_TYPE_MIGRATE_LEN; "MIGRATE-LEN"
170
+ when TLV_TYPE_MIGRATE_PAYLOAD; "MIGRATE-PAYLOAD"
171
+ when TLV_TYPE_MIGRATE_ARCH; "MIGRATE-ARCH"
172
+
173
+ # Extension classes don't exist yet, so can't use their constants
174
+ # here.
175
+ #when Extensions::Stdapi::TLV_TYPE_IP; 'ip-address'
176
+ else; "unknown-#{type}"
177
+ end
178
+ val = value.inspect
179
+ if val.length > 50
180
+ val = val[0,50] + ' ..."'
181
+ end
182
+ "#<#{self.class} type=#{stype} meta-type=#{meta} #{self.class.to_s =~ /Packet/ ? "tlvs=#{@tlvs.inspect}" : "value=#{val}"} >"
183
+ end
184
+
117
185
  ##
118
186
  #
119
187
  # Conditionals
@@ -71,7 +71,7 @@ module PacketDispatcher
71
71
  response = send_packet_wait_response(packet, t)
72
72
 
73
73
  if (response == nil)
74
- raise TimeoutError
74
+ raise TimeoutError.new("Send timed out")
75
75
  elsif (response.result != 0)
76
76
  e = RequestError.new(packet.method, response.result)
77
77
 
@@ -186,7 +186,7 @@ module PacketDispatcher
186
186
  # thread above.
187
187
  while(not @finish)
188
188
  if(@pqueue.empty?)
189
- select(nil, nil, nil, 0.10)
189
+ ::IO.select(nil, nil, nil, 0.10)
190
190
  next
191
191
  end
192
192
 
@@ -59,19 +59,19 @@ class PacketResponseWaiter
59
59
  def wait(interval)
60
60
  if( interval and interval == -1 )
61
61
  while(not self.done)
62
- select(nil, nil, nil, 0.1)
63
- end
64
- else
62
+ ::IO.select(nil, nil, nil, 0.1)
63
+ end
64
+ else
65
65
  begin
66
66
  Timeout.timeout(interval) {
67
67
  while(not self.done)
68
- select(nil, nil, nil, 0.1)
68
+ ::IO.select(nil, nil, nil, 0.1)
69
69
  end
70
70
  }
71
71
  rescue Timeout::Error
72
72
  self.response = nil
73
73
  end
74
- end
74
+ end
75
75
  return self.response
76
76
  end
77
77
 
@@ -53,6 +53,8 @@ class Console
53
53
  # assumed that init_ui has been called prior.
54
54
  #
55
55
  def interact(&block)
56
+ init_tab_complete
57
+
56
58
  # Run queued commands
57
59
  commands.delete_if { |ent|
58
60
  run_single(ent)
@@ -86,8 +86,11 @@ class Console::CommandDispatcher::Stdapi::Fs
86
86
  print_line("Usage: cd directory")
87
87
  return true
88
88
  end
89
-
90
- client.fs.dir.chdir(args[0])
89
+ if args[0] =~ /\%(\w*)\%/
90
+ client.fs.dir.chdir(client.fs.file.expand_path(args[0].upcase))
91
+ else
92
+ client.fs.dir.chdir(args[0])
93
+ end
91
94
 
92
95
  return true
93
96
  end
@@ -460,8 +460,8 @@ class Console::CommandDispatcher::Stdapi::Sys
460
460
 
461
461
  print_line("Computer: " + info['Computer'])
462
462
  print_line("OS : " + info['OS'])
463
- print_line("Arch : " + info['Architecture'])
464
- print_line("Language: " + info['System Language'])
463
+ print_line("Arch : " + info['Architecture']) if info['Architecture']
464
+ print_line("Language: " + info['System Language']) if info['System Language']
465
465
 
466
466
  return true
467
467
  end
File without changes
@@ -301,15 +301,18 @@ class Client
301
301
  end
302
302
 
303
303
  #
304
- # Transmit a HTTP request and receive the response
304
+ # Transmit an HTTP request and receive the response
305
+ # If persist is set, then the request will attempt
306
+ # to reuse an existing connection.
305
307
  #
306
- def send_recv(req, t = -1)
308
+ def send_recv(req, t = -1, persist=false)
309
+ @pipeline = persist
307
310
  send_request(req)
308
311
  read_response(t)
309
312
  end
310
313
 
311
314
  #
312
- # Send a HTTP request to the server
315
+ # Send an HTTP request to the server
313
316
  #
314
317
  def send_request(req)
315
318
  connect
@@ -362,10 +365,12 @@ class Client
362
365
  rblob = rbody.to_s + rbufq.to_s
363
366
  tries = 0
364
367
  begin
368
+ # XXX This doesn't deal with chunked encoding or "Content-type: text/html; charset=..."
365
369
  while tries < 20 and resp.headers["Content-Type"]== "text/html" and rblob !~ /<\/html>/i
366
370
  buff = conn.get_once(-1, 0.05)
367
371
  break if not buff
368
372
  rblob += buff
373
+ tries += 1
369
374
  end
370
375
  rescue ::Errno::EPIPE, ::EOFError, ::IOError
371
376
  end
@@ -92,6 +92,7 @@ class Packet
92
92
  end
93
93
  end
94
94
  rescue
95
+ # This rescue might be a problem because it will swallow TimeoutError
95
96
  self.error = $!
96
97
  return ParseCode::Error
97
98
  end
@@ -331,8 +332,17 @@ protected
331
332
  # Remove any leading newlines or spaces
332
333
  self.bufq.lstrip!
333
334
 
335
+ # If we didn't get a newline, then this might not be the full
336
+ # length, go back and get more.
337
+ # e.g.
338
+ # first packet: "200"
339
+ # second packet: "0\r\n\r\n<html>..."
340
+ if not bufq.index("\n")
341
+ return
342
+ end
343
+
334
344
  # Extract the actual hexadecimal length value
335
- clen = self.bufq.slice!(/^[a-zA-Z0-9]*\r?\n/)
345
+ clen = self.bufq.slice!(/^[a-fA-F0-9]+\r?\n/)
336
346
 
337
347
  clen.rstrip! if (clen)
338
348
 
@@ -96,7 +96,7 @@ EVADE = Rex::Proto::SMB::Evasions
96
96
  while ( (chunk = data.slice!(0, size)).length > 0 )
97
97
  ret = self.socket.put(chunk)
98
98
  if (wait > 0)
99
- select(nil, nil, nil, wait)
99
+ ::IO.select(nil, nil, nil, wait)
100
100
  end
101
101
  end
102
102
  return ret
@@ -347,26 +347,74 @@ CONST = Rex::Proto::SMB::Constants
347
347
  #
348
348
  # Process Type 3 NTLM Message (in Base64)
349
349
  #
350
+ # from http://www.innovation.ch/personal/ronald/ntlm.html
351
+ #
352
+ # struct {
353
+ # byte protocol[8]; // 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
354
+ # byte type; // 0x03
355
+ # byte zero[3];
356
+ #
357
+ # short lm_resp_len; // LanManager response length (always 0x18)
358
+ # short lm_resp_len; // LanManager response length (always 0x18)
359
+ # short lm_resp_off; // LanManager response offset
360
+ # byte zero[2];
361
+ #
362
+ # short nt_resp_len; // NT response length (always 0x18)
363
+ # short nt_resp_len; // NT response length (always 0x18)
364
+ # short nt_resp_off; // NT response offset
365
+ # byte zero[2];
366
+ #
367
+ # short dom_len; // domain string length
368
+ # short dom_len; // domain string length
369
+ # short dom_off; // domain string offset (always 0x40)
370
+ # byte zero[2];
371
+ #
372
+ # short user_len; // username string length
373
+ # short user_len; // username string length
374
+ # short user_off; // username string offset
375
+ # byte zero[2];
376
+ #
377
+ # short host_len; // host string length
378
+ # short host_len; // host string length
379
+ # short host_off; // host string offset
380
+ # byte zero[6];
381
+ #
382
+ # short msg_len; // message length
383
+ # byte zero[2];
384
+ #
385
+ # short flags; // 0x8201
386
+ # byte zero[2];
387
+ #
388
+ # byte dom[*]; // domain string (unicode UTF-16LE)
389
+ # byte user[*]; // username string (unicode UTF-16LE)
390
+ # byte host[*]; // host string (unicode UTF-16LE)
391
+ # byte lm_resp[*]; // LanManager response
392
+ # byte nt_resp[*]; // NT response
393
+ # } type_3_message
394
+ #
350
395
  def self.process_type3_message(message)
351
396
  decode = Rex::Text.decode_base64(message.strip)
352
- type = decode[8]
397
+ type = decode[8,1].unpack("C").first
353
398
  if (type == 3)
354
- domoff = decode[32] # domain offset
355
- domlen = decode[28] # domain length
356
- useroff = decode[40] # username offset
357
- userlen = decode[36] # username length
358
- hostoff = decode[48] # hostname offset
359
- hostlen = decode[44] # hostname length
360
- lmoff = decode[16] # LM hash offset
361
- lmlen = decode[12] # LM hash length
362
- ntoff = decode[24] # NT hash offset
363
- ntlen = decode[20] # NT hash length
364
-
365
- domain = decode[domoff..domoff+domlen-1]
366
- user = decode[useroff..useroff+userlen-1]
367
- host = decode[hostoff..hostoff+hostlen-1]
368
- lm = decode[lmoff..lmoff+lmlen-1].unpack("H*")
369
- nt = decode[ntoff..ntoff+ntlen-1].unpack("H*")
399
+ lm_len = decode[12,2].unpack("v").first
400
+ lm_offset = decode[16,2].unpack("v").first
401
+ lm = decode[lm_offset, lm_len].unpack("H*").first
402
+
403
+ nt_len = decode[20,2].unpack("v").first
404
+ nt_offset = decode[24,2].unpack("v").first
405
+ nt = decode[nt_offset, nt_len].unpack("H*").first
406
+
407
+ dom_len = decode[28,2].unpack("v").first
408
+ dom_offset = decode[32,2].unpack("v").first
409
+ domain = decode[dom_offset, dom_len]
410
+
411
+ user_len = decode[36,2].unpack("v").first
412
+ user_offset = decode[40,2].unpack("v").first
413
+ user = decode[user_offset, user_len]
414
+
415
+ host_len = decode[44,2].unpack("v").first
416
+ host_offset = decode[48,2].unpack("v").first
417
+ host = decode[host_offset, host_len]
370
418
 
371
419
  return domain, user, host, lm, nt
372
420
  else
@@ -386,13 +434,13 @@ CONST = Rex::Proto::SMB::Constants
386
434
  win_name = Rex::Text.to_unicode(win_name)
387
435
  decode = Rex::Text.decode_base64(message.strip)
388
436
 
389
- type = decode[8]
437
+ type = decode[8,1].unpack("C").first
390
438
 
391
439
  if (type == 1)
392
440
  # A type 1 message has been received, lets build a type 2 message response
393
441
 
394
- reqflags = decode[12..15]
395
- reqflags = Integer("0x" + reqflags.unpack("h8").to_s.reverse)
442
+ reqflags = decode[12,4]
443
+ reqflags = reqflags.unpack("V").first
396
444
 
397
445
  if (reqflags & CONST::REQUEST_TARGET) == CONST::REQUEST_TARGET
398
446
 
@@ -470,12 +518,12 @@ CONST = Rex::Proto::SMB::Constants
470
518
  def self.downgrade_type_message(message)
471
519
  decode = Rex::Text.decode_base64(message.strip)
472
520
 
473
- type = decode[8]
521
+ type = decode[8,1].unpack("C").first
474
522
 
475
523
  if (type > 0 and type < 4)
476
524
  reqflags = decode[12..15] if (type == 1 or type == 3)
477
525
  reqflags = decode[20..23] if (type == 2)
478
- reqflags = Integer("0x" + reqflags.unpack("h8").to_s.reverse)
526
+ reqflags = reqflags.unpack("V")
479
527
 
480
528
  # Remove NEGOTIATE_NTLMV2_KEY and NEGOTIATE_ALWAYS_SIGN, this lowers the negotiation
481
529
  # down to LMv1/NTLMv1.
@@ -497,9 +545,9 @@ CONST = Rex::Proto::SMB::Constants
497
545
  idx = 0
498
546
  0.upto(3) do |cnt|
499
547
  if (type == 2)
500
- decode[23-cnt] = Integer("0x" + flags[idx .. idx + 1])
548
+ decode[23-cnt] = [flags[idx,1]].pack("C")
501
549
  else
502
- decode[15-cnt] = Integer("0x" + flags[idx .. idx + 1])
550
+ decode[15-cnt] = [flags[idx,1]].pack("C")
503
551
  end
504
552
  idx += 2
505
553
  end
@@ -0,0 +1,3 @@
1
+ # $Id: tftp.rb 9333 2010-05-21 00:03:04Z jduck $
2
+ require 'rex/proto/tftp/constants'
3
+ require 'rex/proto/tftp/server'
@@ -0,0 +1,37 @@
1
+ # $Id: constants.rb 9334 2010-05-21 00:15:10Z jduck $
2
+ require 'rex/proto/tftp'
3
+
4
+ module Rex
5
+ module Proto
6
+ module TFTP
7
+
8
+ OPCODES = %w{ Unknown RRQ WRQ DATA ACK ERROR }
9
+ OpRead = 1
10
+ OpWrite = 2
11
+ OpData = 3
12
+ OpAck = 4
13
+ OpError = 5
14
+
15
+
16
+ ERRCODES = [
17
+ "Undefined",
18
+ "File not found",
19
+ "Access violation",
20
+ "Disk full or allocation exceeded",
21
+ "Illegal TFTP operation",
22
+ "Unknown transfer ID",
23
+ "File already exists",
24
+ "No such user"
25
+ ]
26
+
27
+ ErrFileNotFound = 1
28
+ ErrAccessViolation = 2
29
+ ErrDiskFull = 3
30
+ ErrIllegalOperation = 4
31
+ ErrUnknownTransferId = 5
32
+ ErrFileExists = 6
33
+ ErrNoSuchUser = 7
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,249 @@
1
+ # $Id: server.rb 9375 2010-05-26 22:39:56Z jduck $
2
+ require 'rex/socket'
3
+ require 'rex/proto/tftp'
4
+
5
+ module Rex
6
+ module Proto
7
+ module TFTP
8
+
9
+ #
10
+ # Little util function
11
+ #
12
+ def self.get_string(data)
13
+ ret = data.slice!(0,data.index("\x00"))
14
+ # Slice off the nul byte.
15
+ data.slice!(0,1)
16
+ ret
17
+ end
18
+
19
+
20
+ ##
21
+ #
22
+ # TFTP Server class
23
+ #
24
+ ##
25
+ class Server
26
+
27
+ def initialize(port = 69, listen_host = '0.0.0.0', context = {})
28
+ self.listen_host = listen_host
29
+ self.listen_port = port
30
+ self.context = context
31
+ self.sock = nil
32
+ @shutting_down = false
33
+
34
+ self.files = []
35
+ self.transfers = []
36
+ end
37
+
38
+
39
+ #
40
+ # Start the TFTP server
41
+ #
42
+ def start
43
+ self.sock = Rex::Socket::Udp.create(
44
+ 'LocalHost' => listen_host,
45
+ 'LocalPort' => listen_port,
46
+ 'Context' => context
47
+ )
48
+
49
+ self.thread = Thread.new {
50
+ monitor_socket
51
+ }
52
+ end
53
+
54
+
55
+ #
56
+ # Stop the TFTP server
57
+ #
58
+ def stop
59
+ @shutting_down = true
60
+
61
+ # Wait a maximum of 30 seconds for all transfers to finish.
62
+ start = Time.now
63
+ while (self.transfers.length > 0)
64
+ ::IO.select(nil, nil, nil, 0.5)
65
+ dur = Time.now - start
66
+ break if (dur > 30)
67
+ end
68
+
69
+ self.files.clear
70
+ self.thread.kill
71
+ self.sock.close
72
+ end
73
+
74
+
75
+ #
76
+ # Register a filename and content for a client to request
77
+ #
78
+ def register_file(fn, content)
79
+ self.files << {
80
+ :name => fn,
81
+ :data => content
82
+ }
83
+ end
84
+
85
+
86
+ #
87
+ # Send an error packet w/the specified code and string
88
+ #
89
+ def send_error(from, num)
90
+ if (num < 1 or num >= ERRCODES.length)
91
+ # ignore..
92
+ return
93
+ end
94
+ pkt = [OpError, num].pack('nn')
95
+ pkt << ERRCODES[num]
96
+ pkt << "\x00"
97
+ send_packet(from, pkt)
98
+ end
99
+
100
+
101
+ #
102
+ # Send a single packet to the specified host
103
+ #
104
+ def send_packet(from, pkt)
105
+ self.sock.sendto(pkt, from[0], from[1])
106
+ end
107
+
108
+
109
+ #
110
+ # Find the hash entry for a file that may be offered
111
+ #
112
+ def find_file(fname)
113
+ self.files.each do |f|
114
+ if (fname == f[:name])
115
+ return f
116
+ end
117
+ end
118
+ nil
119
+ end
120
+
121
+
122
+ attr_accessor :listen_host, :listen_port, :context
123
+ attr_accessor :sock, :files, :transfers
124
+ attr_accessor :thread
125
+
126
+
127
+ protected
128
+
129
+ #
130
+ # See if there is anything to do.. If so, dispatch it.
131
+ #
132
+ def monitor_socket
133
+ while true
134
+ rds = [@sock]
135
+ wds = []
136
+ self.transfers.each do |tr|
137
+ if (not tr[:last_sent])
138
+ wds << @sock
139
+ break
140
+ end
141
+ end
142
+ eds = [@sock]
143
+
144
+ r,w,e = ::IO.select(rds,wds,eds,1)
145
+
146
+ if (r != nil and r[0] == self.sock)
147
+ buf,host,port = self.sock.recvfrom(65535)
148
+ # Lame compatabilitiy :-/
149
+ from = [host, port]
150
+ dispatch_request(from, buf)
151
+ end
152
+
153
+ #
154
+ # Check to see if transfers need maintenance
155
+ #
156
+ self.transfers.each do |tr|
157
+ # Are we awaiting an ack?
158
+ if (tr[:last_sent])
159
+ elapsed = Time.now - tr[:last_sent]
160
+ if (elapsed >= 3)
161
+ # max retries reached?
162
+ if (tr[:retries] < 3)
163
+ #puts "[-] ack timed out, resending block"
164
+ tr[:last_sent] = nil
165
+ tr[:retries] += 1
166
+ else
167
+ #puts "[-] maximum tries reached, terminating transfer"
168
+ self.transfers.delete(tr)
169
+ end
170
+ end
171
+ elsif (w != nil and w[0] == self.sock)
172
+ # No ack waiting, send next block..
173
+ chunk = tr[:file][:data].slice(tr[:offset], 512)
174
+ if (chunk and chunk.length >= 0)
175
+ pkt = [OpData, tr[:block]].pack('nn')
176
+ pkt << chunk
177
+ send_packet(tr[:from], pkt)
178
+ tr[:last_sent] = Time.now
179
+ else
180
+ # no more chunks.. transfer is most likely done.
181
+ self.transfers.delete(tr)
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+
189
+ #
190
+ # Dispatch a packet that we received
191
+ #
192
+ def dispatch_request(from, buf)
193
+
194
+ op = buf.unpack('n')[0]
195
+ buf.slice!(0,2)
196
+
197
+ #start = "[*] TFTP - %s:%u - %s" % [from[0], from[1], OPCODES[op]]
198
+
199
+ case op
200
+ when OpRead
201
+ # Process RRQ packets
202
+ fn = TFTP::get_string(buf)
203
+ mode = TFTP::get_string(buf).downcase
204
+
205
+ #puts "%s %s %s" % [start, fn, mode]
206
+
207
+ if (not @shutting_down) and (file = self.find_file(fn))
208
+ self.transfers << {
209
+ :from => from,
210
+ :file => file,
211
+ :block => 1,
212
+ :offset => 0,
213
+ :last_sent => nil,
214
+ :retries => 0
215
+ }
216
+ else
217
+ #puts "[-] file not found!"
218
+ send_error(from, ErrFileNotFound)
219
+ end
220
+
221
+ when OpAck
222
+ # Process ACK packets
223
+ block = buf.unpack('n')[0]
224
+ #puts "%s %d" % [start, block]
225
+
226
+ self.transfers.each do |tr|
227
+ if (from == tr[:from] and block == tr[:block])
228
+ # acked! send the next block
229
+ tr[:block] += 1
230
+ tr[:offset] += 512
231
+ tr[:last_sent] = nil
232
+ tr[:retries] = 0
233
+ end
234
+ end
235
+
236
+ else
237
+ # Other packets are unsupported
238
+ #puts start
239
+ send_error(from, ErrAccessViolation)
240
+
241
+ end
242
+ end
243
+
244
+ end
245
+
246
+ end
247
+ end
248
+ end
249
+