1# Copyright (c) 2012, Michael DeHaan <michael.dehaan@gmail.com> 2# Copyright 2015 Abhijit Menon-Sen <ams@2ndQuadrant.com> 3# Copyright 2017 Toshio Kuratomi <tkuratomi@ansible.com> 4# Copyright (c) 2017 Ansible Project 5# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import (absolute_import, division, print_function) 8__metaclass__ = type 9 10DOCUMENTATION = ''' 11 connection: ssh 12 short_description: connect via ssh client binary 13 description: 14 - This connection plugin allows ansible to communicate to the target machines via normal ssh command line. 15 - Ansible does not expose a channel to allow communication between the user and the ssh process to accept 16 a password manually to decrypt an ssh key when using this connection plugin (which is the default). The 17 use of ``ssh-agent`` is highly recommended. 18 author: ansible (@core) 19 version_added: historical 20 options: 21 host: 22 description: Hostname/ip to connect to. 23 default: inventory_hostname 24 vars: 25 - name: ansible_host 26 - name: ansible_ssh_host 27 host_key_checking: 28 description: Determines if ssh should check host keys 29 type: boolean 30 ini: 31 - section: defaults 32 key: 'host_key_checking' 33 - section: ssh_connection 34 key: 'host_key_checking' 35 version_added: '2.5' 36 env: 37 - name: ANSIBLE_HOST_KEY_CHECKING 38 - name: ANSIBLE_SSH_HOST_KEY_CHECKING 39 version_added: '2.5' 40 vars: 41 - name: ansible_host_key_checking 42 version_added: '2.5' 43 - name: ansible_ssh_host_key_checking 44 version_added: '2.5' 45 password: 46 description: Authentication password for the C(remote_user). Can be supplied as CLI option. 47 vars: 48 - name: ansible_password 49 - name: ansible_ssh_pass 50 - name: ansible_ssh_password 51 ssh_args: 52 description: Arguments to pass to all ssh cli tools 53 default: '-C -o ControlMaster=auto -o ControlPersist=60s' 54 ini: 55 - section: 'ssh_connection' 56 key: 'ssh_args' 57 env: 58 - name: ANSIBLE_SSH_ARGS 59 vars: 60 - name: ansible_ssh_args 61 version_added: '2.7' 62 ssh_common_args: 63 description: Common extra args for all ssh CLI tools 64 ini: 65 - section: 'ssh_connection' 66 key: 'ssh_common_args' 67 version_added: '2.7' 68 env: 69 - name: ANSIBLE_SSH_COMMON_ARGS 70 version_added: '2.7' 71 vars: 72 - name: ansible_ssh_common_args 73 ssh_executable: 74 default: ssh 75 description: 76 - This defines the location of the ssh binary. It defaults to ``ssh`` which will use the first ssh binary available in $PATH. 77 - This option is usually not required, it might be useful when access to system ssh is restricted, 78 or when using ssh wrappers to connect to remote hosts. 79 env: [{name: ANSIBLE_SSH_EXECUTABLE}] 80 ini: 81 - {key: ssh_executable, section: ssh_connection} 82 #const: ANSIBLE_SSH_EXECUTABLE 83 version_added: "2.2" 84 vars: 85 - name: ansible_ssh_executable 86 version_added: '2.7' 87 sftp_executable: 88 default: sftp 89 description: 90 - This defines the location of the sftp binary. It defaults to ``sftp`` which will use the first binary available in $PATH. 91 env: [{name: ANSIBLE_SFTP_EXECUTABLE}] 92 ini: 93 - {key: sftp_executable, section: ssh_connection} 94 version_added: "2.6" 95 vars: 96 - name: ansible_sftp_executable 97 version_added: '2.7' 98 scp_executable: 99 default: scp 100 description: 101 - This defines the location of the scp binary. It defaults to `scp` which will use the first binary available in $PATH. 102 env: [{name: ANSIBLE_SCP_EXECUTABLE}] 103 ini: 104 - {key: scp_executable, section: ssh_connection} 105 version_added: "2.6" 106 vars: 107 - name: ansible_scp_executable 108 version_added: '2.7' 109 scp_extra_args: 110 description: Extra exclusive to the ``scp`` CLI 111 vars: 112 - name: ansible_scp_extra_args 113 env: 114 - name: ANSIBLE_SCP_EXTRA_ARGS 115 version_added: '2.7' 116 ini: 117 - key: scp_extra_args 118 section: ssh_connection 119 version_added: '2.7' 120 sftp_extra_args: 121 description: Extra exclusive to the ``sftp`` CLI 122 vars: 123 - name: ansible_sftp_extra_args 124 env: 125 - name: ANSIBLE_SFTP_EXTRA_ARGS 126 version_added: '2.7' 127 ini: 128 - key: sftp_extra_args 129 section: ssh_connection 130 version_added: '2.7' 131 ssh_extra_args: 132 description: Extra exclusive to the 'ssh' CLI 133 vars: 134 - name: ansible_ssh_extra_args 135 env: 136 - name: ANSIBLE_SSH_EXTRA_ARGS 137 version_added: '2.7' 138 ini: 139 - key: ssh_extra_args 140 section: ssh_connection 141 version_added: '2.7' 142 retries: 143 # constant: ANSIBLE_SSH_RETRIES 144 description: Number of attempts to connect. 145 default: 3 146 type: integer 147 env: 148 - name: ANSIBLE_SSH_RETRIES 149 ini: 150 - section: connection 151 key: retries 152 - section: ssh_connection 153 key: retries 154 vars: 155 - name: ansible_ssh_retries 156 version_added: '2.7' 157 port: 158 description: Remote port to connect to. 159 type: int 160 default: 22 161 ini: 162 - section: defaults 163 key: remote_port 164 env: 165 - name: ANSIBLE_REMOTE_PORT 166 vars: 167 - name: ansible_port 168 - name: ansible_ssh_port 169 remote_user: 170 description: 171 - User name with which to login to the remote server, normally set by the remote_user keyword. 172 - If no user is supplied, Ansible will let the ssh client binary choose the user as it normally 173 ini: 174 - section: defaults 175 key: remote_user 176 env: 177 - name: ANSIBLE_REMOTE_USER 178 vars: 179 - name: ansible_user 180 - name: ansible_ssh_user 181 pipelining: 182 default: ANSIBLE_PIPELINING 183 description: 184 - Pipelining reduces the number of SSH operations required to execute a module on the remote server, 185 by executing many Ansible modules without actual file transfer. 186 - This can result in a very significant performance improvement when enabled. 187 - However this conflicts with privilege escalation (become). 188 For example, when using sudo operations you must first disable 'requiretty' in the sudoers file for the target hosts, 189 which is why this feature is disabled by default. 190 env: 191 - name: ANSIBLE_PIPELINING 192 #- name: ANSIBLE_SSH_PIPELINING 193 ini: 194 - section: defaults 195 key: pipelining 196 #- section: ssh_connection 197 # key: pipelining 198 type: boolean 199 vars: 200 - name: ansible_pipelining 201 - name: ansible_ssh_pipelining 202 private_key_file: 203 description: 204 - Path to private key file to use for authentication 205 ini: 206 - section: defaults 207 key: private_key_file 208 env: 209 - name: ANSIBLE_PRIVATE_KEY_FILE 210 vars: 211 - name: ansible_private_key_file 212 - name: ansible_ssh_private_key_file 213 214 control_path: 215 description: 216 - This is the location to save ssh's ControlPath sockets, it uses ssh's variable substitution. 217 - Since 2.3, if null, ansible will generate a unique hash. Use `%(directory)s` to indicate where to use the control dir path setting. 218 env: 219 - name: ANSIBLE_SSH_CONTROL_PATH 220 ini: 221 - key: control_path 222 section: ssh_connection 223 vars: 224 - name: ansible_control_path 225 version_added: '2.7' 226 control_path_dir: 227 default: ~/.ansible/cp 228 description: 229 - This sets the directory to use for ssh control path if the control path setting is null. 230 - Also, provides the `%(directory)s` variable for the control path setting. 231 env: 232 - name: ANSIBLE_SSH_CONTROL_PATH_DIR 233 ini: 234 - section: ssh_connection 235 key: control_path_dir 236 vars: 237 - name: ansible_control_path_dir 238 version_added: '2.7' 239 sftp_batch_mode: 240 default: 'yes' 241 description: 'TODO: write it' 242 env: [{name: ANSIBLE_SFTP_BATCH_MODE}] 243 ini: 244 - {key: sftp_batch_mode, section: ssh_connection} 245 type: bool 246 vars: 247 - name: ansible_sftp_batch_mode 248 version_added: '2.7' 249 scp_if_ssh: 250 default: smart 251 description: 252 - "Prefered method to use when transfering files over ssh" 253 - When set to smart, Ansible will try them until one succeeds or they all fail 254 - If set to True, it will force 'scp', if False it will use 'sftp' 255 env: [{name: ANSIBLE_SCP_IF_SSH}] 256 ini: 257 - {key: scp_if_ssh, section: ssh_connection} 258 vars: 259 - name: ansible_scp_if_ssh 260 version_added: '2.7' 261 use_tty: 262 version_added: '2.5' 263 default: 'yes' 264 description: add -tt to ssh commands to force tty allocation 265 env: [{name: ANSIBLE_SSH_USETTY}] 266 ini: 267 - {key: usetty, section: ssh_connection} 268 type: bool 269 vars: 270 - name: ansible_ssh_use_tty 271 version_added: '2.7' 272''' 273 274import errno 275import fcntl 276import hashlib 277import os 278import pty 279import re 280import subprocess 281import time 282 283from functools import wraps 284from ansible import constants as C 285from ansible.errors import ( 286 AnsibleAuthenticationFailure, 287 AnsibleConnectionFailure, 288 AnsibleError, 289 AnsibleFileNotFound, 290) 291from ansible.errors import AnsibleOptionsError 292from ansible.module_utils.compat import selectors 293from ansible.module_utils.six import PY3, text_type, binary_type 294from ansible.module_utils.six.moves import shlex_quote 295from ansible.module_utils._text import to_bytes, to_native, to_text 296from ansible.module_utils.parsing.convert_bool import BOOLEANS, boolean 297from ansible.plugins.connection import ConnectionBase, BUFSIZE 298from ansible.plugins.shell.powershell import _parse_clixml 299from ansible.utils.display import Display 300from ansible.utils.path import unfrackpath, makedirs_safe 301 302display = Display() 303 304 305b_NOT_SSH_ERRORS = (b'Traceback (most recent call last):', # Python-2.6 when there's an exception 306 # while invoking a script via -m 307 b'PHP Parse error:', # Php always returns error 255 308 ) 309 310SSHPASS_AVAILABLE = None 311 312 313class AnsibleControlPersistBrokenPipeError(AnsibleError): 314 ''' ControlPersist broken pipe ''' 315 pass 316 317 318def _handle_error(remaining_retries, command, return_tuple, no_log, host, display=display): 319 320 # sshpass errors 321 if command == b'sshpass': 322 # Error 5 is invalid/incorrect password. Raise an exception to prevent retries from locking the account. 323 if return_tuple[0] == 5: 324 msg = 'Invalid/incorrect username/password. Skipping remaining {0} retries to prevent account lockout:'.format(remaining_retries) 325 if remaining_retries <= 0: 326 msg = 'Invalid/incorrect password:' 327 if no_log: 328 msg = '{0} <error censored due to no log>'.format(msg) 329 else: 330 msg = '{0} {1}'.format(msg, to_native(return_tuple[2]).rstrip()) 331 raise AnsibleAuthenticationFailure(msg) 332 333 # sshpass returns codes are 1-6. We handle 5 previously, so this catches other scenarios. 334 # No exception is raised, so the connection is retried. 335 elif return_tuple[0] in [1, 2, 3, 4, 6]: 336 msg = 'sshpass error:' 337 if no_log: 338 msg = '{0} <error censored due to no log>'.format(msg) 339 else: 340 msg = '{0} {1}'.format(msg, to_native(return_tuple[2]).rstrip()) 341 342 if return_tuple[0] == 255: 343 SSH_ERROR = True 344 for signature in b_NOT_SSH_ERRORS: 345 if signature in return_tuple[1]: 346 SSH_ERROR = False 347 break 348 349 if SSH_ERROR: 350 msg = "Failed to connect to the host via ssh:" 351 if no_log: 352 msg = '{0} <error censored due to no log>'.format(msg) 353 else: 354 msg = '{0} {1}'.format(msg, to_native(return_tuple[2]).rstrip()) 355 raise AnsibleConnectionFailure(msg) 356 357 # For other errors, no execption is raised so the connection is retried and we only log the messages 358 if 1 <= return_tuple[0] <= 254: 359 msg = u"Failed to connect to the host via ssh:" 360 if no_log: 361 msg = u'{0} <error censored due to no log>'.format(msg) 362 else: 363 msg = u'{0} {1}'.format(msg, to_text(return_tuple[2]).rstrip()) 364 display.vvv(msg, host=host) 365 366 367def _ssh_retry(func): 368 """ 369 Decorator to retry ssh/scp/sftp in the case of a connection failure 370 371 Will retry if: 372 * an exception is caught 373 * ssh returns 255 374 Will not retry if 375 * sshpass returns 5 (invalid password, to prevent account lockouts) 376 * remaining_tries is < 2 377 * retries limit reached 378 """ 379 @wraps(func) 380 def wrapped(self, *args, **kwargs): 381 remaining_tries = int(C.ANSIBLE_SSH_RETRIES) + 1 382 cmd_summary = u"%s..." % to_text(args[0]) 383 for attempt in range(remaining_tries): 384 cmd = args[0] 385 if attempt != 0 and self._play_context.password and isinstance(cmd, list): 386 # If this is a retry, the fd/pipe for sshpass is closed, and we need a new one 387 self.sshpass_pipe = os.pipe() 388 cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict') 389 390 try: 391 try: 392 return_tuple = func(self, *args, **kwargs) 393 if self._play_context.no_log: 394 display.vvv(u'rc=%s, stdout and stderr censored due to no log' % return_tuple[0], host=self.host) 395 else: 396 display.vvv(return_tuple, host=self.host) 397 # 0 = success 398 # 1-254 = remote command return code 399 # 255 could be a failure from the ssh command itself 400 except (AnsibleControlPersistBrokenPipeError): 401 # Retry one more time because of the ControlPersist broken pipe (see #16731) 402 cmd = args[0] 403 if self._play_context.password and isinstance(cmd, list): 404 # This is a retry, so the fd/pipe for sshpass is closed, and we need a new one 405 self.sshpass_pipe = os.pipe() 406 cmd[1] = b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict') 407 display.vvv(u"RETRYING BECAUSE OF CONTROLPERSIST BROKEN PIPE") 408 return_tuple = func(self, *args, **kwargs) 409 410 remaining_retries = remaining_tries - attempt - 1 411 _handle_error(remaining_retries, cmd[0], return_tuple, self._play_context.no_log, self.host) 412 413 break 414 415 # 5 = Invalid/incorrect password from sshpass 416 except AnsibleAuthenticationFailure: 417 # Raising this exception, which is subclassed from AnsibleConnectionFailure, prevents further retries 418 raise 419 420 except (AnsibleConnectionFailure, Exception) as e: 421 422 if attempt == remaining_tries - 1: 423 raise 424 else: 425 pause = 2 ** attempt - 1 426 if pause > 30: 427 pause = 30 428 429 if isinstance(e, AnsibleConnectionFailure): 430 msg = u"ssh_retry: attempt: %d, ssh return code is 255. cmd (%s), pausing for %d seconds" % (attempt + 1, cmd_summary, pause) 431 else: 432 msg = (u"ssh_retry: attempt: %d, caught exception(%s) from cmd (%s), " 433 u"pausing for %d seconds" % (attempt + 1, to_text(e), cmd_summary, pause)) 434 435 display.vv(msg, host=self.host) 436 437 time.sleep(pause) 438 continue 439 440 return return_tuple 441 return wrapped 442 443 444class Connection(ConnectionBase): 445 ''' ssh based connections ''' 446 447 transport = 'ssh' 448 has_pipelining = True 449 450 def __init__(self, *args, **kwargs): 451 super(Connection, self).__init__(*args, **kwargs) 452 453 self.host = self._play_context.remote_addr 454 self.port = self._play_context.port 455 self.user = self._play_context.remote_user 456 self.control_path = C.ANSIBLE_SSH_CONTROL_PATH 457 self.control_path_dir = C.ANSIBLE_SSH_CONTROL_PATH_DIR 458 459 # Windows operates differently from a POSIX connection/shell plugin, 460 # we need to set various properties to ensure SSH on Windows continues 461 # to work 462 if getattr(self._shell, "_IS_WINDOWS", False): 463 self.has_native_async = True 464 self.always_pipeline_modules = True 465 self.module_implementation_preferences = ('.ps1', '.exe', '') 466 self.allow_executable = False 467 468 # The connection is created by running ssh/scp/sftp from the exec_command, 469 # put_file, and fetch_file methods, so we don't need to do any connection 470 # management here. 471 472 def _connect(self): 473 return self 474 475 @staticmethod 476 def _create_control_path(host, port, user, connection=None, pid=None): 477 '''Make a hash for the controlpath based on con attributes''' 478 pstring = '%s-%s-%s' % (host, port, user) 479 if connection: 480 pstring += '-%s' % connection 481 if pid: 482 pstring += '-%s' % to_text(pid) 483 m = hashlib.sha1() 484 m.update(to_bytes(pstring)) 485 digest = m.hexdigest() 486 cpath = '%(directory)s/' + digest[:10] 487 return cpath 488 489 @staticmethod 490 def _sshpass_available(): 491 global SSHPASS_AVAILABLE 492 493 # We test once if sshpass is available, and remember the result. It 494 # would be nice to use distutils.spawn.find_executable for this, but 495 # distutils isn't always available; shutils.which() is Python3-only. 496 497 if SSHPASS_AVAILABLE is None: 498 try: 499 p = subprocess.Popen(["sshpass"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 500 p.communicate() 501 SSHPASS_AVAILABLE = True 502 except OSError: 503 SSHPASS_AVAILABLE = False 504 505 return SSHPASS_AVAILABLE 506 507 @staticmethod 508 def _persistence_controls(b_command): 509 ''' 510 Takes a command array and scans it for ControlPersist and ControlPath 511 settings and returns two booleans indicating whether either was found. 512 This could be smarter, e.g. returning false if ControlPersist is 'no', 513 but for now we do it simple way. 514 ''' 515 516 controlpersist = False 517 controlpath = False 518 519 for b_arg in (a.lower() for a in b_command): 520 if b'controlpersist' in b_arg: 521 controlpersist = True 522 elif b'controlpath' in b_arg: 523 controlpath = True 524 525 return controlpersist, controlpath 526 527 def _add_args(self, b_command, b_args, explanation): 528 """ 529 Adds arguments to the ssh command and displays a caller-supplied explanation of why. 530 531 :arg b_command: A list containing the command to add the new arguments to. 532 This list will be modified by this method. 533 :arg b_args: An iterable of new arguments to add. This iterable is used 534 more than once so it must be persistent (ie: a list is okay but a 535 StringIO would not) 536 :arg explanation: A text string containing explaining why the arguments 537 were added. It will be displayed with a high enough verbosity. 538 .. note:: This function does its work via side-effect. The b_command list has the new arguments appended. 539 """ 540 display.vvvvv(u'SSH: %s: (%s)' % (explanation, ')('.join(to_text(a) for a in b_args)), host=self._play_context.remote_addr) 541 b_command += b_args 542 543 def _build_command(self, binary, *other_args): 544 ''' 545 Takes a binary (ssh, scp, sftp) and optional extra arguments and returns 546 a command line as an array that can be passed to subprocess.Popen. 547 ''' 548 549 b_command = [] 550 551 # 552 # First, the command to invoke 553 # 554 555 # If we want to use password authentication, we have to set up a pipe to 556 # write the password to sshpass. 557 558 if self._play_context.password: 559 if not self._sshpass_available(): 560 raise AnsibleError("to use the 'ssh' connection type with passwords, you must install the sshpass program") 561 562 self.sshpass_pipe = os.pipe() 563 b_command += [b'sshpass', b'-d' + to_bytes(self.sshpass_pipe[0], nonstring='simplerepr', errors='surrogate_or_strict')] 564 565 if binary == 'ssh': 566 b_command += [to_bytes(self._play_context.ssh_executable, errors='surrogate_or_strict')] 567 else: 568 b_command += [to_bytes(binary, errors='surrogate_or_strict')] 569 570 # 571 # Next, additional arguments based on the configuration. 572 # 573 574 # sftp batch mode allows us to correctly catch failed transfers, but can 575 # be disabled if the client side doesn't support the option. However, 576 # sftp batch mode does not prompt for passwords so it must be disabled 577 # if not using controlpersist and using sshpass 578 if binary == 'sftp' and C.DEFAULT_SFTP_BATCH_MODE: 579 if self._play_context.password: 580 b_args = [b'-o', b'BatchMode=no'] 581 self._add_args(b_command, b_args, u'disable batch mode for sshpass') 582 b_command += [b'-b', b'-'] 583 584 if self._play_context.verbosity > 3: 585 b_command.append(b'-vvv') 586 587 # 588 # Next, we add [ssh_connection]ssh_args from ansible.cfg. 589 # 590 591 if self._play_context.ssh_args: 592 b_args = [to_bytes(a, errors='surrogate_or_strict') for a in 593 self._split_ssh_args(self._play_context.ssh_args)] 594 self._add_args(b_command, b_args, u"ansible.cfg set ssh_args") 595 596 # Now we add various arguments controlled by configuration file settings 597 # (e.g. host_key_checking) or inventory variables (ansible_ssh_port) or 598 # a combination thereof. 599 600 if not C.HOST_KEY_CHECKING: 601 b_args = (b"-o", b"StrictHostKeyChecking=no") 602 self._add_args(b_command, b_args, u"ANSIBLE_HOST_KEY_CHECKING/host_key_checking disabled") 603 604 if self._play_context.port is not None: 605 b_args = (b"-o", b"Port=" + to_bytes(self._play_context.port, nonstring='simplerepr', errors='surrogate_or_strict')) 606 self._add_args(b_command, b_args, u"ANSIBLE_REMOTE_PORT/remote_port/ansible_port set") 607 608 key = self._play_context.private_key_file 609 if key: 610 b_args = (b"-o", b'IdentityFile="' + to_bytes(os.path.expanduser(key), errors='surrogate_or_strict') + b'"') 611 self._add_args(b_command, b_args, u"ANSIBLE_PRIVATE_KEY_FILE/private_key_file/ansible_ssh_private_key_file set") 612 613 if not self._play_context.password: 614 self._add_args( 615 b_command, ( 616 b"-o", b"KbdInteractiveAuthentication=no", 617 b"-o", b"PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey", 618 b"-o", b"PasswordAuthentication=no" 619 ), 620 u"ansible_password/ansible_ssh_password not set" 621 ) 622 623 user = self._play_context.remote_user 624 if user: 625 self._add_args( 626 b_command, 627 (b"-o", b'User="%s"' % to_bytes(self._play_context.remote_user, errors='surrogate_or_strict')), 628 u"ANSIBLE_REMOTE_USER/remote_user/ansible_user/user/-u set" 629 ) 630 631 self._add_args( 632 b_command, 633 (b"-o", b"ConnectTimeout=" + to_bytes(self._play_context.timeout, errors='surrogate_or_strict', nonstring='simplerepr')), 634 u"ANSIBLE_TIMEOUT/timeout set" 635 ) 636 637 # Add in any common or binary-specific arguments from the PlayContext 638 # (i.e. inventory or task settings or overrides on the command line). 639 640 for opt in (u'ssh_common_args', u'{0}_extra_args'.format(binary)): 641 attr = getattr(self._play_context, opt, None) 642 if attr is not None: 643 b_args = [to_bytes(a, errors='surrogate_or_strict') for a in self._split_ssh_args(attr)] 644 self._add_args(b_command, b_args, u"PlayContext set %s" % opt) 645 646 # Check if ControlPersist is enabled and add a ControlPath if one hasn't 647 # already been set. 648 649 controlpersist, controlpath = self._persistence_controls(b_command) 650 651 if controlpersist: 652 self._persistent = True 653 654 if not controlpath: 655 cpdir = unfrackpath(self.control_path_dir) 656 b_cpdir = to_bytes(cpdir, errors='surrogate_or_strict') 657 658 # The directory must exist and be writable. 659 makedirs_safe(b_cpdir, 0o700) 660 if not os.access(b_cpdir, os.W_OK): 661 raise AnsibleError("Cannot write to ControlPath %s" % to_native(cpdir)) 662 663 if not self.control_path: 664 self.control_path = self._create_control_path( 665 self.host, 666 self.port, 667 self.user 668 ) 669 b_args = (b"-o", b"ControlPath=" + to_bytes(self.control_path % dict(directory=cpdir), errors='surrogate_or_strict')) 670 self._add_args(b_command, b_args, u"found only ControlPersist; added ControlPath") 671 672 # Finally, we add any caller-supplied extras. 673 if other_args: 674 b_command += [to_bytes(a) for a in other_args] 675 676 return b_command 677 678 def _send_initial_data(self, fh, in_data, ssh_process): 679 ''' 680 Writes initial data to the stdin filehandle of the subprocess and closes 681 it. (The handle must be closed; otherwise, for example, "sftp -b -" will 682 just hang forever waiting for more commands.) 683 ''' 684 685 display.debug(u'Sending initial data') 686 687 try: 688 fh.write(to_bytes(in_data)) 689 fh.close() 690 except (OSError, IOError) as e: 691 # The ssh connection may have already terminated at this point, with a more useful error 692 # Only raise AnsibleConnectionFailure if the ssh process is still alive 693 time.sleep(0.001) 694 ssh_process.poll() 695 if getattr(ssh_process, 'returncode', None) is None: 696 raise AnsibleConnectionFailure( 697 'Data could not be sent to remote host "%s". Make sure this host can be reached ' 698 'over ssh: %s' % (self.host, to_native(e)), orig_exc=e 699 ) 700 701 display.debug(u'Sent initial data (%d bytes)' % len(in_data)) 702 703 # Used by _run() to kill processes on failures 704 @staticmethod 705 def _terminate_process(p): 706 """ Terminate a process, ignoring errors """ 707 try: 708 p.terminate() 709 except (OSError, IOError): 710 pass 711 712 # This is separate from _run() because we need to do the same thing for stdout 713 # and stderr. 714 def _examine_output(self, source, state, b_chunk, sudoable): 715 ''' 716 Takes a string, extracts complete lines from it, tests to see if they 717 are a prompt, error message, etc., and sets appropriate flags in self. 718 Prompt and success lines are removed. 719 720 Returns the processed (i.e. possibly-edited) output and the unprocessed 721 remainder (to be processed with the next chunk) as strings. 722 ''' 723 724 output = [] 725 for b_line in b_chunk.splitlines(True): 726 display_line = to_text(b_line).rstrip('\r\n') 727 suppress_output = False 728 729 # display.debug("Examining line (source=%s, state=%s): '%s'" % (source, state, display_line)) 730 if self.become.expect_prompt() and self.become.check_password_prompt(b_line): 731 display.debug(u"become_prompt: (source=%s, state=%s): '%s'" % (source, state, display_line)) 732 self._flags['become_prompt'] = True 733 suppress_output = True 734 elif self.become.success and self.become.check_success(b_line): 735 display.debug(u"become_success: (source=%s, state=%s): '%s'" % (source, state, display_line)) 736 self._flags['become_success'] = True 737 suppress_output = True 738 elif sudoable and self.become.check_incorrect_password(b_line): 739 display.debug(u"become_error: (source=%s, state=%s): '%s'" % (source, state, display_line)) 740 self._flags['become_error'] = True 741 elif sudoable and self.become.check_missing_password(b_line): 742 display.debug(u"become_nopasswd_error: (source=%s, state=%s): '%s'" % (source, state, display_line)) 743 self._flags['become_nopasswd_error'] = True 744 745 if not suppress_output: 746 output.append(b_line) 747 748 # The chunk we read was most likely a series of complete lines, but just 749 # in case the last line was incomplete (and not a prompt, which we would 750 # have removed from the output), we retain it to be processed with the 751 # next chunk. 752 753 remainder = b'' 754 if output and not output[-1].endswith(b'\n'): 755 remainder = output[-1] 756 output = output[:-1] 757 758 return b''.join(output), remainder 759 760 def _bare_run(self, cmd, in_data, sudoable=True, checkrc=True): 761 ''' 762 Starts the command and communicates with it until it ends. 763 ''' 764 765 # We don't use _shell.quote as this is run on the controller and independent from the shell plugin chosen 766 display_cmd = u' '.join(shlex_quote(to_text(c)) for c in cmd) 767 display.vvv(u'SSH: EXEC {0}'.format(display_cmd), host=self.host) 768 769 # Start the given command. If we don't need to pipeline data, we can try 770 # to use a pseudo-tty (ssh will have been invoked with -tt). If we are 771 # pipelining data, or can't create a pty, we fall back to using plain 772 # old pipes. 773 774 p = None 775 776 if isinstance(cmd, (text_type, binary_type)): 777 cmd = to_bytes(cmd) 778 else: 779 cmd = list(map(to_bytes, cmd)) 780 781 if not in_data: 782 try: 783 # Make sure stdin is a proper pty to avoid tcgetattr errors 784 master, slave = pty.openpty() 785 if PY3 and self._play_context.password: 786 # pylint: disable=unexpected-keyword-arg 787 p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE, pass_fds=self.sshpass_pipe) 788 else: 789 p = subprocess.Popen(cmd, stdin=slave, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 790 stdin = os.fdopen(master, 'wb', 0) 791 os.close(slave) 792 except (OSError, IOError): 793 p = None 794 795 if not p: 796 if PY3 and self._play_context.password: 797 # pylint: disable=unexpected-keyword-arg 798 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, pass_fds=self.sshpass_pipe) 799 else: 800 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 801 stdin = p.stdin 802 803 # If we are using SSH password authentication, write the password into 804 # the pipe we opened in _build_command. 805 806 if self._play_context.password: 807 os.close(self.sshpass_pipe[0]) 808 try: 809 os.write(self.sshpass_pipe[1], to_bytes(self._play_context.password) + b'\n') 810 except OSError as e: 811 # Ignore broken pipe errors if the sshpass process has exited. 812 if e.errno != errno.EPIPE or p.poll() is None: 813 raise 814 os.close(self.sshpass_pipe[1]) 815 816 # 817 # SSH state machine 818 # 819 820 # Now we read and accumulate output from the running process until it 821 # exits. Depending on the circumstances, we may also need to write an 822 # escalation password and/or pipelined input to the process. 823 824 states = [ 825 'awaiting_prompt', 'awaiting_escalation', 'ready_to_send', 'awaiting_exit' 826 ] 827 828 # Are we requesting privilege escalation? Right now, we may be invoked 829 # to execute sftp/scp with sudoable=True, but we can request escalation 830 # only when using ssh. Otherwise we can send initial data straightaway. 831 832 state = states.index('ready_to_send') 833 if to_bytes(self.get_option('ssh_executable')) in cmd and sudoable: 834 prompt = getattr(self.become, 'prompt', None) 835 if prompt: 836 # We're requesting escalation with a password, so we have to 837 # wait for a password prompt. 838 state = states.index('awaiting_prompt') 839 display.debug(u'Initial state: %s: %s' % (states[state], to_text(prompt))) 840 elif self.become and self.become.success: 841 # We're requesting escalation without a password, so we have to 842 # detect success/failure before sending any initial data. 843 state = states.index('awaiting_escalation') 844 display.debug(u'Initial state: %s: %s' % (states[state], to_text(self.become.success))) 845 846 # We store accumulated stdout and stderr output from the process here, 847 # but strip any privilege escalation prompt/confirmation lines first. 848 # Output is accumulated into tmp_*, complete lines are extracted into 849 # an array, then checked and removed or copied to stdout or stderr. We 850 # set any flags based on examining the output in self._flags. 851 852 b_stdout = b_stderr = b'' 853 b_tmp_stdout = b_tmp_stderr = b'' 854 855 self._flags = dict( 856 become_prompt=False, become_success=False, 857 become_error=False, become_nopasswd_error=False 858 ) 859 860 # select timeout should be longer than the connect timeout, otherwise 861 # they will race each other when we can't connect, and the connect 862 # timeout usually fails 863 timeout = 2 + self._play_context.timeout 864 for fd in (p.stdout, p.stderr): 865 fcntl.fcntl(fd, fcntl.F_SETFL, fcntl.fcntl(fd, fcntl.F_GETFL) | os.O_NONBLOCK) 866 867 # TODO: bcoca would like to use SelectSelector() when open 868 # filehandles is low, then switch to more efficient ones when higher. 869 # select is faster when filehandles is low. 870 selector = selectors.DefaultSelector() 871 selector.register(p.stdout, selectors.EVENT_READ) 872 selector.register(p.stderr, selectors.EVENT_READ) 873 874 # If we can send initial data without waiting for anything, we do so 875 # before we start polling 876 if states[state] == 'ready_to_send' and in_data: 877 self._send_initial_data(stdin, in_data, p) 878 state += 1 879 880 try: 881 while True: 882 poll = p.poll() 883 events = selector.select(timeout) 884 885 # We pay attention to timeouts only while negotiating a prompt. 886 887 if not events: 888 # We timed out 889 if state <= states.index('awaiting_escalation'): 890 # If the process has already exited, then it's not really a 891 # timeout; we'll let the normal error handling deal with it. 892 if poll is not None: 893 break 894 self._terminate_process(p) 895 raise AnsibleError('Timeout (%ds) waiting for privilege escalation prompt: %s' % (timeout, to_native(b_stdout))) 896 897 # Read whatever output is available on stdout and stderr, and stop 898 # listening to the pipe if it's been closed. 899 900 for key, event in events: 901 if key.fileobj == p.stdout: 902 b_chunk = p.stdout.read() 903 if b_chunk == b'': 904 # stdout has been closed, stop watching it 905 selector.unregister(p.stdout) 906 # When ssh has ControlMaster (+ControlPath/Persist) enabled, the 907 # first connection goes into the background and we never see EOF 908 # on stderr. If we see EOF on stdout, lower the select timeout 909 # to reduce the time wasted selecting on stderr if we observe 910 # that the process has not yet existed after this EOF. Otherwise 911 # we may spend a long timeout period waiting for an EOF that is 912 # not going to arrive until the persisted connection closes. 913 timeout = 1 914 b_tmp_stdout += b_chunk 915 display.debug(u"stdout chunk (state=%s):\n>>>%s<<<\n" % (state, to_text(b_chunk))) 916 elif key.fileobj == p.stderr: 917 b_chunk = p.stderr.read() 918 if b_chunk == b'': 919 # stderr has been closed, stop watching it 920 selector.unregister(p.stderr) 921 b_tmp_stderr += b_chunk 922 display.debug("stderr chunk (state=%s):\n>>>%s<<<\n" % (state, to_text(b_chunk))) 923 924 # We examine the output line-by-line until we have negotiated any 925 # privilege escalation prompt and subsequent success/error message. 926 # Afterwards, we can accumulate output without looking at it. 927 928 if state < states.index('ready_to_send'): 929 if b_tmp_stdout: 930 b_output, b_unprocessed = self._examine_output('stdout', states[state], b_tmp_stdout, sudoable) 931 b_stdout += b_output 932 b_tmp_stdout = b_unprocessed 933 934 if b_tmp_stderr: 935 b_output, b_unprocessed = self._examine_output('stderr', states[state], b_tmp_stderr, sudoable) 936 b_stderr += b_output 937 b_tmp_stderr = b_unprocessed 938 else: 939 b_stdout += b_tmp_stdout 940 b_stderr += b_tmp_stderr 941 b_tmp_stdout = b_tmp_stderr = b'' 942 943 # If we see a privilege escalation prompt, we send the password. 944 # (If we're expecting a prompt but the escalation succeeds, we 945 # didn't need the password and can carry on regardless.) 946 947 if states[state] == 'awaiting_prompt': 948 if self._flags['become_prompt']: 949 display.debug(u'Sending become_password in response to prompt') 950 become_pass = self.become.get_option('become_pass', playcontext=self._play_context) 951 stdin.write(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n') 952 # On python3 stdin is a BufferedWriter, and we don't have a guarantee 953 # that the write will happen without a flush 954 stdin.flush() 955 self._flags['become_prompt'] = False 956 state += 1 957 elif self._flags['become_success']: 958 state += 1 959 960 # We've requested escalation (with or without a password), now we 961 # wait for an error message or a successful escalation. 962 963 if states[state] == 'awaiting_escalation': 964 if self._flags['become_success']: 965 display.vvv(u'Escalation succeeded') 966 self._flags['become_success'] = False 967 state += 1 968 elif self._flags['become_error']: 969 display.vvv(u'Escalation failed') 970 self._terminate_process(p) 971 self._flags['become_error'] = False 972 raise AnsibleError('Incorrect %s password' % self.become.name) 973 elif self._flags['become_nopasswd_error']: 974 display.vvv(u'Escalation requires password') 975 self._terminate_process(p) 976 self._flags['become_nopasswd_error'] = False 977 raise AnsibleError('Missing %s password' % self.become.name) 978 elif self._flags['become_prompt']: 979 # This shouldn't happen, because we should see the "Sorry, 980 # try again" message first. 981 display.vvv(u'Escalation prompt repeated') 982 self._terminate_process(p) 983 self._flags['become_prompt'] = False 984 raise AnsibleError('Incorrect %s password' % self.become.name) 985 986 # Once we're sure that the privilege escalation prompt, if any, has 987 # been dealt with, we can send any initial data and start waiting 988 # for output. 989 990 if states[state] == 'ready_to_send': 991 if in_data: 992 self._send_initial_data(stdin, in_data, p) 993 state += 1 994 995 # Now we're awaiting_exit: has the child process exited? If it has, 996 # and we've read all available output from it, we're done. 997 998 if poll is not None: 999 if not selector.get_map() or not events: 1000 break 1001 # We should not see further writes to the stdout/stderr file 1002 # descriptors after the process has closed, set the select 1003 # timeout to gather any last writes we may have missed. 1004 timeout = 0 1005 continue 1006 1007 # If the process has not yet exited, but we've already read EOF from 1008 # its stdout and stderr (and thus no longer watching any file 1009 # descriptors), we can just wait for it to exit. 1010 1011 elif not selector.get_map(): 1012 p.wait() 1013 break 1014 1015 # Otherwise there may still be outstanding data to read. 1016 finally: 1017 selector.close() 1018 # close stdin, stdout, and stderr after process is terminated and 1019 # stdout/stderr are read completely (see also issues #848, #64768). 1020 stdin.close() 1021 p.stdout.close() 1022 p.stderr.close() 1023 1024 if C.HOST_KEY_CHECKING: 1025 if cmd[0] == b"sshpass" and p.returncode == 6: 1026 raise AnsibleError('Using a SSH password instead of a key is not possible because Host Key checking is enabled and sshpass does not support ' 1027 'this. Please add this host\'s fingerprint to your known_hosts file to manage this host.') 1028 1029 controlpersisterror = b'Bad configuration option: ControlPersist' in b_stderr or b'unknown configuration option: ControlPersist' in b_stderr 1030 if p.returncode != 0 and controlpersisterror: 1031 raise AnsibleError('using -c ssh on certain older ssh versions may not support ControlPersist, set ANSIBLE_SSH_ARGS="" ' 1032 '(or ssh_args in [ssh_connection] section of the config file) before running again') 1033 1034 # If we find a broken pipe because of ControlPersist timeout expiring (see #16731), 1035 # we raise a special exception so that we can retry a connection. 1036 controlpersist_broken_pipe = b'mux_client_hello_exchange: write packet: Broken pipe' in b_stderr 1037 if p.returncode == 255: 1038 1039 additional = to_native(b_stderr) 1040 if controlpersist_broken_pipe: 1041 raise AnsibleControlPersistBrokenPipeError('Data could not be sent because of ControlPersist broken pipe: %s' % additional) 1042 1043 elif in_data and checkrc: 1044 raise AnsibleConnectionFailure('Data could not be sent to remote host "%s". Make sure this host can be reached over ssh: %s' 1045 % (self.host, additional)) 1046 1047 return (p.returncode, b_stdout, b_stderr) 1048 1049 @_ssh_retry 1050 def _run(self, cmd, in_data, sudoable=True, checkrc=True): 1051 """Wrapper around _bare_run that retries the connection 1052 """ 1053 return self._bare_run(cmd, in_data, sudoable=sudoable, checkrc=checkrc) 1054 1055 @_ssh_retry 1056 def _file_transport_command(self, in_path, out_path, sftp_action): 1057 # scp and sftp require square brackets for IPv6 addresses, but 1058 # accept them for hostnames and IPv4 addresses too. 1059 host = '[%s]' % self.host 1060 1061 smart_methods = ['sftp', 'scp', 'piped'] 1062 1063 # Windows does not support dd so we cannot use the piped method 1064 if getattr(self._shell, "_IS_WINDOWS", False): 1065 smart_methods.remove('piped') 1066 1067 # Transfer methods to try 1068 methods = [] 1069 1070 # Use the transfer_method option if set, otherwise use scp_if_ssh 1071 ssh_transfer_method = self._play_context.ssh_transfer_method 1072 if ssh_transfer_method is not None: 1073 if not (ssh_transfer_method in ('smart', 'sftp', 'scp', 'piped')): 1074 raise AnsibleOptionsError('transfer_method needs to be one of [smart|sftp|scp|piped]') 1075 if ssh_transfer_method == 'smart': 1076 methods = smart_methods 1077 else: 1078 methods = [ssh_transfer_method] 1079 else: 1080 # since this can be a non-bool now, we need to handle it correctly 1081 scp_if_ssh = C.DEFAULT_SCP_IF_SSH 1082 if not isinstance(scp_if_ssh, bool): 1083 scp_if_ssh = scp_if_ssh.lower() 1084 if scp_if_ssh in BOOLEANS: 1085 scp_if_ssh = boolean(scp_if_ssh, strict=False) 1086 elif scp_if_ssh != 'smart': 1087 raise AnsibleOptionsError('scp_if_ssh needs to be one of [smart|True|False]') 1088 if scp_if_ssh == 'smart': 1089 methods = smart_methods 1090 elif scp_if_ssh is True: 1091 methods = ['scp'] 1092 else: 1093 methods = ['sftp'] 1094 1095 for method in methods: 1096 returncode = stdout = stderr = None 1097 if method == 'sftp': 1098 cmd = self._build_command(self.get_option('sftp_executable'), to_bytes(host)) 1099 in_data = u"{0} {1} {2}\n".format(sftp_action, shlex_quote(in_path), shlex_quote(out_path)) 1100 in_data = to_bytes(in_data, nonstring='passthru') 1101 (returncode, stdout, stderr) = self._bare_run(cmd, in_data, checkrc=False) 1102 elif method == 'scp': 1103 scp = self.get_option('scp_executable') 1104 1105 if sftp_action == 'get': 1106 cmd = self._build_command(scp, u'{0}:{1}'.format(host, self._shell.quote(in_path)), out_path) 1107 else: 1108 cmd = self._build_command(scp, in_path, u'{0}:{1}'.format(host, self._shell.quote(out_path))) 1109 in_data = None 1110 (returncode, stdout, stderr) = self._bare_run(cmd, in_data, checkrc=False) 1111 elif method == 'piped': 1112 if sftp_action == 'get': 1113 # we pass sudoable=False to disable pty allocation, which 1114 # would end up mixing stdout/stderr and screwing with newlines 1115 (returncode, stdout, stderr) = self.exec_command('dd if=%s bs=%s' % (in_path, BUFSIZE), sudoable=False) 1116 with open(to_bytes(out_path, errors='surrogate_or_strict'), 'wb+') as out_file: 1117 out_file.write(stdout) 1118 else: 1119 with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as f: 1120 in_data = to_bytes(f.read(), nonstring='passthru') 1121 if not in_data: 1122 count = ' count=0' 1123 else: 1124 count = '' 1125 (returncode, stdout, stderr) = self.exec_command('dd of=%s bs=%s%s' % (out_path, BUFSIZE, count), in_data=in_data, sudoable=False) 1126 1127 # Check the return code and rollover to next method if failed 1128 if returncode == 0: 1129 return (returncode, stdout, stderr) 1130 else: 1131 # If not in smart mode, the data will be printed by the raise below 1132 if len(methods) > 1: 1133 display.warning(u'%s transfer mechanism failed on %s. Use ANSIBLE_DEBUG=1 to see detailed information' % (method, host)) 1134 display.debug(u'%s' % to_text(stdout)) 1135 display.debug(u'%s' % to_text(stderr)) 1136 1137 if returncode == 255: 1138 raise AnsibleConnectionFailure("Failed to connect to the host via %s: %s" % (method, to_native(stderr))) 1139 else: 1140 raise AnsibleError("failed to transfer file to %s %s:\n%s\n%s" % 1141 (to_native(in_path), to_native(out_path), to_native(stdout), to_native(stderr))) 1142 1143 def _escape_win_path(self, path): 1144 """ converts a Windows path to one that's supported by SFTP and SCP """ 1145 # If using a root path then we need to start with / 1146 prefix = "" 1147 if re.match(r'^\w{1}:', path): 1148 prefix = "/" 1149 1150 # Convert all '\' to '/' 1151 return "%s%s" % (prefix, path.replace("\\", "/")) 1152 1153 # 1154 # Main public methods 1155 # 1156 def exec_command(self, cmd, in_data=None, sudoable=True): 1157 ''' run a command on the remote host ''' 1158 1159 super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable) 1160 1161 display.vvv(u"ESTABLISH SSH CONNECTION FOR USER: {0}".format(self._play_context.remote_user), host=self._play_context.remote_addr) 1162 1163 if getattr(self._shell, "_IS_WINDOWS", False): 1164 # Become method 'runas' is done in the wrapper that is executed, 1165 # need to disable sudoable so the bare_run is not waiting for a 1166 # prompt that will not occur 1167 sudoable = False 1168 1169 # Make sure our first command is to set the console encoding to 1170 # utf-8, this must be done via chcp to get utf-8 (65001) 1171 cmd_parts = ["chcp.com", "65001", self._shell._SHELL_REDIRECT_ALLNULL, self._shell._SHELL_AND] 1172 cmd_parts.extend(self._shell._encode_script(cmd, as_list=True, strict_mode=False, preserve_rc=False)) 1173 cmd = ' '.join(cmd_parts) 1174 1175 # we can only use tty when we are not pipelining the modules. piping 1176 # data into /usr/bin/python inside a tty automatically invokes the 1177 # python interactive-mode but the modules are not compatible with the 1178 # interactive-mode ("unexpected indent" mainly because of empty lines) 1179 1180 ssh_executable = self._play_context.ssh_executable 1181 1182 # -tt can cause various issues in some environments so allow the user 1183 # to disable it as a troubleshooting method. 1184 use_tty = self.get_option('use_tty') 1185 1186 if not in_data and sudoable and use_tty: 1187 args = (ssh_executable, '-tt', self.host, cmd) 1188 else: 1189 args = (ssh_executable, self.host, cmd) 1190 1191 cmd = self._build_command(*args) 1192 (returncode, stdout, stderr) = self._run(cmd, in_data, sudoable=sudoable) 1193 1194 # When running on Windows, stderr may contain CLIXML encoded output 1195 if getattr(self._shell, "_IS_WINDOWS", False) and stderr.startswith(b"#< CLIXML"): 1196 stderr = _parse_clixml(stderr) 1197 1198 return (returncode, stdout, stderr) 1199 1200 def put_file(self, in_path, out_path): 1201 ''' transfer a file from local to remote ''' 1202 1203 super(Connection, self).put_file(in_path, out_path) 1204 1205 display.vvv(u"PUT {0} TO {1}".format(in_path, out_path), host=self.host) 1206 if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')): 1207 raise AnsibleFileNotFound("file or module does not exist: {0}".format(to_native(in_path))) 1208 1209 if getattr(self._shell, "_IS_WINDOWS", False): 1210 out_path = self._escape_win_path(out_path) 1211 1212 return self._file_transport_command(in_path, out_path, 'put') 1213 1214 def fetch_file(self, in_path, out_path): 1215 ''' fetch a file from remote to local ''' 1216 1217 super(Connection, self).fetch_file(in_path, out_path) 1218 1219 display.vvv(u"FETCH {0} TO {1}".format(in_path, out_path), host=self.host) 1220 1221 # need to add / if path is rooted 1222 if getattr(self._shell, "_IS_WINDOWS", False): 1223 in_path = self._escape_win_path(in_path) 1224 1225 return self._file_transport_command(in_path, out_path, 'get') 1226 1227 def reset(self): 1228 # If we have a persistent ssh connection (ControlPersist), we can ask it to stop listening. 1229 cmd = self._build_command(self._play_context.ssh_executable, '-O', 'stop', self.host) 1230 controlpersist, controlpath = self._persistence_controls(cmd) 1231 cp_arg = [a for a in cmd if a.startswith(b"ControlPath=")] 1232 1233 # only run the reset if the ControlPath already exists or if it isn't 1234 # configured and ControlPersist is set 1235 run_reset = False 1236 if controlpersist and len(cp_arg) > 0: 1237 cp_path = cp_arg[0].split(b"=", 1)[-1] 1238 if os.path.exists(cp_path): 1239 run_reset = True 1240 elif controlpersist: 1241 run_reset = True 1242 1243 if run_reset: 1244 display.vvv(u'sending stop: %s' % to_text(cmd)) 1245 p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 1246 stdout, stderr = p.communicate() 1247 status_code = p.wait() 1248 if status_code != 0: 1249 display.warning(u"Failed to reset connection:%s" % to_text(stderr)) 1250 1251 self.close() 1252 1253 def close(self): 1254 self._connected = False 1255