Repository: ansible/ansible

Issue:
become method dzdo fails
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

become_method: dzdo
##### ANSIBLE VERSION

ansible 2.2.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
##### CONFIGURATION
##### OS / ENVIRONMENT

Red Hat Enterprise Linux Server release 6.8 (Santiago)
Red Hat Enterprise Linux Server release 7.2 (Maipo)
##### SUMMARY

when using DZDO as become method the playbook timesout

```
fatal: [localhost]: FAILED! => {"failed": true, "msg": "Timeout (32s) waiting for privilege escalation prompt: "}
```
##### STEPS TO REPRODUCE

set become: true and become_method to dzdo (using Centrify)
##### EXPECTED RESULTS

Playbook use dzdo to escalate and run playbook
##### ACTUAL RESULTS

```
[rhargr1@hostname playbooks]$ ansible-playbook test.yml -K -k -vvvv
Using /etc/ansible/ansible.cfg as config file
SSH password: 
DZDO password[defaults to SSH password]: 
Loading callback plugin default of type stdout, v2.0 from /usr/lib/python2.7/site-packages/ansible-2.2.0-py2.7.egg/ansible/plugins/callback/__init__.pyc

PLAYBOOK: test.yml *************************************************************
1 plays in test.yml

PLAY [localhost] ***************************************************************

TASK [setup] *******************************************************************
Using module file /usr/lib/python2.7/site-packages/ansible-2.2.0-py2.7.egg/ansible/modules/core/system/setup.py
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC sshpass -d12 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o ConnectTimeout=30 -o ControlPath=/home/rhargr1/.ansible/cp/ansible-ssh-%h-%p-%r localhost '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1475261136.68-174066422501462 `" && echo ansible-tmp-1475261136.68-174066422501462="` echo $HOME/.ansible/tmp/ansible-tmp-1475261136.68-174066422501462 `" ) && sleep 0'"'"''
<localhost> PUT /tmp/tmp8TWCNu TO /home/rhargr1/.ansible/tmp/ansible-tmp-1475261136.68-174066422501462/setup.py
<localhost> SSH: EXEC sshpass -d12 sftp -o BatchMode=no -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o ConnectTimeout=30 -o ControlPath=/home/rhargr1/.ansible/cp/ansible-ssh-%h-%p-%r '[localhost]'
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC sshpass -d12 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o ConnectTimeout=30 -o ControlPath=/home/rhargr1/.ansible/cp/ansible-ssh-%h-%p-%r localhost '/bin/sh -c '"'"'chmod u+x /home/rhargr1/.ansible/tmp/ansible-tmp-1475261136.68-174066422501462/ /home/rhargr1/.ansible/tmp/ansible-tmp-1475261136.68-174066422501462/setup.py && sleep 0'"'"''
<localhost> ESTABLISH SSH CONNECTION FOR USER: None
<localhost> SSH: EXEC sshpass -d12 ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o ConnectTimeout=30 -o ControlPath=/home/rhargr1/.ansible/cp/ansible-ssh-%h-%p-%r -tt localhost '/bin/sh -c '"'"'dzdo -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-gygeuqrvgecoqtjhhvwrpultgiggwfcw; /usr/bin/python /home/rhargr1/.ansible/tmp/ansible-tmp-1475261136.68-174066422501462/setup.py; rm -rf "/home/rhargr1/.ansible/tmp/ansible-tmp-1475261136.68-174066422501462/" > /dev/null 2>&1'"'"'"'"'"'"'"'"' && sleep 0'"'"''
fatal: [localhost]: FAILED! => {
    "failed": true, 
    "msg": "Timeout (32s) waiting for privilege escalation prompt: "
}
        to retry, use: --limit @/home/rhargr1/playbooks/test.retry

PLAY RECAP *********************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1   
```



Discussion:

Resolving patch:
diff --git a/lib/ansible/executor/play_iterator.py b/lib/ansible/executor/play_iterator.py
--- a/lib/ansible/executor/play_iterator.py
+++ b/lib/ansible/executor/play_iterator.py
@@ -198,7 +198,7 @@ def __init__(self, inventory, play, play_context, variable_manager, all_vars, st
             self._host_states[host.name] = HostState(blocks=self._blocks)
             # if the host's name is in the variable manager's fact cache, then set
             # its _gathered_facts flag to true for smart gathering tests later
-            if host.name in variable_manager._fact_cache:
+            if host.name in variable_manager._fact_cache and variable_manager._fact_cache.get('module_setup', False):
                 host._gathered_facts = True
             # if we're looking to start at a specific task, iterate through
             # the tasks for this host until we find the specified task
diff --git a/lib/ansible/playbook/play_context.py b/lib/ansible/playbook/play_context.py
--- a/lib/ansible/playbook/play_context.py
+++ b/lib/ansible/playbook/play_context.py
@@ -562,8 +562,11 @@ def detect_ksu_prompt(data):
             elif self.become_method == 'dzdo':
 
                 exe = self.become_exe or 'dzdo'
-
-                becomecmd = '%s -u %s %s' % (exe, self.become_user, command)
+                if self.become_pass:
+                    prompt = '[dzdo via ansible, key=%s] password: ' % randbits
+                    becomecmd = '%s -p %s -u %s %s' % (exe, pipes.quote(prompt), self.become_user, command)
+                else:
+                    becomecmd = '%s -u %s %s' % (exe, self.become_user, command)
 
             else:
                 raise AnsibleError("Privilege escalation method not found: %s" % self.become_method)
diff --git a/lib/ansible/plugins/connection/ssh.py b/lib/ansible/plugins/connection/ssh.py
--- a/lib/ansible/plugins/connection/ssh.py
+++ b/lib/ansible/plugins/connection/ssh.py
@@ -148,9 +148,6 @@ def _build_command(self, binary, *other_args):
 
         if self._play_context.verbosity > 3:
             self._command += ['-vvv']
-        elif binary == self._play_context.ssh_executable:
-            # Older versions of ssh (e.g. in RHEL 6) don't accept sftp -q.
-            self._command += ['-q']
 
         # Next, we add [ssh_connection]ssh_args from ansible.cfg.
 
@@ -611,7 +608,7 @@ def exec_command(self, *args, **kwargs):
                 if return_tuple[0] != 255:
                     break
                 else:
-                    raise AnsibleConnectionFailure("Failed to connect to the host via ssh.")
+                    raise AnsibleConnectionFailure("Failed to connect to the host via ssh: %s" % to_native(return_tuple[2]))
             except (AnsibleConnectionFailure, Exception) as e:
                 if attempt == remaining_tries - 1:
                     raise


Test patch:
diff --git a/test/units/playbook/test_play_context.py b/test/units/playbook/test_play_context.py
--- a/test/units/playbook/test_play_context.py
+++ b/test/units/playbook/test_play_context.py
@@ -20,6 +20,7 @@
 __metaclass__ = type
 
 import os
+import pipes
 
 from ansible.compat.tests import unittest
 from ansible.compat.tests.mock import patch, MagicMock
@@ -177,6 +178,11 @@ def test_play_context_make_become_cmd(self):
         cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
         self.assertEqual(cmd, """%s -u %s %s -c 'echo %s; %s'""" % (dzdo_exe, play_context.become_user, default_exe, play_context.success_key, default_cmd))
 
+        play_context.become_pass = 'testpass'
+        play_context.become_method = 'dzdo'
+        cmd = play_context.make_become_cmd(cmd=default_cmd, executable="/bin/bash")
+        self.assertEqual(cmd, """%s -p %s -u %s %s -c 'echo %s; %s'""" % (dzdo_exe, pipes.quote(play_context.prompt), play_context.become_user, default_exe, play_context.success_key, default_cmd))
+
 class TestTaskAndVariableOverrride(unittest.TestCase):
 
     inventory_vars = (