~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/sftp.py

[merge] Robey Pointer - some sftp fixes, and an http patch

Show diffs side-by-side

added added

removed removed

Lines of Context:
36
36
from bzrlib.config import config_dir
37
37
from bzrlib.trace import mutter, warning, error
38
38
from bzrlib.transport import Transport, register_transport
39
 
from bzrlib.ui import ui_factory
40
39
 
41
40
try:
42
41
    import paramiko
54
53
if 'sftp' not in urlparse.uses_netloc: urlparse.uses_netloc.append('sftp')
55
54
 
56
55
 
57
 
_close_fds = True
58
 
if sys.platform == 'win32':
59
 
    # close_fds not supported on win32
60
 
    _close_fds = False
61
 
 
62
56
_ssh_vendor = None
63
57
def _get_ssh_vendor():
64
58
    """Find out what version of SSH is on the system."""
70
64
 
71
65
    try:
72
66
        p = subprocess.Popen(['ssh', '-V'],
73
 
                             close_fds=_close_fds,
 
67
                             close_fds=True,
74
68
                             stdin=subprocess.PIPE,
75
69
                             stdout=subprocess.PIPE,
76
70
                             stderr=subprocess.PIPE)
119
113
                args.extend(['-l', user])
120
114
            args.extend(['-s', 'sftp', hostname])
121
115
 
122
 
        self.proc = subprocess.Popen(args, close_fds=_close_fds,
 
116
        self.proc = subprocess.Popen(args, close_fds=True,
123
117
                                     stdin=subprocess.PIPE,
124
118
                                     stdout=subprocess.PIPE)
125
119
 
287
281
 
288
282
    def relpath(self, abspath):
289
283
        username, password, host, port, path = self._split_url(abspath)
290
 
        error = []
291
 
        if (username != self._username):
292
 
            error.append('username mismatch')
293
 
        if (host != self._host):
294
 
            error.append('host mismatch')
295
 
        if (port != self._port):
296
 
            error.append('port mismatch')
297
 
        if (not path.startswith(self._path)):
298
 
            error.append('path mismatch')
299
 
        if error:
300
 
            raise NonRelativePath('path %r is not under base URL %r: %s'
301
 
                           % (abspath, self.base, ', '.join(error)))
 
284
        if (username != self._username or host != self._host or
 
285
            port != self._port or not path.startswith(self._path)):
 
286
            raise NonRelativePath('path %r is not under base URL %r'
 
287
                           % (abspath, self.base))
302
288
        pl = len(self._path)
303
289
        return path[pl:].lstrip('/')
304
290
 
629
615
    def _parse_url(self, url):
630
616
        (self._username, self._password,
631
617
         self._host, self._port, self._path) = self._split_url(url)
 
618
         if self._port is None:
 
619
             self._port = 22
632
620
 
633
621
    def _sftp_connect(self):
634
622
        """Connect to the remote sftp server.
690
678
                (self._host, our_server_key_hex, server_key_hex),
691
679
                ['Try editing %s or %s' % (filename1, filename2)])
692
680
 
693
 
        self._sftp_auth(t)
 
681
        self._sftp_auth(t, self._username or getpass.getuser(), self._host)
694
682
        
695
683
        try:
696
684
            self._sftp = t.open_sftp_client()
698
686
            raise BzrError('Unable to find path %s on SFTP server %s' % \
699
687
                (self._path, self._host))
700
688
 
701
 
    def _sftp_auth(self, transport):
702
 
        # paramiko requires a username, but it might be none if nothing was supplied
703
 
        # use the local username, just in case.
704
 
        # We don't override self._username, because if we aren't using paramiko,
705
 
        # the username might be specified in ~/.ssh/config and we don't want to
706
 
        # force it to something else
707
 
        # Also, it would mess up the self.relpath() functionality
708
 
        username = self._username or getpass.getuser()
709
 
 
 
689
    def _sftp_auth(self, transport, username, host):
710
690
        agent = paramiko.Agent()
711
691
        for key in agent.get_keys():
712
692
            mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
713
693
            try:
714
 
                transport.auth_publickey(username, key)
 
694
                transport.auth_publickey(self._username, key)
715
695
                return
716
696
            except paramiko.SSHException, e:
717
697
                pass
718
698
        
719
699
        # okay, try finding id_rsa or id_dss?  (posix only)
720
 
        if self._try_pkey_auth(transport, paramiko.RSAKey, username, 'id_rsa'):
721
 
            return
722
 
        if self._try_pkey_auth(transport, paramiko.DSSKey, username, 'id_dsa'):
723
 
            return
724
 
 
 
700
        if self._try_pkey_auth(transport, paramiko.RSAKey, 'id_rsa'):
 
701
            return
 
702
        if self._try_pkey_auth(transport, paramiko.DSSKey, 'id_dsa'):
 
703
            return
725
704
 
726
705
        if self._password:
727
706
            try:
728
 
                transport.auth_password(username, self._password)
 
707
                transport.auth_password(self._username, self._password)
729
708
                return
730
709
            except paramiko.SSHException, e:
731
710
                pass
732
711
 
733
 
            # FIXME: Don't keep a password held in memory if you can help it
734
 
            #self._password = None
735
 
 
736
712
        # give up and ask for a password
737
 
        password = ui_factory.get_password(prompt='SSH %(user)s@%(host)s password',
738
 
                                           user=username, host=self._host)
 
713
        # FIXME: shouldn't be implementing UI this deep into bzrlib
 
714
        enc = sys.stdout.encoding
 
715
        password = getpass.getpass('SSH %s@%s password: ' %
 
716
            (self._username.encode(enc, 'replace'), self._host.encode(enc, 'replace')))
739
717
        try:
740
 
            transport.auth_password(username, password)
 
718
            transport.auth_password(self._username, password)
741
719
        except paramiko.SSHException:
742
720
            raise SFTPTransportError('Unable to authenticate to SSH host as %s@%s' % \
743
 
                (username, self._host))
 
721
                (self._username, self._host))
744
722
 
745
 
    def _try_pkey_auth(self, transport, pkey_class, username, filename):
 
723
    def _try_pkey_auth(self, transport, pkey_class, filename):
746
724
        filename = os.path.expanduser('~/.ssh/' + filename)
747
725
        try:
748
726
            key = pkey_class.from_private_key_file(filename)
749
 
            transport.auth_publickey(username, key)
 
727
            transport.auth_publickey(self._username, key)
750
728
            return True
751
729
        except paramiko.PasswordRequiredException:
752
 
            password = ui_factory.get_password(prompt='SSH %(filename)s password',
753
 
                                               filename=filename)
 
730
            # FIXME: shouldn't be implementing UI this deep into bzrlib
 
731
            enc = sys.stdout.encoding
 
732
            password = getpass.getpass('SSH %s password: ' % 
 
733
                (os.path.basename(filename).encode(enc, 'replace'),))
754
734
            try:
755
735
                key = pkey_class.from_private_key_file(filename, password)
756
 
                transport.auth_publickey(username, key)
 
736
                transport.auth_publickey(self._username, key)
757
737
                return True
758
738
            except paramiko.SSHException:
759
739
                mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))