/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/sftp.py

Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
"""Implementation of Transport over SFTP, using paramiko."""
19
19
 
20
20
import errno
21
 
import getpass
22
 
import itertools
23
21
import os
24
22
import random
25
 
import re
26
23
import select
27
24
import socket
28
25
import stat
33
30
import urlparse
34
31
import weakref
35
32
 
36
 
from bzrlib.config import config_dir, ensure_config_dir_exists
37
 
from bzrlib.errors import (ConnectionError,
38
 
                           FileExists, 
39
 
                           TransportNotPossible, NoSuchFile, PathNotChild,
 
33
from bzrlib.errors import (FileExists, 
 
34
                           NoSuchFile, PathNotChild,
40
35
                           TransportError,
41
36
                           LockError, 
42
37
                           PathError,
43
38
                           ParamikoNotPresent,
 
39
                           UnknownSSH,
44
40
                           )
45
41
from bzrlib.osutils import pathjoin, fancy_rename, getcwd
46
 
from bzrlib.trace import mutter, warning, error
 
42
from bzrlib.trace import mutter, warning
47
43
from bzrlib.transport import (
48
44
    register_urlparse_netloc_protocol,
49
45
    Server,
50
46
    split_url,
 
47
    ssh,
51
48
    Transport,
52
49
    )
53
 
import bzrlib.ui
54
50
import bzrlib.urlutils as urlutils
55
51
 
56
52
try:
63
59
                               CMD_HANDLE, CMD_OPEN)
64
60
    from paramiko.sftp_attr import SFTPAttributes
65
61
    from paramiko.sftp_file import SFTPFile
66
 
    from paramiko.sftp_client import SFTPClient
67
62
 
68
63
 
69
64
register_urlparse_netloc_protocol('sftp')
70
65
 
71
66
 
72
 
def _ignore_sigint():
73
 
    # TODO: This should possibly ignore SIGHUP as well, but bzr currently
74
 
    # doesn't handle it itself.
75
 
    # <https://launchpad.net/products/bzr/+bug/41433/+index>
76
 
    import signal
77
 
    signal.signal(signal.SIGINT, signal.SIG_IGN)
78
 
    
79
 
 
80
 
def os_specific_subprocess_params():
81
 
    """Get O/S specific subprocess parameters."""
82
 
    if sys.platform == 'win32':
83
 
        # setting the process group and closing fds is not supported on 
84
 
        # win32
85
 
        return {}
86
 
    else:
87
 
        # We close fds other than the pipes as the child process does not need 
88
 
        # them to be open.
89
 
        #
90
 
        # We also set the child process to ignore SIGINT.  Normally the signal
91
 
        # would be sent to every process in the foreground process group, but
92
 
        # this causes it to be seen only by bzr and not by ssh.  Python will
93
 
        # generate a KeyboardInterrupt in bzr, and we will then have a chance
94
 
        # to release locks or do other cleanup over ssh before the connection
95
 
        # goes away.  
96
 
        # <https://launchpad.net/products/bzr/+bug/5987>
97
 
        #
98
 
        # Running it in a separate process group is not good because then it
99
 
        # can't get non-echoed input of a password or passphrase.
100
 
        # <https://launchpad.net/products/bzr/+bug/40508>
101
 
        return {'preexec_fn': _ignore_sigint,
102
 
                'close_fds': True,
103
 
                }
104
 
 
105
 
 
106
 
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
107
 
# don't use prefetch unless paramiko version >= 1.5.5 (there were bugs earlier)
108
 
_default_do_prefetch = (_paramiko_version >= (1, 5, 5))
109
 
 
110
 
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
111
 
# to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
112
 
# so we get an AttributeError exception. So we will not try to
113
 
# connect to an agent if we are on win32 and using Paramiko older than 1.6
114
 
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
115
 
 
116
 
 
117
 
_ssh_vendor = None
118
 
def _get_ssh_vendor():
119
 
    """Find out what version of SSH is on the system."""
120
 
    global _ssh_vendor
121
 
    if _ssh_vendor is not None:
122
 
        return _ssh_vendor
123
 
 
124
 
    _ssh_vendor = 'none'
125
 
 
126
 
    if 'BZR_SSH' in os.environ:
127
 
        _ssh_vendor = os.environ['BZR_SSH']
128
 
        if _ssh_vendor == 'paramiko':
129
 
            _ssh_vendor = 'none'
130
 
        return _ssh_vendor
131
 
 
132
 
    try:
133
 
        p = subprocess.Popen(['ssh', '-V'],
134
 
                             stdin=subprocess.PIPE,
135
 
                             stdout=subprocess.PIPE,
136
 
                             stderr=subprocess.PIPE,
137
 
                             **os_specific_subprocess_params())
138
 
        returncode = p.returncode
139
 
        stdout, stderr = p.communicate()
140
 
    except OSError:
141
 
        returncode = -1
142
 
        stdout = stderr = ''
143
 
    if 'OpenSSH' in stderr:
144
 
        mutter('ssh implementation is OpenSSH')
145
 
        _ssh_vendor = 'openssh'
146
 
    elif 'SSH Secure Shell' in stderr:
147
 
        mutter('ssh implementation is SSH Corp.')
148
 
        _ssh_vendor = 'ssh'
149
 
 
150
 
    if _ssh_vendor != 'none':
151
 
        return _ssh_vendor
152
 
 
153
 
    # XXX: 20051123 jamesh
154
 
    # A check for putty's plink or lsh would go here.
155
 
 
156
 
    mutter('falling back to paramiko implementation')
157
 
    return _ssh_vendor
158
 
 
159
 
 
160
 
class SFTPSubprocess:
161
 
    """A socket-like object that talks to an ssh subprocess via pipes."""
162
 
    def __init__(self, hostname, vendor, port=None, user=None):
163
 
        assert vendor in ['openssh', 'ssh']
164
 
        if vendor == 'openssh':
165
 
            args = ['ssh',
166
 
                    '-oForwardX11=no', '-oForwardAgent=no',
167
 
                    '-oClearAllForwardings=yes', '-oProtocol=2',
168
 
                    '-oNoHostAuthenticationForLocalhost=yes']
169
 
            if port is not None:
170
 
                args.extend(['-p', str(port)])
171
 
            if user is not None:
172
 
                args.extend(['-l', user])
173
 
            args.extend(['-s', hostname, 'sftp'])
174
 
        elif vendor == 'ssh':
175
 
            args = ['ssh', '-x']
176
 
            if port is not None:
177
 
                args.extend(['-p', str(port)])
178
 
            if user is not None:
179
 
                args.extend(['-l', user])
180
 
            args.extend(['-s', 'sftp', hostname])
181
 
 
182
 
        self.proc = subprocess.Popen(args,
183
 
                                     stdin=subprocess.PIPE,
184
 
                                     stdout=subprocess.PIPE,
185
 
                                     **os_specific_subprocess_params())
186
 
 
187
 
    def send(self, data):
188
 
        return os.write(self.proc.stdin.fileno(), data)
189
 
 
190
 
    def recv_ready(self):
191
 
        # TODO: jam 20051215 this function is necessary to support the
192
 
        # pipelined() function. In reality, it probably should use
193
 
        # poll() or select() to actually return if there is data
194
 
        # available, otherwise we probably don't get any benefit
195
 
        return True
196
 
 
197
 
    def recv(self, count):
198
 
        return os.read(self.proc.stdout.fileno(), count)
199
 
 
200
 
    def close(self):
201
 
        self.proc.stdin.close()
202
 
        self.proc.stdout.close()
203
 
        self.proc.wait()
204
 
 
205
 
 
206
 
class LoopbackSFTP(object):
207
 
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
208
 
 
209
 
    def __init__(self, sock):
210
 
        self.__socket = sock
211
 
 
212
 
    def send(self, data):
213
 
        return self.__socket.send(data)
214
 
 
215
 
    def recv(self, n):
216
 
        return self.__socket.recv(n)
217
 
 
218
 
    def recv_ready(self):
219
 
        return True
220
 
 
221
 
    def close(self):
222
 
        self.__socket.close()
223
 
 
224
 
 
225
 
SYSTEM_HOSTKEYS = {}
226
 
BZR_HOSTKEYS = {}
227
 
 
228
67
# This is a weakref dictionary, so that we can reuse connections
229
68
# that are still active. Long term, it might be nice to have some
230
69
# sort of expiration policy, such as disconnect if inactive for
231
70
# X seconds. But that requires a lot more fanciness.
232
71
_connected_hosts = weakref.WeakValueDictionary()
233
72
 
 
73
 
 
74
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
 
75
# don't use prefetch unless paramiko version >= 1.5.5 (there were bugs earlier)
 
76
_default_do_prefetch = (_paramiko_version >= (1, 5, 5))
 
77
 
 
78
 
234
79
def clear_connection_cache():
235
80
    """Remove all hosts from the SFTP connection cache.
236
81
 
239
84
    _connected_hosts.clear()
240
85
 
241
86
 
242
 
def load_host_keys():
243
 
    """
244
 
    Load system host keys (probably doesn't work on windows) and any
245
 
    "discovered" keys from previous sessions.
246
 
    """
247
 
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
248
 
    try:
249
 
        SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
250
 
    except Exception, e:
251
 
        mutter('failed to load system host keys: ' + str(e))
252
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
253
 
    try:
254
 
        BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
255
 
    except Exception, e:
256
 
        mutter('failed to load bzr host keys: ' + str(e))
257
 
        save_host_keys()
258
 
 
259
 
 
260
 
def save_host_keys():
261
 
    """
262
 
    Save "discovered" host keys in $(config)/ssh_host_keys/.
263
 
    """
264
 
    global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
265
 
    bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
266
 
    ensure_config_dir_exists()
267
 
 
268
 
    try:
269
 
        f = open(bzr_hostkey_path, 'w')
270
 
        f.write('# SSH host keys collected by bzr\n')
271
 
        for hostname, keys in BZR_HOSTKEYS.iteritems():
272
 
            for keytype, key in keys.iteritems():
273
 
                f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
274
 
        f.close()
275
 
    except IOError, e:
276
 
        mutter('failed to save bzr host keys: ' + str(e))
277
 
 
278
 
 
279
87
class SFTPLock(object):
280
88
    """This fakes a lock in a remote location."""
281
89
    __slots__ = ['path', 'lock_path', 'lock_file', 'transport']
311
119
            pass
312
120
 
313
121
 
314
 
class SFTPTransport (Transport):
315
 
    """Transport implementation for SFTP access"""
 
122
class SFTPTransport(Transport):
 
123
    """Transport implementation for SFTP access."""
316
124
 
317
125
    _do_prefetch = _default_do_prefetch
318
126
    # TODO: jam 20060717 Conceivably these could be configurable, either
608
416
        """Walk the relative paths of all files in this transport."""
609
417
        queue = list(self.list_dir('.'))
610
418
        while queue:
611
 
            relpath = urllib.quote(queue.pop(0))
 
419
            relpath = queue.pop(0)
612
420
            st = self.stat(relpath)
613
421
            if stat.S_ISDIR(st.st_mode):
614
422
                for i, basename in enumerate(self.list_dir(relpath)):
722
530
        Return a list of all files at the given location.
723
531
        """
724
532
        # does anything actually use this?
 
533
        # -- Unknown
 
534
        # This is at least used by copy_tree for remote upgrades.
 
535
        # -- David Allouche 2006-08-11
725
536
        path = self._remote_path(relpath)
726
537
        try:
727
 
            return self._sftp.listdir(path)
 
538
            entries = self._sftp.listdir(path)
728
539
        except (IOError, paramiko.SSHException), e:
729
540
            self._translate_io_exception(e, path, ': failed to list_dir')
 
541
        return [urlutils.escape(entry) for entry in entries]
730
542
 
731
543
    def rmdir(self, relpath):
732
544
        """See Transport.rmdir."""
812
624
 
813
625
        TODO: Raise a more reasonable ConnectionFailed exception
814
626
        """
815
 
        global _connected_hosts
816
 
 
817
 
        idx = (self._host, self._port, self._username)
818
 
        try:
819
 
            self._sftp = _connected_hosts[idx]
820
 
            return
821
 
        except KeyError:
822
 
            pass
823
 
        
824
 
        vendor = _get_ssh_vendor()
825
 
        if vendor == 'loopback':
826
 
            sock = socket.socket()
827
 
            try:
828
 
                sock.connect((self._host, self._port))
829
 
            except socket.error, e:
830
 
                raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
831
 
                                      % (self._host, self._port, e))
832
 
            self._sftp = SFTPClient(LoopbackSFTP(sock))
833
 
        elif vendor != 'none':
834
 
            try:
835
 
                sock = SFTPSubprocess(self._host, vendor, self._port,
836
 
                                      self._username)
837
 
                self._sftp = SFTPClient(sock)
838
 
            except (EOFError, paramiko.SSHException), e:
839
 
                raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
840
 
                                      % (self._host, self._port, e))
841
 
            except (OSError, IOError), e:
842
 
                # If the machine is fast enough, ssh can actually exit
843
 
                # before we try and send it the sftp request, which
844
 
                # raises a Broken Pipe
845
 
                if e.errno not in (errno.EPIPE,):
846
 
                    raise
847
 
                raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
848
 
                                      % (self._host, self._port, e))
849
 
        else:
850
 
            self._paramiko_connect()
851
 
 
852
 
        _connected_hosts[idx] = self._sftp
853
 
 
854
 
    def _paramiko_connect(self):
855
 
        global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
856
 
        
857
 
        load_host_keys()
858
 
 
859
 
        try:
860
 
            t = paramiko.Transport((self._host, self._port or 22))
861
 
            t.set_log_channel('bzr.paramiko')
862
 
            t.start_client()
863
 
        except (paramiko.SSHException, socket.error), e:
864
 
            raise ConnectionError('Unable to reach SSH host %s:%s: %s' 
865
 
                                  % (self._host, self._port, e))
866
 
            
867
 
        server_key = t.get_remote_server_key()
868
 
        server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
869
 
        keytype = server_key.get_name()
870
 
        if SYSTEM_HOSTKEYS.has_key(self._host) and SYSTEM_HOSTKEYS[self._host].has_key(keytype):
871
 
            our_server_key = SYSTEM_HOSTKEYS[self._host][keytype]
872
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
873
 
        elif BZR_HOSTKEYS.has_key(self._host) and BZR_HOSTKEYS[self._host].has_key(keytype):
874
 
            our_server_key = BZR_HOSTKEYS[self._host][keytype]
875
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
876
 
        else:
877
 
            warning('Adding %s host key for %s: %s' % (keytype, self._host, server_key_hex))
878
 
            if not BZR_HOSTKEYS.has_key(self._host):
879
 
                BZR_HOSTKEYS[self._host] = {}
880
 
            BZR_HOSTKEYS[self._host][keytype] = server_key
881
 
            our_server_key = server_key
882
 
            our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
883
 
            save_host_keys()
884
 
        if server_key != our_server_key:
885
 
            filename1 = os.path.expanduser('~/.ssh/known_hosts')
886
 
            filename2 = pathjoin(config_dir(), 'ssh_host_keys')
887
 
            raise TransportError('Host keys for %s do not match!  %s != %s' % \
888
 
                (self._host, our_server_key_hex, server_key_hex),
889
 
                ['Try editing %s or %s' % (filename1, filename2)])
890
 
 
891
 
        self._sftp_auth(t)
892
 
        
893
 
        try:
894
 
            self._sftp = t.open_sftp_client()
895
 
        except paramiko.SSHException, e:
896
 
            raise ConnectionError('Unable to start sftp client %s:%d' %
897
 
                                  (self._host, self._port), e)
898
 
 
899
 
    def _sftp_auth(self, transport):
900
 
        # paramiko requires a username, but it might be none if nothing was supplied
901
 
        # use the local username, just in case.
902
 
        # We don't override self._username, because if we aren't using paramiko,
903
 
        # the username might be specified in ~/.ssh/config and we don't want to
904
 
        # force it to something else
905
 
        # Also, it would mess up the self.relpath() functionality
906
 
        username = self._username or getpass.getuser()
907
 
 
908
 
        if _use_ssh_agent:
909
 
            agent = paramiko.Agent()
910
 
            for key in agent.get_keys():
911
 
                mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
912
 
                try:
913
 
                    transport.auth_publickey(username, key)
914
 
                    return
915
 
                except paramiko.SSHException, e:
916
 
                    pass
917
 
        
918
 
        # okay, try finding id_rsa or id_dss?  (posix only)
919
 
        if self._try_pkey_auth(transport, paramiko.RSAKey, username, 'id_rsa'):
920
 
            return
921
 
        if self._try_pkey_auth(transport, paramiko.DSSKey, username, 'id_dsa'):
922
 
            return
923
 
 
924
 
        if self._password:
925
 
            try:
926
 
                transport.auth_password(username, self._password)
927
 
                return
928
 
            except paramiko.SSHException, e:
929
 
                pass
930
 
 
931
 
            # FIXME: Don't keep a password held in memory if you can help it
932
 
            #self._password = None
933
 
 
934
 
        # give up and ask for a password
935
 
        password = bzrlib.ui.ui_factory.get_password(
936
 
                prompt='SSH %(user)s@%(host)s password',
937
 
                user=username, host=self._host)
938
 
        try:
939
 
            transport.auth_password(username, password)
940
 
        except paramiko.SSHException, e:
941
 
            raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
942
 
                                  (username, self._host), e)
943
 
 
944
 
    def _try_pkey_auth(self, transport, pkey_class, username, filename):
945
 
        filename = os.path.expanduser('~/.ssh/' + filename)
946
 
        try:
947
 
            key = pkey_class.from_private_key_file(filename)
948
 
            transport.auth_publickey(username, key)
949
 
            return True
950
 
        except paramiko.PasswordRequiredException:
951
 
            password = bzrlib.ui.ui_factory.get_password(
952
 
                    prompt='SSH %(filename)s password',
953
 
                    filename=filename)
954
 
            try:
955
 
                key = pkey_class.from_private_key_file(filename, password)
956
 
                transport.auth_publickey(username, key)
957
 
                return True
958
 
            except paramiko.SSHException:
959
 
                mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
960
 
        except paramiko.SSHException:
961
 
            mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
962
 
        except IOError:
963
 
            pass
964
 
        return False
 
627
        self._sftp = _sftp_connect(self._host, self._port, self._username,
 
628
                self._password)
965
629
 
966
630
    def _sftp_open_exclusive(self, abspath, mode=None):
967
631
        """Open a remote path exclusively.
996
660
 
997
661
 
998
662
# ------------- server test implementation --------------
999
 
import socket
1000
663
import threading
1001
664
 
1002
665
from bzrlib.tests.stub_sftp import StubServer, StubSFTPServer
1065
728
                        x)
1066
729
 
1067
730
 
 
731
class SocketDelay(object):
 
732
    """A socket decorator to make TCP appear slower.
 
733
 
 
734
    This changes recv, send, and sendall to add a fixed latency to each python
 
735
    call if a new roundtrip is detected. That is, when a recv is called and the
 
736
    flag new_roundtrip is set, latency is charged. Every send and send_all
 
737
    sets this flag.
 
738
 
 
739
    In addition every send, sendall and recv sleeps a bit per character send to
 
740
    simulate bandwidth.
 
741
 
 
742
    Not all methods are implemented, this is deliberate as this class is not a
 
743
    replacement for the builtin sockets layer. fileno is not implemented to
 
744
    prevent the proxy being bypassed. 
 
745
    """
 
746
 
 
747
    simulated_time = 0
 
748
    _proxied_arguments = dict.fromkeys([
 
749
        "close", "getpeername", "getsockname", "getsockopt", "gettimeout",
 
750
        "setblocking", "setsockopt", "settimeout", "shutdown"])
 
751
 
 
752
    def __init__(self, sock, latency, bandwidth=1.0, 
 
753
                 really_sleep=True):
 
754
        """ 
 
755
        :param bandwith: simulated bandwith (MegaBit)
 
756
        :param really_sleep: If set to false, the SocketDelay will just
 
757
        increase a counter, instead of calling time.sleep. This is useful for
 
758
        unittesting the SocketDelay.
 
759
        """
 
760
        self.sock = sock
 
761
        self.latency = latency
 
762
        self.really_sleep = really_sleep
 
763
        self.time_per_byte = 1 / (bandwidth / 8.0 * 1024 * 1024) 
 
764
        self.new_roundtrip = False
 
765
 
 
766
    def sleep(self, s):
 
767
        if self.really_sleep:
 
768
            time.sleep(s)
 
769
        else:
 
770
            SocketDelay.simulated_time += s
 
771
 
 
772
    def __getattr__(self, attr):
 
773
        if attr in SocketDelay._proxied_arguments:
 
774
            return getattr(self.sock, attr)
 
775
        raise AttributeError("'SocketDelay' object has no attribute %r" %
 
776
                             attr)
 
777
 
 
778
    def dup(self):
 
779
        return SocketDelay(self.sock.dup(), self.latency, self.time_per_byte,
 
780
                           self._sleep)
 
781
 
 
782
    def recv(self, *args):
 
783
        data = self.sock.recv(*args)
 
784
        if data and self.new_roundtrip:
 
785
            self.new_roundtrip = False
 
786
            self.sleep(self.latency)
 
787
        self.sleep(len(data) * self.time_per_byte)
 
788
        return data
 
789
 
 
790
    def sendall(self, data, flags=0):
 
791
        if not self.new_roundtrip:
 
792
            self.new_roundtrip = True
 
793
            self.sleep(self.latency)
 
794
        self.sleep(len(data) * self.time_per_byte)
 
795
        return self.sock.sendall(data, flags)
 
796
 
 
797
    def send(self, data, flags=0):
 
798
        if not self.new_roundtrip:
 
799
            self.new_roundtrip = True
 
800
            self.sleep(self.latency)
 
801
        bytes_sent = self.sock.send(data, flags)
 
802
        self.sleep(bytes_sent * self.time_per_byte)
 
803
        return bytes_sent
 
804
 
 
805
 
1068
806
class SFTPServer(Server):
1069
807
    """Common code for SFTP server facilities."""
1070
808
 
1074
812
        self._server_homedir = None
1075
813
        self._listener = None
1076
814
        self._root = None
1077
 
        self._vendor = 'none'
 
815
        self._vendor = ssh.ParamikoVendor()
1078
816
        # sftp server logs
1079
817
        self.logs = []
 
818
        self.add_latency = 0
1080
819
 
1081
820
    def _get_sftp_url(self, path):
1082
821
        """Calculate an sftp url to this server for path."""
1086
825
        """StubServer uses this to log when a new server is created."""
1087
826
        self.logs.append(message)
1088
827
 
 
828
    def _run_server_entry(self, sock):
 
829
        """Entry point for all implementations of _run_server.
 
830
        
 
831
        If self.add_latency is > 0.000001 then sock is given a latency adding
 
832
        decorator.
 
833
        """
 
834
        if self.add_latency > 0.000001:
 
835
            sock = SocketDelay(sock, self.add_latency)
 
836
        return self._run_server(sock)
 
837
 
1089
838
    def _run_server(self, s):
1090
839
        ssh_server = paramiko.Transport(s)
1091
840
        key_file = pathjoin(self._homedir, 'test_rsa.key')
1103
852
        event.wait(5.0)
1104
853
    
1105
854
    def setUp(self):
1106
 
        global _ssh_vendor
1107
 
        self._original_vendor = _ssh_vendor
1108
 
        _ssh_vendor = self._vendor
 
855
        self._original_vendor = ssh._ssh_vendor
 
856
        ssh._ssh_vendor = self._vendor
1109
857
        if sys.platform == 'win32':
1110
858
            # Win32 needs to use the UNICODE api
1111
859
            self._homedir = getcwd()
1117
865
        self._root = '/'
1118
866
        if sys.platform == 'win32':
1119
867
            self._root = ''
1120
 
        self._listener = SocketListener(self._run_server)
 
868
        self._listener = SocketListener(self._run_server_entry)
1121
869
        self._listener.setDaemon(True)
1122
870
        self._listener.start()
1123
871
 
1124
872
    def tearDown(self):
1125
873
        """See bzrlib.transport.Server.tearDown."""
1126
 
        global _ssh_vendor
1127
874
        self._listener.stop()
1128
 
        _ssh_vendor = self._original_vendor
 
875
        ssh._ssh_vendor = self._original_vendor
1129
876
 
1130
877
    def get_bogus_url(self):
1131
878
        """See bzrlib.transport.Server.get_bogus_url."""
1150
897
 
1151
898
    def __init__(self):
1152
899
        super(SFTPServerWithoutSSH, self).__init__()
1153
 
        self._vendor = 'loopback'
 
900
        self._vendor = ssh.LoopbackVendor()
1154
901
 
1155
902
    def _run_server(self, sock):
 
903
        # Re-import these as locals, so that they're still accessible during
 
904
        # interpreter shutdown (when all module globals get set to None, leading
 
905
        # to confusing errors like "'NoneType' object has no attribute 'error'".
 
906
        import socket, errno
1156
907
        class FakeChannel(object):
1157
908
            def get_transport(self):
1158
909
                return self
1208
959
        super(SFTPSiblingAbsoluteServer, self).setUp()
1209
960
 
1210
961
 
 
962
def _sftp_connect(host, port, username, password):
 
963
    """Connect to the remote sftp server.
 
964
 
 
965
    :raises: a TransportError 'could not connect'.
 
966
 
 
967
    :returns: an paramiko.sftp_client.SFTPClient
 
968
 
 
969
    TODO: Raise a more reasonable ConnectionFailed exception
 
970
    """
 
971
    idx = (host, port, username)
 
972
    try:
 
973
        return _connected_hosts[idx]
 
974
    except KeyError:
 
975
        pass
 
976
    
 
977
    sftp = _sftp_connect_uncached(host, port, username, password)
 
978
    _connected_hosts[idx] = sftp
 
979
    return sftp
 
980
 
 
981
def _sftp_connect_uncached(host, port, username, password):
 
982
    vendor = ssh._get_ssh_vendor()
 
983
    sftp = vendor.connect_sftp(username, password, host, port)
 
984
    return sftp
 
985
 
 
986
 
1211
987
def get_test_permutations():
1212
988
    """Return the permutations to be used in testing."""
1213
989
    return [(SFTPTransport, SFTPAbsoluteServer),