Skip to content

Commit c14525d

Browse files
committed
Initial commit.
0 parents  commit c14525d

File tree

1 file changed

+143
-0
lines changed

1 file changed

+143
-0
lines changed

ftplibtls.py

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Copyright (c) Quectel Wireless Solution, Co., Ltd.All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# -*- coding: utf-8 -*-
16+
"""An FTP subclass which adds FTP-over-TLS support as described in RFC-4217.
17+
18+
Example with FTP-over-TLS. Note that you must call the ``prot_b`` method after
19+
connecting and authentication to actually establish secure communication for
20+
data transfers::
21+
22+
>>> from ftplibtls import FTP_TLS
23+
>>> ftp = FTP_TLS('servername') # default port 21
24+
>>> ftp.login('username', 'password')
25+
>>> ftp.prot_p()
26+
>>> ftp.retrlines('LIST')
27+
28+
"""
29+
30+
# Based on CPython standard library implementation adapted for MicroPython
31+
32+
try:
33+
import _ssl
34+
except ImportError:
35+
import ussl as _ssl
36+
37+
try:
38+
import socket as _socket
39+
except ImportError:
40+
import usocket as _socket
41+
42+
import ftplib
43+
44+
45+
class FTP_TLS(ftplib.FTP):
46+
"""An FTP subclass which adds FTP-over-TLS support as described in RFC-4217.
47+
48+
Connect as usual to port 21, implicitly securing the FTP control connection
49+
before authenticating.
50+
51+
Securing the data connection requires the user to explicitly ask for it by
52+
calling the ``prot_p()`` method.
53+
54+
The ``ssl`` module of the ESP8266 port of MicroPython does not support
55+
certficate validation, so the following instantiation argument is
56+
ignored:
57+
58+
* ``cert_reqs``
59+
60+
See the module docstring for a usage example.
61+
62+
"""
63+
64+
def __init__(self, host=None, port=None, user=None, passwd=None, acct=None,
65+
keyfile=None, certfile=None, cert_reqs=None,
66+
timeout=ftplib._GLOBAL_DEFAULT_TIMEOUT, source_address=None, ipvtype = 0):
67+
self._prot_p = False
68+
self.keyfile = keyfile
69+
self.certfile = certfile
70+
self._keydata = None
71+
self._certdata = None
72+
super().__init__(host, port, user, passwd, acct, timeout, source_address, ipvtype = ipvtype)
73+
74+
def login(self, user=None, passwd=None, acct=None, secure=True):
75+
if secure and isinstance(self.sock._sock, _socket.socket):
76+
self.auth()
77+
78+
return super().login(user, passwd, acct)
79+
80+
def auth(self):
81+
"""Set up secure control connection by using TLS/SSL."""
82+
if not isinstance(self.sock._sock, _socket.socket):
83+
raise ValueError("Already using TLS")
84+
85+
resp = self.voidcmd('AUTH TLS')
86+
self.sock._sock = self._wrap_socket(self.sock._sock)
87+
self.file = self.sock._sock
88+
return resp
89+
90+
def ccc(self):
91+
"""Switch back to a clear-text control connection."""
92+
if isinstance(self.sock._sock, _socket.socket):
93+
raise ValueError("Not using TLS")
94+
95+
resp = self.voidcmd('CCC')
96+
self.sock._sock = self.sock.unwrap()
97+
self.file = self.sock._sock
98+
return resp
99+
100+
def prot_p(self):
101+
"""Set up secure data connection."""
102+
# PROT defines whether or not the data channel is to be protected.
103+
# Though RFC-2228 defines four possible protection levels,
104+
# RFC-4217 only recommends two, Clear and Private.
105+
# Clear (PROT C) means that no security is to be used on the
106+
# data-channel, Private (PROT P) means that the data-channel
107+
# should be protected by TLS.
108+
# PBSZ command MUST still be issued, but must have a parameter of
109+
# '0' to indicate that no buffering is taking place and the data
110+
# connection should not be encapsulated.
111+
self.voidcmd('PBSZ 0')
112+
resp = self.voidcmd('PROT P')
113+
self._prot_p = True
114+
return resp
115+
116+
def prot_c(self):
117+
"""Set up clear text data connection."""
118+
resp = self.voidcmd('PROT C')
119+
self._prot_p = False
120+
return resp
121+
122+
# Overridden FTP methods
123+
124+
def ntransfercmd(self, cmd, rest=None):
125+
conn, size = super().ntransfercmd(cmd, rest)
126+
127+
if self._prot_p:
128+
conn._sock = self._wrap_socket(conn._sock)
129+
130+
return conn, size
131+
132+
# Internal helper methods
133+
134+
def _wrap_socket(self, socket):
135+
if self.keyfile and not self._keydata:
136+
with open(self.keyfile, 'rb') as f:
137+
self._keydata = f.read()
138+
139+
if self.certfile and not self._certdata:
140+
with open(self.certfile, 'rb') as f:
141+
self._certdata = f.read()
142+
143+
return _ssl.wrap_socket(socket, key=self._keydata, cert=self._certdata)

0 commit comments

Comments
 (0)