1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Support for secure authentication using GSSAPI over FTP.
19
See RFC2228 for details.
22
from bzrlib import config, errors
23
from bzrlib.trace import info, mutter
24
from bzrlib.transport.ftp import FtpTransport
25
from bzrlib.transport import register_transport_proto, register_transport
29
except ImportError, e:
30
mutter('failed to import kerberos lib: %s', e)
31
raise errors.DependencyNotPresent('kerberos', e)
33
import ftplib, getpass
35
class SecureFtp(ftplib.FTP):
36
"""Extended version of ftplib.FTP that can authenticate using GSSAPI."""
37
def mic_putcmd(self, line):
38
kerberos.authGSSClientWrap(
39
self.vc, line.rstrip("\r\n"), 'jelmer')
40
wrapped = kerberos.authGSSClientResponse(self.vc)
42
ftplib.FTP.putcmd(self, "MIC " + wrapped)
44
def mic_getmultiline(self):
45
resp = ftplib.FTP.getmultiline(self)
46
assert resp[:3] == '631'
47
kerberos.authGSSClientUnwrap(self.vc, resp[4:])
48
response = kerberos.authGSSClientResponse(self.vc)
51
def gssapi_login(self):
52
# Try GSSAPI login first
53
resp = self.sendcmd('AUTH GSSAPI')
55
rc, self.vc = kerberos.authGSSClientInit("ftp@%s" % self.host)
57
kerberos.authGSSClientStep(self.vc, "")
58
while resp[:3] in ('334', '335'):
59
authdata = kerberos.authGSSClientResponse(self.vc)
60
resp = self.sendcmd('ADAT ' + authdata)
61
if resp[:3] in ('235', '335'):
62
kerberos.authGSSClientStep(self.vc, resp[9:])
63
info("Authenticated as %s" % kerberos.authGSSClientUserName(
66
self.putcmd = self.mic_putcmd
67
self.getmultiline = self.mic_getmultiline
70
class SecureFtpTransport(FtpTransport):
71
def _create_connection(self, credentials=None):
72
"""Create a new connection with the provided credentials.
74
:param credentials: The credentials needed to establish the connection.
76
:return: The created connection and its associated credentials.
78
The credentials are only the password as it may have been entered
79
interactively by the user and may be different from the one provided
80
in base url at transport creation time.
82
if credentials is None:
83
user, password = self._user, self._password
85
user, password = credentials
87
auth = config.AuthenticationConfig()
89
user = auth.get_user('ftp', self._host, port=self._port)
91
# Default to local user
92
user = getpass.getuser()
94
mutter("Constructing FTP instance against %r" %
95
((self._host, self._port, user, '********',
98
connection = SecureFtp()
99
connection.connect(host=self._host, port=self._port)
101
connection.gssapi_login()
102
except ftplib.error_perm, e:
103
if user and user != 'anonymous' and \
104
password is None: # '' is a valid password
105
password = auth.get_password('ftp', self._host, user,
107
connection.login(user=user, passwd=password)
108
connection.set_pasv(not self.is_active)
109
except socket.error, e:
110
raise errors.SocketConnectionError(self._host, self._port,
111
msg='Unable to connect to',
113
except ftplib.error_perm, e:
114
raise errors.TransportError(msg="Error setting up connection:"
115
" %s" % str(e), orig_error=e)
116
return connection, (user, password)
119
def get_test_permutations():
120
"""Return the permutations to be used in testing."""
121
# Dummy server to have the test suite report the number of tests
122
# needing that feature. We raise UnavailableFeature from methods before
123
# the test server is being used. Doing so in the setUp method has bad
124
# side-effects (tearDown is never called).
125
class UnavailableFTPServer(object):
134
raise tests.UnavailableFeature(tests.FTPServerFeature)
136
def get_bogus_url(self):
137
raise tests.UnavailableFeature(tests.FTPServerFeature)
139
return [(FtpTransport, UnavailableFTPServer)]