63
59
CMD_HANDLE, CMD_OPEN)
64
60
from paramiko.sftp_attr import SFTPAttributes
65
61
from paramiko.sftp_file import SFTPFile
66
from paramiko.sftp_client import SFTPClient
69
64
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
67
# This is a weakref dictionary, so that we can reuse connections
229
68
# that are still active. Long term, it might be nice to have some
230
69
# sort of expiration policy, such as disconnect if inactive for
231
70
# X seconds. But that requires a lot more fanciness.
232
71
_connected_hosts = weakref.WeakValueDictionary()
74
_paramiko_version = getattr(paramiko, '__version_info__', (0, 0, 0))
75
# don't use prefetch unless paramiko version >= 1.5.5 (there were bugs earlier)
76
_default_do_prefetch = (_paramiko_version >= (1, 5, 5))
234
79
def clear_connection_cache():
235
80
"""Remove all hosts from the SFTP connection cache.
239
84
_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
87
class SFTPLock(object):
280
88
"""This fakes a lock in a remote location."""
281
89
__slots__ = ['path', 'lock_path', 'lock_file', 'transport']
604
425
# raise the original with its traceback if we can.
428
def _put_non_atomic_helper(self, relpath, writer, mode=None,
429
create_parent_dir=False,
431
abspath = self._remote_path(relpath)
433
# TODO: jam 20060816 paramiko doesn't publicly expose a way to
434
# set the file mode at create time. If it does, use it.
435
# But for now, we just chmod later anyway.
437
def _open_and_write_file():
438
"""Try to open the target file, raise error on failure"""
442
fout = self._sftp.file(abspath, mode='wb')
443
fout.set_pipelined(True)
445
except (paramiko.SSHException, IOError), e:
446
self._translate_io_exception(e, abspath,
449
# This is designed to chmod() right before we close.
450
# Because we set_pipelined() earlier, theoretically we might
451
# avoid the round trip for fout.close()
453
self._sftp.chmod(abspath, mode)
458
if not create_parent_dir:
459
_open_and_write_file()
462
# Try error handling to create the parent directory if we need to
464
_open_and_write_file()
466
# Try to create the parent directory, and then go back to
468
parent_dir = os.path.dirname(abspath)
469
self._mkdir(parent_dir, dir_mode)
470
_open_and_write_file()
472
def put_file_non_atomic(self, relpath, f, mode=None,
473
create_parent_dir=False,
475
"""Copy the file-like object into the target location.
477
This function is not strictly safe to use. It is only meant to
478
be used when you already know that the target does not exist.
479
It is not safe, because it will open and truncate the remote
480
file. So there may be a time when the file has invalid contents.
482
:param relpath: The remote location to put the contents.
483
:param f: File-like object.
484
:param mode: Possible access permissions for new file.
485
None means do not set remote permissions.
486
:param create_parent_dir: If we cannot create the target file because
487
the parent directory does not exist, go ahead and
488
create it, and then try again.
492
self._put_non_atomic_helper(relpath, writer, mode=mode,
493
create_parent_dir=create_parent_dir,
496
def put_bytes_non_atomic(self, relpath, bytes, mode=None,
497
create_parent_dir=False,
501
self._put_non_atomic_helper(relpath, writer, mode=mode,
502
create_parent_dir=create_parent_dir,
607
505
def iter_files_recursive(self):
608
506
"""Walk the relative paths of all files in this transport."""
609
507
queue = list(self.list_dir('.'))
611
relpath = urllib.quote(queue.pop(0))
509
relpath = queue.pop(0)
612
510
st = self.stat(relpath)
613
511
if stat.S_ISDIR(st.st_mode):
614
512
for i, basename in enumerate(self.list_dir(relpath)):
517
def _mkdir(self, abspath, mode=None):
523
self._sftp.mkdir(abspath, local_mode)
525
self._sftp.chmod(abspath, mode=mode)
526
except (paramiko.SSHException, IOError), e:
527
self._translate_io_exception(e, abspath, ': unable to mkdir',
528
failure_exc=FileExists)
619
530
def mkdir(self, relpath, mode=None):
620
531
"""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)
532
self._mkdir(self._remote_path(relpath), mode=mode)
635
534
def _translate_io_exception(self, e, path, more_info='',
636
535
failure_exc=PathError):
813
716
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),))
718
self._sftp = _sftp_connect(self._host, self._port, self._username,
966
721
def _sftp_open_exclusive(self, abspath, mode=None):
967
722
"""Open a remote path exclusively.
832
class SocketDelay(object):
833
"""A socket decorator to make TCP appear slower.
835
This changes recv, send, and sendall to add a fixed latency to each python
836
call if a new roundtrip is detected. That is, when a recv is called and the
837
flag new_roundtrip is set, latency is charged. Every send and send_all
840
In addition every send, sendall and recv sleeps a bit per character send to
843
Not all methods are implemented, this is deliberate as this class is not a
844
replacement for the builtin sockets layer. fileno is not implemented to
845
prevent the proxy being bypassed.
849
_proxied_arguments = dict.fromkeys([
850
"close", "getpeername", "getsockname", "getsockopt", "gettimeout",
851
"setblocking", "setsockopt", "settimeout", "shutdown"])
853
def __init__(self, sock, latency, bandwidth=1.0,
856
:param bandwith: simulated bandwith (MegaBit)
857
:param really_sleep: If set to false, the SocketDelay will just
858
increase a counter, instead of calling time.sleep. This is useful for
859
unittesting the SocketDelay.
862
self.latency = latency
863
self.really_sleep = really_sleep
864
self.time_per_byte = 1 / (bandwidth / 8.0 * 1024 * 1024)
865
self.new_roundtrip = False
868
if self.really_sleep:
871
SocketDelay.simulated_time += s
873
def __getattr__(self, attr):
874
if attr in SocketDelay._proxied_arguments:
875
return getattr(self.sock, attr)
876
raise AttributeError("'SocketDelay' object has no attribute %r" %
880
return SocketDelay(self.sock.dup(), self.latency, self.time_per_byte,
883
def recv(self, *args):
884
data = self.sock.recv(*args)
885
if data and self.new_roundtrip:
886
self.new_roundtrip = False
887
self.sleep(self.latency)
888
self.sleep(len(data) * self.time_per_byte)
891
def sendall(self, data, flags=0):
892
if not self.new_roundtrip:
893
self.new_roundtrip = True
894
self.sleep(self.latency)
895
self.sleep(len(data) * self.time_per_byte)
896
return self.sock.sendall(data, flags)
898
def send(self, data, flags=0):
899
if not self.new_roundtrip:
900
self.new_roundtrip = True
901
self.sleep(self.latency)
902
bytes_sent = self.sock.send(data, flags)
903
self.sleep(bytes_sent * self.time_per_byte)
1068
907
class SFTPServer(Server):
1069
908
"""Common code for SFTP server facilities."""
1208
1060
super(SFTPSiblingAbsoluteServer, self).setUp()
1063
def _sftp_connect(host, port, username, password):
1064
"""Connect to the remote sftp server.
1066
:raises: a TransportError 'could not connect'.
1068
:returns: an paramiko.sftp_client.SFTPClient
1070
TODO: Raise a more reasonable ConnectionFailed exception
1072
idx = (host, port, username)
1074
return _connected_hosts[idx]
1078
sftp = _sftp_connect_uncached(host, port, username, password)
1079
_connected_hosts[idx] = sftp
1082
def _sftp_connect_uncached(host, port, username, password):
1083
vendor = ssh._get_ssh_vendor()
1084
sftp = vendor.connect_sftp(username, password, host, port)
1211
1088
def get_test_permutations():
1212
1089
"""Return the permutations to be used in testing."""
1213
1090
return [(SFTPTransport, SFTPAbsoluteServer),