/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* plugins/audit/kdc_j_encode.c - Utilities to json encode KDC audit stuff */
/*
 * Copyright (C) 2013 by the Massachusetts Institute of Technology.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in
 *   the documentation and/or other materials provided with the
 *   distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <k5-int.h>
#include <k5-json.h>
#include "kdc_j_encode.h"
#include "j_dict.h"
#include <krb5/audit_plugin.h>
#include <syslog.h>

static krb5_error_code
string_to_value(const char *in, k5_json_object obj, const char *key);
static krb5_error_code
princ_to_value(krb5_principal princ, k5_json_object obj, const char *key);
static krb5_error_code
data_to_value(krb5_data *data, k5_json_object obj, const char *key);
static krb5_error_code
int32_to_value(krb5_int32 int32, k5_json_object obj, const char *key);
static krb5_error_code
bool_to_value(krb5_boolean b, k5_json_object obj, const char *key);
static krb5_error_code
addr_to_obj(krb5_address *a, k5_json_object obj);
static krb5_error_code
eventinfo_to_value(k5_json_object obj, const char *name,
                   const int stage, const krb5_boolean ev_success);
static krb5_error_code
addr_to_value(const krb5_address *address, k5_json_object obj,
              const char *key);
static krb5_error_code
req_to_value(krb5_kdc_req *req, const krb5_boolean ev_success,
             k5_json_object obj);
static krb5_error_code
rep_to_value(krb5_kdc_rep *rep, const krb5_boolean ev_success,
             k5_json_object obj);
static krb5_error_code
tkt_to_value(krb5_ticket *tkt, k5_json_object obj, const char *key);
static char *map_patype(krb5_preauthtype pa_type);

#define NULL_STATE "state is NULL"
#define T_RENEWED 1
#define T_NOT_RENEWED 2
#define T_VALIDATED 1
#define T_NOT_VALIDATED 2

/* KDC server STOP. Returns 0 on success. */
krb5_error_code
kau_j_kdc_stop(const krb5_boolean ev_success, char **jout)
{
    krb5_error_code ret = 0;
    k5_json_object obj = NULL;

    *jout = NULL;

    /* Main object. */
    if (k5_json_object_create(&obj))
        return ENOMEM;

    /* Audit event_ID and ev_success. */
    ret = string_to_value("KDC_STOP", obj, AU_EVENT_NAME);
    if (!ret)
        ret = bool_to_value(ev_success, obj, AU_EVENT_STATUS);
    if (!ret)
        ret = k5_json_encode(obj, jout);
    k5_json_release(obj);

    return ret;
}

/* KDC server START. Returns 0 on success. */
krb5_error_code
kau_j_kdc_start(const krb5_boolean ev_success, char **jout)
{
    krb5_error_code ret = 0;
    k5_json_object obj = NULL;

    *jout = NULL;

    /* Main object. */
    if (k5_json_object_create(&obj))
        return ENOMEM;

    /* Audit event_ID and ev_success. */
    ret = string_to_value("KDC_START", obj, AU_EVENT_NAME);
    if (!ret)
        ret = bool_to_value(ev_success, obj, AU_EVENT_STATUS);
    if (!ret)
        ret = k5_json_encode(obj, jout);
    k5_json_release(obj);

    return ret;
}

/* AS-REQ. Returns 0 on success. */
krb5_error_code
kau_j_as_req(const krb5_boolean ev_success, krb5_audit_state *state,
             char **jout)
{
    krb5_error_code ret = 0;
    k5_json_object obj = NULL;

    *jout = NULL;

    if (!state) {
        *jout = NULL_STATE;
        return 0;
    }

    /* Main object. */
    if (k5_json_object_create(&obj))
        return ENOMEM;
    /* Audit event_ID and ev_success. */
    ret = eventinfo_to_value(obj, "AS_REQ", state->stage, ev_success);
    if (ret)
        goto error;
    /* TGT ticket ID */
    ret = string_to_value(state->tkt_out_id, obj, AU_TKT_OUT_ID);
    if (ret)
        goto error;
    /* Request ID. */
    ret = string_to_value(state->req_id, obj, AU_REQ_ID);
    if (ret)
        goto error;
    /* Client's port and address. */
    ret = int32_to_value(state->cl_port, obj, AU_FROMPORT);
    if (ret)
        goto error;
    ret = addr_to_value(state->cl_addr, obj, AU_FROMADDR);
    if (ret)
        goto error;
    /* KDC status msg */
    ret = string_to_value(state->status, obj, AU_KDC_STATUS);
    if (ret)
        goto error;
    /* non-local client's referral realm. */
    ret = data_to_value(state->cl_realm, obj, AU_CREF_REALM);
    if (ret)
        goto error;
    /* Request. */
    ret = req_to_value(state->request, ev_success, obj);
    if (ret == ENOMEM)
        goto error;
    /* Reply/ticket info. */
    ret = rep_to_value(state->reply, ev_success, obj);
    if (ret == ENOMEM)
        goto error;
    ret = k5_json_encode(obj, jout);

error:
    k5_json_release(obj);
    return ret;
}

/* TGS-REQ. Returns 0 on success. */
krb5_error_code
kau_j_tgs_req(const krb5_boolean ev_success, krb5_audit_state *state,
              char **jout)
{
    krb5_error_code ret = 0;
    k5_json_object obj = NULL;
    krb5_kdc_req *req = state->request;
    int tkt_validated = 0, tkt_renewed = 0;

    *jout = NULL;

    if (!state) {
        *jout = NULL_STATE;
        return 0;
    }

    /* Main object. */
    if (k5_json_object_create(&obj))
        return ENOMEM;

    /* Audit Event ID and ev_success. */
    ret = eventinfo_to_value(obj, "TGS_REQ", state->stage, ev_success);
    if (ret)
        goto error;
    /* Primary and derived ticket IDs. */
    ret = string_to_value(state->tkt_in_id, obj, AU_TKT_IN_ID);
    if (ret)
        goto error;
    ret = string_to_value(state->tkt_out_id, obj, AU_TKT_OUT_ID);
    if (ret)
        goto error;
    /* Request ID */
    ret = string_to_value(state->req_id, obj, AU_REQ_ID);
    if (ret)
        goto error;
    /* client’s address and port. */
    ret = int32_to_value(state->cl_port, obj, AU_FROMPORT);
    if (ret)
        goto error;
    ret = addr_to_value(state->cl_addr, obj, AU_FROMADDR);
    if (ret)
        goto error;
    /* Ticket was renewed, validated. */
    if ((ev_success == TRUE) && (req != NULL)) {
        tkt_renewed = (req->kdc_options & KDC_OPT_RENEW) ?
                      T_RENEWED : T_NOT_RENEWED;
        tkt_validated = (req->kdc_options & KDC_OPT_VALIDATE) ?
                      T_VALIDATED : T_NOT_VALIDATED;
    }
    ret = int32_to_value(tkt_renewed, obj, AU_TKT_RENEWED);
    if (ret)
        goto error;
    ret = int32_to_value(tkt_validated, obj, AU_TKT_VALIDATED);
    if (ret)
        goto error;
    /* KDC status msg, including "ISSUE". */
    ret = string_to_value(state->status, obj, AU_KDC_STATUS);
    if (ret)
        goto error;
    /* request */
    ret = req_to_value(req, ev_success, obj);
    if (ret == ENOMEM)
        goto error;
    /* reply/ticket */
    ret = rep_to_value(state->reply, ev_success, obj);
    if (ret == ENOMEM)
        goto error;
    ret = k5_json_encode(obj, jout);

error:
    k5_json_release(obj);
    return ret;
}

/* S4U2Self protocol extension. Returns 0 on success. */
krb5_error_code
kau_j_tgs_s4u2self(const krb5_boolean ev_success, krb5_audit_state *state,
                   char **jout)
{
    krb5_error_code ret = 0;
    k5_json_object obj = NULL;

    *jout = NULL;

    if (!state) {
        *jout = NULL_STATE;
        return 0;
    }

    /* Main object. */
    if (k5_json_object_create(&obj))
        return ENOMEM;

    /* Audit Event ID and ev_success. */
    ret = eventinfo_to_value(obj, "S4U2SELF", state->stage, ev_success);
    if (ret)
        goto error;
    /* Front-end server's TGT ticket ID. */
    ret = string_to_value(state->tkt_in_id, obj, AU_TKT_IN_ID);
    if (ret)
        goto error;
    /* service "to self" ticket or referral TGT ticket ID. */
    ret = string_to_value(state->tkt_out_id, obj, AU_TKT_OUT_ID);
    if (ret)
        goto error;
    /* Request ID. */
    ret = string_to_value(state->req_id, obj, AU_REQ_ID);
    if (ret)
        goto error;
    if (ev_success == FALSE) {
        /* KDC status msg. */
        ret = string_to_value(state->status, obj, AU_KDC_STATUS);
        if (ret)
            goto error;
        /* Local policy or S4U protocol constraints. */
        ret = int32_to_value(state->violation, obj, AU_VIOLATION);
        if (ret)
            goto error;
    }
    /* Impersonated user. */
    ret = princ_to_value(state->s4u2self_user, obj, AU_REQ_S4U2S_USER);
    if (ret)
        goto error;

    ret = k5_json_encode(obj, jout);

error:
    k5_json_release(obj);
    return ret;
}

/* S4U2Proxy protocol extension. Returns 0 on success. */
krb5_error_code
kau_j_tgs_s4u2proxy(const krb5_boolean ev_success, krb5_audit_state *state,
                    char **jout)
{
    krb5_error_code ret = 0;
    k5_json_object obj = NULL;
    krb5_kdc_req *req = state->request;

    *jout = NULL;

    if (!state) {
        *jout = NULL_STATE;
        return 0;
    }

    /* Main object. */
    if (k5_json_object_create(&obj))
        return ENOMEM;

    /* Audit Event ID and ev_success. */
    ret = eventinfo_to_value(obj, "S4U2PROXY", state->stage, ev_success);
    if (ret)
        goto error;
    /* Front-end server's TGT ticket ID. */
    ret = string_to_value(state->tkt_in_id, obj, AU_TKT_IN_ID);
    if (ret)
        goto error;
    /* Resource service or referral TGT ticket ID. */
    ret = string_to_value(state->tkt_out_id, obj, AU_TKT_OUT_ID);
    if (ret)
        goto error;
    /* User's evidence ticket ID. */
    ret = string_to_value(state->evid_tkt_id, obj, AU_EVIDENCE_TKT_ID);
    if (ret)
        goto error;
    /* Request ID. */
    ret = string_to_value(state->req_id, obj, AU_REQ_ID);
    if (ret)
        goto error;

    if (ev_success == FALSE) {
        /* KDC status msg. */
        ret = string_to_value(state->status, obj, AU_KDC_STATUS);
        if (ret)
            goto error;
        /* Local policy or S4U protocol constraints. */
        ret = int32_to_value(state->violation, obj, AU_VIOLATION);
        if (ret)
            goto error;
    }
    /* Delegated user. */
    if (req != NULL) {
        ret = princ_to_value(req->second_ticket[0]->enc_part2->client,
                             obj, AU_REQ_S4U2P_USER);
        if (ret)
            goto error;
    }
    ret = k5_json_encode(obj, jout);

error:
    k5_json_release(obj);
    return ret;
}

/* U2U. Returns 0 on success. */
krb5_error_code
kau_j_tgs_u2u(const krb5_boolean ev_success, krb5_audit_state *state,
              char **jout)
{
    krb5_error_code ret = 0;
    k5_json_object obj = NULL;
    krb5_kdc_req *req = state->request;

    if (!state) {
        *jout = NULL_STATE;
        return 0;
    }

    *jout = NULL;

    /* Main object. */
    if (k5_json_object_create(&obj))
        return ENOMEM;
    /* Audit Event ID and ev_success. */
    ret = eventinfo_to_value(obj, "U2U", state->stage, ev_success);
    if (ret)
        goto error;
    /* Front-end server's TGT ticket ID. */
    ret = string_to_value(state->tkt_in_id, obj, AU_TKT_IN_ID);
    if (ret)
        goto error;
    /* Service ticket ID. */
    ret = string_to_value(state->tkt_out_id, obj, AU_TKT_OUT_ID);
    if (ret)
        goto error;
    /* Request ID. */
    ret = string_to_value(state->req_id, obj, AU_REQ_ID);
    if (ret)
        goto error;

    if (ev_success == FALSE) {
        /* KDC status msg. */
        ret = string_to_value(state->status, obj, AU_KDC_STATUS);
        if (ret)
            goto error;
    }
    /* Client in the second ticket. */
    if (req != NULL) {
        ret = princ_to_value(req->second_ticket[0]->enc_part2->client,
                             obj, AU_REQ_U2U_USER);
        if (ret)
            goto error;
    }
    /* Enctype of a session key of the second ticket. */
    ret = int32_to_value(req->second_ticket[0]->enc_part2->session->enctype,
                         obj, AU_SRV_ETYPE);
    if (ret)
        goto error;

    ret = k5_json_encode(obj, jout);

error:
    k5_json_release(obj);
    return ret;
}

/* Low level utilities */

/* Converts string into a property of a JSON object. Returns 0 on success.*/
static krb5_error_code
string_to_value(const char *in, k5_json_object obj, const char *key)
{
    krb5_error_code ret = 0;
    k5_json_string str = NULL;

    if (in == NULL)
        return 0;

    ret = k5_json_string_create(in, &str);
    if (ret)
        return ret;
    ret = k5_json_object_set(obj, key, str);
    k5_json_release(str);

    return ret;
}

/*
 * Converts a krb5_data struct into a property of a JSON object.
 * (Borrowed from preauth_otp.c)
 * Returns 0 on success.
 */
static krb5_error_code
data_to_value(krb5_data *data, k5_json_object obj, const char *key)
{
    krb5_error_code ret = 0;
    k5_json_string str = NULL;

    if (data == NULL || data->data == NULL || data->length < 1)
        return 0;

    ret = k5_json_string_create_len(data->data, data->length, &str);
    if (ret)
        return ret;
    ret = k5_json_object_set(obj, key, str);
    k5_json_release(str);

    return ret;
}

/*
 * Converts krb5_int32 into a property of a JSON object.
 * Returns 0 on success.
 */
static krb5_error_code
int32_to_value(krb5_int32 int32, k5_json_object obj, const char *key)
{
    krb5_error_code ret = 0;
    k5_json_number num = NULL;

    ret = k5_json_number_create(int32, &num);
    if (ret)
        return ENOMEM;
    ret = k5_json_object_set(obj, key, num);
    k5_json_release(num);

    return ret;
}

/*
 * Converts krb5_boolean into a property of a JSON object.
 * Returns 0 on success.
 */
static krb5_error_code
bool_to_value(krb5_boolean in, k5_json_object obj, const char *key)
{
    krb5_error_code ret = 0;
    k5_json_bool b = 0;

    ret = k5_json_bool_create(in, &b);
    if (ret)
        return ENOMEM;

    ret = k5_json_object_set(obj, key, b);
    k5_json_release(b);

    return ret;
}

/* Wrapper-level utilities */

/* Wrapper for stage and event_status tags. Returns 0 on success. */
static krb5_error_code
eventinfo_to_value(k5_json_object obj, const char *name,
                   const int stage, const krb5_boolean ev_success)
{
    krb5_error_code ret = 0;

    ret = string_to_value(name, obj, AU_EVENT_NAME);
    if (ret)
        return ret;
    ret = int32_to_value(stage, obj, AU_STAGE);
    if (!ret)
        ret = bool_to_value(ev_success, obj, AU_EVENT_STATUS);

    return ret;
}

/*
 * Converts krb5_principal into a property of a JSON object.
 * Returns 0 on success.
 */
static krb5_error_code
princ_to_value(krb5_principal princ, k5_json_object obj, const char *key)
{
    krb5_error_code ret = 0;
    k5_json_object tmp = NULL;
    k5_json_array arr = NULL;
    k5_json_string str = NULL;
    int i = 0;

    if (princ == NULL || princ->data == NULL)
        return 0;

    /* Main object. */
    if (k5_json_object_create(&tmp))
        return ENOMEM;

    ret = k5_json_array_create(&arr);
    if (ret)
        goto error;
    for (i = 0; i < princ->length; i++) {
        ret = k5_json_string_create_len((&princ->data[i])->data,
                                       (&princ->data[i])->length, &str);
        if (ret)
            goto error;
        ret = k5_json_array_add(arr, str);
        k5_json_release(str);
        if (ret)
            goto error;
    }
    ret = k5_json_object_set(tmp, AU_COMPONENTS, arr);
    if (ret)
        goto error;
    ret = data_to_value(&princ->realm, tmp, AU_REALM);
    if (ret)
        goto error;
    ret = int32_to_value(princ->length, tmp, AU_LENGTH);
    if (ret)
        goto error;
    ret = int32_to_value(princ->type, tmp, AU_TYPE);
    if (ret)
        goto error;

    ret = k5_json_object_set(obj, key, tmp);

error:
    k5_json_release(tmp);
    k5_json_release(arr);
    return ret;
}

/*
 * Helper for JSON encoding of krb5_address.
 * Returns 0 on success.
 */
static krb5_error_code
addr_to_obj(krb5_address *a, k5_json_object obj)
{
    krb5_error_code ret = 0;
    k5_json_number num = NULL;
    k5_json_array arr = NULL;
    int i;

    if (a == NULL || a->contents == NULL || a->length <= 0)
        return 0;

    ret = int32_to_value(a->addrtype, obj, AU_TYPE);
    if (ret)
        goto error;
    ret = int32_to_value(a->length, obj, AU_LENGTH);
    if (ret)
        goto error;

    if (a->addrtype == ADDRTYPE_INET || a->addrtype == ADDRTYPE_INET6) {
        ret = k5_json_array_create(&arr);
        if (ret)
            goto error;
        for (i = 0; i < (int)a->length; i++) {
            ret = k5_json_number_create(a->contents[i], &num);
            if (ret)
                goto error;
            ret = k5_json_array_add(arr, num);
            k5_json_release(num);
            if (ret)
                goto error;
        }
        ret = k5_json_object_set(obj, AU_IP, arr);
        if (ret)
            goto error;
    }

error:
    k5_json_release(arr);
    return ret;
}

/*
 * Converts krb5_fulladdr into a property of a JSON object.
 * Returns 0 on success.
 */
static krb5_error_code
addr_to_value(const krb5_address *address, k5_json_object obj, const char *key)
{
    krb5_error_code ret = 0;
    k5_json_object addr_obj = NULL;

    if (address == NULL)
        return 0;

    ret = k5_json_object_create(&addr_obj);
    if (ret)
        return ret;
    ret = addr_to_obj((krb5_address *)address, addr_obj);
    if (!ret)
        ret = k5_json_object_set(obj, key, addr_obj);
    k5_json_release(addr_obj);

    return ret;
}

/*
 * Helper for JSON encoding of krb5_kdc_req.
 * Returns 0 on success.
 */
static krb5_error_code
req_to_value(krb5_kdc_req *req, const krb5_boolean ev_success,
             k5_json_object obj)
{
    krb5_error_code ret = 0;
    k5_json_number num = NULL;
    k5_json_string str = NULL;
    k5_json_object tmpa = NULL;
    k5_json_array arr = NULL, arra = NULL, arrpa = NULL;
    krb5_pa_data **padata;
    int i = 0;

    if (req == NULL)
        return 0;

    ret = princ_to_value(req->client, obj, AU_REQ_CLIENT);
    if (ret)
        goto error;
    ret = princ_to_value(req->server, obj, AU_REQ_SERVER);
    if (ret)
        goto error;

    ret = int32_to_value(req->kdc_options, obj, AU_REQ_KDC_OPTIONS);
        if (ret)
            goto error;
    ret = int32_to_value(req->from, obj, AU_REQ_TKT_START);
        if (ret)
            goto error;
    ret = int32_to_value(req->till, obj, AU_REQ_TKT_END);
        if (ret)
            goto error;
    ret = int32_to_value(req->rtime, obj, AU_REQ_TKT_RENEW_TILL);
        if (ret)
            goto error;
    /* Available/requested enctypes. */
    ret = k5_json_array_create(&arr);
    if (ret)
        goto error;
    for (i = 0; (i < req->nktypes); i++) {
        if (req->ktype[i] > 0) {
            ret = k5_json_number_create(req->ktype[i], &num);
            if (ret)
                goto error;
            ret = k5_json_array_add(arr, num);
            k5_json_release(num);
            if (ret)
                goto error;
        }
    }
    ret = k5_json_object_set(obj, AU_REQ_AVAIL_ETYPES, arr);
    if (ret)
        goto error;
    /* Pre-auth types. */
    if (ev_success == TRUE && req->padata) {
            ret = k5_json_array_create(&arrpa);
            if (ret)
                goto error;
            for (padata = req->padata; *padata; padata++) {
                if (strlen(map_patype((*padata)->pa_type)) > 1) {
                    ret = k5_json_string_create(map_patype((*padata)->pa_type),
                                                &str);
                    if (ret)
                        goto error;
                    ret = k5_json_array_add(arrpa, str);
                    k5_json_release(str);
                    if (ret)
                        goto error;
                }
            }
            ret = k5_json_object_set(obj, AU_REQ_PA_TYPE, arrpa);
    }
    /* List of requested addresses. */
    if (req->addresses) {
        ret = k5_json_array_create(&arra);
        if (ret)
                goto error;
        for (i = 0; req->addresses[i] != NULL; i++) {
            ret = k5_json_object_create(&tmpa);
            if (ret)
                goto error;
            ret = addr_to_obj(req->addresses[i], tmpa);
            if (ret)
                goto error;
            ret = k5_json_array_add(arra, tmpa);
            k5_json_release(tmpa);
            if (ret)
                goto error;
        }
        ret = k5_json_object_set(obj, AU_REQ_ADDRESSES, arra);
        if (ret)
            goto error;
    }
error:
    k5_json_release(arr);
    k5_json_release(arra);
    k5_json_release(arrpa);
    return ret;
}

/*
 * Helper for JSON encoding of krb5_kdc_rep.
 * Returns 0 on success.
 */
static krb5_error_code
rep_to_value(krb5_kdc_rep *rep, const krb5_boolean ev_success,
             k5_json_object obj)
{
    krb5_error_code ret = 0;
    krb5_pa_data **padata;
    k5_json_array arrpa = NULL;
    k5_json_string str = NULL;

    if (rep == NULL)
        return 0;

    if (ev_success == TRUE) {
        ret = tkt_to_value(rep->ticket, obj, AU_REP_TICKET);
        /* Enctype of the reply-encrypting key. */
        ret = int32_to_value(rep->enc_part.enctype, obj, AU_REP_ETYPE);
        if (ret)
            goto error;
    } else {

        if (rep->padata) {
            ret = k5_json_array_create(&arrpa);
            if (ret)
                goto error;
            for (padata = rep->padata; *padata; padata++) {
                if (strlen(map_patype((*padata)->pa_type)) > 1) {
                    ret = k5_json_string_create(map_patype((*padata)->pa_type),
                                                          &str);
                    if (ret)
                        goto error;
                    ret = k5_json_array_add(arrpa, str);
                    k5_json_release(str);
                    if (ret)
                        goto error;
                }
            }
        }
        ret = k5_json_object_set(obj, AU_REP_PA_TYPE, arrpa);
    }
error:
    k5_json_release(arrpa);
    return ret;
}

/*
 * Converts krb5_ticket into a property of a JSON object.
 * Returns 0 on success.
 */
static krb5_error_code
tkt_to_value(krb5_ticket *tkt, k5_json_object obj,
              const char *key)
{
    krb5_error_code ret = 0;
    k5_json_object tmp = NULL;
    krb5_enc_tkt_part *part2 = NULL;

    if (tkt == NULL)
        return 0;

    /* Main object. */
    if (k5_json_object_create(&tmp))
        return ENOMEM;

    /*
     * CNAME - potentially redundant data...
     * ...but it is part of the ticket. So, record it as such.
     */
    ret = princ_to_value(tkt->server, tmp, AU_CNAME);
    if (ret)
        goto error;
    ret = princ_to_value(tkt->server, tmp, AU_SNAME);
    if (ret)
        goto error;
    /* Enctype of a long-term key of service. */
    if (tkt->enc_part.enctype)
        ret = int32_to_value(tkt->enc_part.enctype, tmp, AU_SRV_ETYPE);
    if (ret)
        goto error;
    if (tkt->enc_part2)
        part2 = tkt->enc_part2;
    if (part2) {
        ret = princ_to_value(part2->client, tmp, AU_CNAME);
        if (ret)
            goto error;
        ret = int32_to_value(part2->flags, tmp, AU_FLAGS);
        if (ret)
            goto error;
        /* Chosen by KDC session key enctype (short-term key). */
        ret = int32_to_value(part2->session->enctype, tmp, AU_SESS_ETYPE);
        if (ret)
            goto error;
        ret = int32_to_value(part2->times.starttime, tmp, AU_START);
        if (ret)
            goto error;
        ret = int32_to_value(part2->times.endtime, tmp, AU_END);
        if (ret)
            goto error;
        ret = int32_to_value(part2->times.renew_till, tmp, AU_RENEW_TILL);
        if (ret)
            goto error;
        ret = int32_to_value(part2->times.authtime, tmp, AU_AUTHTIME);
        if (ret)
            goto error;
        if (part2->transited.tr_contents.length > 0) {
            ret = data_to_value(&part2->transited.tr_contents,
                               tmp, AU_TR_CONTENTS);
            if (ret)
                goto error;
        }
    } /* part2 != NULL */

    if (!ret)
        ret = k5_json_object_set(obj, key, tmp);

error:
    k5_json_release(tmp);
    return ret;
}

/* Map preauth numeric type to the naming string. */
struct _patype_str {
    krb5_preauthtype id;
    char *name;
};
struct _patype_str  patype_str[] = {
    {KRB5_PADATA_ENC_TIMESTAMP, "ENC_TIMESTAMP"},
    {KRB5_PADATA_PW_SALT, "PW_SALT"},
    {KRB5_PADATA_ENC_UNIX_TIME, "ENC_UNIX_TIME"},
    {KRB5_PADATA_SAM_CHALLENGE, "SAM_CHALLENGE"},
    {KRB5_PADATA_SAM_RESPONSE, "SAM_RESPONSE"},
    {KRB5_PADATA_PK_AS_REQ_OLD, "PK_AS_REQ_OLD"},
    {KRB5_PADATA_PK_AS_REP_OLD, "PK_AS_REP_OLD"},
    {KRB5_PADATA_PK_AS_REQ, "PK_AS_REQ"},
    {KRB5_PADATA_PK_AS_REP, "PK_AS_REP"},
    {KRB5_PADATA_ETYPE_INFO2, "ETYPE_INFO2"},
    {KRB5_PADATA_SAM_CHALLENGE_2, "SAM_CHALLENGE_2"},
    {KRB5_PADATA_SAM_RESPONSE_2, "SAM_RESPONSE_2"},
    {KRB5_PADATA_PAC_REQUEST, "PAC_REQUEST"},
    {KRB5_PADATA_FOR_USER, "FOR_USER"},
    {KRB5_PADATA_S4U_X509_USER, "S4U_X509_USER"},
    {KRB5_PADATA_ENCRYPTED_CHALLENGE, "ENCRYPTED_CHALLENGE"},
    {KRB5_PADATA_OTP_CHALLENGE, "OTP_CHALLENGE"},
    {KRB5_PADATA_OTP_REQUEST, "OTP_REQUEST"},
    {KRB5_PADATA_OTP_PIN_CHANGE, "OTP_PIN_CHANGE"}
};


static char *
map_patype(krb5_preauthtype pa_type)
{
    int i = 0;
    int n = sizeof(patype_str)/sizeof(patype_str[0]);

    for (i = 0; i < n; i++) {
        if (pa_type == patype_str[i].id)
            return patype_str[i].name;
    }
    return "";
}
