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/ftp/client.rb
Views: 11704
1
# -*- coding: binary -*-
2
3
require 'rex/socket'
4
5
module Rex
6
module Proto
7
module Ftp
8
###
9
#
10
# Acts as a client to an FTP server
11
# See the RFC: https://www.w3.org/Protocols/rfc959/
12
#
13
###
14
class Client
15
#
16
# Creates a new client instance.
17
#
18
def initialize(host, port = 21, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '', verbose = false)
19
self.hostname = host
20
self.port = port.to_i
21
self.context = context
22
self.ssl = ssl
23
self.ssl_version = ssl_version
24
self.proxies = proxies
25
self.username = username
26
self.password = password
27
self.verbose = verbose
28
end
29
30
#
31
# This method estabilishes a connection to the host on the port
32
# defined in opts{}, if the connection is successful, the method
33
# returns a socket which can be used to communicate with the client
34
#
35
def connect
36
nsock = Rex::Socket::Tcp.create(
37
'PeerHost' => self.hostname,
38
'PeerPort' => self.port,
39
'LocalHost' => "0.0.0.0",
40
'LocalPort' => 0.to_i,
41
'SSL' => self.ssl,
42
'SSLVersion' => self.ssl_version,
43
'Proxies' => self.proxies
44
)
45
self.sock = nsock
46
self.banner = recv_ftp_resp(nsock)
47
print_status("Connected to target FTP server.") if self.verbose
48
nsock
49
end
50
51
#
52
# This method reads an FTP response based on FTP continuation
53
#
54
def recv_ftp_resp(nsock = self.sock)
55
found_end = false
56
resp = ""
57
left = ""
58
if !@ftpbuff.empty?
59
left << @ftpbuff
60
@ftpbuff = ""
61
end
62
while true
63
data = nsock.get_once(-1, ftp_timeout)
64
if !data
65
@ftpbuff << resp
66
@ftpbuff << left
67
return data
68
end
69
70
got = left + data
71
left = ""
72
73
# handle the end w/o newline case
74
enlidx = got.rindex(0x0a.chr)
75
if enlidx != (got.length - 1)
76
if !enlidx
77
left << got
78
next
79
else
80
left << got.slice!((enlidx + 1)..got.length)
81
end
82
end
83
84
# split into lines
85
rarr = got.split(/\r?\n/)
86
rarr.each do |ln|
87
if !found_end
88
resp << ln
89
resp << "\r\n"
90
if ln.length > 3 && ln[3, 1] == ' '
91
found_end = true
92
end
93
else
94
left << ln
95
left << "\r\n"
96
end
97
end
98
if found_end
99
@ftpbuff << left
100
print_status("FTP recv: #{resp.inspect}") if self.verbose
101
return resp
102
end
103
end
104
end
105
106
#
107
# This method transmits a FTP command and does not wait for a response
108
#
109
def raw_send(cmd, nsock = self.sock)
110
print_status("FTP send: #{cmd.inspect}") if self.verbose
111
nsock.put(cmd)
112
end
113
114
#
115
# This method transmits a FTP command and waits for a response. If one is
116
# received, it is returned to the caller.
117
#
118
def raw_send_recv(cmd, nsock = self.sock)
119
nsock.put(cmd)
120
nsock.get_once(-1, ftp_timeout)
121
end
122
123
#
124
# This method uses the senduser and sendpass methods defined below
125
# in order to login to the ftp server
126
#
127
def connect_login
128
ftpsock = connect
129
130
if !(self.user && self.pass)
131
print_error("No username and password were supplied, unable to login")
132
return false
133
end
134
135
print_status("Authenticating as #{user} with password #{pass}...") if self.verbose
136
res = send_user(user, ftpsock)
137
138
if res !~ /^(331|2)/
139
print_error("The server rejected our username") if self.verbose
140
return false
141
end
142
143
if pass
144
print_status("Sending password...") if self.verbose
145
res = send_pass(pass, ftpsock)
146
if res !~ /^2/
147
print_error("The server rejected our password") if self.verbose
148
return false
149
end
150
end
151
152
true
153
end
154
155
#
156
# This method logs in as the supplied user by transmitting the FTP
157
# 'USER <user>' command.
158
#
159
def send_user(user, nsock = self.sock)
160
raw_send("USER #{user}\r\n", nsock)
161
recv_ftp_resp(nsock)
162
end
163
164
#
165
# This method completes user authentication by sending the supplied
166
# password using the FTP 'PASS <pass>' command.
167
#
168
def send_pass(pass, nsock = self.sock)
169
raw_send("PASS #{pass}\r\n", nsock)
170
recv_ftp_resp(nsock)
171
end
172
173
#
174
# This method handles establishing datasocket for data channel
175
#
176
def data_connect(mode = nil, nsock = self.sock)
177
if mode
178
res = send_cmd([ 'TYPE', mode ], true, nsock)
179
return nil if not res =~ /^200/
180
end
181
182
# force datasocket to renegotiate
183
self.datasocket.shutdown if self.datasocket != nil
184
185
res = send_cmd(['PASV'], true, nsock)
186
return nil if not res =~ /^227/
187
188
# 227 Entering Passive Mode (127,0,0,1,196,5)
189
if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/
190
# convert port to FTP syntax
191
datahost = "#{$1}.#{$2}.#{$3}.#{$4}"
192
dataport = ($5.to_i * 256) + $6.to_i
193
self.datasocket = Rex::Socket::Tcp.create(
194
'PeerHost' => datahost,
195
'PeerPort' => dataport
196
)
197
end
198
self.datasocket
199
end
200
201
#
202
# This method handles disconnecting our data channel
203
#
204
def data_disconnect
205
self.datasocket.shutdown if self.datasocket
206
self.datasocket = nil
207
end
208
209
#
210
# This method sends one command with zero or more parameters
211
#
212
def send_cmd(args, recv = true, nsock = self.sock)
213
cmd = args.join(" ") + "\r\n"
214
ret = raw_send(cmd, nsock)
215
if recv
216
return recv_ftp_resp(nsock)
217
end
218
ret
219
end
220
221
#
222
# This method sends a QUIT command.
223
#
224
def send_quit(nsock = self.sock)
225
raw_send("QUIT\r\n", nsock)
226
recv_ftp_resp(nsock)
227
end
228
229
#
230
# This method transmits the command in args and receives / uploads DATA via data channel
231
# For commands not needing data, it will fall through to the original send_cmd
232
#
233
# For commands that send data only, the return will be the server response.
234
# For commands returning both data and a server response, an array will be returned.
235
#
236
# NOTE: This function always waits for a response from the server.
237
#
238
def send_cmd_data(args, data, mode = 'a', nsock = self.sock)
239
type = nil
240
# implement some aliases for various commands
241
if args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i
242
# TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i
243
args[0] = "LIST"
244
type = "get"
245
elsif args[0] =~ /^GET$/i
246
args[0] = "RETR"
247
type = "get"
248
elsif args[0] =~ /^PUT$/i
249
args[0] = "STOR"
250
type = "put"
251
end
252
253
# fall back if it's not a supported data command
254
if !type
255
return send_cmd(args, true, nsock)
256
end
257
258
# Set the transfer mode and connect to the remove server
259
return nil if !data_connect(mode)
260
261
# Our pending command should have got a connection now.
262
res = send_cmd(args, true, nsock)
263
# make sure could open port
264
return nil unless res =~ /^(150|125) /
265
266
# dispatch to the proper method
267
if type == "get"
268
# failed listings just disconnect..
269
begin
270
data = self.datasocket.get_once(-1, ftp_timeout)
271
rescue ::EOFError
272
data = nil
273
end
274
else
275
sent = self.datasocket.put(data)
276
end
277
278
# close data channel so command channel updates
279
data_disconnect
280
281
# get status of transfer
282
ret = nil
283
if type == "get"
284
ret = recv_ftp_resp(nsock)
285
ret = [ ret, data ]
286
else
287
ret = recv_ftp_resp(nsock)
288
end
289
290
ret
291
end
292
293
#
294
# Function implementing 'ls' or list files command
295
#
296
def ls
297
datasocket = data_connect
298
send_cmd(["list"])
299
output = datasocket.get
300
data_disconnect
301
output
302
end
303
304
#
305
# Function implementing 'pwd' or present working directory command
306
#
307
def pwd
308
send_cmd(["pwd"])
309
end
310
311
#
312
# Function implementing 'cd' or change directory command
313
#
314
def cd(path)
315
send_cmd(["cwd " + path])
316
end
317
318
#
319
# Function implementing download command
320
#
321
def download(filename)
322
datasocket = data_connect
323
send_cmd(["retr", filename])
324
output = datasocket.get
325
file = File.open(filename, "wb")
326
file.write(output)
327
file.close
328
data_disconnect
329
end
330
331
#
332
# Function implementing upload command
333
#
334
def upload
335
datasocket = data_connect
336
file = File.open(filename, "rb")
337
data = file.read
338
file.close
339
send_cmd(["stor", filename])
340
datasocket.write(data)
341
data_disconnect
342
end
343
end
344
end
345
end
346
end
347
348