369
372
file_existed = True
371
374
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)
378
self._sftp.rename(tmp_abspath, final_path)
380
self._translate_io_exception(e, relpath)
381
except paramiko.SSHException, x:
382
raise SFTPTransportError('Unable to rename into file %r' % (path,), x)
388
self._sftp.unlink(tmp_safety)
390
self._sftp.rename(tmp_safety, final_path)
382
392
def iter_files_recursive(self):
383
393
"""Walk the relative paths of all files in this transport."""
430
440
"""Copy the item at rel_from to the location at rel_to"""
431
441
path_from = self._abspath(rel_from)
432
442
path_to = self._abspath(rel_to)
443
self._copy_abspaths(path_from, path_to)
445
def _copy_abspaths(self, path_from, path_to):
446
"""Copy files given an absolute path
448
:param path_from: Path on remote server to read
449
:param path_to: Path on remote server to write
452
TODO: Should the destination location be atomically created?
453
This has not been specified
454
TODO: This should use some sort of remote copy, rather than
455
pulling the data locally, and then writing it remotely
434
458
fin = self._sftp.file(path_from, 'rb')
444
468
except (IOError, paramiko.SSHException), x:
445
469
raise SFTPTransportError('Unable to copy %r to %r' % (path_from, path_to), x)
471
def copy_to(self, relpaths, other, pb=None):
472
"""Copy a set of entries from self into another Transport.
474
:param relpaths: A list/generator of entries to be copied.
476
if isinstance(other, SFTPTransport) and other._sftp is self._sftp:
477
# Both from & to are on the same remote filesystem
478
# We can use a remote copy, instead of pulling locally, and pushing
480
total = self._get_total(relpaths)
482
for path in relpaths:
483
path_from = self._abspath(relpath)
484
path_to = other._abspath(relpath)
485
self._update_pb(pb, 'copy-to', count, total)
486
self._copy_abspaths(path_from, path_to)
490
return super(SFTPTransport, self).copy_to(relpaths, other, pb=pb)
492
# The dummy implementation just does a simple get + put
493
def copy_entry(path):
494
other.put(path, self.get(path))
496
return self._iterate_over(relpaths, copy_entry, pb, 'copy_to', expand=False)
447
498
def move(self, rel_from, rel_to):
448
499
"""Move the item at rel_from to the location at rel_to"""
449
500
path_from = self._abspath(rel_from)
565
616
def _parse_url(self, url):
566
617
(self._username, self._password,
567
618
self._host, self._port, self._path) = self._split_url(url)
619
if self._port is None:
569
622
def _sftp_connect(self):
623
"""Connect to the remote sftp server.
624
After this, self._sftp should have a valid connection (or
625
we raise an SFTPTransportError 'could not connect').
627
TODO: Raise a more reasonable ConnectionFailed exception
629
global _connected_hosts
631
idx = (self._host, self._port, self._username)
633
self._sftp = _connected_hosts[idx]
570
638
vendor = _get_ssh_vendor()
571
if (self._path is None) or (self._path == ''):
575
self._path = urllib.unquote(self._path[1:])
576
639
if vendor != 'none':
577
640
sock = SFTPSubprocess(self._host, self._port, self._username)
578
641
self._sftp = SFTPClient(sock)
580
643
self._paramiko_connect()
645
_connected_hosts[idx] = self._sftp
582
647
def _paramiko_connect(self):
583
648
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
588
t = paramiko.Transport((self._host, self._port or 22))
653
t = paramiko.Transport((self._host, self._port))
590
655
except paramiko.SSHException:
591
656
raise SFTPTransportError('Unable to reach SSH host %s:%d' % (self._host, self._port))
622
687
raise BzrError('Unable to find path %s on SFTP server %s' % \
623
688
(self._path, self._host))
625
def _sftp_auth(self, transport, username, host):
690
def _sftp_auth(self, transport):
691
# paramiko requires a username, but it might be none if nothing was supplied
692
# use the local username, just in case.
693
# We don't override self._username, because if we aren't using paramiko,
694
# the username might be specified in ~/.ssh/config and we don't want to
695
# force it to something else
696
# Also, it would mess up the self.relpath() functionality
697
username = self._username or getpass.getuser()
626
699
agent = paramiko.Agent()
627
700
for key in agent.get_keys():
628
701
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
630
transport.auth_publickey(self._username, key)
703
transport.auth_publickey(username, key)
632
705
except paramiko.SSHException, e:
635
708
# 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'):
709
if self._try_pkey_auth(transport, paramiko.RSAKey, username, 'id_rsa'):
711
if self._try_pkey_auth(transport, paramiko.DSSKey, username, 'id_dsa'):
641
715
if self._password:
643
transport.auth_password(self._username, self._password)
717
transport.auth_password(username, self._password)
645
719
except paramiko.SSHException, e:
722
# FIXME: Don't keep a password held in memory if you can help it
723
#self._password = None
648
725
# 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')))
726
password = ui_factory.get_password(prompt='SSH %(user)s@%(host)s password',
727
user=username, host=self._host)
654
transport.auth_password(self._username, password)
729
transport.auth_password(username, password)
655
730
except paramiko.SSHException:
656
731
raise SFTPTransportError('Unable to authenticate to SSH host as %s@%s' % \
657
(self._username, self._host))
732
(username, self._host))
659
def _try_pkey_auth(self, transport, pkey_class, filename):
734
def _try_pkey_auth(self, transport, pkey_class, username, filename):
660
735
filename = os.path.expanduser('~/.ssh/' + filename)
662
737
key = pkey_class.from_private_key_file(filename)
663
transport.auth_publickey(self._username, key)
738
transport.auth_publickey(username, key)
665
740
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'),))
741
password = ui_factory.get_password(prompt='SSH %(filename)s password',
671
744
key = pkey_class.from_private_key_file(filename, password)
672
transport.auth_publickey(self._username, key)
745
transport.auth_publickey(username, key)
674
747
except paramiko.SSHException:
675
748
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))