~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:
4304.2.1 by Vincent Ladeuil
Fix bug #367726 by reverting some default user handling introduced
121
            user = auth.get_user('ftp', self._host, port=self._port,
122
                                 default=getpass.getuser())
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
123
        mutter("Constructing FTP instance against %r" %
124
               ((self._host, self._port, user, '********',
125
                self.is_active),))
126
        try:
3331.2.11 by Jelmer Vernooij
Review comments from Martin.
127
            connection = GSSAPIFtp()
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
128
            connection.connect(host=self._host, port=self._port)
129
            try:
3331.2.7 by Jelmer Vernooij
Fix handling of multilines.
130
                connection.gssapi_login(user=user)
3331.2.4 by Jelmer Vernooij
Move GSSAPI support to a separate file.
131
            except ftplib.error_perm, e:
132
                if user and user != 'anonymous' and \
133
                        password is None: # '' is a valid password
134
                    password = auth.get_password('ftp', self._host, user,
135
                                                 port=self._port)
136
                connection.login(user=user, passwd=password)
137
            connection.set_pasv(not self.is_active)
138
        except socket.error, e:
139
            raise errors.SocketConnectionError(self._host, self._port,
140
                                               msg='Unable to connect to',
141
                                               orig_error= e)
142
        except ftplib.error_perm, e:
143
            raise errors.TransportError(msg="Error setting up connection:"
144
                                        " %s" % str(e), orig_error=e)
145
        return connection, (user, password)
146
147
148
def get_test_permutations():
149
    """Return the permutations to be used in testing."""
4167.1.3 by Vincent Ladeuil
Fix GSSAPIFtpTransport tests when no FTP test server is available
150
    from bzrlib.tests import ftp_server
151
    if ftp_server.FTPServerFeature.available():
3508.1.23 by Vincent Ladeuil
Fix as per Martin's review.
152
        return [(GSSAPIFtpTransport, ftp_server.FTPTestServer)]
3331.2.8 by Jelmer Vernooij
Run regular FTP tests against SecureFTP transport.
153
    else:
3331.2.10 by Jelmer Vernooij
Review feedback from Vincent.
154
        return []