librex 0.0.6 → 0.0.7
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.
- data/README.md +3 -5
- data/Rakefile +26 -0
- data/lib/rex/compat.rb +1 -1
- data/lib/rex/exploitation/javascriptosdetect.rb +125 -62
- data/lib/rex/file.rb +15 -0
- data/lib/rex/io/stream.rb +1 -1
- data/lib/rex/parser/nmap_xml.rb +6 -0
- data/lib/rex/poly/block.rb +9 -0
- data/lib/rex/post/meterpreter/client.rb +0 -8
- data/lib/rex/post/meterpreter/extensions/priv/priv.rb +6 -0
- data/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb +1 -1
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_advapi32.rb +49 -35
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb +26 -0
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +9 -2
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb +630 -0
- data/lib/rex/post/meterpreter/packet.rb +3 -1
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +143 -57
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +6 -0
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb +9 -3
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +6 -4
- data/lib/rex/proto.rb +1 -0
- data/lib/rex/proto/dhcp/server.rb +4 -2
- data/lib/rex/proto/http/packet.rb +5 -6
- data/lib/rex/proto/ntlm.rb +7 -0
- data/lib/rex/proto/ntlm.rb.ut.rb +177 -0
- data/lib/rex/proto/ntlm/base.rb +326 -0
- data/lib/rex/proto/ntlm/constants.rb +74 -0
- data/lib/rex/proto/ntlm/crypt.rb +340 -0
- data/lib/rex/proto/ntlm/exceptions.rb +9 -0
- data/lib/rex/proto/ntlm/message.rb +533 -0
- data/lib/rex/proto/ntlm/utils.rb +358 -0
- data/lib/rex/proto/smb/client.rb +548 -86
- data/lib/rex/proto/smb/client.rb.ut.rb +4 -4
- data/lib/rex/proto/smb/constants.rb +7 -24
- data/lib/rex/proto/smb/crypt.rb +12 -71
- data/lib/rex/proto/smb/exceptions.rb +12 -0
- data/lib/rex/proto/smb/simpleclient.rb +17 -5
- data/lib/rex/proto/smb/utils.rb +3 -460
- data/lib/rex/proto/tftp/server.rb +2 -2
- data/lib/rex/script/base.rb +2 -2
- data/lib/rex/socket.rb +12 -0
- data/lib/rex/socket.rb.ut.rb +31 -10
- data/lib/rex/socket/ssl_tcp_server.rb.ut.rb +15 -5
- data/lib/rex/text.rb +55 -4
- data/lib/rex/ui/output.rb +0 -2
- data/lib/rex/ui/text/dispatcher_shell.rb +95 -10
- data/lib/rex/ui/text/output/buffer.rb +0 -4
- data/lib/rex/ui/text/shell.rb +8 -0
- data/lib/rex/ui/text/table.rb +21 -1
- metadata +15 -19
- data/lib/rex/proto/smb/crypt.rb.ut.rb +0 -20
@@ -1,4 +1,4 @@
|
|
1
|
-
# $Id: server.rb
|
1
|
+
# $Id: server.rb 11636 2011-01-25 02:24:37Z hdm $
|
2
2
|
require 'rex/socket'
|
3
3
|
require 'rex/proto/tftp'
|
4
4
|
|
@@ -194,7 +194,7 @@ protected
|
|
194
194
|
if @output_dir
|
195
195
|
fn = tr[:file][:name].split(File::SEPARATOR)[-1]
|
196
196
|
if fn
|
197
|
-
fn = ::File.join(@output_dir, fn)
|
197
|
+
fn = ::File.join(@output_dir, Rex::FileUtils.clean_path(fn))
|
198
198
|
::File.open(fn, "wb") { |fd|
|
199
199
|
fd.write(tr[:file][:data])
|
200
200
|
}
|
data/lib/rex/script/base.rb
CHANGED
data/lib/rex/socket.rb
CHANGED
@@ -131,6 +131,18 @@ module Socket
|
|
131
131
|
(addr =~ /^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))$/) ? true : false
|
132
132
|
end
|
133
133
|
|
134
|
+
#
|
135
|
+
# Return true if +addr+ is within the ranges specified in RFC1918, or
|
136
|
+
# RFC5735/RFC3927
|
137
|
+
#
|
138
|
+
def self.is_internal?(addr)
|
139
|
+
if self.dotted_ip?(addr)
|
140
|
+
addr =~ /^(?:10\.|192\.168|172.(?:1[6-9]|2[0-9]|3[01])\.|169\.254)/
|
141
|
+
else
|
142
|
+
false
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
134
146
|
#
|
135
147
|
# Wrapper for Resolv.getaddress that takes special care to see if the
|
136
148
|
# supplied address is already a dotted quad, for instance. This is
|
data/lib/rex/socket.rb.ut.rb
CHANGED
@@ -42,25 +42,35 @@ class Rex::Socket::UnitTest < Test::Unit::TestCase
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def test_to_sockaddr
|
45
|
-
assert_equal("
|
46
|
-
|
47
|
-
|
45
|
+
assert_equal(([2] + [0]*14).pack("sC*"), Rex::Socket.to_sockaddr(0, 0), "null sockaddr")
|
46
|
+
=begin
|
47
|
+
# This is platform dependent, pain to test
|
48
|
+
if (Rex::Socket.support_ipv6?)
|
49
|
+
# Use the constant for AF_INET6 since it is different per platform
|
50
|
+
# (10 on linux and 28 on BSD)
|
51
|
+
inaddr_any_sockaddr = ([::Socket::AF_INET6, 22] + [0]*24).pack('sSC*')
|
52
|
+
else
|
53
|
+
inaddr_any_sockaddr = ([2, 22] + [0]*12).pack('snC*')
|
54
|
+
end
|
55
|
+
=end
|
56
|
+
assert_equal(([2, 0x16, 1, 2, 3, 4] + [0]*8).pack('snC*'), Rex::Socket.to_sockaddr("1.2.3.4", 22), "1.2.3.4 addr, port 22 sockaddr")
|
48
57
|
end
|
49
58
|
|
50
59
|
def test_from_sockaddr
|
51
|
-
|
52
|
-
|
60
|
+
# 1.9.1 raises ArgumentError if we don't have an af == AF_INET or AF_INET6
|
61
|
+
af, host, port = Rex::Socket.from_sockaddr(([2, 0] + [0]*12).pack('snC*'))
|
62
|
+
assert_equal(2, af, "af = 2")
|
53
63
|
assert_equal('0.0.0.0', host, "zero host")
|
54
64
|
assert_equal(0, port, "zero port")
|
55
65
|
|
56
|
-
af, host, port = Rex::Socket.from_sockaddr([2].pack('
|
66
|
+
af, host, port = Rex::Socket.from_sockaddr(([2, 22]+[0]*12).pack('snC*'))
|
57
67
|
assert_equal(2, af, "af = 2")
|
58
|
-
assert_equal('0.0.0.0', host, "zero host")
|
59
68
|
assert_equal(22, port, "port = 22")
|
69
|
+
assert_equal('0.0.0.0', host, "zero host")
|
60
70
|
|
61
|
-
af, host, port = Rex::Socket.from_sockaddr([2].pack('
|
71
|
+
af, host, port = Rex::Socket.from_sockaddr(([2, 22, 1, 2, 3, 4] + [0]*8).pack('snC*') )
|
62
72
|
assert_equal(2, af, "af = 2")
|
63
|
-
assert_equal('1.2.3.4', host, "
|
73
|
+
assert_equal('1.2.3.4', host, "host = '1.2.3.4'")
|
64
74
|
assert_equal(22, port, "port = 22")
|
65
75
|
end
|
66
76
|
|
@@ -83,4 +93,15 @@ class Rex::Socket::UnitTest < Test::Unit::TestCase
|
|
83
93
|
assert_equal("255.255.0.0", Rex::Socket.bit2netmask(16))
|
84
94
|
end
|
85
95
|
|
86
|
-
|
96
|
+
def test_is_internal
|
97
|
+
assert( ! Rex::Socket.is_internal?("1.2.3.4"))
|
98
|
+
assert( ! Rex::Socket.is_internal?("172.15.3.4"))
|
99
|
+
assert( ! Rex::Socket.is_internal?("172.32.3.4"))
|
100
|
+
assert(Rex::Socket.is_internal?("10.2.3.4"))
|
101
|
+
assert(Rex::Socket.is_internal?("192.168.3.4"))
|
102
|
+
16.upto(31) do |octet|
|
103
|
+
assert(Rex::Socket.is_internal?("172.#{octet}.3.4"))
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
@@ -5,17 +5,23 @@ $:.unshift(File.join(File.dirname(__FILE__), '..', '..'))
|
|
5
5
|
require 'test/unit'
|
6
6
|
require 'rex/socket/ssl_tcp_server'
|
7
7
|
require 'rex/socket/ssl_tcp'
|
8
|
+
require 'rex/text'
|
8
9
|
|
9
10
|
class Rex::Socket::SslTcpServer::UnitTest < Test::Unit::TestCase
|
10
11
|
|
11
12
|
# XXX. The client data is sent & decrypted just fine. The server data is not. the client thread just spins. BAH.
|
13
|
+
#
|
14
|
+
# As of 2011-03-04, works fine on 1.8.6-p399, 1.8.7-p330, 1.9.1-p378
|
15
|
+
#
|
12
16
|
def test_tcp_server
|
13
|
-
return;
|
17
|
+
#return;
|
14
18
|
|
15
19
|
serv_port = 65433
|
16
20
|
c = nil
|
17
21
|
|
18
22
|
threads = []
|
23
|
+
|
24
|
+
# Server thread
|
19
25
|
threads << Thread.new() {
|
20
26
|
serv = Rex::Socket.create_tcp_server('LocalPort' => serv_port, 'SSL' => true)
|
21
27
|
assert_kind_of(Rex::Socket::SslTcpServer, serv, "type => ssl")
|
@@ -24,12 +30,16 @@ class Rex::Socket::SslTcpServer::UnitTest < Test::Unit::TestCase
|
|
24
30
|
s = serv.accept
|
25
31
|
assert_equal("client_data\n", s.get_once(), "s: get_once")
|
26
32
|
assert_equal(3, s.write("Yo\n"), "s: put Yo")
|
27
|
-
|
28
|
-
|
29
|
-
|
33
|
+
# Make sure methods are Strings for 1.9 compat (which returns
|
34
|
+
# symbols)
|
35
|
+
meths = s.methods.map {|m| m.to_s}
|
36
|
+
assert(meths.include?("<<"), "Has <<")
|
37
|
+
assert(meths.include?(">>"), "Has >>")
|
38
|
+
assert(meths.include?("has_read_data?"), "Has has_read_data?")
|
30
39
|
serv.close
|
31
40
|
}
|
32
41
|
|
42
|
+
# Client thread
|
33
43
|
threads << Thread.new() {
|
34
44
|
sleep(2)
|
35
45
|
assert_nothing_raised {
|
@@ -48,4 +58,4 @@ class Rex::Socket::SslTcpServer::UnitTest < Test::Unit::TestCase
|
|
48
58
|
threads.each { |aThread| aThread.join }
|
49
59
|
end
|
50
60
|
|
51
|
-
end
|
61
|
+
end
|
data/lib/rex/text.rb
CHANGED
@@ -237,7 +237,7 @@ module Text
|
|
237
237
|
#
|
238
238
|
def self.to_hex_ascii(str, prefix = "\\x", count = 1, suffix=nil)
|
239
239
|
raise ::RuntimeError, "unable to chunk into #{count} byte chunks" if ((str.length % count) > 0)
|
240
|
-
return str.unpack('H*')[0].gsub(Regexp.new(".{#{count * 2}}", nil, 'n')) { |s|
|
240
|
+
return str.unpack('H*')[0].gsub(Regexp.new(".{#{count * 2}}", nil, 'n')) { |s|
|
241
241
|
(0x20..0x7e) === s.to_i(16) ? s.to_i(16).chr : prefix + s + suffix.to_s
|
242
242
|
}
|
243
243
|
end
|
@@ -414,6 +414,33 @@ module Text
|
|
414
414
|
end
|
415
415
|
end
|
416
416
|
|
417
|
+
#
|
418
|
+
# Converts a unicode string to standard ASCII text.
|
419
|
+
#
|
420
|
+
def self.to_ascii(str='', type = 'utf-16le', mode = '', size = '')
|
421
|
+
return '' if not str
|
422
|
+
case type
|
423
|
+
when 'utf-16le'
|
424
|
+
return str.unpack('v*').pack('C*')
|
425
|
+
when 'utf-16be'
|
426
|
+
return str.unpack('n*').pack('C*')
|
427
|
+
when 'utf-32le'
|
428
|
+
return str.unpack('V*').pack('C*')
|
429
|
+
when 'utf-32be'
|
430
|
+
return str.unpack('N*').pack('C*')
|
431
|
+
when 'utf-7'
|
432
|
+
raise TypeError, 'invalid utf type, not yet implemented'
|
433
|
+
when 'utf-8'
|
434
|
+
raise TypeError, 'invalid utf type, not yet implemented'
|
435
|
+
when 'uhwtfms' # suggested name from HD :P
|
436
|
+
raise TypeError, 'invalid utf type, not yet implemented'
|
437
|
+
when 'uhwtfms-half' # suggested name from HD :P
|
438
|
+
raise TypeError, 'invalid utf type, not yet implemented'
|
439
|
+
else
|
440
|
+
raise TypeError, 'invalid utf type'
|
441
|
+
end
|
442
|
+
end
|
443
|
+
|
417
444
|
#
|
418
445
|
# Encode a string in a manor useful for HTTP URIs and URI Parameters.
|
419
446
|
#
|
@@ -795,7 +822,7 @@ module Text
|
|
795
822
|
sets.size.times { counter << 0}
|
796
823
|
0.upto(len-1) do |i|
|
797
824
|
setnum = i % sets.size
|
798
|
-
|
825
|
+
|
799
826
|
puts counter.inspect
|
800
827
|
end
|
801
828
|
|
@@ -891,7 +918,8 @@ module Text
|
|
891
918
|
raise RuntimeError, "Invalid gzip compression level" if (level < 1 or level > 9)
|
892
919
|
|
893
920
|
s = ""
|
894
|
-
|
921
|
+
s.force_encoding('ASCII-8BIT') if s.respond_to?(:encoding)
|
922
|
+
gz = Zlib::GzipWriter.new(StringIO.new(s, 'wb'), level)
|
895
923
|
gz << str
|
896
924
|
gz.close
|
897
925
|
return s
|
@@ -904,7 +932,8 @@ module Text
|
|
904
932
|
raise RuntimeError, "Gzip support is not present." if (!zlib_present?)
|
905
933
|
|
906
934
|
s = ""
|
907
|
-
|
935
|
+
s.force_encoding('ASCII-8BIT') if s.respond_to?(:encoding)
|
936
|
+
gz = Zlib::GzipReader.new(StringIO.new(str, 'rb'))
|
908
937
|
s << gz.read
|
909
938
|
gz.close
|
910
939
|
return s
|
@@ -1034,6 +1063,28 @@ module Text
|
|
1034
1063
|
[bits.join].pack("B32").unpack("N")[0]
|
1035
1064
|
end
|
1036
1065
|
|
1066
|
+
#
|
1067
|
+
# Split a string by n charachter into an array
|
1068
|
+
#
|
1069
|
+
def self.split_to_a(str, n)
|
1070
|
+
if n > 0
|
1071
|
+
s = str.dup
|
1072
|
+
until s.empty?
|
1073
|
+
(ret ||= []).push s.slice!(0, n)
|
1074
|
+
end
|
1075
|
+
else
|
1076
|
+
ret = str
|
1077
|
+
end
|
1078
|
+
ret
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
#
|
1082
|
+
#Pack a value as 64 bit litle endian; does not exist for Array.pack
|
1083
|
+
#
|
1084
|
+
def self.pack_int64le(val)
|
1085
|
+
[val & 0x00000000ffffffff, val >> 32].pack("V2")
|
1086
|
+
end
|
1087
|
+
|
1037
1088
|
|
1038
1089
|
protected
|
1039
1090
|
|
data/lib/rex/ui/output.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'rex/ui'
|
2
|
+
require 'pp'
|
2
3
|
|
3
4
|
module Rex
|
4
5
|
module Ui
|
@@ -79,11 +80,75 @@ module DispatcherShell
|
|
79
80
|
shell.update_prompt(prompt)
|
80
81
|
end
|
81
82
|
|
83
|
+
#
|
84
|
+
# Displays the help banner. With no arguments, this is just a list of
|
85
|
+
# all commands grouped by dispatcher. Otherwise, tries to use a method
|
86
|
+
# named cmd_#{+cmd+}_help for the first dispatcher that has a command
|
87
|
+
# named +cmd+.
|
88
|
+
#
|
89
|
+
def cmd_help(cmd=nil, *ignored)
|
90
|
+
if cmd
|
91
|
+
help_found = false
|
92
|
+
cmd_found = false
|
93
|
+
shell.dispatcher_stack.each do |dispatcher|
|
94
|
+
next unless dispatcher.respond_to?(:commands)
|
95
|
+
next if (dispatcher.commands.nil?)
|
96
|
+
next if (dispatcher.commands.length == 0)
|
97
|
+
|
98
|
+
if dispatcher.respond_to?("cmd_#{cmd}")
|
99
|
+
cmd_found = true
|
100
|
+
break unless dispatcher.respond_to? "cmd_#{cmd}_help"
|
101
|
+
dispatcher.send("cmd_#{cmd}_help")
|
102
|
+
help_found = true
|
103
|
+
break
|
104
|
+
end
|
105
|
+
end
|
106
|
+
print_error("No help for #{cmd}, try -h") if cmd_found and not help_found
|
107
|
+
print_error("No such command") if not cmd_found
|
108
|
+
else
|
109
|
+
print(shell.help_to_s)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
#
|
114
|
+
# Tab completion for the help command
|
115
|
+
#
|
116
|
+
# By default just returns a list of all commands in all dispatchers.
|
117
|
+
#
|
118
|
+
def cmd_help_tabs(str, words)
|
119
|
+
return [] if words.length > 1
|
120
|
+
|
121
|
+
tabs = []
|
122
|
+
shell.dispatcher_stack.each { |dispatcher|
|
123
|
+
tabs += dispatcher.commands.keys
|
124
|
+
}
|
125
|
+
return tabs
|
126
|
+
end
|
127
|
+
|
128
|
+
alias cmd_? cmd_help
|
129
|
+
|
130
|
+
|
82
131
|
#
|
83
132
|
# No tab completion items by default
|
84
133
|
#
|
85
134
|
attr_accessor :shell, :tab_complete_items
|
86
135
|
|
136
|
+
#
|
137
|
+
# Provide a generic tab completion for file names.
|
138
|
+
#
|
139
|
+
# If the only completion is a directory, this descends into that directory
|
140
|
+
# and continues completions with filenames contained within.
|
141
|
+
#
|
142
|
+
def tab_complete_filenames(str, words)
|
143
|
+
matches = ::Readline::FILENAME_COMPLETION_PROC.call(str)
|
144
|
+
if matches and matches.length == 1 and File.directory?(matches[0])
|
145
|
+
dir = matches[0]
|
146
|
+
dir += File::SEPARATOR if dir[-1,1] != File::SEPARATOR
|
147
|
+
matches = ::Readline::FILENAME_COMPLETION_PROC.call(dir)
|
148
|
+
end
|
149
|
+
matches
|
150
|
+
end
|
151
|
+
|
87
152
|
end
|
88
153
|
|
89
154
|
#
|
@@ -91,8 +156,6 @@ module DispatcherShell
|
|
91
156
|
#
|
92
157
|
include Shell
|
93
158
|
|
94
|
-
attr_accessor :on_command_proc
|
95
|
-
|
96
159
|
#
|
97
160
|
# Initialize the dispatcher shell.
|
98
161
|
#
|
@@ -148,20 +211,22 @@ module DispatcherShell
|
|
148
211
|
|
149
212
|
# If no command is set and it supports commands, add them all
|
150
213
|
if (tab_words.empty? and dispatcher.respond_to?('commands'))
|
151
|
-
items.concat(dispatcher.commands.
|
214
|
+
items.concat(dispatcher.commands.keys)
|
152
215
|
end
|
153
216
|
|
154
217
|
# If the dispatcher exports a tab completion function, use it
|
155
218
|
if(dispatcher.respond_to?('tab_complete_helper'))
|
156
219
|
res = dispatcher.tab_complete_helper(str, tab_words)
|
220
|
+
else
|
221
|
+
res = tab_complete_helper(dispatcher, str, tab_words)
|
222
|
+
end
|
157
223
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
end
|
224
|
+
if (res.nil?)
|
225
|
+
# A nil response indicates no optional arguments
|
226
|
+
return [''] if items.empty?
|
227
|
+
else
|
228
|
+
# Otherwise we add the completion items to the list
|
229
|
+
items.concat(res)
|
165
230
|
end
|
166
231
|
}
|
167
232
|
|
@@ -184,6 +249,26 @@ module DispatcherShell
|
|
184
249
|
}
|
185
250
|
end
|
186
251
|
|
252
|
+
#
|
253
|
+
# Provide command-specific tab completion
|
254
|
+
#
|
255
|
+
def tab_complete_helper(dispatcher, str, words)
|
256
|
+
items = []
|
257
|
+
|
258
|
+
tabs_meth = "cmd_#{words[0]}_tabs"
|
259
|
+
# Is the user trying to tab complete one of our commands?
|
260
|
+
if (dispatcher.commands.include?(words[0]) and dispatcher.respond_to?(tabs_meth))
|
261
|
+
res = dispatcher.send(tabs_meth, str, words)
|
262
|
+
return [] if res.nil?
|
263
|
+
items.concat(res)
|
264
|
+
else
|
265
|
+
# Avoid the default completion list for known commands
|
266
|
+
return []
|
267
|
+
end
|
268
|
+
|
269
|
+
return items
|
270
|
+
end
|
271
|
+
|
187
272
|
#
|
188
273
|
# Run a single command line.
|
189
274
|
#
|
data/lib/rex/ui/text/shell.rb
CHANGED
@@ -204,6 +204,7 @@ module Shell
|
|
204
204
|
def print_error(msg='')
|
205
205
|
return if (output.nil?)
|
206
206
|
|
207
|
+
self.on_print_proc.call(msg) if self.on_print_proc
|
207
208
|
# Errors are not subject to disabled output
|
208
209
|
log_output(output.print_error(msg))
|
209
210
|
end
|
@@ -214,6 +215,7 @@ module Shell
|
|
214
215
|
def print_status(msg='')
|
215
216
|
return if (disable_output == true)
|
216
217
|
|
218
|
+
self.on_print_proc.call(msg) if self.on_print_proc
|
217
219
|
log_output(output.print_status(msg))
|
218
220
|
end
|
219
221
|
|
@@ -223,6 +225,7 @@ module Shell
|
|
223
225
|
def print_good(msg='')
|
224
226
|
return if (disable_output == true)
|
225
227
|
|
228
|
+
self.on_print_proc.call(msg) if self.on_print_proc
|
226
229
|
log_output(output.print_good(msg))
|
227
230
|
end
|
228
231
|
|
@@ -232,6 +235,7 @@ module Shell
|
|
232
235
|
def print_line(msg='')
|
233
236
|
return if (disable_output == true)
|
234
237
|
|
238
|
+
self.on_print_proc.call(msg) if self.on_print_proc
|
235
239
|
log_output(output.print_line(msg))
|
236
240
|
end
|
237
241
|
|
@@ -240,6 +244,7 @@ module Shell
|
|
240
244
|
#
|
241
245
|
def print(msg='')
|
242
246
|
return if (disable_output == true)
|
247
|
+
self.on_print_proc.call(msg) if self.on_print_proc
|
243
248
|
log_output(output.print(msg))
|
244
249
|
end
|
245
250
|
|
@@ -256,6 +261,9 @@ module Shell
|
|
256
261
|
#
|
257
262
|
attr_reader :output
|
258
263
|
|
264
|
+
attr_accessor :on_command_proc
|
265
|
+
attr_accessor :on_print_proc
|
266
|
+
|
259
267
|
protected
|
260
268
|
|
261
269
|
#
|