/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/transport/ftp/_gssapi.py

merge bzr.dev.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""Support for secure authentication using GSSAPI over FTP.
 
18
 
 
19
See RFC2228 for details.
 
20
"""
 
21
 
 
22
import base64, ftplib, getpass, socket
 
23
 
 
24
from bzrlib import (
 
25
    config,
 
26
    errors,
 
27
    )
 
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
 
 
38
if getattr(kerberos, "authGSSClientWrap", None) is None:
 
39
    raise errors.DependencyNotPresent('kerberos',
 
40
        "missing encryption function authGSSClientWrap")
 
41
 
 
42
 
 
43
class GSSAPIFtp(ftplib.FTP):
 
44
    """Extended version of ftplib.FTP that can authenticate using GSSAPI."""
 
45
 
 
46
    def mic_putcmd(self, line):
 
47
        rc = kerberos.authGSSClientWrap(self.vc, base64.b64encode(line))
 
48
        wrapped = kerberos.authGSSClientResponse(self.vc)
 
49
        ftplib.FTP.putcmd(self, "MIC " + wrapped)
 
50
 
 
51
    def mic_getline(self):
 
52
        resp = ftplib.FTP.getline(self)
 
53
        if resp[:4] != '631 ':
 
54
            raise AssertionError
 
55
        rc = kerberos.authGSSClientUnwrap(self.vc, resp[4:].strip("\r\n"))
 
56
        response = base64.b64decode(kerberos.authGSSClientResponse(self.vc))
 
57
        return response
 
58
 
 
59
    def gssapi_login(self, user):
 
60
        # Try GSSAPI login first
 
61
 
 
62
        # Used FTP response codes:
 
63
        # 235 [ADAT=base64data] - indicates that the security data exchange
 
64
        #     completed successfully.
 
65
        # 334 [ADAT=base64data] - indicates that the requested security
 
66
        #     mechanism is ok, and includes security data to be used by the
 
67
        #     client to construct the next command.
 
68
        # 335 [ADAT=base64data] - indicates that the security data is
 
69
        #     acceptable, and more is required to complete the security
 
70
        #     data exchange.
 
71
 
 
72
        resp = self.sendcmd('AUTH GSSAPI')
 
73
        if resp.startswith('334 '):
 
74
            rc, self.vc = kerberos.authGSSClientInit("ftp@%s" % self.host)
 
75
            if kerberos.authGSSClientStep(self.vc, "") != 1:
 
76
                while resp[:4] in ('334 ', '335 '):
 
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:])
 
81
                        if not ((resp.startswith('235 ') and rc == 1) or
 
82
                                (resp.startswith('335 ') and rc == 0)):
 
83
                            raise ftplib.error_reply, resp
 
84
            info("Authenticated as %s" % kerberos.authGSSClientUserName(
 
85
                    self.vc))
 
86
 
 
87
            # Monkey patch ftplib
 
88
            self.putcmd = self.mic_putcmd
 
89
            self.getline = self.mic_getline
 
90
            self.sendcmd('USER ' + user)
 
91
            return resp
 
92
        mutter("Unable to use GSSAPI authentication: %s", resp)
 
93
 
 
94
 
 
95
class GSSAPIFtpTransport(FtpTransport):
 
96
    """FTP transport implementation that will try to use GSSAPI authentication.
 
97
 
 
98
    """
 
99
 
 
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
 
 
107
        The credentials are a tuple with the username and password. The
 
108
        password is used if GSSAPI Authentication is not available.
 
109
 
 
110
        The username and password can both be None, in which case the
 
111
        credentials specified in the URL or provided by the
 
112
        AuthenticationConfig() are used.
 
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
                                 default=getpass.getuser())
 
123
        mutter("Constructing FTP instance against %r" %
 
124
               ((self._host, self._port, user, '********',
 
125
                self.is_active),))
 
126
        try:
 
127
            connection = GSSAPIFtp()
 
128
            connection.connect(host=self._host, port=self._port)
 
129
            try:
 
130
                connection.gssapi_login(user=user)
 
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."""
 
150
    from bzrlib.tests import ftp_server
 
151
    if ftp_server.FTPServerFeature.available():
 
152
        return [(GSSAPIFtpTransport, ftp_server.FTPTestServer)]
 
153
    else:
 
154
        return []