GSSAPI="BASE"  # This ensures that a full module is generated by Cythin

# Due to a bug in MIT Kerberos, add_cred_with_password was not properly
# exported for some time.  In order to work around this,
# add_cred_with_password is in its own file.  For more information, see:
# https://github.com/krb5/krb5/pull/244

from gssapi.raw.cython_types cimport *
from gssapi.raw.cython_converters cimport c_get_mech_oid_set
from gssapi.raw.cython_converters cimport c_create_oid_set
from gssapi.raw.cython_converters cimport c_py_ttl_to_c, c_c_ttl_to_py
from gssapi.raw.creds cimport Creds
from gssapi.raw.names cimport Name

from gssapi.raw.misc import GSSError
from gssapi.raw.named_tuples import AcquireCredResult

cdef extern from "python_gssapi_ext.h":
    OM_uint32 gss_acquire_cred_with_password(OM_uint32 *min_stat,
                                             const gss_name_t desired_name,
                                             const gss_buffer_t password,
                                             OM_uint32 ttl,
                                             const gss_OID_set desired_mechs,
                                             gss_cred_usage_t cred_usage,
                                             gss_cred_id_t *output_creds,
                                             gss_OID_set *actual_mechs,
                                             OM_uint32 *actual_ttl) nogil


def acquire_cred_with_password(Name name not None, password not None,
                               lifetime=None, mechs=None, usage="initiate"):
    """
    acquire_cred_with_password(name, password, lifetime=None, mechs=None, \
usage="initiate")
    Acquire credentials through provided password.

    This function is originally from Solaris and is not documented by either
    MIT or Heimdal.

    In general, it functions similarly to :func:`acquire_cred`.

    Args:
        name (Name): the name to acquire credentials for
        password (bytes): the password used to acquire credentialss with
        lifetime (int): the lifetime for the credentials (or None for
            indefinite)
        mechs ([MechType]): the desired mechanisms for which the credentials
            should work (or None for the default set)
        usage (str): usage type for credentials.  Possible values:
            'initiate' (default), 'accept', 'both' (failsafe).

    Returns:
        AcquireCredResult: the resulting credentials, the actual mechanisms
        with which they may be used, and their actual lifetime (or None for
        indefinite or not supported)

    Raises:
        GSSError
    """

    cdef gss_buffer_desc password_buffer = gss_buffer_desc(len(password),
                                                           password)

    cdef OM_uint32 input_ttl = c_py_ttl_to_c(lifetime)

    cdef gss_OID_set desired_mechs
    if mechs is not None:
        desired_mechs = c_get_mech_oid_set(mechs)
    else:
        desired_mechs = GSS_C_NO_OID_SET

    cdef gss_cred_usage_t c_usage
    if usage == "initiate":
        c_usage = GSS_C_INITIATE
    elif usage == "accept":
        c_usage = GSS_C_ACCEPT
    elif usage == 'both':
        c_usage = GSS_C_BOTH
    else:
        raise ValueError(f'Invalid usage "{usage}" - permitted values are '
                         '"initiate", "accept", and "both"')

    cdef gss_cred_id_t creds
    cdef gss_OID_set actual_mechs
    cdef OM_uint32 actual_ttl

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_acquire_cred_with_password(
            &min_stat, name.raw_name, &password_buffer, input_ttl,
            desired_mechs, c_usage, &creds, &actual_mechs, &actual_ttl)

    cdef OM_uint32 tmp_min_stat
    if mechs is not None:
        gss_release_oid_set(&tmp_min_stat, &desired_mechs)

    cdef Creds rc = Creds()
    if maj_stat == GSS_S_COMPLETE:
        rc.raw_creds = creds
        return AcquireCredResult(rc, c_create_oid_set(actual_mechs),
                                 c_c_ttl_to_py(actual_ttl))
    else:
        raise GSSError(maj_stat, min_stat)
