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

from libc.stdlib cimport free

from gssapi.raw.cython_types cimport *
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.oids cimport OID
from gssapi.raw.chan_bindings cimport ChannelBindings

from gssapi.raw.types import MechType, RequirementFlag, IntEnumFlagSet
from gssapi.raw.misc import GSSError
from gssapi.raw.named_tuples import AcceptSecContextResult
from gssapi.raw.named_tuples import InitSecContextResult
from gssapi.raw.named_tuples import InquireContextResult


cdef extern from "python_gssapi.h":
    OM_uint32 gss_init_sec_context(OM_uint32 *min_stat,
                                   const gss_cred_id_t initiator_creds,
                                   gss_ctx_id_t *context,
                                   const gss_name_t target_name,
                                   const gss_OID mech_type,
                                   OM_uint32 flags,
                                   OM_uint32 ttl,
                                   const gss_channel_bindings_t chan_bdgs,
                                   const gss_buffer_t input_token,
                                   gss_OID *actual_mech_type,
                                   gss_buffer_t output_token,
                                   OM_uint32 *actual_flags,
                                   OM_uint32 *actual_ttl) nogil

    OM_uint32 gss_accept_sec_context(OM_uint32 *min_stat,
                                     gss_ctx_id_t *context,
                                     const gss_cred_id_t acceptor_creds,
                                     const gss_buffer_t input_token,
                                     const gss_channel_bindings_t chan_bdgs,
                                     const gss_name_t *initiator_name,
                                     gss_OID *mech_type,
                                     gss_buffer_t output_token,
                                     OM_uint32 *flags,
                                     OM_uint32 *ttl,
                                     gss_cred_id_t *delegated_creds) nogil

    OM_uint32 gss_delete_sec_context(OM_uint32 *min_stat,
                                     gss_ctx_id_t *context,
                                     gss_buffer_t output_token) nogil

    OM_uint32 gss_process_context_token(OM_uint32 *min_stat,
                                        const gss_ctx_id_t context,
                                        const gss_buffer_t token) nogil

    OM_uint32 gss_context_time(OM_uint32 *min_stat,
                               const gss_ctx_id_t context_handle,
                               OM_uint32 *ttl) nogil

    OM_uint32 gss_inquire_context(OM_uint32 *min_stat,
                                  const gss_ctx_id_t context,
                                  gss_name_t *initiator_name,
                                  gss_name_t *target_name,
                                  OM_uint32 *ttl,
                                  gss_OID *mech_type,
                                  OM_uint32 *ctx_flags,
                                  int *locally_initiated,
                                  int *is_open) nogil

    OM_uint32 gss_export_sec_context(OM_uint32 *min_stat,
                                     gss_ctx_id_t *context,
                                     gss_buffer_t interprocess_token) nogil

    OM_uint32 gss_import_sec_context(OM_uint32 *min_stat,
                                     const gss_buffer_t interprocess_token,
                                     gss_ctx_id_t *context) nogil


cdef class SecurityContext:
    # defined in pxd
    # cdef gss_ctx_id_t raw_ctx

    def __cinit__(self, SecurityContext cpy=None):
        if cpy is not None:
            self.raw_ctx = cpy.raw_ctx
            cpy.raw_ctx = GSS_C_NO_CONTEXT
        else:
            self.raw_ctx = GSS_C_NO_CONTEXT

    property _started:
        """Whether the underlying context is NULL."""

        def __get__(self):
            return self.raw_ctx is not NULL

    def __dealloc__(self):
        # basically just deleteSecContext, but we are not
        # allowed to call methods here
        cdef OM_uint32 maj_stat, min_stat
        if self.raw_ctx is not GSS_C_NO_CONTEXT:
            # local deletion only
            maj_stat = gss_delete_sec_context(&min_stat, &self.raw_ctx,
                                              GSS_C_NO_BUFFER)
            if maj_stat != GSS_S_COMPLETE:
                raise GSSError(maj_stat, min_stat)

            self.raw_ctx = NULL


# TODO(directxman12): figure out whether GSS_C_NO_NAME can be passed in here
def init_sec_context(Name target_name not None, Creds creds=None,
                     SecurityContext context=None,
                     OID mech=None,
                     flags=None, lifetime=None,
                     ChannelBindings channel_bindings=None,
                     input_token=None):
    cdef gss_OID mech_oid
    if mech is not None:
        mech_oid = &mech.raw_oid
    else:
        mech_oid = GSS_C_NO_OID

    # TODO(directxman12): should we default to this?
    cdef OM_uint32 req_flags = IntEnumFlagSet(RequirementFlag, flags or [
        RequirementFlag.mutual_authentication,
        RequirementFlag.out_of_sequence_detection])

    cdef gss_channel_bindings_t bdng
    if channel_bindings is not None:
        bdng = channel_bindings.__cvalue__()
    else:
        bdng = GSS_C_NO_CHANNEL_BINDINGS

    cdef gss_buffer_desc input_token_buffer = gss_buffer_desc(0, NULL)

    cdef OM_uint32 input_ttl = c_py_ttl_to_c(lifetime)

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

    cdef gss_cred_id_t act_cred
    if creds is not None:
        act_cred = creds.raw_creds
    else:
        act_cred = GSS_C_NO_CREDENTIAL

    if input_token is not None:
        input_token_buffer.value = input_token
        input_token_buffer.length = len(input_token)

    cdef gss_OID actual_mech_type = GSS_C_NO_OID
    cdef gss_buffer_desc output_token_buffer = gss_buffer_desc(0, NULL)
    cdef OM_uint32 ret_flags
    cdef OM_uint32 output_ttl

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_init_sec_context(&min_stat, act_cred,
                                        &output_context.raw_ctx,
                                        target_name.raw_name,
                                        mech_oid, req_flags, input_ttl,
                                        bdng, &input_token_buffer,
                                        &actual_mech_type,
                                        &output_token_buffer,
                                        &ret_flags, &output_ttl)

    output_token = None
    if output_token_buffer.length:
        output_token = \
            (<char*>output_token_buffer.value)[:output_token_buffer.length]
    cdef OM_uint32 tmp_min_stat
    gss_release_buffer(&tmp_min_stat, &output_token_buffer)

    if channel_bindings is not None:
        free(bdng)

    cdef OID output_mech_type = OID()
    if maj_stat == GSS_S_COMPLETE or maj_stat == GSS_S_CONTINUE_NEEDED:
        if actual_mech_type is not GSS_C_NO_OID:
            output_mech_type.raw_oid = actual_mech_type[0]

        return InitSecContextResult(output_context, output_mech_type,
                                    IntEnumFlagSet(RequirementFlag, ret_flags),
                                    output_token,
                                    c_c_ttl_to_py(output_ttl),
                                    maj_stat == GSS_S_CONTINUE_NEEDED)
    else:
        raise GSSError(maj_stat, min_stat, token=output_token)


def accept_sec_context(input_token not None, Creds acceptor_creds=None,
                       SecurityContext context=None,
                       ChannelBindings channel_bindings=None):
    cdef gss_channel_bindings_t bdng
    if channel_bindings is not None:
        bdng = channel_bindings.__cvalue__()
    else:
        bdng = GSS_C_NO_CHANNEL_BINDINGS

    cdef gss_buffer_desc input_token_buffer = gss_buffer_desc(len(input_token),
                                                              input_token)

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

    cdef gss_cred_id_t act_acceptor_cred
    if acceptor_creds is not None:
        act_acceptor_cred = acceptor_creds.raw_creds
    else:
        act_acceptor_cred = GSS_C_NO_CREDENTIAL

    cdef gss_name_t initiator_name
    cdef gss_OID mech_type
    # GSS_C_EMPTY_BUFFER
    cdef gss_buffer_desc output_token_buffer = gss_buffer_desc(0, NULL)
    cdef OM_uint32 ret_flags
    cdef OM_uint32 output_ttl
    cdef gss_cred_id_t delegated_cred

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_accept_sec_context(&min_stat, &output_context.raw_ctx,
                                          act_acceptor_cred,
                                          &input_token_buffer, bdng,
                                          &initiator_name,
                                          &mech_type, &output_token_buffer,
                                          &ret_flags, &output_ttl,
                                          &delegated_cred)

    output_token = None
    if output_token_buffer.length:
        output_token = \
            (<char*>output_token_buffer.value)[:output_token_buffer.length]
    cdef OM_uint32 tmp_min_stat
    gss_release_buffer(&tmp_min_stat, &output_token_buffer)

    if channel_bindings is not None:
        free(bdng)

    cdef Name on = Name()
    cdef Creds oc = None
    cdef OID py_mech_type
    if maj_stat == GSS_S_COMPLETE or maj_stat == GSS_S_CONTINUE_NEEDED:
        if output_ttl == GSS_C_INDEFINITE:
            output_ttl_py = None
        else:
            output_ttl_py = output_ttl

        on.raw_name = initiator_name

        if delegated_cred is not NULL:
            oc = Creds()
            oc.raw_creds = delegated_cred

        if mech_type is not NULL:
            py_mech_type = OID()
            py_mech_type.raw_oid = mech_type[0]
        else:
            py_mech_type = None

        return AcceptSecContextResult(output_context, on, py_mech_type,
                                      output_token,
                                      IntEnumFlagSet(RequirementFlag,
                                                     ret_flags),
                                      output_ttl_py, oc,
                                      maj_stat == GSS_S_CONTINUE_NEEDED)
    else:
        raise GSSError(maj_stat, min_stat, token=output_token)


def inquire_context(SecurityContext context not None, initiator_name=True,
                    target_name=True, lifetime=True, mech=True,
                    flags=True, locally_init=True, complete=True):
    cdef gss_name_t output_init_name
    cdef gss_name_t *init_name_ptr = NULL
    if initiator_name:
        init_name_ptr = &output_init_name

    cdef gss_name_t output_target_name
    cdef gss_name_t *target_name_ptr = NULL
    if target_name:
        target_name_ptr = &output_target_name

    cdef OM_uint32 ttl
    cdef OM_uint32 *ttl_ptr = NULL
    if lifetime:
        ttl_ptr = &ttl

    cdef gss_OID output_mech_type
    cdef gss_OID *mech_type_ptr = NULL
    if mech:
        mech_type_ptr = &output_mech_type

    cdef OM_uint32 output_flags
    cdef OM_uint32 *flags_ptr = NULL
    if flags:
        flags_ptr = &output_flags

    cdef int output_locally_init
    cdef int *locally_init_ptr = NULL
    if locally_init:
        locally_init_ptr = &output_locally_init

    cdef int is_complete
    cdef int *is_complete_ptr = NULL
    if complete:
        is_complete_ptr = &is_complete

    cdef OM_uint32 maj_stat, min_stat

    maj_stat = gss_inquire_context(&min_stat, context.raw_ctx, init_name_ptr,
                                   target_name_ptr, ttl_ptr, mech_type_ptr,
                                   flags_ptr, locally_init_ptr,
                                   is_complete_ptr)

    cdef Name sn
    cdef OID py_mech_type
    cdef Name tn
    if maj_stat == GSS_S_COMPLETE:
        if initiator_name:
            sn = Name()
            sn.raw_name = output_init_name
        else:
            sn = None

        if target_name and output_target_name != GSS_C_NO_NAME:
            tn = Name()
            tn.raw_name = output_target_name
        else:
            tn = None

        if mech:
            py_mech_type = OID()
            py_mech_type.raw_oid = output_mech_type[0]
        else:
            py_mech_type = None

        if lifetime and ttl != GSS_C_INDEFINITE:
            py_ttl = ttl
        else:
            py_ttl = None

        if flags:
            py_flags = IntEnumFlagSet(RequirementFlag, output_flags)
        else:
            py_flags = None

        if locally_init:
            py_locally_init = <bint>output_locally_init
        else:
            py_locally_init = None

        if complete:
            py_complete = <bint>is_complete
        else:
            py_complete = None

        return InquireContextResult(sn, tn, py_ttl, py_mech_type, py_flags,
                                    py_locally_init, py_complete)
    else:
        raise GSSError(maj_stat, min_stat)


def context_time(SecurityContext context not None):
    cdef OM_uint32 ttl

    cdef OM_uint32 maj_stat, min_stat

    maj_stat = gss_context_time(&min_stat, context.raw_ctx, &ttl)

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


def process_context_token(SecurityContext context not None, token):
    cdef gss_buffer_desc token_buffer = gss_buffer_desc(len(token), token)

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_process_context_token(&min_stat, context.raw_ctx,
                                             &token_buffer)

    if maj_stat != GSS_S_COMPLETE:
        raise GSSError(maj_stat, min_stat)


def import_sec_context(token not None):
    cdef gss_buffer_desc token_buffer = gss_buffer_desc(len(token), token)

    cdef gss_ctx_id_t ctx

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_import_sec_context(&min_stat, &token_buffer, &ctx)

    if maj_stat == GSS_S_COMPLETE:
        res = SecurityContext()
        res.raw_ctx = ctx
        return res
    else:
        raise GSSError(maj_stat, min_stat)


def export_sec_context(SecurityContext context not None):
    cdef gss_buffer_desc output_token = gss_buffer_desc(0, NULL)

    cdef OM_uint32 maj_stat, min_stat

    with nogil:
        maj_stat = gss_export_sec_context(&min_stat, &context.raw_ctx,
                                          &output_token)

    if maj_stat == GSS_S_COMPLETE:
        res_token = (<char*>output_token.value)[:output_token.length]
        gss_release_buffer(&min_stat, &output_token)
        return res_token
    else:
        raise GSSError(maj_stat, min_stat)


def delete_sec_context(SecurityContext context not None, local_only=True):
    cdef OM_uint32 maj_stat, min_stat
    # GSS_C_EMPTY_BUFFER
    cdef gss_buffer_desc output_token = gss_buffer_desc(0, NULL)
    if not local_only:
        maj_stat = gss_delete_sec_context(&min_stat, &context.raw_ctx,
                                          &output_token)
    else:
        maj_stat = gss_delete_sec_context(&min_stat, &context.raw_ctx,
                                          GSS_C_NO_BUFFER)

    if maj_stat == GSS_S_COMPLETE:
        res = (<char*>output_token.value)[:output_token.length]
        context.raw_ctx = NULL
        return res
    else:
        raise GSSError(maj_stat, min_stat)
