【问题标题】:Getting a OSError when trying to LIST ftp directories in Python尝试在 Python 中列出 ftp 目录时出现 OSError
【发布时间】:2018-03-19 21:37:21
【问题描述】:

使用 Python 3.6.3。

我正在通过 FTP_TLS 连接到 FTP 以使用 ftplib

ftps = ftplib.FTP_TLS('example.com', timeout=5)

# TLS is more secure than SSL
ftps.ssl_version = ssl.PROTOCOL_TLS

# login before securing control channel
ftps.login('username@example.com', 'password')

# switch to secure data connection
ftps.prot_p()

# Explicitly set Passive mode
ftps.set_pasv(True)

这一切都有效。我可以发送ftps.mkd('mydir')(创建目录)和ftps.cwd('mydir')(更改工作目录),都可以正常工作。

但如果我发送任何这些(据我所知,它们基本上都是同义词):

        ftps.dir()
        ftps.nlst()
        ftps.retrlines('LIST')
        ftps.retrlines('MLSD')

然后我得到一个异常(下面还包括所有 ftplib 调试信息;通常也与 FileZilla 显示的内容相匹配):

*cmd* 'AUTH TLS'
*put* 'AUTH TLS\r\n'
*get* '234 AUTH TLS OK.\n'
*resp* '234 AUTH TLS OK.'
*cmd* 'USER username@example.com'
*put* 'USER username@example.com\r\n'
*get* '331 User username@example.com OK. Password required\n'
*resp* '331 User username@example.com OK. Password required'
*cmd* 'PASS ******************************'
*put* 'PASS ******************************\r\n'
*get* '230 OK. Current restricted directory is /\n'
*resp* '230 OK. Current restricted directory is /'
*cmd* 'PBSZ 0'
*put* 'PBSZ 0\r\n'
*get* '200 PBSZ=0\n'
*resp* '200 PBSZ=0'
*cmd* 'PROT P'
*put* 'PROT P\r\n'
*get* '200 Data protection level set to "private"\n'
*resp* '200 Data protection level set to "private"'
*cmd* 'MKD mydir'
*put* 'MKD mydir\r\n'
*get* '257 "mydir" : The directory was successfully created\n'
*resp* '257 "mydir" : The directory was successfully created'
*cmd* 'CWD mydir'
*put* 'CWD mydir\r\n'
*get* '250 OK. Current directory is /mydir\n'
*resp* '250 OK. Current directory is /mydir'
*cmd* 'TYPE A'
*put* 'TYPE A\r\n'
*get* '200 TYPE is now ASCII\n'
*resp* '200 TYPE is now ASCII'
*cmd* 'PASV'
*put* 'PASV\r\n'
*get* '227 Entering Passive Mode (8,8,8,8,8,8)\n'
*resp* '227 Entering Passive Mode (8,8,8,8,8,8)'
*cmd* 'MLSD'
*put* 'MLSD\r\n'
*get* '150 Accepted data connection\n'
*resp* '150 Accepted data connection'
Traceback (most recent call last):
  File "c:\my_script.py", line 384, in run_ftps
    ftps.retrlines('MLSD')
  File "c:\libs\Python36\lib\ftplib.py", line 485, in retrlines
    conn.unwrap()
  File "C:\libs\Python36\lib\ssl.py", line 1051, in unwrap
    s = self._sslobj.unwrap()
  File "C:\libs\Python36\lib\ssl.py", line 698, in unwrap
    return self._sslobj.shutdown()
OSError: [Errno 0] Error

同样的 FTP 命令 (LIST) 通过 filezilla 可以正常工作。

我可以通过谷歌搜索找到的最接近的内容是:https://bugs.python.org/msg253161 - 我不确定它是否相关或相关。

短版:“OSError: [Errno 0] Error”究竟是什么意思,如何列出我的目录内容?

编辑:这个问题似乎只发生在 FTP_TLS 上。通过普通的 FTP 连接可以正常工作,但我需要 FTP_TLS。

【问题讨论】:

    标签: python python-3.x ftp ftplib ftps


    【解决方案1】:
        s = self._sslobj.unwrap()
      File "E:\Software\_libs\Python36\lib\ssl.py", line 698, in unwrap
        return self._sslobj.shutdown()
    OSError: [Errno 0] Error
    

    如果服务器在没有正确关闭 TLS 的情况下关闭 TCP 连接,则会发生此错误。 OSError: [Errno 0] 表示底层 TCP 套接字实际上根本没有错误,即服务器正常关闭了 TCP 套接字。只是,服务器在此之前没有执行所需的 TLS 级别关闭。

    这在我看来就像服务器实现中的一个错误,但也许这也是导致此问题的配置选项。而且,面对这种不正确的协议实现,python ftplib 的容忍度似乎不如其他客户端。但是您可以通过搜索"Server did not properly shut down TLS connection"找到与其他客户类似的报告。

    解决方法可能是将 ftplib.py 中的 retrlinesretrbinary 中的 conn.unwrap() 放入 try...except 语句中,然后忽略错误。

    【讨论】:

      【解决方案2】:

      问题可能是FTP服务器要求新数据通道中的TLS会话与控制通道相同。这在 Python 3.7 中尚未修复。 ftplib.FTP_TLS 的子类,如在此处找到的解决方案 https://stackoverflow.com/a/43301750 中,我做了一个小修复:

      import ftplib
      from ssl import SSLSocket
      
      
      class ReusedSslSocket(SSLSocket):
          def unwrap(self):
              pass
      
      
      class MyFTP_TLS(ftplib.FTP_TLS):
          """Explicit FTPS, with shared TLS session"""
          def ntransfercmd(self, cmd, rest=None):
              conn, size = ftplib.FTP.ntransfercmd(self, cmd, rest)
              if self._prot_p:
                  conn = self.context.wrap_socket(conn,
                                                  server_hostname=self.host,
                                                  session=self.sock.session)  # reuses TLS session            
                  conn.__class__ = ReusedSslSocket  # we should not close reused ssl socket when file transfers finish
              return conn, size
      

      像这样使用它:

      ftps = MyFTP_TLS('example.com', timeout=5)
      

      【讨论】:

      • 为我工作。您应该在开头添加from ssl import SSLSocket
      • @bct 谢谢。我添加了缺少的导入。
      【解决方案3】:

      我今天遇到了这个问题,想在这个解决方案中添加一点。

      我需要让 makepasv 返回主机而不是防火墙后面的 FTP 服务器的内部 IP 地址。此 passv 修复与 TLS 会话修复一起解决了我的问题。

      class ReusedSslSocket(SSLSocket):
          def unwrap(self):
              pass
      class FTP_TLS_IgnoreHost(FTP_TLS):
          def makepasv(self):
              _, port = super().makepasv()
              return self.host, port
          """Explicit FTPS, with shared TLS session"""
          def ntransfercmd(self, cmd, rest=None):
              conn, size = FTP.ntransfercmd(self, cmd, rest)
              if self._prot_p:
                  conn = self.context.wrap_socket(conn,
                            server_hostname=self.host,
                            session=self.sock.session)  # reuses TLS session            
                  conn.__class__ = ReusedSslSocket  # we should not close reused ssl socket when file transfers finish
              return conn, size
      
      ftp = FTP_TLS_IgnoreHost(host, timeout=5)
      ftp.ssl_version = ssl.PROTOCOL_TLS
      ftp.auth()
      ftp.login(user,passwd)
      ftp.prot_p()
      ftp.set_pasv(True)
      pwd = ftp.pwd()
      print(pwd)
      print(ftp.nlst())
      ftp.close()
      

      【讨论】:

        猜你喜欢
        • 2016-02-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-04
        • 1970-01-01
        • 2021-03-15
        • 2019-01-14
        相关资源
        最近更新 更多