~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/sftp.py

Dirty merge of the mainline

Show diffs side-by-side

added added

removed removed

Lines of Context:
34
34
                           FileExists, 
35
35
                           TransportNotPossible, NoSuchFile, PathNotChild,
36
36
                           TransportError,
37
 
                           LockError
 
37
                           LockError, ParamikoNotPresent
38
38
                           )
39
39
from bzrlib.osutils import pathjoin, fancy_rename
40
40
from bzrlib.trace import mutter, warning, error
43
43
 
44
44
try:
45
45
    import paramiko
46
 
except ImportError:
47
 
    error('The SFTP transport requires paramiko.')
48
 
    raise
 
46
except ImportError, e:
 
47
    raise ParamikoNotPresent(e)
49
48
else:
50
49
    from paramiko.sftp import (SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
51
50
                               SFTP_FLAG_EXCL, SFTP_FLAG_TRUNC,
144
143
        self.proc.wait()
145
144
 
146
145
 
 
146
class LoopbackSFTP(object):
 
147
    """Simple wrapper for a socket that pretends to be a paramiko Channel."""
 
148
 
 
149
    def __init__(self, sock):
 
150
        self.__socket = sock
 
151
 
 
152
    def send(self, data):
 
153
        return self.__socket.send(data)
 
154
 
 
155
    def recv(self, n):
 
156
        return self.__socket.recv(n)
 
157
 
 
158
    def recv_ready(self):
 
159
        return True
 
160
 
 
161
    def close(self):
 
162
        self.__socket.close()
 
163
 
 
164
 
147
165
SYSTEM_HOSTKEYS = {}
148
166
BZR_HOSTKEYS = {}
149
167
 
153
171
# X seconds. But that requires a lot more fanciness.
154
172
_connected_hosts = weakref.WeakValueDictionary()
155
173
 
 
174
 
156
175
def load_host_keys():
157
176
    """
158
177
    Load system host keys (probably doesn't work on windows) and any
170
189
        mutter('failed to load bzr host keys: ' + str(e))
171
190
        save_host_keys()
172
191
 
 
192
 
173
193
def save_host_keys():
174
194
    """
175
195
    Save "discovered" host keys in $(config)/ssh_host_keys/.
209
229
    def __del__(self):
210
230
        """Should this warn, or actually try to cleanup?"""
211
231
        if self.lock_file:
212
 
            warn("SFTPLock %r not explicitly unlocked" % (self.path,))
 
232
            warning("SFTPLock %r not explicitly unlocked" % (self.path,))
213
233
            self.unlock()
214
234
 
215
235
    def unlock(self):
224
244
            pass
225
245
 
226
246
 
227
 
 
228
247
class SFTPTransport (Transport):
229
248
    """
230
249
    Transport implementation for SFTP access.
597
616
        # that we have taken the lock.
598
617
        return SFTPLock(relpath, self)
599
618
 
600
 
 
601
619
    def _unparse_url(self, path=None):
602
620
        if path is None:
603
621
            path = self._path
604
622
        path = urllib.quote(path)
605
 
        if path.startswith('/'):
606
 
            path = '/%2F' + path[1:]
607
 
        else:
608
 
            path = '/' + path
 
623
        # handle homedir paths
 
624
        if not path.startswith('/'):
 
625
            path = "/~/" + path
609
626
        netloc = urllib.quote(self._host)
610
627
        if self._username is not None:
611
628
            netloc = '%s@%s' % (urllib.quote(self._username), netloc)
645
662
        # as a homedir relative path (the path begins with a double slash
646
663
        # if it is absolute).
647
664
        # see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
648
 
        if path.startswith('/'):
649
 
            path = path[1:]
650
 
 
 
665
        # RBC 20060118 we are not using this as its too user hostile. instead
 
666
        # we are following lftp and using /~/foo to mean '~/foo'.
 
667
        # handle homedir paths
 
668
        if path.startswith('/~/'):
 
669
            path = path[3:]
 
670
        elif path == '/~':
 
671
            path = ''
651
672
        return (username, password, host, port, path)
652
673
 
653
674
    def _parse_url(self, url):
671
692
            pass
672
693
        
673
694
        vendor = _get_ssh_vendor()
674
 
        if vendor != 'none':
 
695
        if vendor == 'loopback':
 
696
            sock = socket.socket()
 
697
            sock.connect((self._host, self._port))
 
698
            self._sftp = SFTPClient(LoopbackSFTP(sock))
 
699
        elif vendor != 'none':
675
700
            sock = SFTPSubprocess(self._host, vendor, self._port,
676
701
                                  self._username)
677
702
            self._sftp = SFTPClient(sock)
687
712
 
688
713
        try:
689
714
            t = paramiko.Transport((self._host, self._port or 22))
 
715
            t.set_log_channel('bzr.paramiko')
690
716
            t.start_client()
691
717
        except paramiko.SSHException, e:
692
718
            raise ConnectionError('Unable to reach SSH host %s:%d' %
867
893
        s, _ = self._socket.accept()
868
894
        # now close the listen socket
869
895
        self._socket.close()
870
 
        self._callback(s, self.stop_event)
871
 
    
 
896
        try:
 
897
            self._callback(s, self.stop_event)
 
898
        except socket.error:
 
899
            pass #Ignore socket errors
 
900
        except Exception, x:
 
901
            # probably a failed test
 
902
            warning('Exception from within unit test server thread: %r' % x)
 
903
 
872
904
    def stop(self):
873
905
        self.stop_event.set()
874
 
        # We should consider waiting for the other thread
875
 
        # to stop, because otherwise we get spurious
876
 
        #   bzr: ERROR: Socket exception: Connection reset by peer (54)
877
 
        # because the test suite finishes before the thread has a chance
878
 
        # to close. (Especially when only running a few tests)
879
 
        
880
 
        
 
906
        # use a timeout here, because if the test fails, the server thread may
 
907
        # never notice the stop_event.
 
908
        self.join(5.0)
 
909
 
 
910
 
881
911
class SFTPServer(Server):
882
912
    """Common code for SFTP server facilities."""
883
913
 
884
 
    def _get_sftp_url(self, path):
885
 
        """Calculate a sftp url to this server for path."""
886
 
        return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
887
 
 
888
914
    def __init__(self):
889
915
        self._original_vendor = None
890
916
        self._homedir = None
891
917
        self._server_homedir = None
892
918
        self._listener = None
893
919
        self._root = None
 
920
        self._vendor = 'none'
894
921
        # sftp server logs
895
922
        self.logs = []
896
923
 
 
924
    def _get_sftp_url(self, path):
 
925
        """Calculate an sftp url to this server for path."""
 
926
        return 'sftp://foo:bar@localhost:%d/%s' % (self._listener.port, path)
 
927
 
897
928
    def log(self, message):
898
 
        """What to do here? do we need this? Its for the StubServer.."""
 
929
        """StubServer uses this to log when a new server is created."""
899
930
        self.logs.append(message)
900
931
 
901
932
    def _run_server(self, s, stop_event):
912
943
        ssh_server.start_server(event, server)
913
944
        event.wait(5.0)
914
945
        stop_event.wait(30.0)
915
 
 
 
946
    
916
947
    def setUp(self):
917
 
        """See bzrlib.transport.Server.setUp."""
918
 
        # XXX: 20051124 jamesh
919
 
        # The tests currently pop up a password prompt when an external ssh
920
 
        # is used.  This forces the use of the paramiko implementation.
921
948
        global _ssh_vendor
922
949
        self._original_vendor = _ssh_vendor
923
 
        _ssh_vendor = 'none'
 
950
        _ssh_vendor = self._vendor
924
951
        self._homedir = os.getcwdu()
925
952
        if self._server_homedir is None:
926
953
            self._server_homedir = self._homedir
937
964
        _ssh_vendor = self._original_vendor
938
965
 
939
966
 
940
 
class SFTPAbsoluteServer(SFTPServer):
 
967
class SFTPServerWithoutSSH(SFTPServer):
 
968
    """
 
969
    Common code for an SFTP server over a clear TCP loopback socket,
 
970
    instead of over an SSH secured socket.
 
971
    """
 
972
 
 
973
    def __init__(self):
 
974
        super(SFTPServerWithoutSSH, self).__init__()
 
975
        self._vendor = 'loopback'
 
976
 
 
977
    def _run_server(self, sock, stop_event):
 
978
        class FakeChannel(object):
 
979
            def get_transport(self):
 
980
                return self
 
981
            def get_log_channel(self):
 
982
                return 'paramiko'
 
983
            def get_name(self):
 
984
                return '1'
 
985
            def get_hexdump(self):
 
986
                return False
 
987
 
 
988
        server = paramiko.SFTPServer(FakeChannel(), 'sftp', StubServer(self), StubSFTPServer,
 
989
                                     root=self._root, home=self._server_homedir)
 
990
        server.start_subsystem('sftp', None, sock)
 
991
        server.finish_subsystem()
 
992
 
 
993
 
 
994
class SFTPAbsoluteServer(SFTPServerWithoutSSH):
941
995
    """A test server for sftp transports, using absolute urls."""
942
996
 
943
997
    def get_url(self):
944
998
        """See bzrlib.transport.Server.get_url."""
945
 
        return self._get_sftp_url("%%2f%s" % 
946
 
                urlescape(self._homedir[1:]))
947
 
 
948
 
 
949
 
class SFTPHomeDirServer(SFTPServer):
 
999
        return self._get_sftp_url(urlescape(self._homedir[1:]))
 
1000
 
 
1001
 
 
1002
class SFTPHomeDirServer(SFTPServerWithoutSSH):
950
1003
    """A test server for sftp transports, using homedir relative urls."""
951
1004
 
952
1005
    def get_url(self):
953
1006
        """See bzrlib.transport.Server.get_url."""
954
 
        return self._get_sftp_url("")
 
1007
        return self._get_sftp_url("~/")
955
1008
 
956
1009
 
957
1010
class SFTPSiblingAbsoluteServer(SFTPAbsoluteServer):