"""
GGF Extensions

GGF provides extended credential and security context inquiry that allows
application to retrieve more information about the client's credentials and
security context. One common use case is to use
:meth:`inquire_sec_context_by_oid` to retrieve the "session" key that is
required by the SMB protocol for signing and encrypting a message.

Draft IETF document for these extensions can be found at
https://tools.ietf.org/html/draft-engert-ggf-gss-extensions-00
"""
GSSAPI="BASE"  # This ensures that a full module is generated by Cython

from gssapi.raw.cython_types cimport *
from gssapi.raw.ext_buffer_sets cimport *
from gssapi.raw.misc import GSSError
from gssapi.raw.oids cimport OID
from gssapi.raw.creds cimport Creds
from gssapi.raw.sec_contexts cimport SecurityContext

cdef extern from "python_gssapi_ext.h":

    OM_uint32 gss_inquire_cred_by_oid(OM_uint32 *minor_status,
                                      const gss_cred_id_t cred_handle,
                                      const gss_OID desired_object,
                                      gss_buffer_set_t *data_set) nogil

    OM_uint32 gss_inquire_sec_context_by_oid(OM_uint32 *minor_status,
                                             const gss_ctx_id_t context_handle,
                                             const gss_OID desired_object,
                                             gss_buffer_set_t *data_set) nogil

    OM_uint32 gss_set_sec_context_option(OM_uint32 *minor_status,
                                         gss_ctx_id_t *context_handle,
                                         const gss_OID desired_object,
                                         const gss_buffer_t value) nogil


def inquire_cred_by_oid(Creds cred_handle not None,
                        OID desired_aspect not None):
    """
    inquire_cred_by_oid(cred_handle, desired_aspect)

    This method inspects a :class:`Creds` object for information
    specific to a particular desired aspect as an OID.

    Args:
        cred_handle (Creds): the Credentials to query
        desired_aspect (OID): the desired aspect of the Credentials to inquire
            about.

    Returns:
        list: A list of zero or more pieces of data (as bytes objects)

    Raises:
        GSSError
    """

    cdef gss_buffer_set_t *data_set_ptr = NULL
    cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET
    cdef OM_uint32 maj_stat, min_stat

    data_set_ptr = &data_set

    with nogil:
        maj_stat = gss_inquire_cred_by_oid(&min_stat, cred_handle.raw_creds,
                                           &desired_aspect.raw_oid,
                                           data_set_ptr)

    if maj_stat == GSS_S_COMPLETE:
        py_tokens = []

        if data_set != GSS_C_NO_BUFFER_SET:
            for i in range(data_set.count):
                token = data_set.elements[i]
                py_tokens.append((<char*>token.value)[:token.length])

            gss_release_buffer_set(&min_stat, &data_set)

        return py_tokens
    else:
        raise GSSError(maj_stat, min_stat)


def inquire_sec_context_by_oid(SecurityContext context not None,
                               OID desired_aspect not None):
    """
    inquire_sec_context_by_oid(context, desired_aspect)

    This method inspects a :class:`SecurityContext` object for information
    specific to a particular desired aspect as an OID.

    This method can be used with the GSS_KRB5_INQ_SSPI_SESSION_KEY_OID OID to
    retrieve the required key that is used to derive the SMB/SAMBA signing and
    encryption keys.

    Args:
        context (SecurityContext): the Security Context to query
        desired_aspect (OID): the desired aspect of the Security Context to
            inquire about.

    Returns:
        list: A list of zero or more pieces of data (as bytes objects)

    Raises:
        GSSError
    """

    cdef gss_buffer_set_t *data_set_ptr = NULL
    cdef gss_buffer_set_t data_set = GSS_C_NO_BUFFER_SET
    cdef OM_uint32 maj_stat, min_stat

    data_set_ptr = &data_set

    with nogil:
        maj_stat = gss_inquire_sec_context_by_oid(&min_stat, context.raw_ctx,
                                                  &desired_aspect.raw_oid,
                                                  data_set_ptr)

    if maj_stat == GSS_S_COMPLETE:
        py_tokens = []

        if data_set != GSS_C_NO_BUFFER_SET:
            for i in range(data_set.count):
                token = data_set.elements[i]
                py_tokens.append((<char*>token.value)[:token.length])

            gss_release_buffer_set(&min_stat, &data_set)

        return py_tokens
    else:
        raise GSSError(maj_stat, min_stat)


def set_sec_context_option(OID desired_aspect not None,
                           SecurityContext context=None,
                           value=None):
    """
    set_sec_context_option(desired_aspect, context=None, value=None)

    This method is used to set a value for a specific OID of a
    :class:`SecurityContext` object. The OID and value to pass in depends on
    the mech the SecurityContext backs.

    An example of how this can be used would be to reset the NTLM crypto engine
    used in gss-ntlmssp. The OID that controls this value is
    '1.3.6.1.4.1.7165.655.1.3' and it takes it a byte value that represents
    an int32 where 1 resets the verifier handle and any other int resets the
    sender handle.

    Args:
        desired_aspect (OID): the desired aspect of the Security Context to set
            the value for.
        context (SecurityContext): the Security Context to set, or None to
            create a new context.
        value (bytes): the value to set on the desired aspect of the Security
            Context or None to send GSS_C_EMPTY_BUFFER.

    Returns:
        SecurityContext: The output security context.

    Raises:
        GSSError
    """

    cdef gss_buffer_desc value_buffer
    if value is not None:
        value_buffer = gss_buffer_desc(len(value), value)
    else:
        # GSS_C_EMPTY_BUFFER
        value_buffer = gss_buffer_desc(0, NULL)

    cdef SecurityContext output_context = context
    if output_context is None:
        output_context = SecurityContext()

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_set_sec_context_option(&min_stat,
                                              &output_context.raw_ctx,
                                              &desired_aspect.raw_oid,
                                              &value_buffer)

    if maj_stat == GSS_S_COMPLETE:
        return output_context
    else:
        raise GSSError(maj_stat, min_stat)
