/*1* Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan2* (Royal Institute of Technology, Stockholm, Sweden).3* All rights reserved.4*5* Portions Copyright (c) 2009 Apple Inc. All rights reserved.6*7* Redistribution and use in source and binary forms, with or without8* modification, are permitted provided that the following conditions9* are met:10*11* 1. Redistributions of source code must retain the above copyright12* notice, this list of conditions and the following disclaimer.13*14* 2. Redistributions in binary form must reproduce the above copyright15* notice, this list of conditions and the following disclaimer in the16* documentation and/or other materials provided with the distribution.17*18* 3. Neither the name of the Institute nor the names of its contributors19* may be used to endorse or promote products derived from this software20* without specific prior written permission.21*22* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND23* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE24* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE25* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE26* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL27* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS28* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)29* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT30* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY31* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF32* SUCH DAMAGE.33*/3435#include "krb5_locl.h"3637/**38* @page krb5_ccache_intro The credential cache functions39* @section section_krb5_ccache Kerberos credential caches40*41* krb5_ccache structure holds a Kerberos credential cache.42*43* Heimdal support the follow types of credential caches:44*45* - SCC46* Store the credential in a database47* - FILE48* Store the credential in memory49* - MEMORY50* Store the credential in memory51* - API52* A credential cache server based solution for Mac OS X53* - KCM54* A credential cache server based solution for all platforms55*56* @subsection Example57*58* This is a minimalistic version of klist:59@code60#include <krb5.h>6162int63main (int argc, char **argv)64{65krb5_context context;66krb5_cc_cursor cursor;67krb5_error_code ret;68krb5_ccache id;69krb5_creds creds;7071if (krb5_init_context (&context) != 0)72errx(1, "krb5_context");7374ret = krb5_cc_default (context, &id);75if (ret)76krb5_err(context, 1, ret, "krb5_cc_default");7778ret = krb5_cc_start_seq_get(context, id, &cursor);79if (ret)80krb5_err(context, 1, ret, "krb5_cc_start_seq_get");8182while((ret = krb5_cc_next_cred(context, id, &cursor, &creds)) == 0){83char *principal;8485krb5_unparse_name(context, creds.server, &principal);86printf("principal: %s\\n", principal);87free(principal);88krb5_free_cred_contents (context, &creds);89}90ret = krb5_cc_end_seq_get(context, id, &cursor);91if (ret)92krb5_err(context, 1, ret, "krb5_cc_end_seq_get");9394krb5_cc_close(context, id);9596krb5_free_context(context);97return 0;98}99* @endcode100*/101102/**103* Add a new ccache type with operations `ops', overwriting any104* existing one if `override'.105*106* @param context a Keberos context107* @param ops type of plugin symbol108* @param override flag to select if the registration is to overide109* an existing ops with the same name.110*111* @return Return an error code or 0, see krb5_get_error_message().112*113* @ingroup krb5_ccache114*/115116KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL117krb5_cc_register(krb5_context context,118const krb5_cc_ops *ops,119krb5_boolean override)120{121int i;122123for(i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {124if(strcmp(context->cc_ops[i]->prefix, ops->prefix) == 0) {125if(!override) {126krb5_set_error_message(context,127KRB5_CC_TYPE_EXISTS,128N_("cache type %s already exists", "type"),129ops->prefix);130return KRB5_CC_TYPE_EXISTS;131}132break;133}134}135if(i == context->num_cc_ops) {136const krb5_cc_ops **o = realloc(rk_UNCONST(context->cc_ops),137(context->num_cc_ops + 1) *138sizeof(context->cc_ops[0]));139if(o == NULL) {140krb5_set_error_message(context, KRB5_CC_NOMEM,141N_("malloc: out of memory", ""));142return KRB5_CC_NOMEM;143}144context->cc_ops = o;145context->cc_ops[context->num_cc_ops] = NULL;146context->num_cc_ops++;147}148context->cc_ops[i] = ops;149return 0;150}151152/*153* Allocate the memory for a `id' and the that function table to154* `ops'. Returns 0 or and error code.155*/156157krb5_error_code158_krb5_cc_allocate(krb5_context context,159const krb5_cc_ops *ops,160krb5_ccache *id)161{162krb5_ccache p;163164p = malloc (sizeof(*p));165if(p == NULL) {166krb5_set_error_message(context, KRB5_CC_NOMEM,167N_("malloc: out of memory", ""));168return KRB5_CC_NOMEM;169}170p->ops = ops;171*id = p;172173return 0;174}175176/*177* Allocate memory for a new ccache in `id' with operations `ops'178* and name `residual'. Return 0 or an error code.179*/180181static krb5_error_code182allocate_ccache (krb5_context context,183const krb5_cc_ops *ops,184const char *residual,185krb5_ccache *id)186{187krb5_error_code ret;188#ifdef KRB5_USE_PATH_TOKENS189char * exp_residual = NULL;190191ret = _krb5_expand_path_tokens(context, residual, &exp_residual);192if (ret)193return ret;194195residual = exp_residual;196#endif197198ret = _krb5_cc_allocate(context, ops, id);199if (ret) {200#ifdef KRB5_USE_PATH_TOKENS201if (exp_residual)202free(exp_residual);203#endif204return ret;205}206207ret = (*id)->ops->resolve(context, id, residual);208if(ret) {209free(*id);210*id = NULL;211}212213#ifdef KRB5_USE_PATH_TOKENS214if (exp_residual)215free(exp_residual);216#endif217218return ret;219}220221static int222is_possible_path_name(const char * name)223{224const char * colon;225226if ((colon = strchr(name, ':')) == NULL)227return TRUE;228229#ifdef _WIN32230/* <drive letter>:\path\to\cache ? */231232if (colon == name + 1 &&233strchr(colon + 1, ':') == NULL)234return TRUE;235#endif236237return FALSE;238}239240/**241* Find and allocate a ccache in `id' from the specification in `residual'.242* If the ccache name doesn't contain any colon, interpret it as a file name.243*244* @param context a Keberos context.245* @param name string name of a credential cache.246* @param id return pointer to a found credential cache.247*248* @return Return 0 or an error code. In case of an error, id is set249* to NULL, see krb5_get_error_message().250*251* @ingroup krb5_ccache252*/253254255KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL256krb5_cc_resolve(krb5_context context,257const char *name,258krb5_ccache *id)259{260int i;261262*id = NULL;263264for(i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {265size_t prefix_len = strlen(context->cc_ops[i]->prefix);266267if(strncmp(context->cc_ops[i]->prefix, name, prefix_len) == 0268&& name[prefix_len] == ':') {269return allocate_ccache (context, context->cc_ops[i],270name + prefix_len + 1,271id);272}273}274if (is_possible_path_name(name))275return allocate_ccache (context, &krb5_fcc_ops, name, id);276else {277krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,278N_("unknown ccache type %s", "name"), name);279return KRB5_CC_UNKNOWN_TYPE;280}281}282283/**284* Generates a new unique ccache of `type` in `id'. If `type' is NULL,285* the library chooses the default credential cache type. The supplied286* `hint' (that can be NULL) is a string that the credential cache287* type can use to base the name of the credential on, this is to make288* it easier for the user to differentiate the credentials.289*290* @return Return an error code or 0, see krb5_get_error_message().291*292* @ingroup krb5_ccache293*/294295KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL296krb5_cc_new_unique(krb5_context context, const char *type,297const char *hint, krb5_ccache *id)298{299const krb5_cc_ops *ops;300krb5_error_code ret;301302ops = krb5_cc_get_prefix_ops(context, type);303if (ops == NULL) {304krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,305"Credential cache type %s is unknown", type);306return KRB5_CC_UNKNOWN_TYPE;307}308309ret = _krb5_cc_allocate(context, ops, id);310if (ret)311return ret;312ret = (*id)->ops->gen_new(context, id);313if (ret) {314free(*id);315*id = NULL;316}317return ret;318}319320/**321* Return the name of the ccache `id'322*323* @ingroup krb5_ccache324*/325326327KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL328krb5_cc_get_name(krb5_context context,329krb5_ccache id)330{331return id->ops->get_name(context, id);332}333334/**335* Return the type of the ccache `id'.336*337* @ingroup krb5_ccache338*/339340341KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL342krb5_cc_get_type(krb5_context context,343krb5_ccache id)344{345return id->ops->prefix;346}347348/**349* Return the complete resolvable name the cache350351* @param context a Keberos context352* @param id return pointer to a found credential cache353* @param str the returned name of a credential cache, free with krb5_xfree()354*355* @return Returns 0 or an error (and then *str is set to NULL).356*357* @ingroup krb5_ccache358*/359360361KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL362krb5_cc_get_full_name(krb5_context context,363krb5_ccache id,364char **str)365{366const char *type, *name;367368*str = NULL;369370type = krb5_cc_get_type(context, id);371if (type == NULL) {372krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,373"cache have no name of type");374return KRB5_CC_UNKNOWN_TYPE;375}376377name = krb5_cc_get_name(context, id);378if (name == NULL) {379krb5_set_error_message(context, KRB5_CC_BADNAME,380"cache of type %s have no name", type);381return KRB5_CC_BADNAME;382}383384if (asprintf(str, "%s:%s", type, name) == -1) {385krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));386*str = NULL;387return ENOMEM;388}389return 0;390}391392/**393* Return krb5_cc_ops of a the ccache `id'.394*395* @ingroup krb5_ccache396*/397398399KRB5_LIB_FUNCTION const krb5_cc_ops * KRB5_LIB_CALL400krb5_cc_get_ops(krb5_context context, krb5_ccache id)401{402return id->ops;403}404405/*406* Expand variables in `str' into `res'407*/408409krb5_error_code410_krb5_expand_default_cc_name(krb5_context context, const char *str, char **res)411{412return _krb5_expand_path_tokens(context, str, res);413}414415/*416* Return non-zero if envirnoment that will determine default krb5cc417* name has changed.418*/419420static int421environment_changed(krb5_context context)422{423const char *e;424425/* if the cc name was set, don't change it */426if (context->default_cc_name_set)427return 0;428429/* XXX performance: always ask KCM/API if default name has changed */430if (context->default_cc_name &&431(strncmp(context->default_cc_name, "KCM:", 4) == 0 ||432strncmp(context->default_cc_name, "API:", 4) == 0))433return 1;434435if(issuid())436return 0;437438e = getenv("KRB5CCNAME");439if (e == NULL) {440if (context->default_cc_name_env) {441free(context->default_cc_name_env);442context->default_cc_name_env = NULL;443return 1;444}445} else {446if (context->default_cc_name_env == NULL)447return 1;448if (strcmp(e, context->default_cc_name_env) != 0)449return 1;450}451return 0;452}453454/**455* Switch the default default credential cache for a specific456* credcache type (and name for some implementations).457*458* @return Return an error code or 0, see krb5_get_error_message().459*460* @ingroup krb5_ccache461*/462463KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL464krb5_cc_switch(krb5_context context, krb5_ccache id)465{466#ifdef _WIN32467_krb5_set_default_cc_name_to_registry(context, id);468#endif469470if (id->ops->set_default == NULL)471return 0;472473return (*id->ops->set_default)(context, id);474}475476/**477* Return true if the default credential cache support switch478*479* @ingroup krb5_ccache480*/481482KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL483krb5_cc_support_switch(krb5_context context, const char *type)484{485const krb5_cc_ops *ops;486487ops = krb5_cc_get_prefix_ops(context, type);488if (ops && ops->set_default)489return 1;490return FALSE;491}492493/**494* Set the default cc name for `context' to `name'.495*496* @ingroup krb5_ccache497*/498499KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL500krb5_cc_set_default_name(krb5_context context, const char *name)501{502krb5_error_code ret = 0;503char *p = NULL, *exp_p = NULL;504505if (name == NULL) {506const char *e = NULL;507508if(!issuid()) {509e = getenv("KRB5CCNAME");510if (e) {511p = strdup(e);512if (context->default_cc_name_env)513free(context->default_cc_name_env);514context->default_cc_name_env = strdup(e);515}516}517518#ifdef _WIN32519if (e == NULL) {520e = p = _krb5_get_default_cc_name_from_registry(context);521}522#endif523if (e == NULL) {524e = krb5_config_get_string(context, NULL, "libdefaults",525"default_cc_name", NULL);526if (e) {527ret = _krb5_expand_default_cc_name(context, e, &p);528if (ret)529return ret;530}531if (e == NULL) {532const krb5_cc_ops *ops = KRB5_DEFAULT_CCTYPE;533e = krb5_config_get_string(context, NULL, "libdefaults",534"default_cc_type", NULL);535if (e) {536ops = krb5_cc_get_prefix_ops(context, e);537if (ops == NULL) {538krb5_set_error_message(context,539KRB5_CC_UNKNOWN_TYPE,540"Credential cache type %s "541"is unknown", e);542return KRB5_CC_UNKNOWN_TYPE;543}544}545ret = (*ops->get_default_name)(context, &p);546if (ret)547return ret;548}549}550context->default_cc_name_set = 0;551} else {552p = strdup(name);553context->default_cc_name_set = 1;554}555556if (p == NULL) {557krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));558return ENOMEM;559}560561ret = _krb5_expand_path_tokens(context, p, &exp_p);562free(p);563if (ret)564return ret;565566if (context->default_cc_name)567free(context->default_cc_name);568569context->default_cc_name = exp_p;570571return 0;572}573574/**575* Return a pointer to a context static string containing the default576* ccache name.577*578* @return String to the default credential cache name.579*580* @ingroup krb5_ccache581*/582583584KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL585krb5_cc_default_name(krb5_context context)586{587if (context->default_cc_name == NULL || environment_changed(context))588krb5_cc_set_default_name(context, NULL);589590return context->default_cc_name;591}592593/**594* Open the default ccache in `id'.595*596* @return Return an error code or 0, see krb5_get_error_message().597*598* @ingroup krb5_ccache599*/600601602KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL603krb5_cc_default(krb5_context context,604krb5_ccache *id)605{606const char *p = krb5_cc_default_name(context);607608if (p == NULL) {609krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));610return ENOMEM;611}612return krb5_cc_resolve(context, p, id);613}614615/**616* Create a new ccache in `id' for `primary_principal'.617*618* @return Return an error code or 0, see krb5_get_error_message().619*620* @ingroup krb5_ccache621*/622623624KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL625krb5_cc_initialize(krb5_context context,626krb5_ccache id,627krb5_principal primary_principal)628{629return (*id->ops->init)(context, id, primary_principal);630}631632633/**634* Remove the ccache `id'.635*636* @return Return an error code or 0, see krb5_get_error_message().637*638* @ingroup krb5_ccache639*/640641642KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL643krb5_cc_destroy(krb5_context context,644krb5_ccache id)645{646krb5_error_code ret;647648ret = (*id->ops->destroy)(context, id);649krb5_cc_close (context, id);650return ret;651}652653/**654* Stop using the ccache `id' and free the related resources.655*656* @return Return an error code or 0, see krb5_get_error_message().657*658* @ingroup krb5_ccache659*/660661662KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL663krb5_cc_close(krb5_context context,664krb5_ccache id)665{666krb5_error_code ret;667ret = (*id->ops->close)(context, id);668free(id);669return ret;670}671672/**673* Store `creds' in the ccache `id'.674*675* @return Return an error code or 0, see krb5_get_error_message().676*677* @ingroup krb5_ccache678*/679680681KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL682krb5_cc_store_cred(krb5_context context,683krb5_ccache id,684krb5_creds *creds)685{686return (*id->ops->store)(context, id, creds);687}688689/**690* Retrieve the credential identified by `mcreds' (and `whichfields')691* from `id' in `creds'. 'creds' must be free by the caller using692* krb5_free_cred_contents.693*694* @param context A Kerberos 5 context695* @param id a Kerberos 5 credential cache696* @param whichfields what fields to use for matching credentials, same697* flags as whichfields in krb5_compare_creds()698* @param mcreds template credential to use for comparing699* @param creds returned credential, free with krb5_free_cred_contents()700*701* @return Return an error code or 0, see krb5_get_error_message().702*703* @ingroup krb5_ccache704*/705706707KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL708krb5_cc_retrieve_cred(krb5_context context,709krb5_ccache id,710krb5_flags whichfields,711const krb5_creds *mcreds,712krb5_creds *creds)713{714krb5_error_code ret;715krb5_cc_cursor cursor;716717if (id->ops->retrieve != NULL) {718return (*id->ops->retrieve)(context, id, whichfields,719mcreds, creds);720}721722ret = krb5_cc_start_seq_get(context, id, &cursor);723if (ret)724return ret;725while((ret = krb5_cc_next_cred(context, id, &cursor, creds)) == 0){726if(krb5_compare_creds(context, whichfields, mcreds, creds)){727ret = 0;728break;729}730krb5_free_cred_contents (context, creds);731}732krb5_cc_end_seq_get(context, id, &cursor);733return ret;734}735736/**737* Return the principal of `id' in `principal'.738*739* @return Return an error code or 0, see krb5_get_error_message().740*741* @ingroup krb5_ccache742*/743744745KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL746krb5_cc_get_principal(krb5_context context,747krb5_ccache id,748krb5_principal *principal)749{750return (*id->ops->get_princ)(context, id, principal);751}752753/**754* Start iterating over `id', `cursor' is initialized to the755* beginning. Caller must free the cursor with krb5_cc_end_seq_get().756*757* @return Return an error code or 0, see krb5_get_error_message().758*759* @ingroup krb5_ccache760*/761762763KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL764krb5_cc_start_seq_get (krb5_context context,765const krb5_ccache id,766krb5_cc_cursor *cursor)767{768return (*id->ops->get_first)(context, id, cursor);769}770771/**772* Retrieve the next cred pointed to by (`id', `cursor') in `creds'773* and advance `cursor'.774*775* @return Return an error code or 0, see krb5_get_error_message().776*777* @ingroup krb5_ccache778*/779780781KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL782krb5_cc_next_cred (krb5_context context,783const krb5_ccache id,784krb5_cc_cursor *cursor,785krb5_creds *creds)786{787return (*id->ops->get_next)(context, id, cursor, creds);788}789790/**791* Destroy the cursor `cursor'.792*793* @ingroup krb5_ccache794*/795796797KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL798krb5_cc_end_seq_get (krb5_context context,799const krb5_ccache id,800krb5_cc_cursor *cursor)801{802return (*id->ops->end_get)(context, id, cursor);803}804805/**806* Remove the credential identified by `cred', `which' from `id'.807*808* @ingroup krb5_ccache809*/810811812KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL813krb5_cc_remove_cred(krb5_context context,814krb5_ccache id,815krb5_flags which,816krb5_creds *cred)817{818if(id->ops->remove_cred == NULL) {819krb5_set_error_message(context,820EACCES,821"ccache %s does not support remove_cred",822id->ops->prefix);823return EACCES; /* XXX */824}825return (*id->ops->remove_cred)(context, id, which, cred);826}827828/**829* Set the flags of `id' to `flags'.830*831* @ingroup krb5_ccache832*/833834835KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL836krb5_cc_set_flags(krb5_context context,837krb5_ccache id,838krb5_flags flags)839{840return (*id->ops->set_flags)(context, id, flags);841}842843/**844* Get the flags of `id', store them in `flags'.845*846* @ingroup krb5_ccache847*/848849KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL850krb5_cc_get_flags(krb5_context context,851krb5_ccache id,852krb5_flags *flags)853{854*flags = 0;855return 0;856}857858/**859* Copy the contents of `from' to `to' if the given match function860* return true.861*862* @param context A Kerberos 5 context.863* @param from the cache to copy data from.864* @param to the cache to copy data to.865* @param match a match function that should return TRUE if cred argument should be copied, if NULL, all credentials are copied.866* @param matchctx context passed to match function.867* @param matched set to true if there was a credential that matched, may be NULL.868*869* @return Return an error code or 0, see krb5_get_error_message().870*871* @ingroup krb5_ccache872*/873874KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL875krb5_cc_copy_match_f(krb5_context context,876const krb5_ccache from,877krb5_ccache to,878krb5_boolean (*match)(krb5_context, void *, const krb5_creds *),879void *matchctx,880unsigned int *matched)881{882krb5_error_code ret;883krb5_cc_cursor cursor;884krb5_creds cred;885krb5_principal princ;886887if (matched)888*matched = 0;889890ret = krb5_cc_get_principal(context, from, &princ);891if (ret)892return ret;893ret = krb5_cc_initialize(context, to, princ);894if (ret) {895krb5_free_principal(context, princ);896return ret;897}898ret = krb5_cc_start_seq_get(context, from, &cursor);899if (ret) {900krb5_free_principal(context, princ);901return ret;902}903904while ((ret = krb5_cc_next_cred(context, from, &cursor, &cred)) == 0) {905if (match == NULL || (*match)(context, matchctx, &cred) == 0) {906if (matched)907(*matched)++;908ret = krb5_cc_store_cred(context, to, &cred);909if (ret)910break;911}912krb5_free_cred_contents(context, &cred);913}914krb5_cc_end_seq_get(context, from, &cursor);915krb5_free_principal(context, princ);916if (ret == KRB5_CC_END)917ret = 0;918return ret;919}920921/**922* Just like krb5_cc_copy_match_f(), but copy everything.923*924* @ingroup @krb5_ccache925*/926927KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL928krb5_cc_copy_cache(krb5_context context,929const krb5_ccache from,930krb5_ccache to)931{932return krb5_cc_copy_match_f(context, from, to, NULL, NULL, NULL);933}934935/**936* Return the version of `id'.937*938* @ingroup krb5_ccache939*/940941942KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL943krb5_cc_get_version(krb5_context context,944const krb5_ccache id)945{946if(id->ops->get_version)947return (*id->ops->get_version)(context, id);948else949return 0;950}951952/**953* Clear `mcreds' so it can be used with krb5_cc_retrieve_cred954*955* @ingroup krb5_ccache956*/957958959KRB5_LIB_FUNCTION void KRB5_LIB_CALL960krb5_cc_clear_mcred(krb5_creds *mcred)961{962memset(mcred, 0, sizeof(*mcred));963}964965/**966* Get the cc ops that is registered in `context' to handle the967* prefix. prefix can be a complete credential cache name or a968* prefix, the function will only use part up to the first colon (:)969* if there is one. If prefix the argument is NULL, the default ccache970* implemtation is returned.971*972* @return Returns NULL if ops not found.973*974* @ingroup krb5_ccache975*/976977978KRB5_LIB_FUNCTION const krb5_cc_ops * KRB5_LIB_CALL979krb5_cc_get_prefix_ops(krb5_context context, const char *prefix)980{981char *p, *p1;982int i;983984if (prefix == NULL)985return KRB5_DEFAULT_CCTYPE;986if (prefix[0] == '/')987return &krb5_fcc_ops;988989p = strdup(prefix);990if (p == NULL) {991krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));992return NULL;993}994p1 = strchr(p, ':');995if (p1)996*p1 = '\0';997998for(i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {999if(strcmp(context->cc_ops[i]->prefix, p) == 0) {1000free(p);1001return context->cc_ops[i];1002}1003}1004free(p);1005return NULL;1006}10071008struct krb5_cc_cache_cursor_data {1009const krb5_cc_ops *ops;1010krb5_cc_cursor cursor;1011};10121013/**1014* Start iterating over all caches of specified type. See also1015* krb5_cccol_cursor_new().10161017* @param context A Kerberos 5 context1018* @param type optional type to iterate over, if NULL, the default cache is used.1019* @param cursor cursor should be freed with krb5_cc_cache_end_seq_get().1020*1021* @return Return an error code or 0, see krb5_get_error_message().1022*1023* @ingroup krb5_ccache1024*/102510261027KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1028krb5_cc_cache_get_first (krb5_context context,1029const char *type,1030krb5_cc_cache_cursor *cursor)1031{1032const krb5_cc_ops *ops;1033krb5_error_code ret;10341035if (type == NULL)1036type = krb5_cc_default_name(context);10371038ops = krb5_cc_get_prefix_ops(context, type);1039if (ops == NULL) {1040krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,1041"Unknown type \"%s\" when iterating "1042"trying to iterate the credential caches", type);1043return KRB5_CC_UNKNOWN_TYPE;1044}10451046if (ops->get_cache_first == NULL) {1047krb5_set_error_message(context, KRB5_CC_NOSUPP,1048N_("Credential cache type %s doesn't support "1049"iterations over caches", "type"),1050ops->prefix);1051return KRB5_CC_NOSUPP;1052}10531054*cursor = calloc(1, sizeof(**cursor));1055if (*cursor == NULL) {1056krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));1057return ENOMEM;1058}10591060(*cursor)->ops = ops;10611062ret = ops->get_cache_first(context, &(*cursor)->cursor);1063if (ret) {1064free(*cursor);1065*cursor = NULL;1066}1067return ret;1068}10691070/**1071* Retrieve the next cache pointed to by (`cursor') in `id'1072* and advance `cursor'.1073*1074* @param context A Kerberos 5 context1075* @param cursor the iterator cursor, returned by krb5_cc_cache_get_first()1076* @param id next ccache1077*1078* @return Return 0 or an error code. Returns KRB5_CC_END when the end1079* of caches is reached, see krb5_get_error_message().1080*1081* @ingroup krb5_ccache1082*/108310841085KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1086krb5_cc_cache_next (krb5_context context,1087krb5_cc_cache_cursor cursor,1088krb5_ccache *id)1089{1090return cursor->ops->get_cache_next(context, cursor->cursor, id);1091}10921093/**1094* Destroy the cursor `cursor'.1095*1096* @return Return an error code or 0, see krb5_get_error_message().1097*1098* @ingroup krb5_ccache1099*/110011011102KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1103krb5_cc_cache_end_seq_get (krb5_context context,1104krb5_cc_cache_cursor cursor)1105{1106krb5_error_code ret;1107ret = cursor->ops->end_cache_get(context, cursor->cursor);1108cursor->ops = NULL;1109free(cursor);1110return ret;1111}11121113/**1114* Search for a matching credential cache that have the1115* `principal' as the default principal. On success, `id' needs to be1116* freed with krb5_cc_close() or krb5_cc_destroy().1117*1118* @param context A Kerberos 5 context1119* @param client The principal to search for1120* @param id the returned credential cache1121*1122* @return On failure, error code is returned and `id' is set to NULL.1123*1124* @ingroup krb5_ccache1125*/112611271128KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1129krb5_cc_cache_match (krb5_context context,1130krb5_principal client,1131krb5_ccache *id)1132{1133krb5_cccol_cursor cursor;1134krb5_error_code ret;1135krb5_ccache cache = NULL;11361137*id = NULL;11381139ret = krb5_cccol_cursor_new (context, &cursor);1140if (ret)1141return ret;11421143while (krb5_cccol_cursor_next (context, cursor, &cache) == 0 && cache != NULL) {1144krb5_principal principal;11451146ret = krb5_cc_get_principal(context, cache, &principal);1147if (ret == 0) {1148krb5_boolean match;11491150match = krb5_principal_compare(context, principal, client);1151krb5_free_principal(context, principal);1152if (match)1153break;1154}11551156krb5_cc_close(context, cache);1157cache = NULL;1158}11591160krb5_cccol_cursor_free(context, &cursor);11611162if (cache == NULL) {1163char *str;11641165krb5_unparse_name(context, client, &str);11661167krb5_set_error_message(context, KRB5_CC_NOTFOUND,1168N_("Principal %s not found in any "1169"credential cache", ""),1170str ? str : "<out of memory>");1171if (str)1172free(str);1173return KRB5_CC_NOTFOUND;1174}1175*id = cache;11761177return 0;1178}11791180/**1181* Move the content from one credential cache to another. The1182* operation is an atomic switch.1183*1184* @param context a Keberos context1185* @param from the credential cache to move the content from1186* @param to the credential cache to move the content to11871188* @return On sucess, from is freed. On failure, error code is1189* returned and from and to are both still allocated, see krb5_get_error_message().1190*1191* @ingroup krb5_ccache1192*/11931194KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1195krb5_cc_move(krb5_context context, krb5_ccache from, krb5_ccache to)1196{1197krb5_error_code ret;11981199if (strcmp(from->ops->prefix, to->ops->prefix) != 0) {1200krb5_set_error_message(context, KRB5_CC_NOSUPP,1201N_("Moving credentials between diffrent "1202"types not yet supported", ""));1203return KRB5_CC_NOSUPP;1204}12051206ret = (*to->ops->move)(context, from, to);1207if (ret == 0) {1208memset(from, 0, sizeof(*from));1209free(from);1210}1211return ret;1212}12131214#define KRB5_CONF_NAME "krb5_ccache_conf_data"1215#define KRB5_REALM_NAME "X-CACHECONF:"12161217static krb5_error_code1218build_conf_principals(krb5_context context, krb5_ccache id,1219krb5_const_principal principal,1220const char *name, krb5_creds *cred)1221{1222krb5_principal client;1223krb5_error_code ret;1224char *pname = NULL;12251226memset(cred, 0, sizeof(*cred));12271228ret = krb5_cc_get_principal(context, id, &client);1229if (ret)1230return ret;12311232if (principal) {1233ret = krb5_unparse_name(context, principal, &pname);1234if (ret)1235return ret;1236}12371238ret = krb5_make_principal(context, &cred->server,1239KRB5_REALM_NAME,1240KRB5_CONF_NAME, name, pname, NULL);1241free(pname);1242if (ret) {1243krb5_free_principal(context, client);1244return ret;1245}1246ret = krb5_copy_principal(context, client, &cred->client);1247krb5_free_principal(context, client);1248return ret;1249}12501251/**1252* Return TRUE (non zero) if the principal is a configuration1253* principal (generated part of krb5_cc_set_config()). Returns FALSE1254* (zero) if not a configuration principal.1255*1256* @param context a Keberos context1257* @param principal principal to check if it a configuration principal1258*1259* @ingroup krb5_ccache1260*/12611262KRB5_LIB_FUNCTION krb5_boolean KRB5_LIB_CALL1263krb5_is_config_principal(krb5_context context,1264krb5_const_principal principal)1265{1266if (strcmp(principal->realm, KRB5_REALM_NAME) != 0)1267return FALSE;12681269if (principal->name.name_string.len == 0 ||1270strcmp(principal->name.name_string.val[0], KRB5_CONF_NAME) != 0)1271return FALSE;12721273return TRUE;1274}12751276/**1277* Store some configuration for the credential cache in the cache.1278* Existing configuration under the same name is over-written.1279*1280* @param context a Keberos context1281* @param id the credential cache to store the data for1282* @param principal configuration for a specific principal, if1283* NULL, global for the whole cache.1284* @param name name under which the configuraion is stored.1285* @param data data to store, if NULL, configure is removed.1286*1287* @ingroup krb5_ccache1288*/12891290KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1291krb5_cc_set_config(krb5_context context, krb5_ccache id,1292krb5_const_principal principal,1293const char *name, krb5_data *data)1294{1295krb5_error_code ret;1296krb5_creds cred;12971298ret = build_conf_principals(context, id, principal, name, &cred);1299if (ret)1300goto out;13011302/* Remove old configuration */1303ret = krb5_cc_remove_cred(context, id, 0, &cred);1304if (ret && ret != KRB5_CC_NOTFOUND)1305goto out;13061307if (data) {1308/* not that anyone care when this expire */1309cred.times.authtime = time(NULL);1310cred.times.endtime = cred.times.authtime + 3600 * 24 * 30;13111312ret = krb5_data_copy(&cred.ticket, data->data, data->length);1313if (ret)1314goto out;13151316ret = krb5_cc_store_cred(context, id, &cred);1317}13181319out:1320krb5_free_cred_contents (context, &cred);1321return ret;1322}13231324/**1325* Get some configuration for the credential cache in the cache.1326*1327* @param context a Keberos context1328* @param id the credential cache to store the data for1329* @param principal configuration for a specific principal, if1330* NULL, global for the whole cache.1331* @param name name under which the configuraion is stored.1332* @param data data to fetched, free with krb5_data_free()1333*1334* @ingroup krb5_ccache1335*/133613371338KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1339krb5_cc_get_config(krb5_context context, krb5_ccache id,1340krb5_const_principal principal,1341const char *name, krb5_data *data)1342{1343krb5_creds mcred, cred;1344krb5_error_code ret;13451346memset(&cred, 0, sizeof(cred));1347krb5_data_zero(data);13481349ret = build_conf_principals(context, id, principal, name, &mcred);1350if (ret)1351goto out;13521353ret = krb5_cc_retrieve_cred(context, id, 0, &mcred, &cred);1354if (ret)1355goto out;13561357ret = krb5_data_copy(data, cred.ticket.data, cred.ticket.length);13581359out:1360krb5_free_cred_contents (context, &cred);1361krb5_free_cred_contents (context, &mcred);1362return ret;1363}13641365/*1366*1367*/13681369struct krb5_cccol_cursor_data {1370int idx;1371krb5_cc_cache_cursor cursor;1372};13731374/**1375* Get a new cache interation cursor that will interate over all1376* credentials caches independent of type.1377*1378* @param context a Keberos context1379* @param cursor passed into krb5_cccol_cursor_next() and free with krb5_cccol_cursor_free().1380*1381* @return Returns 0 or and error code, see krb5_get_error_message().1382*1383* @ingroup krb5_ccache1384*/13851386KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1387krb5_cccol_cursor_new(krb5_context context, krb5_cccol_cursor *cursor)1388{1389*cursor = calloc(1, sizeof(**cursor));1390if (*cursor == NULL) {1391krb5_set_error_message(context, ENOMEM, N_("malloc: out of memory", ""));1392return ENOMEM;1393}1394(*cursor)->idx = 0;1395(*cursor)->cursor = NULL;13961397return 0;1398}13991400/**1401* Get next credential cache from the iteration.1402*1403* @param context A Kerberos 5 context1404* @param cursor the iteration cursor1405* @param cache the returned cursor, pointer is set to NULL on failure1406* and a cache on success. The returned cache needs to be freed1407* with krb5_cc_close() or destroyed with krb5_cc_destroy().1408* MIT Kerberos behavies slightly diffrent and sets cache to NULL1409* when all caches are iterated over and return 0.1410*1411* @return Return 0 or and error, KRB5_CC_END is returned at the end1412* of iteration. See krb5_get_error_message().1413*1414* @ingroup krb5_ccache1415*/141614171418KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1419krb5_cccol_cursor_next(krb5_context context, krb5_cccol_cursor cursor,1420krb5_ccache *cache)1421{1422krb5_error_code ret;14231424*cache = NULL;14251426while (cursor->idx < context->num_cc_ops) {14271428if (cursor->cursor == NULL) {1429ret = krb5_cc_cache_get_first (context,1430context->cc_ops[cursor->idx]->prefix,1431&cursor->cursor);1432if (ret) {1433cursor->idx++;1434continue;1435}1436}1437ret = krb5_cc_cache_next(context, cursor->cursor, cache);1438if (ret == 0)1439break;14401441krb5_cc_cache_end_seq_get(context, cursor->cursor);1442cursor->cursor = NULL;1443if (ret != KRB5_CC_END)1444break;14451446cursor->idx++;1447}1448if (cursor->idx >= context->num_cc_ops) {1449krb5_set_error_message(context, KRB5_CC_END,1450N_("Reached end of credential caches", ""));1451return KRB5_CC_END;1452}14531454return 0;1455}14561457/**1458* End an iteration and free all resources, can be done before end is reached.1459*1460* @param context A Kerberos 5 context1461* @param cursor the iteration cursor to be freed.1462*1463* @return Return 0 or and error, KRB5_CC_END is returned at the end1464* of iteration. See krb5_get_error_message().1465*1466* @ingroup krb5_ccache1467*/14681469KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1470krb5_cccol_cursor_free(krb5_context context, krb5_cccol_cursor *cursor)1471{1472krb5_cccol_cursor c = *cursor;14731474*cursor = NULL;1475if (c) {1476if (c->cursor)1477krb5_cc_cache_end_seq_get(context, c->cursor);1478free(c);1479}1480return 0;1481}14821483/**1484* Return the last time the credential cache was modified.1485*1486* @param context A Kerberos 5 context1487* @param id The credential cache to probe1488* @param mtime the last modification time, set to 0 on error.14891490* @return Return 0 or and error. See krb5_get_error_message().1491*1492* @ingroup krb5_ccache1493*/149414951496KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1497krb5_cc_last_change_time(krb5_context context,1498krb5_ccache id,1499krb5_timestamp *mtime)1500{1501*mtime = 0;1502return (*id->ops->lastchange)(context, id, mtime);1503}15041505/**1506* Return the last modfication time for a cache collection. The query1507* can be limited to a specific cache type. If the function return 01508* and mtime is 0, there was no credentials in the caches.1509*1510* @param context A Kerberos 5 context1511* @param type The credential cache to probe, if NULL, all type are traversed.1512* @param mtime the last modification time, set to 0 on error.15131514* @return Return 0 or and error. See krb5_get_error_message().1515*1516* @ingroup krb5_ccache1517*/15181519KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1520krb5_cccol_last_change_time(krb5_context context,1521const char *type,1522krb5_timestamp *mtime)1523{1524krb5_cccol_cursor cursor;1525krb5_error_code ret;1526krb5_ccache id;1527krb5_timestamp t = 0;15281529*mtime = 0;15301531ret = krb5_cccol_cursor_new (context, &cursor);1532if (ret)1533return ret;15341535while (krb5_cccol_cursor_next(context, cursor, &id) == 0 && id != NULL) {15361537if (type && strcmp(krb5_cc_get_type(context, id), type) != 0)1538continue;15391540ret = krb5_cc_last_change_time(context, id, &t);1541krb5_cc_close(context, id);1542if (ret)1543continue;1544if (t > *mtime)1545*mtime = t;1546}15471548krb5_cccol_cursor_free(context, &cursor);15491550return 0;1551}1552/**1553* Return a friendly name on credential cache. Free the result with krb5_xfree().1554*1555* @return Return an error code or 0, see krb5_get_error_message().1556*1557* @ingroup krb5_ccache1558*/15591560KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1561krb5_cc_get_friendly_name(krb5_context context,1562krb5_ccache id,1563char **name)1564{1565krb5_error_code ret;1566krb5_data data;15671568ret = krb5_cc_get_config(context, id, NULL, "FriendlyName", &data);1569if (ret) {1570krb5_principal principal;1571ret = krb5_cc_get_principal(context, id, &principal);1572if (ret)1573return ret;1574ret = krb5_unparse_name(context, principal, name);1575krb5_free_principal(context, principal);1576} else {1577ret = asprintf(name, "%.*s", (int)data.length, (char *)data.data);1578krb5_data_free(&data);1579if (ret <= 0) {1580ret = ENOMEM;1581krb5_set_error_message(context, ret, N_("malloc: out of memory", ""));1582} else1583ret = 0;1584}15851586return ret;1587}15881589/**1590* Set the friendly name on credential cache.1591*1592* @return Return an error code or 0, see krb5_get_error_message().1593*1594* @ingroup krb5_ccache1595*/15961597KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1598krb5_cc_set_friendly_name(krb5_context context,1599krb5_ccache id,1600const char *name)1601{1602krb5_data data;16031604data.data = rk_UNCONST(name);1605data.length = strlen(name);16061607return krb5_cc_set_config(context, id, NULL, "FriendlyName", &data);1608}16091610/**1611* Get the lifetime of the initial ticket in the cache1612*1613* Get the lifetime of the initial ticket in the cache, if the initial1614* ticket was not found, the error code KRB5_CC_END is returned.1615*1616* @param context A Kerberos 5 context.1617* @param id a credential cache1618* @param t the relative lifetime of the initial ticket1619*1620* @return Return an error code or 0, see krb5_get_error_message().1621*1622* @ingroup krb5_ccache1623*/16241625KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1626krb5_cc_get_lifetime(krb5_context context, krb5_ccache id, time_t *t)1627{1628krb5_cc_cursor cursor;1629krb5_error_code ret;1630krb5_creds cred;1631time_t now;16321633*t = 0;1634now = time(NULL);16351636ret = krb5_cc_start_seq_get(context, id, &cursor);1637if (ret)1638return ret;16391640while ((ret = krb5_cc_next_cred(context, id, &cursor, &cred)) == 0) {1641if (cred.flags.b.initial) {1642if (now < cred.times.endtime)1643*t = cred.times.endtime - now;1644krb5_free_cred_contents(context, &cred);1645break;1646}1647krb5_free_cred_contents(context, &cred);1648}16491650krb5_cc_end_seq_get(context, id, &cursor);16511652return ret;1653}16541655/**1656* Set the time offset betwen the client and the KDC1657*1658* If the backend doesn't support KDC offset, use the context global setting.1659*1660* @param context A Kerberos 5 context.1661* @param id a credential cache1662* @param offset the offset in seconds1663*1664* @return Return an error code or 0, see krb5_get_error_message().1665*1666* @ingroup krb5_ccache1667*/16681669KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1670krb5_cc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat offset)1671{1672if (id->ops->set_kdc_offset == NULL) {1673context->kdc_sec_offset = offset;1674context->kdc_usec_offset = 0;1675return 0;1676}1677return (*id->ops->set_kdc_offset)(context, id, offset);1678}16791680/**1681* Get the time offset betwen the client and the KDC1682*1683* If the backend doesn't support KDC offset, use the context global setting.1684*1685* @param context A Kerberos 5 context.1686* @param id a credential cache1687* @param offset the offset in seconds1688*1689* @return Return an error code or 0, see krb5_get_error_message().1690*1691* @ingroup krb5_ccache1692*/16931694KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL1695krb5_cc_get_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat *offset)1696{1697if (id->ops->get_kdc_offset == NULL) {1698*offset = context->kdc_sec_offset;1699return 0;1700}1701return (*id->ops->get_kdc_offset)(context, id, offset);1702}170317041705#ifdef _WIN3217061707#define REGPATH_MIT_KRB5 "SOFTWARE\\MIT\\Kerberos5"1708char *1709_krb5_get_default_cc_name_from_registry(krb5_context context)1710{1711HKEY hk_k5 = 0;1712LONG code;1713char * ccname = NULL;17141715code = RegOpenKeyEx(HKEY_CURRENT_USER,1716REGPATH_MIT_KRB5,17170, KEY_READ, &hk_k5);17181719if (code != ERROR_SUCCESS)1720return NULL;17211722ccname = _krb5_parse_reg_value_as_string(context, hk_k5, "ccname",1723REG_NONE, 0);17241725RegCloseKey(hk_k5);17261727return ccname;1728}17291730int1731_krb5_set_default_cc_name_to_registry(krb5_context context, krb5_ccache id)1732{1733HKEY hk_k5 = 0;1734LONG code;1735int ret = -1;1736char * ccname = NULL;17371738code = RegOpenKeyEx(HKEY_CURRENT_USER,1739REGPATH_MIT_KRB5,17400, KEY_READ|KEY_WRITE, &hk_k5);17411742if (code != ERROR_SUCCESS)1743return -1;17441745ret = asprintf(&ccname, "%s:%s", krb5_cc_get_type(context, id), krb5_cc_get_name(context, id));1746if (ret < 0)1747goto cleanup;17481749ret = _krb5_store_string_to_reg_value(context, hk_k5, "ccname",1750REG_SZ, ccname, -1, 0);17511752cleanup:17531754if (ccname)1755free(ccname);17561757RegCloseKey(hk_k5);17581759return ret;1760}17611762#endif176317641765