46
48
CMD_HANDLE, CMD_OPEN)
47
49
from paramiko.sftp_attr import SFTPAttributes
48
50
from paramiko.sftp_file import SFTPFile
51
from paramiko.sftp_client import SFTPClient
53
if 'sftp' not in urlparse.uses_netloc: urlparse.uses_netloc.append('sftp')
57
def _get_ssh_vendor():
58
"""Find out what version of SSH is on the system."""
60
if _ssh_vendor is not None:
66
p = subprocess.Popen(['ssh', '-V'],
68
stdin=subprocess.PIPE,
69
stdout=subprocess.PIPE,
70
stderr=subprocess.PIPE)
71
returncode = p.returncode
72
stdout, stderr = p.communicate()
76
if 'OpenSSH' in stderr:
77
mutter('ssh implementation is OpenSSH')
78
_ssh_vendor = 'openssh'
79
elif 'SSH Secure Shell' in stderr:
80
mutter('ssh implementation is SSH Corp.')
83
if _ssh_vendor != 'none':
86
# XXX: 20051123 jamesh
87
# A check for putty's plink or lsh would go here.
89
mutter('falling back to paramiko implementation')
94
"""A socket-like object that talks to an ssh subprocess via pipes."""
95
def __init__(self, hostname, port=None, user=None):
96
vendor = _get_ssh_vendor()
97
assert vendor in ['openssh', 'ssh']
98
if vendor == 'openssh':
100
'-oForwardX11=no', '-oForwardAgent=no',
101
'-oClearAllForwardings=yes', '-oProtocol=2',
102
'-oNoHostAuthenticationForLocalhost=yes']
104
args.extend(['-p', str(port)])
106
args.extend(['-l', user])
107
args.extend(['-s', hostname, 'sftp'])
108
elif vendor == 'ssh':
111
args.extend(['-p', str(port)])
113
args.extend(['-l', user])
114
args.extend(['-s', 'sftp', hostname])
116
self.proc = subprocess.Popen(args, close_fds=True,
117
stdin=subprocess.PIPE,
118
stdout=subprocess.PIPE)
120
def send(self, data):
121
return os.write(self.proc.stdin.fileno(), data)
123
def recv(self, count):
124
return os.read(self.proc.stdout.fileno(), count)
127
self.proc.stdin.close()
128
self.proc.stdout.close()
51
132
SYSTEM_HOSTKEYS = {}
202
281
def relpath(self, abspath):
203
# FIXME: this is identical to HttpTransport -- share it
204
m = self._url_matcher.match(abspath)
206
if not path.startswith(self._path):
282
username, password, host, port, path = self._split_url(abspath)
283
if (username != self._username or host != self._host or
284
port != self._port or not path.startswith(self._path)):
207
285
raise NonRelativePath('path %r is not under base URL %r'
208
286
% (abspath, self.base))
210
return abspath[pl:].lstrip('/')
288
return path[pl:].lstrip('/')
212
290
def has(self, relpath):
485
564
def _unparse_url(self, path=None):
487
566
path = self._path
489
username = urllib.quote(self._username)
491
host += ':%d' % self._port
492
return 'sftp://%s@%s/%s' % (username, host, urllib.quote(path))
567
path = urllib.quote(path)
568
if path.startswith('/'):
569
path = '/%2F' + path[1:]
572
netloc = urllib.quote(self._host)
573
if self._username is not None:
574
netloc = '%s@%s' % (urllib.quote(self._username), netloc)
575
if self._port is not None:
576
netloc = '%s:%d' % (netloc, self._port)
578
return urlparse.urlunparse(('sftp', netloc, path, '', '', ''))
580
def _split_url(self, url):
581
if isinstance(url, unicode):
582
url = url.encode('utf-8')
583
(scheme, netloc, path, params,
584
query, fragment) = urlparse.urlparse(url, allow_fragments=False)
585
assert scheme == 'sftp'
586
username = password = host = port = None
588
username, host = netloc.split('@', 1)
590
username, password = username.split(':', 1)
591
password = urllib.unquote(password)
592
username = urllib.unquote(username)
597
host, port = host.rsplit(':', 1)
601
raise SFTPTransportError('%s: invalid port number' % port)
602
host = urllib.unquote(host)
604
path = urllib.unquote(path)
606
# the initial slash should be removed from the path, and treated
607
# as a homedir relative path (the path begins with a double slash
608
# if it is absolute).
609
# see draft-ietf-secsh-scp-sftp-ssh-uri-03.txt
610
if path.startswith('/'):
613
return (username, password, host, port, path)
494
615
def _parse_url(self, url):
495
assert url[:7] == 'sftp://'
496
m = self._url_matcher.match(url)
498
raise SFTPTransportError('Unable to parse SFTP URL %r' % (url,))
499
self._username, self._password, self._host, self._port, self._path = m.groups()
500
if self._username is None:
501
self._username = getpass.getuser()
504
# username field is 'user:pass@' in this case, and password is ':pass'
505
username_len = len(self._username) - len(self._password) - 1
506
self._username = urllib.unquote(self._username[:username_len])
507
self._password = urllib.unquote(self._password[1:])
509
self._username = urllib.unquote(self._username[:-1])
510
if self._port is None:
513
self._port = int(self._port[1:])
514
if (self._path is None) or (self._path == ''):
518
self._path = urllib.unquote(self._path[1:])
616
(self._username, self._password,
617
self._host, self._port, self._path) = self._split_url(url)
618
if self._port is None:
520
621
def _sftp_connect(self):
521
global SYSTEM_HOSTKEYS, BZR_HOSTKEYS
622
"""Connect to the remote sftp server.
623
After this, self._sftp should have a valid connection (or
624
we raise an SFTPTransportError 'could not connect').
626
TODO: Raise a more reasonable ConnectionFailed exception
628
global _connected_hosts
525
630
idx = (self._host, self._port, self._username)
559
678
(self._host, our_server_key_hex, server_key_hex),
560
679
['Try editing %s or %s' % (filename1, filename2)])
562
self._sftp_auth(t, self._username, self._host)
681
self._sftp_auth(t, self._username or getpass.getuser(), self._host)
565
684
self._sftp = t.open_sftp_client()
566
685
except paramiko.SSHException:
567
686
raise BzrError('Unable to find path %s on SFTP server %s' % \
568
687
(self._path, self._host))
570
_connected_hosts[idx] = self._sftp
572
689
def _sftp_auth(self, transport, username, host):
573
690
agent = paramiko.Agent()