/*
 *	context.c
 *
 *	Copyright (c) 1997, by Sun Microsystems, Inc.
 *	All rights reserved.
 *
 */

#pragma ident	"@(#)context.c	1.4	98/06/11 SMI"

#include <sys/note.h>
#include "dh_gssapi.h"

/*
 * This module contains the implementation of the gssapi context support
 * routines for the Diffie-Hellman mechanism.
 *
 * The GSS routines that are supported by this module are:
 *	gss_context_time
 *	gss_delete_sec_context
 *	gss_inquire_context
 *	gss_wrap_size_limit
 *
 * The following routines are not supported for the Diffie-Hellman
 * Mechanism at this time.
 *	gss_export_sec_context
 *	gss_import_sec_context
 *
 * The following routine is not supported since it is obsolete in version 2
 * of the GSS-API.
 *	gss_process_context_token.
 *
 * Note that support for gss_init_sec_context and gss_accept_sec_context is
 * found in context_establish.c
 */

OM_uint32
__dh_gss_context_time(void *ctx, /* Mechanism context (not used) */
		    OM_uint32 * minor, /* GSS minor status */
		    gss_ctx_id_t context, /* GSS context handle */
		    OM_uint32* time_remaining /* Time remaining */)

{
_NOTE(ARGUNUSED(ctx))
	/* Context is a dh context */
	dh_gss_context_t cntx = (dh_gss_context_t)context;
	time_t now = time(0);

	if (minor == 0)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	if (time_remaining == 0)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	/* Validate context */
	if ((*minor = __dh_validate_context(cntx)) != DH_SUCCESS)
		return (GSS_S_NO_CONTEXT);

	/* See if it is always valid */
	if (cntx->expire == (time_t)GSS_C_INDEFINITE) {
		*time_remaining = GSS_C_INDEFINITE;
		return (GSS_S_COMPLETE);
	}

	/* Calculate the remainning time */
	*time_remaining = (now < cntx->expire) ? cntx->expire - now : 0;

	/* Return expired if there is no time left */
	return (*time ? GSS_S_COMPLETE : GSS_S_CONTEXT_EXPIRED);
}

/*
 * Delete a Diffie-Hellman context that is pointed to by context.
 * On a successfull return *context will be NULL.
 */

OM_uint32
__dh_gss_delete_sec_context(void *ctx, /* Mechanism context */
			    OM_uint32 *minor, /* Mechanism status */
			    gss_ctx_id_t *context, /* GSS context */
			    gss_buffer_t token /* GSS token */)
{
_NOTE(ARGUNUSED(ctx))

	dh_gss_context_t cntx;

	if (context == 0)
		return (GSS_S_CALL_INACCESSIBLE_READ |
			GSS_S_CALL_INACCESSIBLE_WRITE);

	/* context is a Diffie-Hellman context */
	cntx = (dh_gss_context_t)*context;

	if (minor == 0)
		return (GSS_S_CALL_INACCESSIBLE_WRITE);

	/*
	 * If token then set the length to zero value to zero to indicate
	 * We indicat a null token since we don't need to send a token to
	 * the other side.
	 */

	if (token) {
		token->length = 0;
		token->value = NULL;
	}

	/* Deleting a null context is OK */
	if (cntx == NULL)
		return (GSS_S_COMPLETE);

	/* Validate the context */
	if ((*minor = __dh_validate_context(cntx)) != DH_SUCCESS)
		return (GSS_S_NO_CONTEXT);

	/* Zero out the session keys! */
	memset(cntx->keys, 0, cntx->no_keys * sizeof (des_block));

	/* Unregister the context */
	*minor = __dh_remove_context(cntx);

	/* Free storage */
	__dh_destroy_seq_hist(cntx);
	free(cntx->remote);
	free(cntx->local);
	Free(cntx->keys);
	Free(cntx);

	/* Set context to NULL */
	*context = NULL;

	return (GSS_S_COMPLETE);
}


/*
 * Diffie-Hellman mechanism currently does not support exporting and importing
 * gss contexts.
 */

OM_uint32
/*ARGSUSED*/
__dh_gss_export_sec_context(void *ctx, OM_uint32 *minor,
			    gss_ctx_id_t *context, gss_buffer_t token)
{
	return (GSS_S_UNAVAILABLE);
}

OM_uint32
/*ARGSUSED*/
__dh_gss_import_sec_context(void * ctx, OM_uint32 *minor,
			    gss_buffer_t token, gss_ctx_id_t *context)
{
	return (GSS_S_UNAVAILABLE);
}

/*
 * Get the state of a Diffie-Hellman context
 */

OM_uint32
__dh_gss_inquire_context(void *ctx, /* Mechanism context */
			OM_uint32 *minor, /* Mechanism status */
			gss_ctx_id_t context, /* GSS context */
			gss_name_t *initiator, /* Name of initiator */
			gss_name_t *acceptor, /* Name of acceptor */
			OM_uint32 *time_rec, /* Amount of time left */
			gss_OID *mech, /* return OID of mechanism */
			OM_uint32 *flags_rec, /* flags set on context */
			int *local, /* True if we're the initiator */
			int *open /* True if the context is established */)
{
	dh_gss_context_t cntx;
	OM_uint32 stat = GSS_S_COMPLETE;
	OM_uint32 t;

	/* context is a Diffie-Hellman */
	cntx = (dh_gss_context_t)context;

	/* Validate the context */
	if ((*minor = __dh_validate_context(cntx)) != DH_SUCCESS)
		return (GSS_S_NO_CONTEXT);

	/* If the caller wants the mechanism OID set *mech to if we can */
	if (mech) {
		if (ctx == 0) {
			*mech = GSS_C_NO_OID;
			return (GSS_S_CALL_INACCESSIBLE_READ);
		}
		else
			*mech = ((dh_context_t)ctx)->mech;
	}

	/* set t to be the time left on the context */
	if (cntx->expire == GSS_C_INDEFINITE)
		t = GSS_C_INDEFINITE;
	else {
		time_t now = time(0);
		t = now > cntx->expire ? 0 : (OM_uint32)(cntx->expire - now);
	}

	/* If the caller wants the initiator set *initiator to it. */
	if (initiator) {
		dh_principal p = cntx->initiate ? cntx->local : cntx->remote;
		*initiator = strdup(p);
	}

	/* If the callers wants the acceptor set *acceptor to it. */
	if (acceptor) {
		dh_principal p = cntx->initiate ? cntx->remote : cntx->local;
		*acceptor = strdup(p);
	}

	/* If the caller wants the time remaining set *time_rec to t */
	if (time_rec)
		*time_rec = t;


	/* Return the flags in flags_rec if set */
	if (flags_rec)
		*flags_rec = cntx->flags;

	/* ditto for local */
	if (local)
		*local = cntx->initiate;

	/* ditto for open */
	if (open)
		*open = (cntx->state == ESTABLISHED);


	/* return GSS_S_CONTEXT_EXPIRED if no time is left on the context */
	return ((t == 0 ? GSS_S_CONTEXT_EXPIRED : GSS_S_COMPLETE) | stat);
}

/*
 * __dh_gss_process_context_token.
 * This routine is not implemented. It is depricated in version 2.
 */

OM_uint32
/*ARGSUSED*/
__dh_gss_process_context_token(void *ctx, OM_uint32 *minor,
    gss_ctx_id_t context, gss_buffer_t token)
{
	return (GSS_S_UNAVAILABLE);
}

/*
 * This implements the gss_wrap_size_limit entry point for Diffie-Hellman
 * mechanism. See RFC 2078 for details. The idea here is for a context,
 * qop, whether confidentiality is specified, and an output size, return
 * the maximum input size that will fit in the given output size. Typically
 * the output size would be the MTU of the higher level protocol using the
 * GSS-API.
 */

OM_uint32
__dh_gss_wrap_size_limit(void *ctx, /* Mechanism context (not used) */
			OM_uint32 *minor, /* Mechanism status */
			gss_ctx_id_t context, /* GSS context handle */
			int conf_req, /* True if confidentiality is wanted */
			gss_qop_t qop_req, /* Requested QOP */
			OM_uint32 output_size, /* The maximum ouput size */
			OM_uint32 *input_size /* Input size returned */)
{
_NOTE(ARGUNUSED(ctx))
	OM_uint32 major, stat = GSS_S_COMPLETE;
	unsigned int msgsize, sigsize, pad = 1, size;
	dh_token_desc token;
	dh_wrap_t wrap = &token.ver.dh_version_u.body.dh_token_body_desc_u.seal;
	OM_uint32 left;

	if (input_size == 0)
		stat = GSS_S_CALL_INACCESSIBLE_WRITE;

	/* We check for valid unexpired context by calling gss_context_time. */
	if ((major = stat | __dh_gss_context_time(ctx, minor, context, &left))
	    != GSS_S_COMPLETE)
		return (major | stat);

	/* Find the signature size for this qop. */
	if ((*minor = __get_sig_size(qop_req, &sigsize)) != DH_SUCCESS)
		return (GSS_S_BAD_QOP | stat);

	/* Just return if we can't give the caller what he ask for. */
	if (stat)
		return (stat);

	/*
	 * If we requested confidentiality, get the cipher pad for the
	 * requested qop. Since we can't support privacy the cipher pad
	 * is always 1.
	 */
	if (conf_req)
		pad = 1;

	/*
	 * Set up an empty wrap token to calculate header and signature
	 * overhead.
	 */

	token.ver.verno = DH_PROTO_VERSION;
	token.ver.dh_version_u.body.type = DH_WRAP;
	wrap->mic.qop = qop_req;
	wrap->mic.seqnum = 0;
	wrap->mic.client_flag = 0;
	wrap->body.body_len = 0;
	wrap->body.body_val = 0;
	token.verifier.dh_signature_len = sigsize;
	token.verifier.dh_signature_val = 0;

	/* This is the size of an empy wrap token */
	size =  xdr_sizeof((xdrproc_t)xdr_dh_token_desc, (void *)&token);

	/* This is the amount of space left to put our message. */
	msgsize = (output_size > size) ? output_size - size : 0;

	/* XDR needs to pad to a four byte boundry */
	msgsize = (msgsize / 4) * 4;

	/* We need to pad to pad bytes for encryption (=1 if conf_req = 0) */
	msgsize = (msgsize / pad) * pad;

	/*
	 * The serialization of the inner message includes
	 * the original length.
	 */

	msgsize = (msgsize > sizeof (u_int)) ? msgsize - sizeof (u_int) : 0;

	/*
	 * We now have the space for the inner wrap message, which is also
	 * XDR encoded and is padded to a four byte boundry.
	 */

	msgsize = (msgsize / 4) * 4;

	*input_size = msgsize;

	return (GSS_S_COMPLETE);
}
