rex 2.0.8 → 2.0.9
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.
- checksums.yaml +4 -4
- data/lib/rex.rb +1 -0
- data/lib/rex/arch.rb +5 -0
- data/lib/rex/arch/x86.rb +19 -5
- data/lib/rex/arch/zarch.rb +17 -0
- data/lib/rex/compat.rb +5 -4
- data/lib/rex/constants.rb +3 -1
- data/lib/rex/encoder/alpha2/alpha_mixed.rb +70 -9
- data/lib/rex/encoder/alpha2/alpha_upper.rb +67 -8
- data/lib/rex/exploitation/cmdstager.rb +1 -0
- data/lib/rex/exploitation/cmdstager/certutil.rb +115 -0
- data/lib/rex/exploitation/cmdstager/echo.rb +6 -3
- data/lib/rex/exploitation/egghunter.rb +1 -1
- data/lib/rex/google/geolocation.rb +68 -0
- data/lib/rex/io/bidirectional_pipe.rb +0 -4
- data/lib/rex/java/serialization.rb +2 -0
- data/lib/rex/java/serialization/decode_error.rb +11 -0
- data/lib/rex/java/serialization/encode_error.rb +11 -0
- data/lib/rex/java/serialization/model.rb +2 -0
- data/lib/rex/java/serialization/model/annotation.rb +3 -3
- data/lib/rex/java/serialization/model/block_data.rb +3 -3
- data/lib/rex/java/serialization/model/block_data_long.rb +3 -3
- data/lib/rex/java/serialization/model/class_desc.rb +6 -6
- data/lib/rex/java/serialization/model/contents.rb +17 -10
- data/lib/rex/java/serialization/model/field.rb +12 -11
- data/lib/rex/java/serialization/model/long_utf.rb +3 -3
- data/lib/rex/java/serialization/model/new_array.rb +22 -23
- data/lib/rex/java/serialization/model/new_class.rb +57 -0
- data/lib/rex/java/serialization/model/new_class_desc.rb +15 -16
- data/lib/rex/java/serialization/model/new_enum.rb +5 -5
- data/lib/rex/java/serialization/model/new_object.rb +22 -17
- data/lib/rex/java/serialization/model/proxy_class_desc.rb +109 -0
- data/lib/rex/java/serialization/model/reference.rb +4 -4
- data/lib/rex/java/serialization/model/stream.rb +7 -7
- data/lib/rex/java/serialization/model/utf.rb +3 -3
- data/lib/rex/json_hash_file.rb +94 -0
- data/lib/rex/logging/log_sink.rb +1 -0
- data/lib/rex/logging/sinks/timestamp_flatfile.rb +21 -0
- data/lib/rex/parser/appscan_nokogiri.rb +13 -23
- data/lib/rex/parser/fs/ntfs.rb +10 -5
- data/lib/rex/parser/nmap_nokogiri.rb +3 -1
- data/lib/rex/parser/openvas_nokogiri.rb +70 -73
- data/lib/rex/parser/winscp.rb +108 -0
- data/lib/rex/parser/x509_certificate.rb +92 -0
- data/lib/rex/payloads.rb +0 -1
- data/lib/rex/payloads/meterpreter/config.rb +154 -0
- data/lib/rex/payloads/meterpreter/uri_checksum.rb +136 -0
- data/lib/rex/post/meterpreter.rb +1 -1
- data/lib/rex/post/meterpreter/client.rb +26 -3
- data/lib/rex/post/meterpreter/client_core.rb +387 -75
- data/lib/rex/post/meterpreter/extensions/android/android.rb +127 -37
- data/lib/rex/post/meterpreter/extensions/android/tlv.rb +46 -25
- data/lib/rex/post/meterpreter/extensions/extapi/extapi.rb +4 -0
- data/lib/rex/post/meterpreter/extensions/extapi/ntds/ntds.rb +39 -0
- data/lib/rex/post/meterpreter/extensions/extapi/pageant/pageant.rb +44 -0
- data/lib/rex/post/meterpreter/extensions/extapi/tlv.rb +9 -0
- data/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb +16 -1
- data/lib/rex/post/meterpreter/extensions/priv/priv.rb +1 -1
- data/lib/rex/post/meterpreter/extensions/python/python.rb +114 -0
- data/lib/rex/post/meterpreter/extensions/python/tlv.rb +21 -0
- data/lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb +17 -14
- data/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb +33 -12
- data/lib/rex/post/meterpreter/extensions/stdapi/fs/mount.rb +57 -0
- data/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb +3 -3
- data/lib/rex/post/meterpreter/extensions/stdapi/stdapi.rb +3 -1
- data/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb +2 -0
- data/lib/rex/post/meterpreter/extensions/stdapi/sys/process.rb +16 -3
- data/lib/rex/post/meterpreter/extensions/stdapi/sys/registry.rb +29 -6
- data/lib/rex/post/meterpreter/extensions/stdapi/sys/registry_subsystem/registry_key.rb +5 -1
- data/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb +18 -6
- data/lib/rex/post/meterpreter/extensions/stdapi/ui.rb +2 -2
- data/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb +34 -36
- data/lib/rex/post/meterpreter/packet.rb +29 -0
- data/lib/rex/post/meterpreter/packet_dispatcher.rb +20 -7
- data/lib/rex/post/meterpreter/ui/console.rb +1 -0
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb +230 -72
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +544 -34
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb +188 -57
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/kiwi.rb +115 -93
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/lanattacks/dhcp.rb +1 -1
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/mimikatz.rb +1 -1
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/priv/elevate.rb +49 -15
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/priv/timestomp.rb +11 -2
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/python.rb +187 -0
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +324 -133
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/net.rb +52 -2
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +68 -65
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb +9 -1
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb +113 -118
- data/lib/rex/post/meterpreter/ui/console/interactive_channel.rb +3 -0
- data/lib/rex/powershell.rb +62 -0
- data/lib/rex/powershell/command.rb +359 -0
- data/lib/rex/{exploitation/powershell → powershell}/function.rb +0 -2
- data/lib/rex/{exploitation/powershell → powershell}/obfu.rb +0 -2
- data/lib/rex/{exploitation/powershell → powershell}/output.rb +11 -5
- data/lib/rex/{exploitation/powershell → powershell}/param.rb +0 -2
- data/lib/rex/powershell/parser.rb +182 -0
- data/lib/rex/powershell/payload.rb +78 -0
- data/lib/rex/{exploitation/powershell → powershell}/psh_methods.rb +16 -2
- data/lib/rex/{exploitation/powershell → powershell}/script.rb +2 -4
- data/lib/rex/proto/dcerpc/client.rb +6 -6
- data/lib/rex/proto/dcerpc/exceptions.rb +26 -0
- data/lib/rex/proto/http/client.rb +3 -3
- data/lib/rex/proto/http/client_request.rb +0 -5
- data/lib/rex/proto/http/response.rb +86 -0
- data/lib/rex/proto/ipmi/utils.rb +30 -26
- data/lib/rex/proto/kerberos/client.rb +1 -1
- data/lib/rex/proto/kerberos/model/kdc_request.rb +2 -2
- data/lib/rex/proto/rfb/client.rb +8 -3
- data/lib/rex/proto/rfb/constants.rb +1 -1
- data/lib/rex/proto/rmi.rb +2 -0
- data/lib/rex/proto/rmi/decode_error.rb +10 -0
- data/lib/rex/proto/rmi/exception.rb +10 -0
- data/lib/rex/proto/rmi/model.rb +5 -0
- data/lib/rex/proto/rmi/model/call.rb +4 -4
- data/lib/rex/proto/rmi/model/call_data.rb +137 -0
- data/lib/rex/proto/rmi/model/dgc_ack.rb +2 -2
- data/lib/rex/proto/rmi/model/element.rb +26 -11
- data/lib/rex/proto/rmi/model/output_header.rb +4 -4
- data/lib/rex/proto/rmi/model/ping.rb +2 -2
- data/lib/rex/proto/rmi/model/ping_ack.rb +2 -2
- data/lib/rex/proto/rmi/model/protocol_ack.rb +2 -2
- data/lib/rex/proto/rmi/model/return_data.rb +5 -5
- data/lib/rex/proto/rmi/model/return_value.rb +124 -0
- data/lib/rex/proto/rmi/model/unique_identifier.rb +77 -0
- data/lib/rex/proto/steam.rb +3 -0
- data/lib/rex/proto/steam/message.rb +125 -0
- data/lib/rex/proto/tftp/client.rb +35 -14
- data/lib/rex/random_identifier_generator.rb +2 -0
- data/lib/rex/ropbuilder.rb +1 -1
- data/lib/rex/socket/parameters.rb +9 -0
- data/lib/rex/socket/ssl_tcp.rb +25 -41
- data/lib/rex/socket/ssl_tcp_server.rb +10 -21
- data/lib/rex/sslscan/result.rb +20 -1
- data/lib/rex/text.rb +241 -55
- data/lib/rex/ui/output.rb +0 -3
- data/lib/rex/ui/subscriber.rb +0 -10
- data/lib/rex/ui/text/color.rb +9 -0
- data/lib/rex/ui/text/dispatcher_shell.rb +1 -0
- data/lib/rex/ui/text/output.rb +15 -4
- data/lib/rex/ui/text/output/file.rb +1 -0
- data/lib/rex/ui/text/output/stdio.rb +0 -16
- data/lib/rex/ui/text/shell.rb +3 -0
- data/lib/rex/ui/text/table.rb +85 -19
- data/lib/rex/user_agent.rb +118 -0
- data/rex.gemspec +2 -2
- metadata +41 -14
- data/lib/rex/exploitation/powershell.rb +0 -62
- data/lib/rex/exploitation/powershell/parser.rb +0 -183
- data/lib/rex/payloads/meterpreter.rb +0 -2
- data/lib/rex/payloads/meterpreter/patch.rb +0 -136
|
@@ -177,7 +177,7 @@ class Console::CommandDispatcher::Lanattacks::Dhcp
|
|
|
177
177
|
|
|
178
178
|
def print_dhcp_load_options_usage
|
|
179
179
|
print("dhcp_load_options <datastore> [-h]\n\n" +
|
|
180
|
-
"Load settings from a
|
|
180
|
+
"Load settings from a datastore to the active DHCP server.\n\n" +
|
|
181
181
|
"The datastore must be a hash of name/value pairs.\n" +
|
|
182
182
|
"Valid names are:\n" +
|
|
183
183
|
@@dhcp_set_option_valid_options.map {|o| " - #{o}\n" }.join('') +
|
|
@@ -37,7 +37,7 @@ class Console::CommandDispatcher::Mimikatz
|
|
|
37
37
|
#
|
|
38
38
|
def commands
|
|
39
39
|
{
|
|
40
|
-
"mimikatz_command" => "Run a custom
|
|
40
|
+
"mimikatz_command" => "Run a custom command",
|
|
41
41
|
"wdigest" => "Attempt to retrieve wdigest creds",
|
|
42
42
|
"msv" => "Attempt to retrieve msv creds (hashes)",
|
|
43
43
|
"livessp" => "Attempt to retrieve livessp creds",
|
|
@@ -17,17 +17,20 @@ class Console::CommandDispatcher::Priv::Elevate
|
|
|
17
17
|
|
|
18
18
|
include Console::CommandDispatcher
|
|
19
19
|
|
|
20
|
-
ELEVATE_TECHNIQUE_NONE
|
|
21
|
-
ELEVATE_TECHNIQUE_ANY
|
|
22
|
-
ELEVATE_TECHNIQUE_SERVICE_NAMEDPIPE
|
|
23
|
-
ELEVATE_TECHNIQUE_SERVICE_NAMEDPIPE2
|
|
24
|
-
ELEVATE_TECHNIQUE_SERVICE_TOKENDUP
|
|
25
|
-
|
|
26
|
-
ELEVATE_TECHNIQUE_DESCRIPTION =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
ELEVATE_TECHNIQUE_NONE = -1
|
|
21
|
+
ELEVATE_TECHNIQUE_ANY = 0
|
|
22
|
+
ELEVATE_TECHNIQUE_SERVICE_NAMEDPIPE = 1
|
|
23
|
+
ELEVATE_TECHNIQUE_SERVICE_NAMEDPIPE2 = 2
|
|
24
|
+
ELEVATE_TECHNIQUE_SERVICE_TOKENDUP = 3
|
|
25
|
+
|
|
26
|
+
ELEVATE_TECHNIQUE_DESCRIPTION =
|
|
27
|
+
[
|
|
28
|
+
"All techniques available",
|
|
29
|
+
"Named Pipe Impersonation (In Memory/Admin)",
|
|
30
|
+
"Named Pipe Impersonation (Dropper/Admin)",
|
|
31
|
+
"Token Duplication (In Memory/Admin)"
|
|
32
|
+
]
|
|
33
|
+
|
|
31
34
|
#
|
|
32
35
|
# List of supported commands.
|
|
33
36
|
#
|
|
@@ -45,6 +48,25 @@ class Console::CommandDispatcher::Priv::Elevate
|
|
|
45
48
|
end
|
|
46
49
|
|
|
47
50
|
|
|
51
|
+
#
|
|
52
|
+
# Returns the description of the technique(s)
|
|
53
|
+
#
|
|
54
|
+
def translate_technique_index(index)
|
|
55
|
+
translation = ''
|
|
56
|
+
|
|
57
|
+
case index
|
|
58
|
+
when 0
|
|
59
|
+
desc = ELEVATE_TECHNIQUE_DESCRIPTION.dup
|
|
60
|
+
desc.shift
|
|
61
|
+
translation = desc
|
|
62
|
+
else
|
|
63
|
+
translation = [ ELEVATE_TECHNIQUE_DESCRIPTION[index] ]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
translation
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
|
|
48
70
|
#
|
|
49
71
|
# Attempt to elevate the meterpreter to that of local system.
|
|
50
72
|
#
|
|
@@ -73,17 +95,29 @@ class Console::CommandDispatcher::Priv::Elevate
|
|
|
73
95
|
}
|
|
74
96
|
|
|
75
97
|
if( technique < 0 or technique >= ELEVATE_TECHNIQUE_DESCRIPTION.length )
|
|
76
|
-
print_error( "Technique '#{technique}' is out of range." )
|
|
98
|
+
print_error( "Technique '#{technique}' is out of range." )
|
|
77
99
|
return false;
|
|
78
100
|
end
|
|
79
101
|
|
|
80
|
-
|
|
102
|
+
begin
|
|
103
|
+
result = client.priv.getsystem( technique )
|
|
104
|
+
rescue Rex::Post::Meterpreter::RequestError => e
|
|
105
|
+
print_error("#{e.message} The following was attempted:")
|
|
106
|
+
translate_technique_index(technique).each do |desc|
|
|
107
|
+
print_error(desc)
|
|
108
|
+
end
|
|
109
|
+
elog("#{e.class} #{e.message} (Technique: #{technique})\n#{e.backtrace * "\n"}")
|
|
110
|
+
return
|
|
111
|
+
end
|
|
81
112
|
|
|
82
113
|
# got system?
|
|
83
114
|
if result[0]
|
|
84
|
-
print_line( "...got system
|
|
115
|
+
print_line( "...got system via technique #{result[1]} (#{translate_technique_index(result[1]).first})." )
|
|
85
116
|
else
|
|
86
|
-
print_line( "...failed to get system
|
|
117
|
+
print_line( "...failed to get system while attempting the following:" )
|
|
118
|
+
translate_technique_index(technique).each do |desc|
|
|
119
|
+
print_error(desc)
|
|
120
|
+
end
|
|
87
121
|
end
|
|
88
122
|
|
|
89
123
|
return result
|
|
@@ -52,12 +52,21 @@ class Console::CommandDispatcher::Priv::Timestomp
|
|
|
52
52
|
#
|
|
53
53
|
def cmd_timestomp(*args)
|
|
54
54
|
if (args.length < 2)
|
|
55
|
-
print_line("\nUsage: timestomp file_path
|
|
55
|
+
print_line("\nUsage: timestomp OPTIONS file_path\n" +
|
|
56
56
|
@@timestomp_opts.usage)
|
|
57
57
|
return
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
file_path =
|
|
60
|
+
file_path = nil
|
|
61
|
+
args.each { |a| file_path = a unless a[0] == "-" }
|
|
62
|
+
|
|
63
|
+
if file_path.nil?
|
|
64
|
+
print_line("\nNo file_path specified.")
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
args.delete(file_path)
|
|
69
|
+
|
|
61
70
|
modified = nil
|
|
62
71
|
accessed = nil
|
|
63
72
|
creation = nil
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# -*- coding: binary -*-
|
|
2
|
+
require 'rex/post/meterpreter'
|
|
3
|
+
|
|
4
|
+
module Rex
|
|
5
|
+
module Post
|
|
6
|
+
module Meterpreter
|
|
7
|
+
module Ui
|
|
8
|
+
|
|
9
|
+
###
|
|
10
|
+
#
|
|
11
|
+
# Python extension - interact with a python interpreter
|
|
12
|
+
#
|
|
13
|
+
###
|
|
14
|
+
class Console::CommandDispatcher::Python
|
|
15
|
+
|
|
16
|
+
Klass = Console::CommandDispatcher::Python
|
|
17
|
+
|
|
18
|
+
include Console::CommandDispatcher
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# Name for this dispatcher
|
|
22
|
+
#
|
|
23
|
+
def name
|
|
24
|
+
'Python'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
#
|
|
28
|
+
# List of supported commands.
|
|
29
|
+
#
|
|
30
|
+
def commands
|
|
31
|
+
{
|
|
32
|
+
'python_reset' => 'Resets/restarts the Python interpreter',
|
|
33
|
+
'python_execute' => 'Execute a python command string',
|
|
34
|
+
'python_import' => 'Import/run a python file or module'
|
|
35
|
+
}
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def cmd_python_reset(*args)
|
|
39
|
+
client.python.reset
|
|
40
|
+
print_good('Python interpreter successfully reset')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@@python_import_opts = Rex::Parser::Arguments.new(
|
|
44
|
+
'-h' => [false, 'Help banner'],
|
|
45
|
+
'-f' => [true, 'Path to the file (.py, .pyc), or module directory to import'],
|
|
46
|
+
'-n' => [true, 'Name of the module (optional, for single files only)'],
|
|
47
|
+
'-r' => [true, 'Name of the variable containing the result (optional, single files only)']
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def python_import_usage
|
|
51
|
+
print_line('Usage: python_import <-f file path> [-n mod name] [-r result var name]')
|
|
52
|
+
print_line
|
|
53
|
+
print_line('Loads a python code file or module from disk into memory on the target.')
|
|
54
|
+
print_line('The module loader requires a path to a folder that contains the module,')
|
|
55
|
+
print_line('and the folder name will be used as the module name. Only .py files will')
|
|
56
|
+
print_line('work with modules.')
|
|
57
|
+
print_line(@@python_import_opts.usage)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
#
|
|
61
|
+
# Import/run a python file
|
|
62
|
+
#
|
|
63
|
+
def cmd_python_import(*args)
|
|
64
|
+
if args.length == 0 || args.include?('-h')
|
|
65
|
+
python_import_usage
|
|
66
|
+
return false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
result_var = nil
|
|
70
|
+
source = nil
|
|
71
|
+
mod_name = nil
|
|
72
|
+
|
|
73
|
+
@@python_import_opts.parse(args) { |opt, idx, val|
|
|
74
|
+
case opt
|
|
75
|
+
when '-f'
|
|
76
|
+
source = val
|
|
77
|
+
when '-n'
|
|
78
|
+
mod_name = val
|
|
79
|
+
when '-r'
|
|
80
|
+
result_var = val
|
|
81
|
+
end
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
unless source
|
|
85
|
+
print_error("The -f parameter must be specified")
|
|
86
|
+
return false
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
if ::File.directory?(source)
|
|
90
|
+
files = ::Find.find(source).select { |p| /.*\.py$/ =~ p }
|
|
91
|
+
if files.length == 0
|
|
92
|
+
fail_with("No .py files found in #{source}")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
base_name = ::File.basename(source)
|
|
96
|
+
unless source.end_with?('/')
|
|
97
|
+
source << '/'
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
print_status("Importing #{source} with base module name #{base_name} ...")
|
|
101
|
+
|
|
102
|
+
files.each do |file|
|
|
103
|
+
rel_path = file[source.length, file.length - source.length]
|
|
104
|
+
parts = rel_path.split('/')
|
|
105
|
+
|
|
106
|
+
mod_parts = [base_name] + parts[0, parts.length - 1]
|
|
107
|
+
|
|
108
|
+
if parts[-1] != '__init__.py'
|
|
109
|
+
mod_parts << ::File.basename(parts[-1], '.*')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
mod_name = mod_parts.join('.')
|
|
113
|
+
print_status("Importing #{file} as #{mod_name} ...")
|
|
114
|
+
result = client.python.import(file, mod_name, nil)
|
|
115
|
+
handle_exec_result(result, nil)
|
|
116
|
+
end
|
|
117
|
+
else
|
|
118
|
+
print_status("Importing #{source} ...")
|
|
119
|
+
result = client.python.import(source, mod_name, result_var)
|
|
120
|
+
handle_exec_result(result, result_var)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
@@python_execute_opts = Rex::Parser::Arguments.new(
|
|
126
|
+
'-h' => [false, 'Help banner'],
|
|
127
|
+
'-r' => [true, 'Name of the variable containing the result (optional)']
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def python_execute_usage
|
|
131
|
+
print_line('Usage: python_execute <python code> [-r result var name]')
|
|
132
|
+
print_line
|
|
133
|
+
print_line('Runs the given python string on the target. If a result is required,')
|
|
134
|
+
print_line('it should be stored in a python variable, and that variable should')
|
|
135
|
+
print_line('passed using the -r parameter.')
|
|
136
|
+
print_line(@@python_execute_opts.usage)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
#
|
|
140
|
+
# Execute a simple python command string
|
|
141
|
+
#
|
|
142
|
+
def cmd_python_execute(*args)
|
|
143
|
+
if args.length == 0 || args.include?('-h')
|
|
144
|
+
python_execute_usage
|
|
145
|
+
return false
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
code = args.shift
|
|
149
|
+
result_var = nil
|
|
150
|
+
|
|
151
|
+
@@python_execute_opts.parse(args) { |opt, idx, val|
|
|
152
|
+
case opt
|
|
153
|
+
when '-r'
|
|
154
|
+
result_var = val
|
|
155
|
+
end
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
result = client.python.execute_string(code, result_var)
|
|
159
|
+
|
|
160
|
+
handle_exec_result(result, result_var)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def handle_exec_result(result, result_var)
|
|
166
|
+
if result[:result]
|
|
167
|
+
print_good("#{result_var} = #{result[:result]}")
|
|
168
|
+
elsif result[:stdout].length == 0 and result[:stderr].length == 0
|
|
169
|
+
print_good("Command executed without returning a result")
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
if result[:stdout].length > 0
|
|
173
|
+
print_good("Content written to stdout:\n#{result[:stdout]}")
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
if result[:stderr].length > 0
|
|
177
|
+
print_error("Content written to stderr:\n#{result[:stderr]}")
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# -*- coding: binary -*-
|
|
2
2
|
require 'tempfile'
|
|
3
|
+
require 'filesize'
|
|
3
4
|
require 'rex/post/meterpreter'
|
|
4
5
|
|
|
5
6
|
module Rex
|
|
@@ -30,49 +31,63 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
|
30
31
|
@@upload_opts = Rex::Parser::Arguments.new(
|
|
31
32
|
"-h" => [ false, "Help banner." ],
|
|
32
33
|
"-r" => [ false, "Upload recursively." ])
|
|
34
|
+
#
|
|
35
|
+
# Options for the ls command
|
|
36
|
+
#
|
|
37
|
+
@@ls_opts = Rex::Parser::Arguments.new(
|
|
38
|
+
"-h" => [ false, "Help banner." ],
|
|
39
|
+
"-S" => [ true, "Search string." ],
|
|
40
|
+
"-t" => [ false, "Sort by time" ],
|
|
41
|
+
"-s" => [ false, "Sort by size" ],
|
|
42
|
+
"-r" => [ false, "Reverse sort order" ],
|
|
43
|
+
"-x" => [ false, "Show short file names" ],
|
|
44
|
+
"-l" => [ false, "List in long format (default)" ],
|
|
45
|
+
"-R" => [ false, "Recursively list subdirectories encountered" ])
|
|
33
46
|
|
|
34
47
|
#
|
|
35
48
|
# List of supported commands.
|
|
36
49
|
#
|
|
37
50
|
def commands
|
|
38
51
|
all = {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
52
|
+
'cat' => 'Read the contents of a file to the screen',
|
|
53
|
+
'cd' => 'Change directory',
|
|
54
|
+
'del' => 'Delete the specified file',
|
|
55
|
+
'download' => 'Download a file or directory',
|
|
56
|
+
'edit' => 'Edit a file',
|
|
57
|
+
'getlwd' => 'Print local working directory',
|
|
58
|
+
'getwd' => 'Print working directory',
|
|
59
|
+
'lcd' => 'Change local working directory',
|
|
60
|
+
'lpwd' => 'Print local working directory',
|
|
61
|
+
'ls' => 'List files',
|
|
62
|
+
'mkdir' => 'Make directory',
|
|
63
|
+
'pwd' => 'Print working directory',
|
|
64
|
+
'rm' => 'Delete the specified file',
|
|
65
|
+
'mv' => 'Move source to destination',
|
|
66
|
+
'rmdir' => 'Remove directory',
|
|
67
|
+
'search' => 'Search for files',
|
|
68
|
+
'upload' => 'Upload a file or directory',
|
|
69
|
+
'show_mount' => 'List all mount points/logical drives',
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
reqs = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
'cat' => [],
|
|
74
|
+
'cd' => ['stdapi_fs_chdir'],
|
|
75
|
+
'del' => ['stdapi_fs_rm'],
|
|
76
|
+
'download' => [],
|
|
77
|
+
'edit' => [],
|
|
78
|
+
'getlwd' => [],
|
|
79
|
+
'getwd' => ['stdapi_fs_getwd'],
|
|
80
|
+
'lcd' => [],
|
|
81
|
+
'lpwd' => [],
|
|
82
|
+
'ls' => ['stdapi_fs_stat', 'stdapi_fs_ls'],
|
|
83
|
+
'mkdir' => ['stdapi_fs_mkdir'],
|
|
84
|
+
'pwd' => ['stdapi_fs_getwd'],
|
|
85
|
+
'rmdir' => ['stdapi_fs_delete_dir'],
|
|
86
|
+
'rm' => ['stdapi_fs_delete_file'],
|
|
87
|
+
'mv' => ['stdapi_fs_file_move'],
|
|
88
|
+
'search' => ['stdapi_fs_search'],
|
|
89
|
+
'upload' => [],
|
|
90
|
+
'show_mount' => ['stdapi_fs_mount_show'],
|
|
76
91
|
}
|
|
77
92
|
|
|
78
93
|
all.delete_if do |cmd, desc|
|
|
@@ -99,57 +114,101 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
|
99
114
|
#
|
|
100
115
|
# Search for files.
|
|
101
116
|
#
|
|
102
|
-
def cmd_search(
|
|
117
|
+
def cmd_search(*args)
|
|
103
118
|
|
|
104
119
|
root = nil
|
|
105
|
-
glob = nil
|
|
106
120
|
recurse = true
|
|
121
|
+
globs = []
|
|
122
|
+
files = []
|
|
107
123
|
|
|
108
124
|
opts = Rex::Parser::Arguments.new(
|
|
109
125
|
"-h" => [ false, "Help Banner." ],
|
|
110
126
|
"-d" => [ true, "The directory/drive to begin searching from. Leave empty to search all drives. (Default: #{root})" ],
|
|
111
|
-
"-f" => [ true, "
|
|
127
|
+
"-f" => [ true, "A file pattern glob to search for. (e.g. *secret*.doc?)" ],
|
|
112
128
|
"-r" => [ true, "Recursivly search sub directories. (Default: #{recurse})" ]
|
|
113
129
|
)
|
|
114
130
|
|
|
115
131
|
opts.parse(args) { | opt, idx, val |
|
|
116
132
|
case opt
|
|
117
133
|
when "-h"
|
|
118
|
-
print_line(
|
|
119
|
-
print_line(
|
|
120
|
-
print_line(
|
|
134
|
+
print_line("Usage: search [-d dir] [-r recurse] -f pattern [-f pattern]...")
|
|
135
|
+
print_line("Search for files.")
|
|
136
|
+
print_line(opts.usage)
|
|
121
137
|
return
|
|
122
138
|
when "-d"
|
|
123
139
|
root = val
|
|
124
140
|
when "-f"
|
|
125
|
-
|
|
141
|
+
globs << val
|
|
126
142
|
when "-r"
|
|
127
|
-
recurse = false if
|
|
143
|
+
recurse = false if val =~ /^(f|n|0)/i
|
|
128
144
|
end
|
|
129
145
|
}
|
|
130
146
|
|
|
131
|
-
if
|
|
132
|
-
print_error(
|
|
147
|
+
if globs.empty?
|
|
148
|
+
print_error("You must specify a valid file glob to search for, e.g. >search -f *.doc")
|
|
133
149
|
return
|
|
134
150
|
end
|
|
135
151
|
|
|
136
|
-
|
|
152
|
+
globs.uniq.each do |glob|
|
|
153
|
+
files += client.fs.file.search(root, glob, recurse)
|
|
154
|
+
end
|
|
137
155
|
|
|
138
|
-
if
|
|
139
|
-
print_line(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
156
|
+
if files.empty?
|
|
157
|
+
print_line("No files matching your search were found.")
|
|
158
|
+
return
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
print_line("Found #{files.length} result#{ files.length > 1 ? 's' : '' }...")
|
|
162
|
+
files.each do | file |
|
|
163
|
+
if file['size'] > 0
|
|
164
|
+
print(" #{file['path']}#{ file['path'].empty? ? '' : '\\' }#{file['name']} (#{file['size']} bytes)\n")
|
|
165
|
+
else
|
|
166
|
+
print(" #{file['path']}#{ file['path'].empty? ? '' : '\\' }#{file['name']}\n")
|
|
146
167
|
end
|
|
147
|
-
else
|
|
148
|
-
print_line( "No files matching your search were found." )
|
|
149
168
|
end
|
|
150
169
|
|
|
151
170
|
end
|
|
152
171
|
|
|
172
|
+
#
|
|
173
|
+
# Show all the mount points/logical drives (currently geared towards
|
|
174
|
+
# the Windows Meterpreter).
|
|
175
|
+
#
|
|
176
|
+
def cmd_show_mount(*args)
|
|
177
|
+
if args.include?('-h')
|
|
178
|
+
print_line('Usage: show_mount')
|
|
179
|
+
return true
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
mounts = client.fs.mount.show_mount
|
|
183
|
+
|
|
184
|
+
table = Rex::Ui::Text::Table.new(
|
|
185
|
+
'Header' => 'Mounts / Drives',
|
|
186
|
+
'Indent' => 0,
|
|
187
|
+
'SortIndex' => 0,
|
|
188
|
+
'Columns' => [
|
|
189
|
+
'Name', 'Type', 'Size (Total)', 'Size (Free)', 'Mapped to'
|
|
190
|
+
]
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
mounts.each do |d|
|
|
194
|
+
ts = ::Filesize.from("#{d[:total_space]} B").pretty.split(' ')
|
|
195
|
+
fs = ::Filesize.from("#{d[:free_space]} B").pretty.split(' ')
|
|
196
|
+
table << [
|
|
197
|
+
d[:name],
|
|
198
|
+
d[:type],
|
|
199
|
+
"#{ts[0].rjust(6)} #{ts[1].ljust(3)}",
|
|
200
|
+
"#{fs[0].rjust(6)} #{fs[1].ljust(3)}",
|
|
201
|
+
d[:unc]
|
|
202
|
+
]
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
print_line
|
|
206
|
+
print_line(table.to_s)
|
|
207
|
+
print_line
|
|
208
|
+
print_line("Total mounts/drives: #{mounts.length}")
|
|
209
|
+
print_line
|
|
210
|
+
end
|
|
211
|
+
|
|
153
212
|
#
|
|
154
213
|
# Reads the contents of a file and prints them to the screen.
|
|
155
214
|
#
|
|
@@ -223,29 +282,41 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
|
223
282
|
|
|
224
283
|
alias :cmd_del :cmd_rm
|
|
225
284
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
285
|
+
#
|
|
286
|
+
# Move source to destination
|
|
287
|
+
#
|
|
288
|
+
def cmd_mv(*args)
|
|
289
|
+
if (args.length < 2)
|
|
290
|
+
print_line("Usage: mv oldfile newfile")
|
|
291
|
+
return true
|
|
292
|
+
end
|
|
293
|
+
client.fs.file.mv(args[0],args[1])
|
|
294
|
+
return true
|
|
295
|
+
end
|
|
234
296
|
|
|
235
|
-
|
|
297
|
+
alias :cmd_move :cmd_mv
|
|
298
|
+
alias :cmd_rename :cmd_mv
|
|
236
299
|
|
|
237
|
-
|
|
238
|
-
|
|
300
|
+
#
|
|
301
|
+
# Move source to destination
|
|
302
|
+
#
|
|
303
|
+
def cmd_cp(*args)
|
|
304
|
+
if (args.length < 2)
|
|
305
|
+
print_line("Usage: cp oldfile newfile")
|
|
306
|
+
return true
|
|
307
|
+
end
|
|
308
|
+
client.fs.file.cp(args[0],args[1])
|
|
309
|
+
return true
|
|
310
|
+
end
|
|
239
311
|
|
|
240
|
-
|
|
241
|
-
alias :cmd_rename :cmd_mv
|
|
312
|
+
alias :cmd_copy :cmd_cp
|
|
242
313
|
|
|
243
314
|
|
|
244
315
|
def cmd_download_help
|
|
245
|
-
print_line
|
|
316
|
+
print_line("Usage: download [options] src1 src2 src3 ... destination")
|
|
246
317
|
print_line
|
|
247
|
-
print_line
|
|
248
|
-
print_line
|
|
318
|
+
print_line("Downloads remote files and directories to the local machine.")
|
|
319
|
+
print_line(@@download_opts.usage)
|
|
249
320
|
end
|
|
250
321
|
|
|
251
322
|
#
|
|
@@ -289,24 +360,62 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
|
289
360
|
dest = last
|
|
290
361
|
end
|
|
291
362
|
|
|
363
|
+
# Download to a directory, not a pattern
|
|
364
|
+
if client.fs.file.is_glob?(dest)
|
|
365
|
+
dest = ::File.dirname(dest)
|
|
366
|
+
end
|
|
367
|
+
|
|
292
368
|
# Go through each source item and download them
|
|
293
369
|
src_items.each { |src|
|
|
294
|
-
|
|
370
|
+
glob = nil
|
|
371
|
+
if client.fs.file.is_glob?(src)
|
|
372
|
+
glob = ::File.basename(src)
|
|
373
|
+
src = ::File.dirname(src)
|
|
374
|
+
end
|
|
295
375
|
|
|
296
|
-
if
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
376
|
+
# Use search if possible for recursive pattern matching. It will work
|
|
377
|
+
# more intuitively since it will not try to match on intermediate
|
|
378
|
+
# directories, only file names.
|
|
379
|
+
if glob && recursive && client.commands.include?('stdapi_fs_search')
|
|
380
|
+
|
|
381
|
+
files = client.fs.file.search(src, glob, recursive)
|
|
382
|
+
if !files.empty?
|
|
383
|
+
print_line("Downloading #{files.length} file#{files.length > 1 ? 's' : ''}...")
|
|
384
|
+
|
|
385
|
+
files.each do |file|
|
|
386
|
+
src_separator = client.fs.file.separator
|
|
387
|
+
src_path = file['path'] + client.fs.file.separator + file['name']
|
|
388
|
+
dest_path = src_path.tr(src_separator, ::File::SEPARATOR)
|
|
389
|
+
|
|
390
|
+
client.fs.file.download(dest_path, src_path) do |step, src, dst|
|
|
391
|
+
puts step
|
|
392
|
+
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
|
|
393
|
+
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
|
|
394
|
+
end
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
else
|
|
398
|
+
print_status("No matching files found for download")
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
else
|
|
402
|
+
# Perform direct matching
|
|
403
|
+
stat = client.fs.file.stat(src)
|
|
404
|
+
if (stat.directory?)
|
|
405
|
+
client.fs.dir.download(dest, src, recursive, true, glob) do |step, src, dst|
|
|
406
|
+
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
|
|
407
|
+
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
|
|
408
|
+
end
|
|
409
|
+
elsif (stat.file?)
|
|
410
|
+
client.fs.file.download(dest, src) do |step, src, dst|
|
|
411
|
+
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
|
|
412
|
+
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
|
|
413
|
+
end
|
|
414
|
+
end
|
|
306
415
|
end
|
|
307
416
|
}
|
|
308
417
|
|
|
309
|
-
|
|
418
|
+
true
|
|
310
419
|
end
|
|
311
420
|
|
|
312
421
|
#
|
|
@@ -324,16 +433,8 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
|
324
433
|
meterp_temp.binmode
|
|
325
434
|
temp_path = meterp_temp.path
|
|
326
435
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
client.fs.file.download_file(temp_path, args[0])
|
|
330
|
-
rescue RequestError => re
|
|
331
|
-
# If the file doesn't exist, then it's okay. Otherwise, throw the
|
|
332
|
-
# error.
|
|
333
|
-
if re.result != 2
|
|
334
|
-
raise $!
|
|
335
|
-
end
|
|
336
|
-
end
|
|
436
|
+
# Try to download the file, but don't worry if it doesn't exist
|
|
437
|
+
client.fs.file.download_file(temp_path, args[0]) rescue nil
|
|
337
438
|
|
|
338
439
|
# Spawn the editor (default to vi)
|
|
339
440
|
editor = Rex::Compat.getenv('EDITOR') || 'vi'
|
|
@@ -357,49 +458,139 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
|
357
458
|
|
|
358
459
|
alias cmd_getlwd cmd_lpwd
|
|
359
460
|
|
|
461
|
+
|
|
462
|
+
def cmd_ls_help
|
|
463
|
+
print_line "Usage: ls [options]"
|
|
464
|
+
print_line
|
|
465
|
+
print_line "Lists contents of directory or file info, searchable"
|
|
466
|
+
print_line @@ls_opts.usage
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def list_path(path, columns, sort, order, short, recursive = false, depth = 0, search_term = nil)
|
|
470
|
+
|
|
471
|
+
# avoid infinite recursion
|
|
472
|
+
if depth > 100
|
|
473
|
+
return
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
tbl = Rex::Ui::Text::Table.new(
|
|
477
|
+
'Header' => "Listing: #{path}",
|
|
478
|
+
'SortIndex' => columns.index(sort),
|
|
479
|
+
'SortOrder' => order,
|
|
480
|
+
'Columns' => columns,
|
|
481
|
+
'SearchTerm' => search_term)
|
|
482
|
+
|
|
483
|
+
items = 0
|
|
484
|
+
|
|
485
|
+
# Enumerate each item...
|
|
486
|
+
# No need to sort as Table will do it for us
|
|
487
|
+
client.fs.dir.entries_with_info(path).each do |p|
|
|
488
|
+
|
|
489
|
+
ffstat = p['StatBuf']
|
|
490
|
+
fname = p['FileName'] || 'unknown'
|
|
491
|
+
|
|
492
|
+
row = [
|
|
493
|
+
ffstat ? ffstat.prettymode : '',
|
|
494
|
+
ffstat ? ffstat.size : '',
|
|
495
|
+
ffstat ? ffstat.ftype[0,3] : '',
|
|
496
|
+
ffstat ? ffstat.mtime : '',
|
|
497
|
+
fname
|
|
498
|
+
]
|
|
499
|
+
row.insert(4, p['FileShortName'] || '') if short
|
|
500
|
+
|
|
501
|
+
if fname != '.' && fname != '..'
|
|
502
|
+
if row.join(' ') =~ /#{search_term}/
|
|
503
|
+
tbl << row
|
|
504
|
+
items += 1
|
|
505
|
+
end
|
|
506
|
+
|
|
507
|
+
if recursive && ffstat && ffstat.directory?
|
|
508
|
+
if client.fs.file.is_glob?(path)
|
|
509
|
+
child_path = ::File.dirname(path) + ::File::SEPARATOR + fname
|
|
510
|
+
child_path += ::File::SEPARATOR + ::File.basename(path)
|
|
511
|
+
else
|
|
512
|
+
child_path = path + ::File::SEPARATOR + fname
|
|
513
|
+
end
|
|
514
|
+
begin
|
|
515
|
+
list_path(child_path, columns, sort, order, short, recursive, depth + 1, search_term)
|
|
516
|
+
rescue RequestError
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
if items > 0
|
|
523
|
+
print_line(tbl.to_s)
|
|
524
|
+
else
|
|
525
|
+
print_line("No entries exist in #{path}")
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
360
529
|
#
|
|
361
530
|
# Lists files
|
|
362
531
|
#
|
|
363
|
-
# TODO: make this more useful
|
|
364
|
-
#
|
|
365
532
|
def cmd_ls(*args)
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
533
|
+
# Set defaults
|
|
534
|
+
path = client.fs.dir.getwd
|
|
535
|
+
search_term = nil
|
|
536
|
+
sort = 'Name'
|
|
537
|
+
short = nil
|
|
538
|
+
order = :forward
|
|
539
|
+
recursive = nil
|
|
540
|
+
|
|
541
|
+
# Parse the args
|
|
542
|
+
@@ls_opts.parse(args) { |opt, idx, val|
|
|
543
|
+
case opt
|
|
544
|
+
# Sort options
|
|
545
|
+
when '-s'
|
|
546
|
+
sort = 'Size'
|
|
547
|
+
when '-t'
|
|
548
|
+
sort = 'Last modified'
|
|
549
|
+
# Output options
|
|
550
|
+
when '-x'
|
|
551
|
+
short = true
|
|
552
|
+
when '-l'
|
|
553
|
+
short = nil
|
|
554
|
+
when '-r'
|
|
555
|
+
order = :reverse
|
|
556
|
+
when '-R'
|
|
557
|
+
recursive = true
|
|
558
|
+
# Search
|
|
559
|
+
when '-S'
|
|
560
|
+
search_term = val
|
|
561
|
+
if search_term.nil?
|
|
562
|
+
print_error("Enter a search term")
|
|
563
|
+
return true
|
|
564
|
+
else
|
|
565
|
+
search_term = /#{search_term}/nmi
|
|
566
|
+
end
|
|
567
|
+
# Help and path
|
|
568
|
+
when "-h"
|
|
569
|
+
cmd_ls_help
|
|
570
|
+
return 0
|
|
571
|
+
when nil
|
|
572
|
+
path = val
|
|
573
|
+
end
|
|
574
|
+
}
|
|
378
575
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
p['StatBuf'] ? p['StatBuf'].ftype[0,3] : '',
|
|
391
|
-
p['StatBuf'] ? p['StatBuf'].mtime : '',
|
|
392
|
-
p['FileName'] || 'unknown'
|
|
393
|
-
]
|
|
394
|
-
|
|
395
|
-
items += 1
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
if (items > 0)
|
|
399
|
-
print("\n" + tbl.to_s + "\n")
|
|
400
|
-
else
|
|
401
|
-
print_line("No entries exist in #{path}")
|
|
576
|
+
columns = [ 'Mode', 'Size', 'Type', 'Last modified', 'Name' ]
|
|
577
|
+
columns.insert(4, 'Short Name') if short
|
|
578
|
+
|
|
579
|
+
stat_path = path
|
|
580
|
+
|
|
581
|
+
# Check session capabilities
|
|
582
|
+
is_glob = client.fs.file.is_glob?(path)
|
|
583
|
+
if is_glob
|
|
584
|
+
if !client.commands.include?('stdapi_fs_search')
|
|
585
|
+
print_line('File globbing not supported with this session')
|
|
586
|
+
return
|
|
402
587
|
end
|
|
588
|
+
stat_path = ::File.dirname(path)
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
stat = client.fs.file.stat(stat_path)
|
|
592
|
+
if stat.directory?
|
|
593
|
+
list_path(path, columns, sort, order, short, recursive, 0, search_term)
|
|
403
594
|
else
|
|
404
595
|
print_line("#{stat.prettymode} #{stat.size} #{stat.ftype[0,3]} #{stat.mtime} #{path}")
|
|
405
596
|
end
|
|
@@ -452,10 +643,10 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
|
452
643
|
end
|
|
453
644
|
|
|
454
645
|
def cmd_upload_help
|
|
455
|
-
print_line
|
|
646
|
+
print_line("Usage: upload [options] src1 src2 src3 ... destination")
|
|
456
647
|
print_line
|
|
457
|
-
print_line
|
|
458
|
-
print_line
|
|
648
|
+
print_line("Uploads local files and directories to the remote machine.")
|
|
649
|
+
print_line(@@upload_opts.usage)
|
|
459
650
|
end
|
|
460
651
|
|
|
461
652
|
#
|