使用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。
答案 0 :(得分: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)
答案 1 :(得分: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"找到与其他客户类似的报告。
修复可能是将conn.unwrap()
置于ftpib.py中的retrlines
和retrbinary
放入try ... except语句中,然后忽略该错误。
答案 2 :(得分:0)
我今天遇到了这个问题,想在此解决方案中添加一些内容。
我需要让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()