X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=src%2Flib%2Fkrb5%2Fkrb%2Fget_in_tkt.c;h=738bd9c3778ed9020c730570d11edd1ddee766a3;hb=bc096a77ffdab283d77c2e0fc1fdd15b9f77eb41;hp=5419f3723c499ffab79e739ce9f989aa650f6c75;hpb=ca9c1212fcb150c3514f8a9ef2f25709b5146de4;p=krb5.git diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c index 5419f3723..738bd9c37 100644 --- a/src/lib/krb5/krb/get_in_tkt.c +++ b/src/lib/krb5/krb/get_in_tkt.c @@ -1,6 +1,6 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/krb/get_in_tkt.c */ /* - * lib/krb5/krb/get_in_tkt.c - * * Copyright 1990,1991, 2003, 2008 by the Massachusetts Institute of Technology. * All Rights Reserved. * @@ -8,7 +8,7 @@ * 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 @@ -22,9 +22,6 @@ * 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. - * - * - * krb5_get_in_tkt() */ #include @@ -32,70 +29,24 @@ #include "k5-int.h" #include "int-proto.h" #include "os-proto.h" - -#if APPLE_PKINIT -#define IN_TKT_DEBUG 0 -#if IN_TKT_DEBUG -#define inTktDebug(args...) printf(args) -#else -#define inTktDebug(args...) -#endif -#endif /* APPLE_PKINIT */ - -/* - All-purpose initial ticket routine, usually called via - krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey. - - Attempts to get an initial ticket for creds->client to use server - creds->server, (realm is taken from creds->client), with options - options, and using creds->times.starttime, creds->times.endtime, - creds->times.renew_till as from, till, and rtime. - creds->times.renew_till is ignored unless the RENEWABLE option is requested. - - key_proc is called to fill in the key to be used for decryption. - keyseed is passed on to key_proc. - - decrypt_proc is called to perform the decryption of the response (the - encrypted part is in dec_rep->enc_part; the decrypted part should be - allocated and filled into dec_rep->enc_part2 - arg is passed on to decrypt_proc. - - If addrs is non-NULL, it is used for the addresses requested. If it is - null, the system standard addresses are used. - - A succesful call will place the ticket in the credentials cache ccache - and fill in creds with the ticket information used/returned.. - - returns system errors, encryption errors - - */ - +#include "fast.h" +#include "init_creds_ctx.h" /* some typedef's for the function args to make things look a bit cleaner */ -typedef krb5_error_code (*git_key_proc) (krb5_context, - krb5_enctype, - krb5_data *, - krb5_const_pointer, - krb5_keyblock **); - -typedef krb5_error_code (*git_decrypt_proc) (krb5_context, - const krb5_keyblock *, - krb5_const_pointer, - krb5_kdc_rep * ); - -static krb5_error_code make_preauth_list (krb5_context, - krb5_preauthtype *, - int, krb5_pa_data ***); +static krb5_error_code make_preauth_list (krb5_context, + krb5_preauthtype *, + int, krb5_pa_data ***); static krb5_error_code sort_krb5_padata_sequence(krb5_context context, - krb5_data *realm, - krb5_pa_data **padata); + krb5_data *realm, + krb5_pa_data **padata); /* * This function performs 32 bit bounded addition so we can generate * lifetimes without overflowing krb5_int32 */ -static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y) +static krb5_int32 +krb5int_addint32 (krb5_int32 x, krb5_int32 y) { if ((x > 0) && (y > (KRB5_INT32_MAX - x))) { /* sum will be be greater than KRB5_INT32_MAX */ @@ -104,189 +55,102 @@ static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y) /* sum will be less than KRB5_INT32_MIN */ return KRB5_INT32_MIN; } - - return x + y; -} -#if APPLE_PKINIT -/* - * Common code to generate krb5_kdc_req.nonce. Like the original MIT code this - * just uses krb5_timeofday(); it should use a PRNG. Even more unfortunately this - * value is used interchangeably with an explicit now_time throughout this module... - */ -static krb5_error_code -gen_nonce(krb5_context context, - krb5_int32 *nonce) -{ - krb5_int32 time_now; - krb5_error_code retval = krb5_timeofday(context, &time_now); - if(retval) { - return retval; - } - *nonce = time_now; - return 0; + return x + y; } -#endif /* APPLE_PKINIT */ -/* - * This function sends a request to the KDC, and gets back a response; - * the response is parsed into ret_err_reply or ret_as_reply if the - * reponse is a KRB_ERROR or a KRB_AS_REP packet. If it is some other - * unexpected response, an error is returned. - */ static krb5_error_code -send_as_request(krb5_context context, - krb5_data *packet, const krb5_data *realm, - krb5_error ** ret_err_reply, - krb5_kdc_rep ** ret_as_reply, - int *use_master) +decrypt_as_reply(krb5_context context, krb5_kdc_req *request, + krb5_kdc_rep *as_reply, krb5_keyblock *key) { - krb5_kdc_rep *as_reply = 0; - krb5_error_code retval; - krb5_data reply; - char k4_version; /* same type as *(krb5_data::data) */ - int tcp_only = 0; - - reply.data = 0; - - /* set the nonce if the caller expects us to do it */ - - k4_version = packet->data[0]; -send_again: - retval = krb5_sendto_kdc(context, packet, - realm, - &reply, use_master, tcp_only); -#if APPLE_PKINIT - inTktDebug("krb5_sendto_kdc returned %d\n", (int)retval); -#endif /* APPLE_PKINIT */ - - if (retval) - goto cleanup; - - /* now decode the reply...could be error or as_rep */ - if (krb5_is_krb_error(&reply)) { - krb5_error *err_reply; - - if ((retval = decode_krb5_error(&reply, &err_reply))) - /* some other error code--??? */ - goto cleanup; - - if (ret_err_reply) { - if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG - && tcp_only == 0) { - tcp_only = 1; - krb5_free_error(context, err_reply); - free(reply.data); - reply.data = 0; - goto send_again; - } - *ret_err_reply = err_reply; - } else - krb5_free_error(context, err_reply); - goto cleanup; - } - - /* - * Check to make sure it isn't a V4 reply. - */ - if (!krb5_is_as_rep(&reply)) { -/* these are in as well but it isn't worth including. */ -#define V4_KRB_PROT_VERSION 4 -#define V4_AUTH_MSG_ERR_REPLY (5<<1) - /* check here for V4 reply */ - unsigned int t_switch; - - /* From v4 g_in_tkt.c: This used to be - switch (pkt_msg_type(rpkt) & ~1) { - but SCO 3.2v4 cc compiled that incorrectly. */ - t_switch = reply.data[1]; - t_switch &= ~1; - - if (t_switch == V4_AUTH_MSG_ERR_REPLY - && (reply.data[0] == V4_KRB_PROT_VERSION - || reply.data[0] == k4_version)) { - retval = KRB5KRB_AP_ERR_V4_REPLY; - } else { - retval = KRB5KRB_AP_ERR_MSG_TYPE; - } - goto cleanup; - } - - /* It must be a KRB_AS_REP message, or an bad returned packet */ - if ((retval = decode_krb5_as_rep(&reply, &as_reply))) - /* some other error code ??? */ - goto cleanup; - - if (as_reply->msg_type != KRB5_AS_REP) { - retval = KRB5KRB_AP_ERR_MSG_TYPE; - krb5_free_kdc_rep(context, as_reply); - goto cleanup; - } - - if (ret_as_reply) - *ret_as_reply = as_reply; - else - krb5_free_kdc_rep(context, as_reply); + if (as_reply->enc_part2) + return 0; -cleanup: - if (reply.data) - free(reply.data); - return retval; + return krb5_kdc_rep_decrypt_proc(context, key, NULL, as_reply); } +/** + * Fully anonymous replies include a pa_pkinit_kx padata type including the KDC + * contribution key. This routine confirms that the session key is of the + * right form for fully anonymous requests. It is here rather than in the + * preauth code because the session key cannot be verified until the AS reply + * is decrypted and the preauth code all runs before the AS reply is decrypted. + */ static krb5_error_code -decrypt_as_reply(krb5_context context, - krb5_kdc_req *request, - krb5_kdc_rep *as_reply, - git_key_proc key_proc, - krb5_const_pointer keyseed, - krb5_keyblock * key, - git_decrypt_proc decrypt_proc, - krb5_const_pointer decryptarg) +verify_anonymous( krb5_context context, krb5_kdc_req *request, + krb5_kdc_rep *reply, krb5_keyblock *as_key) { - krb5_error_code retval; - krb5_keyblock * decrypt_key = 0; - krb5_data salt; - - if (as_reply->enc_part2) - return 0; - - if (key) - decrypt_key = key; - else { - if ((retval = krb5_principal2salt(context, request->client, &salt))) - return(retval); - - retval = (*key_proc)(context, as_reply->enc_part.enctype, - &salt, keyseed, &decrypt_key); - free(salt.data); - if (retval) - goto cleanup; + krb5_error_code ret = 0; + krb5_pa_data *pa; + krb5_data scratch; + krb5_keyblock *kdc_key = NULL, *expected = NULL; + krb5_enc_data *enc = NULL; + krb5_keyblock *session = reply->enc_part2->session; + + if (!krb5_principal_compare_any_realm(context, request->client, + krb5_anonymous_principal())) + return 0; /* Only applies to fully anonymous */ + pa = krb5int_find_pa_data(context, reply->padata, KRB5_PADATA_PKINIT_KX); + if (pa == NULL) + goto verification_error; + scratch.length = pa->length; + scratch.data = (char *) pa->contents; + ret = decode_krb5_enc_data( &scratch, &enc); + if (ret) + goto cleanup; + scratch.data = k5alloc(enc->ciphertext.length, &ret); + if (ret) + goto cleanup; + scratch.length = enc->ciphertext.length; + ret = krb5_c_decrypt(context, as_key, KRB5_KEYUSAGE_PA_PKINIT_KX, + NULL /*cipherstate*/, enc, &scratch); + if (ret) { + free(scratch.data); + goto cleanup; } - - if ((retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply))) - goto cleanup; - + ret = decode_krb5_encryption_key( &scratch, &kdc_key); + zap(scratch.data, scratch.length); + free(scratch.data); + if (ret) + goto cleanup; + ret = krb5_c_fx_cf2_simple(context, kdc_key, "PKINIT", + as_key, "KEYEXCHANGE", &expected); + if (ret) + goto cleanup; + if ((expected->enctype != session->enctype) || + (expected->length != session->length) || + (memcmp(expected->contents, session->contents, expected->length) != 0)) + goto verification_error; cleanup: - if (!key && decrypt_key) - krb5_free_keyblock(context, decrypt_key); - return (retval); + if (kdc_key) + krb5_free_keyblock(context, kdc_key); + if (expected) + krb5_free_keyblock(context, expected); + if (enc) + krb5_free_enc_data(context, enc); + return ret; +verification_error: + ret = KRB5_KDCREP_MODIFIED; + krb5_set_error_message(context, ret, _("Reply has wrong form of session " + "key for anonymous request")); + goto cleanup; } static krb5_error_code -verify_as_reply(krb5_context context, - krb5_timestamp time_now, - krb5_kdc_req *request, - krb5_kdc_rep *as_reply) +verify_as_reply(krb5_context context, + krb5_timestamp time_now, + krb5_kdc_req *request, + krb5_kdc_rep *as_reply) { - krb5_error_code retval; - int canon_req; - int canon_ok; + krb5_error_code retval; + int canon_req; + int canon_ok; + krb5_timestamp time_offset; /* check the contents for sanity: */ if (!as_reply->enc_part2->times.starttime) - as_reply->enc_part2->times.starttime = - as_reply->enc_part2->times.authtime; + as_reply->enc_part2->times.starttime = + as_reply->enc_part2->times.authtime; /* * We only allow the AS-REP server name to be changed if the @@ -294,1202 +158,1557 @@ verify_as_reply(krb5_context context, * principal) and we requested (and received) a TGT. */ canon_req = ((request->kdc_options & KDC_OPT_CANONICALIZE) != 0) || - (krb5_princ_type(context, request->client) == KRB5_NT_ENTERPRISE_PRINCIPAL); + (krb5_princ_type(context, request->client) == + KRB5_NT_ENTERPRISE_PRINCIPAL) || + (request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS); if (canon_req) { - canon_ok = IS_TGS_PRINC(context, request->server) && - IS_TGS_PRINC(context, as_reply->enc_part2->server); + canon_ok = IS_TGS_PRINC(context, request->server) && + IS_TGS_PRINC(context, as_reply->enc_part2->server); + if (!canon_ok && (request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS)) { + canon_ok = krb5_principal_compare_any_realm(context, + as_reply->client, + krb5_anonymous_principal()); + } } else - canon_ok = 0; - + canon_ok = 0; + if ((!canon_ok && - (!krb5_principal_compare(context, as_reply->client, request->client) || - !krb5_principal_compare(context, as_reply->enc_part2->server, request->server))) - || !krb5_principal_compare(context, as_reply->enc_part2->server, as_reply->ticket->server) - || (request->nonce != as_reply->enc_part2->nonce) - /* XXX check for extraneous flags */ - /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */ - || ((request->kdc_options & KDC_OPT_POSTDATED) && - (request->from != 0) && - (request->from != as_reply->enc_part2->times.starttime)) - || ((request->till != 0) && - (as_reply->enc_part2->times.endtime > request->till)) - || ((request->kdc_options & KDC_OPT_RENEWABLE) && - (request->rtime != 0) && - (as_reply->enc_part2->times.renew_till > request->rtime)) - || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) && - !(request->kdc_options & KDC_OPT_RENEWABLE) && - (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) && - (request->till != 0) && - (as_reply->enc_part2->times.renew_till > request->till)) - ) { -#if APPLE_PKINIT - inTktDebug("verify_as_reply: KDCREP_MODIFIED\n"); - #if IN_TKT_DEBUG - if(request->client->realm.length && request->client->data->length) - inTktDebug("request: name %s realm %s\n", - request->client->realm.data, request->client->data->data); - if(as_reply->client->realm.length && as_reply->client->data->length) - inTktDebug("reply : name %s realm %s\n", - as_reply->client->realm.data, as_reply->client->data->data); - #endif -#endif /* APPLE_PKINIT */ - return KRB5_KDCREP_MODIFIED; + (!krb5_principal_compare(context, as_reply->client, request->client) || + !krb5_principal_compare(context, as_reply->enc_part2->server, request->server))) + || !krb5_principal_compare(context, as_reply->enc_part2->server, as_reply->ticket->server) + || (request->nonce != as_reply->enc_part2->nonce) + /* XXX check for extraneous flags */ + /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */ + || ((request->kdc_options & KDC_OPT_POSTDATED) && + (request->from != 0) && + (request->from != as_reply->enc_part2->times.starttime)) + || ((request->till != 0) && + (as_reply->enc_part2->times.endtime > request->till)) + || ((request->kdc_options & KDC_OPT_RENEWABLE) && + (request->rtime != 0) && + (as_reply->enc_part2->times.renew_till > request->rtime)) + || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) && + !(request->kdc_options & KDC_OPT_RENEWABLE) && + (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) && + (request->till != 0) && + (as_reply->enc_part2->times.renew_till > request->till)) + ) { + return KRB5_KDCREP_MODIFIED; } if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) { - retval = krb5_set_real_time(context, - as_reply->enc_part2->times.authtime, -1); - if (retval) - return retval; + time_offset = as_reply->enc_part2->times.authtime - time_now; + retval = krb5_set_time_offsets(context, time_offset, 0); + if (retval) + return retval; } else { - if ((request->from == 0) && - (labs(as_reply->enc_part2->times.starttime - time_now) - > context->clockskew)) - return (KRB5_KDCREP_SKEW); + if ((request->from == 0) && + (labs(as_reply->enc_part2->times.starttime - time_now) + > context->clockskew)) + return (KRB5_KDCREP_SKEW); } return 0; } static krb5_error_code -stash_as_reply(krb5_context context, - krb5_timestamp time_now, - krb5_kdc_req *request, - krb5_kdc_rep *as_reply, - krb5_creds * creds, - krb5_ccache ccache) +stash_as_reply(krb5_context context, + krb5_timestamp time_now, + krb5_kdc_req *request, + krb5_kdc_rep *as_reply, + krb5_creds * creds, + krb5_ccache ccache) { - krb5_error_code retval; - krb5_data * packet; - krb5_principal client; - krb5_principal server; + krb5_error_code retval; + krb5_data * packet; + krb5_principal client; + krb5_principal server; client = NULL; server = NULL; if (!creds->client) if ((retval = krb5_copy_principal(context, as_reply->client, &client))) - goto cleanup; + goto cleanup; if (!creds->server) - if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server, - &server))) - goto cleanup; + if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server, + &server))) + goto cleanup; /* fill in the credentials */ - if ((retval = krb5_copy_keyblock_contents(context, - as_reply->enc_part2->session, - &creds->keyblock))) - goto cleanup; + if ((retval = krb5_copy_keyblock_contents(context, + as_reply->enc_part2->session, + &creds->keyblock))) + goto cleanup; creds->times = as_reply->enc_part2->times; - creds->is_skey = FALSE; /* this is an AS_REQ, so cannot - be encrypted in skey */ + creds->is_skey = FALSE; /* this is an AS_REQ, so cannot + be encrypted in skey */ creds->ticket_flags = as_reply->enc_part2->flags; if ((retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs, - &creds->addresses))) - goto cleanup; + &creds->addresses))) + goto cleanup; creds->second_ticket.length = 0; creds->second_ticket.data = 0; if ((retval = encode_krb5_ticket(as_reply->ticket, &packet))) - goto cleanup; + goto cleanup; creds->ticket = *packet; free(packet); /* store it in the ccache! */ if (ccache) - if ((retval = krb5_cc_store_cred(context, ccache, creds))) - goto cleanup; + if ((retval = krb5_cc_store_cred(context, ccache, creds))) + goto cleanup; if (!creds->client) - creds->client = client; + creds->client = client; if (!creds->server) - creds->server = server; + creds->server = server; cleanup: if (retval) { - if (client) - krb5_free_principal(context, client); - if (server) - krb5_free_principal(context, server); - if (creds->keyblock.contents) { - memset(creds->keyblock.contents, 0, - creds->keyblock.length); - free(creds->keyblock.contents); - creds->keyblock.contents = 0; - creds->keyblock.length = 0; - } - if (creds->ticket.data) { - free(creds->ticket.data); - creds->ticket.data = 0; - } - if (creds->addresses) { - krb5_free_addresses(context, creds->addresses); - creds->addresses = 0; - } + if (client) + krb5_free_principal(context, client); + if (server) + krb5_free_principal(context, server); + if (creds->keyblock.contents) { + memset(creds->keyblock.contents, 0, + creds->keyblock.length); + free(creds->keyblock.contents); + creds->keyblock.contents = 0; + creds->keyblock.length = 0; + } + if (creds->ticket.data) { + free(creds->ticket.data); + creds->ticket.data = 0; + } + if (creds->addresses) { + krb5_free_addresses(context, creds->addresses); + creds->addresses = 0; + } } return (retval); } static krb5_error_code -make_preauth_list(krb5_context context, - krb5_preauthtype * ptypes, - int nptypes, - krb5_pa_data *** ret_list) +make_preauth_list(krb5_context context, + krb5_preauthtype * ptypes, + int nptypes, + krb5_pa_data *** ret_list) { - krb5_preauthtype * ptypep; - krb5_pa_data ** preauthp; - int i; + krb5_preauthtype * ptypep; + krb5_pa_data ** preauthp; + int i; if (nptypes < 0) { - for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++) - ; + for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++) + ; } - + /* allocate space for a NULL to terminate the list */ - + if ((preauthp = - (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL) - return(ENOMEM); - + (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL) + return(ENOMEM); + for (i=0; i=0; i--) - free(preauthp[i]); - free(preauthp); - return (ENOMEM); - } - preauthp[i]->magic = KV5M_PA_DATA; - preauthp[i]->pa_type = ptypes[i]; - preauthp[i]->length = 0; - preauthp[i]->contents = 0; + if ((preauthp[i] = + (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) { + for (; i>=0; i--) + free(preauthp[i]); + free(preauthp); + return (ENOMEM); + } + preauthp[i]->magic = KV5M_PA_DATA; + preauthp[i]->pa_type = ptypes[i]; + preauthp[i]->length = 0; + preauthp[i]->contents = 0; } - + /* fill in the terminating NULL */ - + preauthp[nptypes] = NULL; - + *ret_list = preauthp; return 0; } #define MAX_IN_TKT_LOOPS 16 -static const krb5_enctype get_in_tkt_enctypes[] = { - ENCTYPE_DES3_CBC_SHA1, - ENCTYPE_ARCFOUR_HMAC, - ENCTYPE_DES_CBC_MD5, - ENCTYPE_DES_CBC_MD4, - ENCTYPE_DES_CBC_CRC, - 0 -}; -krb5_error_code KRB5_CALLCONV -krb5_get_in_tkt(krb5_context context, - krb5_flags options, - krb5_address * const * addrs, - krb5_enctype * ktypes, - krb5_preauthtype * ptypes, - git_key_proc key_proc, - krb5_const_pointer keyseed, - git_decrypt_proc decrypt_proc, - krb5_const_pointer decryptarg, - krb5_creds * creds, - krb5_ccache ccache, - krb5_kdc_rep ** ret_as_reply) +static krb5_error_code +request_enc_pa_rep(krb5_pa_data ***padptr) { - krb5_error_code retval; - krb5_timestamp time_now; - krb5_keyblock * decrypt_key = 0; - krb5_kdc_req request; - krb5_data *encoded_request; - krb5_pa_data **padata = 0; - krb5_error * err_reply; - krb5_kdc_rep * as_reply = 0; - krb5_pa_data ** preauth_to_use = 0; - int loopcount = 0; - krb5_int32 do_more = 0; - int canon_flag; - int use_master = 0; - int referral_count = 0; - krb5_principal_data referred_client; - -#if APPLE_PKINIT - inTktDebug("krb5_get_in_tkt top\n"); -#endif /* APPLE_PKINIT */ - - if (! krb5_realm_compare(context, creds->client, creds->server)) - return KRB5_IN_TKT_REALM_MISMATCH; - - if (ret_as_reply) - *ret_as_reply = 0; - - referred_client = *(creds->client); - referred_client.realm.data = NULL; - referred_client.realm.length = 0; + size_t size = 0; + krb5_pa_data **pad = *padptr; + krb5_pa_data *pa= NULL; + if (pad) + for (size=0; pad[size]; size++); + pad = realloc(pad, sizeof(*pad)*(size+2)); + + if (pad == NULL) + return ENOMEM; + pad[size+1] = NULL; + pa = malloc(sizeof(krb5_pa_data)); + if (pa == NULL) + return ENOMEM; + pa->contents = NULL; + pa->length = 0; + pa->pa_type = KRB5_ENCPADATA_REQ_ENC_PA_REP; + pad[size] = pa; + *padptr = pad; + return 0; +} - /* per referrals draft, enterprise principals imply canonicalization */ - canon_flag = ((options & KDC_OPT_CANONICALIZE) != 0) || - creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL; - - /* - * Set up the basic request structure - */ - request.magic = KV5M_KDC_REQ; - request.msg_type = KRB5_AS_REQ; - request.addresses = 0; - request.ktype = 0; - request.padata = 0; - if (addrs) - request.addresses = (krb5_address **) addrs; - else - if ((retval = krb5_os_localaddr(context, &request.addresses))) - goto cleanup; - request.kdc_options = options; - request.client = creds->client; - request.server = creds->server; - request.nonce = 0; - request.from = creds->times.starttime; - request.till = creds->times.endtime; - request.rtime = creds->times.renew_till; -#if APPLE_PKINIT - retval = gen_nonce(context, (krb5_int32 *)&time_now); - if(retval) { - goto cleanup; - } - request.nonce = time_now; -#endif /* APPLE_PKINIT */ +/* Sort a pa_data sequence so that types named in the "preferred_preauth_types" + * libdefaults entry are listed before any others. */ +static krb5_error_code +sort_krb5_padata_sequence(krb5_context context, krb5_data *realm, + krb5_pa_data **padata) +{ + int i, j, base; + krb5_error_code ret; + const char *p; + long l; + char *q, *preauth_types = NULL; + krb5_pa_data *tmp; + int need_free_string = 1; - request.ktype = malloc (sizeof(get_in_tkt_enctypes)); - if (request.ktype == NULL) { - retval = ENOMEM; - goto cleanup; - } - memcpy(request.ktype, get_in_tkt_enctypes, sizeof(get_in_tkt_enctypes)); - for (request.nktypes = 0;request.ktype[request.nktypes];request.nktypes++); - if (ktypes) { - int i, req, next = 0; - for (req = 0; ktypes[req]; req++) { - if (ktypes[req] == request.ktype[next]) { - next++; - continue; - } - for (i = next + 1; i < request.nktypes; i++) - if (ktypes[req] == request.ktype[i]) { - /* Found the enctype we want, but not in the - position we want. Move it, but keep the old - one from the desired slot around in case it's - later in our requested-ktypes list. */ - krb5_enctype t; - t = request.ktype[next]; - request.ktype[next] = request.ktype[i]; - request.ktype[i] = t; - next++; - break; - } - /* If we didn't find it, don't do anything special, just - drop it. */ - } - request.ktype[next] = 0; - request.nktypes = next; + if ((padata == NULL) || (padata[0] == NULL)) { + return 0; } - request.authorization_data.ciphertext.length = 0; - request.authorization_data.ciphertext.data = 0; - request.unenc_authdata = 0; - request.second_ticket = 0; - /* - * If a list of preauth types are passed in, convert it to a - * preauth_to_use list. - */ - if (ptypes) { - retval = make_preauth_list(context, ptypes, -1, &preauth_to_use); - if (retval) - goto cleanup; + ret = krb5int_libdefault_string(context, realm, KRB5_CONF_PREFERRED_PREAUTH_TYPES, + &preauth_types); + if ((ret != 0) || (preauth_types == NULL)) { + /* Try to use PKINIT first. */ + preauth_types = "17, 16, 15, 14"; + need_free_string = 0; } - - while (1) { - if (loopcount++ > MAX_IN_TKT_LOOPS) { - retval = KRB5_GET_IN_TKT_LOOP; - goto cleanup; - } - -#if APPLE_PKINIT - inTktDebug("krb5_get_in_tkt calling krb5_obtain_padata\n"); -#endif /* APPLE_PKINIT */ - if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc, - keyseed, creds, &request)) != 0) - goto cleanup; - if (preauth_to_use) - krb5_free_pa_data(context, preauth_to_use); - preauth_to_use = 0; - - err_reply = 0; - as_reply = 0; - - if ((retval = krb5_timeofday(context, &time_now))) - goto cleanup; - /* - * XXX we know they are the same size... and we should do - * something better than just the current time - */ - request.nonce = (krb5_int32) time_now; - - if ((retval = encode_krb5_as_req(&request, &encoded_request)) != 0) - goto cleanup; - retval = send_as_request(context, encoded_request, - krb5_princ_realm(context, request.client), &err_reply, - &as_reply, &use_master); - krb5_free_data_contents(context, encoded_request); - if (retval != 0) - goto cleanup; - - if (err_reply) { - if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED && - err_reply->e_data.length > 0) { - retval = decode_krb5_padata_sequence(&err_reply->e_data, - &preauth_to_use); - krb5_free_error(context, err_reply); - if (retval) - goto cleanup; - retval = sort_krb5_padata_sequence(context, - &request.server->realm, - padata); - if (retval) - goto cleanup; - continue; - } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) { - if (++referral_count > KRB5_REFERRAL_MAXHOPS || - err_reply->client == NULL || - err_reply->client->realm.length == 0) { - retval = KRB5KDC_ERR_WRONG_REALM; - krb5_free_error(context, err_reply); - goto cleanup; - } - /* Rewrite request.client with realm from error reply */ - if (referred_client.realm.data) { - krb5_free_data_contents(context, &referred_client.realm); - referred_client.realm.data = NULL; - } - retval = krb5int_copy_data_contents(context, - &err_reply->client->realm, - &referred_client.realm); - krb5_free_error(context, err_reply); - if (retval) - goto cleanup; - request.client = &referred_client; - continue; - } else { - retval = (krb5_error_code) err_reply->error - + ERROR_TABLE_BASE_krb5; - krb5_free_error(context, err_reply); - goto cleanup; - } - } else if (!as_reply) { - retval = KRB5KRB_AP_ERR_MSG_TYPE; - goto cleanup; - } - if ((retval = krb5_process_padata(context, &request, as_reply, - key_proc, keyseed, decrypt_proc, - &decrypt_key, creds, - &do_more)) != 0) - goto cleanup; - - if (!do_more) - break; +#ifdef DEBUG + fprintf (stderr, "preauth data types before sorting:"); + for (i = 0; padata[i]; i++) { + fprintf (stderr, " %d", padata[i]->pa_type); } - - if ((retval = decrypt_as_reply(context, &request, as_reply, key_proc, - keyseed, decrypt_key, decrypt_proc, - decryptarg))) - goto cleanup; + fprintf (stderr, "\n"); +#endif - if ((retval = verify_as_reply(context, time_now, &request, as_reply))) - goto cleanup; + base = 0; + for (p = preauth_types; *p != '\0';) { + /* skip whitespace to find an entry */ + p += strspn(p, ", "); + if (*p != '\0') { + /* see if we can extract a number */ + l = strtol(p, &q, 10); + if ((q != NULL) && (q > p)) { + /* got a valid number; search for a matchin entry */ + for (i = base; padata[i] != NULL; i++) { + /* bubble the matching entry to the front of the list */ + if (padata[i]->pa_type == l) { + tmp = padata[i]; + for (j = i; j > base; j--) + padata[j] = padata[j - 1]; + padata[base] = tmp; + base++; + break; + } + } + p = q; + } else { + break; + } + } + } + if (need_free_string) + free(preauth_types); - if ((retval = stash_as_reply(context, time_now, &request, as_reply, - creds, ccache))) - goto cleanup; +#ifdef DEBUG + fprintf (stderr, "preauth data types after sorting:"); + for (i = 0; padata[i]; i++) + fprintf (stderr, " %d", padata[i]->pa_type); + fprintf (stderr, "\n"); +#endif -cleanup: - if (request.ktype) - free(request.ktype); - if (!addrs && request.addresses) - krb5_free_addresses(context, request.addresses); - if (request.padata) - krb5_free_pa_data(context, request.padata); - if (padata) - krb5_free_pa_data(context, padata); - if (preauth_to_use) - krb5_free_pa_data(context, preauth_to_use); - if (decrypt_key) - krb5_free_keyblock(context, decrypt_key); - if (as_reply) { - if (ret_as_reply) - *ret_as_reply = as_reply; - else - krb5_free_kdc_rep(context, as_reply); - } - if (referred_client.realm.data) - krb5_free_data_contents(context, &referred_client.realm); - return (retval); + return 0; } -/* begin libdefaults parsing code. This should almost certainly move - somewhere else, but I don't know where the correct somewhere else - is yet. */ +static krb5_error_code +build_in_tkt_name(krb5_context context, + char *in_tkt_service, + krb5_const_principal client, + krb5_principal *server) +{ + krb5_error_code ret; -/* XXX Duplicating this is annoying; try to work on a better way.*/ -static const char *const conf_yes[] = { - "y", "yes", "true", "t", "1", "on", - 0, -}; + *server = NULL; -static const char *const conf_no[] = { - "n", "no", "false", "nil", "0", "off", - 0, -}; + if (in_tkt_service) { + /* this is ugly, because so are the data structures involved. I'm + in the library, so I'm going to manipulate the data structures + directly, otherwise, it will be worse. */ + + if ((ret = krb5_parse_name(context, in_tkt_service, server))) + return ret; + + /* stuff the client realm into the server principal. + realloc if necessary */ + if ((*server)->realm.length < client->realm.length) { + char *p = realloc((*server)->realm.data, + client->realm.length); + if (p == NULL) { + krb5_free_principal(context, *server); + *server = NULL; + return ENOMEM; + } + (*server)->realm.data = p; + } -int -_krb5_conf_boolean(const char *s) + (*server)->realm.length = client->realm.length; + memcpy((*server)->realm.data, client->realm.data, client->realm.length); + } else { + ret = krb5_build_principal_ext(context, server, + client->realm.length, + client->realm.data, + KRB5_TGS_NAME_SIZE, + KRB5_TGS_NAME, + client->realm.length, + client->realm.data, + 0); + if (ret) + return ret; + } + /* + * Windows Server 2008 R2 RODC insists on TGS principal names having the + * right name type. + */ + if (krb5_princ_size(context, *server) == 2 && + data_eq_string(*krb5_princ_component(context, *server, 0), + KRB5_TGS_NAME)) { + krb5_princ_type(context, *server) = KRB5_NT_SRV_INST; + } + return 0; +} + +void KRB5_CALLCONV +krb5_init_creds_free(krb5_context context, + krb5_init_creds_context ctx) { - const char *const *p; + if (ctx == NULL) + return; - for(p=conf_yes; *p; p++) { - if (!strcasecmp(*p,s)) - return 1; + if (ctx->opte != NULL && krb5_gic_opt_is_shadowed(ctx->opte)) { + krb5_get_init_creds_opt_free(context, + (krb5_get_init_creds_opt *)ctx->opte); } + free(ctx->in_tkt_service); + zap(ctx->password.data, ctx->password.length); + krb5_free_data_contents(context, &ctx->password); + krb5_free_error(context, ctx->err_reply); + krb5_free_pa_data(context, ctx->err_padata); + krb5_free_cred_contents(context, &ctx->cred); + krb5_free_kdc_req(context, ctx->request); + krb5_free_kdc_rep(context, ctx->reply); + krb5_free_data(context, ctx->outer_request_body); + krb5_free_data(context, ctx->inner_request_body); + krb5_free_data(context, ctx->encoded_previous_request); + krb5int_fast_free_state(context, ctx->fast_state); + krb5_free_pa_data(context, ctx->preauth_to_use); + krb5_free_data_contents(context, &ctx->salt); + krb5_free_data_contents(context, &ctx->s2kparams); + krb5_free_keyblock_contents(context, &ctx->as_key); + free(ctx); +} + +krb5_error_code +k5_init_creds_get(krb5_context context, krb5_init_creds_context ctx, + int *use_master) +{ + krb5_error_code code; + krb5_data request; + krb5_data reply; + krb5_data realm; + unsigned int flags = 0; + int tcp_only = 0; - for(p=conf_no; *p; p++) { - if (!strcasecmp(*p,s)) - return 0; + request.length = 0; + request.data = NULL; + reply.length = 0; + reply.data = NULL; + realm.length = 0; + realm.data = NULL; + + for (;;) { + code = krb5_init_creds_step(context, + ctx, + &reply, + &request, + &realm, + &flags); + if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only) { + TRACE_INIT_CREDS_RETRY_TCP(context); + tcp_only = 1; + } else if (code != 0 || !(flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE)) + break; + + krb5_free_data_contents(context, &reply); + + code = krb5_sendto_kdc(context, &request, &realm, + &reply, use_master, tcp_only); + if (code != 0) + break; + + krb5_free_data_contents(context, &request); + krb5_free_data_contents(context, &realm); } - /* Default to "no" */ - return 0; + krb5_free_data_contents(context, &request); + krb5_free_data_contents(context, &reply); + krb5_free_data_contents(context, &realm); + + return code; } -static krb5_error_code -krb5_libdefault_string(krb5_context context, const krb5_data *realm, - const char *option, char **ret_value) +/* Heimdal API */ +krb5_error_code KRB5_CALLCONV +krb5_init_creds_get(krb5_context context, + krb5_init_creds_context ctx) { - profile_t profile; - const char *names[5]; - char **nameval = NULL; - krb5_error_code retval; - char realmstr[1024]; + int use_master = 0; + + return k5_init_creds_get(context, ctx, &use_master); +} + +krb5_error_code KRB5_CALLCONV +krb5_init_creds_get_creds(krb5_context context, + krb5_init_creds_context ctx, + krb5_creds *creds) +{ + if (!ctx->complete) + return KRB5_NO_TKT_SUPPLIED; - if (realm->length > sizeof(realmstr)-1) - return(EINVAL); + return krb5int_copy_creds_contents(context, &ctx->cred, creds); +} - strncpy(realmstr, realm->data, realm->length); - realmstr[realm->length] = '\0'; +krb5_error_code KRB5_CALLCONV +krb5_init_creds_get_times(krb5_context context, + krb5_init_creds_context ctx, + krb5_ticket_times *times) +{ + if (!ctx->complete) + return KRB5_NO_TKT_SUPPLIED; - if (!context || (context->magic != KV5M_CONTEXT)) - return KV5M_CONTEXT; + *times = ctx->cred.times; - profile = context->profile; - - names[0] = KRB5_CONF_LIBDEFAULTS; + return 0; +} - /* - * Try number one: - * - * [libdefaults] - * REALM = { - * option = - * } - */ +krb5_error_code KRB5_CALLCONV +krb5_init_creds_get_error(krb5_context context, + krb5_init_creds_context ctx, + krb5_error **error) +{ + krb5_error_code code; + krb5_error *ret = NULL; - names[1] = realmstr; - names[2] = option; - names[3] = 0; - retval = profile_get_values(profile, names, &nameval); - if (retval == 0 && nameval && nameval[0]) - goto goodbye; + *error = NULL; - /* - * Try number two: - * - * [libdefaults] - * option = - */ - - names[1] = option; - names[2] = 0; - retval = profile_get_values(profile, names, &nameval); - if (retval == 0 && nameval && nameval[0]) - goto goodbye; - -goodbye: - if (!nameval) - return(ENOENT); - - if (!nameval[0]) { - retval = ENOENT; - } else { - *ret_value = strdup(nameval[0]); - if (!*ret_value) - retval = ENOMEM; - } + if (ctx->err_reply == NULL) + return 0; - profile_free_list(nameval); + ret = k5alloc(sizeof(*ret), &code); + if (code != 0) + goto cleanup; - return retval; -} + ret->magic = KV5M_ERROR; + ret->ctime = ctx->err_reply->ctime; + ret->cusec = ctx->err_reply->cusec; + ret->susec = ctx->err_reply->susec; + ret->stime = ctx->err_reply->stime; + ret->error = ctx->err_reply->error; + + if (ctx->err_reply->client != NULL) { + code = krb5_copy_principal(context, ctx->err_reply->client, + &ret->client); + if (code != 0) + goto cleanup; + } -/* not static so verify_init_creds() can call it */ -/* as well as the DNS code */ + code = krb5_copy_principal(context, ctx->err_reply->server, &ret->server); + if (code != 0) + goto cleanup; -krb5_error_code -krb5_libdefault_boolean(krb5_context context, const krb5_data *realm, - const char *option, int *ret_value) -{ - char *string = NULL; - krb5_error_code retval; + code = krb5int_copy_data_contents(context, &ctx->err_reply->text, + &ret->text); + if (code != 0) + goto cleanup; - retval = krb5_libdefault_string(context, realm, option, &string); + code = krb5int_copy_data_contents(context, &ctx->err_reply->e_data, + &ret->e_data); + if (code != 0) + goto cleanup; - if (retval) - return(retval); + *error = ret; - *ret_value = _krb5_conf_boolean(string); - free(string); +cleanup: + if (code != 0) + krb5_free_error(context, ret); - return(0); + return code; } -/* Sort a pa_data sequence so that types named in the "preferred_preauth_types" - * libdefaults entry are listed before any others. */ +/** + * Throw away any state related to specific realm either at the beginning of a + * request, or when a realm changes, or when we start to use FAST after + * assuming we would not do so. + * + * @param padata padata from an error if an error from the realm we now expect + * to talk to caused the restart. Used to infer negotiation characteristics + * such as whether FAST is used. + */ static krb5_error_code -sort_krb5_padata_sequence(krb5_context context, krb5_data *realm, - krb5_pa_data **padata) +restart_init_creds_loop(krb5_context context, krb5_init_creds_context ctx, + krb5_pa_data **padata) { - int i, j, base; - krb5_error_code ret; - const char *p; - long l; - char *q, *preauth_types = NULL; - krb5_pa_data *tmp; - int need_free_string = 1; - - if ((padata == NULL) || (padata[0] == NULL)) { - return 0; + krb5_error_code code = 0; + unsigned char random_buf[4]; + krb5_data random_data; + if (ctx->preauth_to_use) { + krb5_free_pa_data(context, ctx->preauth_to_use); + ctx->preauth_to_use = NULL; } - ret = krb5_libdefault_string(context, realm, KRB5_CONF_PREFERRED_PREAUTH_TYPES, - &preauth_types); - if ((ret != 0) || (preauth_types == NULL)) { - /* Try to use PKINIT first. */ - preauth_types = "17, 16, 15, 14"; - need_free_string = 0; + if (ctx->fast_state) { + krb5int_fast_free_state(context, ctx->fast_state); + ctx->fast_state = NULL; } - -#ifdef DEBUG - fprintf (stderr, "preauth data types before sorting:"); - for (i = 0; padata[i]; i++) { - fprintf (stderr, " %d", padata[i]->pa_type); + code = krb5int_fast_make_state(context, &ctx->fast_state); + if (code != 0) + goto cleanup; + ctx->preauth_rock.fast_state = ctx->fast_state; + krb5_preauth_request_context_init(context); + if (ctx->outer_request_body) { + krb5_free_data(context, ctx->outer_request_body); + ctx->outer_request_body = NULL; + } + if (ctx->opte && + (ctx->opte->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) { + if ((code = make_preauth_list(context, ctx->opte->preauth_list, + ctx->opte->preauth_list_length, + &ctx->preauth_to_use))) + goto cleanup; } - fprintf (stderr, "\n"); -#endif - base = 0; - for (p = preauth_types; *p != '\0';) { - /* skip whitespace to find an entry */ - p += strspn(p, ", "); - if (*p != '\0') { - /* see if we can extract a number */ - l = strtol(p, &q, 10); - if ((q != NULL) && (q > p)) { - /* got a valid number; search for a matchin entry */ - for (i = base; padata[i] != NULL; i++) { - /* bubble the matching entry to the front of the list */ - if (padata[i]->pa_type == l) { - tmp = padata[i]; - for (j = i; j > base; j--) - padata[j] = padata[j - 1]; - padata[base] = tmp; - base++; - break; - } - } - p = q; - } else { - break; - } - } + /* Set the request nonce. */ + random_data.length = 4; + random_data.data = (char *)random_buf; + code = krb5_c_random_make_octets(context, &random_data); + if (code !=0) + goto cleanup; + /* + * See RT ticket 3196 at MIT. If we set the high bit, we may have + * compatibility problems with Heimdal, because we (incorrectly) encode + * this value as signed. + */ + ctx->request->nonce = 0x7fffffff & load_32_n(random_buf); + krb5_free_principal(context, ctx->request->server); + ctx->request->server = NULL; + + code = build_in_tkt_name(context, ctx->in_tkt_service, + ctx->request->client, + &ctx->request->server); + if (code != 0) + goto cleanup; + + ctx->request_time = time(NULL); + + code = krb5int_fast_as_armor(context, ctx->fast_state, + ctx->opte, ctx->request); + if (code != 0) + goto cleanup; + if (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata)) { + code = krb5int_fast_as_armor(context, ctx->fast_state, + ctx->opte, ctx->request); + if (code != 0) + goto cleanup; } - if (need_free_string) - free(preauth_types); + /* give the preauth plugins a chance to prep the request body */ + krb5_preauth_prepare_request(context, ctx->opte, ctx->request); -#ifdef DEBUG - fprintf (stderr, "preauth data types after sorting:"); - for (i = 0; padata[i]; i++) - fprintf (stderr, " %d", padata[i]->pa_type); - fprintf (stderr, "\n"); -#endif + ctx->request->from = krb5int_addint32(ctx->request_time, + ctx->start_time); + ctx->request->till = krb5int_addint32(ctx->request->from, + ctx->tkt_life); - return 0; + if (ctx->renew_life > 0) { + ctx->request->rtime = + krb5int_addint32(ctx->request->from, ctx->renew_life); + if (ctx->request->rtime < ctx->request->till) { + /* don't ask for a smaller renewable time than the lifetime */ + ctx->request->rtime = ctx->request->till; + } + ctx->request->kdc_options &= ~(KDC_OPT_RENEWABLE_OK); + } else + ctx->request->rtime = 0; + code = krb5int_fast_prep_req_body(context, ctx->fast_state, + ctx->request, + &ctx->outer_request_body); + if (code != 0) + goto cleanup; +cleanup: + return code; } krb5_error_code KRB5_CALLCONV -krb5_get_init_creds(krb5_context context, - krb5_creds *creds, - krb5_principal client, - krb5_prompter_fct prompter, - void *prompter_data, - krb5_deltat start_time, - char *in_tkt_service, - krb5_gic_opt_ext *options, - krb5_gic_get_as_key_fct gak_fct, - void *gak_data, - int *use_master, - krb5_kdc_rep **as_reply) +krb5_init_creds_init(krb5_context context, + krb5_principal client, + krb5_prompter_fct prompter, + void *data, + krb5_deltat start_time, + krb5_get_init_creds_opt *options, + krb5_init_creds_context *pctx) { - krb5_error_code ret; - krb5_kdc_req request; - krb5_data *encoded_request_body, *encoded_previous_request; - krb5_pa_data **preauth_to_use, **kdc_padata; - int tempint; - char *tempstr; - krb5_deltat tkt_life; - krb5_deltat renew_life; - int loopcount; - krb5_data salt; - krb5_data s2kparams; - krb5_keyblock as_key; - krb5_error *err_reply; - krb5_kdc_rep *local_as_reply; - krb5_timestamp time_now; - krb5_enctype etype = 0; - krb5_preauth_client_rock get_data_rock; - int canon_flag = 0; - krb5_principal_data referred_client; - - /* initialize everything which will be freed at cleanup */ - - s2kparams.data = NULL; - s2kparams.length = 0; - request.server = NULL; - request.ktype = NULL; - request.addresses = NULL; - request.padata = NULL; - encoded_request_body = NULL; - encoded_previous_request = NULL; - preauth_to_use = NULL; - kdc_padata = NULL; - as_key.length = 0; - salt.length = 0; - salt.data = NULL; - - local_as_reply = 0; -#if APPLE_PKINIT - inTktDebug("krb5_get_init_creds top\n"); -#endif /* APPLE_PKINIT */ - - err_reply = NULL; - - /* referred_client is used to rewrite the client realm for referrals */ - referred_client = *client; - referred_client.realm.data = NULL; - referred_client.realm.length = 0; + krb5_error_code code; + krb5_init_creds_context ctx; + int tmp; + char *str = NULL; + krb5_gic_opt_ext *opte; + krb5_get_init_creds_opt local_opts; - /* - * Set up the basic request structure - */ - request.magic = KV5M_KDC_REQ; - request.msg_type = KRB5_AS_REQ; + TRACE_INIT_CREDS(context, client); - /* request.nonce is filled in when we send a request to the kdc */ - request.nonce = 0; + ctx = k5alloc(sizeof(*ctx), &code); + if (code != 0) + goto cleanup; - /* request.padata is filled in later */ + ctx->request = k5alloc(sizeof(krb5_kdc_req), &code); + if (code != 0) + goto cleanup; + ctx->enc_pa_rep_permitted = 1; + code = krb5_copy_principal(context, client, &ctx->request->client); + if (code != 0) + goto cleanup; - request.kdc_options = context->kdc_default_options; + ctx->prompter = prompter; + ctx->prompter_data = data; + ctx->gak_fct = krb5_get_as_key_password; + ctx->gak_data = &ctx->password; - /* forwardable */ + ctx->request_time = 0; /* filled in later */ + ctx->start_time = start_time; - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE)) - tempint = options->forwardable; - else if ((ret = krb5_libdefault_boolean(context, &client->realm, - KRB5_CONF_FORWARDABLE, &tempint)) == 0) - ; + if (options == NULL) { + /* + * We initialize a non-extended options because that way the shadowed + * flag will be sent and they will be freed when the init_creds context + * is freed. The options will be extended and copied off the stack into + * storage by opt_to_opte. + */ + krb5_get_init_creds_opt_init(&local_opts); + options = &local_opts; + } + + code = krb5int_gic_opt_to_opte(context, options, + &ctx->opte, 1, "krb5_init_creds_init"); + if (code != 0) + goto cleanup; + + opte = ctx->opte; + + ctx->preauth_rock.magic = CLIENT_ROCK_MAGIC; + ctx->preauth_rock.etype = &ctx->etype; + ctx->preauth_rock.as_key = &ctx->as_key; + ctx->preauth_rock.gak_fct = &ctx->gak_fct; + ctx->preauth_rock.gak_data = &ctx->gak_data; + ctx->preauth_rock.default_salt = &ctx->default_salt; + ctx->preauth_rock.salt = &ctx->salt; + ctx->preauth_rock.s2kparams = &ctx->s2kparams; + ctx->preauth_rock.client = client; + ctx->preauth_rock.prompter = prompter; + ctx->preauth_rock.prompter_data = data; + + /* Initialise request parameters as per krb5_get_init_creds() */ + ctx->request->kdc_options = context->kdc_default_options; + + /* forwaradble */ + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE) + tmp = opte->forwardable; + else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm, + KRB5_CONF_FORWARDABLE, &tmp) == 0) + ; else - tempint = 0; - if (tempint) - request.kdc_options |= KDC_OPT_FORWARDABLE; + tmp = 0; + if (tmp) + ctx->request->kdc_options |= KDC_OPT_FORWARDABLE; /* proxiable */ - - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE)) - tempint = options->proxiable; - else if ((ret = krb5_libdefault_boolean(context, &client->realm, - KRB5_CONF_PROXIABLE, &tempint)) == 0) - ; + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE) + tmp = opte->proxiable; + else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm, + KRB5_CONF_PROXIABLE, &tmp) == 0) + ; else - tempint = 0; - if (tempint) - request.kdc_options |= KDC_OPT_PROXIABLE; + tmp = 0; + if (tmp) + ctx->request->kdc_options |= KDC_OPT_PROXIABLE; /* canonicalize */ - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_CANONICALIZE)) - tempint = 1; - else if ((ret = krb5_libdefault_boolean(context, &client->realm, - KRB5_CONF_CANONICALIZE, &tempint)) == 0) - ; + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_CANONICALIZE) + tmp = 1; + else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm, + KRB5_CONF_CANONICALIZE, &tmp) == 0) + ; else - tempint = 0; - if (tempint) - request.kdc_options |= KDC_OPT_CANONICALIZE; + tmp = 0; + if (tmp) + ctx->request->kdc_options |= KDC_OPT_CANONICALIZE; /* allow_postdate */ - - if (start_time > 0) - request.kdc_options |= (KDC_OPT_ALLOW_POSTDATE|KDC_OPT_POSTDATED); - + if (ctx->start_time > 0) + ctx->request->kdc_options |= KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED; + /* ticket lifetime */ - - if ((ret = krb5_timeofday(context, &request.from))) - goto cleanup; - request.from = krb5int_addint32(request.from, start_time); - - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)) { - tkt_life = options->tkt_life; - } else if ((ret = krb5_libdefault_string(context, &client->realm, - KRB5_CONF_TICKET_LIFETIME, &tempstr)) - == 0) { - ret = krb5_string_to_deltat(tempstr, &tkt_life); - free(tempstr); - if (ret) { - goto cleanup; - } + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE) + ctx->tkt_life = options->tkt_life; + else if (krb5int_libdefault_string(context, &ctx->request->client->realm, + KRB5_CONF_TICKET_LIFETIME, &str) == 0) { + code = krb5_string_to_deltat(str, &ctx->tkt_life); + if (code != 0) + goto cleanup; + free(str); + str = NULL; + } else + ctx->tkt_life = 24 * 60 * 60; /* previously hardcoded in kinit */ + + /* renewable lifetime */ + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE) + ctx->renew_life = options->renew_life; + else if (krb5int_libdefault_string(context, &ctx->request->client->realm, + KRB5_CONF_RENEW_LIFETIME, &str) == 0) { + code = krb5_string_to_deltat(str, &ctx->renew_life); + if (code != 0) + goto cleanup; + free(str); + str = NULL; + } else + ctx->renew_life = 0; + + if (ctx->renew_life > 0) + ctx->request->kdc_options |= KDC_OPT_RENEWABLE; + + /* enctypes */ + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST) { + ctx->request->ktype = + k5alloc((opte->etype_list_length * sizeof(krb5_enctype)), + &code); + if (code != 0) + goto cleanup; + ctx->request->nktypes = opte->etype_list_length; + memcpy(ctx->request->ktype, opte->etype_list, + ctx->request->nktypes * sizeof(krb5_enctype)); + } else if (krb5_get_default_in_tkt_ktypes(context, + &ctx->request->ktype) == 0) { + ctx->request->nktypes = krb5int_count_etypes(ctx->request->ktype); } else { - /* this used to be hardcoded in kinit.c */ - tkt_life = 24*60*60; + /* there isn't any useful default here. */ + code = KRB5_CONFIG_ETYPE_NOSUPP; + goto cleanup; } - request.till = krb5int_addint32(request.from, tkt_life); - - /* renewable lifetime */ - - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)) { - renew_life = options->renew_life; - } else if ((ret = krb5_libdefault_string(context, &client->realm, - KRB5_CONF_RENEW_LIFETIME, &tempstr)) - == 0) { - ret = krb5_string_to_deltat(tempstr, &renew_life); - free(tempstr); - if (ret) { - goto cleanup; - } + + /* + * Set a default enctype for optimistic preauth. If we're not doing + * optimistic preauth, this should ordinarily get overwritten when we + * process the etype-info2 of the preauth-required error. + */ + if (ctx->request->nktypes > 0) + ctx->etype = ctx->request->ktype[0]; + + /* addresess */ + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST) { + code = krb5_copy_addresses(context, opte->address_list, + &ctx->request->addresses); + if (code != 0) + goto cleanup; + } else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm, + KRB5_CONF_NOADDRESSES, &tmp) != 0 + || tmp) { + ctx->request->addresses = NULL; } else { - renew_life = 0; + code = krb5_os_localaddr(context, &ctx->request->addresses); + if (code != 0) + goto cleanup; } - if (renew_life > 0) - request.kdc_options |= KDC_OPT_RENEWABLE; - - if (renew_life > 0) { - request.rtime = krb5int_addint32(request.from, renew_life); - if (request.rtime < request.till) { - /* don't ask for a smaller renewable time than the lifetime */ - request.rtime = request.till; - } - /* we are already asking for renewable tickets so strip this option */ - request.kdc_options &= ~(KDC_OPT_RENEWABLE_OK); + + if (opte->flags & KRB5_GET_INIT_CREDS_OPT_SALT) { + code = krb5int_copy_data_contents(context, opte->salt, &ctx->salt); + if (code != 0) + goto cleanup; + ctx->default_salt = FALSE; } else { - request.rtime = 0; + ctx->salt = empty_data(); + ctx->default_salt = TRUE; } - - /* client */ - request.client = client; + /* Anonymous. */ + if(opte->flags & KRB5_GET_INIT_CREDS_OPT_ANONYMOUS) { + ctx->request->kdc_options |= KDC_OPT_REQUEST_ANONYMOUS; + /* Remap @REALM to WELLKNOWN/ANONYMOUS@REALM. */ + if (client->length == 1 && client->data[0].length ==0) { + krb5_principal new_client; + code = krb5_build_principal_ext(context, &new_client, + client->realm.length, + client->realm.data, + strlen(KRB5_WELLKNOWN_NAMESTR), + KRB5_WELLKNOWN_NAMESTR, + strlen(KRB5_ANONYMOUS_PRINCSTR), + KRB5_ANONYMOUS_PRINCSTR, + 0); + if (code) + goto cleanup; + krb5_free_principal(context, ctx->request->client); + ctx->request->client = new_client; + krb5_princ_type(context, ctx->request->client) = KRB5_NT_WELLKNOWN; + } + } + /* We will also handle anonymous if the input principal is the anonymous + * principal. */ + if (krb5_principal_compare_any_realm(context, ctx->request->client, + krb5_anonymous_principal())) { + ctx->request->kdc_options |= KDC_OPT_REQUEST_ANONYMOUS; + krb5_princ_type(context, ctx->request->client) = KRB5_NT_WELLKNOWN; + } + code = restart_init_creds_loop(context, ctx, NULL); + if (code) + goto cleanup; - /* per referrals draft, enterprise principals imply canonicalization */ - canon_flag = ((request.kdc_options & KDC_OPT_CANONICALIZE) != 0) || - client->type == KRB5_NT_ENTERPRISE_PRINCIPAL; + *pctx = ctx; + ctx = NULL; - /* service */ - - if (in_tkt_service) { - /* this is ugly, because so are the data structures involved. I'm - in the library, so I'm going to manipulate the data structures - directly, otherwise, it will be worse. */ - - if ((ret = krb5_parse_name(context, in_tkt_service, &request.server))) - goto cleanup; - - /* stuff the client realm into the server principal. - realloc if necessary */ - if (request.server->realm.length < request.client->realm.length) { - char *p = realloc(request.server->realm.data, - request.client->realm.length); - if (p == NULL) { - ret = ENOMEM; - goto cleanup; - } - request.server->realm.data = p; - } - - request.server->realm.length = request.client->realm.length; - memcpy(request.server->realm.data, request.client->realm.data, - request.client->realm.length); - } else { - if ((ret = krb5_build_principal_ext(context, &request.server, - request.client->realm.length, - request.client->realm.data, - KRB5_TGS_NAME_SIZE, - KRB5_TGS_NAME, - request.client->realm.length, - request.client->realm.data, - 0))) - goto cleanup; +cleanup: + krb5_init_creds_free(context, ctx); + free(str); + + return code; +} + +krb5_error_code KRB5_CALLCONV +krb5_init_creds_set_service(krb5_context context, + krb5_init_creds_context ctx, + const char *service) +{ + char *s; + + TRACE_INIT_CREDS_SERVICE(context, service); + + s = strdup(service); + if (s == NULL) + return ENOMEM; + + free(ctx->in_tkt_service); + ctx->in_tkt_service = s; + + krb5_preauth_request_context_fini(context); + return restart_init_creds_loop(context, ctx, NULL); +} + +static krb5_error_code +init_creds_validate_reply(krb5_context context, + krb5_init_creds_context ctx, + krb5_data *reply) +{ + krb5_error_code code; + krb5_error *error = NULL; + krb5_kdc_rep *as_reply = NULL; + + krb5_free_error(context, ctx->err_reply); + ctx->err_reply = NULL; + + krb5_free_kdc_rep(context, ctx->reply); + ctx->reply = NULL; + + if (krb5_is_krb_error(reply)) { + code = decode_krb5_error(reply, &error); + if (code != 0) + return code; + + assert(error != NULL); + + TRACE_INIT_CREDS_ERROR_REPLY(context, + error->error + ERROR_TABLE_BASE_krb5); + if (error->error == KRB_ERR_RESPONSE_TOO_BIG) { + krb5_free_error(context, error); + return KRB5KRB_ERR_RESPONSE_TOO_BIG; + } else { + ctx->err_reply = error; + return 0; + } } - krb5_preauth_request_context_init(context); + /* + * Check to make sure it isn't a V4 reply. + */ + if (reply->length != 0 && !krb5_is_as_rep(reply)) { +/* these are in as well but it isn't worth including. */ +#define V4_KRB_PROT_VERSION 4 +#define V4_AUTH_MSG_ERR_REPLY (5<<1) + /* check here for V4 reply */ + unsigned int t_switch; + + /* From v4 g_in_tkt.c: This used to be + switch (pkt_msg_type(rpkt) & ~1) { + but SCO 3.2v4 cc compiled that incorrectly. */ + t_switch = reply->data[1]; + t_switch &= ~1; + + if (t_switch == V4_AUTH_MSG_ERR_REPLY + && reply->data[0] == V4_KRB_PROT_VERSION) { + code = KRB5KRB_AP_ERR_V4_REPLY; + } else { + code = KRB5KRB_AP_ERR_MSG_TYPE; + } + return code; + } + /* It must be a KRB_AS_REP message, or an bad returned packet */ + code = decode_krb5_as_rep(reply, &as_reply); + if (code != 0) + return code; - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)) { - request.ktype = options->etype_list; - request.nktypes = options->etype_list_length; - } else if ((ret = krb5_get_default_in_tkt_ktypes(context, - &request.ktype)) == 0) { - for (request.nktypes = 0; - request.ktype[request.nktypes]; - request.nktypes++) - ; - } else { - /* there isn't any useful default here. ret is set from above */ - goto cleanup; + if (as_reply->msg_type != KRB5_AS_REP) { + krb5_free_kdc_rep(context, as_reply); + return KRB5KRB_AP_ERR_MSG_TYPE; } - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)) { - request.addresses = options->address_list; + ctx->reply = as_reply; + + return 0; +} + +static krb5_error_code +init_creds_step_request(krb5_context context, + krb5_init_creds_context ctx, + krb5_data *out) +{ + krb5_error_code code; + krb5_boolean got_real; + char random_buf[4]; + krb5_data random_data; + + if (ctx->loopcount >= MAX_IN_TKT_LOOPS) { + code = KRB5_GET_IN_TKT_LOOP; + goto cleanup; } - /* it would be nice if this parsed out an address list, but - that would be work. */ - else if (((ret = krb5_libdefault_boolean(context, &client->realm, - KRB5_CONF_NOADDRESSES, &tempint)) != 0) - || (tempint == 1)) { - ; + /* + * RFC 6113 requires a new nonce for the inner request on each try. It's + * permitted to change the nonce even for non-FAST so we do here. + */ + random_data.length = 4; + random_data.data = (char *)random_buf; + code = krb5_c_random_make_octets(context, &random_data); + if (code !=0) + goto cleanup; + /* + * See RT ticket 3196 at MIT. If we set the high bit, we may have + * compatibility problems with Heimdal, because we (incorrectly) encode + * this value as signed. + */ + ctx->request->nonce = 0x7fffffff & load_32_n(random_buf); + krb5_free_data(context, ctx->inner_request_body); + ctx->inner_request_body = NULL; + code = encode_krb5_kdc_req_body(ctx->request, &ctx->inner_request_body); + if (code) + goto cleanup; + + if (ctx->err_reply == NULL) { + /* either our first attempt, or retrying after PREAUTH_NEEDED */ + code = krb5_do_preauth(context, + ctx->request, + ctx->inner_request_body, + ctx->encoded_previous_request, + ctx->preauth_to_use, + &ctx->request->padata, + ctx->prompter, + ctx->prompter_data, + &ctx->preauth_rock, + ctx->opte, + &got_real); + if (code == 0 && !got_real && ctx->preauth_required) + code = KRB5_PREAUTH_FAILED; + if (code != 0) + goto cleanup; } else { - if ((ret = krb5_os_localaddr(context, &request.addresses))) - goto cleanup; + if (ctx->preauth_to_use != NULL) { + /* + * Retry after an error other than PREAUTH_NEEDED, + * using ctx->err_padata to figure out what to change. + */ + code = krb5_do_preauth_tryagain(context, + ctx->request, + ctx->inner_request_body, + ctx->encoded_previous_request, + ctx->preauth_to_use, + &ctx->request->padata, + ctx->err_reply, + ctx->err_padata, + ctx->prompter, + ctx->prompter_data, + &ctx->preauth_rock, + ctx->opte); + } else { + /* No preauth supplied, so can't query the plugins. */ + code = KRB5KRB_ERR_GENERIC; + } + if (code != 0) { + /* couldn't come up with anything better */ + code = ctx->err_reply->error + ERROR_TABLE_BASE_krb5; + goto cleanup; + } } - request.authorization_data.ciphertext.length = 0; - request.authorization_data.ciphertext.data = 0; - request.unenc_authdata = 0; - request.second_ticket = 0; + if (ctx->encoded_previous_request != NULL) { + krb5_free_data(context, ctx->encoded_previous_request); + ctx->encoded_previous_request = NULL; + } + if (ctx->request->padata) + ctx->sent_nontrivial_preauth = 1; + if (ctx->enc_pa_rep_permitted) + code = request_enc_pa_rep(&ctx->request->padata); + if (code) + goto cleanup; + code = krb5int_fast_prep_req(context, ctx->fast_state, + ctx->request, ctx->outer_request_body, + encode_krb5_as_req, + &ctx->encoded_previous_request); + if (code != 0) + goto cleanup; - /* set up the other state. */ + code = krb5int_copy_data_contents(context, + ctx->encoded_previous_request, + out); + if (code != 0) + goto cleanup; - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) { - if ((ret = make_preauth_list(context, options->preauth_list, - options->preauth_list_length, - &preauth_to_use))) - goto cleanup; +cleanup: + krb5_free_pa_data(context, ctx->request->padata); + ctx->request->padata = NULL; + return code; +} + +/* + * The control flow is complicated. In order to switch from non-FAST mode to + * FAST mode, we need to reset our pre-authentication state. FAST negotiation + * attempts to make sure we rarely have to do this. When FAST negotiation is + * working, we record whether FAST is available when we obtain an armor ticket; + * if so, we start out with FAST enabled . There are two complicated + * situations. + * + * First, if we get a PREAUTH_REQUIRED error including PADATA_FX_FAST back from + * a KDC in a case where we were not expecting to use FAST, and we have an + * armor ticket available, then we want to use FAST. That involves clearing + * out the pre-auth state, reinitializing the plugins and trying again with an + * armor key. + * + * Secondly, using the negotiation can cause problems with some older KDCs. + * Negotiation involves including a special padata item. Some KDCs, including + * MIT prior to 1.7, will return PREAUTH_FAILED rather than PREAUTH_REQUIRED in + * pre-authentication is required and unknown padata are included in the + * request. To make matters worse, these KDCs typically do not include a list + * of padata in PREAUTH_FAILED errors. So, if we get PREAUTH_FAILED and we + * generated no pre-authentication other than the negotiation then we want to + * retry without negotiation. In this case it is probably also desirable to + * retry with the preauth plugin state cleared. + * + * In all these cases we should not start over more than once. Control flow is + * managed by several variables. + * + * sent_nontrivial_preauth: if true, we sent preauth other than negotiation; + * no restart on PREAUTH_FAILED + * + * KRB5INT_FAST_ARMOR_AVAIL: fast_state_flag if desired we could generate + * armor; if not set, then we can't use FAST even if the KDC wants to. + * + * have_restarted: true if we've already restarted + */ +static krb5_boolean +negotiation_requests_restart(krb5_context context, krb5_init_creds_context ctx, + krb5_pa_data **padata) +{ + if (ctx->have_restarted) + return FALSE; + if (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata)) { + TRACE_INIT_CREDS_RESTART_FAST(context); + return TRUE; + } + if (ctx->err_reply->error == KDC_ERR_PREAUTH_FAILED && + !ctx->sent_nontrivial_preauth) { + TRACE_INIT_CREDS_RESTART_PREAUTH_FAILED(context); + return TRUE; } + return FALSE; +} - /* the salt is allocated from somewhere, unless it is from the caller, - then it is a reference */ +/* Ensure that the reply enctype was among the requested enctypes. */ +static krb5_error_code +check_reply_enctype(krb5_init_creds_context ctx) +{ + int i; - if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT)) { - salt = *options->salt; - } else { - salt.length = SALT_TYPE_AFS_LENGTH; - salt.data = NULL; + for (i = 0; i < ctx->request->nktypes; i++) { + if (ctx->request->ktype[i] == ctx->reply->enc_part.enctype) + return 0; } + return KRB5_CONFIG_ETYPE_NOSUPP; +} +/* Note the difference between the KDC's time, as reported to us in a + * preauth-required error, and the current time. */ +static void +note_req_timestamp(krb5_context kcontext, krb5_clpreauth_rock rock, + krb5_timestamp kdc_time, krb5_int32 kdc_usec) +{ + krb5_timestamp now; + krb5_int32 usec; + + if (k5_time_with_offset(0, 0, &now, &usec) != 0) + return; + rock->pa_offset = kdc_time - now; + rock->pa_offset_usec = kdc_usec - usec; + rock->pa_offset_state = (rock->fast_state->armor_key != NULL) ? + AUTH_OFFSET : UNAUTH_OFFSET; +} - /* set the request nonce */ - if ((ret = krb5_timeofday(context, &time_now))) - goto cleanup; - /* - * XXX we know they are the same size... and we should do - * something better than just the current time - */ - { - unsigned char random_buf[4]; - krb5_data random_data; - - random_data.length = 4; - random_data.data = (char *)random_buf; - if (krb5_c_random_make_octets(context, &random_data) == 0) - /* See RT ticket 3196 at MIT. If we set the high bit, we - may have compatibility problems with Heimdal, because - we (incorrectly) encode this value as signed. */ - request.nonce = 0x7fffffff & load_32_n(random_buf); - else - /* XXX Yuck. Old version. */ - request.nonce = (krb5_int32) time_now; - } - /* give the preauth plugins a chance to prep the request body */ - krb5_preauth_prepare_request(context, options, &request); - ret = encode_krb5_kdc_req_body(&request, &encoded_request_body); - if (ret) +static krb5_error_code +init_creds_step_reply(krb5_context context, + krb5_init_creds_context ctx, + krb5_data *in) +{ + krb5_error_code code; + krb5_pa_data **kdc_padata = NULL; + krb5_boolean retry = FALSE; + int canon_flag = 0; + krb5_keyblock *strengthen_key = NULL; + krb5_keyblock encrypting_key; + krb5_boolean fast_avail, got_real; + + encrypting_key.length = 0; + encrypting_key.contents = NULL; + + /* process previous KDC response */ + code = init_creds_validate_reply(context, ctx, in); + if (code != 0) goto cleanup; - get_data_rock.magic = CLIENT_ROCK_MAGIC; - get_data_rock.as_reply = NULL; - - /* now, loop processing preauth data and talking to the kdc */ - for (loopcount = 0; loopcount < MAX_IN_TKT_LOOPS; loopcount++) { - if (request.padata) { - krb5_free_pa_data(context, request.padata); - request.padata = NULL; - } - if (!err_reply) { - /* either our first attempt, or retrying after PREAUTH_NEEDED */ - if ((ret = krb5_do_preauth(context, - &request, - encoded_request_body, - encoded_previous_request, - preauth_to_use, &request.padata, - &salt, &s2kparams, &etype, &as_key, - prompter, prompter_data, - gak_fct, gak_data, - &get_data_rock, options))) - goto cleanup; - } else { - if (preauth_to_use != NULL) { - /* - * Retry after an error other than PREAUTH_NEEDED, - * using e-data to figure out what to change. - */ - ret = krb5_do_preauth_tryagain(context, - &request, - encoded_request_body, - encoded_previous_request, - preauth_to_use, &request.padata, - err_reply, - &salt, &s2kparams, &etype, - &as_key, - prompter, prompter_data, - gak_fct, gak_data, - &get_data_rock, options); - } else { - /* No preauth supplied, so can't query the plug-ins. */ - ret = KRB5KRB_ERR_GENERIC; - } - if (ret) { - /* couldn't come up with anything better */ - ret = err_reply->error + ERROR_TABLE_BASE_krb5; - } - krb5_free_error(context, err_reply); - err_reply = NULL; - if (ret) - goto cleanup; - } - - if (encoded_previous_request != NULL) { - krb5_free_data(context, encoded_previous_request); - encoded_previous_request = NULL; + /* per referrals draft, enterprise principals imply canonicalization */ + canon_flag = ((ctx->request->kdc_options & KDC_OPT_CANONICALIZE) != 0) || + ctx->request->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL; + + if (ctx->err_reply != NULL) { + code = krb5int_fast_process_error(context, ctx->fast_state, + &ctx->err_reply, &ctx->err_padata, + &retry); + if (code != 0) + goto cleanup; + if (negotiation_requests_restart(context, ctx, ctx->err_padata)) { + ctx->have_restarted = 1; + krb5_preauth_request_context_fini(context); + if ((ctx->fast_state->fast_state_flags & KRB5INT_FAST_DO_FAST) ==0) + ctx->enc_pa_rep_permitted = 0; + code = restart_init_creds_loop(context, ctx, ctx->err_padata); + krb5_free_error(context, ctx->err_reply); + ctx->err_reply = NULL; + krb5_free_pa_data(context, ctx->err_padata); + ctx->err_padata = NULL; + } else if (ctx->err_reply->error == KDC_ERR_PREAUTH_REQUIRED && + retry) { + /* reset the list of preauth types to try */ + krb5_free_pa_data(context, ctx->preauth_to_use); + ctx->preauth_to_use = ctx->err_padata; + ctx->err_padata = NULL; + note_req_timestamp(context, &ctx->preauth_rock, + ctx->err_reply->stime, ctx->err_reply->susec); + /* this will trigger a new call to krb5_do_preauth() */ + krb5_free_error(context, ctx->err_reply); + ctx->err_reply = NULL; + code = sort_krb5_padata_sequence(context, + &ctx->request->client->realm, + ctx->preauth_to_use); + ctx->preauth_required = TRUE; + + } else if (canon_flag && ctx->err_reply->error == KDC_ERR_WRONG_REALM) { + if (ctx->err_reply->client == NULL || + !krb5_princ_realm(context, ctx->err_reply->client)->length) { + code = KRB5KDC_ERR_WRONG_REALM; + goto cleanup; + } + TRACE_INIT_CREDS_REFERRAL(context, &ctx->err_reply->client->realm); + /* Rewrite request.client with realm from error reply */ + krb5_free_data_contents(context, &ctx->request->client->realm); + code = krb5int_copy_data_contents(context, + &ctx->err_reply->client->realm, + &ctx->request->client->realm); + /* this will trigger a new call to krb5_do_preauth() */ + krb5_free_error(context, ctx->err_reply); + ctx->err_reply = NULL; + krb5_preauth_request_context_fini(context); + /* Permit another negotiation based restart. */ + ctx->have_restarted = 0; + ctx->sent_nontrivial_preauth = 0; + code = restart_init_creds_loop(context, ctx, NULL); + if (code != 0) + goto cleanup; + } else { + if (retry) { + code = 0; + } else { + /* error + no hints = give up */ + code = (krb5_error_code)ctx->err_reply->error + + ERROR_TABLE_BASE_krb5; + } } - ret = encode_krb5_as_req(&request, &encoded_previous_request); - if (ret) - goto cleanup; - - err_reply = 0; - local_as_reply = 0; - if ((ret = send_as_request(context, encoded_previous_request, - krb5_princ_realm(context, request.client), &err_reply, - &local_as_reply, use_master))) - goto cleanup; - - if (err_reply) { - if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED && - err_reply->e_data.length > 0) { - /* reset the list of preauth types to try */ - if (preauth_to_use) { - krb5_free_pa_data(context, preauth_to_use); - preauth_to_use = NULL; - } - ret = decode_krb5_padata_sequence(&err_reply->e_data, - &preauth_to_use); - krb5_free_error(context, err_reply); - err_reply = NULL; - if (ret) - goto cleanup; - ret = sort_krb5_padata_sequence(context, - &request.server->realm, - preauth_to_use); - if (ret) - goto cleanup; - /* continue to next iteration */ - } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) { - if (err_reply->client == NULL || - err_reply->client->realm.length == 0) { - ret = KRB5KDC_ERR_WRONG_REALM; - krb5_free_error(context, err_reply); - goto cleanup; - } - /* Rewrite request.client with realm from error reply */ - if (referred_client.realm.data) { - krb5_free_data_contents(context, &referred_client.realm); - referred_client.realm.data = NULL; - } - ret = krb5int_copy_data_contents(context, - &err_reply->client->realm, - &referred_client.realm); - krb5_free_error(context, err_reply); - err_reply = NULL; - if (ret) - goto cleanup; - request.client = &referred_client; - } else { - if (err_reply->e_data.length > 0) { - /* continue to next iteration */ - } else { - /* error + no hints = give up */ - ret = (krb5_error_code) err_reply->error - + ERROR_TABLE_BASE_krb5; - krb5_free_error(context, err_reply); - goto cleanup; - } - } - } else if (local_as_reply) { - break; - } else { - ret = KRB5KRB_AP_ERR_MSG_TYPE; - goto cleanup; - } - } -#if APPLE_PKINIT - inTktDebug("krb5_get_init_creds done with send_as_request loop lc %d\n", - (int)loopcount); -#endif /* APPLE_PKINIT */ - if (loopcount == MAX_IN_TKT_LOOPS) { - ret = KRB5_GET_IN_TKT_LOOP; - goto cleanup; + /* Return error code, or continue with next iteration */ + goto cleanup; } + /* We have a response. Process it. */ + assert(ctx->reply != NULL); + + /* Check for replies (likely forged) with unasked-for enctypes. */ + code = check_reply_enctype(ctx); + if (code != 0) + goto cleanup; + /* process any preauth data in the as_reply */ krb5_clear_preauth_context_use_counts(context); - if ((ret = sort_krb5_padata_sequence(context, &request.server->realm, - local_as_reply->padata))) - goto cleanup; - get_data_rock.as_reply = local_as_reply; - if ((ret = krb5_do_preauth(context, - &request, - encoded_request_body, encoded_previous_request, - local_as_reply->padata, &kdc_padata, - &salt, &s2kparams, &etype, &as_key, prompter, - prompter_data, gak_fct, gak_data, - &get_data_rock, options))) { -#if APPLE_PKINIT - inTktDebug("krb5_get_init_creds krb5_do_preauth returned %d\n", (int)ret); -#endif /* APPLE_PKINIT */ - goto cleanup; - } + code = krb5int_fast_process_response(context, ctx->fast_state, + ctx->reply, &strengthen_key); + if (code != 0) + goto cleanup; + + code = sort_krb5_padata_sequence(context, &ctx->request->client->realm, + ctx->reply->padata); + if (code != 0) + goto cleanup; + + ctx->etype = ctx->reply->enc_part.enctype; + + code = krb5_do_preauth(context, + ctx->request, + ctx->inner_request_body, + ctx->encoded_previous_request, + ctx->reply->padata, + &kdc_padata, + ctx->prompter, + ctx->prompter_data, + &ctx->preauth_rock, + ctx->opte, + &got_real); + if (code != 0) + goto cleanup; + + /* + * If we haven't gotten a salt from another source yet, set up one + * corresponding to the client principal returned by the KDC. We + * could get the same effect by passing local_as_reply->client to + * gak_fct below, but that would put the canonicalized client name + * in the prompt, which raises issues of needing to sanitize + * unprintable characters. So for now we just let it affect the + * salt. local_as_reply->client will be checked later on in + * verify_as_reply. + */ + if (ctx->default_salt) { + code = krb5_principal2salt(context, ctx->reply->client, &ctx->salt); + TRACE_INIT_CREDS_SALT_PRINC(context, &ctx->salt); + if (code != 0) + goto cleanup; + } /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY, the AS_REP comes back encrypted in the user's longterm key instead of in the SAD. If there was a SAM preauth, there will be an as_key here which will be the SAD. If that fails, use the gak_fct to get the password, and try again. */ - + /* XXX because etypes are handled poorly (particularly wrt SAM, where the etype is fixed by the kdc), we may want to try decrypt_as_reply twice. If there's an as_key available, try it. If decrypting the as_rep fails, or if there isn't an as_key at all yet, then use the gak_fct to get one, and try again. */ - - if (as_key.length) - ret = decrypt_as_reply(context, NULL, local_as_reply, NULL, - NULL, &as_key, krb5_kdc_rep_decrypt_proc, - NULL); - else - ret = -1; - - if (ret) { - /* if we haven't get gotten a key, get it now */ - - if ((ret = ((*gak_fct)(context, request.client, - local_as_reply->enc_part.enctype, - prompter, prompter_data, &salt, &s2kparams, - &as_key, gak_data)))) - goto cleanup; - - if ((ret = decrypt_as_reply(context, NULL, local_as_reply, NULL, - NULL, &as_key, krb5_kdc_rep_decrypt_proc, - NULL))) - goto cleanup; + if (ctx->as_key.length) { + TRACE_INIT_CREDS_AS_KEY_PREAUTH(context, &ctx->as_key); + code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key, + &encrypting_key); + if (code != 0) + goto cleanup; + code = decrypt_as_reply(context, NULL, ctx->reply, &encrypting_key); + if (code != 0) + TRACE_INIT_CREDS_PREAUTH_DECRYPT_FAIL(context, code); + } else + code = -1; + + if (code != 0) { + /* if we haven't get gotten a key, get it now */ + TRACE_INIT_CREDS_GAK(context, &ctx->salt, &ctx->s2kparams); + code = (*ctx->gak_fct)(context, ctx->request->client, + ctx->reply->enc_part.enctype, + ctx->prompter, ctx->prompter_data, + &ctx->salt, &ctx->s2kparams, + &ctx->as_key, ctx->gak_data); + if (code != 0) + goto cleanup; + TRACE_INIT_CREDS_AS_KEY_GAK(context, &ctx->as_key); + + code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key, + &encrypting_key); + if (code != 0) + goto cleanup; + + code = decrypt_as_reply(context, NULL, ctx->reply, &encrypting_key); + if (code != 0) + goto cleanup; } - if ((ret = verify_as_reply(context, time_now, &request, local_as_reply))) - goto cleanup; + TRACE_INIT_CREDS_DECRYPTED_REPLY(context, ctx->reply->enc_part2->session); - /* XXX this should be inside stash_as_reply, but as long as - get_in_tkt is still around using that arg as an in/out, I can't - do that */ - memset(creds, 0, sizeof(*creds)); + code = krb5int_fast_verify_nego(context, ctx->fast_state, + ctx->reply, ctx->encoded_previous_request, + &encrypting_key, &fast_avail); + if (code) + goto cleanup; + code = verify_as_reply(context, ctx->request_time, + ctx->request, ctx->reply); + if (code != 0) + goto cleanup; + code = verify_anonymous(context, ctx->request, ctx->reply, + &ctx->as_key); + if (code) + goto cleanup; - if ((ret = stash_as_reply(context, time_now, &request, local_as_reply, - creds, NULL))) - goto cleanup; + code = stash_as_reply(context, ctx->request_time, ctx->request, + ctx->reply, &ctx->cred, NULL); + if (code != 0) + goto cleanup; + if (ctx->opte && ctx->opte->opt_private->out_ccache) { + krb5_ccache out_ccache = ctx->opte->opt_private->out_ccache; + krb5_data config_data; + code = krb5_cc_initialize(context, out_ccache, ctx->cred.client); + if (code != 0) + goto cc_cleanup; + code = krb5_cc_store_cred(context, out_ccache, &ctx->cred); + if (code != 0) + goto cc_cleanup; + if (fast_avail) { + config_data.data = "yes"; + config_data.length = strlen(config_data.data); + code = krb5_cc_set_config(context, out_ccache, ctx->cred.server, + KRB5_CONF_FAST_AVAIL, &config_data); + } + cc_cleanup: + if (code !=0) { + const char *msg; + msg = krb5_get_error_message(context, code); + krb5_set_error_message(context, code, + _("%s while storing credentials"), msg); + krb5_free_error_message(context, msg); + } + } + + krb5_preauth_request_context_fini(context); /* success */ + code = 0; + ctx->complete = TRUE; + +cleanup: + krb5_free_pa_data(context, kdc_padata); + krb5_free_keyblock(context, strengthen_key); + krb5_free_keyblock_contents(context, &encrypting_key); + + return code; +} + +/* + * Do next step of credentials acquisition. + * + * On success returns 0 or KRB5KRB_ERR_RESPONSE_TOO_BIG if the request + * should be sent with TCP. + */ +krb5_error_code KRB5_CALLCONV +krb5_init_creds_step(krb5_context context, + krb5_init_creds_context ctx, + krb5_data *in, + krb5_data *out, + krb5_data *realm, + unsigned int *flags) +{ + krb5_error_code code = 0, code2; + + *flags = 0; + + out->data = NULL; + out->length = 0; + + realm->data = NULL; + realm->length = 0; + + if (ctx->complete) + return EINVAL; + + if (in->length != 0) { + code = init_creds_step_reply(context, ctx, in); + if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) { + code2 = krb5int_copy_data_contents(context, + ctx->encoded_previous_request, + out); + if (code2 != 0) { + code = code2; + goto cleanup; + } + goto copy_realm; + } + if (code != 0 || ctx->complete) + goto cleanup; + } + + code = init_creds_step_request(context, ctx, out); + if (code != 0) + goto cleanup; + + /* Only a new request increments the loop count, not a TCP retry */ + ctx->loopcount++; - ret = 0; +copy_realm: + assert(ctx->request->server != NULL); + + code2 = krb5int_copy_data_contents(context, + &ctx->request->server->realm, + realm); + if (code2 != 0) { + code = code2; + goto cleanup; + } cleanup: - if (ret != 0) { - char *client_name; - /* See if we can produce a more detailed error message. */ - switch (ret) { - case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN: - client_name = NULL; - if (krb5_unparse_name(context, client, &client_name) == 0) { - krb5_set_error_message(context, ret, - "Client '%s' not found in Kerberos database", - client_name); - free(client_name); - } - break; - default: - break; - } + if (code == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN) { + char *client_name; + + /* See if we can produce a more detailed error message */ + code2 = krb5_unparse_name(context, ctx->request->client, &client_name); + if (code2 == 0) { + krb5_set_error_message(context, code, + _("Client '%s' not found in Kerberos " + "database"), client_name); + krb5_free_unparsed_name(context, client_name); + } } - krb5_preauth_request_context_fini(context); - if (encoded_previous_request != NULL) { - krb5_free_data(context, encoded_previous_request); - encoded_previous_request = NULL; + + *flags = ctx->complete ? 0 : KRB5_INIT_CREDS_STEP_FLAG_CONTINUE; + return code; +} + +krb5_error_code KRB5_CALLCONV +krb5int_get_init_creds(krb5_context context, + krb5_creds *creds, + krb5_principal client, + krb5_prompter_fct prompter, + void *prompter_data, + krb5_deltat start_time, + char *in_tkt_service, + krb5_get_init_creds_opt *options, + krb5_gic_get_as_key_fct gak_fct, + void *gak_data, + int *use_master, + krb5_kdc_rep **as_reply) +{ + krb5_error_code code; + krb5_init_creds_context ctx = NULL; + + code = krb5_init_creds_init(context, + client, + prompter, + prompter_data, + start_time, + options, + &ctx); + if (code != 0) + goto cleanup; + + ctx->gak_fct = gak_fct; + ctx->gak_data = gak_data; + + if (in_tkt_service) { + code = krb5_init_creds_set_service(context, ctx, in_tkt_service); + if (code != 0) + goto cleanup; + } + + code = k5_init_creds_get(context, ctx, use_master); + if (code != 0) + goto cleanup; + + code = krb5_init_creds_get_creds(context, ctx, creds); + if (code != 0) + goto cleanup; + + if (as_reply != NULL) { + *as_reply = ctx->reply; + ctx->reply = NULL; + } + +cleanup: + krb5_init_creds_free(context, ctx); + + return code; +} + +krb5_error_code +krb5int_populate_gic_opt(krb5_context context, krb5_get_init_creds_opt **out, + krb5_flags options, krb5_address *const *addrs, + krb5_enctype *ktypes, + krb5_preauthtype *pre_auth_types, krb5_creds *creds) +{ + int i; + krb5_int32 starttime; + krb5_get_init_creds_opt *opt; + krb5_error_code retval; + + *out = NULL; + retval = krb5_get_init_creds_opt_alloc(context, &opt); + if (retval) + return(retval); + + if (addrs) + krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs); + if (ktypes) { + i = krb5int_count_etypes(ktypes); + if (i) + krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i); + } + if (pre_auth_types) { + for (i=0; pre_auth_types[i]; i++); + if (i) + krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i); } - if (encoded_request_body != NULL) { - krb5_free_data(context, encoded_request_body); - encoded_request_body = NULL; + if (options&KDC_OPT_FORWARDABLE) + krb5_get_init_creds_opt_set_forwardable(opt, 1); + else krb5_get_init_creds_opt_set_forwardable(opt, 0); + if (options&KDC_OPT_PROXIABLE) + krb5_get_init_creds_opt_set_proxiable(opt, 1); + else krb5_get_init_creds_opt_set_proxiable(opt, 0); + if (creds && creds->times.endtime) { + retval = krb5_timeofday(context, &starttime); + if (retval) + goto cleanup; + if (creds->times.starttime) starttime = creds->times.starttime; + krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime); } - if (request.server) - krb5_free_principal(context, request.server); - if (request.ktype && - (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)))) - free(request.ktype); - if (request.addresses && - (!(options && - (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)))) - krb5_free_addresses(context, request.addresses); - if (preauth_to_use) - krb5_free_pa_data(context, preauth_to_use); - if (kdc_padata) - krb5_free_pa_data(context, kdc_padata); - if (request.padata) - krb5_free_pa_data(context, request.padata); - if (as_key.length) - krb5_free_keyblock_contents(context, &as_key); - if (salt.data && - (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT)))) - free(salt.data); - krb5_free_data_contents(context, &s2kparams); - if (as_reply) - *as_reply = local_as_reply; - else if (local_as_reply) - krb5_free_kdc_rep(context, local_as_reply); - if (referred_client.realm.data) - krb5_free_data_contents(context, &referred_client.realm); - - return(ret); + *out = opt; + return 0; + +cleanup: + krb5_get_init_creds_opt_free(context, opt); + return retval; }