Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/client.py: 16%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

275 statements  

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# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 

18 

19""" 

20SSH client & key policies 

21""" 

22 

23import getpass 

24import inspect 

25import os 

26import socket 

27import warnings 

28from binascii import hexlify 

29from errno import ECONNREFUSED, EHOSTUNREACH 

30 

31from paramiko.agent import Agent 

32from paramiko.common import DEBUG 

33from paramiko.config import SSH_PORT 

34from paramiko.ecdsakey import ECDSAKey 

35from paramiko.ed25519key import Ed25519Key 

36from paramiko.hostkeys import HostKeys 

37from paramiko.rsakey import RSAKey 

38from paramiko.ssh_exception import ( 

39 BadHostKeyException, 

40 NoValidConnectionsError, 

41 SSHException, 

42) 

43from paramiko.transport import Transport 

44from paramiko.util import ClosingContextManager 

45 

46 

47class SSHClient(ClosingContextManager): 

48 """ 

49 A high-level representation of a session with an SSH server. This class 

50 wraps `.Transport`, `.Channel`, and `.SFTPClient` to take care of most 

51 aspects of authenticating and opening channels. A typical use case is:: 

52 

53 client = SSHClient() 

54 client.load_system_host_keys() 

55 client.connect('ssh.example.com') 

56 stdin, stdout, stderr = client.exec_command('ls -l') 

57 

58 You may pass in explicit overrides for authentication and server host key 

59 checking. The default mechanism is to try to use local key files or an 

60 SSH agent (if one is running). 

61 

62 Instances of this class may be used as context managers. 

63 

64 .. versionadded:: 1.6 

65 """ 

66 

67 def __init__(self): 

68 """ 

69 Create a new SSHClient. 

70 """ 

71 self._system_host_keys = HostKeys() 

72 self._host_keys = HostKeys() 

73 self._host_keys_filename = None 

74 self._log_channel = None 

75 self._policy = RejectPolicy() 

76 self._transport = None 

77 self._agent = None 

78 

79 def load_system_host_keys(self, filename=None): 

80 """ 

81 Load host keys from a system (read-only) file. Host keys read with 

82 this method will not be saved back by `save_host_keys`. 

83 

84 This method can be called multiple times. Each new set of host keys 

85 will be merged with the existing set (new replacing old if there are 

86 conflicts). 

87 

88 If ``filename`` is left as ``None``, an attempt will be made to read 

89 keys from the user's local "known hosts" file, as used by OpenSSH, 

90 and no exception will be raised if the file can't be read. This is 

91 probably only useful on posix. 

92 

93 :param str filename: the filename to read, or ``None`` 

94 

95 :raises: ``IOError`` -- 

96 if a filename was provided and the file could not be read 

97 """ 

98 if filename is None: 

99 # try the user's .ssh key file, and mask exceptions 

100 filename = os.path.expanduser("~/.ssh/known_hosts") 

101 try: 

102 self._system_host_keys.load(filename) 

103 except IOError: 

104 pass 

105 return 

106 self._system_host_keys.load(filename) 

107 

108 def load_host_keys(self, filename): 

109 """ 

110 Load host keys from a local host-key file. Host keys read with this 

111 method will be checked after keys loaded via `load_system_host_keys`, 

112 but will be saved back by `save_host_keys` (so they can be modified). 

113 The missing host key policy `.AutoAddPolicy` adds keys to this set and 

114 saves them, when connecting to a previously-unknown server. 

115 

116 This method can be called multiple times. Each new set of host keys 

117 will be merged with the existing set (new replacing old if there are 

118 conflicts). When automatically saving, the last hostname is used. 

119 

120 :param str filename: the filename to read 

121 

122 :raises: ``IOError`` -- if the filename could not be read 

123 """ 

124 self._host_keys_filename = filename 

125 self._host_keys.load(filename) 

126 

127 def save_host_keys(self, filename): 

128 """ 

129 Save the host keys back to a file. Only the host keys loaded with 

130 `load_host_keys` (plus any added directly) will be saved -- not any 

131 host keys loaded with `load_system_host_keys`. 

132 

133 :param str filename: the filename to save to 

134 

135 :raises: ``IOError`` -- if the file could not be written 

136 """ 

137 

138 # update local host keys from file (in case other SSH clients 

139 # have written to the known_hosts file meanwhile. 

140 if self._host_keys_filename is not None: 

141 self.load_host_keys(self._host_keys_filename) 

142 

143 with open(filename, "w") as f: 

144 for hostname, keys in self._host_keys.items(): 

145 for keytype, key in keys.items(): 

146 f.write( 

147 "{} {} {}\n".format( 

148 hostname, keytype, key.get_base64() 

149 ) 

150 ) 

151 

152 def get_host_keys(self): 

153 """ 

154 Get the local `.HostKeys` object. This can be used to examine the 

155 local host keys or change them. 

156 

157 :return: the local host keys as a `.HostKeys` object. 

158 """ 

159 return self._host_keys 

160 

161 def set_log_channel(self, name): 

162 """ 

163 Set the channel for logging. The default is ``"paramiko.transport"`` 

164 but it can be set to anything you want. 

165 

166 :param str name: new channel name for logging 

167 """ 

168 self._log_channel = name 

169 

170 def set_missing_host_key_policy(self, policy): 

171 """ 

172 Set policy to use when connecting to servers without a known host key. 

173 

174 Specifically: 

175 

176 * A **policy** is a "policy class" (or instance thereof), namely some 

177 subclass of `.MissingHostKeyPolicy` such as `.RejectPolicy` (the 

178 default), `.AutoAddPolicy`, `.WarningPolicy`, or a user-created 

179 subclass. 

180 * A host key is **known** when it appears in the client object's cached 

181 host keys structures (those manipulated by `load_system_host_keys` 

182 and/or `load_host_keys`). 

183 

184 :param .MissingHostKeyPolicy policy: 

185 the policy to use when receiving a host key from a 

186 previously-unknown server 

187 """ 

188 if inspect.isclass(policy): 

189 policy = policy() 

190 self._policy = policy 

191 

192 def _families_and_addresses(self, hostname, port): 

193 """ 

194 Yield pairs of address families and addresses to try for connecting. 

195 

196 :param str hostname: the server to connect to 

197 :param int port: the server port to connect to 

198 :returns: Yields an iterable of ``(family, address)`` tuples 

199 """ 

200 guess = True 

201 addrinfos = socket.getaddrinfo( 

202 hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM 

203 ) 

204 for family, socktype, proto, canonname, sockaddr in addrinfos: 

205 if socktype == socket.SOCK_STREAM: 

206 yield family, sockaddr 

207 guess = False 

208 

209 # some OS like AIX don't indicate SOCK_STREAM support, so just 

210 # guess. :( We only do this if we did not get a single result marked 

211 # as socktype == SOCK_STREAM. 

212 if guess: 

213 for family, _, _, _, sockaddr in addrinfos: 

214 yield family, sockaddr 

215 

216 def connect( 

217 self, 

218 hostname, 

219 port=SSH_PORT, 

220 username=None, 

221 password=None, 

222 pkey=None, 

223 key_filename=None, 

224 timeout=None, 

225 allow_agent=True, 

226 look_for_keys=True, 

227 compress=False, 

228 sock=None, 

229 banner_timeout=None, 

230 auth_timeout=None, 

231 channel_timeout=None, 

232 passphrase=None, 

233 disabled_algorithms=None, 

234 transport_factory=None, 

235 auth_strategy=None, 

236 ): 

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 `load_system_host_keys`) 

240 and any local host keys (`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 `set_missing_host_key_policy`). The default policy is 

243 to reject the key and raise an `.SSHException`. 

244 

245 Authentication is attempted in the following order of priority: 

246 

247 - The ``pkey`` or ``key_filename`` passed in (if any) 

248 

249 - ``key_filename`` may contain OpenSSH public certificate paths 

250 as well as regular private-key paths; when files ending in 

251 ``-cert.pub`` are found, they are assumed to match a private 

252 key, and both components will be loaded. (The private key 

253 itself does *not* need to be listed in ``key_filename`` for 

254 this to occur - *just* the certificate.) 

255 

256 - Any key we can find through an SSH agent 

257 - Any ``id_*`` keys discoverable in ``~/.ssh/`` 

258 

259 - When OpenSSH-style public certificates exist that match an 

260 existing such private key (so e.g. one has ``id_rsa`` and 

261 ``id_rsa-cert.pub``) the certificate will be loaded alongside 

262 the private key and used for authentication. 

263 

264 - Plain username/password auth, if a password was given 

265 

266 If a private key requires a password to unlock it, and a password is 

267 passed in, that password will be used to attempt to unlock the key. 

268 

269 :param str hostname: the server to connect to 

270 :param int port: the server port to connect to 

271 :param str username: 

272 the username to authenticate as (defaults to the current local 

273 username) 

274 :param str password: 

275 Used for password authentication; is also used for private key 

276 decryption if ``passphrase`` is not given. 

277 :param str passphrase: 

278 Used for decrypting private keys. 

279 :param .PKey pkey: an optional private key to use for authentication 

280 :param str key_filename: 

281 the filename, or list of filenames, of optional private key(s) 

282 and/or certs to try for authentication 

283 :param float timeout: 

284 an optional timeout (in seconds) for the TCP connect 

285 :param bool allow_agent: 

286 set to False to disable connecting to the SSH agent 

287 :param bool look_for_keys: 

288 set to False to disable searching for discoverable private key 

289 files in ``~/.ssh/`` 

290 :param bool compress: set to True to turn on compression 

291 :param socket sock: 

292 an open socket or socket-like object (such as a `.Channel`) to use 

293 for communication to the target host 

294 :param float banner_timeout: an optional timeout (in seconds) to wait 

295 for the SSH banner to be presented. 

296 :param float auth_timeout: an optional timeout (in seconds) to wait for 

297 an authentication response. 

298 :param float channel_timeout: an optional timeout (in seconds) to wait 

299 for a channel open response. 

300 :param dict disabled_algorithms: 

301 an optional dict passed directly to `.Transport` and its keyword 

302 argument of the same name. 

303 :param transport_factory: 

304 an optional callable which is handed a subset of the constructor 

305 arguments (primarily those related to the socket and algorithm 

306 selection) and generates a `.Transport` instance to be used by this 

307 client. Defaults to `.Transport.__init__`. 

308 :param auth_strategy: 

309 an optional instance of `.AuthStrategy`, triggering use of this 

310 newer authentication mechanism instead of SSHClient's legacy auth 

311 method. 

312 

313 .. warning:: 

314 This parameter is **incompatible** with all other 

315 authentication-related parameters (such as, but not limited to, 

316 ``password``, ``key_filename`` and ``allow_agent``) and will 

317 trigger an exception if given alongside them. 

318 

319 :returns: 

320 `.AuthResult` if ``auth_strategy`` is non-``None``; otherwise, 

321 returns ``None``. 

322 

323 :raises BadHostKeyException: 

324 if the server's host key could not be verified. 

325 :raises AuthenticationException: 

326 if authentication failed. 

327 :raises UnableToAuthenticate: 

328 if authentication failed (when ``auth_strategy`` is non-``None``; 

329 and note that this is a subclass of ``AuthenticationException``). 

330 :raises socket.error: 

331 if a socket error (other than connection-refused or 

332 host-unreachable) occurred while connecting. 

333 :raises NoValidConnectionsError: 

334 if all valid connection targets for the requested hostname (eg IPv4 

335 and IPv6) yielded connection-refused or host-unreachable socket 

336 errors. 

337 :raises SSHException: 

338 if there was any other error connecting or establishing an SSH 

339 session. 

340 

341 .. versionchanged:: 1.15 

342 Added the ``banner_timeout`` argument. 

343 .. versionchanged:: 2.4 

344 Added the ``passphrase`` argument. 

345 .. versionchanged:: 2.6 

346 Added the ``disabled_algorithms`` argument. 

347 .. versionchanged:: 2.12 

348 Added the ``transport_factory`` argument. 

349 .. versionchanged:: 3.2 

350 Added the ``auth_strategy`` argument. 

351 """ 

352 if not sock: 

353 errors = {} 

354 # Try multiple possible address families (e.g. IPv4 vs IPv6) 

355 to_try = list(self._families_and_addresses(hostname, port)) 

356 for af, addr in to_try: 

357 try: 

358 sock = socket.socket(af, socket.SOCK_STREAM) 

359 if timeout is not None: 

360 try: 

361 sock.settimeout(timeout) 

362 except: 

363 pass 

364 sock.connect(addr) 

365 # Break out of the loop on success 

366 break 

367 except socket.error as e: 

368 # As mentioned in socket docs it is better 

369 # to close sockets explicitly 

370 if sock: 

371 sock.close() 

372 # Raise anything that isn't a straight up connection error 

373 # (such as a resolution error) 

374 if e.errno not in (ECONNREFUSED, EHOSTUNREACH): 

375 raise 

376 # Capture anything else so we know how the run looks once 

377 # iteration is complete. Retain info about which attempt 

378 # this was. 

379 errors[addr] = e 

380 

381 # Make sure we explode usefully if no address family attempts 

382 # succeeded. We've no way of knowing which error is the "right" 

383 # one, so we construct a hybrid exception containing all the real 

384 # ones, of a subclass that client code should still be watching for 

385 # (socket.error) 

386 if len(errors) == len(to_try): 

387 raise NoValidConnectionsError(errors) 

388 

389 if transport_factory is None: 

390 transport_factory = Transport 

391 t = self._transport = transport_factory( 

392 sock, 

393 disabled_algorithms=disabled_algorithms, 

394 ) 

395 t.use_compression(compress=compress) 

396 if self._log_channel is not None: 

397 t.set_log_channel(self._log_channel) 

398 if banner_timeout is not None: 

399 t.banner_timeout = banner_timeout 

400 if auth_timeout is not None: 

401 t.auth_timeout = auth_timeout 

402 if channel_timeout is not None: 

403 t.channel_timeout = channel_timeout 

404 

405 if port == SSH_PORT: 

406 server_hostkey_name = hostname 

407 else: 

408 server_hostkey_name = "[{}]:{}".format(hostname, port) 

409 our_server_keys = None 

410 

411 our_server_keys = self._system_host_keys.get(server_hostkey_name) 

412 if our_server_keys is None: 

413 # TODO: this is getting us the test suite _test_connection host key 

414 # setup by virtue of that code doing tc.get_host_keys().add() (that 

415 # method wraps self._host_keys) 

416 # TODO: it should be analogous to running paramiko w/ a 

417 # ~/.ssh/known_hosts file 

418 our_server_keys = self._host_keys.get(server_hostkey_name) 

419 if our_server_keys is not None: 

420 # TODO: keytype needs to turn into one of the rsa-sha2 keys if it's 

421 # an RSAKey. 

422 # TODO: where is the equivalent on our server-side? hopefully the 

423 # tests exercise that lol 

424 # TODO: also, is it just me or does this [0] mean we literally do 

425 # not actually implement real HostKeyAlgorithms agreement like 

426 # ssh.c does?! eesh 

427 keytype = our_server_keys.keys()[0] 

428 sec_opts = t.get_security_options() 

429 # TODO: clean this up a bit, but it does help some tests pass! 

430 if keytype == "ssh-rsa": 

431 if "rsa-sha2-512" in sec_opts.key_types: 

432 keytype = "rsa-sha2-512" 

433 elif "rsa-sha2-256" in sec_opts.key_types: 

434 keytype = "rsa-sha2-256" 

435 else: 

436 # TODO: vvv 

437 raise Exception( 

438 "TODO: REPLACEME with appropriate exception for 'what even is this key type in your known_hosts files?'" # noqa 

439 ) 

440 other_types = [x for x in sec_opts.key_types if x != keytype] 

441 sec_opts.key_types = [keytype] + other_types 

442 

443 t.start_client(timeout=timeout) 

444 

445 server_key = t.get_remote_server_key() 

446 if our_server_keys is None: 

447 # will raise exception if the key is rejected 

448 self._policy.missing_host_key( 

449 self, server_hostkey_name, server_key 

450 ) 

451 else: 

452 # TODO: this should 'just work' but dblcheck (HostKeys will be 

453 # offering an ssh-rsa-via-sha2 key as "ssh-rsa" still, and the 

454 # key would be RSAKey whose .get_name would still say 

455 # "ssh-rsa") 

456 our_key = our_server_keys.get(server_key.get_name()) 

457 if our_key != server_key: 

458 if our_key is None: 

459 our_key = list(our_server_keys.values())[0] 

460 raise BadHostKeyException(hostname, server_key, our_key) 

461 

462 if username is None: 

463 username = getpass.getuser() 

464 

465 # New auth flow! 

466 if auth_strategy is not None: 

467 return auth_strategy.authenticate(transport=t) 

468 

469 # Old auth flow! 

470 if key_filename is None: 

471 key_filenames = [] 

472 elif isinstance(key_filename, str): 

473 key_filenames = [key_filename] 

474 else: 

475 key_filenames = key_filename 

476 

477 self._auth( 

478 username, 

479 password, 

480 pkey, 

481 key_filenames, 

482 allow_agent, 

483 look_for_keys, 

484 passphrase, 

485 ) 

486 

487 def close(self): 

488 """ 

489 Close this SSHClient and its underlying `.Transport`. 

490 

491 This should be called anytime you are done using the client object. 

492 

493 .. warning:: 

494 Paramiko registers garbage collection hooks that will try to 

495 automatically close connections for you, but this is not presently 

496 reliable. Failure to explicitly close your client after use may 

497 lead to end-of-process hangs! 

498 """ 

499 if self._transport is None: 

500 return 

501 self._transport.close() 

502 self._transport = None 

503 

504 if self._agent is not None: 

505 self._agent.close() 

506 self._agent = None 

507 

508 def exec_command( 

509 self, 

510 command, 

511 bufsize=-1, 

512 timeout=None, 

513 get_pty=False, 

514 environment=None, 

515 ): 

516 """ 

517 Execute a command on the SSH server. A new `.Channel` is opened and 

518 the requested command is executed. The command's input and output 

519 streams are returned as Python ``file``-like objects representing 

520 stdin, stdout, and stderr. 

521 

522 :param str command: the command to execute 

523 :param int bufsize: 

524 interpreted the same way as by the built-in ``file()`` function in 

525 Python 

526 :param int timeout: 

527 set command's channel timeout. See `.Channel.settimeout` 

528 :param bool get_pty: 

529 Request a pseudo-terminal from the server (default ``False``). 

530 See `.Channel.get_pty` 

531 :param dict environment: 

532 a dict of shell environment variables, to be merged into the 

533 default environment that the remote command executes within. 

534 

535 .. warning:: 

536 Servers may silently reject some environment variables; see the 

537 warning in `.Channel.set_environment_variable` for details. 

538 

539 :return: 

540 the stdin, stdout, and stderr of the executing command, as a 

541 3-tuple 

542 

543 :raises: `.SSHException` -- if the server fails to execute the command 

544 

545 .. versionchanged:: 1.10 

546 Added the ``get_pty`` kwarg. 

547 """ 

548 chan = self._transport.open_session(timeout=timeout) 

549 if get_pty: 

550 chan.get_pty() 

551 chan.settimeout(timeout) 

552 if environment: 

553 chan.update_environment(environment) 

554 chan.exec_command(command) 

555 stdin = chan.makefile_stdin("wb", bufsize) 

556 stdout = chan.makefile("r", bufsize) 

557 stderr = chan.makefile_stderr("r", bufsize) 

558 return stdin, stdout, stderr 

559 

560 def invoke_shell( 

561 self, 

562 term="vt100", 

563 width=80, 

564 height=24, 

565 width_pixels=0, 

566 height_pixels=0, 

567 environment=None, 

568 ): 

569 """ 

570 Start an interactive shell session on the SSH server. A new `.Channel` 

571 is opened and connected to a pseudo-terminal using the requested 

572 terminal type and size. 

573 

574 :param str term: 

575 the terminal type to emulate (for example, ``"vt100"``) 

576 :param int width: the width (in characters) of the terminal window 

577 :param int height: the height (in characters) of the terminal window 

578 :param int width_pixels: the width (in pixels) of the terminal window 

579 :param int height_pixels: the height (in pixels) of the terminal window 

580 :param dict environment: the command's environment 

581 :return: a new `.Channel` connected to the remote shell 

582 

583 :raises: `.SSHException` -- if the server fails to invoke a shell 

584 """ 

585 chan = self._transport.open_session() 

586 chan.get_pty(term, width, height, width_pixels, height_pixels) 

587 chan.invoke_shell() 

588 return chan 

589 

590 def open_sftp(self): 

591 """ 

592 Open an SFTP session on the SSH server. 

593 

594 :return: a new `.SFTPClient` session object 

595 """ 

596 return self._transport.open_sftp_client() 

597 

598 def get_transport(self): 

599 """ 

600 Return the underlying `.Transport` object for this SSH connection. 

601 This can be used to perform lower-level tasks, like opening specific 

602 kinds of channels. 

603 

604 :return: the `.Transport` for this connection 

605 """ 

606 return self._transport 

607 

608 def _key_from_filepath(self, filename, klass, password): 

609 """ 

610 Attempt to derive a `.PKey` from given string path ``filename``: 

611 

612 - If ``filename`` appears to be a cert, the matching private key is 

613 loaded. 

614 - Otherwise, the filename is assumed to be a private key, and the 

615 matching public cert will be loaded if it exists. 

616 """ 

617 cert_suffix = "-cert.pub" 

618 # Assume privkey, not cert, by default 

619 if filename.endswith(cert_suffix): 

620 key_path = filename[: -len(cert_suffix)] 

621 cert_path = filename 

622 else: 

623 key_path = filename 

624 cert_path = filename + cert_suffix 

625 # Blindly try the key path; if no private key, nothing will work. 

626 key = klass.from_private_key_file(key_path, password) 

627 # TODO: change this to 'Loading' instead of 'Trying' sometime; probably 

628 # when #387 is released, since this is a critical log message users are 

629 # likely testing/filtering for (bah.) 

630 msg = "Trying discovered key {} in {}".format( 

631 hexlify(key.get_fingerprint()), key_path 

632 ) 

633 self._log(DEBUG, msg) 

634 # Attempt to load cert if it exists. 

635 if os.path.isfile(cert_path): 

636 key.load_certificate(cert_path) 

637 self._log(DEBUG, "Adding public certificate {}".format(cert_path)) 

638 return key 

639 

640 def _auth( 

641 self, 

642 username, 

643 password, 

644 pkey, 

645 key_filenames, 

646 allow_agent, 

647 look_for_keys, 

648 passphrase, 

649 ): 

650 """ 

651 Try, in order: 

652 

653 - The key(s) passed in, if one was passed in. 

654 - Any key we can find through an SSH agent (if allowed). 

655 - Any id_* key discoverable in ~/.ssh/ (if allowed). 

656 - Plain username/password auth, if a password was given. 

657 

658 (The password might be needed to unlock a private key [if 'passphrase' 

659 isn't also given], or for two-factor authentication [for which it is 

660 required].) 

661 """ 

662 saved_exception = None 

663 two_factor = False 

664 allowed_types = set() 

665 two_factor_types = {"keyboard-interactive", "password"} 

666 if passphrase is None and password is not None: 

667 passphrase = password 

668 

669 if pkey is not None: 

670 try: 

671 self._log( 

672 DEBUG, 

673 "Trying SSH key {}".format( 

674 hexlify(pkey.get_fingerprint()) 

675 ), 

676 ) 

677 allowed_types = set( 

678 self._transport.auth_publickey(username, pkey) 

679 ) 

680 two_factor = allowed_types & two_factor_types 

681 if not two_factor: 

682 return 

683 except SSHException as e: 

684 saved_exception = e 

685 

686 if not two_factor: 

687 for key_filename in key_filenames: 

688 # TODO (backwards incompat): leverage PKey.from_path() if we 

689 # don't end up just killing SSHClient entirely 

690 for pkey_class in (RSAKey, ECDSAKey, Ed25519Key): 

691 try: 

692 key = self._key_from_filepath( 

693 key_filename, pkey_class, passphrase 

694 ) 

695 allowed_types = set( 

696 self._transport.auth_publickey(username, key) 

697 ) 

698 two_factor = allowed_types & two_factor_types 

699 if not two_factor: 

700 return 

701 break 

702 except SSHException as e: 

703 saved_exception = e 

704 

705 if not two_factor and allow_agent: 

706 if self._agent is None: 

707 self._agent = Agent() 

708 

709 for key in self._agent.get_keys(): 

710 try: 

711 id_ = hexlify(key.get_fingerprint()) 

712 self._log(DEBUG, "Trying SSH agent key {}".format(id_)) 

713 # for 2-factor auth a successfully auth'd key password 

714 # will return an allowed 2fac auth method 

715 allowed_types = set( 

716 self._transport.auth_publickey(username, key) 

717 ) 

718 two_factor = allowed_types & two_factor_types 

719 if not two_factor: 

720 return 

721 break 

722 except SSHException as e: 

723 saved_exception = e 

724 

725 if not two_factor: 

726 keyfiles = [] 

727 

728 for keytype, name in [ 

729 (RSAKey, "rsa"), 

730 (ECDSAKey, "ecdsa"), 

731 (Ed25519Key, "ed25519"), 

732 ]: 

733 # ~/ssh/ is for windows 

734 for directory in [".ssh", "ssh"]: 

735 full_path = os.path.expanduser( 

736 "~/{}/id_{}".format(directory, name) 

737 ) 

738 if os.path.isfile(full_path): 

739 # TODO: only do this append if below did not run 

740 keyfiles.append((keytype, full_path)) 

741 if os.path.isfile(full_path + "-cert.pub"): 

742 keyfiles.append((keytype, full_path + "-cert.pub")) 

743 

744 if not look_for_keys: 

745 keyfiles = [] 

746 

747 for pkey_class, filename in keyfiles: 

748 try: 

749 key = self._key_from_filepath( 

750 filename, pkey_class, passphrase 

751 ) 

752 # for 2-factor auth a successfully auth'd key will result 

753 # in ['password'] 

754 allowed_types = set( 

755 self._transport.auth_publickey(username, key) 

756 ) 

757 two_factor = allowed_types & two_factor_types 

758 if not two_factor: 

759 return 

760 break 

761 except (SSHException, IOError) as e: 

762 saved_exception = e 

763 

764 if password is not None: 

765 try: 

766 self._transport.auth_password(username, password) 

767 return 

768 except SSHException as e: 

769 saved_exception = e 

770 elif two_factor: 

771 try: 

772 self._transport.auth_interactive_dumb(username) 

773 return 

774 except SSHException as e: 

775 saved_exception = e 

776 

777 # if we got an auth-failed exception earlier, re-raise it 

778 if saved_exception is not None: 

779 raise saved_exception 

780 raise SSHException("No authentication methods available") 

781 

782 def _log(self, level, msg): 

783 self._transport._log(level, msg) 

784 

785 

786class MissingHostKeyPolicy: 

787 """ 

788 Interface for defining the policy that `.SSHClient` should use when the 

789 SSH server's hostname is not in either the system host keys or the 

790 application's keys. Pre-made classes implement policies for automatically 

791 adding the key to the application's `.HostKeys` object (`.AutoAddPolicy`), 

792 and for automatically rejecting the key (`.RejectPolicy`). 

793 

794 This function may be used to ask the user to verify the key, for example. 

795 """ 

796 

797 def missing_host_key(self, client, hostname, key): 

798 """ 

799 Called when an `.SSHClient` receives a server key for a server that 

800 isn't in either the system or local `.HostKeys` object. To accept 

801 the key, simply return. To reject, raised an exception (which will 

802 be passed to the calling application). 

803 """ 

804 pass 

805 

806 

807class AutoAddPolicy(MissingHostKeyPolicy): 

808 """ 

809 Policy for automatically adding the hostname and new host key to the 

810 local `.HostKeys` object, and saving it. This is used by `.SSHClient`. 

811 """ 

812 

813 def missing_host_key(self, client, hostname, key): 

814 client._host_keys.add(hostname, key.get_name(), key) 

815 if client._host_keys_filename is not None: 

816 client.save_host_keys(client._host_keys_filename) 

817 client._log( 

818 DEBUG, 

819 "Adding {} host key for {}: {}".format( 

820 key.get_name(), hostname, hexlify(key.get_fingerprint()) 

821 ), 

822 ) 

823 

824 

825class RejectPolicy(MissingHostKeyPolicy): 

826 """ 

827 Policy for automatically rejecting the unknown hostname & key. This is 

828 used by `.SSHClient`. 

829 """ 

830 

831 def missing_host_key(self, client, hostname, key): 

832 client._log( 

833 DEBUG, 

834 "Rejecting {} host key for {}: {}".format( 

835 key.get_name(), hostname, hexlify(key.get_fingerprint()) 

836 ), 

837 ) 

838 raise SSHException( 

839 "Server {!r} not found in known_hosts".format(hostname) 

840 ) 

841 

842 

843class WarningPolicy(MissingHostKeyPolicy): 

844 """ 

845 Policy for logging a Python-style warning for an unknown host key, but 

846 accepting it. This is used by `.SSHClient`. 

847 """ 

848 

849 def missing_host_key(self, client, hostname, key): 

850 warnings.warn( 

851 "Unknown {} host key for {}: {}".format( 

852 key.get_name(), hostname, hexlify(key.get_fingerprint()) 

853 ) 

854 )