Download SSH public key from remote host "hostname", optionally validate
    its fingerprint against "fingerprint" variable and save the record in the
    known_hosts file.

    If such a record does already exists in there, do nothing.

    user
        The user who owns the ssh authorized keys file to modify

    hostname
        The name of the remote host (e.g. "github.com")

    fingerprint
        The fingerprint of the key which must be present in the known_hosts
        file (optional if key specified)

    key
        The public key which must be presented in the known_hosts file
        (optional if fingerprint specified)

    port
        optional parameter, denoting the port of the remote host, which will be
        used in case, if the public key will be requested from it. By default
        the port 22 is used.

    enc
        Defines what type of key is being used, can be ed25519, ecdsa ssh-rsa
        or ssh-dss

    config
        The location of the authorized keys file relative to the user's home
        directory, defaults to ".ssh/known_hosts". If no user is specified,
        defaults to "/etc/ssh/ssh_known_hosts". If present, must be an
        absolute path when a user is not specified.

    hash_known_hosts : True
        Hash all hostnames and addresses in the known hosts file.

    timeout : int
        Set the timeout for connection attempts.  If ``timeout`` seconds have
        elapsed since a connection was initiated to a host or since the last
        time anything was read from that host, then the connection is closed
        and the host in question considered unavailable.  Default is 5 seconds.

        .. versionadded:: 2016.3.0

    fingerprint_hash_type
        The public key fingerprint hash type that the public key fingerprint
        was originally hashed with. This defaults to ``sha256`` if not specified.

        .. versionadded:: 2016.11.4
        .. versionchanged:: 2017.7.0: default changed from ``md5`` to ``sha256``

    CLI Example:

    .. code-block:: bash

        salt '*' ssh.set_known_host <user> fingerprint='xx:xx:..:xx' enc='ssh-rsa' config='.ssh/known_hosts'

def set_known_host(user=None,
                   hostname=None,
                   fingerprint=None,
                   key=None,
                   port=None,
                   enc=None,
                   config=None,
                   hash_known_hosts=True,
                   timeout=5,
                   fingerprint_hash_type=None):
    '''
    Download SSH public key from remote host "hostname", optionally validate
    its fingerprint against "fingerprint" variable and save the record in the
    known_hosts file.

    If such a record does already exists in there, do nothing.

    user
        The user who owns the ssh authorized keys file to modify

    hostname
        The name of the remote host (e.g. "github.com")

    fingerprint
        The fingerprint of the key which must be present in the known_hosts
        file (optional if key specified)

    key
        The public key which must be presented in the known_hosts file
        (optional if fingerprint specified)

    port
        optional parameter, denoting the port of the remote host, which will be
        used in case, if the public key will be requested from it. By default
        the port 22 is used.

    enc
        Defines what type of key is being used, can be ed25519, ecdsa ssh-rsa
        or ssh-dss

    config
        The location of the authorized keys file relative to the user's home
        directory, defaults to ".ssh/known_hosts". If no user is specified,
        defaults to "/etc/ssh/ssh_known_hosts". If present, must be an
        absolute path when a user is not specified.

    hash_known_hosts : True
        Hash all hostnames and addresses in the known hosts file.

    timeout : int
        Set the timeout for connection attempts.  If ``timeout`` seconds have
        elapsed since a connection was initiated to a host or since the last
        time anything was read from that host, then the connection is closed
        and the host in question considered unavailable.  Default is 5 seconds.

        .. versionadded:: 2016.3.0

    fingerprint_hash_type
        The public key fingerprint hash type that the public key fingerprint
        was originally hashed with. This defaults to ``sha256`` if not specified.

        .. versionadded:: 2016.11.4
        .. versionchanged:: 2017.7.0: default changed from ``md5`` to ``sha256``

    CLI Example:

    .. code-block:: bash

        salt '*' ssh.set_known_host <user> fingerprint='xx:xx:..:xx' enc='ssh-rsa' config='.ssh/known_hosts'
    '''
    if not hostname:
        return {'status': 'error',
                'error': 'hostname argument required'}

    update_required = False
    check_required = False
    stored_host_entries = get_known_host_entries(user,
                                 hostname,
                                 config=config,
                                 port=port,
                                 fingerprint_hash_type=fingerprint_hash_type)
    stored_keys = [h['key'] for h in stored_host_entries] if stored_host_entries else []
    stored_fingerprints = [h['fingerprint'] for h in stored_host_entries] if stored_host_entries else []

    if not stored_host_entries:
        update_required = True
    elif fingerprint and fingerprint not in stored_fingerprints:
        update_required = True
    elif key and key not in stored_keys:
        update_required = True
    elif key is None and fingerprint is None:
        check_required = True

    if not update_required and not check_required:
        return {'status': 'exists', 'keys': stored_keys}

    if not key:
        remote_host_entries = recv_known_host_entries(hostname,
                                      enc=enc,
                                      port=port,
                                      hash_known_hosts=hash_known_hosts,
                                      timeout=timeout,
                                      fingerprint_hash_type=fingerprint_hash_type)
        known_keys = [h['key'] for h in remote_host_entries] if remote_host_entries else []
        known_fingerprints = [h['fingerprint'] for h in remote_host_entries] if remote_host_entries else []
        if not remote_host_entries:
            return {'status': 'error',
                    'error': 'Unable to receive remote host keys'}

        if fingerprint and fingerprint not in known_fingerprints:
            return {'status': 'error',
                    'error': ('Remote host public keys found but none of their '
                              'fingerprints match the one you have provided')}

        if check_required:
            for key in known_keys:
                if key in stored_keys:
                    return {'status': 'exists', 'keys': stored_keys}

    full = _get_known_hosts_file(config=config, user=user)

    if isinstance(full, dict):
        return full

    if os.path.isfile(full):
        origmode = os.stat(full).st_mode

        # remove existing known_host entry with matching hostname and encryption key type
        # use ssh-keygen -F to find the specific line(s) for this host + enc combo
        ssh_hostname = _hostname_and_port_to_ssh_hostname(hostname, port)
        cmd = ['ssh-keygen', '-F', ssh_hostname, '-f', full]
        lines = __salt__['cmd.run'](cmd,
                                    ignore_retcode=True,
                                    python_shell=False).splitlines()
        remove_lines = list(
            _get_matched_host_line_numbers(lines, enc)
        )

        if remove_lines:
            try:
                with salt.utils.files.fopen(full, 'r+') as ofile:
                    known_hosts_lines = salt.utils.data.decode(list(ofile))
                    # Delete from last line to first to avoid invalidating earlier indexes
                    for line_no in sorted(remove_lines, reverse=True):
                        del known_hosts_lines[line_no - 1]
                    # Write out changed known_hosts file
                    ofile.seek(0)
                    ofile.truncate()
                    ofile.writelines(
                        salt.utils.data.decode(known_hosts_lines, to_str=True)
                    )
            except (IOError, OSError) as exception:
                raise CommandExecutionError(
                    "Couldn't remove old entry(ies) from known hosts file: '{0}'".format(exception)
                )
    else:
        origmode = None

    # set up new value
    if key:
        remote_host_entries = [{'hostname': hostname, 'enc': enc, 'key': key}]

    lines = []
    for entry in remote_host_entries:
        if hash_known_hosts or port in [DEFAULT_SSH_PORT, None] or ':' in entry['hostname']:
            line = '{hostname} {enc} {key}\n'.format(**entry)
        else:
            entry['port'] = port
            line = '[{hostname}]:{port} {enc} {key}\n'.format(**entry)
        lines.append(line)

    # ensure ~/.ssh exists
    ssh_dir = os.path.dirname(full)
    if user:
        uinfo = __salt__['user.info'](user)

    try:
        log.debug('Ensuring ssh config dir "%s" exists', ssh_dir)
        os.makedirs(ssh_dir)
    except OSError as exc:
        if exc.args[1] == 'Permission denied':
            log.error('Unable to create directory %s: '
                      '%s', ssh_dir, exc.args[1])
        elif exc.args[1] == 'File exists':
            log.debug('%s already exists, no need to create '
                      'it', ssh_dir)
    else:
        # set proper ownership/permissions
        if user:
            os.chown(ssh_dir, uinfo['uid'], uinfo['gid'])
            os.chmod(ssh_dir, 0o700)

    # write line to known_hosts file
    try:
        with salt.utils.files.fopen(full, 'ab') as ofile:
            ofile.writelines(salt.utils.data.encode(lines))
    except (IOError, OSError) as exception:
        raise CommandExecutionError(
            "Couldn't append to known hosts file: '{0}'".format(exception)
        )

    if not salt.utils.platform.is_windows():
        if os.geteuid() == 0 and user:
            os.chown(full, uinfo['uid'], uinfo['gid'])
        if origmode:
            os.chmod(full, origmode)
        else:
            os.chmod(full, 0o600)

    if key and hash_known_hosts:
        cmd_result = __salt__['ssh.hash_known_hosts'](user=user, config=full)

    rval = {'status': 'updated', 'old': stored_host_entries, 'new': remote_host_entries}
    return rval