Robot Framework SSH Library
pythonclient.py
Go to the documentation of this file.
1 # Copyright 2008-2015 Nokia Networks
2 # Copyright 2016- Robot Framework Foundation
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 import glob
16 import os
17 import ntpath
18 import time
19 
20 try:
21  import paramiko
22 except ImportError:
23  raise ImportError(
24  'Importing Paramiko library failed. '
25  'Make sure you have Paramiko installed.'
26  )
27 
28 try:
29  import scp
30 except ImportError:
31  raise ImportError(
32  'Importing SCP library failed. '
33  'Make sure you have SCP installed.'
34  )
35 
36 from .abstractclient import (AbstractShell, AbstractSFTPClient,
37  AbstractSSHClient, AbstractCommand,
38  SSHClientException, SFTPFileInfo)
39 from .pythonforward import LocalPortForwarding
40 from .utils import is_bytes, is_list_like, is_unicode, is_truthy
41 from robot.api import logger
42 
43 
44 # There doesn't seem to be a simpler way to increase banner timeout
45 def _custom_start_client(self, *args, **kwargs):
46  self.banner_timeout = 45
47  self._orig_start_client(*args, **kwargs)
48 
49 
50 paramiko.transport.Transport._orig_start_client = \
51  paramiko.transport.Transport.start_client
52 paramiko.transport.Transport.start_client = _custom_start_client
53 
54 # See http://code.google.com/p/robotframework-sshlibrary/issues/detail?id=55
55 def _custom_log(self, level, msg, *args):
56  escape = lambda s: s.replace('%', '%%')
57  if is_list_like(msg):
58  msg = [escape(m) for m in msg]
59  else:
60  msg = escape(msg)
61  return self._orig_log(level, msg, *args)
62 
63 
64 paramiko.sftp_client.SFTPClient._orig_log = paramiko.sftp_client.SFTPClient._log
65 paramiko.sftp_client.SFTPClient._log = _custom_log
66 
67 
69  tunnel = None
70 
71  def _get_client(self):
72  client = paramiko.SSHClient()
73  client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
74  return client
75 
76  @staticmethod
77  def enable_logging(path):
78  paramiko.util.log_to_file(path)
79  return True
80 
81  @staticmethod
82  def _read_login_ssh_config(host, username, port_number, proxy_cmd):
83  ssh_config_file = os.path.expanduser("~/.ssh/config")
84  if os.path.exists(ssh_config_file):
85  conf = paramiko.SSHConfig()
86  with open(ssh_config_file) as f:
87  conf.parse(f)
88  port = int(PythonSSHClient._get_ssh_config_port(conf, host, port_number))
89  user = PythonSSHClient._get_ssh_config_user(conf, host, username)
90  proxy_command = PythonSSHClient._get_ssh_config_proxy_cmd(conf, host, proxy_cmd)
91  host = PythonSSHClient._get_ssh_config_host(conf, host)
92  return host, user, port, proxy_command
93  return host, username, port_number, proxy_cmd
94 
95  @staticmethod
96  def _read_public_key_ssh_config(host, username, port_number, proxy_cmd, identity_file):
97  ssh_config_file = os.path.expanduser("~/.ssh/config")
98  if os.path.exists(ssh_config_file):
99  conf = paramiko.SSHConfig()
100  with open(ssh_config_file) as f:
101  conf.parse(f)
102  port = int(PythonSSHClient._get_ssh_config_port(conf, host, port_number))
103  id_file = PythonSSHClient._get_ssh_config_identity_file(conf, host, identity_file)
104  user = PythonSSHClient._get_ssh_config_user(conf, host, username)
105  proxy_command = PythonSSHClient._get_ssh_config_proxy_cmd(conf, host, proxy_cmd)
106  host = PythonSSHClient._get_ssh_config_host(conf, host)
107  return host, user, port, id_file, proxy_command
108  return host, username, port_number, identity_file, proxy_cmd
109 
110  @staticmethod
111  def _get_ssh_config_user(conf, host, user):
112  try:
113  return conf.lookup(host)['user'] if not None else user
114  except KeyError:
115  return None
116 
117  @staticmethod
118  def _get_ssh_config_proxy_cmd(conf, host, proxy_cmd):
119  try:
120  return conf.lookup(host)['proxycommand'] if not None else proxy_cmd
121  except KeyError:
122  return proxy_cmd
123 
124  @staticmethod
125  def _get_ssh_config_identity_file(conf, host, id_file):
126  try:
127  return conf.lookup(host)['identityfile'][0] if not None else id_file
128  except KeyError:
129  return id_file
130 
131  @staticmethod
132  def _get_ssh_config_port(conf, host, port_number):
133  try:
134  return conf.lookup(host)['port'] if not None else port_number
135  except KeyError:
136  return port_number
137 
138  @staticmethod
139  def _get_ssh_config_host(conf, host):
140  try:
141  return conf.lookup(host)['hostname'] if not None else host
142  except KeyError:
143  return host
144 
145  def _get_jumphost_tunnel(self, jumphost_connection):
146  dest_addr = (self.configconfig.host, self.configconfig.port)
147  jump_addr = (jumphost_connection.config.host, jumphost_connection.config.port)
148  jumphost_transport = jumphost_connection.client.get_transport()
149  if not jumphost_transport:
150  raise RuntimeError("Could not get transport for {}:{}. Have you logged in?".format(*jump_addr))
151  return jumphost_transport.open_channel("direct-tcpip", dest_addr, jump_addr)
152 
153  def _login(self, username, password, allow_agent=False, look_for_keys=False, proxy_cmd=None,
154  read_config=False, jumphost_connection=None, keep_alive_interval=None):
155  if read_config:
156  hostname = self.configconfig.host
157  self.configconfig.host, username, self.configconfig.port, proxy_cmd = \
158  self._read_login_ssh_config_read_login_ssh_config(hostname, username, self.configconfig.port, proxy_cmd)
159 
160  sock_tunnel = None
161 
162  if proxy_cmd and jumphost_connection:
163  raise ValueError("`proxy_cmd` and `jumphost_connection` are mutually exclusive SSH features.")
164  elif proxy_cmd:
165  sock_tunnel = paramiko.ProxyCommand(proxy_cmd)
166  elif jumphost_connection:
167  sock_tunnel = self._get_jumphost_tunnel_get_jumphost_tunnel(jumphost_connection)
168  try:
169  if not password and not allow_agent:
170  # If no password is given, try login without authentication
171  try:
172  self.clientclient.connect(self.configconfig.host, self.configconfig.port, username,
173  password, look_for_keys=look_for_keys,
174  allow_agent=allow_agent,
175  timeout=float(self.configconfig.timeout), sock=sock_tunnel)
176  except paramiko.SSHException:
177  pass
178  transport = self.clientclient.get_transport()
179  transport.set_keepalive(keep_alive_interval)
180  transport.auth_none(username)
181  else:
182  try:
183  self.clientclient.connect(self.configconfig.host, self.configconfig.port, username,
184  password, look_for_keys=look_for_keys,
185  allow_agent=allow_agent,
186  timeout=float(self.configconfig.timeout), sock=sock_tunnel)
187  transport = self.clientclient.get_transport()
188  transport.set_keepalive(keep_alive_interval)
189  except paramiko.AuthenticationException:
190  try:
191  transport = self.clientclient.get_transport()
192  transport.set_keepalive(keep_alive_interval)
193  try:
194  transport.auth_none(username)
195  except:
196  pass
197  transport.auth_password(username, password)
198  except:
199  raise SSHClientException
200  except paramiko.AuthenticationException:
201  raise SSHClientException
202 
203  def _login_with_public_key(self, username, key_file, password, allow_agent, look_for_keys, proxy_cmd=None,
204  jumphost_connection=None, read_config=False, keep_alive_interval=None):
205  if read_config:
206  hostname = self.configconfig.host
207  self.configconfig.host, username, self.configconfig.port, key_file, proxy_cmd = \
208  self._read_public_key_ssh_config_read_public_key_ssh_config(hostname, username, self.configconfig.port, proxy_cmd, key_file)
209 
210  sock_tunnel = None
211  if key_file is not None:
212  if not os.path.exists(key_file):
213  raise SSHClientException("Given key file '%s' does not exist." %
214  key_file)
215  try:
216  open(key_file).close()
217  except IOError:
218  raise SSHClientException("Could not read key file '%s'." % key_file)
219  else:
220  raise RuntimeError("Keyfile must be specified as keyword argument or in config file.")
221  if proxy_cmd and jumphost_connection:
222  raise ValueError("`proxy_cmd` and `jumphost_connection` are mutually exclusive SSH features.")
223  elif proxy_cmd:
224  sock_tunnel = paramiko.ProxyCommand(proxy_cmd)
225  elif jumphost_connection:
226  sock_tunnel = self._get_jumphost_tunnel_get_jumphost_tunnel(jumphost_connection)
227 
228  try:
229  self.clientclient.connect(self.configconfig.host, self.configconfig.port, username,
230  password, key_filename=key_file,
231  allow_agent=allow_agent,
232  look_for_keys=look_for_keys,
233  timeout=float(self.configconfig.timeout),
234  sock=sock_tunnel)
235  transport = self.clientclient.get_transport()
236  transport.set_keepalive(keep_alive_interval)
237  except paramiko.AuthenticationException:
238  try:
239  transport = self.clientclient.get_transport()
240  transport.set_keepalive(keep_alive_interval)
241  try:
242  transport.auth_none(username)
243  except:
244  pass
245  transport.auth_publickey(username,None)
246  except Exception as err:
247  raise SSHClientException
248 
249  def get_banner(self):
250  return self.clientclient.get_transport().get_banner()
251 
252  @staticmethod
253  def get_banner_without_login(host, port=22):
254  client = paramiko.SSHClient()
255  client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
256  try:
257  client.connect(str(host), int(port), username="bad-username")
258  except paramiko.AuthenticationException:
259  return client.get_transport().get_banner()
260  except Exception:
261  raise SSHClientException('Unable to connect to port {} on {}'.format(port, host))
262 
263  def _start_command(self, command, sudo=False, sudo_password=None, invoke_subsystem=False, forward_agent=False):
264  cmd = RemoteCommand(command, self.configconfig.encoding)
265  transport = self.clientclient.get_transport()
266  if not transport:
267  raise AssertionError("Connection not open")
268  new_shell = transport.open_session(timeout=float(self.configconfig.timeout))
269 
270  if forward_agent:
271  paramiko.agent.AgentRequestHandler(new_shell)
272 
273  cmd.run_in(new_shell, sudo, sudo_password, invoke_subsystem)
274  return cmd
275 
277  return SFTPClient(self.clientclient, self.configconfig.encoding)
278 
280  return SCPTransferClient(self.clientclient, self.configconfig.encoding)
281 
283  return SCPClient(self.clientclient)
284 
285  def _create_shell(self):
286  return Shell(self.clientclient, self.configconfig.term_type,
287  self.configconfig.width, self.configconfig.height)
288 
289  def create_local_ssh_tunnel(self, local_port, remote_host, remote_port, bind_address):
290  self._create_local_port_forwarder_create_local_port_forwarder(local_port, remote_host, remote_port, bind_address)
291 
292  def _create_local_port_forwarder(self, local_port, remote_host, remote_port, bind_address):
293  transport = self.clientclient.get_transport()
294  if not transport:
295  raise AssertionError("Connection not open")
296  self.tunneltunnel = LocalPortForwarding(int(remote_port), remote_host, transport, bind_address)
297  self.tunneltunnel.forward(int(local_port))
298 
299  def close(self):
300  if self.tunneltunnel:
301  self.tunneltunnel.close()
302  return super(PythonSSHClient, self).close()
303 
304 
306 
307  def __init__(self, client, term_type, term_width, term_height):
308  try:
309  self._shell_shell = client.invoke_shell(term_type, term_width, term_height)
310  except AttributeError:
311  raise RuntimeError('Cannot open session, you need to establish a connection first.')
312 
313  def read(self):
314  data = b''
315  while self._output_available_output_available():
316  data += self._shell_shell.recv(4096)
317  return data
318 
319  def read_byte(self):
320  if self._output_available_output_available():
321  return self._shell_shell.recv(1)
322  return b''
323 
324  def resize(self, width, height):
325  self._shell_shell.resize_pty(width=width, height=height)
326 
327  def _output_available(self):
328  return self._shell_shell.recv_ready()
329 
330  def write(self, text):
331  self._shell_shell.sendall(text)
332 
333 
335 
336  def __init__(self, ssh_client, encoding):
337  self.ssh_clientssh_client = ssh_client
338  self._client_client = ssh_client.open_sftp()
339  super(SFTPClient, self).__init__(encoding)
340 
341  def _list(self, path):
342  path = path.encode(self._encoding_encoding)
343  for item in self._client_client.listdir_attr(path):
344  filename = item.filename
345  if is_bytes(filename):
346  filename = filename.decode(self._encoding_encoding)
347  yield SFTPFileInfo(filename, item.st_mode)
348 
349  def _stat(self, path):
350  path = path.encode(self._encoding_encoding)
351  attributes = self._client_client.stat(path)
352  return SFTPFileInfo('', attributes.st_mode)
353 
354  def _create_missing_remote_path(self, path, mode):
355  if is_unicode(path):
356  path = path.encode(self._encoding_encoding)
357  return super(SFTPClient, self)._create_missing_remote_path(path, mode)
358 
359  def _create_remote_file(self, destination, mode):
360  file_exists = self.is_fileis_file(destination)
361  destination = destination.encode(self._encoding_encoding)
362  remote_file = self._client_client.file(destination, 'wb')
363  remote_file.set_pipelined(True)
364  if not file_exists and mode:
365  self._client_client.chmod(destination, mode)
366  return remote_file
367 
368  def _write_to_remote_file(self, remote_file, data, position):
369  remote_file.write(data)
370 
371  def _close_remote_file(self, remote_file):
372  remote_file.close()
373 
374  def _get_file(self, remote_path, local_path, scp_preserve_times):
375  remote_path = remote_path.encode(self._encoding_encoding)
376  self._client_client.get(remote_path, local_path)
377 
378  def _absolute_path(self, path):
379  if not self._is_windows_path_is_windows_path(path):
380  path = self._client_client.normalize(path)
381  if is_bytes(path):
382  path = path.decode(self._encoding_encoding)
383  return path
384 
385  def _is_windows_path(self, path):
386  return bool(ntpath.splitdrive(path)[0])
387 
388  def _readlink(self, path):
389  return self._client_client.readlink(path)
390 
391 
392 class SCPClient():
393  def __init__(self, ssh_client):
394  self._scp_client_scp_client = scp.SCPClient(ssh_client.get_transport())
395 
396  def put_file(self, source, destination, scp_preserve_times, *args):
397  sources = self._get_put_file_sources_get_put_file_sources(source)
398  self._scp_client_scp_client.put(sources, destination, preserve_times=is_truthy(scp_preserve_times))
399 
400  def get_file(self, source, destination, scp_preserve_times, *args):
401  self._scp_client_scp_client.get(source, destination, preserve_times=is_truthy(scp_preserve_times))
402 
403  def put_directory(self, source, destination, scp_preserve_times, *args):
404  self._scp_client_scp_client.put(source, destination, True, preserve_times=is_truthy(scp_preserve_times))
405 
406  def get_directory(self, source, destination, scp_preserve_times, *args):
407  self._scp_client_scp_client.get(source, destination, True, preserve_times=is_truthy(scp_preserve_times))
408 
409  def _get_put_file_sources(self, source):
410  source = source.replace('/', os.sep)
411  if not os.path.exists(source):
412  sources = [f for f in glob.glob(source)]
413  else:
414  sources = [f for f in [source]]
415  if not sources:
416  msg = "There are no source files matching '%s'." % source
417  raise SSHClientException(msg)
418  return sources
419 
420 
422 
423  def __init__(self, ssh_client, encoding):
424  self._scp_client_scp_client = scp.SCPClient(ssh_client.get_transport())
425  super(SCPTransferClient, self).__init__(ssh_client, encoding)
426 
427  def _put_file(self, source, destination, mode, newline, path_separator, scp_preserve_times=False):
428  self._create_remote_file_create_remote_file_create_remote_file(destination, mode)
429  self._scp_client_scp_client.put(source, destination, preserve_times=is_truthy(scp_preserve_times))
430 
431  def _get_file(self, remote_path, local_path, scp_preserve_times=False):
432  self._scp_client_scp_client.get(remote_path, local_path, preserve_times=is_truthy(scp_preserve_times))
433 
434 
436 
437  def read_outputs(self, timeout=None, output_during_execution=False, output_if_timeout=False):
438  stderr, stdout = self._receive_stdout_and_stderr_receive_stdout_and_stderr(timeout, output_during_execution, output_if_timeout)
439  rc = self._shell_shell.recv_exit_status()
440  self._shell_shell.close()
441  return stdout, stderr, rc
442 
443  def _receive_stdout_and_stderr(self, timeout=None, output_during_execution=False, output_if_timeout=False):
444  stdout_filebuffer = self._shell_shell.makefile('rb', -1)
445  stderr_filebuffer = self._shell_shell.makefile_stderr('rb', -1)
446  stdouts = []
447  stderrs = []
448  while self._shell_open_shell_open():
449  self._flush_stdout_and_stderr_flush_stdout_and_stderr(stderr_filebuffer, stderrs, stdout_filebuffer, stdouts, timeout,
450  output_during_execution, output_if_timeout)
451  time.sleep(0.01) # lets not be so busy
452  stdout = (b''.join(stdouts) + stdout_filebuffer.read()).decode(self._encoding_encoding)
453  stderr = (b''.join(stderrs) + stderr_filebuffer.read()).decode(self._encoding_encoding)
454  return stderr, stdout
455 
456  def _flush_stdout_and_stderr(self, stderr_filebuffer, stderrs, stdout_filebuffer, stdouts, timeout=None,
457  output_during_execution=False, output_if_timeout=False):
458  if timeout:
459  end_time = time.time() + timeout
460  while time.time() < end_time:
461  if self._shell_shell.status_event.wait(0):
462  break
463  self._output_logging_output_logging(stderr_filebuffer, stderrs, stdout_filebuffer, stdouts, output_during_execution)
464  if not self._shell_shell.status_event.isSet():
465  if is_truthy(output_if_timeout):
466  logger.info(stdouts)
467  logger.info(stderrs)
468  raise SSHClientException('Timed out in %s seconds' % int(timeout))
469  else:
470  self._output_logging_output_logging(stderr_filebuffer, stderrs, stdout_filebuffer, stdouts, output_during_execution)
471 
472  def _output_logging(self, stderr_filebuffer, stderrs, stdout_filebuffer, stdouts, output_during_execution=False):
473  if self._shell_shell.recv_ready():
474  stdout_output = stdout_filebuffer.read(len(self._shell_shell.in_buffer))
475  if is_truthy(output_during_execution):
476  logger.console(stdout_output)
477  stdouts.append(stdout_output)
478  if self._shell_shell.recv_stderr_ready():
479  stderr_output = stderr_filebuffer.read(len(self._shell_shell.in_stderr_buffer))
480  if is_truthy(output_during_execution):
481  logger.console(stderr_output)
482  stderrs.append(stderr_output)
483 
484  def _shell_open(self):
485  return not (self._shell_shell.closed or
486  self._shell_shell.eof_received or
487  self._shell_shell.eof_sent or
488  not self._shell_shell.active)
489 
490  def _execute(self):
491  self._shell_shell.exec_command(self._command_command)
492 
493  def _execute_with_sudo(self, sudo_password=None):
494  command = 'sudo ' + self._command_command.decode(self._encoding_encoding)
495  if sudo_password is None:
496  self._shell_shell.exec_command(command)
497  else:
498  self._shell_shell.exec_command('echo %s | sudo --stdin --prompt "" %s' % (sudo_password, command))
499 
500  def _invoke(self):
501  self._shell_shell.invoke_subsystem(self._command_command)
Base class for the remote command.
Base class for the SFTP implementation.
def _create_remote_file(self, destination, mode)
def is_file(self, path)
Checks if the path points to a regular file on the remote host.
Base class for the SSH client implementation.
Base class for the shell implementation.
Wrapper class for the language specific file information objects.
def close(self)
Closes the connection.
def _get_ssh_config_port(conf, host, port_number)
def _read_public_key_ssh_config(host, username, port_number, proxy_cmd, identity_file)
Definition: pythonclient.py:96
def _login(self, username, password, allow_agent=False, look_for_keys=False, proxy_cmd=None, read_config=False, jumphost_connection=None, keep_alive_interval=None)
def _get_ssh_config_user(conf, host, user)
def _read_login_ssh_config(host, username, port_number, proxy_cmd)
Definition: pythonclient.py:82
def _start_command(self, command, sudo=False, sudo_password=None, invoke_subsystem=False, forward_agent=False)
def enable_logging(path)
Enables logging of SSH events to a file.
Definition: pythonclient.py:77
def _get_ssh_config_identity_file(conf, host, id_file)
def _login_with_public_key(self, username, key_file, password, allow_agent, look_for_keys, proxy_cmd=None, jumphost_connection=None, read_config=False, keep_alive_interval=None)
def _create_local_port_forwarder(self, local_port, remote_host, remote_port, bind_address)
def _get_jumphost_tunnel(self, jumphost_connection)
def create_local_ssh_tunnel(self, local_port, remote_host, remote_port, bind_address)
def _get_ssh_config_proxy_cmd(conf, host, proxy_cmd)
def read_outputs(self, timeout=None, output_during_execution=False, output_if_timeout=False)
def _flush_stdout_and_stderr(self, stderr_filebuffer, stderrs, stdout_filebuffer, stdouts, timeout=None, output_during_execution=False, output_if_timeout=False)
def _execute_with_sudo(self, sudo_password=None)
def _receive_stdout_and_stderr(self, timeout=None, output_during_execution=False, output_if_timeout=False)
def _output_logging(self, stderr_filebuffer, stderrs, stdout_filebuffer, stdouts, output_during_execution=False)
def __init__(self, ssh_client)
def _get_put_file_sources(self, source)
def get_directory(self, source, destination, scp_preserve_times, *args)
def get_file(self, source, destination, scp_preserve_times, *args)
def put_file(self, source, destination, scp_preserve_times, *args)
def put_directory(self, source, destination, scp_preserve_times, *args)
def _get_file(self, remote_path, local_path, scp_preserve_times=False)
def __init__(self, ssh_client, encoding)
def _put_file(self, source, destination, mode, newline, path_separator, scp_preserve_times=False)
def __init__(self, ssh_client, encoding)
def _create_remote_file(self, destination, mode)
def _create_missing_remote_path(self, path, mode)
def _close_remote_file(self, remote_file)
def _write_to_remote_file(self, remote_file, data, position)
def _get_file(self, remote_path, local_path, scp_preserve_times)
def write(self, text)
Writes the text in the current shell.
def resize(self, width, height)
def read(self)
Reads all the output from the shell.
def __init__(self, client, term_type, term_width, term_height)
def read_byte(self)
Reads a single byte from the shell.
def _custom_start_client(self, *args, **kwargs)
Definition: pythonclient.py:45
def _custom_log(self, level, msg, *args)
Definition: pythonclient.py:55