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
私は半日同じ問題に取り組み、ついにそれを理解しました。
暗黙的な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()
!
これがお役に立てば幸いです^ _ ^
これまでに提案されたソリューションを拡張すると、問題は、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()
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に行きます。
これはもう少し「産業的」な実装です。
前の例では、「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
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')
暗黙の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
をオーバーライドすることもできます。
私はこのスレッドがかなり古く、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)
.
.
.