Python FTP隐式TLS连接问题

Rg *_*lpj 14 python ftp ssl ftps ftplib

我需要连接到能够使用lftp成功连接的FTPS服务器.但是,当我尝试使用Python ftplib.FTP_TLS时,它会超时,堆栈跟踪显示它正在等待服务器发送欢迎消息等.有谁知道问题是什么以及如何克服?我想知道是否需要在服务器端进行某些操作,但是lftp客户端如何正常工作.任何帮助是极大的赞赏.

这是堆栈跟踪:

    ftp = ftplib.FTP_TLS()  
    ftp.connect(cfg.HOST, cfg.PORT, timeout=60)
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 135, in connect  
    self.welcome = self.getresp()  
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 210, in getresp  
    resp = self.getmultiline()  
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 196, in getmultiline  
    line = self.getline()  
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 183, in getline  
    line = self.file.readline()  
  File "C:\Users\username\Softwares\Python27\lib\socket.py", line 447, in readline  
    data = self._sock.recv(self._rbufsize)  
socket.timeout: timed out  
Run Code Online (Sandbox Code Playgroud)

使用lftp成功登录到同一个ftps服务器:

$ lftp
lftp :~> open ftps://ip_address:990
lftp ip_address:~> set ftps:initial-prot P
lftp ip_address:~> login ftps_user_id  ftps_user_passwd
lftp sftp_user_id@ip_address:~> ls
ls: Fatal error: SSL_connect: self signed certificate
lftp ftps_user_id@ip_address:~> set ssl:verif-certificate off
lftp ftps_user_id@ip_address:~> ls
lftp ftps_user_id@ip_address:/>
Run Code Online (Sandbox Code Playgroud)

顺便说一句,我使用的是Python 2.7.3.我使用谷歌做了很多搜索,但没有发现任何有用的信息.

我仍然有这个问题,感谢有人可以提供帮助.仔细查看FTP.connect()与服务器的连接不是问题,但从服务器获取确认(或欢迎消息)是一个问题.lftp没有这个问题,FileZilla没有任何问题,就像在这里的日志 -

Status: Connecting to xx.xx.xx.xxx:990...  
Status: Connection established, initializing TLS...  
Status: Verifying certificate...  
Status: TLS/SSL connection established, waiting for welcome message...  
Response:   220-      Vous allez vous connecter sur un serveur prive  
Response:   220-     Seules les personnes habilitees y sont autorisees  
Response:   220 Les contrevenants s'exposent aux poursuites prevues par la loi.  
Command:    USER xxxxxxxxxxxxx  
Response:   331 Password required for xxxxxxxxxxxxx.  
Command:    PASS **********  
Response:   230 Login OK. Proceed.  
Command:    PBSZ 0  
Response:   200 PBSZ Command OK. Protection buffer size set to 0.  
Command:    PROT P  
Response:   200 PROT Command OK. Using Private data connection  
Status: Connected  
Status: Retrieving directory listing...  
Command:    PWD  
Response:   257 "/" is current folder.  
Command:    TYPE I  
Response:   200 Type set to I.  
Command:    PASV  
Response:   227 Entering Passive Mode (81,93,20,199,4,206).  
Command:    MLSD  
Response:   150 Opening BINARY mode data connection for MLSD /.  
Response:   226 Transfer complete. 0 bytes transferred. 0 bps.  
Status: Directory listing successful  
Run Code Online (Sandbox Code Playgroud)

小智 19

我已经为同样的问题工作了半天,最终弄明白了.

对于隐式FTP TLS/SSL(defualt端口990),我们的客户端程序必须在创建套接字后立即构建TLS/SSL连接.但是python的类FTP_TLS不会从类FTP 重新加载connect函数.我们需要修理它:

class tyFTP(ftplib.FTP_TLS):
  def __init__(self,
               host='',
               user='',
               passwd='',
               acct='',
               keyfile=None,
               certfile=None,
               timeout=60):

    ftplib.FTP_TLS.__init__(self,
                            host=host,
                            user=user,
                            passwd=passwd,
                            acct=acct,
                            keyfile=keyfile,
                            certfile=certfile,
                            timeout=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
    try:
        self.sock = socket.create_connection((self.host, self.port), self.timeout)
        self.af = self.sock.family
        # add this line!!!
        self.sock = ssl.wrap_socket(self.sock,
                                    self.keyfile,
                                    self.certfile,
                                    ssl_version=ssl.PROTOCOL_TLSv1)
        # add end
        self.file = self.sock.makefile('rb')
        self.welcome = self.getresp()
    except Exception as e:
        print(e)
    return self.welcome
Run Code Online (Sandbox Code Playgroud)

此派生类重新加载connect函数,并在套接字周围构建一个包装器到TLS.成功连接并登录FTP服务器后,需要FTP_TLS.prot_p()在执行任何FTP命令之前调用:

希望这会有所帮助^ _ ^

  • 这段代码似乎对我不起作用.它可能是FTPS服务器端协议配置问题.但根据这个帖子:http://openssl.6102.n7.nabble.com/error-1408F10B-SSL-routines-SSL3-GET-RECORD-wrong-version-number-td22477.html`ssl_version = ssl.PROTOCOL_TLSv1 `添加的行的一部分是给我"SSLError:[Errno 1] _ssl.c:504:错误:1408F10B:SSL例程:SSL3_GET_RECORD:错误的版本号" (4认同)

小智 14

扩展到目前为止提出的解决方案,问题是隐式FTPS连接需要在我们有机会调用login()之前自动包装套接字.人们提出的很多子类在connect方法的上下文中都这样做,我们通常可以通过修改self.sock的get/set来控制它,并使用一个属性来自动换行set:

import ftplib
import ssl

class ImplicitFTP_TLS(ftplib.FTP_TLS):
    """FTP_TLS subclass that automatically wraps sockets in SSL to support implicit FTPS."""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._sock = None

    @property
    def sock(self):
        """Return the socket."""
        return self._sock

    @sock.setter
    def sock(self, value):
        """When modifying the socket, ensure that it is ssl wrapped."""
        if value is not None and not isinstance(value, ssl.SSLSocket):
            value = self.context.wrap_socket(value)
        self._sock = value
Run Code Online (Sandbox Code Playgroud)

用法与标准FTP_TLS类基本相同:

ftp_client = ImplicitFTP_TLS()
ftp_client.connect(host='ftp.example.com', port=990)
ftp_client.login(user='USERNAME', passwd='PASSWORD')
ftp_client.prot_p()
Run Code Online (Sandbox Code Playgroud)

  • 我收到错误:使用此代码时: ssl.SSLError: [SSL: WRONG_VERSION_NUMBER] 版本号错误 (_ssl.c:1131) 任何建议请 (4认同)
  • 警告:仅限Python3(否则是一个非常好的解决方案) (2认同)
  • 老兄,你真是一个救星。我一直在努力让 ftplib 本身与 TLS/SSL 隐式连接一起工作。这工作完美无缺。 (2认同)

Bra*_*ker 8

扩展NERV的响应 - 这对我帮助很大,这就是我能够通过端口990上需要身份验证的隐式TLS连接来解决我的问题.

文件名:ImplicitTLS.py

from ftplib import FTP_TLS
import socket
import ssl

class tyFTP(FTP_TLS):
    def __init__(self, host='', user='', passwd='', acct='', keyfile=None, certfile=None, timeout=60):
        FTP_TLS.__init__(self, host, user, passwd, acct, keyfile, certfile, timeout)
    def connect(self, host='', port=0, timeout=-999):
        if host != '':
            self.host = host
        if port > 0:
            self.port = port
        if timeout != -999:
            self.timeout = timeout

        try: 
            self.sock = socket.create_connection((self.host, self.port), self.timeout)
            self.af = self.sock.family
            self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile, ssl_version=ssl.PROTOCOL_TLSv1)
            self.file = self.sock.makefile('rb')
            self.welcome = self.getresp()
        except Exception as e:
            print e
        return self.welcome
Run Code Online (Sandbox Code Playgroud)

然后从我的主应用程序中我做了这个:

from ImplicityTLS import tyFTP
server = tyFTP()
server.connect(host="xxxxx", port=990)
server.login(user="yyyy", passwd="fffff")
server.prot_p()
Run Code Online (Sandbox Code Playgroud)

就是这样,我能够下载文件等.道具去NERV寻找原始答案.


小智 6

NERV 的回答和 Brad Decker 的示例真的很有帮助。为他们点赞。他们为我节省了几个小时。

不幸的是,最初它对我不起作用。

就我而言,一旦我ssl_versionssl.wrap_socket方法中删除了参数,连接就起作用了。此外,要将任何命令发送到服务器,我必须覆盖类中的ntransfercmd方法FTP_TLS并删除ssl_version那里的参数。

这是对我有用的代码:

from ftplib import FTP_TLS, FTP
import socket
import ssl

class IMPLICIT_FTP_TLS(FTP_TLS):
    def __init__(self, host='', user='', passwd='', acct='', keyfile=None,
        certfile=None, timeout=60):
        FTP_TLS.__init__(self, host, user, passwd, acct, keyfile, certfile, 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
        try:
            self.sock = socket.create_connection((self.host, self.port), self.timeout)
            self.af = self.sock.family
            self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
            self.file = self.sock.makefile('rb')
            self.welcome = self.getresp()
        except Exception as e:
            print (e)
        return self.welcome

    def ntransfercmd(self, cmd, rest=None):
        conn, size = FTP.ntransfercmd(self, cmd, rest)
        if self._prot_p:
            conn = ssl.wrap_socket(conn, self.keyfile, self.certfile)
        return conn, size
Run Code Online (Sandbox Code Playgroud)

和强制性样本:

>>> ftps = IMPLICIT_FTP_TLS()
>>> ftps.connect(host='your.ftp.host', port=990)
>>> ftps.login(user="your_user", passwd="your_passwd")
>>> ftps.prot_p()
>>> ftps.retrlines('LIST')
Run Code Online (Sandbox Code Playgroud)


pro*_*lad 5

通过TLS的隐式FTP(具有被动传输模式)

这里的实现有点“工业化”。

让我们注意到,在前面的示例中,init中缺少名为“ context”的属性。

下面的代码与Python 2.7和Python 3都可以完美地工作

代码

import ftplib, socket, ssl
FTPTLS_OBJ = ftplib.FTP_TLS

# Class to manage implicit FTP over TLS connections, with passive transfer mode
# - Important note:
#   If you connect to a VSFTPD server, check that the vsftpd.conf file contains
#   the property require_ssl_reuse=NO
class FTPTLS(FTPTLS_OBJ):

    host = "127.0.0.1"
    port = 990
    user = "anonymous"
    timeout = 60

    logLevel = 0

    # Init both this and super
    def __init__(self, host=None, user=None, passwd=None, acct=None, keyfile=None, certfile=None, context=None, timeout=60):        
        FTPTLS_OBJ.__init__(self, host, user, passwd, acct, keyfile, certfile, context, timeout)

    # Custom function: Open a new FTPS session (both connection & login)
    def openSession(self, host="127.0.0.1", port=990, user="anonymous", password=None, timeout=60):
        self.user = user
        # connect()
        ret = self.connect(host, port, timeout)
        # prot_p(): Set up secure data connection.
        try:
            ret = self.prot_p()
            if (self.logLevel > 1): self._log("INFO - FTPS prot_p() done: " + ret)
        except Exception as e:
            if (self.logLevel > 0): self._log("ERROR - FTPS prot_p() failed - " + str(e))
            raise e
        # login()
        try:
            ret = self.login(user=user, passwd=password)
            if (self.logLevel > 1): self._log("INFO - FTPS login() done: " + ret)
        except Exception as e:
            if (self.logLevel > 0): self._log("ERROR - FTPS login() failed - " + str(e))
            raise e
        if (self.logLevel > 1): self._log("INFO - FTPS session successfully opened")

    # Override function
    def connect(self, host="127.0.0.1", port=990, timeout=60):
        self.host = host
        self.port = port
        self.timeout = timeout
        try:
            self.sock = socket.create_connection((self.host, self.port), self.timeout)
            self.af = self.sock.family
            self.sock = ssl.wrap_socket(self.sock, self.keyfile, self.certfile)
            self.file = self.sock.makefile('r')
            self.welcome = self.getresp()
            if (self.logLevel > 1): self._log("INFO - FTPS connect() done: " + self.welcome)
        except Exception as e:
            if (self.logLevel > 0): self._log("ERROR - FTPS connect() failed - " + str(e))
            raise e
        return self.welcome

    # Override function
    def makepasv(self):
        host, port = FTPTLS_OBJ.makepasv(self)
        # Change the host back to the original IP that was used for the connection
        host = socket.gethostbyname(self.host)
        return host, port

    # Custom function: Close the session
    def closeSession(self):
        try:
            self.close()
            if (self.logLevel > 1): self._log("INFO - FTPS close() done")
        except Exception as e:
            if (self.logLevel > 0): self._log("ERROR - FTPS close() failed - " + str(e))
            raise e
        if (self.logLevel > 1): self._log("INFO - FTPS session successfully closed")

    # Private method for logs
    def _log(self, msg):
        # Be free here on how to implement your own way to redirect logs (e.g: to a console, to a file, etc.)
        print(msg)
Run Code Online (Sandbox Code Playgroud)

使用范例

host = "www.myserver.com"
port = 990
user = "myUserId"
password = "myPassword"

myFtps = FTPTLS()
myFtps.logLevel = 2
myFtps.openSession(host, port, user, password)
print(myFtps.retrlines("LIST"))
myFtps.closeSession()
Run Code Online (Sandbox Code Playgroud)

输出示例

INFO - FTPS connect() done: 220 (vsFTPd 3.0.2)
INFO - FTPS prot_p() done: 200 PROT now Private.
INFO - FTPS login() done: 230 Login successful.
INFO - FTPS session successfully opened
-rw-------    1 ftp      ftp         86735 Mar 22 16:55 MyModel.yaml
-rw-------    1 ftp      ftp          9298 Mar 22 16:55 MyData.csv
226 Directory send OK.
INFO - FTPS close() done
INFO - FTPS session successfully closed
Run Code Online (Sandbox Code Playgroud)