[MERGE/RFC] GSSAPI authentication support for FTP
mbp at canonical.com
Wed Jul 2 06:26:26 BST 2008
Martin Pool has voted resubmit.
Status is now: Resubmit
=== added file 'bzrlib/transport/ftp/_gssapi.py'
+import base64, ftplib, getpass, socket
The coding standard is that the system imports should be before the
+ """Extended version of ftplib.FTP that can authenticate using
This class name is a bit generic, I would expect SecureFtp means SFTP or
maybe ftp-over-ssl. Also there should be a blank line after the
+ def mic_getline(self):
+ resp = ftplib.FTP.getline(self)
+ if resp[:4] != '631 ':
+ raise AssertionError
+ rc = kerberos.authGSSClientUnwrap(self.vc,
+ response =
+ return response
+ def gssapi_login(self, user):
+ # Try GSSAPI login first
+ resp = self.sendcmd('AUTH GSSAPI')
+ if resp[:3] == '334':
+ rc, self.vc = kerberos.authGSSClientInit("ftp@%s" %
+ if kerberos.authGSSClientStep(self.vc, "") != 1:
+ while resp[:3] in ('334', '335'):
+ authdata = kerberos.authGSSClientResponse(self.vc)
+ resp = self.sendcmd('ADAT ' + authdata)
+ if resp[:9] in ('235 ADAT=', '335 ADAT='):
+ rc = kerberos.authGSSClientStep(self.vc,
+ if not ((resp[:3] == '235' and rc == 1) or
+ (resp[:3] == '335' and rc == 0)):
+ raise AssertionError
+ info("Authenticated as %s" %
+ # Monkey patch ftplib
+ self.putcmd = self.mic_putcmd
+ self.getline = self.mic_getline
+ self.sendcmd('USER ' + user)
+ return resp
When you get something unexpected back it would be nice to raise an
that contains e.g. resp so that if a user hits it we have some data to
on - and anyhow the message might help them understand what's going
Perhaps a generic TransportError would be better than AssertionError.
What happens if you get a response other than 334? I would guess
one that means 'not supported' which you should ignore but maybe the
others should at least give a warning?
+ def _create_connection(self, credentials=None):
+ """Create a new connection with the provided credentials.
+ :param credentials: The credentials needed to establish the
+ :return: The created connection and its associated credentials.
+ The credentials are only the password as it may have been
+ interactively by the user and may be different from the one
+ in base url at transport creation time.
+ if credentials is None:
+ user, password = self._user, self._password
+ user, password = credentials
The docstring seems inconsistent with how `credentials` is actually
Other than that it looks pretty good to me.
For details, see:
More information about the bazaar