web-dev-qa-db-ja.com

Python FTPの暗黙的なTLS接続の問題

Lftpを使用して正常に接続できるFTPSサーバーに接続する必要があります。ただし、Python ftplib.FTP_TLSを試してみると、タイムアウトになり、サーバーがウェルカムメッセージなどを送信するのを待機していることがスタックトレースに示されます。問題が何であるかを誰かが知っていますか。サーバー側で何かする必要があるのではないかと思いますが、どうしてlftpクライアントが正常に動作するのでしょうか。助けていただければ幸いです。

スタックトレースは次のとおりです。

    ftp = ftplib.FTP_TLS()  
    ftp.connect(cfg.Host, cfg.PORT, timeout=60)
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 135, in connect  
    self.welcome = self.getresp()  
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 210, in getresp  
    resp = self.getmultiline()  
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 196, in getmultiline  
    line = self.getline()  
  File "C:\Users\username\Softwares\Python27\lib\ftplib.py", line 183, in getline  
    line = self.file.readline()  
  File "C:\Users\username\Softwares\Python27\lib\socket.py", line 447, in readline  
    data = self._sock.recv(self._rbufsize)  
socket.timeout: timed out  

同じftpsサーバーへのlftpを使用したログインの成功:

$ 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  
16
Rg Glpj

私は半日同じ問題に取り組み、ついにそれを理解しました。

暗黙的なFTPTLS/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
_

この派生クラスはconnect関数をリロードし、ソケットのラッパーをTLSに構築します。 FTPサーバーに正常に接続してログインした後、FTPコマンドを実行する前に、次のコマンドを呼び出す必要があります。FTP_TLS.prot_p()

これがお役に立てば幸いです^ _ ^

20
tandztc

これまでに提案されたソリューションを拡張すると、問題は、login()を呼び出す機会を得る前に、暗黙のFTPS接続でソケットを自動的にSSLラップする必要があることです。人々が提案しているサブクラスの多くは、connectメソッドのコンテキストでこれを実行します。より一般的には、self.sockのget/setをプロパティで変更して、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()
16

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に行きます。

8
Brad Decker

パッシブ転送モードを使用した暗黙的なFTPover TLS

これはもう少し「産業的」な実装です。

前の例では、「context」という名前のプロパティがinitにないことに注意してください。

以下のコードはPython 2.7とPythonの両方で完全に機能します

コード

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
6
prossblad

NERVによる回答とBradDeckerによるサンプルは本当に役に立ちました。彼らへの称賛。彼らは私に時間を節約しました。

残念ながら、最初はうまくいきませんでした。

私の場合、ssl_versionメソッドからssl.wrap_socketパラメーターを削除すると、接続は機能しました。また、サーバーにコマンドを送信するには、FTP_TLSクラスからntransfercmdメソッドを上書きし、そこでも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')
4
Juan Moreno

暗黙のTLSを使用するFTPサーバーでも同じ問題が発生しました(これもパッシブモードです)。複数のエラーが発生しました:

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

提案されたソリューションで。ちなみに、@ GeorgeLeslie-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をオーバーライドすることもできます。

0
gaFF

私はこのスレッドがかなり古く、ftpが以前ほど人気が​​ないことを知っていますが、とにかく、それが私が追加の貢献をしたい人を助ける場合に備えて。パッシブモードでIMPLICIT(ポート990)ftpを使用してftpサーバーに接続しようとすると、同様の状況が発生しました。そのような状況では、サーバーは、最初の接続がネゴシエートされた後、通常、新しいホストIPアドレスとポートを提供します。これは、実際のデータ転送が行われることになっている最初の接続に使用されたものとは異なる可能性があります。大したことではありません。Pythonを含むftpsクライアントはこれを処理できます。この特定のサーバーだけが、ルーティング不可能な(ファイアウォールの内部にある可能性が高い)IPアドレスを提供していました。 FileZillaが問題なく接続されていることに気づきましたが、python ftplibは接続できませんでした。その後、次のスレッドに遭遇しました。

ルーティング不可能なIPアドレスをftplibのサーバーアドレスに置き換える方法

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)
     .
     .
     .
0
andrew-3D