Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/proto/ftp/client.rb
Views: 11704
# -*- coding: binary -*-12require 'rex/socket'34module Rex5module Proto6module Ftp7###8#9# Acts as a client to an FTP server10# See the RFC: https://www.w3.org/Protocols/rfc959/11#12###13class Client14#15# Creates a new client instance.16#17def initialize(host, port = 21, ssl = nil, ssl_version = nil, proxies = nil, username = '', password = '', verbose = false)18self.hostname = host19self.port = port.to_i20self.context = context21self.ssl = ssl22self.ssl_version = ssl_version23self.proxies = proxies24self.username = username25self.password = password26self.verbose = verbose27end2829#30# This method estabilishes a connection to the host on the port31# defined in opts{}, if the connection is successful, the method32# returns a socket which can be used to communicate with the client33#34def connect35nsock = Rex::Socket::Tcp.create(36'PeerHost' => self.hostname,37'PeerPort' => self.port,38'LocalHost' => "0.0.0.0",39'LocalPort' => 0.to_i,40'SSL' => self.ssl,41'SSLVersion' => self.ssl_version,42'Proxies' => self.proxies43)44self.sock = nsock45self.banner = recv_ftp_resp(nsock)46print_status("Connected to target FTP server.") if self.verbose47nsock48end4950#51# This method reads an FTP response based on FTP continuation52#53def recv_ftp_resp(nsock = self.sock)54found_end = false55resp = ""56left = ""57if !@ftpbuff.empty?58left << @ftpbuff59@ftpbuff = ""60end61while true62data = nsock.get_once(-1, ftp_timeout)63if !data64@ftpbuff << resp65@ftpbuff << left66return data67end6869got = left + data70left = ""7172# handle the end w/o newline case73enlidx = got.rindex(0x0a.chr)74if enlidx != (got.length - 1)75if !enlidx76left << got77next78else79left << got.slice!((enlidx + 1)..got.length)80end81end8283# split into lines84rarr = got.split(/\r?\n/)85rarr.each do |ln|86if !found_end87resp << ln88resp << "\r\n"89if ln.length > 3 && ln[3, 1] == ' '90found_end = true91end92else93left << ln94left << "\r\n"95end96end97if found_end98@ftpbuff << left99print_status("FTP recv: #{resp.inspect}") if self.verbose100return resp101end102end103end104105#106# This method transmits a FTP command and does not wait for a response107#108def raw_send(cmd, nsock = self.sock)109print_status("FTP send: #{cmd.inspect}") if self.verbose110nsock.put(cmd)111end112113#114# This method transmits a FTP command and waits for a response. If one is115# received, it is returned to the caller.116#117def raw_send_recv(cmd, nsock = self.sock)118nsock.put(cmd)119nsock.get_once(-1, ftp_timeout)120end121122#123# This method uses the senduser and sendpass methods defined below124# in order to login to the ftp server125#126def connect_login127ftpsock = connect128129if !(self.user && self.pass)130print_error("No username and password were supplied, unable to login")131return false132end133134print_status("Authenticating as #{user} with password #{pass}...") if self.verbose135res = send_user(user, ftpsock)136137if res !~ /^(331|2)/138print_error("The server rejected our username") if self.verbose139return false140end141142if pass143print_status("Sending password...") if self.verbose144res = send_pass(pass, ftpsock)145if res !~ /^2/146print_error("The server rejected our password") if self.verbose147return false148end149end150151true152end153154#155# This method logs in as the supplied user by transmitting the FTP156# 'USER <user>' command.157#158def send_user(user, nsock = self.sock)159raw_send("USER #{user}\r\n", nsock)160recv_ftp_resp(nsock)161end162163#164# This method completes user authentication by sending the supplied165# password using the FTP 'PASS <pass>' command.166#167def send_pass(pass, nsock = self.sock)168raw_send("PASS #{pass}\r\n", nsock)169recv_ftp_resp(nsock)170end171172#173# This method handles establishing datasocket for data channel174#175def data_connect(mode = nil, nsock = self.sock)176if mode177res = send_cmd([ 'TYPE', mode ], true, nsock)178return nil if not res =~ /^200/179end180181# force datasocket to renegotiate182self.datasocket.shutdown if self.datasocket != nil183184res = send_cmd(['PASV'], true, nsock)185return nil if not res =~ /^227/186187# 227 Entering Passive Mode (127,0,0,1,196,5)188if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/189# convert port to FTP syntax190datahost = "#{$1}.#{$2}.#{$3}.#{$4}"191dataport = ($5.to_i * 256) + $6.to_i192self.datasocket = Rex::Socket::Tcp.create(193'PeerHost' => datahost,194'PeerPort' => dataport195)196end197self.datasocket198end199200#201# This method handles disconnecting our data channel202#203def data_disconnect204self.datasocket.shutdown if self.datasocket205self.datasocket = nil206end207208#209# This method sends one command with zero or more parameters210#211def send_cmd(args, recv = true, nsock = self.sock)212cmd = args.join(" ") + "\r\n"213ret = raw_send(cmd, nsock)214if recv215return recv_ftp_resp(nsock)216end217ret218end219220#221# This method sends a QUIT command.222#223def send_quit(nsock = self.sock)224raw_send("QUIT\r\n", nsock)225recv_ftp_resp(nsock)226end227228#229# This method transmits the command in args and receives / uploads DATA via data channel230# For commands not needing data, it will fall through to the original send_cmd231#232# For commands that send data only, the return will be the server response.233# For commands returning both data and a server response, an array will be returned.234#235# NOTE: This function always waits for a response from the server.236#237def send_cmd_data(args, data, mode = 'a', nsock = self.sock)238type = nil239# implement some aliases for various commands240if args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i241# TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i242args[0] = "LIST"243type = "get"244elsif args[0] =~ /^GET$/i245args[0] = "RETR"246type = "get"247elsif args[0] =~ /^PUT$/i248args[0] = "STOR"249type = "put"250end251252# fall back if it's not a supported data command253if !type254return send_cmd(args, true, nsock)255end256257# Set the transfer mode and connect to the remove server258return nil if !data_connect(mode)259260# Our pending command should have got a connection now.261res = send_cmd(args, true, nsock)262# make sure could open port263return nil unless res =~ /^(150|125) /264265# dispatch to the proper method266if type == "get"267# failed listings just disconnect..268begin269data = self.datasocket.get_once(-1, ftp_timeout)270rescue ::EOFError271data = nil272end273else274sent = self.datasocket.put(data)275end276277# close data channel so command channel updates278data_disconnect279280# get status of transfer281ret = nil282if type == "get"283ret = recv_ftp_resp(nsock)284ret = [ ret, data ]285else286ret = recv_ftp_resp(nsock)287end288289ret290end291292#293# Function implementing 'ls' or list files command294#295def ls296datasocket = data_connect297send_cmd(["list"])298output = datasocket.get299data_disconnect300output301end302303#304# Function implementing 'pwd' or present working directory command305#306def pwd307send_cmd(["pwd"])308end309310#311# Function implementing 'cd' or change directory command312#313def cd(path)314send_cmd(["cwd " + path])315end316317#318# Function implementing download command319#320def download(filename)321datasocket = data_connect322send_cmd(["retr", filename])323output = datasocket.get324file = File.open(filename, "wb")325file.write(output)326file.close327data_disconnect328end329330#331# Function implementing upload command332#333def upload334datasocket = data_connect335file = File.open(filename, "rb")336data = file.read337file.close338send_cmd(["stor", filename])339datasocket.write(data)340data_disconnect341end342end343end344end345end346347348