63
66
CMD_HANDLE, CMD_OPEN)
64
67
from paramiko.sftp_attr import SFTPAttributes
65
68
from paramiko.sftp_file import SFTPFile
66
from paramiko.sftp_client import SFTPClient
69
71
register_urlparse_netloc_protocol('sftp')
73
# TODO: This should possibly ignore SIGHUP as well, but bzr currently
74
# doesn't handle it itself.
75
# <https://launchpad.net/products/bzr/+bug/41433/+index>
77
signal.signal(signal.SIGINT, signal.SIG_IGN)
80
def os_specific_subprocess_params():
81
"""Get O/S specific subprocess parameters."""
82
if sys.platform == 'win32':
83
# setting the process group and closing fds is not supported on
87
# We close fds other than the pipes as the child process does not need
90
# We also set the child process to ignore SIGINT. Normally the signal
91
# would be sent to every process in the foreground process group, but
92
# this causes it to be seen only by bzr and not by ssh. Python will
93
# generate a KeyboardInterrupt in bzr, and we will then have a chance
94
# to release locks or do other cleanup over ssh before the connection
96
# <https://launchpad.net/products/bzr/+bug/5987>
98
# Running it in a separate process group is not good because then it
99
# can't get non-echoed input of a password or passphrase.
100
# <https://launchpad.net/products/bzr/+bug/40508>
101
return {'preexec_fn': _ignore_sigint,
106
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
107
# don't use prefetch unless paramiko version >= 1.5.5 (there were bugs earlier)
108
_default_do_prefetch = (_paramiko_version >= (1, 5, 5))
110
# Paramiko 1.5 tries to open a socket.AF_UNIX in order to connect
111
# to ssh-agent. That attribute doesn't exist on win32 (it does in cygwin)
112
# so we get an AttributeError exception. So we will not try to
113
# connect to an agent if we are on win32 and using Paramiko older than 1.6
114
_use_ssh_agent = (sys.platform != 'win32' or _paramiko_version >= (1, 6, 0))
118
def _get_ssh_vendor():
119
"""Find out what version of SSH is on the system."""
121
if _ssh_vendor is not None:
126
if 'BZR_SSH' in os.environ:
127
_ssh_vendor = os.environ['BZR_SSH']
128
if _ssh_vendor == 'paramiko':
133
p = subprocess.Popen(['ssh', '-V'],
134
stdin=subprocess.PIPE,
135
stdout=subprocess.PIPE,
136
stderr=subprocess.PIPE,
137
**os_specific_subprocess_params())
138
returncode = p.returncode
139
stdout, stderr = p.communicate()
143
if 'OpenSSH' in stderr:
144
mutter('ssh implementation is OpenSSH')
145
_ssh_vendor = 'openssh'
146
elif 'SSH Secure Shell' in stderr:
147
mutter('ssh implementation is SSH Corp.')
150
if _ssh_vendor != 'none':
153
# XXX: 20051123 jamesh
154
# A check for putty's plink or lsh would go here.
156
mutter('falling back to paramiko implementation')
160
class SFTPSubprocess:
161
"""A socket-like object that talks to an ssh subprocess via pipes."""
162
def __init__(self, hostname, vendor, port=None, user=None):
163
assert vendor in ['openssh', 'ssh']
164
if vendor == 'openssh':
166
'-oForwardX11=no', '-oForwardAgent=no',
167
'-oClearAllForwardings=yes', '-oProtocol=2',
168
'-oNoHostAuthenticationForLocalhost=yes']
170
args.extend(['-p', str(port)])
172
args.extend(['-l', user])
173
args.extend(['-s', hostname, 'sftp'])
174
elif vendor == 'ssh':
177
args.extend(['-p', str(port)])
179
args.extend(['-l', user])
180
args.extend(['-s', 'sftp', hostname])
182
self.proc = subprocess.Popen(args,
183
stdin=subprocess.PIPE,
184
stdout=subprocess.PIPE,
185
**os_specific_subprocess_params())
187
def send(self, data):
188
return os.write(self.proc.stdin.fileno(), data)
190
def recv_ready(self):
191
# TODO: jam 20051215 this function is necessary to support the
192
# pipelined() function. In reality, it probably should use
193
# poll() or select() to actually return if there is data
194
# available, otherwise we probably don't get any benefit
197
def recv(self, count):
198
return os.read(self.proc.stdout.fileno(), count)
201
self.proc.stdin.close()
202
self.proc.stdout.close()
206
class LoopbackSFTP(object):
207
"""Simple wrapper for a socket that pretends to be a paramiko Channel."""
209
def __init__(self, sock):
212
def send(self, data):
213
return self.__socket.send(data)
216
return self.__socket.recv(n)
218
def recv_ready(self):
222
self.__socket.close()
228
74
# This is a weakref dictionary, so that we can reuse connections
229
75
# that are still active. Long term, it might be nice to have some
230
76
# sort of expiration policy, such as disconnect if inactive for
231
77
# X seconds. But that requires a lot more fanciness.
232
78
_connected_hosts = weakref.WeakValueDictionary()
81
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
82
# don't use prefetch unless paramiko version >= 1.5.5 (there were bugs earlier)
83
_default_do_prefetch = (_paramiko_version >= (1, 5, 5))
234
86
def clear_connection_cache():
235
87
"""Remove all hosts from the SFTP connection cache.
239
91
_connected_hosts.clear()
242
def load_host_keys():
244
Load system host keys (probably doesn't work on windows) and any
245
"discovered" keys from previous sessions.
247
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
249
SYSTEM_HOSTKEYS = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
251
mutter('failed to load system host keys: ' + str(e))
252
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
254
BZR_HOSTKEYS = paramiko.util.load_host_keys(bzr_hostkey_path)
256
mutter('failed to load bzr host keys: ' + str(e))
260
def save_host_keys():
262
Save "discovered" host keys in $(config)/ssh_host_keys/.
264
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
265
bzr_hostkey_path = pathjoin(config_dir(), 'ssh_host_keys')
266
ensure_config_dir_exists()
269
f = open(bzr_hostkey_path, 'w')
270
f.write('# SSH host keys collected by bzr\n')
271
for hostname, keys in BZR_HOSTKEYS.iteritems():
272
for keytype, key in keys.iteritems():
273
f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
276
mutter('failed to save bzr host keys: ' + str(e))
279
94
class SFTPLock(object):
280
"""This fakes a lock in a remote location."""
95
"""This fakes a lock in a remote location.
97
A present lock is indicated just by the existence of a file. This
98
doesn't work well on all transports and they are only used in
99
deprecated storage formats.
281
102
__slots__ = ['path', 'lock_path', 'lock_file', 'transport']
282
104
def __init__(self, path, transport):
283
105
assert isinstance(transport, SFTPTransport)
604
439
# raise the original with its traceback if we can.
442
def _put_non_atomic_helper(self, relpath, writer, mode=None,
443
create_parent_dir=False,
445
abspath = self._remote_path(relpath)
447
# TODO: jam 20060816 paramiko doesn't publicly expose a way to
448
# set the file mode at create time. If it does, use it.
449
# But for now, we just chmod later anyway.
451
def _open_and_write_file():
452
"""Try to open the target file, raise error on failure"""
456
fout = self._sftp.file(abspath, mode='wb')
457
fout.set_pipelined(True)
459
except (paramiko.SSHException, IOError), e:
460
self._translate_io_exception(e, abspath,
463
# This is designed to chmod() right before we close.
464
# Because we set_pipelined() earlier, theoretically we might
465
# avoid the round trip for fout.close()
467
self._sftp.chmod(abspath, mode)
472
if not create_parent_dir:
473
_open_and_write_file()
476
# Try error handling to create the parent directory if we need to
478
_open_and_write_file()
480
# Try to create the parent directory, and then go back to
482
parent_dir = os.path.dirname(abspath)
483
self._mkdir(parent_dir, dir_mode)
484
_open_and_write_file()
486
def put_file_non_atomic(self, relpath, f, mode=None,
487
create_parent_dir=False,
489
"""Copy the file-like object into the target location.
491
This function is not strictly safe to use. It is only meant to
492
be used when you already know that the target does not exist.
493
It is not safe, because it will open and truncate the remote
494
file. So there may be a time when the file has invalid contents.
496
:param relpath: The remote location to put the contents.
497
:param f: File-like object.
498
:param mode: Possible access permissions for new file.
499
None means do not set remote permissions.
500
:param create_parent_dir: If we cannot create the target file because
501
the parent directory does not exist, go ahead and
502
create it, and then try again.
506
self._put_non_atomic_helper(relpath, writer, mode=mode,
507
create_parent_dir=create_parent_dir,
510
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
511
create_parent_dir=False,
515
self._put_non_atomic_helper(relpath, writer, mode=mode,
516
create_parent_dir=create_parent_dir,
607
519
def iter_files_recursive(self):
608
520
"""Walk the relative paths of all files in this transport."""
609
521
queue = list(self.list_dir('.'))
611
relpath = urllib.quote(queue.pop(0))
523
relpath = queue.pop(0)
612
524
st = self.stat(relpath)
613
525
if stat.S_ISDIR(st.st_mode):
614
526
for i, basename in enumerate(self.list_dir(relpath)):
531
def _mkdir(self, abspath, mode=None):
537
self._sftp.mkdir(abspath, local_mode)
539
self._sftp.chmod(abspath, mode=mode)
540
except (paramiko.SSHException, IOError), e:
541
self._translate_io_exception(e, abspath, ': unable to mkdir',
542
failure_exc=FileExists)
619
544
def mkdir(self, relpath, mode=None):
620
545
"""Create a directory at the given path."""
621
path = self._remote_path(relpath)
623
# In the paramiko documentation, it says that passing a mode flag
624
# will filtered against the server umask.
625
# StubSFTPServer does not do this, which would be nice, because it is
626
# what we really want :)
627
# However, real servers do use umask, so we really should do it that way
628
self._sftp.mkdir(path)
630
self._sftp.chmod(path, mode=mode)
631
except (paramiko.SSHException, IOError), e:
632
self._translate_io_exception(e, path, ': unable to mkdir',
633
failure_exc=FileExists)
546
self._mkdir(self._remote_path(relpath), mode=mode)
635
548
def _translate_io_exception(self, e, path, more_info='',
636
549
failure_exc=PathError):
813
730
TODO: Raise a more reasonable ConnectionFailed exception
815
global _connected_hosts
817
idx = (self._host, self._port, self._username)
819
self._sftp = _connected_hosts[idx]
824
vendor = _get_ssh_vendor()
825
if vendor == 'loopback':
826
sock = socket.socket()
828
sock.connect((self._host, self._port))
829
except socket.error, e:
830
raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
831
% (self._host, self._port, e))
832
self._sftp = SFTPClient(LoopbackSFTP(sock))
833
elif vendor != 'none':
835
sock = SFTPSubprocess(self._host, vendor, self._port,
837
self._sftp = SFTPClient(sock)
838
except (EOFError, paramiko.SSHException), e:
839
raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
840
% (self._host, self._port, e))
841
except (OSError, IOError), e:
842
# If the machine is fast enough, ssh can actually exit
843
# before we try and send it the sftp request, which
844
# raises a Broken Pipe
845
if e.errno not in (errno.EPIPE,):
847
raise ConnectionError('Unable to connect to SSH host %s:%s: %s'
848
% (self._host, self._port, e))
850
self._paramiko_connect()
852
_connected_hosts[idx] = self._sftp
854
def _paramiko_connect(self):
855
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
860
t = paramiko.Transport((self._host, self._port or 22))
861
t.set_log_channel('bzr.paramiko')
863
except (paramiko.SSHException, socket.error), e:
864
raise ConnectionError('Unable to reach SSH host %s:%s: %s'
865
% (self._host, self._port, e))
867
server_key = t.get_remote_server_key()
868
server_key_hex = paramiko.util.hexify(server_key.get_fingerprint())
869
keytype = server_key.get_name()
870
if SYSTEM_HOSTKEYS.has_key(self._host) and SYSTEM_HOSTKEYS[self._host].has_key(keytype):
871
our_server_key = SYSTEM_HOSTKEYS[self._host][keytype]
872
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
873
elif BZR_HOSTKEYS.has_key(self._host) and BZR_HOSTKEYS[self._host].has_key(keytype):
874
our_server_key = BZR_HOSTKEYS[self._host][keytype]
875
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
877
warning('Adding %s host key for %s: %s' % (keytype, self._host, server_key_hex))
878
if not BZR_HOSTKEYS.has_key(self._host):
879
BZR_HOSTKEYS[self._host] = {}
880
BZR_HOSTKEYS[self._host][keytype] = server_key
881
our_server_key = server_key
882
our_server_key_hex = paramiko.util.hexify(our_server_key.get_fingerprint())
884
if server_key != our_server_key:
885
filename1 = os.path.expanduser('~/.ssh/known_hosts')
886
filename2 = pathjoin(config_dir(), 'ssh_host_keys')
887
raise TransportError('Host keys for %s do not match! %s != %s' % \
888
(self._host, our_server_key_hex, server_key_hex),
889
['Try editing %s or %s' % (filename1, filename2)])
894
self._sftp = t.open_sftp_client()
895
except paramiko.SSHException, e:
896
raise ConnectionError('Unable to start sftp client %s:%d' %
897
(self._host, self._port), e)
899
def _sftp_auth(self, transport):
900
# paramiko requires a username, but it might be none if nothing was supplied
901
# use the local username, just in case.
902
# We don't override self._username, because if we aren't using paramiko,
903
# the username might be specified in ~/.ssh/config and we don't want to
904
# force it to something else
905
# Also, it would mess up the self.relpath() functionality
906
username = self._username or getpass.getuser()
909
agent = paramiko.Agent()
910
for key in agent.get_keys():
911
mutter('Trying SSH agent key %s' % paramiko.util.hexify(key.get_fingerprint()))
913
transport.auth_publickey(username, key)
915
except paramiko.SSHException, e:
918
# okay, try finding id_rsa or id_dss? (posix only)
919
if self._try_pkey_auth(transport, paramiko.RSAKey, username, 'id_rsa'):
921
if self._try_pkey_auth(transport, paramiko.DSSKey, username, 'id_dsa'):
926
transport.auth_password(username, self._password)
928
except paramiko.SSHException, e:
931
# FIXME: Don't keep a password held in memory if you can help it
932
#self._password = None
934
# give up and ask for a password
935
password = bzrlib.ui.ui_factory.get_password(
936
prompt='SSH %(user)s@%(host)s password',
937
user=username, host=self._host)
939
transport.auth_password(username, password)
940
except paramiko.SSHException, e:
941
raise ConnectionError('Unable to authenticate to SSH host as %s@%s' %
942
(username, self._host), e)
944
def _try_pkey_auth(self, transport, pkey_class, username, filename):
945
filename = os.path.expanduser('~/.ssh/' + filename)
947
key = pkey_class.from_private_key_file(filename)
948
transport.auth_publickey(username, key)
950
except paramiko.PasswordRequiredException:
951
password = bzrlib.ui.ui_factory.get_password(
952
prompt='SSH %(filename)s password',
955
key = pkey_class.from_private_key_file(filename, password)
956
transport.auth_publickey(username, key)
958
except paramiko.SSHException:
959
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
960
except paramiko.SSHException:
961
mutter('SSH authentication via %s key failed.' % (os.path.basename(filename),))
732
self._sftp = _sftp_connect(self._host, self._port, self._username,
966
735
def _sftp_open_exclusive(self, abspath, mode=None):
967
736
"""Open a remote path exclusively.
1294
1074
super(SFTPSiblingAbsoluteServer, self).setUp()
1077
def _sftp_connect(host, port, username, password):
1078
"""Connect to the remote sftp server.
1080
:raises: a TransportError 'could not connect'.
1082
:returns: an paramiko.sftp_client.SFTPClient
1084
TODO: Raise a more reasonable ConnectionFailed exception
1086
idx = (host, port, username)
1088
return _connected_hosts[idx]
1092
sftp = _sftp_connect_uncached(host, port, username, password)
1093
_connected_hosts[idx] = sftp
1096
def _sftp_connect_uncached(host, port, username, password):
1097
vendor = ssh._get_ssh_vendor()
1098
sftp = vendor.connect_sftp(username, password, host, port)
1297
1102
def get_test_permutations():
1298
1103
"""Return the permutations to be used in testing."""
1299
1104
return [(SFTPTransport, SFTPAbsoluteServer),