~bzr-pqm/bzr/bzr.dev

3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
1
# Copyright (C) 2005, 2006, 2007, 2008 Canonical Ltd
2
#
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.
7
#
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.
12
#
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
16
17
"""Support for secure authentication using GSSAPI over FTP.
18
19
See RFC2228 for details.
20
"""
21
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
22
import base64, ftplib, getpass, socket
23
3331.2.6 by Jelmer Vernooij
Fix formatting, check for completeness of kerberos functions.
24
from bzrlib import (
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
25
    config,
3331.2.6 by Jelmer Vernooij
Fix formatting, check for completeness of kerberos functions.
26
    errors,
27
    )
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
28
from bzrlib.trace import info, mutter
29
from bzrlib.transport.ftp import FtpTransport
30
from bzrlib.transport import register_transport_proto, register_transport
31
32
try:
33
    import kerberos
34
except ImportError, e:
35
    mutter('failed to import kerberos lib: %s', e)
36
    raise errors.DependencyNotPresent('kerberos', e)
37
3331.2.6 by Jelmer Vernooij
Fix formatting, check for completeness of kerberos functions.
38
if getattr(kerberos, "authGSSClientWrap", None) is None:
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
39
    raise errors.DependencyNotPresent('kerberos',
3649.3.3 by Jelmer Vernooij
Review feedback from John.
40
        "missing encryption function authGSSClientWrap")
3331.2.6 by Jelmer Vernooij
Fix formatting, check for completeness of kerberos functions.
41
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
42
43
class GSSAPIFtp(ftplib.FTP):
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
44
    """Extended version of ftplib.FTP that can authenticate using GSSAPI."""
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
45
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
46
    def mic_putcmd(self, line):
3763.2.1 by Jelmer Vernooij
Remove use of optional parameter in GSSAPI FTP support since it breaks newer versions of Python-Kerberos.
47
        rc = kerberos.authGSSClientWrap(self.vc, base64.b64encode(line))
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
48
        wrapped = kerberos.authGSSClientResponse(self.vc)
49
        ftplib.FTP.putcmd(self, "MIC " + wrapped)
50
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
51
    def mic_getline(self):
52
        resp = ftplib.FTP.getline(self)
3331.2.10 by Jelmer Vernooij
Review feedback from Vincent.
53
        if resp[:4] != '631 ':
54
            raise AssertionError
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
55
        rc = kerberos.authGSSClientUnwrap(self.vc, resp[4:].strip("\r\n"))
3331.2.6 by Jelmer Vernooij
Fix formatting, check for completeness of kerberos functions.
56
        response = base64.b64decode(kerberos.authGSSClientResponse(self.vc))
57
        return response
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
58
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
59
    def gssapi_login(self, user):
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
60
        # Try GSSAPI login first
3649.3.3 by Jelmer Vernooij
Review feedback from John.
61
62
        # Used FTP response codes:
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
63
        # 235 [ADAT=base64data] - indicates that the security data exchange
3649.3.3 by Jelmer Vernooij
Review feedback from John.
64
        #     completed successfully.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
65
        # 334 [ADAT=base64data] - indicates that the requested security
66
        #     mechanism is ok, and includes security data to be used by the
3649.3.3 by Jelmer Vernooij
Review feedback from John.
67
        #     client to construct the next command.
68
        # 335 [ADAT=base64data] - indicates that the security data is
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
69
        #     acceptable, and more is required to complete the security
3649.3.3 by Jelmer Vernooij
Review feedback from John.
70
        #     data exchange.
71
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
72
        resp = self.sendcmd('AUTH GSSAPI')
3649.3.5 by Jelmer Vernooij
use startswith.
73
        if resp.startswith('334 '):
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
74
            rc, self.vc = kerberos.authGSSClientInit("ftp@%s" % self.host)
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
75
            if kerberos.authGSSClientStep(self.vc, "") != 1:
3649.3.5 by Jelmer Vernooij
use startswith.
76
                while resp[:4] in ('334 ', '335 '):
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
77
                    authdata = kerberos.authGSSClientResponse(self.vc)
78
                    resp = self.sendcmd('ADAT ' + authdata)
79
                    if resp[:9] in ('235 ADAT=', '335 ADAT='):
80
                        rc = kerberos.authGSSClientStep(self.vc, resp[9:])
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
81
                        if not ((resp.startswith('235 ') and rc == 1) or
3649.3.5 by Jelmer Vernooij
use startswith.
82
                                (resp.startswith('335 ') and rc == 0)):
3649.3.3 by Jelmer Vernooij
Review feedback from John.
83
                            raise ftplib.error_reply, resp
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
84
            info("Authenticated as %s" % kerberos.authGSSClientUserName(
85
                    self.vc))
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
86
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
87
            # Monkey patch ftplib
88
            self.putcmd = self.mic_putcmd
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
89
            self.getline = self.mic_getline
90
            self.sendcmd('USER ' + user)
91
            return resp
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
92
        mutter("Unable to use GSSAPI authentication: %s", resp)
93
94
95
class GSSAPIFtpTransport(FtpTransport):
3649.3.3 by Jelmer Vernooij
Review feedback from John.
96
    """FTP transport implementation that will try to use GSSAPI authentication.
97
98
    """
99
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
100
    def _create_connection(self, credentials=None):
101
        """Create a new connection with the provided credentials.
102
103
        :param credentials: The credentials needed to establish the connection.
104
105
        :return: The created connection and its associated credentials.
106
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
107
        The credentials are a tuple with the username and password. The
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
108
        password is used if GSSAPI Authentication is not available.
109
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
110
        The username and password can both be None, in which case the
111
        credentials specified in the URL or provided by the
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
112
        AuthenticationConfig() are used.
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
113
        """
114
        if credentials is None:
115
            user, password = self._user, self._password
116
        else:
117
            user, password = credentials
118
119
        auth = config.AuthenticationConfig()
120
        if user is None:
121
            user = auth.get_user('ftp', self._host, port=self._port)
122
            if user is None:
123
                # Default to local user
124
                user = getpass.getuser()
125
126
        mutter("Constructing FTP instance against %r" %
127
               ((self._host, self._port, user, '********',
128
                self.is_active),))
129
        try:
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
130
            connection = GSSAPIFtp()
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
131
            connection.connect(host=self._host, port=self._port)
132
            try:
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
133
                connection.gssapi_login(user=user)
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
134
            except ftplib.error_perm, e:
135
                if user and user != 'anonymous' and \
136
                        password is None: # '' is a valid password
137
                    password = auth.get_password('ftp', self._host, user,
138
                                                 port=self._port)
139
                connection.login(user=user, passwd=password)
140
            connection.set_pasv(not self.is_active)
141
        except socket.error, e:
142
            raise errors.SocketConnectionError(self._host, self._port,
143
                                               msg='Unable to connect to',
144
                                               orig_error= e)
145
        except ftplib.error_perm, e:
146
            raise errors.TransportError(msg="Error setting up connection:"
147
                                        " %s" % str(e), orig_error=e)
148
        return connection, (user, password)
149
150
151
def get_test_permutations():
152
    """Return the permutations to be used in testing."""
4167.1.3 by Vincent Ladeuil
Fix GSSAPIFtpTransport tests when no FTP test server is available
153
    from bzrlib.tests import ftp_server
154
    if ftp_server.FTPServerFeature.available():
3508.1.23 by Vincent Ladeuil
Fix as per Martin's review.
155
        return [(GSSAPIFtpTransport, ftp_server.FTPTestServer)]
3331.2.8 by Jelmer Vernooij
Run regular FTP tests against SecureFTP transport.
156
    else:
3331.2.10 by Jelmer Vernooij
Review feedback from Vincent.
157
        return []