2N/A/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2N/A/*
2N/A * lib/krb5/krb/get_creds.c
2N/A *
2N/A * Copyright 1990, 2008 by the Massachusetts Institute of Technology.
2N/A * All Rights Reserved.
2N/A *
2N/A * Export of this software from the United States of America may
2N/A * require a specific license from the United States Government.
2N/A * It is the responsibility of any person or organization contemplating
2N/A * export to obtain such a license before exporting.
2N/A *
2N/A * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
2N/A * distribute this software and its documentation for any purpose and
2N/A * without fee is hereby granted, provided that the above copyright
2N/A * notice appear in all copies and that both that copyright notice and
2N/A * this permission notice appear in supporting documentation, and that
2N/A * the name of M.I.T. not be used in advertising or publicity pertaining
2N/A * to distribution of the software without specific, written prior
2N/A * permission. Furthermore if you modify this software you must label
2N/A * your software as modified software and not distribute it in such a
2N/A * fashion that it might be confused with the original M.I.T. software.
2N/A * M.I.T. makes no representations about the suitability of
2N/A * this software for any purpose. It is provided "as is" without express
2N/A * or implied warranty.
2N/A *
2N/A *
2N/A * krb5_get_credentials()
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2002, 2011, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A
2N/A/*
2N/A Attempts to use the credentials cache or TGS exchange to get an additional
2N/A ticket for the
2N/A client identified by in_creds->client, the server identified by
2N/A in_creds->server, with options options, expiration date specified in
2N/A in_creds->times.endtime (0 means as long as possible), session key type
2N/A specified in in_creds->keyblock.enctype (if non-zero)
2N/A
2N/A Any returned ticket and intermediate ticket-granting tickets are
2N/A stored in ccache.
2N/A
2N/A returns errors from encryption routines, system errors
2N/A*/
2N/A
2N/A#include "k5-int.h"
2N/A#include "int-proto.h"
2N/A
2N/A/*
2N/A * Set *mcreds and *fields to a matching credential and field set for
2N/A * use with krb5_cc_retrieve_cred, based on a set of input credentials
2N/A * and options. The fields of *mcreds will be aliased to the fields
2N/A * of in_creds, so the contents of *mcreds should not be freed.
2N/A */
2N/Akrb5_error_code
2N/Akrb5int_construct_matching_creds(krb5_context context, krb5_flags options,
2N/A krb5_creds *in_creds, krb5_creds *mcreds,
2N/A krb5_flags *fields)
2N/A{
2N/A krb5_error_code ret; /* Solaris Kerberos */
2N/A
2N/A if (!in_creds || !in_creds->server || !in_creds->client)
2N/A return EINVAL;
2N/A
2N/A memset(mcreds, 0, sizeof(krb5_creds));
2N/A mcreds->magic = KV5M_CREDS;
2N/A if (in_creds->times.endtime != 0) {
2N/A mcreds->times.endtime = in_creds->times.endtime;
2N/A } else {
2N/A /* Solaris Kerberos: change retval to ret */
2N/A ret = krb5_timeofday(context, &mcreds->times.endtime);
2N/A if (ret != 0) return ret;
2N/A }
2N/A /* Solaris Kerberos: our keyblock needs different handling */
2N/A ret = krb5_copy_keyblock_contents(context, &in_creds->keyblock,
2N/A &mcreds->keyblock);
2N/A if (ret != 0)
2N/A return (ret);
2N/A mcreds->authdata = in_creds->authdata;
2N/A mcreds->server = in_creds->server;
2N/A mcreds->client = in_creds->client;
2N/A
2N/A *fields = KRB5_TC_MATCH_TIMES /*XXX |KRB5_TC_MATCH_SKEY_TYPE */
2N/A | KRB5_TC_MATCH_AUTHDATA
2N/A | KRB5_TC_SUPPORTED_KTYPES;
2N/A if (mcreds->keyblock.enctype) {
2N/A krb5_enctype *ktypes;
2N/A int i;
2N/A
2N/A *fields |= KRB5_TC_MATCH_KTYPE;
2N/A ret = krb5_get_tgs_ktypes(context, mcreds->server, &ktypes);
2N/A for (i = 0; ktypes[i]; i++)
2N/A if (ktypes[i] == mcreds->keyblock.enctype)
2N/A break;
2N/A if (ktypes[i] == 0)
2N/A ret = KRB5_CC_NOT_KTYPE;
2N/A free (ktypes);
2N/A if (ret) {
2N/A /* Solaris Kerberos: our keyblock needs different handling */
2N/A krb5_free_keyblock_contents(context, &mcreds->keyblock);
2N/A return ret;
2N/A }
2N/A }
2N/A if (options & (KRB5_GC_USER_USER | KRB5_GC_CONSTRAINED_DELEGATION)) {
2N/A /* also match on identical 2nd tkt and tkt encrypted in a
2N/A session key */
2N/A *fields |= KRB5_TC_MATCH_2ND_TKT;
2N/A if (options & KRB5_GC_USER_USER) {
2N/A *fields |= KRB5_TC_MATCH_IS_SKEY;
2N/A mcreds->is_skey = TRUE;
2N/A }
2N/A mcreds->second_ticket = in_creds->second_ticket;
2N/A if (!in_creds->second_ticket.length) {
2N/A /* Solaris Kerberos: our keyblock needs different handling */
2N/A krb5_free_keyblock_contents(context, &mcreds->keyblock);
2N/A return KRB5_NO_2ND_TKT;
2N/A }
2N/A }
2N/A
2N/A return 0;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_credentials(krb5_context context, krb5_flags options,
2N/A krb5_ccache ccache, krb5_creds *in_creds,
2N/A krb5_creds **out_creds)
2N/A{
2N/A krb5_error_code retval;
2N/A /* Solaris Kerberos set tgts = NULL */
2N/A krb5_creds mcreds, *ncreds, **tgts = NULL, **tgts_iter;
2N/A krb5_flags fields;
2N/A krb5_boolean not_ktype = FALSE;
2N/A int kdcopt = 0;
2N/A
2N/A *out_creds = NULL;
2N/A
2N/A /*
2N/A * See if we already have the ticket cached. To do this usefully
2N/A * for constrained delegation, we would need to look inside
2N/A * second_ticket, which we can't do.
2N/A */
2N/A if ((options & KRB5_GC_CONSTRAINED_DELEGATION) == 0) {
2N/A retval = krb5int_construct_matching_creds(context, options, in_creds,
2N/A &mcreds, &fields);
2N/A
2N/A if (retval)
2N/A return retval;
2N/A
2N/A ncreds = malloc(sizeof(krb5_creds));
2N/A if (!ncreds) {
2N/A /* Solaris Kerberos: our keyblock needs different handling */
2N/A krb5_free_keyblock_contents(context, &mcreds.keyblock);
2N/A return ENOMEM;
2N/A }
2N/A
2N/A memset(ncreds, 0, sizeof(krb5_creds));
2N/A ncreds->magic = KV5M_CREDS;
2N/A
2N/A retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds,
2N/A ncreds);
2N/A /*
2N/A * Solaris Kerberos: our keyblock needs different handling, at this
2N/A * point mcreds.keyblock isn't needed.
2N/A */
2N/A krb5_free_keyblock_contents(context, &mcreds.keyblock);
2N/A
2N/A if (retval == 0) {
2N/A *out_creds = ncreds;
2N/A return 0;
2N/A }
2N/A free(ncreds);
2N/A ncreds = NULL;
2N/A if ((retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE)
2N/A || options & KRB5_GC_CACHED)
2N/A return retval;
2N/A not_ktype = (retval == KRB5_CC_NOT_KTYPE);
2N/A } else if (options & KRB5_GC_CACHED)
2N/A return KRB5_CC_NOTFOUND;
2N/A
2N/A if (options & KRB5_GC_CANONICALIZE)
2N/A kdcopt |= KDC_OPT_CANONICALIZE;
2N/A if (options & KRB5_GC_FORWARDABLE)
2N/A kdcopt |= KDC_OPT_FORWARDABLE;
2N/A if (options & KRB5_GC_NO_TRANSIT_CHECK)
2N/A kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
2N/A if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
2N/A if (options & KRB5_GC_USER_USER)
2N/A return EINVAL;
2N/A kdcopt |= KDC_OPT_FORWARDABLE | KDC_OPT_CNAME_IN_ADDL_TKT;
2N/A }
2N/A
2N/A retval = krb5_get_cred_from_kdc_opt(context, ccache, in_creds,
2N/A &ncreds, &tgts, kdcopt);
2N/A if (tgts) {
2N/A /* Attempt to cache intermediate ticket-granting tickets. */
2N/A for (tgts_iter = tgts; *tgts_iter; tgts_iter++)
2N/A (void) krb5_cc_store_cred(context, ccache, *tgts_iter);
2N/A krb5_free_tgt_creds(context, tgts);
2N/A }
2N/A
2N/A /*
2N/A * Translate KRB5_CC_NOTFOUND if we previously got
2N/A * KRB5_CC_NOT_KTYPE from krb5_cc_retrieve_cred(), in order to
2N/A * handle the case where there is no TGT in the ccache and the
2N/A * input enctype didn't match. This handling is necessary because
2N/A * some callers, such as GSSAPI, iterate through enctypes and
2N/A * KRB5_CC_NOTFOUND passed through from the
2N/A * krb5_get_cred_from_kdc() is semantically incorrect, since the
2N/A * actual failure was the non-existence of a ticket of the correct
2N/A * enctype rather than the missing TGT.
2N/A */
2N/A if ((retval == KRB5_CC_NOTFOUND || retval == KRB5_CC_NOT_KTYPE)
2N/A && not_ktype)
2N/A return KRB5_CC_NOT_KTYPE;
2N/A else if (retval)
2N/A return retval;
2N/A
2N/A if ((options & KRB5_GC_CONSTRAINED_DELEGATION)
2N/A && (ncreds->ticket_flags & TKT_FLG_FORWARDABLE) == 0) {
2N/A /* This ticket won't work for constrained delegation. */
2N/A krb5_free_creds(context, ncreds);
2N/A return KRB5_TKT_NOT_FORWARDABLE;
2N/A }
2N/A
2N/A /* Attempt to cache the returned ticket. */
2N/A if (!(options & KRB5_GC_NO_STORE))
2N/A (void) krb5_cc_store_cred(context, ccache, ncreds);
2N/A
2N/A *out_creds = ncreds;
2N/A return 0;
2N/A}
2N/A
2N/A#define INT_GC_VALIDATE 1
2N/A#define INT_GC_RENEW 2
2N/A
2N/Astatic krb5_error_code
2N/Aget_credentials_val_renew_core(krb5_context context, krb5_flags options,
2N/A krb5_ccache ccache, krb5_creds *in_creds,
2N/A krb5_creds **out_creds, int which)
2N/A{
2N/A krb5_error_code retval;
2N/A krb5_principal tmp;
2N/A krb5_creds **tgts = 0;
2N/A
2N/A switch(which) {
2N/A case INT_GC_VALIDATE:
2N/A retval = krb5_get_cred_from_kdc_validate(context, ccache,
2N/A in_creds, out_creds, &tgts);
2N/A break;
2N/A case INT_GC_RENEW:
2N/A retval = krb5_get_cred_from_kdc_renew(context, ccache,
2N/A in_creds, out_creds, &tgts);
2N/A break;
2N/A default:
2N/A /* Should never happen */
2N/A retval = 255;
2N/A break;
2N/A }
2N/A /*
2N/A * Callers to krb5_get_cred_blah... must free up tgts even in
2N/A * error cases.
2N/A */
2N/A if (tgts) krb5_free_tgt_creds(context, tgts);
2N/A if (retval) return retval;
2N/A
2N/A retval = krb5_cc_get_principal(context, ccache, &tmp);
2N/A if (retval) return retval;
2N/A
2N/A retval = krb5_cc_initialize(context, ccache, tmp);
2N/A /* Solaris Kerberos */
2N/A if (retval) {
2N/A krb5_free_principal(context, tmp);
2N/A return retval;
2N/A }
2N/A
2N/A retval = krb5_cc_store_cred(context, ccache, *out_creds);
2N/A return retval;
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_credentials_validate(krb5_context context, krb5_flags options,
2N/A krb5_ccache ccache, krb5_creds *in_creds,
2N/A krb5_creds **out_creds)
2N/A{
2N/A return(get_credentials_val_renew_core(context, options, ccache,
2N/A in_creds, out_creds,
2N/A INT_GC_VALIDATE));
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_credentials_renew(krb5_context context, krb5_flags options,
2N/A krb5_ccache ccache, krb5_creds *in_creds,
2N/A krb5_creds **out_creds)
2N/A{
2N/A
2N/A return(get_credentials_val_renew_core(context, options, ccache,
2N/A in_creds, out_creds,
2N/A INT_GC_RENEW));
2N/A}
2N/A
2N/Astatic krb5_error_code
2N/Avalidate_or_renew_creds(krb5_context context, krb5_creds *creds,
2N/A krb5_principal client, krb5_ccache ccache,
2N/A char *in_tkt_service, int validate)
2N/A{
2N/A krb5_error_code ret;
2N/A krb5_creds in_creds; /* only client and server need to be filled in */
2N/A krb5_creds *out_creds = 0; /* for check before dereferencing below */
2N/A krb5_creds **tgts;
2N/A
2N/A memset(&in_creds, 0, sizeof(krb5_creds));
2N/A
2N/A in_creds.server = NULL;
2N/A tgts = NULL;
2N/A
2N/A in_creds.client = client;
2N/A
2N/A if (in_tkt_service) {
2N/A /* this is ugly, because so are the data structures involved. I'm
2N/A in the library, so I'm going to manipulate the data structures
2N/A directly, otherwise, it will be worse. */
2N/A
2N/A if ((ret = krb5_parse_name(context, in_tkt_service, &in_creds.server)))
2N/A goto cleanup;
2N/A
2N/A /* stuff the client realm into the server principal.
2N/A realloc if necessary */
2N/A if (in_creds.server->realm.length < in_creds.client->realm.length)
2N/A if ((in_creds.server->realm.data =
2N/A (char *) realloc(in_creds.server->realm.data,
2N/A in_creds.client->realm.length)) == NULL) {
2N/A ret = ENOMEM;
2N/A goto cleanup;
2N/A }
2N/A
2N/A in_creds.server->realm.length = in_creds.client->realm.length;
2N/A memcpy(in_creds.server->realm.data, in_creds.client->realm.data,
2N/A in_creds.client->realm.length);
2N/A } else {
2N/A if ((ret = krb5_build_principal_ext(context, &in_creds.server,
2N/A in_creds.client->realm.length,
2N/A in_creds.client->realm.data,
2N/A KRB5_TGS_NAME_SIZE,
2N/A KRB5_TGS_NAME,
2N/A in_creds.client->realm.length,
2N/A in_creds.client->realm.data,
2N/A 0)))
2N/A goto cleanup;
2N/A }
2N/A
2N/A if (validate)
2N/A ret = krb5_get_cred_from_kdc_validate(context, ccache,
2N/A &in_creds, &out_creds, &tgts);
2N/A else
2N/A ret = krb5_get_cred_from_kdc_renew(context, ccache,
2N/A &in_creds, &out_creds, &tgts);
2N/A
2N/A /* ick. copy the struct contents, free the container */
2N/A if (out_creds) {
2N/A *creds = *out_creds;
2N/A free(out_creds);
2N/A }
2N/A
2N/Acleanup:
2N/A
2N/A if (in_creds.server)
2N/A krb5_free_principal(context, in_creds.server);
2N/A if (tgts)
2N/A krb5_free_tgt_creds(context, tgts);
2N/A
2N/A return(ret);
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_validated_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service)
2N/A{
2N/A return(validate_or_renew_creds(context, creds, client, ccache,
2N/A in_tkt_service, 1));
2N/A}
2N/A
2N/Akrb5_error_code KRB5_CALLCONV
2N/Akrb5_get_renewed_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service)
2N/A{
2N/A return(validate_or_renew_creds(context, creds, client, ccache,
2N/A in_tkt_service, 0));
2N/A}