From 9cde524d4fa3c58d534681bac936e2abc83e4f3a Mon Sep 17 00:00:00 2001 From: Reid Sunderland Date: Tue, 25 Jun 2024 16:00:10 +0000 Subject: [PATCH] Implement support for implicit FTPS on port 990 --- docs/source/Reference/sr3_credentials.7.rst | 2 + .../source/fr/Reference/sr3_credentials.7.rst | 2 + sarracenia/credentials.py | 6 +++ sarracenia/transfer/ftp.py | 39 ++++++++++++++++++- 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/docs/source/Reference/sr3_credentials.7.rst b/docs/source/Reference/sr3_credentials.7.rst index b22fdd2d2..8f3335797 100644 --- a/docs/source/Reference/sr3_credentials.7.rst +++ b/docs/source/Reference/sr3_credentials.7.rst @@ -47,6 +47,7 @@ passwords and settings needed by components. The format is one entry per line. - **ftps://user7:De%3Aize@host passive,binary,tls** - **ftps://user8:%2fdot8@host:2121 active,ascii,tls,prot_p** +- **ftp://user8:%2fdot8@host:990 implicit_ftps** - **https://ladsweb.modaps.eosdis.nasa.gov/ bearer_token=89APCBF0-FEBE-11EA-A705-B0QR41911BF4** @@ -71,6 +72,7 @@ Supported details: - ``prot_p`` - (FTPS) Use a secure data connection for TLS connections (otherwise, clear text is used) - ``bearer_token=`` (or ``bt=``) - (HTTP) Bearer token for authentication - ``login_method=`` - (AMQP) By default, the login method will be automatically determined. This can be overriden by explicity specifying a login method, which may be required if a broker supports multiple methods and an incorrect one is automatically selected. +- ``implicit_ftps`` - (FTPS) Use implicit FTPS (otherwise, explicit FTPS is used). Setting this will also set ``tls`` to True. Note:: SFTP credentials are optional, in that sarracenia will look in the .ssh directory diff --git a/docs/source/fr/Reference/sr3_credentials.7.rst b/docs/source/fr/Reference/sr3_credentials.7.rst index d75e38a07..b3f45425a 100644 --- a/docs/source/fr/Reference/sr3_credentials.7.rst +++ b/docs/source/fr/Reference/sr3_credentials.7.rst @@ -46,6 +46,7 @@ ainsi que les paramètres nécessaires aux composants. Le format est d'une entr - **ftps://user7:De%3Aize@host passive,binary,tls** - **ftps://user8:%2fdot8@host:2121 active,ascii,tls,prot_p** +- **ftp://user8:%2fdot8@host:990 implicit_ftps** - **https://ladsweb.modaps.eosdis.nasa.gov/ bearer_token=89APCBF0-FEBE-11EA-A705-B0QR41911BF4** Dans d’autres fichiers de configuration ou sur la ligne de commande, l’url n’a tout simplement pas le @@ -71,6 +72,7 @@ Détails pris en charge : - ``prot_p`` - (FTPS) Utiliser une connexion de données sécurisée pour les connexions TLS (sinon, du texte clair est utilisé) - ``bearer_token=`` (ou ``bt=``) - (HTTP) Jeton Bearer pour l’authentification - ``login_method=`` - (AMQP) Par défaut, la méthode de connexion sera automatiquement +- ``implicit_ftps`` - (FTPS) Utilisez FTPS implicite (sinon, FTPS explicite est utilisé). Définir ceci définira également ``tls`` sur True. déterminée. Cela peut être remplacé en spécifiant une méthode Particulière de connexion, ce qui peut être nécessaire si un broker prend en charge plusieurs méthodes et qu’une méthode incorrecte est automatiquement diff --git a/sarracenia/credentials.py b/sarracenia/credentials.py index c719a70c1..e51f402a7 100755 --- a/sarracenia/credentials.py +++ b/sarracenia/credentials.py @@ -66,6 +66,7 @@ class Credential: bearer_token (str): bearer token for HTTP authentication login_method (str): force a specific login method for AMQP (PLAIN, AMQPLAIN, EXTERNAL or GSSAPI) + implicit_ftps (bool): use implicit FTPS, defaults to ``False`` (i.e. explicit FTPS) Usage: @@ -101,6 +102,7 @@ def __init__(self, urlstr=None): self.s3_endpoint = None self.s3_session_token = None self.azure_credentials = None + self.implicit_ftps = False def __str__(self): """Returns attributes of the Credential object as a readable string. @@ -133,6 +135,7 @@ def __str__(self): #want to show they provided a session token, but not leak it (like passwords above) s += " %s" % 'Yes' if self.s3_session_token != None else 'No' s += " %s" % 'Yes' if self.azure_credentials != None else 'No' + s += " %s" % self.implicit_ftps return s @@ -383,6 +386,9 @@ def _parse(self, line): details.s3_endpoint = parts[1].strip() elif keyword == 'azure_storage_credentials': details.azure_credentials = urllib.parse.unquote(parts[1].strip()) + elif keyword == 'implicit_ftps': + details.implicit_ftps = True + details.tls = True else: logger.warning("bad credential option (%s)" % keyword) diff --git a/sarracenia/transfer/ftp.py b/sarracenia/transfer/ftp.py index 55424ba04..c6b796686 100755 --- a/sarracenia/transfer/ftp.py +++ b/sarracenia/transfer/ftp.py @@ -20,7 +20,7 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # -import ftplib, os, subprocess, sys, time +import ftplib, os, subprocess, sys, time, ssl import logging from sarracenia.transfer import Transfer from sarracenia.transfer import alarm_cancel, alarm_set, alarm_raise @@ -28,6 +28,26 @@ logger = logging.getLogger(__name__) +class IMPLICIT_FTP_TLS(ftplib.FTP_TLS): + """ FTP_TLS subclass that automatically wraps sockets in SSL to support implicit FTPS. + Copied from https://stackoverflow.com/questions/12164470/python-ftp-implicit-tls-connection-issue + """ + + 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 class Ftp(Transfer): """ @@ -176,13 +196,27 @@ def connect(self): try: expire = -999 if self.o.timeout: expire = self.o.timeout - if self.port == '' or self.port == None: self.port = 21 + if self.port == '' or self.port == None: + if self.implicit_ftps: + self.port = 990 + else: + self.port = 21 + # plain FTP with no encryption (usually port 21) if not self.tls: ftp = ftplib.FTP() ftp.encoding = 'utf-8' ftp.connect(self.host, self.port, timeout=expire) ftp.login(self.user, unquote(self.password)) + # implicit FTPS (usually port 990) + elif self.tls and self.implicit_ftps: + ftp = IMPLICIT_FTP_TLS() + ftp.encoding = 'utf-8' + ftp.connect(host=self.host, port=self.port, timeout=expire) + ftp.login(user=self.user, passwd=unquote(self.password)) + if self.prot_p: + ftp.prot_p() + # explicit FTPS (port 21) else: # ftplib supports FTPS with TLS ftp = ftplib.FTP_TLS(self.host, @@ -233,6 +267,7 @@ def credentials(self): self.binary = details.binary self.tls = details.tls self.prot_p = details.prot_p + self.implicit_ftps = details.implicit_ftps return True