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