Python + Twisted + FtpClient + SOCKS

Acabo de empezar a usar Twisted. Quiero conectarme a un servidor FTP y realizar algunas operaciones básicas (si es posible, use subprocesos). Estoy usando este ejemplo .

Lo que hace el trabajo bastante bien. ¿La pregunta es cómo agregar un uso de proxy SOCKS4 / 5 al código? ¿Alguien puede proporcionar un ejemplo de trabajo? He intentado este enlace también.

Pero,

# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ An example of using the FTP client """ # Twisted imports from twisted.protocols.ftp import FTPClient, FTPFileListProtocol from twisted.internet.protocol import Protocol, ClientCreator from twisted.python import usage from twisted.internet import reactor, endpoints # Socks support test from socksclient import SOCKSv4ClientProtocol, SOCKSWrapper from twisted.web import client # Standard library imports import string import sys try: from cStringIO import StringIO except ImportError: from StringIO import StringIO class BufferingProtocol(Protocol): """Simple utility class that holds all data written to it in a buffer.""" def __init__(self): self.buffer = StringIO() def dataReceived(self, data): self.buffer.write(data) # Define some callbacks def success(response): print 'Success! Got response:' print '---' if response is None: print None else: print string.join(response, '\n') print '---' def fail(error): print 'Failed. Error was:' print error def showFiles(result, fileListProtocol): print 'Processed file listing:' for file in fileListProtocol.files: print ' %s: %d bytes, %s' \ % (file['filename'], file['size'], file['date']) print 'Total: %d files' % (len(fileListProtocol.files)) def showBuffer(result, bufferProtocol): print 'Got data:' print bufferProtocol.buffer.getvalue() class Options(usage.Options): optParameters = [['host', 'h', 'example.com'], ['port', 'p', 21], ['username', 'u', 'webmaster'], ['password', None, 'justapass'], ['passive', None, 0], ['debug', 'd', 1], ] # Socks support def wrappercb(proxy): print "connected to proxy", proxy pass def run(): def sockswrapper(proxy, url): dest = client._parse(url) # scheme, host, port, path endpoint = endpoints.TCP4ClientEndpoint(reactor, dest[1], dest[2]) return SOCKSWrapper(reactor, proxy[1], proxy[2], endpoint) # Get config config = Options() config.parseOptions() config.opts['port'] = int(config.opts['port']) config.opts['passive'] = int(config.opts['passive']) config.opts['debug'] = int(config.opts['debug']) # Create the client FTPClient.debug = config.opts['debug'] creator = ClientCreator(reactor, FTPClient, config.opts['username'], config.opts['password'], passive=config.opts['passive']) #creator.connectTCP(config.opts['host'], config.opts['port']).addCallback(connectionMade).addErrback(connectionFailed) # Socks support proxy = (None, '1.1.1.1', 1111, True, None, None) sw = sockswrapper(proxy, "ftp://example.com") d = sw.connect(creator) d.addCallback(wrappercb) reactor.run() def connectionFailed(f): print "Connection Failed:", f reactor.stop() def connectionMade(ftpClient): # Get the current working directory ftpClient.pwd().addCallbacks(success, fail) # Get a detailed listing of the current directory fileList = FTPFileListProtocol() d = ftpClient.list('.', fileList) d.addCallbacks(showFiles, fail, callbackArgs=(fileList,)) # Change to the parent directory ftpClient.cdup().addCallbacks(success, fail) # Create a buffer proto = BufferingProtocol() # Get short listing of current directory, and quit when done d = ftpClient.nlst('.', proto) d.addCallbacks(showBuffer, fail, callbackArgs=(proto,)) d.addCallback(lambda result: reactor.stop()) # this only runs if the module was *not* imported if __name__ == '__main__': run() 

Sé que el código es incorrecto. Necesito solucion

    Bien, aquí hay una solución ( gist ) que usa ftplib incorporado en ftplib , así como el módulo de código abierto SocksiPy .

    No utiliza trenzado ni explícitamente subprocesos, pero el uso y la comunicación entre subprocesos se realiza fácilmente con threading.Thread Subprocesos y threading.Queue en el módulo de threading estándar de python

    Básicamente, necesitamos subclase ftplib.FTP para admitir la sustitución de nuestro propio método create_connection y agregar semánticas de configuración de proxy.

    La lógica “principal” simplemente configura un cliente FTP que se conecta a través de un proxy localhost socks, como el creado por ssh -D localhost:1080 socksproxy.example.com , y descarga una instantánea de origen para autoconf de GNU al disco local.

     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) 

    Para explicar por qué hice algunas de mis preguntas originales:

    1) ¿Necesita respaldar transferencias activas o serán suficientes las transferencias PASV?

    Las transferencias activas son mucho más difíciles de realizar a través de un proxy de calcetines porque requieren el uso del comando PORT. Con el comando PORT, su cliente ftp le dice al servidor FTP que se conecte a usted en un puerto específico (por ejemplo, en su PC) para enviar los datos. Es probable que esto no funcione para los usuarios detrás de un firewall o NAT / router. Si su servidor proxy SOCKS no está detrás de un firewall, o tiene una IP pública, es posible admitir transferencias activas, pero es complicado: requiere su servidor SOCKS (ssh -D es compatible con esto) y la biblioteca del cliente (socksipy no ) para soportar el enlace de puerto remoto. También requiere los enlaces apropiados en la aplicación (mi ejemplo lanza una excepción si passiveserver = False ) para hacer un BIND remoto en lugar de uno local.

    2) ¿Tiene que usarse retorcido?

    Twisted es genial, pero no soy el mejor en eso, y no he encontrado una gran implementación de cliente SOCKS. Lo ideal sería que hubiera una biblioteca por ahí que te permitiera definir y / o encadenar proxies juntos, devolviendo un objeto que implementa la interfaz IReactorTCP , pero todavía no he encontrado nada como esto.

    3) ¿Sus calcetines son un proxy detrás de un VIP o solo un host conectado directamente a Internet?

    Esto importa por la forma en que funciona la seguridad de transferencia PASV. En una transferencia PASV, el cliente le pide al servidor que proporcione un puerto para conectarse para iniciar una transferencia de datos. Cuando el servidor acepta una conexión en ese puerto, DEBE verificar que el cliente esté conectado desde la misma IP de origen que la conexión que solicitó la transferencia. Si su servidor SOCKS está detrás de un VIP, es menos probable que la IP de salida de la conexión realizada para las transferencias de PASV coincida con la IP de salida de la conexión de comunicación principal.