/* Copyright (C) 2011 the GSS-PROXY contributors, see COPYING for license */

#include "gssapi_gpm.h"
#include <pthread.h>

static pthread_key_t gpm_last_status;

static void gpm_destroy_last_status(void *arg)
{
    gssx_status *status = (gssx_status *)arg;
    xdr_free((xdrproc_t)xdr_gssx_status, (char *)status);
    free(status);
}

void gpm_display_status_init_once(void)
{
    (void)pthread_key_create(&gpm_last_status, gpm_destroy_last_status);
}

/* Portable thread local storage for return status. */
void gpm_save_status(gssx_status *status)
{
    gssx_status *last_status;
    int ret;

    last_status = (gssx_status *)pthread_getspecific(gpm_last_status);
    if (last_status != NULL) {
        /* store NULL first so we do not risk a double free if we are
         * racing on a pthread_cancel */
        pthread_setspecific(gpm_last_status, NULL);
        gpm_destroy_last_status(last_status);
    }

    ret = gp_copy_gssx_status_alloc(status, &last_status);
    if (ret == 0) {
        pthread_setspecific(gpm_last_status, last_status);
    }
}

gssx_status *gpm_get_saved_status(void)
{
    return (gssx_status *)pthread_getspecific(gpm_last_status);
}

/* This funciton is used to record internal mech errors that are
 * generated by the proxy client code */
void gpm_save_internal_status(uint32_t err, char *err_str)
{
    gssx_status status;

    memset(&status, 0, sizeof(gssx_status));

#define STD_MAJ_ERROR_STR "Internal gssproxy error"
    status.major_status = GSS_S_FAILURE;
    status.major_status_string.utf8string_val = strdup(STD_MAJ_ERROR_STR);
    status.major_status_string.utf8string_len = sizeof(STD_MAJ_ERROR_STR);
    status.minor_status = err;
    status.minor_status_string.utf8string_val = err_str;
    status.minor_status_string.utf8string_len = strlen(err_str) + 1;
    gpm_save_status(&status);
}

OM_uint32 gpm_display_status(OM_uint32 *minor_status,
                             OM_uint32 status_value,
                             int status_type,
                             const gss_OID mech_type UNUSED,
                             OM_uint32 *message_context,
                             gss_buffer_t status_string)
{
    gssx_status *last_status = gpm_get_saved_status();
    utf8string tmp;
    int ret;

    switch(status_type) {
    case GSS_C_GSS_CODE:
        if (last_status &&
            last_status->major_status == status_value &&
            last_status->major_status_string.utf8string_len) {
                ret = gp_copy_utf8string(&last_status->major_status_string,
                                         &tmp);
                if (ret) {
                    *minor_status = ret;
                    return GSS_S_FAILURE;
                }
                status_string->value = tmp.utf8string_val;
                status_string->length = tmp.utf8string_len;
                *minor_status = 0;
                return GSS_S_COMPLETE;
        } else {
            /* if we do not have it, make it clear */
            return GSS_S_UNAVAILABLE;
        }
    case GSS_C_MECH_CODE:
        if (last_status &&
            last_status->minor_status == status_value &&
            last_status->minor_status_string.utf8string_len) {

            if (*message_context) {
                /* we do not support multiple messages for now */
                *minor_status = EINVAL;
                return GSS_S_FAILURE;
            }

            ret = gp_copy_utf8string(&last_status->minor_status_string,
                                     &tmp);
            if (ret) {
                *minor_status = ret;
                return GSS_S_FAILURE;
            }
            status_string->value = tmp.utf8string_val;
            status_string->length = tmp.utf8string_len;
        } else {
            /* if we do not have it, make it clear */
            return GSS_S_UNAVAILABLE;
        }
        *minor_status = 0;
        return GSS_S_COMPLETE;
    default:
        *minor_status = EINVAL;
        return GSS_S_BAD_STATUS;
    }
}
