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

280 statements  

« prev     ^ index     » next       coverage.py v7.2.2, created at 2023-03-26 06:36 +0000

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 

23from binascii import hexlify 

24import getpass 

25import inspect 

26import os 

27import socket 

28import warnings 

29from errno import ECONNREFUSED, EHOSTUNREACH 

30 

31from paramiko.agent import Agent 

32from paramiko.common import DEBUG 

33from paramiko.config import SSH_PORT 

34from paramiko.dsskey import DSSKey 

35from paramiko.ecdsakey import ECDSAKey 

36from paramiko.ed25519key import Ed25519Key 

37from paramiko.hostkeys import HostKeys 

38from paramiko.rsakey import RSAKey 

39from paramiko.ssh_exception import ( 

40 SSHException, 

41 BadHostKeyException, 

42 NoValidConnectionsError, 

43) 

44from paramiko.transport import Transport 

45from paramiko.util import ClosingContextManager 

46 

47 

48class SSHClient(ClosingContextManager): 

49 """ 

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

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

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

53 

54 client = SSHClient() 

55 client.load_system_host_keys() 

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

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

58 

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

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

61 SSH agent (if one is running). 

62 

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

64 

65 .. versionadded:: 1.6 

66 """ 

67 

68 def __init__(self): 

69 """ 

70 Create a new SSHClient. 

71 """ 

72 self._system_host_keys = HostKeys() 

73 self._host_keys = HostKeys() 

74 self._host_keys_filename = None 

75 self._log_channel = None 

76 self._policy = RejectPolicy() 

77 self._transport = None 

78 self._agent = None 

79 

80 def load_system_host_keys(self, filename=None): 

81 """ 

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

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

84 

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

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

87 conflicts). 

88 

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

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

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

92 probably only useful on posix. 

93 

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

95 

96 :raises: ``IOError`` -- 

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

98 """ 

99 if filename is None: 

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

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

102 try: 

103 self._system_host_keys.load(filename) 

104 except IOError: 

105 pass 

106 return 

107 self._system_host_keys.load(filename) 

108 

109 def load_host_keys(self, filename): 

110 """ 

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

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

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

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

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

116 

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

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

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

120 

121 :param str filename: the filename to read 

122 

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

124 """ 

125 self._host_keys_filename = filename 

126 self._host_keys.load(filename) 

127 

128 def save_host_keys(self, filename): 

129 """ 

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

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

132 host keys loaded with `load_system_host_keys`. 

133 

134 :param str filename: the filename to save to 

135 

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

137 """ 

138 

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

140 # have written to the known_hosts file meanwhile. 

141 if self._host_keys_filename is not None: 

142 self.load_host_keys(self._host_keys_filename) 

143 

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

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

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

147 f.write( 

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

149 hostname, keytype, key.get_base64() 

150 ) 

151 ) 

152 

153 def get_host_keys(self): 

154 """ 

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

156 local host keys or change them. 

157 

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

159 """ 

160 return self._host_keys 

161 

162 def set_log_channel(self, name): 

163 """ 

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

165 but it can be set to anything you want. 

166 

167 :param str name: new channel name for logging 

168 """ 

169 self._log_channel = name 

170 

171 def set_missing_host_key_policy(self, policy): 

172 """ 

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

174 

175 Specifically: 

176 

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

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

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

180 subclass. 

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

182 host keys structures (those manipulated by `load_system_host_keys` 

183 and/or `load_host_keys`). 

184 

185 :param .MissingHostKeyPolicy policy: 

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

187 previously-unknown server 

188 """ 

189 if inspect.isclass(policy): 

190 policy = policy() 

191 self._policy = policy 

192 

193 def _families_and_addresses(self, hostname, port): 

194 """ 

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

196 

197 :param str hostname: the server to connect to 

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

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

200 """ 

201 guess = True 

202 addrinfos = socket.getaddrinfo( 

203 hostname, port, socket.AF_UNSPEC, socket.SOCK_STREAM 

204 ) 

205 for (family, socktype, proto, canonname, sockaddr) in addrinfos: 

206 if socktype == socket.SOCK_STREAM: 

207 yield family, sockaddr 

208 guess = False 

209 

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

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

212 # as socktype == SOCK_STREAM. 

213 if guess: 

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

215 yield family, sockaddr 

216 

217 def connect( 

218 self, 

219 hostname, 

220 port=SSH_PORT, 

221 username=None, 

222 password=None, 

223 pkey=None, 

224 key_filename=None, 

225 timeout=None, 

226 allow_agent=True, 

227 look_for_keys=True, 

228 compress=False, 

229 sock=None, 

230 gss_auth=False, 

231 gss_kex=False, 

232 gss_deleg_creds=True, 

233 gss_host=None, 

234 banner_timeout=None, 

235 auth_timeout=None, 

236 channel_timeout=None, 

237 gss_trust_dns=True, 

238 passphrase=None, 

239 disabled_algorithms=None, 

240 transport_factory=None, 

241 ): 

242 """ 

243 Connect to an SSH server and authenticate to it. The server's host key 

244 is checked against the system host keys (see `load_system_host_keys`) 

245 and any local host keys (`load_host_keys`). If the server's hostname 

246 is not found in either set of host keys, the missing host key policy 

247 is used (see `set_missing_host_key_policy`). The default policy is 

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

249 

250 Authentication is attempted in the following order of priority: 

251 

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

253 

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

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

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

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

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

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

260 

261 - Any key we can find through an SSH agent 

262 - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in 

263 ``~/.ssh/`` 

264 

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

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

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

268 the private key and used for authentication. 

269 

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

271 

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

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

274 

275 :param str hostname: the server to connect to 

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

277 :param str username: 

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

279 username) 

280 :param str password: 

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

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

283 :param str passphrase: 

284 Used for decrypting private keys. 

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

286 :param str key_filename: 

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

288 and/or certs to try for authentication 

289 :param float timeout: 

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

291 :param bool allow_agent: 

292 set to False to disable connecting to the SSH agent 

293 :param bool look_for_keys: 

294 set to False to disable searching for discoverable private key 

295 files in ``~/.ssh/`` 

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

297 :param socket sock: 

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

299 for communication to the target host 

300 :param bool gss_auth: 

301 ``True`` if you want to use GSS-API authentication 

302 :param bool gss_kex: 

303 Perform GSS-API Key Exchange and user authentication 

304 :param bool gss_deleg_creds: Delegate GSS-API client credentials or not 

305 :param str gss_host: 

306 The targets name in the kerberos database. default: hostname 

307 :param bool gss_trust_dns: 

308 Indicates whether or not the DNS is trusted to securely 

309 canonicalize the name of the host being connected to (default 

310 ``True``). 

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

312 for the SSH banner to be presented. 

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

314 an authentication response. 

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

316 for a channel open response. 

317 :param dict disabled_algorithms: 

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

319 argument of the same name. 

320 :param transport_factory: 

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

322 arguments (primarily those related to the socket, GSS 

323 functionality, and algorithm selection) and generates a 

324 `.Transport` instance to be used by this client. Defaults to 

325 `.Transport.__init__`. 

326 

327 :raises BadHostKeyException: 

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

329 :raises AuthenticationException: if authentication failed. 

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``, ``gss_auth``, ``gss_kex``, 

343 ``gss_deleg_creds`` and ``gss_host`` arguments. 

344 .. versionchanged:: 2.3 

345 Added the ``gss_trust_dns`` argument. 

346 .. versionchanged:: 2.4 

347 Added the ``passphrase`` argument. 

348 .. versionchanged:: 2.6 

349 Added the ``disabled_algorithms`` argument. 

350 .. versionchanged:: 2.12 

351 Added the ``transport_factory`` argument. 

352 """ 

353 if not sock: 

354 errors = {} 

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

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

357 for af, addr in to_try: 

358 try: 

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

360 if timeout is not None: 

361 try: 

362 sock.settimeout(timeout) 

363 except: 

364 pass 

365 sock.connect(addr) 

366 # Break out of the loop on success 

367 break 

368 except socket.error as e: 

369 # As mentioned in socket docs it is better 

370 # to close sockets explicitly 

371 if sock: 

372 sock.close() 

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

374 # (such as a resolution error) 

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

376 raise 

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

378 # iteration is complete. Retain info about which attempt 

379 # this was. 

380 errors[addr] = e 

381 

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

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

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

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

386 # (socket.error) 

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

388 raise NoValidConnectionsError(errors) 

389 

390 if transport_factory is None: 

391 transport_factory = Transport 

392 t = self._transport = transport_factory( 

393 sock, 

394 gss_kex=gss_kex, 

395 gss_deleg_creds=gss_deleg_creds, 

396 disabled_algorithms=disabled_algorithms, 

397 ) 

398 t.use_compression(compress=compress) 

399 t.set_gss_host( 

400 # t.hostname may be None, but GSS-API requires a target name. 

401 # Therefore use hostname as fallback. 

402 gss_host=gss_host or hostname, 

403 trust_dns=gss_trust_dns, 

404 gssapi_requested=gss_auth or gss_kex, 

405 ) 

406 if self._log_channel is not None: 

407 t.set_log_channel(self._log_channel) 

408 if banner_timeout is not None: 

409 t.banner_timeout = banner_timeout 

410 if auth_timeout is not None: 

411 t.auth_timeout = auth_timeout 

412 if channel_timeout is not None: 

413 t.channel_timeout = channel_timeout 

414 

415 if port == SSH_PORT: 

416 server_hostkey_name = hostname 

417 else: 

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

419 our_server_keys = None 

420 

421 our_server_keys = self._system_host_keys.get(server_hostkey_name) 

422 if our_server_keys is None: 

423 our_server_keys = self._host_keys.get(server_hostkey_name) 

424 if our_server_keys is not None: 

425 keytype = our_server_keys.keys()[0] 

426 sec_opts = t.get_security_options() 

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

428 sec_opts.key_types = [keytype] + other_types 

429 

430 t.start_client(timeout=timeout) 

431 

432 # If GSS-API Key Exchange is performed we are not required to check the 

433 # host key, because the host is authenticated via GSS-API / SSPI as 

434 # well as our client. 

435 if not self._transport.gss_kex_used: 

436 server_key = t.get_remote_server_key() 

437 if our_server_keys is None: 

438 # will raise exception if the key is rejected 

439 self._policy.missing_host_key( 

440 self, server_hostkey_name, server_key 

441 ) 

442 else: 

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

444 if our_key != server_key: 

445 if our_key is None: 

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

447 raise BadHostKeyException(hostname, server_key, our_key) 

448 

449 if username is None: 

450 username = getpass.getuser() 

451 

452 if key_filename is None: 

453 key_filenames = [] 

454 elif isinstance(key_filename, str): 

455 key_filenames = [key_filename] 

456 else: 

457 key_filenames = key_filename 

458 

459 self._auth( 

460 username, 

461 password, 

462 pkey, 

463 key_filenames, 

464 allow_agent, 

465 look_for_keys, 

466 gss_auth, 

467 gss_kex, 

468 gss_deleg_creds, 

469 t.gss_host, 

470 passphrase, 

471 ) 

472 

473 def close(self): 

474 """ 

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

476 

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

478 

479 .. warning:: 

480 Paramiko registers garbage collection hooks that will try to 

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

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

483 lead to end-of-process hangs! 

484 """ 

485 if self._transport is None: 

486 return 

487 self._transport.close() 

488 self._transport = None 

489 

490 if self._agent is not None: 

491 self._agent.close() 

492 self._agent = None 

493 

494 def exec_command( 

495 self, 

496 command, 

497 bufsize=-1, 

498 timeout=None, 

499 get_pty=False, 

500 environment=None, 

501 ): 

502 """ 

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

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

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

506 stdin, stdout, and stderr. 

507 

508 :param str command: the command to execute 

509 :param int bufsize: 

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

511 Python 

512 :param int timeout: 

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

514 :param bool get_pty: 

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

516 See `.Channel.get_pty` 

517 :param dict environment: 

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

519 default environment that the remote command executes within. 

520 

521 .. warning:: 

522 Servers may silently reject some environment variables; see the 

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

524 

525 :return: 

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

527 3-tuple 

528 

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

530 

531 .. versionchanged:: 1.10 

532 Added the ``get_pty`` kwarg. 

533 """ 

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

535 if get_pty: 

536 chan.get_pty() 

537 chan.settimeout(timeout) 

538 if environment: 

539 chan.update_environment(environment) 

540 chan.exec_command(command) 

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

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

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

544 return stdin, stdout, stderr 

545 

546 def invoke_shell( 

547 self, 

548 term="vt100", 

549 width=80, 

550 height=24, 

551 width_pixels=0, 

552 height_pixels=0, 

553 environment=None, 

554 ): 

555 """ 

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

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

558 terminal type and size. 

559 

560 :param str term: 

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

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

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

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

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

566 :param dict environment: the command's environment 

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

568 

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

570 """ 

571 chan = self._transport.open_session() 

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

573 chan.invoke_shell() 

574 return chan 

575 

576 def open_sftp(self): 

577 """ 

578 Open an SFTP session on the SSH server. 

579 

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

581 """ 

582 return self._transport.open_sftp_client() 

583 

584 def get_transport(self): 

585 """ 

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

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

588 kinds of channels. 

589 

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

591 """ 

592 return self._transport 

593 

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

595 """ 

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

597 

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

599 loaded. 

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

601 matching public cert will be loaded if it exists. 

602 """ 

603 cert_suffix = "-cert.pub" 

604 # Assume privkey, not cert, by default 

605 if filename.endswith(cert_suffix): 

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

607 cert_path = filename 

608 else: 

609 key_path = filename 

610 cert_path = filename + cert_suffix 

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

612 key = klass.from_private_key_file(key_path, password) 

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

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

615 # likely testing/filtering for (bah.) 

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

617 hexlify(key.get_fingerprint()), key_path 

618 ) 

619 self._log(DEBUG, msg) 

620 # Attempt to load cert if it exists. 

621 if os.path.isfile(cert_path): 

622 key.load_certificate(cert_path) 

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

624 return key 

625 

626 def _auth( 

627 self, 

628 username, 

629 password, 

630 pkey, 

631 key_filenames, 

632 allow_agent, 

633 look_for_keys, 

634 gss_auth, 

635 gss_kex, 

636 gss_deleg_creds, 

637 gss_host, 

638 passphrase, 

639 ): 

640 """ 

641 Try, in order: 

642 

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

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

645 - Any "id_rsa", "id_dsa" or "id_ecdsa" key discoverable in ~/.ssh/ 

646 (if allowed). 

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

648 

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

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

651 required].) 

652 """ 

653 saved_exception = None 

654 two_factor = False 

655 allowed_types = set() 

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

657 if passphrase is None and password is not None: 

658 passphrase = password 

659 

660 # If GSS-API support and GSS-PI Key Exchange was performed, we attempt 

661 # authentication with gssapi-keyex. 

662 if gss_kex and self._transport.gss_kex_used: 

663 try: 

664 self._transport.auth_gssapi_keyex(username) 

665 return 

666 except Exception as e: 

667 saved_exception = e 

668 

669 # Try GSS-API authentication (gssapi-with-mic) only if GSS-API Key 

670 # Exchange is not performed, because if we use GSS-API for the key 

671 # exchange, there is already a fully established GSS-API context, so 

672 # why should we do that again? 

673 if gss_auth: 

674 try: 

675 return self._transport.auth_gssapi_with_mic( 

676 username, gss_host, gss_deleg_creds 

677 ) 

678 except Exception as e: 

679 saved_exception = e 

680 

681 if pkey is not None: 

682 try: 

683 self._log( 

684 DEBUG, 

685 "Trying SSH key {}".format( 

686 hexlify(pkey.get_fingerprint()) 

687 ), 

688 ) 

689 allowed_types = set( 

690 self._transport.auth_publickey(username, pkey) 

691 ) 

692 two_factor = allowed_types & two_factor_types 

693 if not two_factor: 

694 return 

695 except SSHException as e: 

696 saved_exception = e 

697 

698 if not two_factor: 

699 for key_filename in key_filenames: 

700 for pkey_class in (RSAKey, DSSKey, ECDSAKey, Ed25519Key): 

701 try: 

702 key = self._key_from_filepath( 

703 key_filename, pkey_class, passphrase 

704 ) 

705 allowed_types = set( 

706 self._transport.auth_publickey(username, key) 

707 ) 

708 two_factor = allowed_types & two_factor_types 

709 if not two_factor: 

710 return 

711 break 

712 except SSHException as e: 

713 saved_exception = e 

714 

715 if not two_factor and allow_agent: 

716 if self._agent is None: 

717 self._agent = Agent() 

718 

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

720 try: 

721 id_ = hexlify(key.get_fingerprint()) 

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

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

724 # will return an allowed 2fac auth method 

725 allowed_types = set( 

726 self._transport.auth_publickey(username, key) 

727 ) 

728 two_factor = allowed_types & two_factor_types 

729 if not two_factor: 

730 return 

731 break 

732 except SSHException as e: 

733 saved_exception = e 

734 

735 if not two_factor: 

736 keyfiles = [] 

737 

738 for keytype, name in [ 

739 (RSAKey, "rsa"), 

740 (DSSKey, "dsa"), 

741 (ECDSAKey, "ecdsa"), 

742 (Ed25519Key, "ed25519"), 

743 ]: 

744 # ~/ssh/ is for windows 

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

746 full_path = os.path.expanduser( 

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

748 ) 

749 if os.path.isfile(full_path): 

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

751 keyfiles.append((keytype, full_path)) 

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

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

754 

755 if not look_for_keys: 

756 keyfiles = [] 

757 

758 for pkey_class, filename in keyfiles: 

759 try: 

760 key = self._key_from_filepath( 

761 filename, pkey_class, passphrase 

762 ) 

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

764 # in ['password'] 

765 allowed_types = set( 

766 self._transport.auth_publickey(username, key) 

767 ) 

768 two_factor = allowed_types & two_factor_types 

769 if not two_factor: 

770 return 

771 break 

772 except (SSHException, IOError) as e: 

773 saved_exception = e 

774 

775 if password is not None: 

776 try: 

777 self._transport.auth_password(username, password) 

778 return 

779 except SSHException as e: 

780 saved_exception = e 

781 elif two_factor: 

782 try: 

783 self._transport.auth_interactive_dumb(username) 

784 return 

785 except SSHException as e: 

786 saved_exception = e 

787 

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

789 if saved_exception is not None: 

790 raise saved_exception 

791 raise SSHException("No authentication methods available") 

792 

793 def _log(self, level, msg): 

794 self._transport._log(level, msg) 

795 

796 

797class MissingHostKeyPolicy: 

798 """ 

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

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

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

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

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

804 

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

806 """ 

807 

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

809 """ 

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

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

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

813 be passed to the calling application). 

814 """ 

815 pass 

816 

817 

818class AutoAddPolicy(MissingHostKeyPolicy): 

819 """ 

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

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

822 """ 

823 

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

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

826 if client._host_keys_filename is not None: 

827 client.save_host_keys(client._host_keys_filename) 

828 client._log( 

829 DEBUG, 

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

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

832 ), 

833 ) 

834 

835 

836class RejectPolicy(MissingHostKeyPolicy): 

837 """ 

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

839 used by `.SSHClient`. 

840 """ 

841 

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

843 client._log( 

844 DEBUG, 

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

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

847 ), 

848 ) 

849 raise SSHException( 

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

851 ) 

852 

853 

854class WarningPolicy(MissingHostKeyPolicy): 

855 """ 

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

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

858 """ 

859 

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

861 warnings.warn( 

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

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

864 ) 

865 )