/*
 * Copyright 2008-2010 by the Massachusetts Institute of Technology.
 * All Rights Reserved.
 *
 * Export of this software from the United States of America may
 *   require a specific license from the United States Government.
 *   It is the responsibility of any person or organization contemplating
 *   export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 * distribute this software and its documentation for any purpose and
 * without fee is hereby granted, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and
 * this permission notice appear in supporting documentation, and that
 * the name of M.I.T. not be used in advertising or publicity pertaining
 * to distribution of the software without specific, written prior
 * permission.  Furthermore if you modify this software you must label
 * your software as modified software and not distribute it in such a
 * fashion that it might be confused with the original M.I.T. software.
 * M.I.T. makes no representations about the suitability of
 * this software for any purpose.  It is provided "as is" without express
 * or implied warranty.
 */

/* Glue routine for gssspi_set_cred_option */

#include "mglueP.h"
#include <stdio.h>
#ifdef HAVE_STDLIB_H
#include <stdlib.h>
#endif
#include <string.h>
#include <time.h>

static OM_uint32
alloc_union_cred(OM_uint32 *minor_status,
		 gss_mechanism mech,
		 gss_cred_id_t mech_cred,
		 gss_union_cred_t *pcred)
{
    OM_uint32		status;
    OM_uint32		temp_minor_status;
    gss_union_cred_t	cred = NULL;

    *pcred = NULL;

    status = GSS_S_FAILURE;

    cred = calloc(1, sizeof(*cred));
    if (cred == NULL) {
	*minor_status = ENOMEM;
	goto cleanup;
    }

    cred->loopback = cred;
    cred->count = 1;

    cred->cred_array = calloc(cred->count, sizeof(gss_cred_id_t));
    if (cred->cred_array == NULL) {
	*minor_status = ENOMEM;
	goto cleanup;
    }
    cred->cred_array[0] = mech_cred;

    status = generic_gss_copy_oid(minor_status,
                                  &mech->mech_type,
                                  &cred->mechs_array);
    if (status != GSS_S_COMPLETE)
        goto cleanup;

    status = GSS_S_COMPLETE;
    *pcred = cred;

cleanup:
    if (status != GSS_S_COMPLETE)
	gss_release_cred(&temp_minor_status, (gss_cred_id_t *)&cred);

    return status;
}

/*
 * This differs from gssspi_set_cred_option() as shipped in 1.7, in that
 * it can return a cred handle. To denote this change we have changed the
 * name of the function from gssspi_set_cred_option() to gss_set_cred_option().
 * However, the dlsym() entry point is still gssspi_set_cred_option(). This
 * fixes a separate issue, namely that a dynamically loaded mechanism could
 * not itself call set_cred_option() without calling its own implementation
 * instead of the mechanism glue's. (This is useful where a mechanism wishes
 * to export a mechanism-specific API that is a wrapper around this function.)
 */
OM_uint32 KRB5_CALLCONV
gss_set_cred_option(OM_uint32 *minor_status,
	            gss_cred_id_t *cred_handle,
	            const gss_OID desired_object,
	            const gss_buffer_t value)
{
    gss_union_cred_t	union_cred;
    gss_mechanism	mech;
    int			i;
    OM_uint32		status;
    OM_uint32		mech_status;
    OM_uint32		mech_minor_status;

    if (minor_status == NULL)
	return GSS_S_CALL_INACCESSIBLE_WRITE;
    *minor_status = 0;

    if (cred_handle == NULL)
	return GSS_S_CALL_INACCESSIBLE_WRITE;

    status = GSS_S_UNAVAILABLE;

    if (*cred_handle == GSS_C_NO_CREDENTIAL) {
	gss_cred_id_t mech_cred = GSS_C_NO_CREDENTIAL;

	/*
	 * We need to give a mechanism the opportunity to allocate a
	 * credentials handle. Unfortunately this does mean that only
	 * the default mechanism can allocate a credentials handle.
	 */
        mech = gssint_get_mechanism(NULL);
        if (mech == NULL)
            return GSS_S_BAD_MECH;

	if (mech->gssspi_set_cred_option == NULL)
	    return GSS_S_UNAVAILABLE;

	status = mech->gssspi_set_cred_option(minor_status,
					      &mech_cred,
					      desired_object,
					      value);
	if (status != GSS_S_COMPLETE) {
	    map_error(minor_status, mech);
	    return status;
	}

	if (mech_cred != GSS_C_NO_CREDENTIAL) {
	    status = alloc_union_cred(minor_status,
				      mech,
				      mech_cred,
				      &union_cred);
	    if (status != GSS_S_COMPLETE)
		return status;
	    *cred_handle = (gss_cred_id_t)union_cred;
	}
    } else {
	union_cred = (gss_union_cred_t)*cred_handle;

	for (i = 0; i < union_cred->count; i++) {
	    mech = gssint_get_mechanism(&union_cred->mechs_array[i]);
	    if (mech == NULL) {
		status = GSS_S_BAD_MECH;
		break;
	    }

	    if (mech->gssspi_set_cred_option == NULL)
		continue;

	    mech_status = mech->gssspi_set_cred_option(&mech_minor_status,
						       &union_cred->cred_array[i],
						       desired_object,
						       value);
	    if (mech_status == GSS_S_UNAVAILABLE)
		continue;
	    else {
		status = mech_status;
		*minor_status = mech_minor_status;
	    }
	    if (status != GSS_S_COMPLETE) {
		map_error(minor_status, mech);
		break;
	    }
	}
    }

    return status;
}

/*
 * Provide this for backward ABI compatibility, but remove it from the
 * header.
 */
OM_uint32 KRB5_CALLCONV
gssspi_set_cred_option(OM_uint32 *minor_status,
	               gss_cred_id_t cred,
	               const gss_OID desired_object,
	               const gss_buffer_t value);

OM_uint32 KRB5_CALLCONV
gssspi_set_cred_option(OM_uint32 *minor_status,
	               gss_cred_id_t cred,
	               const gss_OID desired_object,
	               const gss_buffer_t value)
{
    return gss_set_cred_option(minor_status, &cred,
                               desired_object, value);
}
