We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Git module fails to add hostkey to known_hosts and doesn't notice it
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME

ansible.module_utils.known_hosts

##### ANSIBLE VERSION
```
ansible 2.1.3.0
  config file = 
  configured module search path = Default w/o overrides
```

##### OS / ENVIRONMENT

Ubuntu 16.04 

##### SUMMARY

Function add_host_key which gets called from  add_git_host_key, which gets called from the git module when accept_hostkey=yes, does not check that the output from ssh-keyscan is not empty. But ssh-keyscan has a timeout (5 seconds by default), and if the server could not answer in a timely manner, ssh-keyscan prints nothing to stdout and returns with return code 0. Ansible assumes that this operation is successful and proceeds to call git, which then hangs on a host key confirmation dialog.

The demonstration is as follows (I used -T1 to reproduce more easily, with default timeout of 5 seconds this also happens)
```
~$ ssh-keyscan -t rsa -T 1  bitbucket.org // this is succesful
# bitbucket.org:22 SSH-2.0-conker_1.0.263-aeae7ee app-134
bitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==
~$ echo $? // ret code zero
0
~$ ssh-keyscan -t rsa -T 1  bitbucket.org // this one failed
~$ echo $? // ret code still zero
0
```

##### STEPS TO REPRODUCE

Execute a git task with a slow git server with accept_hostkey=yes

##### EXPECTED RESULTS

Fail with a meaningful error specifying that the keyscan was unsuccessful. Also, a way to increase the default timeout would be nice.

##### ACTUAL RESULTS

The module hangs indefinitely

related issue: https://github.com/ansible/ansible-modules-core/issues/1343


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `lib/ansible/module_utils/known_hosts.py`
Content:
```
1 # This code is part of Ansible, but is an independent component.
2 # This particular file snippet, and this file snippet only, is BSD licensed.
3 # Modules you write using this snippet, which is embedded dynamically by Ansible
4 # still belong to the author of the module, and may assign their own license
5 # to the complete work.
6 #
7 # Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013
8 # All rights reserved.
9 #
10 # Redistribution and use in source and binary forms, with or without modification,
11 # are permitted provided that the following conditions are met:
12 #
13 #    * Redistributions of source code must retain the above copyright
14 #      notice, this list of conditions and the following disclaimer.
15 #    * Redistributions in binary form must reproduce the above copyright notice,
16 #      this list of conditions and the following disclaimer in the documentation
17 #      and/or other materials provided with the distribution.
18 #
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 # IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
27 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 
29 import os
30 import hmac
31 
32 try:
33     import urlparse
34 except ImportError:
35     import urllib.parse as urlparse
36 
37 try:
38     from hashlib import sha1
39 except ImportError:
40     import sha as sha1
41 
42 HASHED_KEY_MAGIC = "|1|"
43 
44 def add_git_host_key(module, url, accept_hostkey=True, create_dir=True):
45 
46     """ idempotently add a git url hostkey """
47 
48     if is_ssh_url(url):
49 
50         fqdn = get_fqdn(url)
51 
52         if fqdn:
53             known_host = check_hostkey(module, fqdn)
54             if not known_host:
55                 if accept_hostkey:
56                     rc, out, err = add_host_key(module, fqdn, create_dir=create_dir)
57                     if rc != 0:
58                         module.fail_json(msg="failed to add %s hostkey: %s" % (fqdn, out + err))
59                 else:
60                     module.fail_json(msg="%s has an unknown hostkey. Set accept_hostkey to True or manually add the hostkey prior to running the git module" % fqdn)
61 
62 def is_ssh_url(url):
63 
64     """ check if url is ssh """
65 
66     if "@" in url and "://" not in url:
67         return True
68     for scheme in "ssh://", "git+ssh://", "ssh+git://":
69         if url.startswith(scheme):
70             return True
71     return False
72 
73 def get_fqdn(repo_url):
74 
75     """ chop the hostname out of a url """
76 
77     result = None
78     if "@" in repo_url and "://" not in repo_url:
79         # most likely an user@host:path or user@host/path type URL
80         repo_url = repo_url.split("@", 1)[1]
81         if repo_url.startswith('['):
82             result = repo_url.split(']', 1)[0] + ']'
83         elif ":" in repo_url:
84             result = repo_url.split(":")[0]
85         elif "/" in repo_url:
86             result = repo_url.split("/")[0]
87     elif "://" in repo_url:
88         # this should be something we can parse with urlparse
89         parts = urlparse.urlparse(repo_url)
90         # parts[1] will be empty on python2.4 on ssh:// or git:// urls, so
91         # ensure we actually have a parts[1] before continuing.
92         if parts[1] != '':
93             result = parts[1]
94             if "@" in result:
95                 result = result.split("@", 1)[1]
96 
97             if result[0].startswith('['):
98                 result = result.split(']', 1)[0] + ']'
99             elif ":" in result:
100                 result = result.split(":")[0]
101     return result
102 
103 def check_hostkey(module, fqdn):
104    return not not_in_host_file(module, fqdn)
105 
106 # this is a variant of code found in connection_plugins/paramiko.py and we should modify
107 # the paramiko code to import and use this.
108 
109 def not_in_host_file(self, host):
110 
111 
112     if 'USER' in os.environ:
113         user_host_file = os.path.expandvars("~${USER}/.ssh/known_hosts")
114     else:
115         user_host_file = "~/.ssh/known_hosts"
116     user_host_file = os.path.expanduser(user_host_file)
117 
118     host_file_list = []
119     host_file_list.append(user_host_file)
120     host_file_list.append("/etc/ssh/ssh_known_hosts")
121     host_file_list.append("/etc/ssh/ssh_known_hosts2")
122     host_file_list.append("/etc/openssh/ssh_known_hosts")
123 
124     hfiles_not_found = 0
125     for hf in host_file_list:
126         if not os.path.exists(hf):
127             hfiles_not_found += 1
128             continue
129 
130         try:
131             host_fh = open(hf)
132         except IOError:
133             hfiles_not_found += 1
134             continue
135         else:
136             data = host_fh.read()
137             host_fh.close()
138 
139         for line in data.split("\n"):
140             if line is None or " " not in line:
141                 continue
142             tokens = line.split()
143             if tokens[0].find(HASHED_KEY_MAGIC) == 0:
144                 # this is a hashed known host entry
145                 try:
146                     (kn_salt,kn_host) = tokens[0][len(HASHED_KEY_MAGIC):].split("|",2)
147                     hash = hmac.new(kn_salt.decode('base64'), digestmod=sha1)
148                     hash.update(host)
149                     if hash.digest() == kn_host.decode('base64'):
150                         return False
151                 except:
152                     # invalid hashed host key, skip it
153                     continue
154             else:
155                 # standard host file entry
156                 if host in tokens[0]:
157                     return False
158 
159     return True
160 
161 
162 def add_host_key(module, fqdn, key_type="rsa", create_dir=False):
163 
164     """ use ssh-keyscan to add the hostkey """
165 
166     keyscan_cmd = module.get_bin_path('ssh-keyscan', True)
167 
168     if 'USER' in os.environ:
169         user_ssh_dir = os.path.expandvars("~${USER}/.ssh/")
170         user_host_file = os.path.expandvars("~${USER}/.ssh/known_hosts")
171     else:
172         user_ssh_dir = "~/.ssh/"
173         user_host_file = "~/.ssh/known_hosts"
174     user_ssh_dir = os.path.expanduser(user_ssh_dir)
175 
176     if not os.path.exists(user_ssh_dir):
177         if create_dir:
178             try:
179                 os.makedirs(user_ssh_dir, int('700', 8))
180             except:
181                 module.fail_json(msg="failed to create host key directory: %s" % user_ssh_dir)
182         else:
183             module.fail_json(msg="%s does not exist" % user_ssh_dir)
184     elif not os.path.isdir(user_ssh_dir):
185         module.fail_json(msg="%s is not a directory" % user_ssh_dir)
186 
187     this_cmd = "%s -t %s %s" % (keyscan_cmd, key_type, fqdn)
188 
189     rc, out, err = module.run_command(this_cmd)
190     module.append_to_file(user_host_file, out)
191 
192     return rc, out, err
193 
194
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/lib/ansible/module_utils/known_hosts.py b/lib/ansible/module_utils/known_hosts.py
--- a/lib/ansible/module_utils/known_hosts.py
+++ b/lib/ansible/module_utils/known_hosts.py
@@ -187,6 +187,9 @@
     this_cmd = "%s -t %s %s" % (keyscan_cmd, key_type, fqdn)
 
     rc, out, err = module.run_command(this_cmd)
+    # ssh-keyscan gives a 0 exit code and prints nothins on timeout
+    if rc != 0 or not out:
+        module.fail_json(msg='failed to get the hostkey for %s' % fqdn)
     module.append_to_file(user_host_file, out)
 
     return rc, out, err

{"golden_diff": "diff --git a/lib/ansible/module_utils/known_hosts.py b/lib/ansible/module_utils/known_hosts.py\n--- a/lib/ansible/module_utils/known_hosts.py\n+++ b/lib/ansible/module_utils/known_hosts.py\n@@ -187,6 +187,9 @@\n     this_cmd = \"%s -t %s %s\" % (keyscan_cmd, key_type, fqdn)\n \n     rc, out, err = module.run_command(this_cmd)\n+    # ssh-keyscan gives a 0 exit code and prints nothins on timeout\n+    if rc != 0 or not out:\n+        module.fail_json(msg='failed to get the hostkey for %s' % fqdn)\n     module.append_to_file(user_host_file, out)\n \n     return rc, out, err\n", "issue": "Git module fails to add hostkey to known_hosts and doesn't notice it\n##### ISSUE TYPE\r\n - Bug Report\r\n\r\n##### COMPONENT NAME\r\n\r\nansible.module_utils.known_hosts\r\n\r\n##### ANSIBLE VERSION\r\n```\r\nansible 2.1.3.0\r\n  config file = \r\n  configured module search path = Default w/o overrides\r\n```\r\n\r\n##### OS / ENVIRONMENT\r\n\r\nUbuntu 16.04 \r\n\r\n##### SUMMARY\r\n\r\nFunction add_host_key which gets called from  add_git_host_key, which gets called from the git module when accept_hostkey=yes, does not check that the output from ssh-keyscan is not empty. But ssh-keyscan has a timeout (5 seconds by default), and if the server could not answer in a timely manner, ssh-keyscan prints nothing to stdout and returns with return code 0. Ansible assumes that this operation is successful and proceeds to call git, which then hangs on a host key confirmation dialog.\r\n\r\nThe demonstration is as follows (I used -T1 to reproduce more easily, with default timeout of 5 seconds this also happens)\r\n```\r\n~$ ssh-keyscan -t rsa -T 1  bitbucket.org // this is succesful\r\n# bitbucket.org:22 SSH-2.0-conker_1.0.263-aeae7ee app-134\r\nbitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAubiN81eDcafrgMeLzaFPsw2kNvEcqTKl/VqLat/MaB33pZy0y3rJZtnqwR2qOOvbwKZYKiEO1O6VqNEBxKvJJelCq0dTXWT5pbO2gDXC6h6QDXCaHo6pOHGPUy+YBaGQRGuSusMEASYiWunYN0vCAI8QaXnWMXNMdFP3jHAJH0eDsoiGnLPBlBp4TNm6rYI74nMzgz3B9IikW4WVK+dc8KZJZWYjAuORU3jc1c/NPskD2ASinf8v3xnfXeukU0sJ5N6m5E8VLjObPEO+mN2t/FZTMZLiFqPWc/ALSqnMnnhwrNi2rbfg/rd/IpL8Le3pSBne8+seeFVBoGqzHM9yXw==\r\n~$ echo $? // ret code zero\r\n0\r\n~$ ssh-keyscan -t rsa -T 1  bitbucket.org // this one failed\r\n~$ echo $? // ret code still zero\r\n0\r\n```\r\n\r\n##### STEPS TO REPRODUCE\r\n\r\nExecute a git task with a slow git server with accept_hostkey=yes\r\n\r\n##### EXPECTED RESULTS\r\n\r\nFail with a meaningful error specifying that the keyscan was unsuccessful. Also, a way to increase the default timeout would be nice.\r\n\r\n##### ACTUAL RESULTS\r\n\r\nThe module hangs indefinitely\r\n\r\nrelated issue: https://github.com/ansible/ansible-modules-core/issues/1343\r\n\n", "before_files": [{"content": "# This code is part of Ansible, but is an independent component.\n# This particular file snippet, and this file snippet only, is BSD licensed.\n# Modules you write using this snippet, which is embedded dynamically by Ansible\n# still belong to the author of the module, and may assign their own license\n# to the complete work.\n#\n# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without modification,\n# are permitted provided that the following conditions are met:\n#\n#    * Redistributions of source code must retain the above copyright\n#      notice, this list of conditions and the following disclaimer.\n#    * Redistributions in binary form must reproduce the above copyright notice,\n#      this list of conditions and the following disclaimer in the documentation\n#      and/or other materials provided with the distribution.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE\n# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nimport os\nimport hmac\n\ntry:\n    import urlparse\nexcept ImportError:\n    import urllib.parse as urlparse\n\ntry:\n    from hashlib import sha1\nexcept ImportError:\n    import sha as sha1\n\nHASHED_KEY_MAGIC = \"|1|\"\n\ndef add_git_host_key(module, url, accept_hostkey=True, create_dir=True):\n\n    \"\"\" idempotently add a git url hostkey \"\"\"\n\n    if is_ssh_url(url):\n\n        fqdn = get_fqdn(url)\n\n        if fqdn:\n            known_host = check_hostkey(module, fqdn)\n            if not known_host:\n                if accept_hostkey:\n                    rc, out, err = add_host_key(module, fqdn, create_dir=create_dir)\n                    if rc != 0:\n                        module.fail_json(msg=\"failed to add %s hostkey: %s\" % (fqdn, out + err))\n                else:\n                    module.fail_json(msg=\"%s has an unknown hostkey. Set accept_hostkey to True or manually add the hostkey prior to running the git module\" % fqdn)\n\ndef is_ssh_url(url):\n\n    \"\"\" check if url is ssh \"\"\"\n\n    if \"@\" in url and \"://\" not in url:\n        return True\n    for scheme in \"ssh://\", \"git+ssh://\", \"ssh+git://\":\n        if url.startswith(scheme):\n            return True\n    return False\n\ndef get_fqdn(repo_url):\n\n    \"\"\" chop the hostname out of a url \"\"\"\n\n    result = None\n    if \"@\" in repo_url and \"://\" not in repo_url:\n        # most likely an user@host:path or user@host/path type URL\n        repo_url = repo_url.split(\"@\", 1)[1]\n        if repo_url.startswith('['):\n            result = repo_url.split(']', 1)[0] + ']'\n        elif \":\" in repo_url:\n            result = repo_url.split(\":\")[0]\n        elif \"/\" in repo_url:\n            result = repo_url.split(\"/\")[0]\n    elif \"://\" in repo_url:\n        # this should be something we can parse with urlparse\n        parts = urlparse.urlparse(repo_url)\n        # parts[1] will be empty on python2.4 on ssh:// or git:// urls, so\n        # ensure we actually have a parts[1] before continuing.\n        if parts[1] != '':\n            result = parts[1]\n            if \"@\" in result:\n                result = result.split(\"@\", 1)[1]\n\n            if result[0].startswith('['):\n                result = result.split(']', 1)[0] + ']'\n            elif \":\" in result:\n                result = result.split(\":\")[0]\n    return result\n\ndef check_hostkey(module, fqdn):\n   return not not_in_host_file(module, fqdn)\n\n# this is a variant of code found in connection_plugins/paramiko.py and we should modify\n# the paramiko code to import and use this.\n\ndef not_in_host_file(self, host):\n\n\n    if 'USER' in os.environ:\n        user_host_file = os.path.expandvars(\"~${USER}/.ssh/known_hosts\")\n    else:\n        user_host_file = \"~/.ssh/known_hosts\"\n    user_host_file = os.path.expanduser(user_host_file)\n\n    host_file_list = []\n    host_file_list.append(user_host_file)\n    host_file_list.append(\"/etc/ssh/ssh_known_hosts\")\n    host_file_list.append(\"/etc/ssh/ssh_known_hosts2\")\n    host_file_list.append(\"/etc/openssh/ssh_known_hosts\")\n\n    hfiles_not_found = 0\n    for hf in host_file_list:\n        if not os.path.exists(hf):\n            hfiles_not_found += 1\n            continue\n\n        try:\n            host_fh = open(hf)\n        except IOError:\n            hfiles_not_found += 1\n            continue\n        else:\n            data = host_fh.read()\n            host_fh.close()\n\n        for line in data.split(\"\\n\"):\n            if line is None or \" \" not in line:\n                continue\n            tokens = line.split()\n            if tokens[0].find(HASHED_KEY_MAGIC) == 0:\n                # this is a hashed known host entry\n                try:\n                    (kn_salt,kn_host) = tokens[0][len(HASHED_KEY_MAGIC):].split(\"|\",2)\n                    hash = hmac.new(kn_salt.decode('base64'), digestmod=sha1)\n                    hash.update(host)\n                    if hash.digest() == kn_host.decode('base64'):\n                        return False\n                except:\n                    # invalid hashed host key, skip it\n                    continue\n            else:\n                # standard host file entry\n                if host in tokens[0]:\n                    return False\n\n    return True\n\n\ndef add_host_key(module, fqdn, key_type=\"rsa\", create_dir=False):\n\n    \"\"\" use ssh-keyscan to add the hostkey \"\"\"\n\n    keyscan_cmd = module.get_bin_path('ssh-keyscan', True)\n\n    if 'USER' in os.environ:\n        user_ssh_dir = os.path.expandvars(\"~${USER}/.ssh/\")\n        user_host_file = os.path.expandvars(\"~${USER}/.ssh/known_hosts\")\n    else:\n        user_ssh_dir = \"~/.ssh/\"\n        user_host_file = \"~/.ssh/known_hosts\"\n    user_ssh_dir = os.path.expanduser(user_ssh_dir)\n\n    if not os.path.exists(user_ssh_dir):\n        if create_dir:\n            try:\n                os.makedirs(user_ssh_dir, int('700', 8))\n            except:\n                module.fail_json(msg=\"failed to create host key directory: %s\" % user_ssh_dir)\n        else:\n            module.fail_json(msg=\"%s does not exist\" % user_ssh_dir)\n    elif not os.path.isdir(user_ssh_dir):\n        module.fail_json(msg=\"%s is not a directory\" % user_ssh_dir)\n\n    this_cmd = \"%s -t %s %s\" % (keyscan_cmd, key_type, fqdn)\n\n    rc, out, err = module.run_command(this_cmd)\n    module.append_to_file(user_host_file, out)\n\n    return rc, out, err\n\n", "path": "lib/ansible/module_utils/known_hosts.py"}], "after_files": [{"content": "# This code is part of Ansible, but is an independent component.\n# This particular file snippet, and this file snippet only, is BSD licensed.\n# Modules you write using this snippet, which is embedded dynamically by Ansible\n# still belong to the author of the module, and may assign their own license\n# to the complete work.\n#\n# Copyright (c), Michael DeHaan <michael.dehaan@gmail.com>, 2012-2013\n# All rights reserved.\n#\n# Redistribution and use in source and binary forms, with or without modification,\n# are permitted provided that the following conditions are met:\n#\n#    * Redistributions of source code must retain the above copyright\n#      notice, this list of conditions and the following disclaimer.\n#    * Redistributions in binary form must reproduce the above copyright notice,\n#      this list of conditions and the following disclaimer in the documentation\n#      and/or other materials provided with the distribution.\n#\n# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.\n# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS\n# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE\n# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\nimport os\nimport hmac\n\ntry:\n    import urlparse\nexcept ImportError:\n    import urllib.parse as urlparse\n\ntry:\n    from hashlib import sha1\nexcept ImportError:\n    import sha as sha1\n\nHASHED_KEY_MAGIC = \"|1|\"\n\ndef add_git_host_key(module, url, accept_hostkey=True, create_dir=True):\n\n    \"\"\" idempotently add a git url hostkey \"\"\"\n\n    if is_ssh_url(url):\n\n        fqdn = get_fqdn(url)\n\n        if fqdn:\n            known_host = check_hostkey(module, fqdn)\n            if not known_host:\n                if accept_hostkey:\n                    rc, out, err = add_host_key(module, fqdn, create_dir=create_dir)\n                    if rc != 0:\n                        module.fail_json(msg=\"failed to add %s hostkey: %s\" % (fqdn, out + err))\n                else:\n                    module.fail_json(msg=\"%s has an unknown hostkey. Set accept_hostkey to True or manually add the hostkey prior to running the git module\" % fqdn)\n\ndef is_ssh_url(url):\n\n    \"\"\" check if url is ssh \"\"\"\n\n    if \"@\" in url and \"://\" not in url:\n        return True\n    for scheme in \"ssh://\", \"git+ssh://\", \"ssh+git://\":\n        if url.startswith(scheme):\n            return True\n    return False\n\ndef get_fqdn(repo_url):\n\n    \"\"\" chop the hostname out of a url \"\"\"\n\n    result = None\n    if \"@\" in repo_url and \"://\" not in repo_url:\n        # most likely an user@host:path or user@host/path type URL\n        repo_url = repo_url.split(\"@\", 1)[1]\n        if repo_url.startswith('['):\n            result = repo_url.split(']', 1)[0] + ']'\n        elif \":\" in repo_url:\n            result = repo_url.split(\":\")[0]\n        elif \"/\" in repo_url:\n            result = repo_url.split(\"/\")[0]\n    elif \"://\" in repo_url:\n        # this should be something we can parse with urlparse\n        parts = urlparse.urlparse(repo_url)\n        # parts[1] will be empty on python2.4 on ssh:// or git:// urls, so\n        # ensure we actually have a parts[1] before continuing.\n        if parts[1] != '':\n            result = parts[1]\n            if \"@\" in result:\n                result = result.split(\"@\", 1)[1]\n\n            if result[0].startswith('['):\n                result = result.split(']', 1)[0] + ']'\n            elif \":\" in result:\n                result = result.split(\":\")[0]\n    return result\n\ndef check_hostkey(module, fqdn):\n   return not not_in_host_file(module, fqdn)\n\n# this is a variant of code found in connection_plugins/paramiko.py and we should modify\n# the paramiko code to import and use this.\n\ndef not_in_host_file(self, host):\n\n\n    if 'USER' in os.environ:\n        user_host_file = os.path.expandvars(\"~${USER}/.ssh/known_hosts\")\n    else:\n        user_host_file = \"~/.ssh/known_hosts\"\n    user_host_file = os.path.expanduser(user_host_file)\n\n    host_file_list = []\n    host_file_list.append(user_host_file)\n    host_file_list.append(\"/etc/ssh/ssh_known_hosts\")\n    host_file_list.append(\"/etc/ssh/ssh_known_hosts2\")\n    host_file_list.append(\"/etc/openssh/ssh_known_hosts\")\n\n    hfiles_not_found = 0\n    for hf in host_file_list:\n        if not os.path.exists(hf):\n            hfiles_not_found += 1\n            continue\n\n        try:\n            host_fh = open(hf)\n        except IOError:\n            hfiles_not_found += 1\n            continue\n        else:\n            data = host_fh.read()\n            host_fh.close()\n\n        for line in data.split(\"\\n\"):\n            if line is None or \" \" not in line:\n                continue\n            tokens = line.split()\n            if tokens[0].find(HASHED_KEY_MAGIC) == 0:\n                # this is a hashed known host entry\n                try:\n                    (kn_salt,kn_host) = tokens[0][len(HASHED_KEY_MAGIC):].split(\"|\",2)\n                    hash = hmac.new(kn_salt.decode('base64'), digestmod=sha1)\n                    hash.update(host)\n                    if hash.digest() == kn_host.decode('base64'):\n                        return False\n                except:\n                    # invalid hashed host key, skip it\n                    continue\n            else:\n                # standard host file entry\n                if host in tokens[0]:\n                    return False\n\n    return True\n\n\ndef add_host_key(module, fqdn, key_type=\"rsa\", create_dir=False):\n\n    \"\"\" use ssh-keyscan to add the hostkey \"\"\"\n\n    keyscan_cmd = module.get_bin_path('ssh-keyscan', True)\n\n    if 'USER' in os.environ:\n        user_ssh_dir = os.path.expandvars(\"~${USER}/.ssh/\")\n        user_host_file = os.path.expandvars(\"~${USER}/.ssh/known_hosts\")\n    else:\n        user_ssh_dir = \"~/.ssh/\"\n        user_host_file = \"~/.ssh/known_hosts\"\n    user_ssh_dir = os.path.expanduser(user_ssh_dir)\n\n    if not os.path.exists(user_ssh_dir):\n        if create_dir:\n            try:\n                os.makedirs(user_ssh_dir, int('700', 8))\n            except:\n                module.fail_json(msg=\"failed to create host key directory: %s\" % user_ssh_dir)\n        else:\n            module.fail_json(msg=\"%s does not exist\" % user_ssh_dir)\n    elif not os.path.isdir(user_ssh_dir):\n        module.fail_json(msg=\"%s is not a directory\" % user_ssh_dir)\n\n    this_cmd = \"%s -t %s %s\" % (keyscan_cmd, key_type, fqdn)\n\n    rc, out, err = module.run_command(this_cmd)\n    # ssh-keyscan gives a 0 exit code and prints nothins on timeout\n    if rc != 0 or not out:\n        module.fail_json(msg='failed to get the hostkey for %s' % fqdn)\n    module.append_to_file(user_host_file, out)\n\n    return rc, out, err\n\n", "path": "lib/ansible/module_utils/known_hosts.py"}]}