/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* lib/gssapi/krb5/ser_sctx.c - [De]serialization of security context */
/*
 * Copyright 1995, 2004, 2008 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.
 */

#include "k5-int.h"
#include "gssapiP_krb5.h"

/*
 * This module contains routines to [de]serialize
 *      krb5_gss_enc_desc and krb5_gss_ctx_id_t.
 * XXX This whole serialization abstraction is unnecessary in a
 * non-messaging environment, which krb5 is.  Someday, this should
 * all get redone without the extra level of indirection. I've done
 * some of this work here, since adding new serializers is an internal
 * krb5 interface, and I won't use those.  There is some more
 * deobfuscation (no longer anonymizing pointers, mostly) which could
 * still be done. --marc
 */

static krb5_error_code
kg_oid_externalize(gss_OID oid, krb5_octet **buffer, size_t *lenremain)
{
    krb5_error_code err;

    err = krb5_ser_pack_int32(KV5M_GSS_OID, buffer, lenremain);
    if (err)
        return err;
    err = krb5_ser_pack_int32((krb5_int32) oid->length,
                              buffer, lenremain);
    if (err)
        return err;
    err = krb5_ser_pack_bytes((krb5_octet *) oid->elements,
                              oid->length, buffer, lenremain);
    if (err)
        return err;
    err = krb5_ser_pack_int32(KV5M_GSS_OID, buffer, lenremain);
    return err;
}

static krb5_error_code
kg_oid_internalize(gss_OID *argp, krb5_octet **buffer, size_t *lenremain)
{
    gss_OID oid;
    krb5_int32 ibuf;
    krb5_octet         *bp;
    size_t             remain;

    bp = *buffer;
    remain = *lenremain;

    /* Read in and check our magic number */
    if (krb5_ser_unpack_int32(&ibuf, &bp, &remain))
        return (EINVAL);

    if (ibuf != KV5M_GSS_OID)
        return (EINVAL);

    oid = (gss_OID) malloc(sizeof(gss_OID_desc));
    if (oid == NULL)
        return ENOMEM;
    if (krb5_ser_unpack_int32(&ibuf, &bp, &remain)) {
        free(oid);
        return EINVAL;
    }
    oid->length = ibuf;
    oid->elements = malloc((size_t)ibuf);
    if (oid->elements == 0) {
        free(oid);
        return ENOMEM;
    }
    if (krb5_ser_unpack_bytes((krb5_octet *) oid->elements,
                              oid->length, &bp, &remain)) {
        free(oid->elements);
        free(oid);
        return EINVAL;
    }

    /* Read in and check our trailing magic number */
    if (krb5_ser_unpack_int32(&ibuf, &bp, &remain)) {
        free(oid->elements);
        free(oid);
        return (EINVAL);
    }

    if (ibuf != KV5M_GSS_OID) {
        free(oid->elements);
        free(oid);
        return (EINVAL);
    }

    *buffer = bp;
    *lenremain = remain;
    *argp = oid;
    return 0;
}

static krb5_error_code
kg_oid_size(gss_OID oid, size_t *sizep)
{
    krb5_error_code kret;
    size_t required;

    kret = EINVAL;
    if (oid != NULL) {
        required = 2*sizeof(krb5_int32); /* For the header and trailer */
        required += sizeof(krb5_int32);
        required += oid->length;

        kret = 0;

        *sizep += required;
    }

    return(kret);
}

static krb5_error_code
kg_seqstate_externalize(g_seqnum_state arg, krb5_octet **buffer,
                        size_t *lenremain)
{
    krb5_error_code err;
    err = krb5_ser_pack_int32(KV5M_GSS_QUEUE, buffer, lenremain);
    if (err == 0)
        err = g_seqstate_externalize(arg, buffer, lenremain);
    if (err == 0)
        err = krb5_ser_pack_int32(KV5M_GSS_QUEUE, buffer, lenremain);
    return err;
}

static krb5_error_code
kg_seqstate_internalize(g_seqnum_state *argp, krb5_octet **buffer,
                        size_t *lenremain)
{
    krb5_int32 ibuf;
    krb5_octet         *bp;
    size_t             remain;
    krb5_error_code    err;

    bp = *buffer;
    remain = *lenremain;

    /* Read in and check our magic number */
    if (krb5_ser_unpack_int32(&ibuf, &bp, &remain))
        return (EINVAL);

    if (ibuf != KV5M_GSS_QUEUE)
        return (EINVAL);

    err = g_seqstate_internalize(argp, &bp, &remain);
    if (err)
        return err;

    /* Read in and check our trailing magic number */
    if (krb5_ser_unpack_int32(&ibuf, &bp, &remain)) {
        g_seqstate_free(*argp);
        return (EINVAL);
    }

    if (ibuf != KV5M_GSS_QUEUE) {
        g_seqstate_free(*argp);
        return (EINVAL);
    }

    *buffer = bp;
    *lenremain = remain;
    return 0;
}

static krb5_error_code
kg_seqstate_size(g_seqnum_state arg, size_t *sizep)
{
    krb5_error_code kret;
    size_t required;

    kret = EINVAL;
    if (arg) {
        required = 2*sizeof(krb5_int32); /* For the header and trailer */
        g_seqstate_size(arg, &required);

        kret = 0;
        *sizep += required;
    }
    return(kret);
}

/*
 * Determine the size required for this krb5_gss_ctx_id_rec.
 */
krb5_error_code
kg_ctx_size(krb5_context kcontext, krb5_gss_ctx_id_t ctx, size_t *sizep)
{
    krb5_error_code     kret;
    size_t              required;

    /*
     * krb5_gss_ctx_id_rec requires:
     *  krb5_int32      for KG_CONTEXT
     *  krb5_int32      for initiate.
     *  krb5_int32      for established.
     *  krb5_int32      for have_acceptor_subkey.
     *  krb5_int32      for seed_init.
     *  krb5_int32      for gss_flags.
     *  sizeof(seed)    for seed
     *  ...             for here
     *  ...             for there
     *  ...             for subkey
     *  krb5_int32      for signalg.
     *  krb5_int32      for cksum_size.
     *  krb5_int32      for sealalg.
     *  ...             for enc
     *  ...             for seq
     *  krb5_int32      for authtime.
     *  krb5_int32      for starttime.
     *  krb5_int32      for endtime.
     *  krb5_int32      for renew_till.
     *  krb5_int32      for flags.
     *  int64_t         for seq_send.
     *  int64_t         for seq_recv.
     *  ...             for seqstate
     *  ...             for auth_context
     *  ...             for mech_used
     *  krb5_int32      for proto
     *  krb5_int32      for cksumtype
     *  ...             for acceptor_subkey
     *  krb5_int32      for acceptor_key_cksumtype
     *  krb5_int32      for cred_rcache
     *  krb5_int32      for number of elements in authdata array
     *  ...             for authdata array
     *  krb5_int32      for trailer.
     */
    kret = EINVAL;
    if (ctx != NULL) {
        required = 21*sizeof(krb5_int32);
        required += 2*sizeof(int64_t);
        required += sizeof(ctx->seed);

        kret = 0;
        if (!kret && ctx->here)
            kret = k5_size_principal(ctx->here->princ, &required);

        if (!kret && ctx->there)
            kret = k5_size_principal(ctx->there->princ, &required);

        if (!kret && ctx->subkey)
            kret = k5_size_keyblock(&ctx->subkey->keyblock, &required);

        if (!kret && ctx->enc)
            kret = k5_size_keyblock(&ctx->enc->keyblock, &required);

        if (!kret && ctx->seq)
            kret = k5_size_keyblock(&ctx->seq->keyblock, &required);

        if (!kret)
            kret = kg_oid_size(ctx->mech_used, &required);

        if (!kret && ctx->seqstate)
            kret = kg_seqstate_size(ctx->seqstate, &required);

        if (!kret)
            kret = k5_size_context(ctx->k5_context, &required);
        if (!kret)
            kret = k5_size_auth_context(ctx->auth_context, &required);
        if (!kret && ctx->acceptor_subkey)
            kret = k5_size_keyblock(&ctx->acceptor_subkey->keyblock,
                                    &required);
        if (!kret && ctx->authdata) {
            krb5_int32 i;

            for (i = 0; !kret && ctx->authdata[i]; i++)
                kret = k5_size_authdata(ctx->authdata[i], &required);
        }
        if (!kret) {
            krb5_gss_name_t initiator_name;

            initiator_name = ctx->initiate ? ctx->here : ctx->there;

            if (initiator_name && initiator_name->ad_context) {
                kret = k5_size_authdata_context(kcontext,
                                                initiator_name->ad_context,
                                                &required);
            }
        }
        *sizep += required;
    }
    return(kret);
}

/*
 * Externalize this krb5_gss_ctx_id_ret.
 */
krb5_error_code
kg_ctx_externalize(krb5_context kcontext, krb5_gss_ctx_id_t ctx,
                   krb5_octet **buffer, size_t *lenremain)
{
    krb5_error_code     kret;
    size_t              required;
    krb5_octet          *bp;
    size_t              remain;
    krb5int_access kaccess;

    kret = krb5int_accessor (&kaccess, KRB5INT_ACCESS_VERSION);
    if (kret)
        return(kret);

    required = 0;
    bp = *buffer;
    remain = *lenremain;
    kret = EINVAL;
    if (ctx != NULL) {
        kret = ENOMEM;
        if (!kg_ctx_size(kcontext, ctx, &required) &&
            (required <= remain)) {
            /* Our identifier */
            (void) krb5_ser_pack_int32(KG_CONTEXT, &bp, &remain);

            /* Now static data */
            (void) krb5_ser_pack_int32((krb5_int32) ctx->initiate,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->established,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->have_acceptor_subkey,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->seed_init,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->gss_flags,
                                       &bp, &remain);
            (void) krb5_ser_pack_bytes((krb5_octet *) ctx->seed,
                                       sizeof(ctx->seed),
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->signalg,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->cksum_size,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->sealalg,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->krb_times.authtime,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->krb_times.starttime,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->krb_times.endtime,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->krb_times.renew_till,
                                       &bp, &remain);
            (void) krb5_ser_pack_int32((krb5_int32) ctx->krb_flags,
                                       &bp, &remain);
            (void) (*kaccess.ser_pack_int64)((int64_t) ctx->seq_send,
                                             &bp, &remain);
            (void) (*kaccess.ser_pack_int64)((int64_t) ctx->seq_recv,
                                             &bp, &remain);

            /* Now dynamic data */
            kret = 0;

            if (!kret && ctx->mech_used)
                kret = kg_oid_externalize(ctx->mech_used, &bp, &remain);

            if (!kret && ctx->here)
                kret = k5_externalize_principal(ctx->here->princ,
                                                &bp, &remain);

            if (!kret && ctx->there)
                kret = k5_externalize_principal(ctx->there->princ,
                                                &bp, &remain);

            if (!kret && ctx->subkey)
                kret = k5_externalize_keyblock(&ctx->subkey->keyblock,
                                               &bp, &remain);

            if (!kret && ctx->enc)
                kret = k5_externalize_keyblock(&ctx->enc->keyblock,
                                               &bp, &remain);

            if (!kret && ctx->seq)
                kret = k5_externalize_keyblock(&ctx->seq->keyblock,
                                               &bp, &remain);

            if (!kret && ctx->seqstate)
                kret = kg_seqstate_externalize(ctx->seqstate, &bp, &remain);

            if (!kret)
                kret = k5_externalize_context(ctx->k5_context, &bp, &remain);

            if (!kret)
                kret = k5_externalize_auth_context(ctx->auth_context,
                                                   &bp, &remain);

            if (!kret)
                kret = krb5_ser_pack_int32((krb5_int32) ctx->proto,
                                           &bp, &remain);
            if (!kret)
                kret = krb5_ser_pack_int32((krb5_int32) ctx->cksumtype,
                                           &bp, &remain);
            if (!kret && ctx->acceptor_subkey)
                kret = k5_externalize_keyblock(&ctx->acceptor_subkey->keyblock,
                                               &bp, &remain);
            if (!kret)
                kret = krb5_ser_pack_int32((krb5_int32) ctx->acceptor_subkey_cksumtype,
                                           &bp, &remain);

            if (!kret)
                kret = krb5_ser_pack_int32((krb5_int32) ctx->cred_rcache,
                                           &bp, &remain);
            if (!kret) {
                krb5_int32 i = 0;

                if (ctx->authdata) {
                    for (; ctx->authdata[i]; i++)
                        ;
                }
                /* authdata count */
                kret = krb5_ser_pack_int32(i, &bp, &remain);
                if (!kret && ctx->authdata) {
                    /* authdata */
                    for (i = 0; !kret && ctx->authdata[i]; i++)
                        kret = k5_externalize_authdata(ctx->authdata[i],
                                                       &bp, &remain);
                }
            }
            /* authdata context */
            if (!kret) {
                krb5_gss_name_t initiator_name;

                initiator_name = ctx->initiate ? ctx->here : ctx->there;

                if (initiator_name && initiator_name->ad_context) {
                    kret = k5_externalize_authdata_context(kcontext,
                                                           initiator_name->
                                                           ad_context,
                                                           &bp, &remain);
                }
            }
            /* trailer */
            if (!kret)
                kret = krb5_ser_pack_int32(KG_CONTEXT, &bp, &remain);
            if (!kret) {
                *buffer = bp;
                *lenremain = remain;
            }
        }
    }
    return(kret);
}

/* Internalize a keyblock and convert it to a key. */
static krb5_error_code
intern_key(krb5_key *key, krb5_octet **bp, size_t *sp)
{
    krb5_keyblock *keyblock;
    krb5_error_code ret;

    ret = k5_internalize_keyblock(&keyblock, bp, sp);
    if (ret != 0)
        return ret;
    ret = krb5_k_create_key(NULL, keyblock, key);
    krb5_free_keyblock(NULL, keyblock);
    return ret;
}

/*
 * Internalize this krb5_gss_ctx_id_t.
 */
krb5_error_code
kg_ctx_internalize(krb5_context kcontext, krb5_gss_ctx_id_t *argp,
                   krb5_octet **buffer, size_t *lenremain)
{
    krb5_error_code     kret;
    krb5_gss_ctx_id_rec *ctx;
    krb5_int32          ibuf;
    krb5_octet          *bp;
    size_t              remain;
    krb5int_access kaccess;
    krb5_principal        princ;

    kret = krb5int_accessor (&kaccess, KRB5INT_ACCESS_VERSION);
    if (kret)
        return(kret);

    bp = *buffer;
    remain = *lenremain;
    kret = EINVAL;
    princ = NULL;
    /* Read our magic number */
    if (krb5_ser_unpack_int32(&ibuf, &bp, &remain))
        ibuf = 0;
    if (ibuf == KG_CONTEXT) {
        kret = ENOMEM;

        /* Get a context */
        if ((remain >= (17*sizeof(krb5_int32)
                        + 2*sizeof(int64_t)
                        + sizeof(ctx->seed))) &&
            (ctx = (krb5_gss_ctx_id_rec *)
             xmalloc(sizeof(krb5_gss_ctx_id_rec)))) {
            memset(ctx, 0, sizeof(krb5_gss_ctx_id_rec));

            ctx->magic = ibuf;

            /* Get static data */
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->initiate = (int) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->established = (int) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->have_acceptor_subkey = (int) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->seed_init = (int) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->gss_flags = (int) ibuf;
            (void) krb5_ser_unpack_bytes((krb5_octet *) ctx->seed,
                                         sizeof(ctx->seed),
                                         &bp, &remain);
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->signalg = (int) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->cksum_size = (int) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->sealalg = (int) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->krb_times.authtime = (krb5_timestamp) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->krb_times.starttime = (krb5_timestamp) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->krb_times.endtime = (krb5_timestamp) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->krb_times.renew_till = (krb5_timestamp) ibuf;
            (void) krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->krb_flags = (krb5_flags) ibuf;
            (void) (*kaccess.ser_unpack_int64)((int64_t *)&ctx->seq_send,
                                               &bp, &remain);
            kret = (*kaccess.ser_unpack_int64)((int64_t *)&ctx->seq_recv,
                                               &bp, &remain);
            if (kret) {
                free(ctx);
                return kret;
            }

            {
                gss_OID tmp;
                kret = kg_oid_internalize(&tmp, &bp, &remain);
                if (kret == 0)
                    ctx->mech_used = tmp;
                else if (kret == EINVAL)
                    kret = 0;
            }
            /* Now get substructure data */
            kret = k5_internalize_principal(&princ, &bp, &remain);
            if (kret == 0) {
                kret = kg_init_name(kcontext, princ, NULL, NULL, NULL,
                                    KG_INIT_NAME_NO_COPY, &ctx->here);
                if (kret)
                    krb5_free_principal(kcontext, princ);
            } else if (kret == EINVAL)
                kret = 0;
            if (!kret) {
                kret = k5_internalize_principal(&princ, &bp, &remain);
                if (kret == 0) {
                    kret = kg_init_name(kcontext, princ, NULL, NULL, NULL,
                                        KG_INIT_NAME_NO_COPY, &ctx->there);
                    if (kret)
                        krb5_free_principal(kcontext, princ);
                } else if (kret == EINVAL)
                    kret = 0;
            }
            if (!kret &&
                (kret = intern_key(&ctx->subkey, &bp, &remain))) {
                if (kret == EINVAL)
                    kret = 0;
            }
            if (!kret &&
                (kret = intern_key(&ctx->enc, &bp, &remain))) {
                if (kret == EINVAL)
                    kret = 0;
            }
            if (!kret &&
                (kret = intern_key(&ctx->seq, &bp, &remain))) {
                if (kret == EINVAL)
                    kret = 0;
            }

            if (!kret) {
                kret = kg_seqstate_internalize(&ctx->seqstate, &bp, &remain);
                if (kret == EINVAL)
                    kret = 0;
            }

            if (!kret)
                kret = k5_internalize_context(&ctx->k5_context, &bp, &remain);

            if (!kret)
                kret = k5_internalize_auth_context(&ctx->auth_context,
                                                   &bp, &remain);

            if (!kret)
                kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->proto = ibuf;
            if (!kret)
                kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->cksumtype = ibuf;
            if (!kret &&
                (kret = intern_key(&ctx->acceptor_subkey, &bp, &remain))) {
                if (kret == EINVAL)
                    kret = 0;
            }
            if (!kret)
                kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->acceptor_subkey_cksumtype = ibuf;
            if (!kret)
                kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            ctx->cred_rcache = ibuf;
            /* authdata */
            if (!kret)
                kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            if (!kret) {
                krb5_int32 nadata = ibuf, i;

                if (nadata > 0) {
                    ctx->authdata = (krb5_authdata **)calloc((size_t)nadata + 1,
                                                             sizeof(krb5_authdata *));
                    if (ctx->authdata == NULL) {
                        kret = ENOMEM;
                    } else {
                        for (i = 0; !kret && i < nadata; i++)
                            kret = k5_internalize_authdata(&ctx->authdata[i],
                                                           &bp, &remain);
                    }
                }
            }
            /* authdata context */
            if (!kret) {
                krb5_gss_name_t initiator_name;

                initiator_name = ctx->initiate ? ctx->here : ctx->there;
                if (initiator_name == NULL) {
                    kret = EINVAL;
                } else {
                    kret = k5_internalize_authdata_context(kcontext,
                                                           &initiator_name->
                                                           ad_context,
                                                           &bp, &remain);
                    if (kret == EINVAL)
                        kret = 0;
                }
            }
            /* Get trailer */
            if (!kret)
                kret = krb5_ser_unpack_int32(&ibuf, &bp, &remain);
            if (!kret && ibuf != KG_CONTEXT)
                kret = EINVAL;

            if (!kret) {
                *buffer = bp;
                *lenremain = remain;
                *argp = ctx;
            } else {
                if (ctx->seq)
                    krb5_k_free_key(kcontext, ctx->seq);
                if (ctx->enc)
                    krb5_k_free_key(kcontext, ctx->enc);
                if (ctx->subkey)
                    krb5_k_free_key(kcontext, ctx->subkey);
                if (ctx->there)
                    kg_release_name(kcontext, &ctx->there);
                if (ctx->here)
                    kg_release_name(kcontext, &ctx->here);
                xfree(ctx);
            }
        }
    }
    return(kret);
}
