Python FTPS 在被动模式下挂在目录列表上

Python FTPS hangs on directory list in passive mode

我设法使用 curl 连接到 FTP 服务器并列出了目录 out:

的内容
$ curl -v --insecure --ftp-ssl --user xxx:yyy blabla:990/out/
> AUTH SSL
< 234 Proceed with negotiation.
...
> USER xxx
< 331 Please specify the password.
> PASS yyy
< 230 Login successful.
> PBSZ 0
< 200 PBSZ set to 0.
> PROT P
< 200 PROT now Private.
> PWD
< 257 "/"
> CWD out
< 250 Directory successfully changed.
> EPSV
< 229 Entering Extended Passive Mode (|||51042|).
*   Trying aaa.bbb.ccc.ddd...
* Connecting to aaa.bbb.ccc.ddd (aaa.bbb.ccc.ddd) port 51042
* Connected to blabla (aaa.bbb.ccc.ddd) port 990 (#0)
> TYPE A
< 200 Switching to ASCII mode.
> LIST
< 150 Here comes the directory listing.
* Maxdownload = -1
* Doing the SSL/TLS handshake on the data stream
* SSL re-using session ID
* TLS 1.0 connection using TLS_RSA_WITH_AES_256_CBC_SHA
* Server certificate: blabla
* Server certificate: Symantec Class 3 Secure Server CA - G4
* Server certificate: VeriSign Class 3 Public Primary Certification Authority - G5
{ [539 bytes data]
100   539    0   539    0     0    900      0 --:--:-- --:--:-- --:--:--   899* Remembering we are in dir "out/"
< 226 Directory send OK.

我尝试使用 python(2.7.10 on Mac OS X 10.10.5)

import ftplib

ftps = ftplib.FTP_TLS()
ftps.set_debuglevel(1)
print ftps.set_pasv(True)
print ftps.connect("blabla", 990)
print ftps.login("xxx", "yyy")
print ftps.sendcmd("PBSZ 0")
print ftps.prot_p()
print ftps.pwd()
print ftps.cwd("out")
print ftps.transfercmd("LIST")
ftps.close()

但是 python LIST command 无限期挂起

*cmd* 'AUTH TLS'
*resp* '234 Proceed with negotiation.'
*cmd* 'USER xxx'
*resp* '331 Please specify the password.'
*cmd* 'PASS ********\n'
*resp* '230 Login successful.'
230 Login successful.
*cmd* 'PBSZ 0'
*resp* '200 PBSZ set to 0.'
200 PBSZ set to 0.
*cmd* 'PBSZ 0'
*resp* '200 PBSZ set to 0.'
*cmd* 'PROT P'
*resp* '200 PROT now Private.'
200 PROT now Private.
*cmd* 'PWD'
*resp* '257 "/"'
/
*cmd* 'CWD out'
*resp* '250 Directory successfully changed.'
250 Directory successfully changed.
*cmd* 'PASV'
*resp* '227 Entering Passive Mode (aa,bb,cc,dd,199,97).'

为什么会这样?

更新:好的,我在我的 curl 命令中添加了选项 --disable-epsv,但也失败了。所以 python 无法列出目录的原因是使用了 PASV。我如何强制 Python 改用 EPSV

更新 2:当 PASV 返回的 IP 地址错误时,这个错误 https://github.com/python/cpython/pull/28 似乎会导致 Python ftplib 挂起。这似乎也是我的问题。但是有人知道如何强制 Python 改用 EPSV 的解决方法吗?

通过阅读 ftplib 的源代码,我认为可以通过设置

强制 ftplib 使用 EPSV
ftps.af = socket.AF_INET6

这使得 ftplib 认为我们正在使用 IPv6 连接(即使我们实际上正在使用 IPv4)并使其使用 EPSV 而不是 PASV。我的完整工作程序在最后

import ftplib
import socket

ftps = ftplib.FTP_TLS()

ftps.connect("blabla", 990)
ftps.login("xxx", "yyy")
ftps.prot_p()
ftps.cwd("out")
ftps.af = socket.AF_INET6
ftps.retrlines("LIST")
ftps.quit()