Package paramiko :: Module client
[frames] | no frames]

Source Code for Module paramiko.client

  1  # Copyright (C) 2006-2007  Robey Pointer <robeypointer@gmail.com> 
  2  # 
  3  # This file is part of paramiko. 
  4  # 
  5  # Paramiko is free software; you can redistribute it and/or modify it under the 
  6  # terms of the GNU Lesser General Public License as published by the Free 
  7  # Software Foundation; either version 2.1 of the License, or (at your option) 
  8  # any later version. 
  9  # 
 10  # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 
 11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
 13  # details. 
 14  # 
 15  # You should have received a copy of the GNU Lesser General Public License 
 16  # along with Paramiko; if not, write to the Free Software Foundation, Inc., 
 17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. 
 18   
 19  """ 
 20  SSH client & key policies 
 21  """ 
 22   
 23  from binascii import hexlify 
 24  import getpass 
 25  import os 
 26  import socket 
 27  import warnings 
 28   
 29  from paramiko.agent import Agent 
 30  from paramiko.common import DEBUG 
 31  from paramiko.config import SSH_PORT 
 32  from paramiko.dsskey import DSSKey 
 33  from paramiko.hostkeys import HostKeys 
 34  from paramiko.py3compat import string_types 
 35  from paramiko.resource import ResourceManager 
 36  from paramiko.rsakey import RSAKey 
 37  from paramiko.ssh_exception import SSHException, BadHostKeyException 
 38  from paramiko.transport import Transport 
 39  from paramiko.util import retry_on_signal 
 40   
 41   
42 -class SSHClient (object):
43 """ 44 A high-level representation of a session with an SSH server. This class 45 wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most 46 aspects of authenticating and opening channels. A typical use case is:: 47 48 client = SSHClient() 49 client.load_system_host_keys() 50 client.connect('ssh.example.com') 51 stdin, stdout, stderr = client.exec_command('ls -l') 52 53 You may pass in explicit overrides for authentication and server host key 54 checking. The default mechanism is to try to use local key files or an 55 SSH agent (if one is running). 56 57 .. versionadded:: 1.6 58 """ 59
60 - def __init__(self):
61 """ 62 Create a new SSHClient. 63 """ 64 self._system_host_keys = HostKeys() 65 self._host_keys = HostKeys() 66 self._host_keys_filename = None 67 self._log_channel = None 68 self._policy = RejectPolicy() 69 self._transport = None 70 self._agent = None
71
72 - def load_system_host_keys(self, filename=None):
73 """ 74 Load host keys from a system (read-only) file. Host keys read with 75 this method will not be saved back by `save_host_keys`. 76 77 This method can be called multiple times. Each new set of host keys 78 will be merged with the existing set (new replacing old if there are 79 conflicts). 80 81 If ``filename`` is left as ``None``, an attempt will be made to read 82 keys from the user's local "known hosts" file, as used by OpenSSH, 83 and no exception will be raised if the file can't be read. This is 84 probably only useful on posix. 85 86 :param str filename: the filename to read, or ``None`` 87 88 :raises IOError: 89 if a filename was provided and the file could not be read 90 """ 91 if filename is None: 92 # try the user's .ssh key file, and mask exceptions 93 filename = os.path.expanduser('~/.ssh/known_hosts') 94 try: 95 self._system_host_keys.load(filename) 96 except IOError: 97 pass 98 return 99 self._system_host_keys.load(filename)
100
101 - def load_host_keys(self, filename):
102 """ 103 Load host keys from a local host-key file. Host keys read with this 104 method will be checked after keys loaded via `load_system_host_keys`, 105 but will be saved back by `save_host_keys` (so they can be modified). 106 The missing host key policy `.AutoAddPolicy` adds keys to this set and 107 saves them, when connecting to a previously-unknown server. 108 109 This method can be called multiple times. Each new set of host keys 110 will be merged with the existing set (new replacing old if there are 111 conflicts). When automatically saving, the last hostname is used. 112 113 :param str filename: the filename to read 114 115 :raises IOError: if the filename could not be read 116 """ 117 self._host_keys_filename = filename 118 self._host_keys.load(filename)
119
120 - def save_host_keys(self, filename):
121 """ 122 Save the host keys back to a file. Only the host keys loaded with 123 `load_host_keys` (plus any added directly) will be saved -- not any 124 host keys loaded with `load_system_host_keys`. 125 126 :param str filename: the filename to save to 127 128 :raises IOError: if the file could not be written 129 """ 130 131 # update local host keys from file (in case other SSH clients 132 # have written to the known_hosts file meanwhile. 133 if self._host_keys_filename is not None: 134 self.load_host_keys(self._host_keys_filename) 135 136 with open(filename, 'w') as f: 137 for hostname, keys in self._host_keys.items(): 138 for keytype, key in keys.items(): 139 f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
140
141 - def get_host_keys(self):
142 """ 143 Get the local `.HostKeys` object. This can be used to examine the 144 local host keys or change them. 145 146 :return: the local host keys as a `.HostKeys` object. 147 """ 148 return self._host_keys
149
150 - def set_log_channel(self, name):
151 """ 152 Set the channel for logging. The default is ``"paramiko.transport"`` 153 but it can be set to anything you want. 154 155 :param str name: new channel name for logging 156 """ 157 self._log_channel = name
158
159 - def set_missing_host_key_policy(self, policy):
160 """ 161 Set the policy to use when connecting to a server that doesn't have a 162 host key in either the system or local `.HostKeys` objects. The 163 default policy is to reject all unknown servers (using `.RejectPolicy`). 164 You may substitute `.AutoAddPolicy` or write your own policy class. 165 166 :param .MissingHostKeyPolicy policy: 167 the policy to use when receiving a host key from a 168 previously-unknown server 169 """ 170 self._policy = policy
171
172 - def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None, 173 key_filename=None, timeout=None, allow_agent=True, look_for_keys=True, 174 compress=False, sock=None):
175 """ 176 Connect to an SSH server and authenticate to it. The server's host key 177 is checked against the system host keys (see `load_system_host_keys`) 178 and any local host keys (`load_host_keys`). If the server's hostname 179 is not found in either set of host keys, the missing host key policy 180 is used (see `set_missing_host_key_policy`). The default policy is 181 to reject the key and raise an `.SSHException`. 182 183 Authentication is attempted in the following order of priority: 184 185 - The ``pkey`` or ``key_filename`` passed in (if any) 186 - Any key we can find through an SSH agent 187 - Any "id_rsa" or "id_dsa" key discoverable in ``~/.ssh/`` 188 - Plain username/password auth, if a password was given 189 190 If a private key requires a password to unlock it, and a password is 191 passed in, that password will be used to attempt to unlock the key. 192 193 :param str hostname: the server to connect to 194 :param int port: the server port to connect to 195 :param str username: 196 the username to authenticate as (defaults to the current local 197 username) 198 :param str password: 199 a password to use for authentication or for unlocking a private key 200 :param .PKey pkey: an optional private key to use for authentication 201 :param str key_filename: 202 the filename, or list of filenames, of optional private key(s) to 203 try for authentication 204 :param float timeout: an optional timeout (in seconds) for the TCP connect 205 :param bool allow_agent: set to False to disable connecting to the SSH agent 206 :param bool look_for_keys: 207 set to False to disable searching for discoverable private key 208 files in ``~/.ssh/`` 209 :param bool compress: set to True to turn on compression 210 :param socket sock: 211 an open socket or socket-like object (such as a `.Channel`) to use 212 for communication to the target host 213 214 :raises BadHostKeyException: if the server's host key could not be 215 verified 216 :raises AuthenticationException: if authentication failed 217 :raises SSHException: if there was any other error connecting or 218 establishing an SSH session 219 :raises socket.error: if a socket error occurred while connecting 220 """ 221 if not sock: 222 for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM): 223 if socktype == socket.SOCK_STREAM: 224 af = family 225 addr = sockaddr 226 break 227 else: 228 # some OS like AIX don't indicate SOCK_STREAM support, so just guess. :( 229 af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM) 230 sock = socket.socket(af, socket.SOCK_STREAM) 231 if timeout is not None: 232 try: 233 sock.settimeout(timeout) 234 except: 235 pass 236 retry_on_signal(lambda: sock.connect(addr)) 237 238 t = self._transport = Transport(sock) 239 t.use_compression(compress=compress) 240 if self._log_channel is not None: 241 t.set_log_channel(self._log_channel) 242 t.start_client() 243 ResourceManager.register(self, t) 244 245 server_key = t.get_remote_server_key() 246 keytype = server_key.get_name() 247 248 if port == SSH_PORT: 249 server_hostkey_name = hostname 250 else: 251 server_hostkey_name = "[%s]:%d" % (hostname, port) 252 our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None) 253 if our_server_key is None: 254 our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None) 255 if our_server_key is None: 256 # will raise exception if the key is rejected; let that fall out 257 self._policy.missing_host_key(self, server_hostkey_name, server_key) 258 # if the callback returns, assume the key is ok 259 our_server_key = server_key 260 261 if server_key != our_server_key: 262 raise BadHostKeyException(hostname, server_key, our_server_key) 263 264 if username is None: 265 username = getpass.getuser() 266 267 if key_filename is None: 268 key_filenames = [] 269 elif isinstance(key_filename, string_types): 270 key_filenames = [key_filename] 271 else: 272 key_filenames = key_filename 273 self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
274
275 - def close(self):
276 """ 277 Close this SSHClient and its underlying `.Transport`. 278 """ 279 if self._transport is None: 280 return 281 self._transport.close() 282 self._transport = None 283 284 if self._agent is not None: 285 self._agent.close() 286 self._agent = None
287
288 - def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False):
289 """ 290 Execute a command on the SSH server. A new `.Channel` is opened and 291 the requested command is executed. The command's input and output 292 streams are returned as Python ``file``-like objects representing 293 stdin, stdout, and stderr. 294 295 :param str command: the command to execute 296 :param int bufsize: 297 interpreted the same way as by the built-in ``file()`` function in 298 Python 299 :param int timeout: 300 set command's channel timeout. See `Channel.settimeout`.settimeout 301 :return: 302 the stdin, stdout, and stderr of the executing command, as a 303 3-tuple 304 305 :raises SSHException: if the server fails to execute the command 306 """ 307 chan = self._transport.open_session() 308 if get_pty: 309 chan.get_pty() 310 chan.settimeout(timeout) 311 chan.exec_command(command) 312 stdin = chan.makefile('wb', bufsize) 313 stdout = chan.makefile('r', bufsize) 314 stderr = chan.makefile_stderr('r', bufsize) 315 return stdin, stdout, stderr
316
317 - def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0, 318 height_pixels=0):
319 """ 320 Start an interactive shell session on the SSH server. A new `.Channel` 321 is opened and connected to a pseudo-terminal using the requested 322 terminal type and size. 323 324 :param str term: 325 the terminal type to emulate (for example, ``"vt100"``) 326 :param int width: the width (in characters) of the terminal window 327 :param int height: the height (in characters) of the terminal window 328 :param int width_pixels: the width (in pixels) of the terminal window 329 :param int height_pixels: the height (in pixels) of the terminal window 330 :return: a new `.Channel` connected to the remote shell 331 332 :raises SSHException: if the server fails to invoke a shell 333 """ 334 chan = self._transport.open_session() 335 chan.get_pty(term, width, height, width_pixels, height_pixels) 336 chan.invoke_shell() 337 return chan
338
339 - def open_sftp(self):
340 """ 341 Open an SFTP session on the SSH server. 342 343 :return: a new `.SFTPClient` session object 344 """ 345 return self._transport.open_sftp_client()
346
347 - def get_transport(self):
348 """ 349 Return the underlying `.Transport` object for this SSH connection. 350 This can be used to perform lower-level tasks, like opening specific 351 kinds of channels. 352 353 :return: the `.Transport` for this connection 354 """ 355 return self._transport
356
357 - def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
358 """ 359 Try, in order: 360 361 - The key passed in, if one was passed in. 362 - Any key we can find through an SSH agent (if allowed). 363 - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed). 364 - Plain username/password auth, if a password was given. 365 366 (The password might be needed to unlock a private key, or for 367 two-factor authentication [for which it is required].) 368 """ 369 saved_exception = None 370 two_factor = False 371 allowed_types = [] 372 373 if pkey is not None: 374 try: 375 self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint())) 376 allowed_types = self._transport.auth_publickey(username, pkey) 377 two_factor = (allowed_types == ['password']) 378 if not two_factor: 379 return 380 except SSHException as e: 381 saved_exception = e 382 383 if not two_factor: 384 for key_filename in key_filenames: 385 for pkey_class in (RSAKey, DSSKey): 386 try: 387 key = pkey_class.from_private_key_file(key_filename, password) 388 self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename)) 389 self._transport.auth_publickey(username, key) 390 two_factor = (allowed_types == ['password']) 391 if not two_factor: 392 return 393 break 394 except SSHException as e: 395 saved_exception = e 396 397 if not two_factor and allow_agent: 398 if self._agent is None: 399 self._agent = Agent() 400 401 for key in self._agent.get_keys(): 402 try: 403 self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint())) 404 # for 2-factor auth a successfully auth'd key will result in ['password'] 405 allowed_types = self._transport.auth_publickey(username, key) 406 two_factor = (allowed_types == ['password']) 407 if not two_factor: 408 return 409 break 410 except SSHException as e: 411 saved_exception = e 412 413 if not two_factor: 414 keyfiles = [] 415 rsa_key = os.path.expanduser('~/.ssh/id_rsa') 416 dsa_key = os.path.expanduser('~/.ssh/id_dsa') 417 if os.path.isfile(rsa_key): 418 keyfiles.append((RSAKey, rsa_key)) 419 if os.path.isfile(dsa_key): 420 keyfiles.append((DSSKey, dsa_key)) 421 # look in ~/ssh/ for windows users: 422 rsa_key = os.path.expanduser('~/ssh/id_rsa') 423 dsa_key = os.path.expanduser('~/ssh/id_dsa') 424 if os.path.isfile(rsa_key): 425 keyfiles.append((RSAKey, rsa_key)) 426 if os.path.isfile(dsa_key): 427 keyfiles.append((DSSKey, dsa_key)) 428 429 if not look_for_keys: 430 keyfiles = [] 431 432 for pkey_class, filename in keyfiles: 433 try: 434 key = pkey_class.from_private_key_file(filename, password) 435 self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename)) 436 # for 2-factor auth a successfully auth'd key will result in ['password'] 437 allowed_types = self._transport.auth_publickey(username, key) 438 two_factor = (allowed_types == ['password']) 439 if not two_factor: 440 return 441 break 442 except (SSHException, IOError) as e: 443 saved_exception = e 444 445 if password is not None: 446 try: 447 self._transport.auth_password(username, password) 448 return 449 except SSHException as e: 450 saved_exception = e 451 elif two_factor: 452 raise SSHException('Two-factor authentication requires a password') 453 454 # if we got an auth-failed exception earlier, re-raise it 455 if saved_exception is not None: 456 raise saved_exception 457 raise SSHException('No authentication methods available')
458
459 - def _log(self, level, msg):
460 self._transport._log(level, msg)
461 462
463 -class MissingHostKeyPolicy (object):
464 """ 465 Interface for defining the policy that `.SSHClient` should use when the 466 SSH server's hostname is not in either the system host keys or the 467 application's keys. Pre-made classes implement policies for automatically 468 adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`), 469 and for automatically rejecting the key (`.RejectPolicy`). 470 471 This function may be used to ask the user to verify the key, for example. 472 """ 473
474 - def missing_host_key(self, client, hostname, key):
475 """ 476 Called when an `.SSHClient` receives a server key for a server that 477 isn't in either the system or local `.HostKeys` object. To accept 478 the key, simply return. To reject, raised an exception (which will 479 be passed to the calling application). 480 """ 481 pass
482 483
484 -class AutoAddPolicy (MissingHostKeyPolicy):
485 """ 486 Policy for automatically adding the hostname and new host key to the 487 local `.HostKeys` object, and saving it. This is used by `.SSHClient`. 488 """ 489
490 - def missing_host_key(self, client, hostname, key):
491 client._host_keys.add(hostname, key.get_name(), key) 492 if client._host_keys_filename is not None: 493 client.save_host_keys(client._host_keys_filename) 494 client._log(DEBUG, 'Adding %s host key for %s: %s' % 495 (key.get_name(), hostname, hexlify(key.get_fingerprint())))
496 497
498 -class RejectPolicy (MissingHostKeyPolicy):
499 """ 500 Policy for automatically rejecting the unknown hostname & key. This is 501 used by `.SSHClient`. 502 """ 503
504 - def missing_host_key(self, client, hostname, key):
505 client._log(DEBUG, 'Rejecting %s host key for %s: %s' % 506 (key.get_name(), hostname, hexlify(key.get_fingerprint()))) 507 raise SSHException('Server %r not found in known_hosts' % hostname)
508 509
510 -class WarningPolicy (MissingHostKeyPolicy):
511 """ 512 Policy for logging a Python-style warning for an unknown host key, but 513 accepting it. This is used by `.SSHClient`. 514 """
515 - def missing_host_key(self, client, hostname, key):
516 warnings.warn('Unknown %s host key for %s: %s' % 517 (key.get_name(), hostname, hexlify(key.get_fingerprint())))
518