274
288
def relpath(self, abspath):
275
289
username, password, host, port, path = self._split_url(abspath)
276
if (username != self._username or host != self._host or
277
port != self._port or not path.startswith(self._path)):
278
raise NonRelativePath('path %r is not under base URL %r'
279
% (abspath, self.base))
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')
300
raise NonRelativePath('path %r is not under base URL %r: %s'
301
% (abspath, self.base, ', '.join(error)))
280
302
pl = len(self._path)
281
303
return path[pl:].lstrip('/')
369
385
file_existed = True
371
387
file_existed = False
373
self._sftp.rename(tmp_abspath, final_path)
375
self._translate_io_exception(e, relpath)
376
except paramiko.SSHException, x:
377
raise SFTPTransportError('Unable to rename into file %r'
380
self._sftp.unlink(tmp_safety)
391
self._sftp.rename(tmp_abspath, final_path)
393
self._translate_io_exception(e, relpath)
394
except paramiko.SSHException, x:
395
raise SFTPTransportError('Unable to rename into file %r' % (path,), x)
401
self._sftp.unlink(tmp_safety)
403
self._sftp.rename(tmp_safety, final_path)
382
405
def iter_files_recursive(self):
383
406
"""Walk the relative paths of all files in this transport."""
430
453
"""Copy the item at rel_from to the location at rel_to"""
431
454
path_from = self._abspath(rel_from)
432
455
path_to = self._abspath(rel_to)
456
self._copy_abspaths(path_from, path_to)
458
def _copy_abspaths(self, path_from, path_to):
459
"""Copy files given an absolute path
461
:param path_from: Path on remote server to read
462
:param path_to: Path on remote server to write
465
TODO: Should the destination location be atomically created?
466
This has not been specified
467
TODO: This should use some sort of remote copy, rather than
468
pulling the data locally, and then writing it remotely
434
471
fin = self._sftp.file(path_from, 'rb')
444
481
except (IOError, paramiko.SSHException), x:
445
482
raise SFTPTransportError('Unable to copy %r to %r' % (path_from, path_to), x)
484
def copy_to(self, relpaths, other, pb=None):
485
"""Copy a set of entries from self into another Transport.
487
:param relpaths: A list/generator of entries to be copied.
489
if isinstance(other, SFTPTransport) and other._sftp is self._sftp:
490
# Both from & to are on the same remote filesystem
491
# We can use a remote copy, instead of pulling locally, and pushing
493
total = self._get_total(relpaths)
495
for path in relpaths:
496
path_from = self._abspath(relpath)
497
path_to = other._abspath(relpath)
498
self._update_pb(pb, 'copy-to', count, total)
499
self._copy_abspaths(path_from, path_to)
503
return super(SFTPTransport, self).copy_to(relpaths, other, pb=pb)
505
# The dummy implementation just does a simple get + put
506
def copy_entry(path):
507
other.put(path, self.get(path))
509
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
447
511
def move(self, rel_from, rel_to):
448
512
"""Move the item at rel_from to the location at rel_to"""
449
513
path_from = self._abspath(rel_from)
567
631
self._host, self._port, self._path) = self._split_url(url)
569
633
def _sftp_connect(self):
634
"""Connect to the remote sftp server.
635
After this, self._sftp should have a valid connection (or
636
we raise an SFTPTransportError 'could not connect').
638
TODO: Raise a more reasonable ConnectionFailed exception
640
global _connected_hosts
642
idx = (self._host, self._port, self._username)
644
self._sftp = _connected_hosts[idx]
570
649
vendor = _get_ssh_vendor()
571
if (self._path is None) or (self._path == ''):
575
self._path = urllib.unquote(self._path[1:])
576
650
if vendor != 'none':
577
651
sock = SFTPSubprocess(self._host, self._port, self._username)
578
652
self._sftp = SFTPClient(sock)
580
654
self._paramiko_connect()
656
_connected_hosts[idx] = self._sftp
582
658
def _paramiko_connect(self):
583
659
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
588
t = paramiko.Transport((self._host, self._port or 22))
664
t = paramiko.Transport((self._host, self._port))
590
666
except paramiko.SSHException:
591
667
raise SFTPTransportError('Unable to reach SSH host %s:%d' % (self._host, self._port))
622
698
raise BzrError('Unable to find path %s on SFTP server %s' % \
623
699
(self._path, self._host))
625
def _sftp_auth(self, transport, username, host):
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()
626
710
agent = paramiko.Agent()
627
711
for key in agent.get_keys():
628
712
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
630
transport.auth_publickey(self._username, key)
714
transport.auth_publickey(username, key)
632
716
except paramiko.SSHException, e:
635
719
# okay, try finding id_rsa or id_dss? (posix only)
636
if self._try_pkey_auth(transport, paramiko.RSAKey, 'id_rsa'):
638
if self._try_pkey_auth(transport, paramiko.DSSKey, 'id_dsa'):
720
if self._try_pkey_auth(transport, paramiko.RSAKey, username, 'id_rsa'):
722
if self._try_pkey_auth(transport, paramiko.DSSKey, username, 'id_dsa'):
641
726
if self._password:
643
transport.auth_password(self._username, self._password)
728
transport.auth_password(username, self._password)
645
730
except paramiko.SSHException, e:
733
# FIXME: Don't keep a password held in memory if you can help it
734
#self._password = None
648
736
# give up and ask for a password
649
# FIXME: shouldn't be implementing UI this deep into bzrlib
650
enc = sys.stdout.encoding
651
password = getpass.getpass('SSH %s@%s password: ' %
652
(self._username.encode(enc, 'replace'), self._host.encode(enc, 'replace')))
737
password = ui_factory.get_password(prompt='SSH %(user)s@%(host)s password',
738
user=username, host=self._host)
654
transport.auth_password(self._username, password)
740
transport.auth_password(username, password)
655
741
except paramiko.SSHException:
656
742
raise SFTPTransportError('Unable to authenticate to SSH host as %s@%s' % \
657
(self._username, self._host))
743
(username, self._host))
659
def _try_pkey_auth(self, transport, pkey_class, filename):
745
def _try_pkey_auth(self, transport, pkey_class, username, filename):
660
746
filename = os.path.expanduser('~/.ssh/' + filename)
662
748
key = pkey_class.from_private_key_file(filename)
663
transport.auth_publickey(self._username, key)
749
transport.auth_publickey(username, key)
665
751
except paramiko.PasswordRequiredException:
666
# FIXME: shouldn't be implementing UI this deep into bzrlib
667
enc = sys.stdout.encoding
668
password = getpass.getpass('SSH %s password: ' %
669
(os.path.basename(filename).encode(enc, 'replace'),))
752
password = ui_factory.get_password(prompt='SSH %(filename)s password',
671
755
key = pkey_class.from_private_key_file(filename, password)
672
transport.auth_publickey(self._username, key)
756
transport.auth_publickey(username, key)
674
758
except paramiko.SSHException:
675
759
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))