/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* lib/krb5/krb/auth_con.c */
/*
 * Copyright 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.
 */

#include "k5-int.h"
#include "int-proto.h"
#include "auth_con.h"

krb5_error_code KRB5_CALLCONV
krb5_auth_con_init(krb5_context context, krb5_auth_context *auth_context)
{
    *auth_context =
        (krb5_auth_context)calloc(1, sizeof(struct _krb5_auth_context));
    if (!*auth_context)
        return ENOMEM;

    /* Default flags, do time not seq */
    (*auth_context)->auth_context_flags =
        KRB5_AUTH_CONTEXT_DO_TIME |  KRB5_AUTH_CONN_INITIALIZED;

    (*auth_context)->req_cksumtype = context->default_ap_req_sumtype;
    (*auth_context)->safe_cksumtype = context->default_safe_sumtype;
    (*auth_context)->checksum_func = NULL;
    (*auth_context)->checksum_func_data = NULL;
    (*auth_context)->negotiated_etype = ENCTYPE_NULL;
    (*auth_context)->magic = KV5M_AUTH_CONTEXT;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_free(krb5_context context, krb5_auth_context auth_context)
{
    if (auth_context == NULL)
        return 0;
    if (auth_context->local_addr)
        krb5_free_address(context, auth_context->local_addr);
    if (auth_context->remote_addr)
        krb5_free_address(context, auth_context->remote_addr);
    if (auth_context->local_port)
        krb5_free_address(context, auth_context->local_port);
    if (auth_context->remote_port)
        krb5_free_address(context, auth_context->remote_port);
    if (auth_context->authentp)
        krb5_free_authenticator(context, auth_context->authentp);
    if (auth_context->key)
        krb5_k_free_key(context, auth_context->key);
    if (auth_context->send_subkey)
        krb5_k_free_key(context, auth_context->send_subkey);
    if (auth_context->recv_subkey)
        krb5_k_free_key(context, auth_context->recv_subkey);
    zapfree(auth_context->cstate.data, auth_context->cstate.length);
    if (auth_context->rcache)
        krb5_rc_close(context, auth_context->rcache);
    if (auth_context->permitted_etypes)
        free(auth_context->permitted_etypes);
    if (auth_context->ad_context)
        krb5_authdata_context_free(context, auth_context->ad_context);
    free(auth_context);
    return 0;
}

krb5_error_code
krb5_auth_con_setaddrs(krb5_context context, krb5_auth_context auth_context, krb5_address *local_addr, krb5_address *remote_addr)
{
    krb5_error_code     retval;

    /* Free old addresses */
    if (auth_context->local_addr)
        (void) krb5_free_address(context, auth_context->local_addr);
    if (auth_context->remote_addr)
        (void) krb5_free_address(context, auth_context->remote_addr);

    retval = 0;
    if (local_addr)
        retval = krb5_copy_addr(context,
                                local_addr,
                                &auth_context->local_addr);
    else
        auth_context->local_addr = NULL;

    if (!retval && remote_addr)
        retval = krb5_copy_addr(context,
                                remote_addr,
                                &auth_context->remote_addr);
    else
        auth_context->remote_addr = NULL;

    return retval;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getaddrs(krb5_context context, krb5_auth_context auth_context, krb5_address **local_addr, krb5_address **remote_addr)
{
    krb5_error_code     retval;

    retval = 0;
    if (local_addr && auth_context->local_addr) {
        retval = krb5_copy_addr(context,
                                auth_context->local_addr,
                                local_addr);
    }
    if (!retval && (remote_addr) && auth_context->remote_addr) {
        retval = krb5_copy_addr(context,
                                auth_context->remote_addr,
                                remote_addr);
    }
    return retval;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_setports(krb5_context context, krb5_auth_context auth_context, krb5_address *local_port, krb5_address *remote_port)
{
    krb5_error_code     retval;

    /* Free old addresses */
    if (auth_context->local_port)
        (void) krb5_free_address(context, auth_context->local_port);
    if (auth_context->remote_port)
        (void) krb5_free_address(context, auth_context->remote_port);

    retval = 0;
    if (local_port)
        retval = krb5_copy_addr(context,
                                local_port,
                                &auth_context->local_port);
    else
        auth_context->local_port = NULL;

    if (!retval && remote_port)
        retval = krb5_copy_addr(context,
                                remote_port,
                                &auth_context->remote_port);
    else
        auth_context->remote_port = NULL;

    return retval;
}


/*
 * This function overloads the keyblock field. It is only useful prior to
 * a krb5_rd_req_decode() call for user to user authentication where the
 * server has the key and needs to use it to decrypt the incoming request.
 * Once decrypted this key is no longer necessary and is then overwritten
 * with the session key sent by the client.
 */
krb5_error_code KRB5_CALLCONV
krb5_auth_con_setuseruserkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock *keyblock)
{
    if (auth_context->key)
        krb5_k_free_key(context, auth_context->key);
    return(krb5_k_create_key(context, keyblock, &(auth_context->key)));
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock **keyblock)
{
    if (auth_context->key)
        return krb5_k_key_keyblock(context, auth_context->key, keyblock);
    *keyblock = NULL;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getkey_k(krb5_context context, krb5_auth_context auth_context,
                       krb5_key *key)
{
    krb5_k_reference_key(context, auth_context->key);
    *key = auth_context->key;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getlocalsubkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock **keyblock)
{
    return krb5_auth_con_getsendsubkey(context, auth_context, keyblock);
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getremotesubkey(krb5_context context, krb5_auth_context auth_context, krb5_keyblock **keyblock)
{
    return krb5_auth_con_getrecvsubkey(context, auth_context, keyblock);
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_setsendsubkey(krb5_context ctx, krb5_auth_context ac, krb5_keyblock *keyblock)
{
    if (ac->send_subkey != NULL)
        krb5_k_free_key(ctx, ac->send_subkey);
    ac->send_subkey = NULL;
    if (keyblock !=NULL)
        return krb5_k_create_key(ctx, keyblock, &ac->send_subkey);
    else
        return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_setsendsubkey_k(krb5_context ctx, krb5_auth_context ac,
                              krb5_key key)
{
    krb5_k_free_key(ctx, ac->send_subkey);
    ac->send_subkey = key;
    krb5_k_reference_key(ctx, key);
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_setrecvsubkey(krb5_context ctx, krb5_auth_context ac, krb5_keyblock *keyblock)
{
    if (ac->recv_subkey != NULL)
        krb5_k_free_key(ctx, ac->recv_subkey);
    ac->recv_subkey = NULL;
    if (keyblock != NULL)
        return krb5_k_create_key(ctx, keyblock, &ac->recv_subkey);
    else
        return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_setrecvsubkey_k(krb5_context ctx, krb5_auth_context ac,
                              krb5_key key)
{
    krb5_k_free_key(ctx, ac->recv_subkey);
    ac->recv_subkey = key;
    krb5_k_reference_key(ctx, key);
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getsendsubkey(krb5_context ctx, krb5_auth_context ac, krb5_keyblock **keyblock)
{
    if (ac->send_subkey != NULL)
        return krb5_k_key_keyblock(ctx, ac->send_subkey, keyblock);
    *keyblock = NULL;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getsendsubkey_k(krb5_context ctx, krb5_auth_context ac,
                              krb5_key *key)
{
    krb5_k_reference_key(ctx, ac->send_subkey);
    *key = ac->send_subkey;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getrecvsubkey(krb5_context ctx, krb5_auth_context ac, krb5_keyblock **keyblock)
{
    if (ac->recv_subkey != NULL)
        return krb5_k_key_keyblock(ctx, ac->recv_subkey, keyblock);
    *keyblock = NULL;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getrecvsubkey_k(krb5_context ctx, krb5_auth_context ac,
                              krb5_key *key)
{
    krb5_k_reference_key(ctx, ac->recv_subkey);
    *key = ac->recv_subkey;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_set_req_cksumtype(krb5_context context, krb5_auth_context auth_context, krb5_cksumtype cksumtype)
{
    auth_context->req_cksumtype = cksumtype;
    return 0;
}

krb5_error_code
krb5_auth_con_set_safe_cksumtype(krb5_context context, krb5_auth_context auth_context, krb5_cksumtype cksumtype)
{
    auth_context->safe_cksumtype = cksumtype;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getlocalseqnumber(krb5_context context, krb5_auth_context auth_context, krb5_int32 *seqnumber)
{
    *seqnumber = auth_context->local_seq_number;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getremoteseqnumber(krb5_context context, krb5_auth_context auth_context, krb5_int32 *seqnumber)
{
    *seqnumber = auth_context->remote_seq_number;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_initivector(krb5_context context, krb5_auth_context auth_context)
{
    krb5_error_code ret;
    krb5_enctype enctype;

    if (auth_context->key == NULL)
        return EINVAL;
    ret = krb5_c_init_state(context, &auth_context->key->keyblock,
                            KRB5_KEYUSAGE_KRB_PRIV_ENCPART,
                            &auth_context->cstate);
    if (ret)
        return ret;

    /*
     * Historically we used a zero-filled buffer of the enctype block size.
     * This matches every existing enctype except RC4 (which has a block size
     * of 1) and des-cbc-crc (which uses the key instead of a zero-filled
     * buffer).  Special-case des-cbc-crc to remain interoperable.
     */
    enctype = krb5_k_key_enctype(context, auth_context->key);
    if (enctype == ENCTYPE_DES_CBC_CRC)
        zap(auth_context->cstate.data, auth_context->cstate.length);

    return 0;
}

krb5_error_code
krb5_auth_con_setivector(krb5_context context, krb5_auth_context auth_context, krb5_pointer ivector)
{
    /*
     * This function was part of the pre-1.2.2 API.  Because it aliased the
     * caller's memory into auth_context, and doesn't provide the size of the
     * cipher state, it's inconvenient to support now, so return an error.
     */
    return EINVAL;
}

krb5_error_code
krb5_auth_con_getivector(krb5_context context, krb5_auth_context auth_context, krb5_pointer *ivector)
{
    *ivector = auth_context->cstate.data;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_setflags(krb5_context context, krb5_auth_context auth_context, krb5_int32 flags)
{
    auth_context->auth_context_flags = flags;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_getflags(krb5_context context, krb5_auth_context auth_context, krb5_int32 *flags)
{
    *flags = auth_context->auth_context_flags;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_setrcache(krb5_context context, krb5_auth_context auth_context, krb5_rcache rcache)
{
    auth_context->rcache = rcache;
    return 0;
}

krb5_error_code
krb5_auth_con_getrcache(krb5_context context, krb5_auth_context auth_context, krb5_rcache *rcache)
{
    *rcache = auth_context->rcache;
    return 0;
}

krb5_error_code
krb5_auth_con_setpermetypes(krb5_context context,
                            krb5_auth_context auth_context,
                            const krb5_enctype *permetypes)
{
    krb5_enctype *newpe;
    krb5_error_code ret;

    ret = k5_copy_etypes(permetypes, &newpe);
    if (ret != 0)
        return ret;

    free(auth_context->permitted_etypes);
    auth_context->permitted_etypes = newpe;
    return 0;
}

krb5_error_code
krb5_auth_con_getpermetypes(krb5_context context,
                            krb5_auth_context auth_context,
                            krb5_enctype **permetypes)
{
    *permetypes = NULL;
    if (auth_context->permitted_etypes == NULL)
        return 0;
    return k5_copy_etypes(auth_context->permitted_etypes, permetypes);
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_set_checksum_func( krb5_context context,
                                 krb5_auth_context  auth_context,
                                 krb5_mk_req_checksum_func func,
                                 void *data)
{
    auth_context->checksum_func = func;
    auth_context->checksum_func_data = data;
    return 0;
}

krb5_error_code KRB5_CALLCONV
krb5_auth_con_get_checksum_func( krb5_context context,
                                 krb5_auth_context auth_context,
                                 krb5_mk_req_checksum_func *func,
                                 void **data)
{
    *func = auth_context->checksum_func;
    *data = auth_context->checksum_func_data;
    return 0;
}

krb5_error_code
krb5_auth_con_get_subkey_enctype(krb5_context context,
                                 krb5_auth_context auth_context,
                                 krb5_enctype *etype)
{
    *etype = auth_context->negotiated_etype;
    return 0;
}

krb5_error_code
krb5_auth_con_get_authdata_context(krb5_context context,
                                   krb5_auth_context auth_context,
                                   krb5_authdata_context *ad_context)
{
    *ad_context = auth_context->ad_context;
    return 0;
}

krb5_error_code
krb5_auth_con_set_authdata_context(krb5_context context,
                                   krb5_auth_context auth_context,
                                   krb5_authdata_context ad_context)
{
    auth_context->ad_context = ad_context;
    return 0;
}
