class CustomFTP(FTP): def ftp_PASV(self): # if we have a DTP port set up, lose it. if self.dtpFactory is not None: # cleanupDTP sets dtpFactory to none. Later we'll do # cleanup here or something. self.cleanupDTP() self.dtpFactory = DTPFactory(pi=self) self.dtpFactory.setTimeout(self.dtpTimeout) self.dtpPort = self.getDTPPort(self.dtpFactory) if self.factory.passivePublicIp is not None: # use explicit public IP for passive mode (when behind load-balancer or such) host = self.factory.passivePublicIp log.msg("using explicit public IP %s" % host) else: # use transport IP host = self.transport.getHost().host log.msg("using transport IP %s" % host) port = self.dtpPort.getHost().port self.reply(ENTERING_PASV_MODE, encodeHostPort(host, port)) return self.dtpFactory.deferred.addCallback(lambda ign: None)
def ftp_PORT(self, address): addr = map(int, address.split(',')) ip = '%d.%d.%d.%d' % tuple(addr[:4]) port = addr[4] << 8 | addr[5] # if we have a DTP port set up, lose it. if self.dtpFactory is not None: self.cleanupDTP() self.dtpFactory = DTPFactory(pi=self, peerHost=self.transport.getPeer().host) self.dtpFactory.setTimeout(self.dtpTimeout) self.dtpPort = reactor.connectTCP(ip, port, self.dtpFactory) def connected(ignored): self.reply(ENTERING_PORT_MODE) if self.tls_mode: self.dtpInstance.transport.startTLS(self.factory.cert_options) def connFailed(err): err.trap(PortConnectionError) return CANT_OPEN_DATA_CNX return self.dtpFactory.deferred.addCallbacks(connected, connFailed)
class SwftpFTPProtocol(FTP, object): _connCountMap = defaultdict(int) maxConnectionsPerUser = 10 PUBLIC_COMMANDS = ['FEAT', 'QUIT', 'AUTH'] tls_mode = False def ftp_AUTH(self, *args, **kwargs): mode, = args if mode == 'TLS': msg("Initiated TLS") self.reply(AUTH_OK) self.transport.startTLS(self.factory.cert_options) return return defer.fail(CmdNotImplementedError('AUTH %s' % mode)) def ftp_PBSZ(self, *args, **kwargs): return CMD_OK def ftp_PROT(self, *args, **kwargs): cmd, = args if cmd == 'P': self.tls_mode = True return CMD_OK def connectionMade(self, *args, **kwargs): log.msg(metric='num_clients') return super(SwftpFTPProtocol, self).connectionMade(*args, **kwargs) def connectionLost(self, *args, **kwargs): log.msg(metric='num_clients', count=-1) if self.shell: username = self.shell.username() msg("User Disconnected (%s) [%s/%s]" % ( username, self._connCountMap[username], self.maxConnectionsPerUser, )) self._connCountMap[username] -= 1 # To avoid a slow memory leak if self._connCountMap[username] == 0: del self._connCountMap[username] return super(SwftpFTPProtocol, self).connectionLost(*args, **kwargs) def ftp_PASS(self, *args, **kwargs): # Check to see if the user has too many connections d = super(SwftpFTPProtocol, self).ftp_PASS(*args, **kwargs) def pass_cb(res): username = self.shell.username() self._connCountMap[username] += 1 msg("User Connected (%s) [%s/%s]" % ( username, self._connCountMap[username], self.maxConnectionsPerUser, )) if self.maxConnectionsPerUser != 0 and \ self._connCountMap[username] > self.maxConnectionsPerUser: msg("Too Many Connections For User (%s) [%s/%s]" % ( username, self._connCountMap[username], self.maxConnectionsPerUser, )) self.sendLine(RESPONSE[TOO_MANY_CONNECTIONS]) self.transport.loseConnection() return res d.addCallback(pass_cb) return d def ftp_LIST(self, path=''): # ignore special flags for command LIST keys = ['-a', '-l', '-la', '-al'] segm = path.split() path = " ".join(s for s in segm if s.lower() not in keys) return super(SwftpFTPProtocol, self).ftp_LIST(path) def ftp_NLST(self, path=''): """ Overwrite for fix http://twistedmatrix.com/trac/ticket/4258 """ return super(SwftpFTPProtocol, self).ftp_NLST(path) def ftp_PASV(self): d = super(SwftpFTPProtocol, self).ftp_PASV() def dtp_connect_timeout_eb(failure): failure.trap(PortConnectionError) if self.tls_mode: def cb(res): self.dtpInstance.transport.startTLS(self.factory.cert_options) d.addCallback(cb) return d.addErrback(dtp_connect_timeout_eb) def ftp_PORT(self, address): addr = map(int, address.split(',')) ip = '%d.%d.%d.%d' % tuple(addr[:4]) port = addr[4] << 8 | addr[5] # if we have a DTP port set up, lose it. if self.dtpFactory is not None: self.cleanupDTP() self.dtpFactory = DTPFactory(pi=self, peerHost=self.transport.getPeer().host) self.dtpFactory.setTimeout(self.dtpTimeout) self.dtpPort = reactor.connectTCP(ip, port, self.dtpFactory) def connected(ignored): self.reply(ENTERING_PORT_MODE) if self.tls_mode: self.dtpInstance.transport.startTLS(self.factory.cert_options) def connFailed(err): err.trap(PortConnectionError) return CANT_OPEN_DATA_CNX return self.dtpFactory.deferred.addCallbacks(connected, connFailed) def ftp_REST(self, value): if self.dtpInstance is None: raise BadCmdSequenceError('PORT or PASV required before RETR') try: value = int(value) if value < 0: raise ValueError except ValueError: raise CmdArgSyntaxError('Value must be nonnegative integer') else: self.dtpInstance.rest_offset = value return (REQ_FILE_ACTN_PENDING_FURTHER_INFO, ) def cleanupDTP(self): """ Overwrite cleanupDTP() for fix socket leak (see http://twistedmatrix.com/trac/ticket/5367) """ transport = None if self.dtpInstance is not None: if self.dtpInstance.transport is not None: transport = self.dtpInstance.transport super(SwftpFTPProtocol, self).cleanupDTP() if transport: transport.abortConnection()