/* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * lib/krb5/krb/get_creds.c * * Copyright 1990 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may * require a specific license from the United States Government. * It is the responsibility of any person or organization contemplating * export to obtain such a license before exporting. * * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and * distribute this software and its documentation for any purpose and * without fee is hereby granted, provided that the above copyright * notice appear in all copies and that both that copyright notice and * this permission notice appear in supporting documentation, and that * the name of M.I.T. not be used in advertising or publicity pertaining * to distribution of the software without specific, written prior * permission. Furthermore if you modify this software you must label * your software as modified software and not distribute it in such a * fashion that it might be confused with the original M.I.T. software. * M.I.T. makes no representations about the suitability of * this software for any purpose. It is provided "as is" without express * or implied warranty. * * * krb5_get_credentials() */ /* Attempts to use the credentials cache or TGS exchange to get an additional ticket for the client identified by in_creds->client, the server identified by in_creds->server, with options options, expiration date specified in in_creds->times.endtime (0 means as long as possible), session key type specified in in_creds->keyblock.enctype (if non-zero) Any returned ticket and intermediate ticket-granting tickets are stored in ccache. returns errors from encryption routines, system errors */ #include "k5-int.h" /*ARGSUSED*/ static krb5_error_code krb5_get_credentials_core(krb5_context context, krb5_flags options, krb5_creds *in_creds, krb5_creds *mcreds, krb5_flags *fields) { /* Solaris Kerberos */ krb5_error_code ret = 0; if (!in_creds || !in_creds->server || !in_creds->client) return EINVAL; memset((char *)mcreds, 0, sizeof(krb5_creds)); mcreds->magic = KV5M_CREDS; /* * Solaris Kerberos: * Set endtime appropriately to make sure we do not rope in * expired creds. If endtime is set to 0 (which it almost always * is, courtesy memset/calloc) the krb5_cc_retrieve_cred() call in * krb5_get_credentials() with KRB5_TC_MATCH_TIMES will * succeed and return the expired cred. * * Hence, endtime below is set to "now" if in_creds->times.endtime * is 0, so that krb5_cc_retrieve_cred fails and we get fresh creds, * if necessary. But, if in_creds has a non-zero endtime, we honor it. */ if (in_creds->times.endtime != 0) mcreds->times.endtime = in_creds->times.endtime; else if ((ret = krb5_timeofday(context, &mcreds->times.endtime)) != 0) return (ret); ret = krb5_copy_keyblock_contents(context, &in_creds->keyblock, &mcreds->keyblock); if (ret) return (ret); mcreds->authdata = in_creds->authdata; mcreds->server = in_creds->server; mcreds->client = in_creds->client; *fields = KRB5_TC_MATCH_TIMES /*XXX |KRB5_TC_MATCH_SKEY_TYPE */ | KRB5_TC_MATCH_AUTHDATA | KRB5_TC_SUPPORTED_KTYPES; if (mcreds->keyblock.enctype) { krb5_enctype *ktypes; int i; *fields |= KRB5_TC_MATCH_KTYPE; ret = krb5_get_tgs_ktypes (context, mcreds->server, &ktypes); for (i = 0; ktypes[i]; i++) if (ktypes[i] == mcreds->keyblock.enctype) break; if (ktypes[i] == 0) ret = KRB5_CC_NOT_KTYPE; free (ktypes); if (ret) { krb5_free_keyblock_contents(context, &mcreds->keyblock); return ret; } } if (options & KRB5_GC_USER_USER) { /* also match on identical 2nd tkt and tkt encrypted in a session key */ *fields |= KRB5_TC_MATCH_2ND_TKT|KRB5_TC_MATCH_IS_SKEY; mcreds->is_skey = TRUE; mcreds->second_ticket = in_creds->second_ticket; if (!in_creds->second_ticket.length) { krb5_free_keyblock_contents(context, &mcreds->keyblock); return KRB5_NO_2ND_TKT; } } return 0; } krb5_error_code KRB5_CALLCONV krb5_get_credentials(krb5_context context, krb5_flags options, krb5_ccache ccache, krb5_creds *in_creds, krb5_creds **out_creds) { krb5_error_code retval; krb5_creds mcreds; krb5_creds *ncreds; krb5_creds **tgts; krb5_flags fields; int not_ktype; retval = krb5_get_credentials_core(context, options, in_creds, &mcreds, &fields); if (retval) return retval; if ((ncreds = (krb5_creds *)malloc(sizeof(krb5_creds))) == NULL) { krb5_free_keyblock_contents(context, &mcreds.keyblock); return ENOMEM; } memset((char *)ncreds, 0, sizeof(krb5_creds)); ncreds->magic = KV5M_CREDS; /* The caller is now responsible for cleaning up in_creds */ /* Solaris Kerberos */ if ((retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds, ncreds)) !=0) { krb5_xfree(ncreds); ncreds = in_creds; } else { *out_creds = ncreds; } if ((retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE) || options & KRB5_GC_CACHED) { krb5_free_keyblock_contents(context, &mcreds.keyblock); return retval; } if (retval == KRB5_CC_NOT_KTYPE) not_ktype = 1; else not_ktype = 0; retval = krb5_get_cred_from_kdc(context, ccache, ncreds, out_creds, &tgts); if (tgts) { register int i = 0; krb5_error_code rv2; while (tgts[i]) { /* Solaris Kerberos */ if ((rv2 = krb5_cc_store_cred(context, ccache, tgts[i])) != 0) { retval = rv2; break; } i++; } krb5_free_tgt_creds(context, tgts); } /* * Translate KRB5_CC_NOTFOUND if we previously got * KRB5_CC_NOT_KTYPE from krb5_cc_retrieve_cred(), in order to * handle the case where there is no TGT in the ccache and the * input enctype didn't match. This handling is necessary because * some callers, such as GSSAPI, iterate through enctypes and * KRB5_CC_NOTFOUND passed through from the * krb5_get_cred_from_kdc() is semantically incorrect, since the * actual failure was the non-existence of a ticket of the correct * enctype rather than the missing TGT. */ if ((retval == KRB5_CC_NOTFOUND || retval == KRB5_CC_NOT_KTYPE) && not_ktype) retval = KRB5_CC_NOT_KTYPE; if (!retval) { /* the purpose of the krb5_get_credentials call is to * obtain a set of credentials for the caller. the * krb5_cc_store_cred() call is to optimize performance * for future calls. Ignore any errors, since the credentials * are still valid even if we fail to store them in the cache. */ /* Solaris Kerberos */ retval = krb5_cc_store_cred(context, ccache, *out_creds); } krb5_free_keyblock_contents(context, &mcreds.keyblock); return retval; } #define INT_GC_VALIDATE 1 #define INT_GC_RENEW 2 /*ARGSUSED*/ static krb5_error_code krb5_get_credentials_val_renew_core(krb5_context context, krb5_flags options, krb5_ccache ccache, krb5_creds *in_creds, krb5_creds **out_creds, int which) { krb5_error_code retval; krb5_principal tmp; krb5_creds **tgts = 0; switch(which) { case INT_GC_VALIDATE: retval = krb5_get_cred_from_kdc_validate(context, ccache, in_creds, out_creds, &tgts); break; case INT_GC_RENEW: retval = krb5_get_cred_from_kdc_renew(context, ccache, in_creds, out_creds, &tgts); break; default: /* Should never happen */ retval = 255; break; } if (retval) return retval; if (tgts) krb5_free_tgt_creds(context, tgts); retval = krb5_cc_get_principal(context, ccache, &tmp); if (retval) return retval; retval = krb5_cc_initialize(context, ccache, tmp); /* Solaris Kerberos */ if (retval) { krb5_free_principal(context, tmp); return retval; } retval = krb5_cc_store_cred(context, ccache, *out_creds); krb5_free_principal(context, tmp); return retval; } krb5_error_code KRB5_CALLCONV krb5_get_credentials_validate(krb5_context context, krb5_flags options, krb5_ccache ccache, krb5_creds *in_creds, krb5_creds **out_creds) { return(krb5_get_credentials_val_renew_core(context, options, ccache, in_creds, out_creds, INT_GC_VALIDATE)); } krb5_error_code KRB5_CALLCONV krb5_get_credentials_renew(krb5_context context, krb5_flags options, krb5_ccache ccache, krb5_creds *in_creds, krb5_creds **out_creds) { return(krb5_get_credentials_val_renew_core(context, options, ccache, in_creds, out_creds, INT_GC_RENEW)); } static krb5_error_code krb5_validate_or_renew_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service, int validate) { krb5_error_code ret; krb5_creds in_creds; /* only client and server need to be filled in */ krb5_creds *out_creds = 0; /* for check before dereferencing below */ krb5_creds **tgts; memset((char *)&in_creds, 0, sizeof(krb5_creds)); in_creds.server = NULL; tgts = NULL; in_creds.client = client; 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, &in_creds.server))) goto cleanup; /* stuff the client realm into the server principal. realloc if necessary */ if (in_creds.server->realm.length < in_creds.client->realm.length) if ((in_creds.server->realm.data = (char *) realloc(in_creds.server->realm.data, in_creds.client->realm.length)) == NULL) { ret = ENOMEM; goto cleanup; } in_creds.server->realm.length = in_creds.client->realm.length; memcpy(in_creds.server->realm.data, in_creds.client->realm.data, in_creds.client->realm.length); } else { if ((ret = krb5_build_principal_ext(context, &in_creds.server, in_creds.client->realm.length, in_creds.client->realm.data, KRB5_TGS_NAME_SIZE, KRB5_TGS_NAME, in_creds.client->realm.length, in_creds.client->realm.data, 0))) goto cleanup; } if (validate) ret = krb5_get_cred_from_kdc_validate(context, ccache, &in_creds, &out_creds, &tgts); else ret = krb5_get_cred_from_kdc_renew(context, ccache, &in_creds, &out_creds, &tgts); /* ick. copy the struct contents, free the container */ if (out_creds) { *creds = *out_creds; krb5_xfree(out_creds); } cleanup: if (in_creds.server) krb5_free_principal(context, in_creds.server); if (tgts) krb5_free_tgt_creds(context, tgts); return(ret); } krb5_error_code KRB5_CALLCONV krb5_get_validated_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service) { return(krb5_validate_or_renew_creds(context, creds, client, ccache, in_tkt_service, 1)); } krb5_error_code KRB5_CALLCONV krb5_get_renewed_creds(krb5_context context, krb5_creds *creds, krb5_principal client, krb5_ccache ccache, char *in_tkt_service) { return(krb5_validate_or_renew_creds(context, creds, client, ccache, in_tkt_service, 0)); }