~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/sftp.py

first cut at merge from integration.

Show diffs side-by-side

added added

removed removed

Lines of Context:
144
144
        self.proc.wait()
145
145
 
146
146
 
 
147
class LoopbackSFTP(object):
 
148
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
 
149
 
 
150
    def __init__(self, sock):
 
151
        self.__socket = sock
 
152
 
 
153
    def send(self, data):
 
154
        return self.__socket.send(data)
 
155
 
 
156
    def recv(self, n):
 
157
        return self.__socket.recv(n)
 
158
 
 
159
    def recv_ready(self):
 
160
        return True
 
161
 
 
162
    def close(self):
 
163
        self.__socket.close()
 
164
 
 
165
 
147
166
SYSTEM_HOSTKEYS = {}
148
167
BZR_HOSTKEYS = {}
149
168
 
153
172
# X seconds. But that requires a lot more fanciness.
154
173
_connected_hosts = weakref.WeakValueDictionary()
155
174
 
 
175
 
156
176
def load_host_keys():
157
177
    """
158
178
    Load system host keys (probably doesn't work on windows) and any
170
190
        mutter('failed to load bzr host keys: ' + str(e))
171
191
        save_host_keys()
172
192
 
 
193
 
173
194
def save_host_keys():
174
195
    """
175
196
    Save "discovered" host keys in $(config)/ssh_host_keys/.
209
230
    def __del__(self):
210
231
        """Should this warn, or actually try to cleanup?"""
211
232
        if self.lock_file:
212
 
            warn("SFTPLock %r not explicitly unlocked" % (self.path,))
 
233
            warning("SFTPLock %r not explicitly unlocked" % (self.path,))
213
234
            self.unlock()
214
235
 
215
236
    def unlock(self):
224
245
            pass
225
246
 
226
247
 
227
 
 
228
248
class SFTPTransport (Transport):
229
249
    """
230
250
    Transport implementation for SFTP access.
633
653
            pass
634
654
        
635
655
        vendor = _get_ssh_vendor()
636
 
        if vendor != 'none':
 
656
        if vendor == 'loopback':
 
657
            sock = socket.socket()
 
658
            sock.connect((self._host, self._port))
 
659
            self._sftp = SFTPClient(LoopbackSFTP(sock))
 
660
        elif vendor != 'none':
637
661
            sock = SFTPSubprocess(self._host, vendor, self._port,
638
662
                                  self._username)
639
663
            self._sftp = SFTPClient(sock)
649
673
 
650
674
        try:
651
675
            t = paramiko.Transport((self._host, self._port or 22))
 
676
            t.set_log_channel('bzr.paramiko')
652
677
            t.start_client()
653
678
        except paramiko.SSHException, e:
654
679
            raise ConnectionError('Unable to reach SSH host %s:%d' %
829
854
        s, _ = self._socket.accept()
830
855
        # now close the listen socket
831
856
        self._socket.close()
832
 
        self._callback(s, self.stop_event)
833
 
    
 
857
        try:
 
858
            self._callback(s, self.stop_event)
 
859
        except Exception, x:
 
860
            # probably a failed test
 
861
            warning('Exception from within unit test server thread: %r' % x)
 
862
 
834
863
    def stop(self):
835
864
        self.stop_event.set()
836
 
        # We should consider waiting for the other thread
837
 
        # to stop, because otherwise we get spurious
838
 
        #   bzr: ERROR: Socket exception: Connection reset by peer (54)
839
 
        # because the test suite finishes before the thread has a chance
840
 
        # to close. (Especially when only running a few tests)
841
 
        
842
 
        
 
865
        # use a timeout here, because if the test fails, the server thread may
 
866
        # never notice the stop_event.
 
867
        self.join(5.0)
 
868
 
 
869
 
843
870
class SFTPServer(Server):
844
871
    """Common code for SFTP server facilities."""
845
872
 
846
 
    def _get_sftp_url(self, path):
847
 
        """Calculate a sftp url to this server for path."""
848
 
        return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
849
 
 
850
873
    def __init__(self):
851
874
        self._original_vendor = None
852
875
        self._homedir = None
853
876
        self._server_homedir = None
854
877
        self._listener = None
855
878
        self._root = None
 
879
        self._vendor = 'none'
856
880
        # sftp server logs
857
881
        self.logs = []
858
882
 
 
883
    def _get_sftp_url(self, path):
 
884
        """Calculate an sftp url to this server for path."""
 
885
        return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
 
886
 
859
887
    def log(self, message):
860
 
        """What to do here? do we need this? Its for the StubServer.."""
 
888
        """StubServer uses this to log when a new server is created."""
861
889
        self.logs.append(message)
862
890
 
863
891
    def _run_server(self, s, stop_event):
874
902
        ssh_server.start_server(event, server)
875
903
        event.wait(5.0)
876
904
        stop_event.wait(30.0)
877
 
 
 
905
    
878
906
    def setUp(self):
879
 
        """See bzrlib.transport.Server.setUp."""
880
 
        # XXX: 20051124 jamesh
881
 
        # The tests currently pop up a password prompt when an external ssh
882
 
        # is used.  This forces the use of the paramiko implementation.
883
907
        global _ssh_vendor
884
908
        self._original_vendor = _ssh_vendor
885
 
        _ssh_vendor = 'none'
 
909
        _ssh_vendor = self._vendor
886
910
        self._homedir = os.getcwdu()
887
911
        if self._server_homedir is None:
888
912
            self._server_homedir = self._homedir
899
923
        _ssh_vendor = self._original_vendor
900
924
 
901
925
 
902
 
class SFTPAbsoluteServer(SFTPServer):
 
926
class SFTPServerWithoutSSH(SFTPServer):
 
927
    """
 
928
    Common code for an SFTP server over a clear TCP loopback socket,
 
929
    instead of over an SSH secured socket.
 
930
    """
 
931
 
 
932
    def __init__(self):
 
933
        super(SFTPServerWithoutSSH, self).__init__()
 
934
        self._vendor = 'loopback'
 
935
 
 
936
    def _run_server(self, sock, stop_event):
 
937
        class FakeChannel(object):
 
938
            def get_transport(self):
 
939
                return self
 
940
            def get_log_channel(self):
 
941
                return 'paramiko'
 
942
            def get_name(self):
 
943
                return '1'
 
944
            def get_hexdump(self):
 
945
                return False
 
946
 
 
947
        server = paramiko.SFTPServer(FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
 
948
                                     root=self._root, home=self._server_homedir)
 
949
        server.start_subsystem('sftp', None, sock)
 
950
        server.finish_subsystem()
 
951
 
 
952
 
 
953
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
903
954
    """A test server for sftp transports, using absolute urls."""
904
955
 
905
956
    def get_url(self):
907
958
        return self._get_sftp_url(urlescape(self._homedir[1:]))
908
959
 
909
960
 
910
 
class SFTPHomeDirServer(SFTPServer):
 
961
class SFTPHomeDirServer(SFTPServerWithoutSSH):
911
962
    """A test server for sftp transports, using homedir relative urls."""
912
963
 
913
964
    def get_url(self):