// Copyright 2013 Apcera Inc. All rights reserved.

package gssapi

/*
#include <gssapi/gssapi.h>

OM_uint32
wrap_gss_acquire_cred(void *fp,
	OM_uint32 * minor_status,
	const gss_name_t desired_name,
	OM_uint32 time_req,
	const gss_OID_set desired_mechs,
	gss_cred_usage_t cred_usage,
	gss_cred_id_t * output_cred_handle,
	gss_OID_set * actual_mechs,
	OM_uint32 * time_rec)
{
	return ((OM_uint32(*) (
		OM_uint32 *,
		const gss_name_t,
		OM_uint32,
		const gss_OID_set,
		gss_cred_usage_t,
		gss_cred_id_t *,
		gss_OID_set *,
		OM_uint32 *)
	) fp)(
		minor_status,
		desired_name,
		time_req,
		desired_mechs,
		cred_usage,
		output_cred_handle,
		actual_mechs,
		time_rec);
}

OM_uint32
wrap_gss_add_cred(void *fp,
	OM_uint32 * minor_status,
	const gss_cred_id_t input_cred_handle,
	const gss_name_t desired_name,
	const gss_OID desired_mech,
	gss_cred_usage_t cred_usage,
	OM_uint32 initiator_time_req,
	OM_uint32 acceptor_time_req,
	gss_cred_id_t * output_cred_handle,
	gss_OID_set * actual_mechs,
	OM_uint32 * initiator_time_rec,
	OM_uint32 * acceptor_time_rec)
{
	return ((OM_uint32(*) (
		OM_uint32 *,
		const gss_cred_id_t,
		const gss_name_t,
		const gss_OID,
		gss_cred_usage_t,
		OM_uint32,
		OM_uint32,
		gss_cred_id_t *,
		gss_OID_set *,
		OM_uint32 *,
		OM_uint32 *)
	) fp)(
		minor_status,
		input_cred_handle,
		desired_name,
		desired_mech,
		cred_usage,
		initiator_time_req,
		acceptor_time_req,
		output_cred_handle,
		actual_mechs,
		initiator_time_rec,
		acceptor_time_rec);
}

OM_uint32
wrap_gss_inquire_cred (void *fp,
	OM_uint32           *minor_status,
	const gss_cred_id_t cred_handle,
	gss_name_t          *name,
	OM_uint32           *lifetime,
	gss_cred_usage_t    *cred_usage,
	gss_OID_set         *mechanisms )
{
	return ((OM_uint32(*) (
		OM_uint32 *,
		const gss_cred_id_t,
		gss_name_t *,
		OM_uint32 *,
		gss_cred_usage_t *,
		gss_OID_set *)
	) fp)(
		minor_status,
		cred_handle,
		name,
		lifetime,
		cred_usage,
		mechanisms);
}

OM_uint32
wrap_gss_inquire_cred_by_mech (void *fp,
	OM_uint32           *minor_status,
	const gss_cred_id_t cred_handle,
	const gss_OID       mech_type,
	gss_name_t          *name,
	OM_uint32           *initiator_lifetime,
	OM_uint32           *acceptor_lifetime,
	gss_cred_usage_t    *cred_usage )
{
	return ((OM_uint32(*) (
		OM_uint32 *,
		const gss_cred_id_t,
		const gss_OID,
		gss_name_t *,
		OM_uint32 *,
		OM_uint32 *,
		gss_cred_usage_t *)
	) fp)(
		minor_status,
		cred_handle,
		mech_type,
		name,
		initiator_lifetime,
		acceptor_lifetime,
		cred_usage);
}

OM_uint32
wrap_gss_release_cred(void *fp,
	OM_uint32 * minor_status,
	gss_cred_id_t * cred_handle)
{
	return ((OM_uint32(*) (
		OM_uint32 *,
		gss_cred_id_t *)
	) fp)(
		minor_status,
		cred_handle);
}

*/
import "C"

import (
	"time"
)

// NewCredId instantiates a new credential.
func (lib *Lib) NewCredId() *CredId {
	return &CredId{
		Lib: lib,
	}
}

// AcquireCred implements gss_acquire_cred API, as per
// https://tools.ietf.org/html/rfc2743#page-31. outputCredHandle, actualMechs
// must be .Release()-ed by the caller
func (lib *Lib) AcquireCred(desiredName *Name, timeReq time.Duration,
	desiredMechs *OIDSet, credUsage CredUsage) (outputCredHandle *CredId,
	actualMechs *OIDSet, timeRec time.Duration, err error) {

	min := C.OM_uint32(0)
	actualMechs = lib.NewOIDSet()
	outputCredHandle = lib.NewCredId()
	timerec := C.OM_uint32(0)

	maj := C.wrap_gss_acquire_cred(lib.Fp_gss_acquire_cred,
		&min,
		desiredName.C_gss_name_t,
		C.OM_uint32(timeReq.Seconds()),
		desiredMechs.C_gss_OID_set,
		C.gss_cred_usage_t(credUsage),
		&outputCredHandle.C_gss_cred_id_t,
		&actualMechs.C_gss_OID_set,
		&timerec)

	err = lib.stashLastStatus(maj, min)
	if err != nil {
		return nil, nil, 0, err
	}

	return outputCredHandle, actualMechs, time.Duration(timerec) * time.Second, nil
}

// AddCred implements gss_add_cred API, as per
// https://tools.ietf.org/html/rfc2743#page-36. outputCredHandle, actualMechs
// must be .Release()-ed by the caller
func (lib *Lib) AddCred(inputCredHandle *CredId,
	desiredName *Name, desiredMech *OID, credUsage CredUsage,
	initiatorTimeReq time.Duration, acceptorTimeReq time.Duration) (
	outputCredHandle *CredId, actualMechs *OIDSet,
	initiatorTimeRec time.Duration, acceptorTimeRec time.Duration,
	err error) {

	min := C.OM_uint32(0)
	actualMechs = lib.NewOIDSet()
	outputCredHandle = lib.NewCredId()
	initSeconds := C.OM_uint32(0)
	acceptSeconds := C.OM_uint32(0)

	maj := C.wrap_gss_add_cred(lib.Fp_gss_add_cred,
		&min,
		inputCredHandle.C_gss_cred_id_t,
		desiredName.C_gss_name_t,
		desiredMech.C_gss_OID,
		C.gss_cred_usage_t(credUsage),
		C.OM_uint32(initiatorTimeReq.Seconds()),
		C.OM_uint32(acceptorTimeReq.Seconds()),
		&outputCredHandle.C_gss_cred_id_t,
		&actualMechs.C_gss_OID_set,
		&initSeconds,
		&acceptSeconds)

	err = lib.stashLastStatus(maj, min)
	if err != nil {
		return nil, nil, 0, 0, err
	}

	return outputCredHandle,
		actualMechs,
		time.Duration(initSeconds) * time.Second,
		time.Duration(acceptSeconds) * time.Second,
		nil
}

// InquireCred implements gss_inquire_cred API, as per
// https://tools.ietf.org/html/rfc2743#page-34. name and mechanisms must be
// .Release()-ed by the caller
func (lib *Lib) InquireCred(credHandle *CredId) (
	name *Name, lifetime time.Duration, credUsage CredUsage, mechanisms *OIDSet,
	err error) {

	min := C.OM_uint32(0)
	name = lib.NewName()
	life := C.OM_uint32(0)
	credUsage = CredUsage(0)
	mechanisms = lib.NewOIDSet()

	maj := C.wrap_gss_inquire_cred(lib.Fp_gss_inquire_cred,
		&min,
		credHandle.C_gss_cred_id_t,
		&name.C_gss_name_t,
		&life,
		(*C.gss_cred_usage_t)(&credUsage),
		&mechanisms.C_gss_OID_set)
	err = lib.stashLastStatus(maj, min)
	if err != nil {
		return nil, 0, 0, nil, err
	}

	return name,
		time.Duration(life) * time.Second,
		credUsage,
		mechanisms,
		nil
}

// InquireCredByMech implements gss_inquire_cred_by_mech API, as per
// https://tools.ietf.org/html/rfc2743#page-39. name must be .Release()-ed by
// the caller
func (lib *Lib) InquireCredByMech(credHandle *CredId, mechType *OID) (
	name *Name, initiatorLifetime time.Duration, acceptorLifetime time.Duration,
	credUsage CredUsage, err error) {

	min := C.OM_uint32(0)
	name = lib.NewName()
	ilife := C.OM_uint32(0)
	alife := C.OM_uint32(0)
	credUsage = CredUsage(0)

	maj := C.wrap_gss_inquire_cred_by_mech(lib.Fp_gss_inquire_cred_by_mech,
		&min,
		credHandle.C_gss_cred_id_t,
		mechType.C_gss_OID,
		&name.C_gss_name_t,
		&ilife,
		&alife,
		(*C.gss_cred_usage_t)(&credUsage))
	err = lib.stashLastStatus(maj, min)
	if err != nil {
		return nil, 0, 0, 0, err
	}

	return name,
		time.Duration(ilife) * time.Second,
		time.Duration(alife) * time.Second,
		credUsage,
		nil
}

// Release frees a credential.
func (c *CredId) Release() error {
	if c == nil || c.C_gss_cred_id_t == nil {
		return nil
	}

	min := C.OM_uint32(0)
	maj := C.wrap_gss_release_cred(c.Fp_gss_release_cred,
		&min,
		&c.C_gss_cred_id_t)

	return c.stashLastStatus(maj, min)
}

//TODO: Test for AddCred with existing cred