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