/* * Modifies: * the memory cache * * Effects: * Save away creds in the ccache. * * Errors: * system errors (mutex locking) * ENOMEM */ krb5_error_code KRB5_CALLCONV krb5_mcc_store(krb5_context ctx, krb5_ccache id, krb5_creds *creds) { krb5_error_code err; krb5_mcc_link *new_node; krb5_mcc_data *mptr = (krb5_mcc_data *)id->data; new_node = malloc(sizeof(krb5_mcc_link)); if (new_node == NULL) return ENOMEM; err = krb5_copy_creds(ctx, creds, &new_node->creds); if (err) goto cleanup; err = k5_cc_mutex_lock(ctx, &mptr->lock); if (err) goto cleanup; new_node->next = mptr->link; mptr->link = new_node; update_mcc_change_time(mptr); k5_cc_mutex_unlock(ctx, &mptr->lock); return 0; cleanup: free(new_node); return err; }
krb5_error_code KRB5_CALLCONV krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache, krb5_creds *in_creds, krb5_flags options, krb5_tkt_creds_context *pctx) { krb5_error_code code; krb5_tkt_creds_context ctx = NULL; TRACE_TKT_CREDS(context, in_creds, ccache); ctx = k5alloc(sizeof(*ctx), &code); if (ctx == NULL) goto cleanup; ctx->req_options = options; ctx->req_kdcopt = 0; if (options & KRB5_GC_CANONICALIZE) ctx->req_kdcopt |= KDC_OPT_CANONICALIZE; if (options & KRB5_GC_FORWARDABLE) ctx->req_kdcopt |= KDC_OPT_FORWARDABLE; if (options & KRB5_GC_NO_TRANSIT_CHECK) ctx->req_kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK; if (options & KRB5_GC_CONSTRAINED_DELEGATION) { if (options & KRB5_GC_USER_USER) { code = EINVAL; goto cleanup; } ctx->req_kdcopt |= KDC_OPT_FORWARDABLE | KDC_OPT_CNAME_IN_ADDL_TKT; } ctx->state = STATE_BEGIN; ctx->cache_code = KRB5_CC_NOTFOUND; code = krb5_copy_creds(context, in_creds, &ctx->in_creds); if (code != 0) goto cleanup; ctx->client = ctx->in_creds->client; ctx->server = ctx->in_creds->server; code = krb5_copy_principal(context, ctx->server, &ctx->req_server); if (code != 0) goto cleanup; code = krb5_cc_dup(context, ccache, &ctx->ccache); if (code != 0) goto cleanup; code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata); if (code != 0) goto cleanup; *pctx = ctx; ctx = NULL; cleanup: krb5_tkt_creds_free(context, ctx); return code; }
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL krb5_tkt_creds_get_creds(krb5_context context, krb5_tkt_creds_context ctx, krb5_creds **cred) { if (ctx->state != NULL) return EINVAL; if (ctx->cred) return krb5_copy_creds(context, ctx->cred, cred); return ctx->error; }
static krb5_error_code add_cred(krb5_context context, krb5_creds const *tkt, krb5_creds ***tgts) { int i; krb5_error_code ret; krb5_creds **tmp = *tgts; for(i = 0; tmp && tmp[i]; i++); /* XXX */ tmp = realloc(tmp, (i+2)*sizeof(*tmp)); if(tmp == NULL) return krb5_enomem(context); *tgts = tmp; ret = krb5_copy_creds(context, tkt, &tmp[i]); tmp[i+1] = NULL; return ret; }
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_flags options, krb5_tkt_creds_context *pctx) { krb5_tkt_creds_context ctx; krb5_error_code ret; *pctx = NULL; ctx = heim_uniq_alloc(sizeof(*ctx), "tkt-ctx", tkt_release); if (ctx == NULL) return ENOMEM; ctx->context = context; ctx->state = tkt_init; ctx->options = options; ctx->ccache = ccache; if (ctx->options & KRB5_GC_FORWARDABLE) ctx->req_kdc_flags.b.forwardable = 1; if (ctx->options & KRB5_GC_USER_USER) { ctx->req_kdc_flags.b.enc_tkt_in_skey = 1; ctx->options |= KRB5_GC_NO_STORE; } if (options & KRB5_GC_CANONICALIZE) ctx->req_kdc_flags.b.canonicalize = 1; ret = krb5_copy_creds(context, in_cred, &ctx->in_cred); if (ret) { heim_release(ctx); return ret; } ret = krb5_unparse_name(context, ctx->in_cred->server, &ctx->server_name); if (ret) { heim_release(ctx); return ret; } *pctx = ctx; return 0; }
static krb5_error_code add_cred(krb5_context context, krb5_creds ***tgts, krb5_creds *tkt) { int i; krb5_error_code ret; krb5_creds **tmp = *tgts; for(i = 0; tmp && tmp[i]; i++); /* XXX */ tmp = realloc(tmp, (i+2)*sizeof(*tmp)); if(tmp == NULL) { krb5_set_error_string(context, "malloc: out of memory"); return ENOMEM; } *tgts = tmp; ret = krb5_copy_creds(context, tkt, &tmp[i]); tmp[i+1] = NULL; return ret; }
static krb5_error_code get_cred_kdc_referral(krb5_context context, krb5_kdc_flags flags, krb5_ccache ccache, krb5_creds *in_creds, krb5_principal impersonate_principal, Ticket *second_ticket, krb5_creds **out_creds, krb5_creds ***ret_tgts) { krb5_const_realm client_realm; krb5_error_code ret; krb5_creds tgt, referral, ticket; int loop = 0; int ok_as_delegate = 1; if (in_creds->server->name.name_string.len < 2 && !flags.b.canonicalize) { krb5_set_error_message(context, KRB5KDC_ERR_PATH_NOT_ACCEPTED, N_("Name too short to do referals, skipping", "")); return KRB5KDC_ERR_PATH_NOT_ACCEPTED; } memset(&tgt, 0, sizeof(tgt)); memset(&ticket, 0, sizeof(ticket)); flags.b.canonicalize = 1; *out_creds = NULL; client_realm = krb5_principal_get_realm(context, in_creds->client); /* find tgt for the clients base realm */ { krb5_principal tgtname; ret = krb5_make_principal(context, &tgtname, client_realm, KRB5_TGS_NAME, client_realm, NULL); if(ret) return ret; ret = find_cred(context, ccache, tgtname, *ret_tgts, &tgt); krb5_free_principal(context, tgtname); if (ret) return ret; } referral = *in_creds; ret = krb5_copy_principal(context, in_creds->server, &referral.server); if (ret) { krb5_free_cred_contents(context, &tgt); return ret; } ret = krb5_principal_set_realm(context, referral.server, client_realm); if (ret) { krb5_free_cred_contents(context, &tgt); krb5_free_principal(context, referral.server); return ret; } while (loop++ < 17) { krb5_creds **tickets; krb5_creds mcreds; char *referral_realm; /* Use cache if we are not doing impersonation or contrainte deleg */ if (impersonate_principal == NULL || flags.b.constrained_delegation) { krb5_cc_clear_mcred(&mcreds); mcreds.server = referral.server; ret = krb5_cc_retrieve_cred(context, ccache, 0, &mcreds, &ticket); } else ret = EINVAL; if (ret) { ret = get_cred_kdc_address(context, ccache, flags, NULL, &referral, &tgt, impersonate_principal, second_ticket, &ticket); if (ret) goto out; } /* Did we get the right ticket ? */ if (krb5_principal_compare_any_realm(context, referral.server, ticket.server)) break; if (!krb5_principal_is_krbtgt(context, ticket.server)) { krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US, N_("Got back an non krbtgt " "ticket referrals", "")); ret = KRB5KRB_AP_ERR_NOT_US; goto out; } referral_realm = ticket.server->name.name_string.val[1]; /* check that there are no referrals loops */ tickets = *ret_tgts; krb5_cc_clear_mcred(&mcreds); mcreds.server = ticket.server; while(tickets && *tickets){ if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM, &mcreds, *tickets)) { krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP, N_("Referral from %s " "loops back to realm %s", ""), tgt.server->realm, referral_realm); ret = KRB5_GET_IN_TKT_LOOP; goto out; } tickets++; } /* * if either of the chain or the ok_as_delegate was stripped * by the kdc, make sure we strip it too. */ if (ok_as_delegate == 0 || ticket.flags.b.ok_as_delegate == 0) { ok_as_delegate = 0; ticket.flags.b.ok_as_delegate = 0; } ret = add_cred(context, &ticket, ret_tgts); if (ret) goto out; /* try realm in the referral */ ret = krb5_principal_set_realm(context, referral.server, referral_realm); krb5_free_cred_contents(context, &tgt); tgt = ticket; memset(&ticket, 0, sizeof(ticket)); if (ret) goto out; } ret = krb5_copy_creds(context, &ticket, out_creds); out: krb5_free_principal(context, referral.server); krb5_free_cred_contents(context, &tgt); krb5_free_cred_contents(context, &ticket); return ret; }
static krb5_error_code tkt_referral_recv(krb5_context context, krb5_tkt_creds_context ctx, krb5_data *in, krb5_data *out, krb5_realm *realm, unsigned int *flags) { krb5_error_code ret; krb5_creds outcred, mcred; unsigned long n; _krb5_debugx(context, 10, "tkt_referral_recv: %s", ctx->server_name); memset(&outcred, 0, sizeof(outcred)); ret = parse_tgs_rep(context, ctx, in, &outcred); if (ret) { _krb5_debugx(context, 10, "tkt_referral_recv: parse_tgs_rep %d", ret); tkt_reset(context, ctx); ctx->state = tkt_capath_init; return 0; } /* * Check if we found the right ticket */ if (krb5_principal_compare_any_realm(context, ctx->next.server, outcred.server)) { ret = krb5_copy_creds(context, &outcred, &ctx->cred); if (ret) return (ctx->error = ret); krb5_free_cred_contents(context, &outcred); ctx->state = tkt_store; return 0; } if (!krb5_principal_is_krbtgt(context, outcred.server)) { krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US, N_("Got back an non krbtgt " "ticket referrals", "")); krb5_free_cred_contents(context, &outcred); ctx->state = tkt_capath_init; return 0; } _krb5_debugx(context, 10, "KDC for realm %s sends a referrals to %s", ctx->tgt.server->realm, outcred.server->name.name_string.val[1]); /* * check if there is a loop */ krb5_cc_clear_mcred(&mcred); mcred.server = outcred.server; for (n = 0; ctx->tickets && ctx->tickets[n]; n++) { if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM, &mcred, ctx->tickets[n])) { _krb5_debugx(context, 5, "Referral from %s loops back to realm %s", ctx->tgt.server->realm, outcred.server->realm); ctx->state = tkt_capath_init; return 0; } } #define MAX_KDC_REFERRALS_LOOPS 15 if (n > MAX_KDC_REFERRALS_LOOPS) { ctx->state = tkt_capath_init; return 0; } /* * filter out ok-as-delegate if needed */ if (ctx->ok_as_delegate == 0 || outcred.flags.b.ok_as_delegate == 0) { ctx->ok_as_delegate = 0; outcred.flags.b.ok_as_delegate = 0; } /* add to iteration cache */ ret = add_cred(context, &outcred, &ctx->tickets); if (ret) { ctx->state = tkt_capath_init; return 0; } /* set up next server to talk to */ krb5_free_cred_contents(context, &ctx->tgt); ctx->tgt = outcred; /* * Setup next target principal to target */ ret = krb5_principal_set_realm(context, ctx->next.server, ctx->tgt.server->realm); if (ret) { ctx->state = tkt_capath_init; return 0; } ctx->state = tkt_referral_send; return 0; }
static krb5_error_code krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts, int kdcopt) { krb5_error_code retval, subretval; krb5_principal client, server, supplied_server, out_supplied_server; krb5_creds tgtq, cc_tgt, *tgtptr, *referral_tgts[KRB5_REFERRAL_MAXHOPS]; krb5_creds *otgtptr = NULL; int tgtptr_isoffpath = 0; krb5_boolean old_use_conf_ktypes; char **hrealms; unsigned int referral_count, i; /* * Set up client and server pointers. Make a fresh and modifyable * copy of the in_cred server and save the supplied version. */ client = in_cred->client; if ((retval=krb5_copy_principal(context, in_cred->server, &server))) return retval; /* We need a second copy for the output creds. */ if ((retval = krb5_copy_principal(context, server, &out_supplied_server)) != 0 ) { krb5_free_principal(context, server); return retval; } supplied_server = in_cred->server; in_cred->server=server; DUMP_PRINC("gc_from_kdc initial client", client); DUMP_PRINC("gc_from_kdc initial server", server); memset(&cc_tgt, 0, sizeof(cc_tgt)); memset(&tgtq, 0, sizeof(tgtq)); memset(&referral_tgts, 0, sizeof(referral_tgts)); tgtptr = NULL; *tgts = NULL; *out_cred=NULL; old_use_conf_ktypes = context->use_conf_ktypes; /* Copy client realm to server if no hint. */ if (krb5_is_referral_realm(&server->realm)) { /* Use the client realm. */ DPRINTF(("gc_from_kdc: no server realm supplied, " "using client realm.\n")); krb5_free_data_contents(context, &server->realm); if (!( server->realm.data = (char *)malloc(client->realm.length+1))) return ENOMEM; memcpy(server->realm.data, client->realm.data, client->realm.length); server->realm.length = client->realm.length; server->realm.data[server->realm.length] = 0; } /* * Retreive initial TGT to match the specified server, either for the * local realm in the default (referral) case or for the remote * realm if we're starting someplace non-local. */ retval = tgt_mcred(context, client, server, client, &tgtq); if (retval) goto cleanup; /* Fast path: Is it in the ccache? */ context->use_conf_ktypes = 1; retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS, &tgtq, &cc_tgt); if (!retval) { tgtptr = &cc_tgt; } else if (!HARD_CC_ERR(retval)) { DPRINTF(("gc_from_kdc: starting do_traversal to find " "initial TGT for referral\n")); tgtptr_isoffpath = 0; otgtptr = NULL; retval = do_traversal(context, ccache, client, server, &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath); } if (retval) { DPRINTF(("gc_from_kdc: failed to find initial TGT for referral\n")); goto cleanup; } DUMP_PRINC("gc_from_kdc: server as requested", supplied_server); /* * Try requesting a service ticket from our local KDC with referrals * turned on. If the first referral succeeds, follow a referral-only * path, otherwise fall back to old-style assumptions. */ /* * Save TGTPTR because we rewrite it in the referral loop, and * we might need to explicitly free it later. */ otgtptr = tgtptr; for (referral_count = 0; referral_count < KRB5_REFERRAL_MAXHOPS; referral_count++) { #if 0 DUMP_PRINC("gc_from_kdc: referral loop: tgt in use", tgtptr->server); DUMP_PRINC("gc_from_kdc: referral loop: request is for", server); #endif retval = krb5_get_cred_via_tkt(context, tgtptr, KDC_OPT_CANONICALIZE | FLAGS2OPTS(tgtptr->ticket_flags) | kdcopt | (in_cred->second_ticket.length ? KDC_OPT_ENC_TKT_IN_SKEY : 0), tgtptr->addresses, in_cred, out_cred); if (retval) { DPRINTF(("gc_from_kdc: referral TGS-REQ request failed: <%s>\n", error_message(retval))); /* If we haven't gone anywhere yet, fail through to the non-referral case. */ if (referral_count==0) { DPRINTF(("gc_from_kdc: initial referral failed; " "punting to fallback.\n")); break; } /* Otherwise, try the same query without canonicalization set, and fail hard if that doesn't work. */ DPRINTF(("gc_from_kdc: referral #%d failed; " "retrying without option.\n", referral_count + 1)); retval = krb5_get_cred_via_tkt(context, tgtptr, FLAGS2OPTS(tgtptr->ticket_flags) | kdcopt | (in_cred->second_ticket.length ? KDC_OPT_ENC_TKT_IN_SKEY : 0), tgtptr->addresses, in_cred, out_cred); /* Whether or not that succeeded, we're done. */ goto cleanup; } /* Referral request succeeded; let's see what it is. */ if (krb5_principal_compare(context, in_cred->server, (*out_cred)->server)) { DPRINTF(("gc_from_kdc: request generated ticket " "for requested server principal\n")); DUMP_PRINC("gc_from_kdc final referred reply", in_cred->server); /* * Check if the return enctype is one that we requested if * needed. */ if (old_use_conf_ktypes || context->tgs_ktype_count == 0) goto cleanup; for (i = 0; i < context->tgs_ktype_count; i++) { if ((*out_cred)->keyblock.enctype == context->tgs_ktypes[i]) { /* Found an allowable etype, so we're done */ goto cleanup; } } /* * We need to try again, but this time use the * tgs_ktypes in the context. At this point we should * have all the tgts to succeed. */ /* Free "wrong" credential */ krb5_free_creds(context, *out_cred); *out_cred = NULL; /* Re-establish tgs etypes */ context->use_conf_ktypes = old_use_conf_ktypes; retval = krb5_get_cred_via_tkt(context, tgtptr, KDC_OPT_CANONICALIZE | FLAGS2OPTS(tgtptr->ticket_flags) | kdcopt | (in_cred->second_ticket.length ? KDC_OPT_ENC_TKT_IN_SKEY : 0), tgtptr->addresses, in_cred, out_cred); goto cleanup; } else if (IS_TGS_PRINC(context, (*out_cred)->server)) { krb5_data *r1, *r2; DPRINTF(("gc_from_kdc: request generated referral tgt\n")); DUMP_PRINC("gc_from_kdc credential received", (*out_cred)->server); if (referral_count == 0) r1 = &tgtptr->server->data[1]; else r1 = &referral_tgts[referral_count-1]->server->data[1]; r2 = &(*out_cred)->server->data[1]; if (data_eq(*r1, *r2)) { DPRINTF(("gc_from_kdc: referred back to " "previous realm; fall back\n")); krb5_free_creds(context, *out_cred); *out_cred = NULL; break; } /* Check for referral routing loop. */ for (i=0;i<referral_count;i++) { #if 0 DUMP_PRINC("gc_from_kdc: loop compare #1", (*out_cred)->server); DUMP_PRINC("gc_from_kdc: loop compare #2", referral_tgts[i]->server); #endif if (krb5_principal_compare(context, (*out_cred)->server, referral_tgts[i]->server)) { DFPRINTF((stderr, "krb5_get_cred_from_kdc_opt: " "referral routing loop - " "got referral back to hop #%d\n", i)); retval=KRB5_KDC_UNREACH; goto cleanup; } } /* Point current tgt pointer at newly-received TGT. */ if (tgtptr == &cc_tgt) krb5_free_cred_contents(context, tgtptr); tgtptr=*out_cred; /* Save pointer to tgt in referral_tgts. */ referral_tgts[referral_count]=*out_cred; *out_cred = NULL; /* Copy krbtgt realm to server principal. */ krb5_free_data_contents(context, &server->realm); retval = krb5int_copy_data_contents(context, &tgtptr->server->data[1], &server->realm); if (retval) return retval; /* * Future work: rewrite server principal per any * supplied padata. */ } else { /* Not a TGT; punt to fallback. */ krb5_free_creds(context, *out_cred); *out_cred = NULL; break; } } DUMP_PRINC("gc_from_kdc client at fallback", client); DUMP_PRINC("gc_from_kdc server at fallback", server); /* * At this point referrals have been tried and have failed. Go * back to the server principal as originally issued and try the * conventional path. */ /* * Referrals have failed. Look up fallback realm if not * originally provided. */ if (krb5_is_referral_realm(&supplied_server->realm)) { if (server->length >= 2) { retval=krb5_get_fallback_host_realm(context, &server->data[1], &hrealms); if (retval) goto cleanup; #if 0 DPRINTF(("gc_from_kdc: using fallback realm of %s\n", hrealms[0])); #endif krb5_free_data_contents(context,&in_cred->server->realm); server->realm.data=hrealms[0]; server->realm.length=strlen(hrealms[0]); free(hrealms); } else { /* * Problem case: Realm tagged for referral but apparently not * in a <type>/<host> format that * krb5_get_fallback_host_realm can deal with. */ DPRINTF(("gc_from_kdc: referral specified " "but no fallback realm avaiable!\n")); return KRB5_ERR_HOST_REALM_UNKNOWN; } } DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite", server); /* * Get a TGT for the target realm. */ krb5_free_cred_contents(context, &tgtq); retval = tgt_mcred(context, client, server, client, &tgtq); if (retval) goto cleanup; /* Fast path: Is it in the ccache? */ /* Free tgtptr data if reused from above. */ if (tgtptr == &cc_tgt) krb5_free_cred_contents(context, tgtptr); tgtptr = NULL; /* Free saved TGT in OTGTPTR if it was off-path. */ if (tgtptr_isoffpath) krb5_free_creds(context, otgtptr); otgtptr = NULL; /* Free TGTS if previously filled by do_traversal() */ if (*tgts != NULL) { for (i = 0; (*tgts)[i] != NULL; i++) { krb5_free_creds(context, (*tgts)[i]); } free(*tgts); *tgts = NULL; } context->use_conf_ktypes = 1; retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS, &tgtq, &cc_tgt); if (!retval) { tgtptr = &cc_tgt; } else if (!HARD_CC_ERR(retval)) { tgtptr_isoffpath = 0; retval = do_traversal(context, ccache, client, server, &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath); } if (retval) goto cleanup; otgtptr = tgtptr; /* * Finally have TGT for target realm! Try using it to get creds. */ if (!krb5_c_valid_enctype(tgtptr->keyblock.enctype)) { retval = KRB5_PROG_ETYPE_NOSUPP; goto cleanup; } context->use_conf_ktypes = old_use_conf_ktypes; retval = krb5_get_cred_via_tkt(context, tgtptr, FLAGS2OPTS(tgtptr->ticket_flags) | kdcopt | (in_cred->second_ticket.length ? KDC_OPT_ENC_TKT_IN_SKEY : 0), tgtptr->addresses, in_cred, out_cred); cleanup: krb5_free_cred_contents(context, &tgtq); if (tgtptr == &cc_tgt) krb5_free_cred_contents(context, tgtptr); if (tgtptr_isoffpath) krb5_free_creds(context, otgtptr); context->use_conf_ktypes = old_use_conf_ktypes; /* Drop the original principal back into in_cred so that it's cached in the expected format. */ DUMP_PRINC("gc_from_kdc: final hacked server principal at cleanup", server); krb5_free_principal(context, server); in_cred->server = supplied_server; if (*out_cred && !retval) { /* Success: free server, swap supplied server back in. */ krb5_free_principal (context, (*out_cred)->server); (*out_cred)->server= out_supplied_server; } else { /* * Failure: free out_supplied_server. Don't free out_cred here * since it's either null or a referral TGT that we free below, * and we may need it to return. */ krb5_free_principal (context, out_supplied_server); } DUMP_PRINC("gc_from_kdc: final server after reversion", in_cred->server); /* * Deal with ccache TGT management: If tgts has been set from * initial non-referral TGT discovery, leave it alone. Otherwise, if * referral_tgts[0] exists return it as the only entry in tgts. * (Further referrals are never cached, only the referral from the * local KDC.) This is part of cleanup because useful received TGTs * should be cached even if the main request resulted in failure. */ if (*tgts == NULL) { if (referral_tgts[0]) { #if 0 /* * This should possibly be a check on the candidate return * credential against the cache, in the circumstance where we * don't want to clutter the cache with near-duplicate * credentials on subsequent iterations. For now, it is * disabled. */ subretval=...?; if (subretval) { #endif /* Allocate returnable TGT list. */ if (!(*tgts=calloc(sizeof (krb5_creds *), 2))) return ENOMEM; subretval=krb5_copy_creds(context, referral_tgts[0], &((*tgts)[0])); if(subretval) return subretval; (*tgts)[1]=NULL; DUMP_PRINC("gc_from_kdc: returning referral TGT for ccache", (*tgts)[0]->server); #if 0 } #endif } } /* Free referral TGTs list. */ for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) { if(referral_tgts[i]) { krb5_free_creds(context, referral_tgts[i]); } } DPRINTF(("gc_from_kdc finishing with %s\n", retval ? error_message(retval) : "no error")); return retval; }
static krb5_error_code krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, krb5_creds *in_cred, krb5_creds **out_cred, krb5_creds ***tgts, int kdcopt) { krb5_creds **ret_tgts = NULL; int ntgts = 0; krb5_creds tgt, tgtq, *tgtr = NULL; krb5_error_code retval; krb5_principal int_server = NULL; /* Intermediate server for request */ krb5_principal *tgs_list = NULL; krb5_principal *top_server = NULL; krb5_principal *next_server = NULL; unsigned int nservers = 0; krb5_boolean old_use_conf_ktypes = context->use_conf_ktypes; /* in case we never get a TGT, zero the return */ *tgts = NULL; memset((char *)&tgtq, 0, sizeof(tgtq)); memset((char *)&tgt, 0, sizeof(tgt)); /* * we know that the desired credentials aren't in the cache yet. * * To get them, we first need a tgt for the realm of the server. * first, we see if we have such a TGT in cache. if not, then * we ask the kdc to give us one. if that doesn't work, then * we try to get a tgt for a realm that is closest to the target. * once we have that, then we ask that realm if it can give us * tgt for the target. if not, we do the process over with this * new tgt. */ /* * (the ticket may be issued by some other intermediate * realm's KDC; so we use KRB5_TC_MATCH_SRV_NAMEONLY) */ if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) goto cleanup; /* get target tgt from cache */ if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server), krb5_princ_realm(context, in_cred->client), &int_server))) { goto cleanup; } if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) { goto cleanup; } /* set endtime to now so krb5_cc_retrieve_cred won't return an expired tik */ if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } context->use_conf_ktypes = 1; if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { goto cleanup; } /* * Note that we want to request a TGT from our local KDC, even * if we already have a TGT for some intermediate realm. The * reason is that our local KDC may have a shortcut to the * destination realm, and if it does we want to use the * shortcut because it will provide greater security. - bcn */ /* * didn't find it in the cache so it is time to get a local * tgt and walk the realms tree. */ krb5_free_principal(context, int_server); int_server = NULL; if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->client), krb5_princ_realm(context, in_cred->client), &int_server))) { goto cleanup; } krb5_free_cred_contents(context, &tgtq); memset((char *)&tgtq, 0, sizeof(tgtq)); if ((retval = krb5_copy_principal(context, in_cred->client, &tgtq.client))) goto cleanup; if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { goto cleanup; } /* get a list of realms to consult */ if ((retval = krb5_walk_realm_tree(context, krb5_princ_realm(context,in_cred->client), krb5_princ_realm(context,in_cred->server), &tgs_list, KRB5_REALM_BRANCH_CHAR))) { goto cleanup; } for (nservers = 0; tgs_list[nservers]; nservers++) ; /* allocate storage for TGT pointers. */ if (!(ret_tgts = (krb5_creds **) calloc(nservers+1, sizeof(krb5_creds)))) { retval = ENOMEM; goto cleanup; } *tgts = ret_tgts; /* * step one is to take the current tgt and see if there is a tgt for * krbtgt/realmof(target)@realmof(tgt). if not, try to get one with * the tgt. * * if we don't get a tgt for the target, then try to find a tgt as * close to the target realm as possible. at each step if there isn't * a tgt in the cache we have to try and get one with our latest tgt. * once we have a tgt for a closer realm, we go back to step one. * * once we have a tgt for the target, we go try and get credentials. */ for (top_server = tgs_list; top_server < tgs_list + nservers; top_server = next_server) { /* look in cache for a tgt for the destination */ krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; krb5_free_principal(context, int_server); int_server = NULL; if ((retval = krb5_tgtname(context, krb5_princ_realm(context, in_cred->server), krb5_princ_realm(context, *top_server), &int_server))) { goto cleanup; } if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { if (retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) { goto cleanup; } /* didn't find it in the cache so try and get one */ /* with current tgt. */ if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { retval = KRB5_PROG_ETYPE_NOSUPP; goto cleanup; } krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); tgtq.times = tgt.times; if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; tgtq.is_skey = FALSE; tgtq.ticket_flags = tgt.ticket_flags; retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgtq.ticket_flags), tgt.addresses, &tgtq, &tgtr); if (retval) { /* * couldn't get one so now loop backwards through the realms * list and try and get a tgt for a realm as close to the * target as possible. the kdc should give us a tgt for the * closest one it knows about, but not all kdc's do this yet. */ for (next_server = tgs_list + nservers - 1; next_server > top_server; next_server--) { krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; krb5_free_principal(context, int_server); int_server = NULL; if ((retval = krb5_tgtname(context, krb5_princ_realm(context, *next_server), krb5_princ_realm(context, *top_server), &int_server))) { goto cleanup; } if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) { goto cleanup; } if ((retval = krb5_cc_retrieve_cred(context, ccache, KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES | KRB5_TC_MATCH_TIMES, &tgtq, &tgt)) != 0) { if (retval != KRB5_CC_NOTFOUND) { goto cleanup; } /* not in the cache so try and get one with our current tgt. */ if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { retval = KRB5_PROG_ETYPE_NOSUPP; goto cleanup; } krb5_free_cred_contents(context, &tgtq); memset(&tgtq, 0, sizeof(tgtq)); tgtq.times = tgt.times; if ((retval = krb5_copy_principal(context, tgt.client, &tgtq.client))) goto cleanup; if ((retval = krb5_copy_principal(context, int_server, &tgtq.server))) goto cleanup; tgtq.is_skey = FALSE; tgtq.ticket_flags = tgt.ticket_flags; retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgtq.ticket_flags), tgt.addresses, &tgtq, &tgtr); if (retval) continue; /* save tgt in return array */ if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) { goto cleanup; } krb5_free_creds(context, tgtr); tgtr = NULL; tgt = *ret_tgts[ntgts++]; } /* got one as close as possible, now start all over */ break; } if (next_server == top_server) { goto cleanup; } continue; } /* * Got a tgt. If it is for the target realm we can go try for the * credentials. If it is not for the target realm, then make sure it * is in the realms hierarchy and if so, save it and start the loop * over from there. Note that we only need to compare the instance * names since that is the target realm of the tgt. */ for (next_server = top_server; *next_server; next_server++) { krb5_data *realm_1 = krb5_princ_component(context, next_server[0], 1); krb5_data *realm_2 = krb5_princ_component(context, tgtr->server, 1); if (realm_1 != NULL && realm_2 != NULL && realm_1->length == realm_2->length && !memcmp(realm_1->data, realm_2->data, realm_1->length)) { break; } } if (!next_server) { retval = KRB5_KDCREP_MODIFIED; goto cleanup; } if ((retval = krb5_copy_creds(context, tgtr, &ret_tgts[ntgts]))) { goto cleanup; } krb5_free_creds(context, tgtr); tgtr = NULL; tgt = *ret_tgts[ntgts++]; /* we're done if it is the target */ if (!*next_server++) break; } } } /* got/finally have tgt! try for the creds */ if (!krb5_c_valid_enctype(tgt.keyblock.enctype)) { retval = KRB5_PROG_ETYPE_NOSUPP; goto cleanup; } context->use_conf_ktypes = old_use_conf_ktypes; retval = krb5_get_cred_via_tkt(context, &tgt, FLAGS2OPTS(tgt.ticket_flags) | kdcopt | (in_cred->second_ticket.length ? KDC_OPT_ENC_TKT_IN_SKEY : 0), tgt.addresses, in_cred, out_cred); /* cleanup and return */ cleanup: if (tgtr) krb5_free_creds(context, tgtr); if(tgs_list) krb5_free_realm_tree(context, tgs_list); krb5_free_cred_contents(context, &tgtq); if (int_server) krb5_free_principal(context, int_server); if (ntgts == 0) { *tgts = NULL; if (ret_tgts) free(ret_tgts); krb5_free_cred_contents(context, &tgt); } context->use_conf_ktypes = old_use_conf_ktypes; return(retval); }
int v4_get_creds(krb5_context ctx, pam_handle_t *pamh, struct _pam_krb5_stash *stash, struct _pam_krb5_user_info *userinfo, struct _pam_krb5_options *options, char *password, int *result) { int i; #if defined(HAVE_KRB5_524_CONVERT_CREDS) || \ defined(HAVE_KRB524_CONVERT_CREDS_KDC) krb5_creds *v4_compat_creds, *in_creds; v4_compat_creds = NULL; if (options->v4_use_524) { if (options->debug) { debug("obtaining v4-compatible key"); } /* We need a DES-CBC-CRC v5 credential to convert to a proper v4 * credential. */ i = v5_get_creds_etype(ctx, userinfo, options, &stash->v5creds, ENCTYPE_DES_CBC_CRC, &v4_compat_creds); if (i == 0) { if (options->debug) { debug("obtained des-cbc-crc v5 creds"); } in_creds = v4_compat_creds; } else { if (options->debug) { debug("failed to obtain des-cbc-crc v5 creds: " "%d (%s)", i, v5_error_message(i)); } in_creds = NULL; if (v5_creds_check_initialized(ctx, &stash->v5creds) == 0) { krb5_copy_creds(ctx, &stash->v5creds, &in_creds); } } #ifdef HAVE_KRB5_524_CONVERT_CREDS if (options->debug) { debug("converting v5 creds to v4 creds (etype = %d)", in_creds ? v5_creds_get_etype(in_creds) : 0); } if ((in_creds != NULL) && (v5_creds_check_initialized(ctx, in_creds) == 0)) { i = krb5_524_convert_creds(ctx, in_creds, &stash->v4creds); if (i == 0) { if (options->debug) { debug("conversion succeeded"); } stash->v4present = 1; if (result) { *result = i; } krb5_free_creds(ctx, in_creds); return PAM_SUCCESS; } else { if (options->debug) { debug("conversion failed: %d (%s)", i, v5_error_message(i)); } } } #else #ifdef HAVE_KRB524_CONVERT_CREDS_KDC if (options->debug) { debug("converting v5 creds to v4 creds (etype = %d)", in_creds ? v5_creds_get_etype(in_creds) : 0); } if ((in_creds != NULL) && (v5_creds_check_initialized(ctx, in_creds) == 0)) { i = krb524_convert_creds_kdc(ctx, in_creds, &stash->v4creds); if (i == 0) { if (options->debug) { debug("conversion succeeded"); } stash->v4present = 1; if (result) { *result = i; } krb5_free_creds(ctx, in_creds); return PAM_SUCCESS; } else { if (options->debug) { debug("conversion failed: %d (%s)", i, v5_error_message(i)); } } } #endif #endif if ((in_creds != NULL) && (v5_creds_check_initialized(ctx, in_creds) == 0)) { krb5_free_creds(ctx, in_creds); } } #endif if ((password != NULL) && (options->v4_use_as_req)) { if (options->debug) { debug("attempting to obtain initial v4 creds"); } i = _pam_krb5_v4_init(ctx, stash, userinfo, options, KRB5_TGS_NAME, NULL, password, result); if (i == PAM_SUCCESS) { if (options->debug) { debug("initial v4 creds obtained"); } stash->v4present = 1; return PAM_SUCCESS; } if (options->debug) { debug("could not obtain initial v4 creds: %d (%s)", i, v5_error_message(i)); } } return PAM_AUTH_ERR; }