#!/usr/bin/python

# Copyright: (c) 2020, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import annotations

DOCUMENTATION = r"""
---
module: httptester_kinit
short_description: Get Kerberos ticket
description: Get Kerberos ticket using kinit non-interactively.
options:
  username:
    description: The username to get the ticket for.
    required: true
    type: str
  password:
    description: The password for I(username).
    required; true
    type: str
author:
- Ansible Project
"""

EXAMPLES = r"""
#
"""

RETURN = r"""
#
"""

import contextlib
import errno
import os
import subprocess

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_bytes, to_text

try:
    import configparser
except ImportError:
    import ConfigParser as configparser


@contextlib.contextmanager
def env_path(name, value, default_value):
    """ Adds a value to a PATH-like env var and preserve the existing value if present. """
    orig_value = os.environ.get(name, None)
    os.environ[name] = '%s:%s' % (value, orig_value or default_value)
    try:
        yield

    finally:
        if orig_value:
            os.environ[name] = orig_value

        else:
            del os.environ[name]


@contextlib.contextmanager
def krb5_conf(module, config):
    """ Runs with a custom krb5.conf file that extends the existing config if present. """
    if config:
        ini_config = configparser.ConfigParser()
        for section, entries in config.items():
            ini_config.add_section(section)
            for key, value in entries.items():
                ini_config.set(section, key, value)

        config_path = os.path.join(module.tmpdir, 'krb5.conf')
        with open(config_path, mode='wt') as config_fd:
            ini_config.write(config_fd)

        with env_path('KRB5_CONFIG', config_path, '/etc/krb5.conf'):
            yield

    else:
        yield


def main():
    module_args = dict(
        username=dict(type='str', required=True),
        password=dict(type='str', required=True, no_log=True),
    )
    module = AnsibleModule(
        argument_spec=module_args,
        required_together=[('username', 'password')],
    )

    # Heimdal has a few quirks that we want to paper over in this module
    #     1. KRB5_TRACE does not work in any released version (<=7.7), we need to use a custom krb5.config to enable it
    #     2. When reading the password it reads from the pty not stdin by default causing an issue with subprocess. We
    #        can control that behaviour with '--password-file=STDIN'
    # Also need to set the custom path to krb5-config and kinit as FreeBSD relies on the newer Heimdal version in the
    # port package.
    sysname = os.uname()[0]
    prefix = '/usr/local/bin/' if sysname == 'FreeBSD' else ''
    is_heimdal = sysname in ['Darwin', 'FreeBSD']

    # Debugging purposes, get the Kerberos version. On platforms like OpenSUSE this may not be on the PATH.
    try:
        process = subprocess.Popen(['%skrb5-config' % prefix, '--version'], stdout=subprocess.PIPE)
        stdout, stderr = process.communicate()
        version = to_text(stdout)
    except OSError as e:
        if e.errno != errno.ENOENT:
            raise
        version = 'Unknown (no krb5-config)'

    kinit_args = ['%skinit' % prefix]
    config = {}
    if is_heimdal:
        kinit_args.append('--password-file=STDIN')
        config['logging'] = {'krb5': 'FILE:/dev/stdout'}
    kinit_args.append(to_text(module.params['username'], errors='surrogate_or_strict'))

    with krb5_conf(module, config):
        # Weirdly setting KRB5_CONFIG in the modules environment block does not work unless we pass it in explicitly.
        # Take a copy of the existing environment to make sure the process has the same env vars as ours. Also set
        # KRB5_TRACE to output and debug logs helping to identify problems when calling kinit with MIT.
        kinit_env = os.environ.copy()
        kinit_env['KRB5_TRACE'] = '/dev/stdout'

        process = subprocess.Popen(kinit_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
                                   env=kinit_env)
        stdout, stderr = process.communicate(to_bytes(module.params['password'], errors='surrogate_or_strict') + b'\n')
        rc = process.returncode

    module.exit_json(changed=True, stdout=to_text(stdout), stderr=to_text(stderr), rc=rc, version=version)


if __name__ == '__main__':
    main()
