成功上传后,FTPS上传会引发SSLError

时间:2017-11-18 20:57:22

标签: python ssl ftplib ftps

我有一个小代码将文件上传到FTPS服务器(不,SFTP不是一个选项:()。但是成功上传后会引发SSLError。

from ftplib import FTP_TLS, parse227, parse229
import io
import os
import socket
import six


def ensure_binary(value):
    if isinstance(value, six.text_type):
        value = value.encode(encoding='utf-8')
    return value


class FTP_TLS_Host(FTP_TLS):
    """FTP_TLS class that ignores host from FTP server."""

    def makepasv(self):
        """Set passive command, but ignore host from ftp response."""
        if self.af == socket.AF_INET:
            __, port = parse227(self.sendcmd('PASV'))
            host = self.host
        else:
            host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
        return host, port


class FTPSClient(object):

    def __init__(self, username, password, hostname, port=990):
        self.username = username
        self.password = password
        self.hostname = hostname
        self.port = port

    def get_conn(self, ftp_class=FTP_TLS_Host):
        conn = ftp_class(timeout=10)
        conn.connect(self.hostname, self.port)
        conn.login(self.username, self.password)
        conn.prot_p()
        return conn

    def upload(self, content, filename_server):
        ftps = self.get_conn()
        binary = io.BytesIO(ensure_binary(content))
        ftps.storbinary('STOR %s' % filename_server, binary)


ftps_client = FTPSClient(
    username=os.environ['FTPS_USERNAME'],
    password=os.environ['FTPS_PASSWORD'],
    hostname=os.environ['FTPS_HOST'],
    port=989,
)
ftps_client.upload('content', 'test.txt')

这是追溯:

File "/test.py", line 54, in <module>
    ftps_client.upload('content', 'test.txt')
  File "test.py", line 45, in upload
    ftps.storbinary('STOR %s' % filename_server, binary)
  File "/2.7/lib/python2.7/ftplib.py", line 769, in storbinary
    conn.unwrap()
  File "/2.7/lib/python2.7/ssl.py", line 823, in unwrap
    s = self._sslobj.shutdown()
ssl.SSLError: ('The read operation timed out',)

如何在不收到SSLError的情况下上传?

1 个答案:

答案 0 :(得分:2)

生殖

创建端口21,目标文件名dir/test.txt。将您的代码放入client.py

openssl req -new -x509 -subj "/CN=ftps-test" -nodes \
  -out vsftpd.crt -keyout vsftpd.key
docker run --name=ftps-test -d -e FTP_USER=user -e FTP_PASSWORD=pass \
  -v `pwd`/vsftpd.crt:/etc/ssl/certs/vsftpd.crt:ro \
  -v `pwd`/vsftpd.key:/etc/ssl/private/vsftpd.key:ro \
  panubo/vsftpd vsftpd /etc/vsftpd_ssl.conf
docker exec ftps-test \
  bash -c 'mkdir /srv/dir && chown ftp /srv/dir'

export FTPS_USERNAME=user
export FTPS_PASSWORD=pass
export FTPS_HOST=$(docker inspect \
  --format '{{ .NetworkSettings.IPAddress }}' ftps-test)
echo "ftps-test $FTPS_HOST" > hosts
export HOSTALIASES=`pwd`/hosts

curl -v --ssl-reqd ftp://ftps-test/ --cacert vsftpd.crt --user user:pass
# Python 2 works as well
python3 client.py

docker exec ftps-test cat /srv/dir/test.txt
docker stop ftps-test && docker rm -v ftps-test

这是输出:

Generating a 2048 bit RSA private key
..............+++
.................................................................+++
writing new private key to 'vsftpd.key'
-----
fe3ae61591747e3e6acb1da6c14d3c66489f7ce5eed2b836c0463d3342b2a739
*   Trying 172.17.0.2...
* Connected to ftps-test (172.17.0.2) port 21 (#0)
< 220 (vsFTPd 3.0.2)
> AUTH SSL
< 234 Proceed with negotiation.
* found 1 certificates in vsftpd.crt
* found 604 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / RSA_AES_128_GCM_SHA256
*    server certificate verification OK
*    server certificate status verification SKIPPED
*    common name: ftps-test (matched)
*    server certificate expiration date OK
*    server certificate activation date OK
*    certificate public key: RSA
*    certificate version: #3
*    subject: CN=ftps-test
*    start date: Fri, 24 Nov 2017 21:39:54 GMT
*    expire date: Sun, 24 Dec 2017 21:39:54 GMT
*    issuer: CN=ftps-test
*    compression: NULL
* ALPN, server did not agree to a protocol
> USER user
< 331 Please specify the password.
> PASS pass
< 230 Login successful.
> PBSZ 0
< 200 PBSZ set to 0.
> PROT P
< 200 PROT now Private.
> PWD
< 257 "/"
* Entry path is '/'
> EPSV
* Connect data stream passively
* ftp_perform ends with SECONDARY: 0
< 229 Entering Extended Passive Mode (|||4560|).
*   Trying 172.17.0.2...
* Connecting to 172.17.0.2 (172.17.0.2) port 4560
* Connected to ftps-test (172.17.0.2) port 21 (#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
* found 1 certificates in vsftpd.crt
* found 604 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL re-using session ID
* SSL connection using TLS1.2 / RSA_AES_128_GCM_SHA256
*    server certificate verification OK
*    server certificate status verification SKIPPED
*    common name: ftps-test (matched)
*    server certificate expiration date OK
*    server certificate activation date OK
*    certificate public key: RSA
*    certificate version: #3
*    subject: CN=ftps-test
*    start date: Fri, 24 Nov 2017 21:39:54 GMT
*    expire date: Sun, 24 Dec 2017 21:39:54 GMT
*    issuer: CN=ftps-test
*    compression: NULL
* ALPN, server did not agree to a protocol
drwxr-xr-x    2 ftp      ftp          4096 Nov 24 21:39 dir
* Remembering we are in dir ""
< 226 Directory send OK.
* Connection #0 to host ftps-test left intact
contentftps-test
ftps-test

结论

这样的代码是可以的。问题可能在于您的服务器配置。