~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Jelmer Vernooij
  • Date: 2008-06-11 19:20:24 UTC
  • mto: (3649.3.2 bzr.dev)
  • mto: This revision was merged to the branch mainline in revision 3658.
  • Revision ID: jelmer@samba.org-20080611192024-e7l1vrflhissbd17
Move GSSAPI support to a separate file.

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Support for secure authentication using GSSAPI over FTP.
 
18
 
 
19
See RFC2228 for details.
 
20
"""
 
21
 
 
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
 
26
 
 
27
try:
 
28
    import kerberos
 
29
except ImportError, e:
 
30
    mutter('failed to import kerberos lib: %s', e)
 
31
    raise errors.DependencyNotPresent('kerberos', e)
 
32
 
 
33
import ftplib, getpass
 
34
 
 
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)
 
41
        print "> " + wrapped
 
42
        ftplib.FTP.putcmd(self, "MIC " + wrapped)
 
43
 
 
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)
 
49
        return response 
 
50
 
 
51
    def gssapi_login(self):
 
52
        # Try GSSAPI login first
 
53
        resp = self.sendcmd('AUTH GSSAPI')
 
54
        if resp[:3] == '334':
 
55
            rc, self.vc = kerberos.authGSSClientInit("ftp@%s" % self.host)
 
56
 
 
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(
 
64
                    self.vc))
 
65
            # Monkey patch ftplib
 
66
            self.putcmd = self.mic_putcmd
 
67
            self.getmultiline = self.mic_getmultiline
 
68
 
 
69
 
 
70
class SecureFtpTransport(FtpTransport):
 
71
    def _create_connection(self, credentials=None):
 
72
        """Create a new connection with the provided credentials.
 
73
 
 
74
        :param credentials: The credentials needed to establish the connection.
 
75
 
 
76
        :return: The created connection and its associated credentials.
 
77
 
 
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.
 
81
        """
 
82
        if credentials is None:
 
83
            user, password = self._user, self._password
 
84
        else:
 
85
            user, password = credentials
 
86
 
 
87
        auth = config.AuthenticationConfig()
 
88
        if user is None:
 
89
            user = auth.get_user('ftp', self._host, port=self._port)
 
90
            if user is None:
 
91
                # Default to local user
 
92
                user = getpass.getuser()
 
93
 
 
94
        mutter("Constructing FTP instance against %r" %
 
95
               ((self._host, self._port, user, '********',
 
96
                self.is_active),))
 
97
        try:
 
98
            connection = SecureFtp()
 
99
            connection.connect(host=self._host, port=self._port)
 
100
            try:
 
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,
 
106
                                                 port=self._port)
 
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',
 
112
                                               orig_error= e)
 
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)
 
117
 
 
118
 
 
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):
 
126
 
 
127
        def setUp(self):
 
128
            pass
 
129
 
 
130
        def tearDown(self):
 
131
            pass
 
132
 
 
133
        def get_url(self):
 
134
            raise tests.UnavailableFeature(tests.FTPServerFeature)
 
135
 
 
136
        def get_bogus_url(self):
 
137
            raise tests.UnavailableFeature(tests.FTPServerFeature)
 
138
 
 
139
    return [(FtpTransport, UnavailableFTPServer)]