Commit message: Remove incorrect KDC assertion

The assertion in return_enc_padata() is reachable because
kdc_make_s4u2self_rep() may have previously added encrypted padata.
It is no longer necessary because the code uses add_pa_data_element()
instead of allocating a new list.

CVE-2018-20217:

In MIT krb5 1.8 or later, an authenticated user who can obtain a TGT
using an older encryption type (DES, DES3, or RC4) can cause an
assertion failure in the KDC by sending an S4U2Self request.

[ghudson@mit.edu: rewrote commit message with CVE description]

ticket: 8767 (new)
tags: pullup
target_version: 1.17
target_version: 1.16-next
target_version: 1.15-next
CVE: CVE-2018-20217
CWE: CWE-617
Vulnerability description: A Reachable Assertion issue was discovered in the KDC in MIT Kerberos 5 (aka krb5) before 1.17. If an attacker can obtain a krbtgt ticket using an older encryption type (single-DES, triple-DES, or RC4), the attacker can crash the KDC by making an S4U2Self request.

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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 = 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);
    /* add_pa_data_element() claims pa on success or failure. */
    return add_pa_data_element(&reply->enc_padata, pa);
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

make_etype_info(krb5_context context, krb5_preauthtype pa_type,
                krb5_principal client, krb5_key_data *client_key,
                krb5_enctype enctype, krb5_pa_data **pa_out)
{
    krb5_error_code retval;
    krb5_etype_info_entry **entry = NULL;
    krb5_data *der_etype_info = NULL;
    int etype_info2 = (pa_type == KRB5_PADATA_ETYPE_INFO2);

    *pa_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_etype_info);
    else
        retval = encode_krb5_etype_info(entry, &der_etype_info);
    if (retval)
        goto cleanup;

    /* Steal the data from der_etype_info to create a pa-data element. */
    retval = alloc_pa_data(pa_type, 0, pa_out);
    if (retval)
        goto cleanup;
    (*pa_out)->contents = (uint8_t *)der_etype_info->data;
    (*pa_out)->length = der_etype_info->length;
    der_etype_info->data = NULL;

cleanup:
    krb5_free_etype_info(context, entry);
    krb5_free_data(context, der_etype_info);
    return retval;
}

Project: krb5
File: kdc_preauth.c

request_body(krb5_context context, krb5_kdcpreauth_rock rock)
{
    return rock->inner_body;
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

add_etype_info(krb5_context context, krb5_kdcpreauth_rock rock,
               krb5_pa_data ***pa_list)
{
    krb5_error_code ret;
    krb5_pa_data *pa;

    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, KRB5_PADATA_ETYPE_INFO,
                              rock->client->princ, rock->client_key,
                              rock->client_keyblock->enctype, &pa);
        if (ret)
            return ret;
        /* add_pa_data_element() claims pa on success or failure. */
        ret = add_pa_data_element(pa_list, pa);
        if (ret)
            return ret;
    }

    /* Always include PA-ETYPE-INFO2. */
    ret = make_etype_info(context, KRB5_PADATA_ETYPE_INFO2,
                          rock->client->princ, rock->client_key,
                          rock->client_keyblock->enctype, &pa);
    if (ret)
        return ret;
    /* add_pa_data_element() claims pa on success or failure. */
    return add_pa_data_element(pa_list, pa);
}

Project: krb5
File: kdc_preauth.c

add_pw_salt(krb5_context context, krb5_kdcpreauth_rock rock,
            krb5_pa_data ***pa_list)
{
    krb5_error_code ret;
    krb5_pa_data *pa;
    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;

    if (salttype == KRB5_KDB_SALTTYPE_AFS3) {
        ret = alloc_pa_data(KRB5_PADATA_AFS3_SALT, salt->length + 1, &pa);
        if (ret)
            goto cleanup;
        memcpy(pa->contents, salt->data, salt->length);
        pa->contents[salt->length] = '\0';
    } else {
        /* Steal memory from salt to make the pa-data entry. */
        ret = alloc_pa_data(KRB5_PADATA_PW_SALT, 0, &pa);
        if (ret)
            goto cleanup;
        pa->length = salt->length;
        pa->contents = (uint8_t *)salt->data;
        salt->data = NULL;
    }

    /* add_pa_data_element() claims pa on success or failure. */
    ret = add_pa_data_element(pa_list, pa);

cleanup:
    krb5_free_data(context, salt);
    return ret;
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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;
    /*Add potentially other enc_padata providers*/
cleanup:
    return code;
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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));
}

Project: krb5
File: kdc_preauth.c

fast_armor(krb5_context context, krb5_kdcpreauth_rock rock)
{
    return rock->rstate->armor_key;
}

Project: krb5
File: kdc_preauth.c

client_name(krb5_context context, krb5_kdcpreauth_rock rock)
{
    return rock->client->princ;
}

Project: krb5
File: kdc_preauth.c

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;
    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 = 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);

    /* add_pa_data_element() claims pa on success or failure. */
    ret = add_pa_data_element(pa_list, pa);

cleanup:
    krb5_free_keyblock_contents(context, &kb);
    krb5_free_checksum_contents(context, &cksum);
    return ret;
}

Project: krb5
File: kdc_preauth.c

free_string(krb5_context context, krb5_kdcpreauth_rock rock, char *string)
{
    krb5_dbe_free_string(context, string);
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

client_entry(krb5_context context, krb5_kdcpreauth_rock rock)
{
    return rock->client;
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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) {
            /* add_pa_data_element() claims send_pa on success or failure. */
            retval = add_pa_data_element(&send_pa_list, 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);
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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, KRB5_KDB_FLAG_ALIAS_OK, &ent))
        return FALSE;
    match = krb5_principal_compare(context, ent->princ, client);
    krb5_db_free_principal(context, ent);
    return match;
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

event_context(krb5_context context, krb5_kdcpreauth_rock rock)
{
    return rock->vctx;
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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_pa_data *pa;

    /* 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, KRB5_PADATA_ETYPE_INFO2,
                          rock->client->princ, rock->client_key,
                          rock->client_keyblock->enctype, &pa);
    if (ret)
        return ret;

    /* add_pa_data_element() claims pa on success or failure. */
    return add_pa_data_element(&state->pa_e_data, pa);
}

Project: krb5
File: kdc_preauth.c

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);
}

Project: krb5
File: kdc_preauth.c

send_freshness_token(krb5_context context, krb5_kdcpreauth_rock rock)
{
    rock->send_freshness_token = TRUE;
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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;
    krb5_pa_data *pa;

    *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 (alloc_pa_data(KRB5_PADATA_FX_FAST, 0, &pa) != 0)
        goto error;
    /* add_pa_data_element() claims pa on success or failure. */
    if (add_pa_data_element(&state->pa_data, pa) != 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);
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

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;
    }
}

Project: krb5
File: kdc_preauth.c

max_time_skew(krb5_context context, krb5_kdcpreauth_rock rock)
{
    return context->clockskew;
}

Project: krb5
File: kdc_preauth.c

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 = alloc_pa_data(state->pa_type, 0, &pa);
            if (ret)
                goto error;
        }
        /* add_pa_data_element() claims pa on success or failure. */
        ret = add_pa_data_element(&state->pa_data, pa);
        if (ret)
            goto error;
    }

    state->ap++;
    hint_list_next(state);
    return;

error:
    hint_list_finish(state, ret);
}

Project: krb5
File: kdc_preauth.c

_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;
    if (etype_info2 && salttype == KRB5_KDB_SALTTYPE_AFS3) {
        switch (etype) {
        case ENCTYPE_DES_CBC_CRC:
        case ENCTYPE_DES_CBC_MD4:
        case ENCTYPE_DES_CBC_MD5:
            retval = alloc_data(&entry->s2kparams, 1);
            if (retval)
                goto cleanup;
            entry->s2kparams.data[0] = 1;
            break;
        default:
            break;
        }
    }

    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;
}

Project: krb5
File: kdc_preauth.c

client_keyblock(krb5_context context, krb5_kdcpreauth_rock rock)
{
    return rock->client_keyblock;
}

Project: krb5
File: kdc_preauth.c

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;
}

Project: krb5
File: kdc_preauth.c

add_auth_indicator(krb5_context context, krb5_kdcpreauth_rock rock,
                   const char *indicator)
{
    return authind_add(context, indicator, rock->auth_indicators);
}