-
-
Save fmoo/4409975 to your computer and use it in GitHub Desktop.
An FTP client implementation that supports sending and receiving data via SOCKS proxy using PASV transfers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ftplib | |
import socket | |
import socks # socksipy (https://github.com/mikedougherty/SocksiPy) | |
class FTP(ftplib.FTP): | |
def __init__(self, host='', user='', passwd='', acct='', | |
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, | |
proxyconfig=None): | |
"""Like ftplib.FTP constructor, but with an added `proxyconfig` kwarg | |
`proxyconfig` should be a dictionary that may contain the following | |
keys: | |
proxytype - The type of the proxy to be used. Three types | |
are supported: PROXY_TYPE_SOCKS4 (including socks4a), | |
PROXY_TYPE_SOCKS5 and PROXY_TYPE_HTTP | |
addr - The address of the server (IP or DNS). | |
port - The port of the server. Defaults to 1080 for SOCKS | |
servers and 8080 for HTTP proxy servers. | |
rdns - Should DNS queries be preformed on the remote side | |
(rather than the local side). The default is True. | |
Note: This has no effect with SOCKS4 servers. | |
username - Username to authenticate with to the server. | |
The default is no authentication. | |
password - Password to authenticate with to the server. | |
Only relevant when username is also provided. | |
""" | |
self.proxyconfig = proxyconfig or {} | |
ftplib.FTP.__init__(self, host, user, passwd, acct, timeout) | |
def connect(self, host='', port=0, timeout=-999): | |
'''Connect to host. Arguments are: | |
- host: hostname to connect to (string, default previous host) | |
- port: port to connect to (integer, default previous port) | |
''' | |
if host != '': | |
self.host = host | |
if port > 0: | |
self.port = port | |
if timeout != -999: | |
self.timeout = timeout | |
self.sock = self.create_connection(self.host, self.port) | |
self.af = self.sock.family | |
self.file = self.sock.makefile('rb') | |
self.welcome = self.getresp() | |
return self.welcome | |
def create_connection(self, host=None, port=None): | |
host, port = host or self.host, port or self.port | |
if self.proxyconfig: | |
phost, pport = self.proxyconfig['addr'], self.proxyconfig['port'] | |
err = None | |
for res in socket.getaddrinfo(phost, pport, 0, socket.SOCK_STREAM): | |
af, socktype, proto, canonname, sa = res | |
sock = None | |
try: | |
sock = socks.socksocket(af, socktype, proto) | |
sock.setproxy(**self.proxyconfig) | |
if self.timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: | |
sock.settimeout(self.timeout) | |
sock.connect((host, port)) | |
return sock | |
except socket.error as _: | |
err = _ | |
if sock is not None: | |
sock.close() | |
if err is not None: | |
raise err | |
else: | |
raise socket.error("getaddrinfo returns an empty list") | |
else: | |
sock = socket.create_connection((host, port), self.timeout) | |
return sock | |
def ntransfercmd(self, cmd, rest=None): | |
size = None | |
if self.passiveserver: | |
host, port = self.makepasv() | |
conn = self.create_connection(host, port) | |
try: | |
if rest is not None: | |
self.sendcmd("REST %s" % rest) | |
resp = self.sendcmd(cmd) | |
# Some servers apparently send a 200 reply to | |
# a LIST or STOR command, before the 150 reply | |
# (and way before the 226 reply). This seems to | |
# be in violation of the protocol (which only allows | |
# 1xx or error messages for LIST), so we just discard | |
# this response. | |
if resp[0] == '2': | |
resp = self.getresp() | |
if resp[0] != '1': | |
raise ftplib.error_reply, resp | |
except: | |
conn.close() | |
raise | |
else: | |
raise Exception("Active transfers not supported") | |
if resp[:3] == '150': | |
# this is conditional in case we received a 125 | |
size = ftplib.parse150(resp) | |
return conn, size | |
if __name__ == '__main__': | |
ftp = FTP(host='ftp.gnu.org', user='anonymous', passwd='guest', | |
proxyconfig=dict(proxytype=socks.PROXY_TYPE_SOCKS5, rdns=False, | |
addr='localhost', port=1080)) | |
with open('autoconf-2.69.tar.xz', mode='w') as f: | |
ftp.retrbinary("RETR /gnu/autoconf/autoconf-2.69.tar.xz", f.write) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment