1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
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
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
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
74
75
77 """
78 Policy for automatically rejecting the unknown hostname & key. This is
79 used by L{SSHClient}.
80 """
81
86
87
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 """
96
97
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
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
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
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
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
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
190
191
192 if self._host_keys_filename is not None:
193 self.load_host_keys(self._host_keys_filename)
194
195 f = open(filename, 'w')
196 for hostname, keys in self._host_keys.iteritems():
197 for keytype, key in keys.iteritems():
198 f.write('%s %s %s\n' % (hostname, keytype, key.get_base64()))
199 f.close()
200
202 """
203 Get the local L{HostKeys} object. This can be used to examine the
204 local host keys or change them.
205
206 @return: the local host keys
207 @rtype: L{HostKeys}
208 """
209 return self._host_keys
210
212 """
213 Set the channel for logging. The default is C{"paramiko.transport"}
214 but it can be set to anything you want.
215
216 @param name: new channel name for logging
217 @type name: str
218 """
219 self._log_channel = name
220
222 """
223 Set the policy to use when connecting to a server that doesn't have a
224 host key in either the system or local L{HostKeys} objects. The
225 default policy is to reject all unknown servers (using L{RejectPolicy}).
226 You may substitute L{AutoAddPolicy} or write your own policy class.
227
228 @param policy: the policy to use when receiving a host key from a
229 previously-unknown server
230 @type policy: L{MissingHostKeyPolicy}
231 """
232 self._policy = policy
233
234 - def connect(self, hostname, port=SSH_PORT, username=None, password=None, pkey=None,
235 key_filename=None, timeout=None, allow_agent=True, look_for_keys=True,
236 compress=False, sock=None):
237 """
238 Connect to an SSH server and authenticate to it. The server's host key
239 is checked against the system host keys (see L{load_system_host_keys})
240 and any local host keys (L{load_host_keys}). If the server's hostname
241 is not found in either set of host keys, the missing host key policy
242 is used (see L{set_missing_host_key_policy}). The default policy is
243 to reject the key and raise an L{SSHException}.
244
245 Authentication is attempted in the following order of priority:
246
247 - The C{pkey} or C{key_filename} passed in (if any)
248 - Any key we can find through an SSH agent
249 - Any "id_rsa" or "id_dsa" key discoverable in C{~/.ssh/}
250 - Plain username/password auth, if a password was given
251
252 If a private key requires a password to unlock it, and a password is
253 passed in, that password will be used to attempt to unlock the key.
254
255 @param hostname: the server to connect to
256 @type hostname: str
257 @param port: the server port to connect to
258 @type port: int
259 @param username: the username to authenticate as (defaults to the
260 current local username)
261 @type username: str
262 @param password: a password to use for authentication or for unlocking
263 a private key
264 @type password: str
265 @param pkey: an optional private key to use for authentication
266 @type pkey: L{PKey}
267 @param key_filename: the filename, or list of filenames, of optional
268 private key(s) to try for authentication
269 @type key_filename: str or list(str)
270 @param timeout: an optional timeout (in seconds) for the TCP connect
271 @type timeout: float
272 @param allow_agent: set to False to disable connecting to the SSH agent
273 @type allow_agent: bool
274 @param look_for_keys: set to False to disable searching for discoverable
275 private key files in C{~/.ssh/}
276 @type look_for_keys: bool
277 @param compress: set to True to turn on compression
278 @type compress: bool
279 @param sock: an open socket or socket-like object (such as a
280 L{Channel}) to use for communication to the target host
281 @type sock: socket
282
283 @raise BadHostKeyException: if the server's host key could not be
284 verified
285 @raise AuthenticationException: if authentication failed
286 @raise SSHException: if there was any other error connecting or
287 establishing an SSH session
288 @raise socket.error: if a socket error occurred while connecting
289 """
290 if not sock:
291 for (family, socktype, proto, canonname, sockaddr) in socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM):
292 if socktype == socket.SOCK_STREAM:
293 af = family
294 addr = sockaddr
295 break
296 else:
297
298 af, _, _, _, addr = socket.getaddrinfo(hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
299 sock = socket.socket(af, socket.SOCK_STREAM)
300 if timeout is not None:
301 try:
302 sock.settimeout(timeout)
303 except:
304 pass
305 retry_on_signal(lambda: sock.connect(addr))
306
307 t = self._transport = Transport(sock)
308 t.use_compression(compress=compress)
309 if self._log_channel is not None:
310 t.set_log_channel(self._log_channel)
311 t.start_client()
312 ResourceManager.register(self, t)
313
314 server_key = t.get_remote_server_key()
315 keytype = server_key.get_name()
316
317 if port == SSH_PORT:
318 server_hostkey_name = hostname
319 else:
320 server_hostkey_name = "[%s]:%d" % (hostname, port)
321 our_server_key = self._system_host_keys.get(server_hostkey_name, {}).get(keytype, None)
322 if our_server_key is None:
323 our_server_key = self._host_keys.get(server_hostkey_name, {}).get(keytype, None)
324 if our_server_key is None:
325
326 self._policy.missing_host_key(self, server_hostkey_name, server_key)
327
328 our_server_key = server_key
329
330 if server_key != our_server_key:
331 raise BadHostKeyException(hostname, server_key, our_server_key)
332
333 if username is None:
334 username = getpass.getuser()
335
336 if key_filename is None:
337 key_filenames = []
338 elif isinstance(key_filename, (str, unicode)):
339 key_filenames = [ key_filename ]
340 else:
341 key_filenames = key_filename
342 self._auth(username, password, pkey, key_filenames, allow_agent, look_for_keys)
343
345 """
346 Close this SSHClient and its underlying L{Transport}.
347 """
348 if self._transport is None:
349 return
350 self._transport.close()
351 self._transport = None
352
353 if self._agent != None:
354 self._agent.close()
355 self._agent = None
356
357 - def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False):
358 """
359 Execute a command on the SSH server. A new L{Channel} is opened and
360 the requested command is executed. The command's input and output
361 streams are returned as python C{file}-like objects representing
362 stdin, stdout, and stderr.
363
364 @param command: the command to execute
365 @type command: str
366 @param bufsize: interpreted the same way as by the built-in C{file()} function in python
367 @type bufsize: int
368 @param timeout: set command's channel timeout. See L{Channel.settimeout}.settimeout
369 @type timeout: int
370 @return: the stdin, stdout, and stderr of the executing command
371 @rtype: tuple(L{ChannelFile}, L{ChannelFile}, L{ChannelFile})
372
373 @raise SSHException: if the server fails to execute the command
374 """
375 chan = self._transport.open_session()
376 if(get_pty):
377 chan.get_pty()
378 chan.settimeout(timeout)
379 chan.exec_command(command)
380 stdin = chan.makefile('wb', bufsize)
381 stdout = chan.makefile('rb', bufsize)
382 stderr = chan.makefile_stderr('rb', bufsize)
383 return stdin, stdout, stderr
384
385 - def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0,
386 height_pixels=0):
387 """
388 Start an interactive shell session on the SSH server. A new L{Channel}
389 is opened and connected to a pseudo-terminal using the requested
390 terminal type and size.
391
392 @param term: the terminal type to emulate (for example, C{"vt100"})
393 @type term: str
394 @param width: the width (in characters) of the terminal window
395 @type width: int
396 @param height: the height (in characters) of the terminal window
397 @type height: int
398 @param width_pixels: the width (in pixels) of the terminal window
399 @type width_pixels: int
400 @param height_pixels: the height (in pixels) of the terminal window
401 @type height_pixels: int
402 @return: a new channel connected to the remote shell
403 @rtype: L{Channel}
404
405 @raise SSHException: if the server fails to invoke a shell
406 """
407 chan = self._transport.open_session()
408 chan.get_pty(term, width, height, width_pixels, height_pixels)
409 chan.invoke_shell()
410 return chan
411
413 """
414 Open an SFTP session on the SSH server.
415
416 @return: a new SFTP session object
417 @rtype: L{SFTPClient}
418 """
419 return self._transport.open_sftp_client()
420
422 """
423 Return the underlying L{Transport} object for this SSH connection.
424 This can be used to perform lower-level tasks, like opening specific
425 kinds of channels.
426
427 @return: the Transport for this connection
428 @rtype: L{Transport}
429 """
430 return self._transport
431
432 - def _auth(self, username, password, pkey, key_filenames, allow_agent, look_for_keys):
433 """
434 Try, in order:
435
436 - The key passed in, if one was passed in.
437 - Any key we can find through an SSH agent (if allowed).
438 - Any "id_rsa" or "id_dsa" key discoverable in ~/.ssh/ (if allowed).
439 - Plain username/password auth, if a password was given.
440
441 (The password might be needed to unlock a private key, or for
442 two-factor authentication [for which it is required].)
443 """
444 saved_exception = None
445 two_factor = False
446 allowed_types = []
447
448 if pkey is not None:
449 try:
450 self._log(DEBUG, 'Trying SSH key %s' % hexlify(pkey.get_fingerprint()))
451 allowed_types = self._transport.auth_publickey(username, pkey)
452 two_factor = (allowed_types == ['password'])
453 if not two_factor:
454 return
455 except SSHException, e:
456 saved_exception = e
457
458 if not two_factor:
459 for key_filename in key_filenames:
460 for pkey_class in (RSAKey, DSSKey):
461 try:
462 key = pkey_class.from_private_key_file(key_filename, password)
463 self._log(DEBUG, 'Trying key %s from %s' % (hexlify(key.get_fingerprint()), key_filename))
464 self._transport.auth_publickey(username, key)
465 two_factor = (allowed_types == ['password'])
466 if not two_factor:
467 return
468 break
469 except SSHException, e:
470 saved_exception = e
471
472 if not two_factor and allow_agent:
473 if self._agent == None:
474 self._agent = Agent()
475
476 for key in self._agent.get_keys():
477 try:
478 self._log(DEBUG, 'Trying SSH agent key %s' % hexlify(key.get_fingerprint()))
479
480 allowed_types = self._transport.auth_publickey(username, key)
481 two_factor = (allowed_types == ['password'])
482 if not two_factor:
483 return
484 break
485 except SSHException, e:
486 saved_exception = e
487
488 if not two_factor:
489 keyfiles = []
490 rsa_key = os.path.expanduser('~/.ssh/id_rsa')
491 dsa_key = os.path.expanduser('~/.ssh/id_dsa')
492 if os.path.isfile(rsa_key):
493 keyfiles.append((RSAKey, rsa_key))
494 if os.path.isfile(dsa_key):
495 keyfiles.append((DSSKey, dsa_key))
496
497 rsa_key = os.path.expanduser('~/ssh/id_rsa')
498 dsa_key = os.path.expanduser('~/ssh/id_dsa')
499 if os.path.isfile(rsa_key):
500 keyfiles.append((RSAKey, rsa_key))
501 if os.path.isfile(dsa_key):
502 keyfiles.append((DSSKey, dsa_key))
503
504 if not look_for_keys:
505 keyfiles = []
506
507 for pkey_class, filename in keyfiles:
508 try:
509 key = pkey_class.from_private_key_file(filename, password)
510 self._log(DEBUG, 'Trying discovered key %s in %s' % (hexlify(key.get_fingerprint()), filename))
511
512 allowed_types = self._transport.auth_publickey(username, key)
513 two_factor = (allowed_types == ['password'])
514 if not two_factor:
515 return
516 break
517 except SSHException, e:
518 saved_exception = e
519 except IOError, e:
520 saved_exception = e
521
522 if password is not None:
523 try:
524 self._transport.auth_password(username, password)
525 return
526 except SSHException, e:
527 saved_exception = e
528 elif two_factor:
529 raise SSHException('Two-factor authentication requires a password')
530
531
532 if saved_exception is not None:
533 raise saved_exception
534 raise SSHException('No authentication methods available')
535
536 - def _log(self, level, msg):
537 self._transport._log(level, msg)
538