/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/* kdc/kdc_preauth.c - Preauthentication routines for the KDC */
/*
* Copyright 1995, 2003, 2007, 2009 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.
*/
/*
* Copyright (C) 1998 by the FundsXpress, INC.
*
* 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 FundsXpress. not be used in advertising or publicity pertaining
* to distribution of the software without specific, written prior
* permission. FundsXpress makes no representations about the suitability of
* this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
* WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
/*
* Copyright (c) 2006-2008, Novell, Inc.
* 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.
* * The copyright holder's name is not used to endorse or promote products
* derived from this software without specific prior written permission.
*
* 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 OWNER 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 "kdc_util.h"
#include "extern.h"
#include <stdio.h>
#include "adm_proto.h"
#include <syslog.h>
#include <assert.h>
#include <krb5/kdcpreauth_plugin.h>
/* Let freshness tokens be valid for ten minutes. */
#define FRESHNESS_LIFETIME 600
typedef struct preauth_system_st {
const char *name;
int type;
int flags;
krb5_kdcpreauth_moddata moddata;
krb5_kdcpreauth_init_fn init;
krb5_kdcpreauth_fini_fn fini;
krb5_kdcpreauth_edata_fn get_edata;
krb5_kdcpreauth_verify_fn verify_padata;
krb5_kdcpreauth_return_fn return_padata;
krb5_kdcpreauth_free_modreq_fn free_modreq;
krb5_kdcpreauth_loop_fn loop;
} preauth_system;
static preauth_system *preauth_systems;
static size_t n_preauth_systems;
static krb5_error_code
make_etype_info(krb5_context context, krb5_boolean etype_info2,
krb5_principal client, krb5_key_data *client_key,
krb5_enctype enctype, krb5_data **der_out);
/* Get all available kdcpreauth vtables and a count of preauth types they
* support. Return an empty list on failure. */
static void
get_plugin_vtables(krb5_context context,
struct krb5_kdcpreauth_vtable_st **vtables_out,
size_t *n_tables_out, size_t *n_systems_out)
{
krb5_plugin_initvt_fn *plugins = NULL, *pl;
struct krb5_kdcpreauth_vtable_st *vtables;
size_t count, n_tables, n_systems, i;
*vtables_out = NULL;
*n_tables_out = *n_systems_out = 0;
/* Auto-register encrypted challenge and (if possible) pkinit. */
k5_plugin_register_dyn(context, PLUGIN_INTERFACE_KDCPREAUTH, "pkinit",
"preauth");
k5_plugin_register_dyn(context, PLUGIN_INTERFACE_KDCPREAUTH, "otp",
"preauth");
k5_plugin_register_dyn(context, PLUGIN_INTERFACE_KDCPREAUTH, "spake",
"preauth");
k5_plugin_register(context, PLUGIN_INTERFACE_KDCPREAUTH,
"encrypted_challenge",
kdcpreauth_encrypted_challenge_initvt);
k5_plugin_register(context, PLUGIN_INTERFACE_KDCPREAUTH,
"encrypted_timestamp",
kdcpreauth_encrypted_timestamp_initvt);
if (k5_plugin_load_all(context, PLUGIN_INTERFACE_KDCPREAUTH, &plugins))
return;
for (count = 0; plugins[count]; count++);
vtables = calloc(count + 1, sizeof(*vtables));
if (vtables == NULL)
goto cleanup;
for (pl = plugins, n_tables = 0; *pl != NULL; pl++) {
if ((*pl)(context, 1, 2, (krb5_plugin_vtable)&vtables[n_tables]) == 0)
n_tables++;
}
for (i = 0, n_systems = 0; i < n_tables; i++) {
for (count = 0; vtables[i].pa_type_list[count] != 0; count++);
n_systems += count;
}
*vtables_out = vtables;
*n_tables_out = n_tables;
*n_systems_out = n_systems;
cleanup:
k5_plugin_free_modules(context, plugins);
}
/* Make a list of realm names. The caller should free the list container but
* not the list elements (which are aliases into kdc_realmlist). */
static krb5_error_code
get_realm_names(struct server_handle *handle, const char ***list_out)
{
const char **list;
int i;
list = calloc(handle->kdc_numrealms + 1, sizeof(*list));
if (list == NULL)
return ENOMEM;
for (i = 0; i < handle->kdc_numrealms; i++)
list[i] = handle->kdc_realmlist[i]->realm_name;
list[i] = NULL;
*list_out = list;
return 0;
}
void
load_preauth_plugins(struct server_handle *handle, krb5_context context,
verto_ctx *ctx)
{
krb5_error_code ret;
struct krb5_kdcpreauth_vtable_st *vtables = NULL, *vt;
size_t n_systems, n_tables, i, j;
krb5_kdcpreauth_moddata moddata;
const char **realm_names = NULL, *emsg;
preauth_system *sys;
/* Get all available kdcpreauth vtables. */
get_plugin_vtables(context, &vtables, &n_tables, &n_systems);
/* Allocate the list of static and plugin preauth systems. */
preauth_systems = calloc(n_systems + 1, sizeof(preauth_system));
if (preauth_systems == NULL)
goto cleanup;
if (get_realm_names(handle, &realm_names))
goto cleanup;
/* Add the dynamically-loaded mechanisms to the list. */
n_systems = 0;
for (i = 0; i < n_tables; i++) {
/* Try to initialize this module. */
vt = &vtables[i];
moddata = NULL;
if (vt->init) {
ret = vt->init(context, &moddata, realm_names);
if (ret) {
emsg = krb5_get_error_message(context, ret);
krb5_klog_syslog(LOG_ERR, _("preauth %s failed to "
"initialize: %s"), vt->name, emsg);
krb5_free_error_message(context, emsg);
continue;
}
}
if (vt->loop) {
ret = vt->loop(context, moddata, ctx);
if (ret) {
emsg = krb5_get_error_message(context, ret);
krb5_klog_syslog(LOG_ERR, _("preauth %s failed to setup "
"loop: %s"), vt->name, emsg);
krb5_free_error_message(context, emsg);
if (vt->fini)
vt->fini(context, moddata);
continue;
}
}
/* Add this module to the systems list once for each pa type. */
for (j = 0; vt->pa_type_list[j] != 0; j++) {
sys = &preauth_systems[n_systems];
sys->name = vt->name;
sys->type = vt->pa_type_list[j];
sys->flags = (vt->flags) ? vt->flags(context, sys->type) : 0;
sys->moddata = moddata;
sys->init = vt->init;
/* Only call fini once for each plugin. */
sys->fini = (j == 0) ? vt->fini : NULL;
sys->get_edata = vt->edata;
sys->verify_padata = vt->verify;
sys->return_padata = vt->return_padata;
sys->free_modreq = vt->free_modreq;
sys->loop = vt->loop;
n_systems++;
}
}
n_preauth_systems = n_systems;
/* Add the end-of-list marker. */
preauth_systems[n_systems].name = "[end]";
preauth_systems[n_systems].type = -1;
cleanup:
free(vtables);
free(realm_names);
}
void
unload_preauth_plugins(krb5_context context)
{
size_t i;
for (i = 0; i < n_preauth_systems; i++) {
if (preauth_systems[i].fini)
preauth_systems[i].fini(context, preauth_systems[i].moddata);
}
free(preauth_systems);
preauth_systems = NULL;
n_preauth_systems = 0;
}
/*
* The make_padata_context() function creates a space for storing any
* request-specific module data which will be needed by return_padata() later.
* Each preauth type gets a storage location of its own.
*/
struct request_pa_context {
int n_contexts;
struct {
preauth_system *pa_system;
krb5_kdcpreauth_modreq modreq;
} *contexts;
};
static krb5_error_code
make_padata_context(krb5_context context, void **padata_context)
{
int i;
struct request_pa_context *ret;
ret = malloc(sizeof(*ret));
if (ret == NULL) {
return ENOMEM;
}
ret->n_contexts = n_preauth_systems;
ret->contexts = malloc(sizeof(ret->contexts[0]) * ret->n_contexts);
if (ret->contexts == NULL) {
free(ret);
return ENOMEM;
}
memset(ret->contexts, 0, sizeof(ret->contexts[0]) * ret->n_contexts);
for (i = 0; i < ret->n_contexts; i++) {
ret->contexts[i].pa_system = &preauth_systems[i];
ret->contexts[i].modreq = NULL;
}
*padata_context = ret;
return 0;
}
/*
* The free_padata_context function frees any context information pointers
* which the check_padata() function created but which weren't already cleaned
* up by return_padata().
*/
void
free_padata_context(krb5_context kcontext, void *padata_context)
{
struct request_pa_context *context = padata_context;
preauth_system *sys;
int i;
if (context == NULL)
return;
for (i = 0; i < context->n_contexts; i++) {
sys = context->contexts[i].pa_system;
if (!sys->free_modreq || !context->contexts[i].modreq)
continue;
sys->free_modreq(kcontext, sys->moddata, context->contexts[i].modreq);
context->contexts[i].modreq = NULL;
}
free(context->contexts);
free(context);
}
static krb5_deltat
max_time_skew(krb5_context context, krb5_kdcpreauth_rock rock)
{
return context->clockskew;
}
static krb5_error_code
client_keys(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_keyblock **keys_out)
{
krb5_kdc_req *request = rock->request;
krb5_db_entry *client = rock->client;
krb5_keyblock *keys, key;
krb5_key_data *entry_key;
int i, k;
keys = calloc(request->nktypes + 1, sizeof(krb5_keyblock));
if (keys == NULL)
return ENOMEM;
k = 0;
for (i = 0; i < request->nktypes; i++) {
entry_key = NULL;
if (krb5_dbe_find_enctype(context, client, request->ktype[i],
-1, 0, &entry_key) != 0)
continue;
if (krb5_dbe_decrypt_key_data(context, NULL, entry_key,
&key, NULL) != 0)
continue;
keys[k++] = key;
}
if (k == 0) {
free(keys);
return ENOENT;
}
*keys_out = keys;
return 0;
}
static void free_keys(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_keyblock *keys)
{
krb5_keyblock *k;
if (keys == NULL)
return;
for (k = keys; k->enctype != 0; k++)
krb5_free_keyblock_contents(context, k);
free(keys);
}
static krb5_data *
request_body(krb5_context context, krb5_kdcpreauth_rock rock)
{
return rock->inner_body;
}
static krb5_keyblock *
fast_armor(krb5_context context, krb5_kdcpreauth_rock rock)
{
return rock->rstate->armor_key;
}
static krb5_error_code
get_string(krb5_context context, krb5_kdcpreauth_rock rock, const char *key,
char **value_out)
{
return krb5_dbe_get_string(context, rock->client, key, value_out);
}
static void
free_string(krb5_context context, krb5_kdcpreauth_rock rock, char *string)
{
krb5_dbe_free_string(context, string);
}
static void *
client_entry(krb5_context context, krb5_kdcpreauth_rock rock)
{
return rock->client;
}
static verto_ctx *
event_context(krb5_context context, krb5_kdcpreauth_rock rock)
{
return rock->vctx;
}
static krb5_boolean
have_client_keys(krb5_context context, krb5_kdcpreauth_rock rock)
{
krb5_kdc_req *request = rock->request;
krb5_key_data *kd;
int i;
for (i = 0; i < request->nktypes; i++) {
if (krb5_dbe_find_enctype(context, rock->client, request->ktype[i],
-1, 0, &kd) == 0)
return TRUE;
}
return FALSE;
}
static const krb5_keyblock *
client_keyblock(krb5_context context, krb5_kdcpreauth_rock rock)
{
return rock->client_keyblock;
}
static krb5_error_code
add_auth_indicator(krb5_context context, krb5_kdcpreauth_rock rock,
const char *indicator)
{
return authind_add(context, indicator, rock->auth_indicators);
}
static krb5_boolean
get_cookie(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_preauthtype pa_type, krb5_data *out)
{
return kdc_fast_search_cookie(rock->rstate, pa_type, out);
}
static krb5_error_code
set_cookie(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_preauthtype pa_type, const krb5_data *data)
{
return kdc_fast_set_cookie(rock->rstate, pa_type, data);
}
static krb5_boolean
match_client(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_principal princ)
{
krb5_db_entry *ent;
krb5_boolean match = FALSE;
krb5_principal req_client = rock->request->client;
krb5_principal client = rock->client->princ;
/* Check for a direct match against the request principal or
* the post-canon client principal. */
if (krb5_principal_compare_flags(context, princ, req_client,
KRB5_PRINCIPAL_COMPARE_ENTERPRISE) ||
krb5_principal_compare(context, princ, client))
return TRUE;
if (krb5_db_get_principal(context, princ, 0, &ent))
return FALSE;
match = krb5_principal_compare(context, ent->princ, client);
krb5_db_free_principal(context, ent);
return match;
}
static krb5_principal
client_name(krb5_context context, krb5_kdcpreauth_rock rock)
{
return rock->client->princ;
}
static void
send_freshness_token(krb5_context context, krb5_kdcpreauth_rock rock)
{
rock->send_freshness_token = TRUE;
}
static krb5_error_code
check_freshness_token(krb5_context context, krb5_kdcpreauth_rock rock,
const krb5_data *token)
{
krb5_timestamp token_ts, now;
krb5_key_data *kd;
krb5_keyblock kb;
krb5_kvno token_kvno;
krb5_checksum cksum;
krb5_data d;
uint8_t *token_cksum;
size_t token_cksum_len;
krb5_boolean valid = FALSE;
char ckbuf[4];
memset(&kb, 0, sizeof(kb));
if (krb5_timeofday(context, &now) != 0)
goto cleanup;
if (token->length <= 8)
goto cleanup;
token_ts = load_32_be(token->data);
token_kvno = load_32_be(token->data + 4);
token_cksum = (uint8_t *)token->data + 8;
token_cksum_len = token->length - 8;
/* Check if the token timestamp is too old. */
if (ts_after(now, ts_incr(token_ts, FRESHNESS_LIFETIME)))
goto cleanup;
/* Fetch and decrypt the local krbtgt key of the token's kvno. */
if (krb5_dbe_find_enctype(context, rock->local_tgt, -1, -1, token_kvno,
&kd) != 0)
goto cleanup;
if (krb5_dbe_decrypt_key_data(context, NULL, kd, &kb, NULL) != 0)
goto cleanup;
/* Verify the token checksum against the current KDC time. The checksum
* must use the mandatory checksum type of the krbtgt key's enctype. */
store_32_be(token_ts, ckbuf);
d = make_data(ckbuf, sizeof(ckbuf));
cksum.magic = KV5M_CHECKSUM;
cksum.checksum_type = 0;
cksum.length = token_cksum_len;
cksum.contents = token_cksum;
(void)krb5_c_verify_checksum(context, &kb, KRB5_KEYUSAGE_PA_AS_FRESHNESS,
&d, &cksum, &valid);
cleanup:
krb5_free_keyblock_contents(context, &kb);
return valid ? 0 : KRB5KDC_ERR_PREAUTH_EXPIRED;
}
static struct krb5_kdcpreauth_callbacks_st callbacks = {
5,
max_time_skew,
client_keys,
free_keys,
request_body,
fast_armor,
get_string,
free_string,
client_entry,
event_context,
have_client_keys,
client_keyblock,
add_auth_indicator,
get_cookie,
set_cookie,
match_client,
client_name,
send_freshness_token,
check_freshness_token
};
static krb5_error_code
find_pa_system(int type, preauth_system **preauth)
{
preauth_system *ap;
if (preauth_systems == NULL)
return KRB5_PREAUTH_BAD_TYPE;
ap = preauth_systems;
while ((ap->type != -1) && (ap->type != type))
ap++;
if (ap->type == -1)
return(KRB5_PREAUTH_BAD_TYPE);
*preauth = ap;
return 0;
}
/* Find a pointer to the request-specific module data for pa_sys. */
static krb5_error_code
find_modreq(preauth_system *pa_sys, struct request_pa_context *context,
krb5_kdcpreauth_modreq **modreq_out)
{
int i;
*modreq_out = NULL;
if (context == NULL)
return KRB5KRB_ERR_GENERIC;
for (i = 0; i < context->n_contexts; i++) {
if (context->contexts[i].pa_system == pa_sys) {
*modreq_out = &context->contexts[i].modreq;
return 0;
}
}
return KRB5KRB_ERR_GENERIC;
}
/*
* Create a list of indices into the preauth_systems array, sorted by order of
* preference.
*/
static krb5_boolean
pa_list_includes(krb5_pa_data **pa_data, krb5_preauthtype pa_type)
{
while (*pa_data != NULL) {
if ((*pa_data)->pa_type == pa_type)
return TRUE;
pa_data++;
}
return FALSE;
}
static void
sort_pa_order(krb5_context context, krb5_kdc_req *request, int *pa_order)
{
size_t i, j, k, n_repliers, n_key_replacers;
/* First, set up the default order. */
i = 0;
for (j = 0; j < n_preauth_systems; j++) {
if (preauth_systems[j].return_padata != NULL)
pa_order[i++] = j;
}
n_repliers = i;
pa_order[n_repliers] = -1;
/* Reorder so that PA_REPLACES_KEY modules are listed first. */
for (i = 0; i < n_repliers; i++) {
/* If this module replaces the key, then it's okay to leave it where it
* is in the order. */
if (preauth_systems[pa_order[i]].flags & PA_REPLACES_KEY)
continue;
/* If not, search for a module which does, and swap in the first one we
* find. */
for (j = i + 1; j < n_repliers; j++) {
if (preauth_systems[pa_order[j]].flags & PA_REPLACES_KEY) {
k = pa_order[j];
pa_order[j] = pa_order[i];
pa_order[i] = k;
break;
}
}
/* If we didn't find one, we have moved all of the key-replacing
* modules, and i is the count of those modules. */
if (j == n_repliers)
break;
}
n_key_replacers = i;
if (request->padata != NULL) {
/* Now reorder the subset of modules which replace the key,
* bubbling those which handle pa_data types provided by the
* client ahead of the others.
*/
for (i = 0; i < n_key_replacers; i++) {
if (pa_list_includes(request->padata,
preauth_systems[pa_order[i]].type))
continue;
for (j = i + 1; j < n_key_replacers; j++) {
if (pa_list_includes(request->padata,
preauth_systems[pa_order[j]].type)) {
k = pa_order[j];
pa_order[j] = pa_order[i];
pa_order[i] = k;
break;
}
}
}
}
#ifdef DEBUG
krb5_klog_syslog(LOG_DEBUG, "original preauth mechanism list:");
for (i = 0; i < n_preauth_systems; i++) {
if (preauth_systems[i].return_padata != NULL)
krb5_klog_syslog(LOG_DEBUG, "... %s(%d)", preauth_systems[i].name,
preauth_systems[i].type);
}
krb5_klog_syslog(LOG_DEBUG, "sorted preauth mechanism list:");
for (i = 0; pa_order[i] != -1; i++) {
krb5_klog_syslog(LOG_DEBUG, "... %s(%d)",
preauth_systems[pa_order[i]].name,
preauth_systems[pa_order[i]].type);
}
#endif
}
const char *missing_required_preauth(krb5_db_entry *client,
krb5_db_entry *server,
krb5_enc_tkt_part *enc_tkt_reply)
{
#ifdef DEBUG
krb5_klog_syslog (
LOG_DEBUG,
"client needs %spreauth, %shw preauth; request has %spreauth, %shw preauth",
isflagset (client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) ? "" : "no ",
isflagset (client->attributes, KRB5_KDB_REQUIRES_HW_AUTH) ? "" : "no ",
isflagset (enc_tkt_reply->flags, TKT_FLG_PRE_AUTH) ? "" : "no ",
isflagset (enc_tkt_reply->flags, TKT_FLG_HW_AUTH) ? "" : "no ");
#endif
if (isflagset(client->attributes, KRB5_KDB_REQUIRES_PRE_AUTH) &&
!isflagset(enc_tkt_reply->flags, TKT_FLG_PRE_AUTH))
return "NEEDED_PREAUTH";
if (isflagset(client->attributes, KRB5_KDB_REQUIRES_HW_AUTH) &&
!isflagset(enc_tkt_reply->flags, TKT_FLG_HW_AUTH))
return "NEEDED_HW_PREAUTH";
return 0;
}
/* Return true if request's enctypes indicate support for etype-info2. */
static krb5_boolean
requires_info2(const krb5_kdc_req *request)
{
int i;
for (i = 0; i < request->nktypes; i++) {
if (enctype_requires_etype_info_2(request->ktype[i]))
return TRUE;
}
return FALSE;
}
/* Add PA-ETYPE-INFO2 and possibly PA-ETYPE-INFO entries to pa_list as
* appropriate for the request and client principal. */
static krb5_error_code
add_etype_info(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_pa_data ***pa_list)
{
krb5_error_code ret;
krb5_data *der;
if (rock->client_key == NULL)
return 0;
if (!requires_info2(rock->request)) {
/* Include PA-ETYPE-INFO only for old clients. */
ret = make_etype_info(context, FALSE, rock->client->princ,
rock->client_key, rock->client_keyblock->enctype,
&der);
if (ret)
return ret;
ret = k5_add_pa_data_from_data(pa_list, KRB5_PADATA_ETYPE_INFO, der);
krb5_free_data(context, der);
if (ret)
return ret;
}
/* Always include PA-ETYPE-INFO2. */
ret = make_etype_info(context, TRUE, rock->client->princ, rock->client_key,
rock->client_keyblock->enctype, &der);
if (ret)
return ret;
ret = k5_add_pa_data_from_data(pa_list, KRB5_PADATA_ETYPE_INFO2, der);
krb5_free_data(context, der);
return ret;
}
/* Add PW-SALT entries to pa_list as appropriate for the request and client
* principal. */
static krb5_error_code
add_pw_salt(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_pa_data ***pa_list)
{
krb5_error_code ret;
krb5_data *salt = NULL;
krb5_int16 salttype;
/* Only include this pa-data for old clients. */
if (rock->client_key == NULL || requires_info2(rock->request))
return 0;
ret = krb5_dbe_compute_salt(context, rock->client_key,
rock->request->client, &salttype, &salt);
if (ret)
return 0;
ret = k5_add_pa_data_from_data(pa_list, KRB5_PADATA_PW_SALT, salt);
krb5_free_data(context, salt);
return ret;
}
static krb5_error_code
add_freshness_token(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_pa_data ***pa_list)
{
krb5_error_code ret;
krb5_timestamp now;
krb5_key_data *kd;
krb5_keyblock kb;
krb5_checksum cksum;
krb5_data d;
krb5_pa_data *pa = NULL;
char ckbuf[4];
memset(&cksum, 0, sizeof(cksum));
memset(&kb, 0, sizeof(kb));
if (!rock->send_freshness_token)
return 0;
if (krb5int_find_pa_data(context, rock->request->padata,
KRB5_PADATA_AS_FRESHNESS) == NULL)
return 0;
/* Fetch and decrypt the current local krbtgt key. */
ret = krb5_dbe_find_enctype(context, rock->local_tgt, -1, -1, 0, &kd);
if (ret)
goto cleanup;
ret = krb5_dbe_decrypt_key_data(context, NULL, kd, &kb, NULL);
if (ret)
goto cleanup;
/* Compute a checksum over the current KDC time. */
ret = krb5_timeofday(context, &now);
if (ret)
goto cleanup;
store_32_be(now, ckbuf);
d = make_data(ckbuf, sizeof(ckbuf));
ret = krb5_c_make_checksum(context, 0, &kb, KRB5_KEYUSAGE_PA_AS_FRESHNESS,
&d, &cksum);
/* Compose a freshness token from the time, krbtgt kvno, and checksum. */
ret = k5_alloc_pa_data(KRB5_PADATA_AS_FRESHNESS, 8 + cksum.length, &pa);
if (ret)
goto cleanup;
store_32_be(now, pa->contents);
store_32_be(kd->key_data_kvno, pa->contents + 4);
memcpy(pa->contents + 8, cksum.contents, cksum.length);
ret = k5_add_pa_data_element(pa_list, &pa);
cleanup:
krb5_free_keyblock_contents(context, &kb);
krb5_free_checksum_contents(context, &cksum);
k5_free_pa_data_element(pa);
return ret;
}
struct hint_state {
kdc_hint_respond_fn respond;
void *arg;
kdc_realm_t *realm;
krb5_kdcpreauth_rock rock;
krb5_kdc_req *request;
krb5_pa_data ***e_data_out;
int hw_only;
preauth_system *ap;
krb5_pa_data **pa_data;
krb5_preauthtype pa_type;
};
static void
hint_list_finish(struct hint_state *state, krb5_error_code code)
{
kdc_hint_respond_fn oldrespond = state->respond;
void *oldarg = state->arg;
kdc_realm_t *kdc_active_realm = state->realm;
/* Add a freshness token if a preauth module requested it and the client
* request indicates support for it. */
if (!code)
code = add_freshness_token(kdc_context, state->rock, &state->pa_data);
if (!code) {
if (state->pa_data == NULL) {
krb5_klog_syslog(LOG_INFO,
_("%spreauth required but hint list is empty"),
state->hw_only ? "hw" : "");
}
*state->e_data_out = state->pa_data;
state->pa_data = NULL;
}
krb5_free_pa_data(kdc_context, state->pa_data);
free(state);
(*oldrespond)(oldarg);
}
static void
hint_list_next(struct hint_state *arg);
static void
finish_get_edata(void *arg, krb5_error_code code, krb5_pa_data *pa)
{
krb5_error_code ret;
struct hint_state *state = arg;
if (code == 0) {
if (pa == NULL) {
ret = k5_alloc_pa_data(state->pa_type, 0, &pa);
if (ret)
goto error;
}
ret = k5_add_pa_data_element(&state->pa_data, &pa);
k5_free_pa_data_element(pa);
if (ret)
goto error;
}
state->ap++;
hint_list_next(state);
return;
error:
hint_list_finish(state, ret);
}
static void
hint_list_next(struct hint_state *state)
{
preauth_system *ap = state->ap;
kdc_realm_t *kdc_active_realm = state->realm;
if (ap->type == -1) {
hint_list_finish(state, 0);
return;
}
if (state->hw_only && !(ap->flags & PA_HARDWARE))
goto next;
if (ap->flags & PA_PSEUDO)
goto next;
state->pa_type = ap->type;
if (ap->get_edata) {
ap->get_edata(kdc_context, state->request, &callbacks, state->rock,
ap->moddata, ap->type, finish_get_edata, state);
} else
finish_get_edata(state, 0, NULL);
return;
next:
state->ap++;
hint_list_next(state);
}
void
get_preauth_hint_list(krb5_kdc_req *request, krb5_kdcpreauth_rock rock,
krb5_pa_data ***e_data_out, kdc_hint_respond_fn respond,
void *arg)
{
kdc_realm_t *kdc_active_realm = rock->rstate->realm_data;
struct hint_state *state;
*e_data_out = NULL;
/* Allocate our state. */
state = calloc(1, sizeof(*state));
if (state == NULL)
goto error;
state->hw_only = isflagset(rock->client->attributes,
KRB5_KDB_REQUIRES_HW_AUTH);
state->respond = respond;
state->arg = arg;
state->request = request;
state->rock = rock;
state->realm = rock->rstate->realm_data;
state->e_data_out = e_data_out;
state->pa_data = NULL;
state->ap = preauth_systems;
/* Add an empty PA-FX-FAST element to advertise FAST support. */
if (k5_add_empty_pa_data(&state->pa_data, KRB5_PADATA_FX_FAST) != 0)
goto error;
if (add_etype_info(kdc_context, rock, &state->pa_data) != 0)
goto error;
hint_list_next(state);
return;
error:
if (state != NULL)
krb5_free_pa_data(kdc_context, state->pa_data);
free(state);
(*respond)(arg);
}
/*
* Add authorization data returned from preauth modules to the ticket
* It is assumed that ad is a "null-terminated" array of krb5_authdata ptrs
*/
static krb5_error_code
add_authorization_data(krb5_enc_tkt_part *enc_tkt_part, krb5_authdata **ad)
{
krb5_authdata **newad;
int oldones, newones;
int i;
if (enc_tkt_part == NULL || ad == NULL)
return EINVAL;
for (newones = 0; ad[newones] != NULL; newones++);
if (newones == 0)
return 0; /* nothing to add */
if (enc_tkt_part->authorization_data == NULL)
oldones = 0;
else
for (oldones = 0;
enc_tkt_part->authorization_data[oldones] != NULL; oldones++);
newad = malloc((oldones + newones + 1) * sizeof(krb5_authdata *));
if (newad == NULL)
return ENOMEM;
/* Copy any existing pointers */
for (i = 0; i < oldones; i++)
newad[i] = enc_tkt_part->authorization_data[i];
/* Add the new ones */
for (i = 0; i < newones; i++)
newad[oldones+i] = ad[i];
/* Terminate the new list */
newad[oldones+i] = NULL;
/* Free any existing list */
if (enc_tkt_part->authorization_data != NULL)
free(enc_tkt_part->authorization_data);
/* Install our new list */
enc_tkt_part->authorization_data = newad;
return 0;
}
struct padata_state {
kdc_preauth_respond_fn respond;
void *arg;
kdc_realm_t *realm;
krb5_kdcpreauth_modreq *modreq_ptr;
krb5_pa_data **padata;
int pa_found;
krb5_context context;
krb5_kdcpreauth_rock rock;
krb5_data *req_pkt;
krb5_kdc_req *request;
krb5_enc_tkt_part *enc_tkt_reply;
void **padata_context;
preauth_system *pa_sys;
krb5_pa_data **pa_e_data;
krb5_boolean typed_e_data_flag;
int pa_ok;
krb5_error_code saved_code;
krb5_pa_data ***e_data_out;
krb5_boolean *typed_e_data_out;
};
/* Return code if it is 0 or one of the codes we pass through to the client.
* Otherwise return KRB5KDC_ERR_PREAUTH_FAILED. */
static krb5_error_code
filter_preauth_error(krb5_error_code code)
{
/* The following switch statement allows us
* to return some preauth system errors back to the client.
*/
switch(code) {
case 0:
case KRB5KRB_AP_ERR_BAD_INTEGRITY:
case KRB5KRB_AP_ERR_SKEW:
case KRB5KDC_ERR_PREAUTH_REQUIRED:
case KRB5KDC_ERR_ETYPE_NOSUPP:
/* rfc 4556 */
case KRB5KDC_ERR_CLIENT_NOT_TRUSTED:
case KRB5KDC_ERR_INVALID_SIG:
case KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED:
case KRB5KDC_ERR_CANT_VERIFY_CERTIFICATE:
case KRB5KDC_ERR_INVALID_CERTIFICATE:
case KRB5KDC_ERR_REVOKED_CERTIFICATE:
case KRB5KDC_ERR_REVOCATION_STATUS_UNKNOWN:
case KRB5KDC_ERR_CLIENT_NAME_MISMATCH:
case KRB5KDC_ERR_INCONSISTENT_KEY_PURPOSE:
case KRB5KDC_ERR_DIGEST_IN_CERT_NOT_ACCEPTED:
case KRB5KDC_ERR_PA_CHECKSUM_MUST_BE_INCLUDED:
case KRB5KDC_ERR_DIGEST_IN_SIGNED_DATA_NOT_ACCEPTED:
case KRB5KDC_ERR_PUBLIC_KEY_ENCRYPTION_NOT_SUPPORTED:
/* earlier drafts of what became rfc 4556 */
case KRB5KDC_ERR_CERTIFICATE_MISMATCH:
case KRB5KDC_ERR_KDC_NOT_TRUSTED:
case KRB5KDC_ERR_REVOCATION_STATUS_UNAVAILABLE:
/* This value is shared with
* KRB5KDC_ERR_DH_KEY_PARAMETERS_NOT_ACCEPTED. */
/* case KRB5KDC_ERR_KEY_TOO_WEAK: */
case KRB5KDC_ERR_DISCARD:
/* pkinit alg-agility */
case KRB5KDC_ERR_NO_ACCEPTABLE_KDF:
/* rfc 6113 */
case KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED:
return code;
default:
return KRB5KDC_ERR_PREAUTH_FAILED;
}
}
/*
* If the client performed optimistic pre-authentication for a multi-round-trip
* mechanism, it may need key information to complete the exchange, so send it
* a PA-ETYPE-INFO2 element in addition to the pa-data from the module.
*/
static krb5_error_code
maybe_add_etype_info2(struct padata_state *state, krb5_error_code code)
{
krb5_error_code ret;
krb5_context context = state->context;
krb5_kdcpreauth_rock rock = state->rock;
krb5_data *der;
/* Only add key information when requesting another preauth round trip. */
if (code != KRB5KDC_ERR_MORE_PREAUTH_DATA_REQUIRED)
return 0;
/* Don't try to add key information when there is no key. */
if (rock->client_key == NULL)
return 0;
/* If the client sent a cookie, it has already seen a KDC response with key
* information. */
if (krb5int_find_pa_data(context, state->request->padata,
KRB5_PADATA_FX_COOKIE) != NULL)
return 0;
ret = make_etype_info(context, TRUE, rock->client->princ, rock->client_key,
rock->client_keyblock->enctype, &der);
if (ret)
return ret;
ret = k5_add_pa_data_from_data(&state->pa_e_data, KRB5_PADATA_ETYPE_INFO2,
der);
krb5_free_data(context, der);
return ret;
}
/* Release state and respond to the AS-REQ processing code with the result of
* checking pre-authentication data. */
static void
finish_check_padata(struct padata_state *state, krb5_error_code code)
{
kdc_preauth_respond_fn respond;
void *arg;
if (state->pa_ok || !state->pa_found) {
/* Return successfully. If we didn't match a preauth system, we may
* return PREAUTH_REQUIRED later, but we didn't fail to verify. */
code = 0;
goto cleanup;
}
/* Add key information to the saved error pa-data if required. */
if (maybe_add_etype_info2(state, code) != 0) {
code = KRB5KDC_ERR_PREAUTH_FAILED;
goto cleanup;
}
/* Return any saved error pa-data, stealing the pointer from state. */
*state->e_data_out = state->pa_e_data;
*state->typed_e_data_out = state->typed_e_data_flag;
state->pa_e_data = NULL;
cleanup:
/* Discard saved error pa-data if we aren't returning it, free state, and
* respond to the AS-REQ processing code. */
respond = state->respond;
arg = state->arg;
krb5_free_pa_data(state->context, state->pa_e_data);
free(state);
(*respond)(arg, filter_preauth_error(code));
}
static void
next_padata(struct padata_state *state);
static void
finish_verify_padata(void *arg, krb5_error_code code,
krb5_kdcpreauth_modreq modreq, krb5_pa_data **e_data,
krb5_authdata **authz_data)
{
struct padata_state *state = arg;
const char *emsg;
krb5_boolean typed_e_data_flag;
assert(state);
*state->modreq_ptr = modreq;
if (code) {
emsg = krb5_get_error_message(state->context, code);
krb5_klog_syslog(LOG_INFO, "preauth (%s) verify failure: %s",
state->pa_sys->name, emsg);
krb5_free_error_message(state->context, emsg);
/* Ignore authorization data returned from modules that fail */
if (authz_data != NULL) {
krb5_free_authdata(state->context, authz_data);
authz_data = NULL;
}
typed_e_data_flag = ((state->pa_sys->flags & PA_TYPED_E_DATA) != 0);
/*
* We'll return edata from either the first PA_REQUIRED module
* that fails, or the first non-PA_REQUIRED module that fails.
* Hang on to edata from the first non-PA_REQUIRED module.
* If we've already got one saved, simply discard this one.
*/
if (state->pa_sys->flags & PA_REQUIRED) {
/* free up any previous edata we might have been saving */
if (state->pa_e_data != NULL)
krb5_free_pa_data(state->context, state->pa_e_data);
state->pa_e_data = e_data;
state->typed_e_data_flag = typed_e_data_flag;
/* Make sure we use the current retval */
state->pa_ok = 0;
finish_check_padata(state, code);
return;
} else if (state->pa_e_data == NULL) {
/* save the first error code and e-data */
state->pa_e_data = e_data;
state->typed_e_data_flag = typed_e_data_flag;
state->saved_code = code;
} else if (e_data != NULL) {
/* discard this extra e-data from non-PA_REQUIRED module */
krb5_free_pa_data(state->context, e_data);
}
} else {
#ifdef DEBUG
krb5_klog_syslog (LOG_DEBUG, ".. .. ok");
#endif
/* Ignore any edata returned on success */
if (e_data != NULL)
krb5_free_pa_data(state->context, e_data);
/* Add any authorization data to the ticket */
if (authz_data != NULL) {
add_authorization_data(state->enc_tkt_reply, authz_data);
free(authz_data);
}
state->pa_ok = 1;
if (state->pa_sys->flags & PA_SUFFICIENT) {
finish_check_padata(state, state->saved_code);
return;
}
}
next_padata(state);
}
static void
next_padata(struct padata_state *state)
{
assert(state);
if (!state->padata)
state->padata = state->request->padata;
else
state->padata++;
if (!*state->padata) {
finish_check_padata(state, state->saved_code);
return;
}
#ifdef DEBUG
krb5_klog_syslog (LOG_DEBUG, ".. pa_type 0x%x", (*state->padata)->pa_type);
#endif
if (find_pa_system((*state->padata)->pa_type, &state->pa_sys))
goto next;
if (find_modreq(state->pa_sys, *state->padata_context, &state->modreq_ptr))
goto next;
#ifdef DEBUG
krb5_klog_syslog (LOG_DEBUG, ".. pa_type %s", state->pa_sys->name);
#endif
if (state->pa_sys->verify_padata == 0)
goto next;
state->pa_found++;
state->pa_sys->verify_padata(state->context, state->req_pkt,
state->request, state->enc_tkt_reply,
*state->padata, &callbacks, state->rock,
state->pa_sys->moddata, finish_verify_padata,
state);
return;
next:
next_padata(state);
}
/*
* This routine is called to verify the preauthentication information
* for a V5 request.
*
* Returns 0 if the pre-authentication is valid, non-zero to indicate
* an error code of some sort.
*/
void
check_padata(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_data *req_pkt, krb5_kdc_req *request,
krb5_enc_tkt_part *enc_tkt_reply, void **padata_context,
krb5_pa_data ***e_data, krb5_boolean *typed_e_data,
kdc_preauth_respond_fn respond, void *arg)
{
struct padata_state *state;
if (request->padata == 0) {
(*respond)(arg, 0);
return;
}
if (make_padata_context(context, padata_context) != 0) {
(*respond)(arg, KRB5KRB_ERR_GENERIC);
return;
}
state = calloc(1, sizeof(*state));
if (state == NULL) {
(*respond)(arg, ENOMEM);
return;
}
state->respond = respond;
state->arg = arg;
state->context = context;
state->rock = rock;
state->req_pkt = req_pkt;
state->request = request;
state->enc_tkt_reply = enc_tkt_reply;
state->padata_context = padata_context;
state->e_data_out = e_data;
state->typed_e_data_out = typed_e_data;
state->realm = rock->rstate->realm_data;
#ifdef DEBUG
krb5_klog_syslog (LOG_DEBUG, "checking padata");
#endif
next_padata(state);
}
/* Return true if k1 and k2 have the same type and contents. */
static krb5_boolean
keyblock_equal(const krb5_keyblock *k1, const krb5_keyblock *k2)
{
if (k1->enctype != k2->enctype)
return FALSE;
if (k1->length != k2->length)
return FALSE;
return memcmp(k1->contents, k2->contents, k1->length) == 0;
}
/*
* return_padata creates any necessary preauthentication
* structures which should be returned by the KDC to the client
*/
krb5_error_code
return_padata(krb5_context context, krb5_kdcpreauth_rock rock,
krb5_data *req_pkt, krb5_kdc_req *request, krb5_kdc_rep *reply,
krb5_keyblock *encrypting_key, void **padata_context)
{
krb5_error_code retval;
krb5_pa_data ** padata;
krb5_pa_data ** send_pa_list = NULL;
krb5_pa_data * send_pa;
krb5_pa_data * pa = 0;
krb5_pa_data null_item;
preauth_system * ap;
int * pa_order = NULL;
int * pa_type;
int size = 0;
krb5_kdcpreauth_modreq *modreq_ptr;
krb5_boolean key_modified;
krb5_keyblock original_key;
memset(&original_key, 0, sizeof(original_key));
if ((!*padata_context) &&
(make_padata_context(context, padata_context) != 0)) {
return KRB5KRB_ERR_GENERIC;
}
for (ap = preauth_systems; ap->type != -1; ap++) {
if (ap->return_padata)
size++;
}
pa_order = k5calloc(size + 1, sizeof(int), &retval);
if (pa_order == NULL)
goto cleanup;
sort_pa_order(context, request, pa_order);
retval = krb5_copy_keyblock_contents(context, encrypting_key,
&original_key);
if (retval)
goto cleanup;
key_modified = FALSE;
null_item.contents = NULL;
null_item.length = 0;
for (pa_type = pa_order; *pa_type != -1; pa_type++) {
ap = &preauth_systems[*pa_type];
if (key_modified && (ap->flags & PA_REPLACES_KEY))
continue;
if (ap->return_padata == 0)
continue;
if (find_modreq(ap, *padata_context, &modreq_ptr))
continue;
pa = &null_item;
null_item.pa_type = ap->type;
if (request->padata) {
for (padata = request->padata; *padata; padata++) {
if ((*padata)->pa_type == ap->type) {
pa = *padata;
break;
}
}
}
send_pa = NULL;
retval = ap->return_padata(context, pa, req_pkt, request, reply,
encrypting_key, &send_pa, &callbacks, rock,
ap->moddata, *modreq_ptr);
if (retval)
goto cleanup;
if (send_pa != NULL) {
retval = k5_add_pa_data_element(&send_pa_list, &send_pa);
k5_free_pa_data_element(send_pa);
if (retval)
goto cleanup;
}
if (!key_modified && !keyblock_equal(&original_key, encrypting_key))
key_modified = TRUE;
}
/*
* Add etype-info and pw-salt pa-data as needed. If we replaced the reply
* key, we can't send consistent etype-info; the salt from the client key
* data doesn't correspond to the replaced reply key, and RFC 4120 section
* 5.2.7.5 forbids us from sending etype-info describing the initial reply
* key in an AS-REP if it doesn't have the same enctype as the replaced
* reply key. For all current and forseeable preauth mechs, we can assume
* the client received etype-info2 in an earlier step and already computed
* the initial reply key if it needed it. The client can determine the
* enctype of the replaced reply key from the etype field of the enc-part
* field of the AS-REP.
*/
if (!key_modified) {
retval = add_etype_info(context, rock, &send_pa_list);
if (retval)
goto cleanup;
retval = add_pw_salt(context, rock, &send_pa_list);
if (retval)
goto cleanup;
}
if (send_pa_list != NULL) {
reply->padata = send_pa_list;
send_pa_list = 0;
}
cleanup:
krb5_free_keyblock_contents(context, &original_key);
free(pa_order);
krb5_free_pa_data(context, send_pa_list);
return (retval);
}
static krb5_error_code
_make_etype_info_entry(krb5_context context,
krb5_principal client_princ, krb5_key_data *client_key,
krb5_enctype etype, krb5_etype_info_entry **entry_out,
int etype_info2)
{
krb5_error_code retval;
krb5_int16 salttype;
krb5_data *salt = NULL;
krb5_etype_info_entry *entry = NULL;
*entry_out = NULL;
entry = malloc(sizeof(*entry));
if (entry == NULL)
return ENOMEM;
entry->magic = KV5M_ETYPE_INFO_ENTRY;
entry->etype = etype;
entry->length = KRB5_ETYPE_NO_SALT;
entry->salt = NULL;
entry->s2kparams = empty_data();
retval = krb5_dbe_compute_salt(context, client_key, client_princ,
&salttype, &salt);
if (retval)
goto cleanup;
entry->length = salt->length;
entry->salt = (unsigned char *)salt->data;
salt->data = NULL;
*entry_out = entry;
entry = NULL;
cleanup:
if (entry != NULL)
krb5_free_data_contents(context, &entry->s2kparams);
free(entry);
krb5_free_data(context, salt);
return retval;
}
/* Encode an etype-info or etype-info2 message for client_key with the given
* enctype, using client to compute the salt if necessary. */
static krb5_error_code
make_etype_info(krb5_context context, krb5_boolean etype_info2,
krb5_principal client, krb5_key_data *client_key,
krb5_enctype enctype, krb5_data **der_out)
{
krb5_error_code retval;
krb5_etype_info_entry **entry = NULL;
*der_out = NULL;
entry = k5calloc(2, sizeof(*entry), &retval);
if (entry == NULL)
goto cleanup;
retval = _make_etype_info_entry(context, client, client_key, enctype,
&entry[0], etype_info2);
if (retval != 0)
goto cleanup;
if (etype_info2)
retval = encode_krb5_etype_info2(entry, der_out);
else
retval = encode_krb5_etype_info(entry, der_out);
cleanup:
krb5_free_etype_info(context, entry);
return retval;
}
/*
* Returns TRUE if the PAC should be included
*/
krb5_boolean
include_pac_p(krb5_context context, krb5_kdc_req *request)
{
krb5_error_code code;
krb5_pa_data **padata;
krb5_boolean retval = TRUE; /* default is to return PAC */
krb5_data data;
krb5_pa_pac_req *req = NULL;
if (request->padata == NULL) {
return retval;
}
for (padata = request->padata; *padata != NULL; padata++) {
if ((*padata)->pa_type == KRB5_PADATA_PAC_REQUEST) {
data.data = (char *)(*padata)->contents;
data.length = (*padata)->length;
code = decode_krb5_pa_pac_req(&data, &req);
if (code == 0) {
retval = req->include_pac;
krb5_free_pa_pac_req(context, req);
req = NULL;
}
break;
}
}
return retval;
}
static krb5_error_code
return_referral_enc_padata( krb5_context context,
krb5_enc_kdc_rep_part *reply,
krb5_db_entry *server)
{
krb5_error_code code;
krb5_tl_data tl_data;
krb5_pa_data *pa;
tl_data.tl_data_type = KRB5_TL_SVR_REFERRAL_DATA;
code = krb5_dbe_lookup_tl_data(context, server, &tl_data);
if (code || tl_data.tl_data_length == 0)
return 0;
code = k5_alloc_pa_data(KRB5_PADATA_SVR_REFERRAL_INFO,
tl_data.tl_data_length, &pa);
if (code)
return code;
memcpy(pa->contents, tl_data.tl_data_contents, tl_data.tl_data_length);
code = k5_add_pa_data_element(&reply->enc_padata, &pa);
k5_free_pa_data_element(pa);
return code;
}
krb5_error_code
return_enc_padata(krb5_context context, krb5_data *req_pkt,
krb5_kdc_req *request, krb5_keyblock *reply_key,
krb5_db_entry *server, krb5_enc_kdc_rep_part *reply_encpart,
krb5_boolean is_referral)
{
krb5_error_code code = 0;
/* This should be initialized and only used for Win2K compat and other
* specific standardized uses such as FAST negotiation. */
if (is_referral) {
code = return_referral_enc_padata(context, reply_encpart, server);
if (code)
return code;
}
code = kdc_handle_protected_negotiation(context, req_pkt, request, reply_key,
&reply_encpart->enc_padata);
if (code)
goto cleanup;
code = kdc_add_pa_pac_options(context, request,
&reply_encpart->enc_padata);
if (code)
goto cleanup;
/*Add potentially other enc_padata providers*/
cleanup:
return code;
}