Python FTP隐式TLS连接问题

2025-03-21 09:06:00
admin
原创
30
摘要:问题描述:我需要连接到 FTPS 服务器,我可以使用 lftp 成功连接到该服务器。但是,当我尝试使用 Python ftplib.FTP_TLS 时,它超时了,堆栈跟踪显示它正在等待服务器发送欢迎消息或类似消息。有人知道问题是什么以及如何克服吗?我想知道服务器端是否需要做些什么,但是为什么 lftp 客户端...

问题描述:

我需要连接到 FTPS 服务器,我可以使用 lftp 成功连接到该服务器。但是,当我尝试使用 Python ftplib.FTP_TLS 时,它超时了,堆栈跟踪显示它正在等待服务器发送欢迎消息或类似消息。有人知道问题是什么以及如何克服吗?我想知道服务器端是否需要做些什么,但是为什么 lftp 客户端工作正常。任何帮助都非常感谢。

以下是堆栈跟踪:

    ftp = ftplib.FTP_TLS()  
    ftp.connect(cfg.HOST, cfg.PORT, timeout=60)
  File "C:Users/usernameSoftwaresPython27libtplib.py", line 135, in connect  
    self.welcome = self.getresp()  
  File "C:Users/usernameSoftwaresPython27libtplib.py", line 210, in getresp  
    resp = self.getmultiline()  
  File "C:Users/usernameSoftwaresPython27libtplib.py", line 196, in getmultiline  
    line = self.getline()  
  File "C:Users/usernameSoftwaresPython27libtplib.py", line 183, in getline  
    line = self.file.readline()  
  File "C:Users/usernameSoftwaresPython27libsocket.py", line 447, in readline  
    data = self._sock.recv(self._rbufsize)  
socket.timeout: timed out  

使用 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:/>

顺便说一句,我使用的是 Python 2.7.3。我使用 Google 进行了大量搜索,但没有找到任何有用的信息。

我仍然遇到这个问题,如果有人能帮助我将不胜感激。仔细查看 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  

解决方案 1:

扩展迄今为止提出的解决方案,问题是隐式 FTPS 连接需要自动对套接字进行 ssl 包装,然后我们才有机会调用 login()。人们提出的许多子类都是在 connect 方法的上下文中执行此操作的,我们可以更普遍地通过修改 self.sock 的 get/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

使用方法基本与标准 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()

解决方案 2:

我为同样的问题研究了半天,终于弄明白了。

对于隐式 FTP TLS/SSL(默认端口 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

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

希望这会有所帮助^_^

解决方案 3:

这是一个更加“工业化”的实现。

我们注意到,在前面的例子中, 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)

使用示例

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()

输出示例

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

解决方案 4:

扩展 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

然后从我的主应用程序中执行以下操作:

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

就是这样,我可以下载文件等等。感谢 NERV 提供原始答案。

解决方案 5:

NERV 的回答和 Brad Decker 的样本确实很有帮助。非常感谢他们。他们为我节省了好几个小时。

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

ssl_version就我的情况而言,一旦我从方法中删除了参数,连接就会正常工作ssl.wrap_socket。此外,要向服务器发送任何命令,我必须ntransfercmdFTP_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

以下是必填示例:

>>> 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')

解决方案 6:

我在使用隐式 TLS(也是被动模式)的 FTP 服务器中也遇到了同样的问题。我遇到了多个错误:

450 TLS session of data connection has not resumed or the session does not match the control connection

以及建议的解决方案。根据@George Leslie-Waksman 的记录,我的解决方案是:

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

    def __init__(self, *args, **kwargs):
        """Initialise self."""
        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

    def ntransfercmd(self, cmd, rest=None):
        """Override the ntransfercmd method"""
        conn, size = ftplib.FTP.ntransfercmd(self, cmd, rest)
        conn = self.sock.context.wrap_socket(
            conn, server_hostname=self.host, session=self.sock.session
        )
        return conn, size

新颖之处在于ntransfercmd覆盖。

然后它的用法类似于ftplib.FTP_TLS(以前的文章中有很多例子)。

覆盖connect而不是使用sock属性/设置器也是可行的。

解决方案 7:

我知道这个帖子已经很老了,ftp 也不像以前那么流行了,但无论如何,如果它能帮助到任何人,我愿意做出额外的贡献。我遇到了类似的情况,尝试使用被动模式下的 IMPLICIT(端口 990)ftps 连接到 ftp 服务器。在这种情况下,服务器在协商初始连接后,通常会提供一个新的主机 IP 地址和端口,可能与用于建立初始连接的 IP 地址和端口不同,实际的数据传输应该通过这些地址和端口进行。没什么大不了的,ftps 客户端(包括 python)可以处理这个问题,只有这个特定的服务器提供了一个不可路由的(可能是防火墙内部的)IP 地址。我注意到 FileZilla 连接没有问题,但 python ftplib 不能。然后我遇到了这个帖子:

如何在 ftplib 上用服务器地址替换不可路由的 IP 地址

这给了我线索。使用 Grzegorz Wierzowiecki 方法,我扩展了该线程中提到的方法并得出了这个结果,解决了我的问题。

import ftplib, os, sys
import socket
import ssl
FTPS_OBJ = ftplib.FTP_TLS



def conn_i_ftps(FTP_Site, Login_Name, Login_Password):
    print "Starting IMPLICIT ftp_tls..."
    ftps = tyFTP()
    print ftps.connect(host=FTP_Site, port=990, timeout=120)
    ftps.prot_p()
    ftps.login(user=Login_Name, passwd=Login_Password)
    print "Logged In"
    ftps.retrlines('LIST')
    # return ftps


class tyFTP(FTPS_OBJ):
    def __init__(self, host='', user='', passwd='', acct='', keyfile=None, certfile=None, timeout=60):
        FTPS_OBJ.__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)
            self.file = self.sock.makefile('rb')
            self.welcome = self.getresp()
        except Exception as e:
            print e
        return self.welcome

    def makepasv(self):
        print port #<---Show passively assigned port
        print host #<---Show the non-routable, passively assigned IP
        host, port = FTPS_OBJ.makepasv(self)
        host = socket.gethostbyname(self.host) #<---- This changes the host back to the original IP that was used for the connection
        print 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
        print host #<----Showing the original IP
        return host, port

然后代码被像这样调用

FTP_Site       =  "ftp.someserver.com"
Login_Name     =  "some_name"
Login_Password =  "some_passwd"

conn_i_ftps(FTP_Site, Login_Name, Login_Password)

我想人们可以在主机改回的行中使用 IF 语句来标识不可路由的地址,就像这样:

if host.split(".")[0] in (10, 192, 172):
     host = socket.gethostbyname(self.host)
     .
     .
     .
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   2482  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1533  
  PLM(产品生命周期管理)项目对于企业优化产品研发流程、提升产品质量以及增强市场竞争力具有至关重要的意义。然而,在项目推进过程中,范围蔓延是一个常见且棘手的问题,它可能导致项目进度延迟、成本超支以及质量下降等一系列不良后果。因此,有效避免PLM项目范围蔓延成为项目成功的关键因素之一。以下将详细阐述三大管控策略,助力企业...
plm系统   0  
  PLM(产品生命周期管理)项目管理在企业产品研发与管理过程中扮演着至关重要的角色。随着市场竞争的加剧和产品复杂度的提升,PLM项目面临着诸多风险。准确量化风险优先级并采取有效措施应对,是确保项目成功的关键。五维评估矩阵作为一种有效的风险评估工具,能帮助项目管理者全面、系统地评估风险,为决策提供有力支持。五维评估矩阵概述...
免费plm软件   0  
  引言PLM(产品生命周期管理)开发流程对于企业产品的全生命周期管控至关重要。它涵盖了从产品概念设计到退役的各个阶段,直接影响着产品质量、开发周期以及企业的市场竞争力。在当今快速发展的科技环境下,客户对产品质量的要求日益提高,市场竞争也愈发激烈,这就使得优化PLM开发流程成为企业的必然选择。缺陷管理工具和六西格玛方法作为...
plm产品全生命周期管理   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用