krb5: Improve cccol sub naming; add gss_store_cred_into2()

- Formalize the TYPE:collection_name:subsidiary_name naming scheme for
   ccaches in ccache collections
    - KEYRING: ccaches are weird because they have one more optional field: the
      "anchor", so rather than just assume a naming convention everywhere, we
      add new functions as well
 - Add krb5_cc_{resolve,default}_sub() that allows one to specify a
   "subsidiary" ccache name in a collection separately from the
   collection name
 - Add krb5_cc_{resolve,default}_for() which take a principal name,
   unparse it, and use it as the subsidiary ccache name (with colons
   replaced)
 - Make kinit use the new interfaces
 - Add missing DIR ccache iteration functionality
 - Revamps test_cc
 - Add krb5_cc_get_collection() and krb5_cc_get_subsidiary()
 - Bump the ccops SPI version number
 - Add gss_store_cred_into2()
 - Make MEMORY:anonymous not linked into the global MEMORY ccache
   collection, and uses this for delegated cred handles

TBD:

 - Split this up into a krb5 change and gss mech_krb5 change?
 - Add krb5_cc_init_and_store() utility, per Greg's suggestion?
This commit is contained in:
Nicolas Williams
2020-01-22 19:18:14 -06:00
parent a7359d6898
commit 7bf4d76e75
33 changed files with 1749 additions and 715 deletions

View File

@@ -44,15 +44,27 @@ RCSID("$Id$");
#define KCMCACHE(X) ((kcm_ccache)(X)->data.data) #define KCMCACHE(X) ((kcm_ccache)(X)->data.data)
#define CACHENAME(X) (KCMCACHE(X)->name) #define CACHENAME(X) (KCMCACHE(X)->name)
static const char * static krb5_error_code
kcmss_get_name(krb5_context context, kcmss_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id,
const char **name,
const char **col,
const char **sub)
{ {
return CACHENAME(id); if (name)
*name = CACHENAME(id);
if (col)
*col = NULL;
if (name)
*sub = CACHENAME(id);
return 0;
} }
static krb5_error_code static krb5_error_code
kcmss_resolve(krb5_context context, krb5_ccache *id, const char *res) kcmss_resolve(krb5_context context,
krb5_ccache *id,
const char *res,
const char *sub)
{ {
return KRB5_FCC_INTERNAL; return KRB5_FCC_INTERNAL;
} }

View File

@@ -1316,65 +1316,12 @@ get_princ_kt(krb5_context context,
free(def_realm); free(def_realm);
} }
static krb5_error_code
get_switched_ccache(krb5_context context,
const char * type,
krb5_principal principal,
krb5_ccache *ccache)
{
krb5_error_code ret;
#ifdef _WIN32
if (strcmp(type, "API") == 0) {
/*
* Windows stores the default ccache name in the
* registry which is shared across multiple logon
* sessions for the same user. The API credential
* cache provides a unique name space per logon
* session. Therefore there is no need to generate
* a unique ccache name. Instead use the principal
* name. This provides a friendlier user experience.
*/
char * unparsed_name;
char * cred_cache;
ret = krb5_unparse_name(context, principal,
&unparsed_name);
if (ret)
krb5_err(context, 1, ret,
N_("unparsing principal name", ""));
ret = asprintf(&cred_cache, "API:%s", unparsed_name);
krb5_free_unparsed_name(context, unparsed_name);
if (ret == -1 || cred_cache == NULL)
krb5_err(context, 1, ret,
N_("building credential cache name", ""));
ret = krb5_cc_resolve(context, cred_cache, ccache);
free(cred_cache);
} else if (strcmp(type, "MSLSA") == 0) {
/*
* The Windows MSLSA cache when it is writeable
* stores tickets for multiple client principals
* in a single credential cache.
*/
ret = krb5_cc_resolve(context, "MSLSA:", ccache);
} else {
ret = krb5_cc_new_unique(context, type, NULL, ccache);
}
#else /* !_WIN32 */
ret = krb5_cc_new_unique(context, type, NULL, ccache);
#endif /* _WIN32 */
return ret;
}
int int
main(int argc, char **argv) main(int argc, char **argv)
{ {
krb5_error_code ret; krb5_error_code ret;
krb5_context context; krb5_context context;
krb5_ccache ccache; krb5_ccache ccache = NULL;
krb5_principal principal = NULL; krb5_principal principal = NULL;
int optidx = 0; int optidx = 0;
krb5_deltat ticket_life = 0; krb5_deltat ticket_life = 0;
@@ -1477,42 +1424,8 @@ main(int argc, char **argv)
if (cred_cache) if (cred_cache)
ret = krb5_cc_resolve(context, cred_cache, &ccache); ret = krb5_cc_resolve(context, cred_cache, &ccache);
else { else
if (argc > 1) { ret = krb5_cc_default_for(context, principal, &ccache);
char s[1024];
ret = krb5_cc_new_unique(context, NULL, NULL, &ccache);
if (ret)
krb5_err(context, 1, ret, "creating cred cache");
snprintf(s, sizeof(s), "%s:%s",
krb5_cc_get_type(context, ccache),
krb5_cc_get_name(context, ccache));
setenv("KRB5CCNAME", s, 1);
unique_ccache = TRUE;
} else {
ret = krb5_cc_cache_match(context, principal, &ccache);
if (ret) {
const char *type;
ret = krb5_cc_default(context, &ccache);
if (ret)
krb5_err(context, 1, ret,
N_("resolving credentials cache", ""));
/*
* Check if the type support switching, and we do,
* then do that instead over overwriting the current
* default credential
*/
type = krb5_cc_get_type(context, ccache);
if (krb5_cc_support_switch(context, type)) {
krb5_cc_close(context, ccache);
ret = get_switched_ccache(context, type, principal,
&ccache);
if (ret == 0)
unique_ccache = TRUE;
}
}
}
}
if (ret) if (ret)
krb5_err(context, 1, ret, N_("resolving credentials cache", "")); krb5_err(context, 1, ret, N_("resolving credentials cache", ""));

View File

@@ -1187,6 +1187,29 @@ gss_store_cred_into(
gss_cred_usage_t * /* cred_usage_stored */ gss_cred_usage_t * /* cred_usage_stored */
); );
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
gss_store_cred_into2(
OM_uint32 * /* minor_status */,
gss_const_cred_id_t /* input_cred_handle */,
gss_cred_usage_t /* input_usage */,
const gss_OID /* desired_mech */,
OM_uint32 /* store_cred_flags */,
gss_const_key_value_set_t /* cred_store */,
gss_OID_set * /* elements_stored */,
gss_cred_usage_t * /* cred_usage_stored */,
gss_buffer_set_t * /* env */
);
enum gss_store_cred_flags {
GSS_C_STORE_CRED_DEFAULT = 1,
GSS_C_STORE_CRED_OVERWRITE = 2,
GSS_C_STORE_CRED_SET_PROCESS = 4,
};
#define GSS_C_STORE_CRED_DEFAULT GSS_C_STORE_CRED_DEFAULT
#define GSS_C_STORE_CRED_OVERWRITE GSS_C_STORE_CRED_OVERWRITE
#define GSS_C_STORE_CRED_SET_PROCESS GSS_C_STORE_CRED_SET_PROCESS
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_CALLCONV GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_CALLCONV
gss_set_neg_mechs( gss_set_neg_mechs(
OM_uint32 * /* minor_status */, OM_uint32 * /* minor_status */,

View File

@@ -474,6 +474,17 @@ _gss_store_cred_into_t(OM_uint32 *minor_status,
gss_OID_set *elements_stored, gss_OID_set *elements_stored,
gss_cred_usage_t *cred_usage_stored); gss_cred_usage_t *cred_usage_stored);
typedef OM_uint32 GSSAPI_CALLCONV
_gss_store_cred_into2_t(OM_uint32 *minor_status,
gss_const_cred_id_t input_cred_handle,
gss_cred_usage_t input_usage,
gss_OID desired_mech,
OM_uint32 store_cred_flags,
gss_const_key_value_set_t cred_store,
gss_OID_set *elements_stored,
gss_cred_usage_t *cred_usage_stored,
gss_buffer_set_t *env);
typedef OM_uint32 GSSAPI_CALLCONV typedef OM_uint32 GSSAPI_CALLCONV
_gss_set_neg_mechs_t(OM_uint32 *minor_status, _gss_set_neg_mechs_t(OM_uint32 *minor_status,
gss_cred_id_t cred_handle, gss_cred_id_t cred_handle,
@@ -623,6 +634,7 @@ typedef struct gssapi_mech_interface_desc {
_gss_query_mechanism_info_t *gm_query_mechanism_info; _gss_query_mechanism_info_t *gm_query_mechanism_info;
_gss_query_meta_data_t *gm_query_meta_data; _gss_query_meta_data_t *gm_query_meta_data;
_gss_exchange_meta_data_t *gm_exchange_meta_data; _gss_exchange_meta_data_t *gm_exchange_meta_data;
_gss_store_cred_into2_t *gm_store_cred_into2;
struct gss_mech_compat_desc_struct *gm_compat; struct gss_mech_compat_desc_struct *gm_compat;
} gssapi_mech_interface_desc, *gssapi_mech_interface; } gssapi_mech_interface_desc, *gssapi_mech_interface;

View File

@@ -169,8 +169,7 @@ gsskrb5_accept_delegated_token
} }
*delegated_cred_handle = NULL; *delegated_cred_handle = NULL;
kret = krb5_cc_new_unique (context, krb5_cc_type_memory, kret = krb5_cc_resolve(context, "MEMORY:anonymous", &ccache);
NULL, &ccache);
if (kret) { if (kret) {
ctx->flags &= ~GSS_C_DELEG_FLAG; ctx->flags &= ~GSS_C_DELEG_FLAG;
goto out; goto out;
@@ -204,7 +203,7 @@ gsskrb5_accept_delegated_token
gsskrb5_cred handle; gsskrb5_cred handle;
ret = _gsskrb5_krb5_import_cred(minor_status, ret = _gsskrb5_krb5_import_cred(minor_status,
ccache, &ccache,
NULL, NULL,
NULL, NULL,
delegated_cred_handle); delegated_cred_handle);
@@ -212,10 +211,7 @@ gsskrb5_accept_delegated_token
goto out; goto out;
handle = (gsskrb5_cred) *delegated_cred_handle; handle = (gsskrb5_cred) *delegated_cred_handle;
handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE; handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
krb5_cc_close(context, ccache);
ccache = NULL;
} }
out: out:

View File

@@ -62,9 +62,18 @@ gss_krb5_copy_ccache(OM_uint32 *minor_status,
#endif #endif
/*
* WARNING: Takes ownership of `id'. Because MEMORY:anonymous is now not
* linked into to the MEMORY ccache namespace, we can't use krb5_cc_resolve()
* with the cache's name anymore. We need a krb5_cc_clone() or some such, with
* attendant new method for ccops. Or we could create a new MEMORY:anonymous
* ccache and copy all the creds from `id' into it. But we know callers of
* this function don't need `id' after calling it, so for now we'll just take
* ownershipd of it.
*/
OM_uint32 OM_uint32
_gsskrb5_krb5_import_cred(OM_uint32 *minor_status, _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
krb5_ccache id, krb5_ccache *id,
krb5_principal keytab_principal, krb5_principal keytab_principal,
krb5_keytab keytab, krb5_keytab keytab,
gss_cred_id_t *cred) gss_cred_id_t *cred)
@@ -88,14 +97,13 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
handle->usage = 0; handle->usage = 0;
if (id) { if (*id) {
time_t now; time_t now;
OM_uint32 left; OM_uint32 left;
char *str;
handle->usage |= GSS_C_INITIATE; handle->usage |= GSS_C_INITIATE;
kret = krb5_cc_get_principal(context, id, kret = krb5_cc_get_principal(context, *id,
&handle->principal); &handle->principal);
if (kret) { if (kret) {
free(handle); free(handle);
@@ -121,7 +129,7 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
krb5_timeofday(context, &now); krb5_timeofday(context, &now);
ret = __gsskrb5_ccache_lifetime(minor_status, ret = __gsskrb5_ccache_lifetime(minor_status,
context, context,
id, *id,
handle->principal, handle->principal,
&left); &left);
if (ret != GSS_S_COMPLETE) { if (ret != GSS_S_COMPLETE) {
@@ -131,12 +139,8 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
} }
handle->endtime = now + left; handle->endtime = now + left;
kret = krb5_cc_get_full_name(context, id, &str); handle->ccache = *id;
if (kret) *id = NULL;
goto out;
kret = krb5_cc_resolve(context, str, &handle->ccache);
free(str);
if (kret) if (kret)
goto out; goto out;
} }

View File

@@ -404,6 +404,7 @@ static gssapi_mech_interface_desc krb5_mech = {
NULL, /* gm_query_mechanism_info */ NULL, /* gm_query_mechanism_info */
NULL, /* gm_query_meta_data */ NULL, /* gm_query_meta_data */
NULL, /* gm_exchange_meta_data */ NULL, /* gm_exchange_meta_data */
_gsskrb5_store_cred_into2,
NULL /* gm_compat */ NULL /* gm_compat */
}; };

View File

@@ -105,7 +105,7 @@ import_cred(OM_uint32 *minor_status,
free(str); free(str);
str = NULL; str = NULL;
major_stat = _gsskrb5_krb5_import_cred(minor_status, id, keytab_principal, major_stat = _gsskrb5_krb5_import_cred(minor_status, &id, keytab_principal,
keytab, cred_handle); keytab, cred_handle);
out: out:
if (id) if (id)

View File

@@ -51,64 +51,54 @@ same_princ(krb5_context context, krb5_ccache id1, krb5_ccache id2)
return same; return same;
} }
/* static OM_uint32
* Like krb5_cc_cache_match(), but only looking in the default collection. add_env(OM_uint32 *minor,
* gss_buffer_set_t *env,
* We need this to avoid looking for MEMORY ccaches, which risks matching the const char *var,
* same credential that we're storing. We could make sure that MEMORY ccaches const char *val)
* are searched for last in krb5_cc_cache_match(), then ignore any MEMORY
* ccaches we find there, but, if we might then store in a ccache that will not
* be found later as the default ccache, then it's not worth it.
*
* XXX In order to remove this, we'll first need to make sure that
* krb5_cc_default() searches all collections when KRB5CCNAME is not set,
* then we'll need to make sure that krb5_cc_cache_match() searches MEMORY
* ccaches last (or else introduce a new ccache type like MEMORY but which
* is never searched or searchable), then make sure that the caller below
* treat finding a MEMORY the same as not finding a ccache at all.
*/
static krb5_error_code
ccache_match(krb5_context context,
krb5_principal princ,
const char *cctype,
krb5_ccache *id)
{ {
krb5_cc_cache_cursor cursor = NULL; OM_uint32 major;
krb5_error_code ret; gss_buffer_desc b;
char *varval = NULL;
*id = NULL; if (asprintf(&varval, "%s=%s", var, val) == -1 || varval == NULL) {
ret = krb5_cc_cache_get_first(context, cctype, &cursor); *minor = ENOMEM;
if (ret) return GSS_S_FAILURE;
return ret;
while (krb5_cc_cache_next(context, cursor, id) == 0) {
krb5_principal p = NULL;
ret = krb5_cc_get_principal(context, *id, &p);
if (ret == 0 &&
krb5_principal_compare(context, princ, p)) {
krb5_free_principal(context, p);
krb5_cc_cache_end_seq_get(context, cursor);
return 0;
}
if (*id)
krb5_cc_close(context, *id);
*id = NULL;
} }
krb5_cc_cache_end_seq_get(context, cursor);
return KRB5_CC_END; b.value = varval;
b.length = strlen(varval) + 1;
major = gss_add_buffer_set_member(minor, &b, env);
free(varval);
return major;
}
static OM_uint32
set_proc(OM_uint32 *minor, gss_buffer_set_t env)
{
size_t i;
/*
* XXX On systems with setpag(), call setpag(). On WIN32... create a
* session, set the access token, ...?
*/
#ifndef WIN32
for (i = 0; i < env->count; i++)
putenv(env->elements[i].value);
#endif
return GSS_S_COMPLETE;
} }
OM_uint32 GSSAPI_CALLCONV OM_uint32 GSSAPI_CALLCONV
_gsskrb5_store_cred_into(OM_uint32 *minor_status, _gsskrb5_store_cred_into2(OM_uint32 *minor_status,
gss_const_cred_id_t input_cred_handle, gss_const_cred_id_t input_cred_handle,
gss_cred_usage_t cred_usage, gss_cred_usage_t cred_usage,
const gss_OID desired_mech, const gss_OID desired_mech,
OM_uint32 overwrite_cred, OM_uint32 store_cred_flags,
OM_uint32 default_cred, gss_const_key_value_set_t cred_store,
gss_const_key_value_set_t cred_store, gss_OID_set *elements_stored,
gss_OID_set *elements_stored, gss_cred_usage_t *cred_usage_stored,
gss_cred_usage_t *cred_usage_stored) gss_buffer_set_t *envp)
{ {
krb5_context context; krb5_context context;
krb5_error_code ret; krb5_error_code ret;
@@ -116,8 +106,11 @@ _gsskrb5_store_cred_into(OM_uint32 *minor_status,
krb5_ccache id = NULL; krb5_ccache id = NULL;
time_t exp_current; time_t exp_current;
time_t exp_new; time_t exp_new;
gss_buffer_set_t env = GSS_C_NO_BUFFER_SET;
const char *cs_ccache_name = NULL; const char *cs_ccache_name = NULL;
OM_uint32 major_status; OM_uint32 major_status, junk;
OM_uint32 overwrite_cred = store_cred_flags & GSS_C_STORE_CRED_OVERWRITE;
OM_uint32 default_cred = store_cred_flags & GSS_C_STORE_CRED_DEFAULT;
*minor_status = 0; *minor_status = 0;
@@ -168,45 +161,17 @@ _gsskrb5_store_cred_into(OM_uint32 *minor_status,
} }
if (cs_ccache_name) { if (cs_ccache_name) {
/*
* Not the default ccache.
*
* Therefore not a collection type cache.
*
* Therefore there's no question of switching the primary ccache.
*
* Therefore we reset default_cred.
*
* XXX Perhaps we should fail in this case if default_cred is true.
*/
default_cred = 0; default_cred = 0;
ret = krb5_cc_resolve(context, cs_ccache_name, &id); ret = krb5_cc_resolve(context, cs_ccache_name, &id);
} else { } else {
const char *cctype = NULL;
/* /*
* Use the default ccache, and if it's a collection, switch it if * Use the default ccache, and if it's a collection, switch it if
* default_cred is true. * default_cred is true.
*/ */
ret = krb5_cc_default(context, &id); ret = krb5_cc_default_for(context, input_cred->principal, &id);
if (ret == 0) { if (ret == 0 &&
cctype = krb5_cc_get_type(context, id); krb5_cc_support_switch(context, krb5_cc_get_type(context, id)))
if (krb5_cc_support_switch(context, cctype)) { overwrite_cred = 1;
/* The default ccache is a collection type */
krb5_cc_close(context, id);
id = NULL;
/* Find a matching ccache or create a new one */
ret = ccache_match(context, input_cred->principal,
cctype, &id);
if (ret || id == NULL) {
/* Since the ccache is new, just store unconditionally */
overwrite_cred = 1;
ret = krb5_cc_new_unique(context, cctype, NULL, &id);
}
}
}
} }
if (ret || id == NULL) { if (ret || id == NULL) {
@@ -215,14 +180,7 @@ _gsskrb5_store_cred_into(OM_uint32 *minor_status,
return ret == 0 ? GSS_S_NO_CRED : GSS_S_FAILURE; return ret == 0 ? GSS_S_NO_CRED : GSS_S_FAILURE;
} }
/* if (!overwrite_cred && same_princ(context, id, input_cred->ccache)) {
* If the new creds are for a different principal than we had before,
* overwrite.
*/
if (!overwrite_cred && !same_princ(context, id, input_cred->ccache))
overwrite_cred = 1;
if (!overwrite_cred) {
/* /*
* If current creds are for the same princ as we already had creds for, * If current creds are for the same princ as we already had creds for,
* and the new creds live longer than the old, overwrite. * and the new creds live longer than the old, overwrite.
@@ -246,9 +204,47 @@ _gsskrb5_store_cred_into(OM_uint32 *minor_status,
NULL); NULL);
if (ret == 0 && default_cred) if (ret == 0 && default_cred)
krb5_cc_switch(context, id); krb5_cc_switch(context, id);
if ((store_cred_flags & GSS_C_STORE_CRED_SET_PROCESS) && envp == NULL)
envp = &env;
if (envp != NULL) {
char *fullname = NULL;
if ((ret = krb5_cc_get_full_name(context, id, &fullname)) == 0) {
major_status = add_env(minor_status, envp, "KRB5CCNAME", fullname);
free(fullname);
if (major_status)
ret = *minor_status;
}
}
(void) krb5_cc_close(context, id); (void) krb5_cc_close(context, id);
HEIMDAL_MUTEX_unlock(&input_cred->cred_id_mutex); HEIMDAL_MUTEX_unlock(&input_cred->cred_id_mutex);
if (ret == 0 && (store_cred_flags & GSS_C_STORE_CRED_SET_PROCESS) &&
(major_status = set_proc(minor_status, *envp)) != GSS_S_COMPLETE)
ret = *minor_status;
(void) gss_release_buffer_set(&junk, &env);
*minor_status = ret; *minor_status = ret;
return ret ? GSS_S_FAILURE : GSS_S_COMPLETE; return ret ? GSS_S_FAILURE : GSS_S_COMPLETE;
} }
OM_uint32 GSSAPI_CALLCONV
_gsskrb5_store_cred_into(OM_uint32 *minor_status,
gss_const_cred_id_t input_cred_handle,
gss_cred_usage_t cred_usage,
const gss_OID desired_mech,
OM_uint32 overwrite_cred,
OM_uint32 default_cred,
gss_const_key_value_set_t cred_store,
gss_OID_set *elements_stored,
gss_cred_usage_t *cred_usage_stored)
{
OM_uint32 store_cred_flags =
(overwrite_cred ? GSS_C_STORE_CRED_OVERWRITE : 0) |
(default_cred ? GSS_C_STORE_CRED_DEFAULT : 0);
return _gsskrb5_store_cred_into2(minor_status, input_cred_handle,
cred_usage, desired_mech,
store_cred_flags, cred_store,
elements_stored, cred_usage_stored, NULL);
}

View File

@@ -96,6 +96,7 @@ EXPORTS
gss_sign gss_sign
gss_store_cred gss_store_cred
gss_store_cred_into gss_store_cred_into
gss_store_cred_into2
gss_test_oid_set_member gss_test_oid_set_member
gss_unseal gss_unseal
gss_unwrap gss_unwrap

View File

@@ -38,14 +38,23 @@ store_mech_cred(OM_uint32 *minor_status,
gssapi_mech_interface m, gssapi_mech_interface m,
const struct _gss_mechanism_cred *mc, const struct _gss_mechanism_cred *mc,
gss_cred_usage_t input_usage, gss_cred_usage_t input_usage,
OM_uint32 overwrite_cred, OM_uint32 store_cred_flags,
OM_uint32 default_cred,
gss_const_key_value_set_t cred_store, gss_const_key_value_set_t cred_store,
gss_cred_usage_t *usage_stored) gss_cred_usage_t *usage_stored,
gss_buffer_set_t *env)
{ {
OM_uint32 major_status; OM_uint32 major_status;
OM_uint32 overwrite_cred =
!!(store_cred_flags & GSS_C_STORE_CRED_OVERWRITE);
OM_uint32 default_cred = !!(store_cred_flags & GSS_C_STORE_CRED_DEFAULT);
if (m->gm_store_cred_into) if (m->gm_store_cred_into2)
major_status = m->gm_store_cred_into2(minor_status, mc->gmc_cred,
input_usage, &m->gm_mech_oid,
store_cred_flags, cred_store,
NULL, usage_stored,
env);
else if (m->gm_store_cred_into)
major_status = m->gm_store_cred_into(minor_status, mc->gmc_cred, major_status = m->gm_store_cred_into(minor_status, mc->gmc_cred,
input_usage, &m->gm_mech_oid, input_usage, &m->gm_mech_oid,
overwrite_cred, default_cred, overwrite_cred, default_cred,
@@ -66,25 +75,28 @@ store_mech_cred(OM_uint32 *minor_status,
* const key/value hashmap-like thing that specifies a credential store in a * const key/value hashmap-like thing that specifies a credential store in a
* mechanism- and implementation-specific way, though Heimdal and MIT agree on * mechanism- and implementation-specific way, though Heimdal and MIT agree on
* at least the following keys for the Kerberos mechanism: ccache, keytab, and * at least the following keys for the Kerberos mechanism: ccache, keytab, and
* client_keytab. * client_keytab. A set of environment variables may be output as well
*/ */
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
gss_store_cred_into(OM_uint32 *minor_status, gss_store_cred_into2(OM_uint32 *minor_status,
gss_const_cred_id_t input_cred_handle, gss_const_cred_id_t input_cred_handle,
gss_cred_usage_t input_usage, gss_cred_usage_t input_usage,
const gss_OID desired_mech, const gss_OID desired_mech,
OM_uint32 overwrite_cred, OM_uint32 store_cred_flags,
OM_uint32 default_cred, gss_const_key_value_set_t cred_store,
gss_const_key_value_set_t cred_store, gss_OID_set *elements_stored,
gss_OID_set *elements_stored, gss_cred_usage_t *cred_usage_stored,
gss_cred_usage_t *cred_usage_stored) gss_buffer_set_t *env)
{ {
struct _gss_cred *cred = (struct _gss_cred *) input_cred_handle; struct _gss_cred *cred = (struct _gss_cred *)input_cred_handle;
struct _gss_mechanism_cred *mc; struct _gss_mechanism_cred *mc;
OM_uint32 major_status; OM_uint32 major_status;
OM_uint32 minor; OM_uint32 minor;
size_t successes; size_t successes;
if (env != NULL)
*env = NULL;
if (input_cred_handle == NULL) if (input_cred_handle == NULL)
return GSS_S_CALL_INACCESSIBLE_READ; return GSS_S_CALL_INACCESSIBLE_READ;
@@ -117,10 +129,9 @@ gss_store_cred_into(OM_uint32 *minor_status,
!gss_oid_equal(&m->gm_mech_oid, desired_mech)) !gss_oid_equal(&m->gm_mech_oid, desired_mech))
continue; continue;
major_status = store_mech_cred(minor_status, m, mc, major_status = store_mech_cred(minor_status, m, mc, input_usage,
input_usage, overwrite_cred, store_cred_flags, cred_store,
default_cred, cred_store, cred_usage_stored, env);
cred_usage_stored);
if (major_status == GSS_S_COMPLETE) { if (major_status == GSS_S_COMPLETE) {
if (elements_stored && desired_mech != GSS_C_NO_OID) if (elements_stored && desired_mech != GSS_C_NO_OID)
gss_add_oid_set_member(&minor, desired_mech, elements_stored); gss_add_oid_set_member(&minor, desired_mech, elements_stored);
@@ -142,3 +153,29 @@ gss_store_cred_into(OM_uint32 *minor_status,
return major_status; return major_status;
} }
/*
* See RFC5588 for gss_store_cred(). This function is a variant that takes a
* const key/value hashmap-like thing that specifies a credential store in a
* mechanism- and implementation-specific way, though Heimdal and MIT agree on
* at least the following keys for the Kerberos mechanism: ccache, keytab, and
* client_keytab.
*/
GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
gss_store_cred_into(OM_uint32 *minor_status,
gss_const_cred_id_t input_cred_handle,
gss_cred_usage_t input_usage,
const gss_OID desired_mech,
OM_uint32 overwrite_cred,
OM_uint32 default_cred,
gss_const_key_value_set_t cred_store,
gss_OID_set *elements_stored,
gss_cred_usage_t *cred_usage_stored)
{
OM_uint32 store_cred_flags =
(overwrite_cred ? GSS_C_STORE_CRED_OVERWRITE : 0) |
(default_cred ? GSS_C_STORE_CRED_DEFAULT : 0);
return gss_store_cred_into2(minor_status, input_cred_handle, input_usage,
desired_mech, store_cred_flags, cred_store,
elements_stored, cred_usage_stored, NULL);
}

View File

@@ -130,6 +130,7 @@ static gssapi_mech_interface_desc ntlm_mech = {
NULL, /* gm_query_mechanism_info */ NULL, /* gm_query_mechanism_info */
NULL, /* gm_query_meta_data */ NULL, /* gm_query_meta_data */
NULL, /* gm_exchange_meta_data */ NULL, /* gm_exchange_meta_data */
NULL, /* gm_store_cred_into2 */
NULL, /* gm_compat */ NULL, /* gm_compat */
}; };

View File

@@ -154,6 +154,7 @@ static gssapi_mech_interface_desc spnego_mech = {
NULL, /* gm_query_mechanism_info */ NULL, /* gm_query_mechanism_info */
NULL, /* gm_query_meta_data */ NULL, /* gm_query_meta_data */
NULL, /* gm_exchange_meta_data */ NULL, /* gm_exchange_meta_data */
NULL, /* gm_store_cred_into2 */
NULL /* gm_compat */ NULL /* gm_compat */
}; };

View File

@@ -94,12 +94,21 @@ gss_err(int exitval, OM_uint32 major, OM_uint32 minor, gss_OID mech,
exit(exitval); exit(exitval);
} }
static int version_flag = 0; static int version_flag = 0;
static int help_flag = 0; static int help_flag = 0;
static int env_flag = 0;
static int def_flag = 0;
static int overwrite_flag = 0;
static struct getargs args[] = { static struct getargs args[] = {
{"version", 0, arg_flag, &version_flag, "print version", NULL }, {"version", 0, arg_flag, &version_flag, "print version", NULL },
{"help", 0, arg_flag, &help_flag, NULL, NULL } {"help", 0, arg_flag, &help_flag, NULL, NULL },
{"env", 'e', arg_flag, &env_flag,
"output env settings", NULL },
{"default", 0, arg_flag, &def_flag,
"switch credential store default principal", NULL },
{"overwrite", 0, arg_flag, &overwrite_flag,
"overwrite matching credential", NULL },
}; };
static void static void
@@ -119,6 +128,8 @@ main(int argc, char **argv)
gss_cred_id_t cred = GSS_C_NO_CREDENTIAL; gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
gss_key_value_element_desc from_elements, to_elements; gss_key_value_element_desc from_elements, to_elements;
gss_key_value_set_desc from, to; gss_key_value_set_desc from, to;
gss_buffer_set_t env = GSS_C_NO_BUFFER_SET;
OM_uint32 store_flags = 0;
int optidx = 0; int optidx = 0;
setprogname(argv[0]); setprogname(argv[0]);
@@ -133,6 +144,11 @@ main(int argc, char **argv)
exit(0); exit(0);
} }
if (def_flag)
store_flags |= GSS_C_STORE_CRED_DEFAULT;
if (overwrite_flag)
store_flags |= GSS_C_STORE_CRED_OVERWRITE;
argc -= optidx; argc -= optidx;
argv += optidx; argv += optidx;
@@ -159,12 +175,35 @@ main(int argc, char **argv)
gss_err(1, major, minor, GSS_KRB5_MECHANISM, gss_err(1, major, minor, GSS_KRB5_MECHANISM,
"failed to acquire creds from %s", argv[0]); "failed to acquire creds from %s", argv[0]);
major = gss_store_cred_into(&minor, from_cred, GSS_C_INITIATE, major = gss_store_cred_into2(&minor, from_cred, GSS_C_INITIATE,
GSS_KRB5_MECHANISM, 1, 1, &to, NULL, NULL); GSS_KRB5_MECHANISM, store_flags, &to, NULL,
NULL, env_flag ? &env : NULL);
if (major != GSS_S_COMPLETE) if (major != GSS_S_COMPLETE)
gss_err(1, major, minor, GSS_KRB5_MECHANISM, gss_err(1, major, minor, GSS_KRB5_MECHANISM,
"failed to store creds into %s", argv[1]); "failed to store creds into %s", argv[1]);
if (env_flag) {
size_t i;
int got_krb5ccname = 0;
if (env == GSS_C_NO_BUFFER_SET)
warnx("No environment settings");
for (i = 0; env != GSS_C_NO_BUFFER_SET && i < env->count; i++) {
got_krb5ccname = got_krb5ccname ||
(env->elements[i].length > sizeof("KRB5CCNAME=") &&
strncmp((const char *)env->elements[i].value, "KRB5CCNAME=",
sizeof("KRB5CCNAME=") - 1) == 0);
printf("%.*s\n", (int)env->elements[i].length,
(const char *)env->elements[i].value);
}
(void) gss_release_buffer_set(&minor, &env);
if (!got_krb5ccname)
errx(1, "KRB5CCNAME environment variable not set by "
"gss_store_cred_into2()");
}
(void) gss_release_cred(&minor, &from_cred); (void) gss_release_cred(&minor, &from_cred);
(void) gss_release_cred(&minor, &to_cred); (void) gss_release_cred(&minor, &to_cred);

View File

@@ -90,6 +90,7 @@ HEIMDAL_GSS_2.0 {
gss_sign; gss_sign;
gss_store_cred; gss_store_cred;
gss_store_cred_into; gss_store_cred_into;
gss_store_cred_into2;
gss_test_oid_set_member; gss_test_oid_set_member;
gss_unseal; gss_unseal;
gss_unwrap; gss_unwrap;

View File

@@ -49,6 +49,7 @@ static void *cc_handle;
typedef struct krb5_acc { typedef struct krb5_acc {
char *cache_name; char *cache_name;
char *cache_subsidiary;
cc_context_t context; cc_context_t context;
cc_ccache_t ccache; cc_ccache_t ccache;
} krb5_acc; } krb5_acc;
@@ -442,41 +443,51 @@ get_cc_name(krb5_acc *a)
} }
static const char* KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
acc_get_name(krb5_context context, acc_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id,
const char **name,
const char **colname,
const char **subsidiary)
{ {
krb5_error_code ret;
krb5_acc *a = ACACHE(id); krb5_acc *a = ACACHE(id);
int32_t error; int32_t error;
if (a->cache_name == NULL) { if (name)
krb5_error_code ret; *name = NULL;
krb5_principal principal; if (colname)
char *name; *colname = NULL;
if (subsidiary)
*subsidiary = NULL;
if (a->cache_subsidiary == NULL) {
krb5_principal principal = NULL;
ret = _krb5_get_default_principal_local(context, &principal); ret = _krb5_get_default_principal_local(context, &principal);
if (ret) if (ret == 0)
return NULL; ret = krb5_unparse_name(context, principal, &a->cache_subsidiary);
ret = krb5_unparse_name(context, principal, &name);
krb5_free_principal(context, principal); krb5_free_principal(context, principal);
if (ret) if (ret)
return NULL; return ret;
error = (*a->context->func->create_new_ccache)(a->context,
cc_credentials_v5,
name,
&a->ccache);
krb5_xfree(name);
if (error)
return NULL;
error = get_cc_name(a);
if (error)
return NULL;
} }
return a->cache_name; if (a->cache_name == NULL) {
error = (*a->context->func->create_new_ccache)(a->context,
cc_credentials_v5,
a->cache_subsidiary,
&a->ccache);
if (error == ccNoError)
error = get_cc_name(a);
if (error != ccNoError)
ret = translate_cc_error(context, error);
}
if (name)
*name = a->cache_name;
if (colname)
*colname = "";
if (subsidiary)
*subsidiary = a->cache_subsidiary;
return ret;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
@@ -497,6 +508,10 @@ acc_alloc(krb5_context context, krb5_ccache *id)
} }
a = ACACHE(*id); a = ACACHE(*id);
a->cache_subsidiary = NULL;
a->cache_name = NULL;
a->context = NULL;
a->ccache = NULL;
error = (*init_func)(&a->context, ccapi_version_3, NULL, NULL); error = (*init_func)(&a->context, ccapi_version_3, NULL, NULL);
if (error) { if (error) {
@@ -504,17 +519,17 @@ acc_alloc(krb5_context context, krb5_ccache *id)
return translate_cc_error(context, error); return translate_cc_error(context, error);
} }
a->cache_name = NULL;
return 0; return 0;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
acc_resolve(krb5_context context, krb5_ccache *id, const char *res) acc_resolve(krb5_context context, krb5_ccache *id, const char *res, const char *sub)
{ {
krb5_error_code ret; krb5_error_code ret;
cc_time_t offset;
cc_int32 error; cc_int32 error;
krb5_acc *a; krb5_acc *a;
char *s = NULL;
ret = acc_alloc(context, id); ret = acc_alloc(context, id);
if (ret) if (ret)
@@ -522,49 +537,44 @@ acc_resolve(krb5_context context, krb5_ccache *id, const char *res)
a = ACACHE(*id); a = ACACHE(*id);
error = (*a->context->func->open_ccache)(a->context, res, &a->ccache); if (sub) {
if (error == ccNoError) { if (asprintf(&s, "%s:%s", res, sub) == -1 || s == NULL ||
cc_time_t offset; (a->cache_subsidiary = strdup(sub)) == NULL) {
error = get_cc_name(a);
if (error != ccNoError) {
acc_close(context, *id); acc_close(context, *id);
*id = NULL; free(s);
return translate_cc_error(context, error); return krb5_enomem(context);
} }
res = s;
error = (*a->ccache->func->get_kdc_time_offset)(a->ccache, /*
cc_credentials_v5, * XXX With a bit of extra refactoring we could use the collection name
&offset); * as the path to the shared object implementing CCAPI... For now we
if (error == 0) * ignore the collection name.
context->kdc_sec_offset = offset; */
} else if (error == ccErrCCacheNotFound) {
a->ccache = NULL;
a->cache_name = NULL;
} else {
*id = NULL;
return translate_cc_error(context, error);
} }
error = (*a->context->func->open_ccache)(a->context, res, &a->ccache);
if (error == ccNoError)
error = get_cc_name(a);
if (error != ccNoError) {
acc_close(context, *id);
*id = NULL;
free(s);
return translate_cc_error(context, error);
}
error = (*a->ccache->func->get_kdc_time_offset)(a->ccache,
cc_credentials_v5,
&offset);
if (error == 0)
context->kdc_sec_offset = offset;
free(s);
return 0; return 0;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
acc_gen_new(krb5_context context, krb5_ccache *id) acc_gen_new(krb5_context context, krb5_ccache *id)
{ {
krb5_error_code ret; return acc_alloc(context, id);
krb5_acc *a;
ret = acc_alloc(context, id);
if (ret)
return ret;
a = ACACHE(*id);
a->ccache = NULL;
a->cache_name = NULL;
return 0;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV

View File

@@ -104,7 +104,7 @@ main (int argc, char **argv)
* Add a new ccache type with operations `ops', overwriting any * Add a new ccache type with operations `ops', overwriting any
* existing one if `override'. * existing one if `override'.
* *
* @param context a Keberos context * @param context a Kerberos context
* @param ops type of plugin symbol * @param ops type of plugin symbol
* @param override flag to select if the registration is to overide * @param override flag to select if the registration is to overide
* an existing ops with the same name. * an existing ops with the same name.
@@ -180,10 +180,11 @@ _krb5_cc_allocate(krb5_context context,
*/ */
static krb5_error_code static krb5_error_code
allocate_ccache (krb5_context context, allocate_ccache(krb5_context context,
const krb5_cc_ops *ops, const krb5_cc_ops *ops,
const char *residual, const char *residual,
krb5_ccache *id) const char *subsidiary,
krb5_ccache *id)
{ {
krb5_error_code ret; krb5_error_code ret;
#ifdef KRB5_USE_PATH_TOKENS #ifdef KRB5_USE_PATH_TOKENS
@@ -210,7 +211,7 @@ allocate_ccache (krb5_context context,
return ret; return ret;
} }
ret = (*id)->ops->resolve(context, id, residual); ret = (*id)->ops->resolve(context, id, residual, subsidiary);
if(ret) { if(ret) {
free(*id); free(*id);
*id = NULL; *id = NULL;
@@ -247,7 +248,7 @@ is_possible_path_name(const char * name)
* Find and allocate a ccache in `id' from the specification in `residual'. * Find and allocate a ccache in `id' from the specification in `residual'.
* If the ccache name doesn't contain any colon, interpret it as a file name. * If the ccache name doesn't contain any colon, interpret it as a file name.
* *
* @param context a Keberos context. * @param context a Kerberos context.
* @param name string name of a credential cache. * @param name string name of a credential cache.
* @param id return pointer to a found credential cache. * @param id return pointer to a found credential cache.
* *
@@ -273,12 +274,12 @@ krb5_cc_resolve(krb5_context context,
if(strncmp(context->cc_ops[i]->prefix, name, prefix_len) == 0 if(strncmp(context->cc_ops[i]->prefix, name, prefix_len) == 0
&& name[prefix_len] == ':') { && name[prefix_len] == ':') {
return allocate_ccache (context, context->cc_ops[i], return allocate_ccache (context, context->cc_ops[i],
name + prefix_len + 1, name + prefix_len + 1, NULL,
id); id);
} }
} }
if (is_possible_path_name(name)) if (is_possible_path_name(name))
return allocate_ccache (context, &krb5_fcc_ops, name, id); return allocate_ccache (context, &krb5_fcc_ops, name, NULL, id);
else { else {
krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE, krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,
N_("unknown ccache type %s", "name"), name); N_("unknown ccache type %s", "name"), name);
@@ -286,6 +287,153 @@ krb5_cc_resolve(krb5_context context,
} }
} }
static const char *
get_default_cc_type(krb5_context context, int simple)
{
const char *def_ccname;
const char *def_cctype =
krb5_config_get_string_default(context, NULL,
secure_getenv("KRB5CCTYPE"),
"libdefaults", "default_cc_type", NULL);
if (!simple &&
(def_ccname = krb5_cc_default_name(context))) {
size_t i;
for (i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {
size_t prefix_len = strlen(context->cc_ops[i]->prefix);
if (!strncmp(context->cc_ops[i]->prefix, def_ccname, prefix_len) &&
def_ccname[prefix_len] == ':')
return context->cc_ops[i]->prefix;
}
if (is_possible_path_name(def_ccname))
return "FILE";
}
return def_cctype ? def_cctype : "DIR";
}
/**
* Find and allocate a ccache in `id' for the subsidiary cache named by
* `subsidiary' in the collection named by `collection'.
*
* @param context a Kerberos context.
* @param cctype string name of a credential cache collection type.
* @param collection string name of a credential cache collection.
* @param subsidiary string name of a credential cache in a collection.
* @param id return pointer to a found credential cache.
*
* @return Return 0 or an error code. In case of an error, id is set
* to NULL, see krb5_get_error_message().
*
* @ingroup krb5_ccache
*/
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_cc_resolve_sub(krb5_context context,
const char *cctype,
const char *collection,
const char *subsidiary,
krb5_ccache *id)
{
size_t i;
*id = NULL;
if (!cctype && collection) {
/* Get the cctype from the collection, maybe */
for (i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {
size_t plen = strlen(context->cc_ops[i]->prefix);
if ((strncmp(context->cc_ops[i]->prefix, collection, plen) ||
collection[plen] != ':'))
continue;
cctype = context->cc_ops[i]->prefix;
collection += plen + 1;
break;
}
}
if (!cctype) {
const char *def_cctype = get_default_cc_type(context, 0);
int might_be_path = collection && is_possible_path_name(collection);
if (def_cctype)
cctype = def_cctype;
else if (might_be_path && subsidiary)
cctype = "DIR"; /* Default to DIR */
else if (might_be_path && !subsidiary)
cctype = "FILE"; /* Default to FILE */
}
/* If either `cctype' is not NULL or `collection' starts with TYPE: */
if (cctype || collection) {
for (i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {
size_t plen = strlen(context->cc_ops[i]->prefix);
if (cctype && strcmp(context->cc_ops[i]->prefix, cctype))
continue;
if (!cctype &&
(strncmp(context->cc_ops[i]->prefix, collection, plen) ||
collection[plen] != ':'))
continue;
return allocate_ccache(context, context->cc_ops[i],
cctype ? collection : collection + plen + 1,
subsidiary, id);
}
}
krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,
N_("unknown ccache type %s", ""),
cctype ? cctype : collection);
return KRB5_CC_UNKNOWN_TYPE;
}
/**
* Find and allocate a ccache in `id' from the specification in `residual', but
* specific to the given principal `principal' by using the principal name as
* the name of a "subsidiary" credentials cache in the collection named by
* `name'. If the ccache name doesn't contain any colon, interpret it as a
* file name.
*
* @param context a Kerberos context.
* @param name string name of a credential cache.
* @param principal principal name of desired credentials.
* @param id return pointer to a found credential cache.
*
* @return Return 0 or an error code. In case of an error, id is set
* to NULL, see krb5_get_error_message().
*
* @ingroup krb5_ccache
*/
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_cc_resolve_for(krb5_context context,
const char *cctype,
const char *name,
krb5_const_principal principal,
krb5_ccache *id)
{
krb5_error_code ret;
char *p, *s;
*id = NULL;
ret = krb5_unparse_name(context, principal, &p);
if (ret)
return ret;
/* Subsidiary components cannot have ':'s in them */
for (s = strchr(p, ':'); s; s = strchr(s + 1, ':'))
*s = '-';
ret = krb5_cc_resolve_sub(context, cctype, name, p, id);
free(p);
return ret;
}
/** /**
* Generates a new unique ccache of `type` in `id'. If `type' is NULL, * Generates a new unique ccache of `type` in `id'. If `type' is NULL,
* the library chooses the default credential cache type. The supplied * the library chooses the default credential cache type. The supplied
@@ -334,7 +482,42 @@ KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
krb5_cc_get_name(krb5_context context, krb5_cc_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id)
{ {
return id->ops->get_name(context, id); const char *name;
(void) id->ops->get_name(context, id, &name, NULL, NULL);
return name;
}
/**
* Return the name of the ccache collection associated with `id'
*
* @ingroup krb5_ccache
*/
KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
krb5_cc_get_collection(krb5_context context, krb5_ccache id)
{
const char *name;
(void) id->ops->get_name(context, id, NULL, &name, NULL);
return name;
}
/**
* Return the name of the subsidiary ccache of `id'
*
* @ingroup krb5_ccache
*/
KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
krb5_cc_get_subsidiary(krb5_context context, krb5_ccache id)
{
const char *name;
(void) id->ops->get_name(context, id, NULL, NULL, &name);
return name;
} }
/** /**
@@ -354,7 +537,7 @@ krb5_cc_get_type(krb5_context context,
/** /**
* Return the complete resolvable name the cache * Return the complete resolvable name the cache
* @param context a Keberos context * @param context a Kerberos context
* @param id return pointer to a found credential cache * @param id return pointer to a found credential cache
* @param str the returned name of a credential cache, free with krb5_xfree() * @param str the returned name of a credential cache, free with krb5_xfree()
* *
@@ -620,8 +803,7 @@ krb5_cc_configured_default_name(krb5_context context)
} }
/* Else try a configured default ccache type's default */ /* Else try a configured default ccache type's default */
cfg = krb5_config_get_string(context, NULL, "libdefaults", cfg = get_default_cc_type(context, 1);
"default_cc_type", NULL);
if (cfg) { if (cfg) {
const krb5_cc_ops *ops; const krb5_cc_ops *ops;
@@ -687,18 +869,52 @@ krb5_cc_configured_default_name(krb5_context context)
* @ingroup krb5_ccache * @ingroup krb5_ccache
*/ */
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_cc_default(krb5_context context, krb5_cc_default(krb5_context context,
krb5_ccache *id) krb5_ccache *id)
{ {
const char *p = krb5_cc_default_name(context); const char *p = krb5_cc_default_name(context);
*id = NULL;
if (p == NULL) if (p == NULL)
return krb5_enomem(context); return krb5_enomem(context);
return krb5_cc_resolve(context, p, id); return krb5_cc_resolve(context, p, id);
} }
/**
* Open the named subsidiary cache from the default ccache collection in `id'.
*
* @return Return an error code or 0, see krb5_get_error_message().
*
* @ingroup krb5_ccache
*/
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_cc_default_sub(krb5_context context,
const char *subsidiary,
krb5_ccache *id)
{
return krb5_cc_resolve_sub(context, get_default_cc_type(context, 0), NULL,
subsidiary, id);
}
/**
* Open the default ccache in `id' that corresponds to the given principal.
*
* @return Return an error code or 0, see krb5_get_error_message().
*
* @ingroup krb5_ccache
*/
KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
krb5_cc_default_for(krb5_context context,
krb5_const_principal principal,
krb5_ccache *id)
{
return krb5_cc_resolve_for(context, get_default_cc_type(context, 0), NULL,
principal, id);
}
/** /**
* Create a new ccache in `id' for `primary_principal'. * Create a new ccache in `id' for `primary_principal'.
* *
@@ -1424,7 +1640,7 @@ krb5_cc_cache_match (krb5_context context,
* Move the content from one credential cache to another. The * Move the content from one credential cache to another. The
* operation is an atomic switch. * operation is an atomic switch.
* *
* @param context a Keberos context * @param context a Kerberos context
* @param from the credential cache to move the content from * @param from the credential cache to move the content from
* @param to the credential cache to move the content to * @param to the credential cache to move the content to
@@ -1515,7 +1731,7 @@ build_conf_principals(krb5_context context, krb5_ccache id,
* principal (generated part of krb5_cc_set_config()). Returns FALSE * principal (generated part of krb5_cc_set_config()). Returns FALSE
* (zero) if not a configuration principal. * (zero) if not a configuration principal.
* *
* @param context a Keberos context * @param context a Kerberos context
* @param principal principal to check if it a configuration principal * @param principal principal to check if it a configuration principal
* *
* @ingroup krb5_ccache * @ingroup krb5_ccache
@@ -1539,7 +1755,7 @@ krb5_is_config_principal(krb5_context context,
* Store some configuration for the credential cache in the cache. * Store some configuration for the credential cache in the cache.
* Existing configuration under the same name is over-written. * Existing configuration under the same name is over-written.
* *
* @param context a Keberos context * @param context a Kerberos context
* @param id the credential cache to store the data for * @param id the credential cache to store the data for
* @param principal configuration for a specific principal, if * @param principal configuration for a specific principal, if
* NULL, global for the whole cache. * NULL, global for the whole cache.
@@ -1586,7 +1802,7 @@ out:
/** /**
* Get some configuration for the credential cache in the cache. * Get some configuration for the credential cache in the cache.
* *
* @param context a Keberos context * @param context a Kerberos context
* @param id the credential cache to store the data for * @param id the credential cache to store the data for
* @param principal configuration for a specific principal, if * @param principal configuration for a specific principal, if
* NULL, global for the whole cache. * NULL, global for the whole cache.
@@ -1637,7 +1853,7 @@ struct krb5_cccol_cursor_data {
* Get a new cache interation cursor that will interate over all * Get a new cache interation cursor that will interate over all
* credentials caches independent of type. * credentials caches independent of type.
* *
* @param context a Keberos context * @param context a Kerberos context
* @param cursor passed into krb5_cccol_cursor_next() and free with krb5_cccol_cursor_free(). * @param cursor passed into krb5_cccol_cursor_next() and free with krb5_cccol_cursor_free().
* *
* @return Returns 0 or and error code, see krb5_get_error_message(). * @return Returns 0 or and error code, see krb5_get_error_message().
@@ -1711,7 +1927,7 @@ krb5_cccol_cursor_next(krb5_context context, krb5_cccol_cursor cursor,
return KRB5_CC_END; return KRB5_CC_END;
} }
return 0; return ret;
} }
/** /**

View File

@@ -37,8 +37,10 @@
typedef struct krb5_dcache{ typedef struct krb5_dcache{
krb5_ccache fcache; krb5_ccache fcache;
char *dir;
char *name; char *name;
char *dir;
char *sub;
unsigned int default_candidate:1;
} krb5_dcache; } krb5_dcache;
#define DCACHE(X) ((krb5_dcache*)(X)->data.data) #define DCACHE(X) ((krb5_dcache*)(X)->data.data)
@@ -46,7 +48,47 @@ typedef struct krb5_dcache{
static krb5_error_code KRB5_CALLCONV dcc_close(krb5_context, krb5_ccache); static krb5_error_code KRB5_CALLCONV dcc_close(krb5_context, krb5_ccache);
static krb5_error_code KRB5_CALLCONV dcc_get_default_name(krb5_context, char **); static krb5_error_code KRB5_CALLCONV dcc_get_default_name(krb5_context, char **);
static krb5_error_code KRB5_CALLCONV dcc_set_default(krb5_context, krb5_ccache);
/*
* Make subsidiary filesystem safe by mapping / and : to -. If the subsidiary
* is longer than 128 bytes, then truncate.
* In all cases, "tkt." is prefixed to be compatible with the DIR requirement
* that subsidiary ccache files be named tkt*.
*
* Thus host/foo.bar.baz@BAR.BAZ -> tkt.host-foo.bar.baz@BAR.BAZ.
*
* In particular, no filesystem component separators will be emitted, and . and
* .. will never be traversed.
*/
static krb5_error_code
fs_encode_subsidiary(krb5_context context,
krb5_dcache *dc,
const char *subsidiary,
char **res)
{
size_t len = strlen(subsidiary);
size_t i;
*res = NULL;
if (asprintf(res, "tkt.%s", subsidiary) == -1 || *res == NULL)
return krb5_enomem(context);
for (i = sizeof("tkt.") - 1; i < len; i++) {
switch ((*res)[i]) {
#ifdef WIN32
case '\\': (*res)[0] = '-'; break;
#endif
case '/': (*res)[0] = '-'; break;
case ':': (*res)[0] = '-'; break;
default: break;
}
}
/* Hopefully this will work on all filesystems */
if (len > 128 - sizeof("tkt.") - 1)
(*res)[127] = '\0';
return 0;
}
static char * static char *
primary_create(krb5_dcache *dc) primary_create(krb5_dcache *dc)
@@ -63,8 +105,14 @@ primary_create(krb5_dcache *dc)
static int static int
is_filename_cacheish(const char *name) is_filename_cacheish(const char *name)
{ {
return strncmp(name, "tkt", 3) == 0; size_t i;
if (strncmp(name, "tkt", sizeof("tkt") - 1))
return 0;
for (i = sizeof("tkt") - 1; name[i]; i++)
if (ISPATHSEP(name[i]))
return 0;
return 1;
} }
static krb5_error_code static krb5_error_code
@@ -77,12 +125,6 @@ set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual)
int fd = -1; int fd = -1;
int asprintf_ret; int asprintf_ret;
if (!is_filename_cacheish(residual)) {
krb5_set_error_message(context, KRB5_CC_FORMAT,
"name %s is not a cache (doesn't start with tkt)", residual);
return KRB5_CC_FORMAT;
}
asprintf_ret = asprintf(&path, "%s/primary-XXXXXX", dc->dir); asprintf_ret = asprintf(&path, "%s/primary-XXXXXX", dc->dir);
if (asprintf_ret == -1 || path == NULL) { if (asprintf_ret == -1 || path == NULL) {
return krb5_enomem(context); return krb5_enomem(context);
@@ -141,14 +183,18 @@ set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual)
} }
static krb5_error_code static krb5_error_code
get_default_cache(krb5_context context, krb5_dcache *dc, char **residual) get_default_cache(krb5_context context, krb5_dcache *dc,
const char *subsidiary, char **residual)
{ {
krb5_error_code ret; krb5_error_code ret;
char buf[MAXPATHLEN]; char buf[MAXPATHLEN];
char *primary; char *primary = NULL;
FILE *f; FILE *f;
*residual = NULL; *residual = NULL;
if (subsidiary)
return fs_encode_subsidiary(context, dc, subsidiary, residual);
primary = primary_create(dc); primary = primary_create(dc);
if (primary == NULL) if (primary == NULL)
return krb5_enomem(context); return krb5_enomem(context);
@@ -197,12 +243,22 @@ get_default_cache(krb5_context context, krb5_dcache *dc, char **residual)
static const char* KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
dcc_get_name(krb5_context context, dcc_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id,
const char **name,
const char **dir,
const char **sub)
{ {
krb5_dcache *dc = DCACHE(id); krb5_dcache *dc = DCACHE(id);
return dc->name;
if (name)
*name = dc->name;
if (dir)
*dir = dc->dir;
if (sub)
*sub = dc->sub;
return 0;
} }
@@ -211,6 +267,12 @@ verify_directory(krb5_context context, const char *path)
{ {
struct stat sb; struct stat sb;
if (!path[0]) {
krb5_set_error_message(context, EINVAL,
N_("DIR empty directory component", ""));
return EINVAL;
}
if (stat(path, &sb) != 0) { if (stat(path, &sb) != 0) {
if (errno == ENOENT) { if (errno == ENOENT) {
/* XXX should use mkdirx_np() */ /* XXX should use mkdirx_np() */
@@ -241,118 +303,176 @@ dcc_release(krb5_context context, krb5_dcache *dc)
{ {
if (dc->fcache) if (dc->fcache)
krb5_cc_close(context, dc->fcache); krb5_cc_close(context, dc->fcache);
if (dc->dir) free(dc->sub);
free(dc->dir); free(dc->dir);
if (dc->name) free(dc->name);
free(dc->name);
memset(dc, 0, sizeof(*dc)); memset(dc, 0, sizeof(*dc));
free(dc); free(dc);
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code
dcc_resolve(krb5_context context, krb5_ccache *id, const char *res) get_default_dir(krb5_context context, char **res)
{ {
char *filename = NULL;
krb5_error_code ret; krb5_error_code ret;
krb5_dcache *dc; char *s;
const char *p;
int asprintf_ret;
p = res; if ((ret = dcc_get_default_name(context, &s)))
do { return ret;
p = strstr(p, ".."); if (strncmp(s, "DIR:", sizeof("DIR:") - 1)) {
if (p && (p == res || ISPATHSEP(p[-1])) && (ISPATHSEP(p[2]) || p[2] == '\0')) { *res = s;
krb5_set_error_message(context, KRB5_CC_FORMAT, s = NULL;
N_("Path contains a .. component", "")); } else if ((*res = strdup(s + sizeof("DIR:") - 1)) == NULL) {
return KRB5_CC_FORMAT; ret = krb5_enomem(context);
}
if (p)
p += 3;
} while (p);
dc = calloc(1, sizeof(*dc));
if (dc == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
} }
free(s);
return ret;
}
/* check for explicit component */ static krb5_error_code KRB5_CALLCONV
if (res[0] == ':') { dcc_resolve(krb5_context context,
char *q; krb5_ccache *id,
const char *res,
dc->dir = strdup(&res[1]); const char *sub)
#ifdef _WIN32 {
q = strrchr(dc->dir, '\\'); krb5_error_code ret;
if (q == NULL) krb5_dcache *dc = NULL;
#endif char *filename = NULL;
q = strrchr(dc->dir, '/'); size_t len;
if (q) { int has_pathsep = 0;
*q++ = '\0';
} else {
krb5_set_error_message(context, KRB5_CC_FORMAT, N_("Cache not an absolute path: %s", ""), dc->dir);
dcc_release(context, dc);
return KRB5_CC_FORMAT;
}
if (!is_filename_cacheish(q)) {
krb5_set_error_message(context, KRB5_CC_FORMAT,
N_("Name %s is not a cache (doesn't start with tkt)", ""), q);
dcc_release(context, dc);
return KRB5_CC_FORMAT;
}
ret = verify_directory(context, dc->dir);
if (ret) {
dcc_release(context, dc);
return ret;
}
dc->name = strdup(res);
if (dc->name == NULL) {
dcc_release(context, dc);
return krb5_enomem(context);
}
if (sub) {
/*
* Here `res' has the directory name (or, if NULL, refers to the
* default DIR cccol), and `sub' has the "subsidiary" name, to which
* we'll prefix "tkt." (though we will insist only on "tkt" later).
*/
if ((dc = calloc(1, sizeof(*dc))) == NULL ||
asprintf(&dc->sub, "tkt.%s", sub) == -1 || dc->sub == NULL) {
free(dc);
return krb5_enomem(context);
}
if (res && res[0] && (dc->dir = strdup(res)) == NULL) {
free(dc->sub);
free(dc);
return krb5_enomem(context);
} else if ((!res || !res[0]) && (ret = get_default_dir(context, &dc->dir))) {
free(dc->sub);
free(dc);
return ret;
}
} else { } else {
char *residual; const char *p;
size_t len; int is_drive_letter_colon = 0;
dc->dir = strdup(res); /*
if (dc->dir == NULL) { * Here `res' has whatever string followed "DIR:", and we need to parse
dcc_release(context, dc); * it into `dc->dir' and `dc->sub'.
return krb5_enomem(context); *
} * Conventions we support for DIR cache naming:
*
* - DIR:path:NAME ---> FILE:path/tktNAME
* - DIR::path/tktNAME ---> FILE:path/tktNAME
* - DIR::NAME ---> FILE:${default_DIR_cccol_path}/tktNAME
* \-> FILE:/tmp/krb5cc_${uid}_dir/tktNAME
* - DIR:path ---> FILE:path/$(cat primary) or FILE:path/tkt
*
*/
len = strlen(dc->dir); if (*res == '\0' || (res[0] == ':' && res[1] == '\0')) {
/* XXX Why not? */
krb5_set_error_message(context, KRB5_CC_FORMAT,
N_("\"DIR:\" is not a valid ccache name", ""));
return KRB5_CC_FORMAT;
}
if (ISPATHSEP(dc->dir[len - 1])) #ifdef WIN32
dc->dir[len - 1] = '\0'; has_pathsep = strchr(res, '\\') != NULL;
#endif
has_pathsep |= strchr(res, '/') != NULL;
ret = verify_directory(context, dc->dir); if ((dc = calloc(1, sizeof(*dc))) == NULL)
if (ret) { return krb5_enomem(context);
dcc_release(context, dc);
return ret;
}
ret = get_default_cache(context, dc, &residual); p = strrchr(res, ':');
if (ret) { #ifdef WIN32
dcc_release(context, dc); is_drive_letter_colon =
return ret; p && ((res[0] == ':' && res[1] != ':' && p - res == 2) ||
} (res[0] != ':' && p - res == 1));
asprintf_ret = asprintf(&dc->name, ":%s/%s", dc->dir, residual); #endif
free(residual);
if (asprintf_ret == -1 || dc->name == NULL) { if (res[0] != ':' && p && !is_drive_letter_colon) {
dc->name = NULL; /* DIR:path:NAME */
dcc_release(context, dc); if ((dc->dir = strndup(res, (p - res))) == NULL ||
return krb5_enomem(context); asprintf(&dc->sub, "tkt.%s", p + 1) < 0 || dc->sub == NULL) {
} dcc_release(context, dc);
return krb5_enomem(context);
}
} else if (res[0] == ':' && has_pathsep) {
char *q;
/* DIR::path/tktNAME (the "tkt" must be there; we'll check) */
if ((dc->dir = strdup(&res[1])) == NULL) {
dcc_release(context, dc);
return krb5_enomem(context);
}
#ifdef _WIN32
q = strrchr(dc->dir, '\\');
if (q == NULL || ((p = strrchr(dc->dir, '/')) && q < p))
#endif
q = strrchr(dc->dir, '/');
*q++ = '\0';
if ((dc->sub = strdup(q)) == NULL) {
dcc_release(context, dc);
return krb5_enomem(context);
}
} else if (res[0] == ':') {
/* DIR::NAME -- no path component separators in NAME */
if ((ret = get_default_dir(context, &dc->dir))) {
dcc_release(context, dc);
return ret;
}
if (asprintf(&dc->sub, "tkt.%s", res + 1) < 0 || dc->sub == NULL) {
dcc_release(context, dc);
return krb5_enomem(context);
}
} else {
/* DIR:path */
if ((dc->dir = strdup(res)) == NULL) {
dcc_release(context, dc);
return krb5_enomem(context);
}
if ((ret = get_default_cache(context, dc, NULL, &dc->sub))) {
dcc_release(context, dc);
return ret;
}
}
} }
asprintf_ret = asprintf(&filename, "FILE%s", dc->name); /* Strip off extra slashes on the end */
if (asprintf_ret == -1 || filename == NULL) { for (len = strlen(dc->dir);
dcc_release(context, dc); len && ISPATHSEP(dc->dir[len - 1]);
return krb5_enomem(context); len -= len ? 1 : 0)
dc->dir[len - 1] = '\0';
/* If we got here then `dc->dir' and `dc->sub' must both be set */
if ((ret = verify_directory(context, dc->dir))) {
dcc_release(context, dc);
return ret;
}
if (!is_filename_cacheish(dc->sub)) {
krb5_set_error_message(context, KRB5_CC_FORMAT,
N_("Name %s is not a cache "
"(doesn't start with tkt)", ""), dc->sub);
dcc_release(context, dc);
return KRB5_CC_FORMAT;
}
if (asprintf(&dc->name, ":%s/%s", dc->dir, dc->sub) == -1 ||
dc->name == NULL ||
asprintf(&filename, "FILE%s", dc->name) == -1 || filename == NULL) {
dcc_release(context, dc);
return krb5_enomem(context);
} }
ret = krb5_cc_resolve(context, filename, &dc->fcache); ret = krb5_cc_resolve(context, filename, &dc->fcache);
@@ -362,86 +482,36 @@ dcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
return ret; return ret;
} }
dc->default_candidate = 1;
(*id)->data.data = dc; (*id)->data.data = dc;
(*id)->data.length = sizeof(*dc); (*id)->data.length = sizeof(*dc);
return 0; return 0;
} }
static char *
copy_default_dcc_cache(krb5_context context)
{
const char *defname;
krb5_error_code ret;
char *name = NULL;
size_t len;
len = strlen(krb5_dcc_ops.prefix);
defname = krb5_cc_default_name(context);
if (defname == NULL ||
strncmp(defname, krb5_dcc_ops.prefix, len) != 0 ||
defname[len] != ':')
{
ret = dcc_get_default_name(context, &name);
if (ret)
return NULL;
return name;
} else {
return strdup(&defname[len + 1]);
}
}
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
dcc_gen_new(krb5_context context, krb5_ccache *id) dcc_gen_new(krb5_context context, krb5_ccache *id)
{ {
krb5_error_code ret; krb5_error_code ret;
char *def_dir = NULL;
char *name = NULL; char *name = NULL;
krb5_dcache *dc; int fd = -1;
int fd;
size_t len;
int asprintf_ret;
name = copy_default_dcc_cache(context); ret = get_default_dir(context, &def_dir);
if (name == NULL) { if (ret == 0)
krb5_set_error_message(context, KRB5_CC_FORMAT, ret = verify_directory(context, def_dir);
N_("Can't generate DIR caches unless its the default type", "")); if (ret == 0 &&
return KRB5_CC_FORMAT; (asprintf(&name, "DIR::%s/tktXXXXXX", def_dir) == -1 || name == NULL))
} ret = krb5_enomem(context);
if (ret == 0 && (fd = mkstemp(name + sizeof("DIR::") - 1)) == -1)
ret = errno;
if (ret == 0)
ret = dcc_resolve(context, id, name + sizeof("DIR:") - 1, NULL);
len = strlen(krb5_dcc_ops.prefix); free(def_dir);
if (strncmp(name, krb5_dcc_ops.prefix, len) == 0 && name[len] == ':')
++len;
else
len = 0;
ret = dcc_resolve(context, id, name + len);
free(name); free(name);
name = NULL; if (fd != -1)
if (ret) close(fd);
return ret; return ret;
dc = DCACHE((*id));
asprintf_ret = asprintf(&name, ":%s/tktXXXXXX", dc->dir);
if (asprintf_ret == -1 || name == NULL) {
dcc_close(context, *id);
return krb5_enomem(context);
}
fd = mkstemp(&name[1]);
if (fd < 0) {
dcc_close(context, *id);
return krb5_enomem(context);
}
close(fd);
free(dc->name);
dc->name = name;
return 0;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
@@ -457,6 +527,25 @@ static krb5_error_code KRB5_CALLCONV
dcc_close(krb5_context context, dcc_close(krb5_context context,
krb5_ccache id) krb5_ccache id)
{ {
krb5_dcache *dc = DCACHE(id);
krb5_principal p = NULL;
struct stat st;
char *primary = NULL;
/*
* If there's no default cache, but we're closing one, and the one we're
* closing has been initialized, then make it the default. This makes the
* first cache created the default.
*
* FIXME We should check if `D2FCACHE(dc)' has live credentials.
*/
if (dc->default_candidate && D2FCACHE(dc) &&
krb5_cc_get_principal(context, D2FCACHE(dc), &p) == 0 &&
(primary = primary_create(dc)) &&
(stat(primary, &st) == -1 || !S_ISREG(st.st_mode) || st.st_size == 0))
dcc_set_default(context, id);
krb5_free_principal(context, p);
free(primary);
dcc_release(context, DCACHE(id)); dcc_release(context, DCACHE(id));
return 0; return 0;
} }
@@ -545,39 +634,61 @@ dcc_get_version(krb5_context context,
} }
struct dcache_iter { struct dcache_iter {
int first; char *primary;
krb5_dcache *dc; krb5_dcache *dc;
DIR *d;
unsigned int first:1;
}; };
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
dcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor) dcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
{ {
struct dcache_iter *iter; struct dcache_iter *iter = NULL;
krb5_error_code ret; const char *name = krb5_cc_default_name(context);
char *name; size_t len;
char *p;
*cursor = NULL; *cursor = NULL;
iter = calloc(1, sizeof(*iter));
if (iter == NULL)
return krb5_enomem(context);
iter->first = 1;
name = copy_default_dcc_cache(context); if (strncmp(name, "DIR:", sizeof("DIR:") - 1) != 0) {
if (name == NULL) {
free(iter);
krb5_set_error_message(context, KRB5_CC_FORMAT, krb5_set_error_message(context, KRB5_CC_FORMAT,
N_("Can't generate DIR caches unless its the default type", "")); N_("Can't list DIR caches unless its the default type", ""));
return KRB5_CC_FORMAT; return KRB5_CC_FORMAT;
} }
ret = dcc_resolve(context, NULL, name); if ((iter = calloc(1, sizeof(*iter))) == NULL ||
free(name); (iter->dc = calloc(1, sizeof(iter->dc[0]))) == NULL ||
if (ret) { (iter->dc->dir = strdup(name + sizeof("DIR:") - 1)) == NULL) {
if (iter)
free(iter->dc);
free(iter); free(iter);
return ret; return krb5_enomem(context);
}
iter->first = 1;
p = strrchr(iter->dc->dir, ':');
#ifdef WIN32
if (p == iter->dc->dir + 1)
p = NULL;
#endif
if (p)
*p = '\0';
/* Strip off extra slashes on the end */
for (len = strlen(iter->dc->dir);
len && ISPATHSEP(iter->dc->dir[len - 1]);
len -= len ? 1 : 0) {
iter->dc->dir[len - 1] = '\0';
} }
/* XXX We need to opendir() here */ if ((iter->d = opendir(iter->dc->dir)) == NULL) {
free(iter->dc->dir);
free(iter->dc);
free(iter);
krb5_set_error_message(context, KRB5_CC_FORMAT,
N_("Can't open DIR %s: %s", ""),
iter->dc->dir, strerror(errno));
return KRB5_CC_FORMAT;
}
*cursor = iter; *cursor = iter;
return 0; return 0;
@@ -587,18 +698,49 @@ static krb5_error_code KRB5_CALLCONV
dcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id) dcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
{ {
struct dcache_iter *iter = cursor; struct dcache_iter *iter = cursor;
krb5_error_code ret;
struct stat st;
struct dirent *dentry;
char *p = NULL;
*id = NULL;
if (iter == NULL) if (iter == NULL)
return krb5_einval(context, 2); return krb5_einval(context, 2);
if (!iter->first) { /* Emit primary subsidiary first */
krb5_clear_error_message(context); if (iter->first &&
return KRB5_CC_END; (ret = get_default_cache(context, iter->dc, NULL, &iter->primary)) == 0 &&
is_filename_cacheish(iter->primary)) {
iter->first = 0;
ret = KRB5_CC_END;
if (asprintf(&p, "FILE:%s/%s", iter->dc->dir, iter->primary) > -1 && p != NULL &&
stat(p + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode))
ret = krb5_cc_resolve(context, p, id);
if (p == NULL)
return krb5_enomem(context);
free(p);
if (ret == 0)
return ret;
p = NULL;
} }
/* XXX We need to readdir() here */
iter->first = 0; iter->first = 0;
for (dentry = readdir(iter->d); dentry; dentry = readdir(iter->d)) {
if (!is_filename_cacheish(dentry->d_name) ||
(iter->primary && strcmp(dentry->d_name, iter->primary) == 0))
continue;
p = NULL;
ret = KRB5_CC_END;
if (asprintf(&p, "FILE:%s/%s", iter->dc->dir, dentry->d_name) > -1 &&
p != NULL &&
stat(p + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode))
ret = krb5_cc_resolve(context, p, id);
free(p);
if (p == NULL)
return krb5_enomem(context);
if (ret == 0)
return ret;
}
return KRB5_CC_END; return KRB5_CC_END;
} }
@@ -610,9 +752,10 @@ dcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
if (iter == NULL) if (iter == NULL)
return krb5_einval(context, 2); return krb5_einval(context, 2);
/* XXX We need to closedir() here */ (void) closedir(iter->d);
if (iter->dc) free(iter->dc->dir);
dcc_release(context, iter->dc); free(iter->dc);
free(iter->primary);
free(iter); free(iter);
return 0; return 0;
} }
@@ -622,14 +765,22 @@ dcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
{ {
krb5_dcache *dcfrom = DCACHE(from); krb5_dcache *dcfrom = DCACHE(from);
krb5_dcache *dcto = DCACHE(to); krb5_dcache *dcto = DCACHE(to);
dcfrom->default_candidate = 0;
dcto->default_candidate = 1;
return krb5_cc_move(context, D2FCACHE(dcfrom), D2FCACHE(dcto)); return krb5_cc_move(context, D2FCACHE(dcfrom), D2FCACHE(dcto));
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
dcc_get_default_name(krb5_context context, char **str) dcc_get_default_name(krb5_context context, char **str)
{ {
return _krb5_expand_default_cc_name(context, const char *def_cc_colname =
KRB5_DEFAULT_CCNAME_DIR, krb5_config_get_string_default(context, NULL, KRB5_DEFAULT_CCNAME_DIR,
"libdefaults", "default_cc_collection",
NULL);
/* What if def_cc_colname does not start with DIR:? We tolerate it. */
return _krb5_expand_default_cc_name(context, def_cc_colname,
str); str);
} }
@@ -637,13 +788,10 @@ static krb5_error_code KRB5_CALLCONV
dcc_set_default(krb5_context context, krb5_ccache id) dcc_set_default(krb5_context context, krb5_ccache id)
{ {
krb5_dcache *dc = DCACHE(id); krb5_dcache *dc = DCACHE(id);
const char *name;
name = krb5_cc_get_name(context, D2FCACHE(dc)); if (dc->sub == NULL)
if (name == NULL)
return ENOENT; return ENOENT;
return set_default_cache(context, dc, dc->sub);
return set_default_cache(context, dc, name);
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV

View File

@@ -62,14 +62,23 @@ struct fcc_cursor {
#define FCC_CURSOR(C) ((struct fcc_cursor*)(C)) #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
static const char* KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
fcc_get_name(krb5_context context, fcc_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id,
const char **name,
const char **colname,
const char **sub)
{ {
if (FCACHE(id) == NULL) if (FCACHE(id) == NULL)
return NULL; return KRB5_CC_NOTFOUND;
return FILENAME(id); if (name)
*name = FILENAME(id);
if (colname)
*colname = FILENAME(id);
if (sub)
*sub = NULL;
return 0;
} }
KRB5_LIB_FUNCTION int KRB5_LIB_CALL KRB5_LIB_FUNCTION int KRB5_LIB_CALL
@@ -178,15 +187,32 @@ static krb5_error_code KRB5_CALLCONV
fcc_lock(krb5_context context, krb5_ccache id, fcc_lock(krb5_context context, krb5_ccache id,
int fd, krb5_boolean exclusive) int fd, krb5_boolean exclusive)
{ {
krb5_error_code ret;
const char *name;
if (exclusive == FALSE) if (exclusive == FALSE)
return 0; return 0;
return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id)); ret = fcc_get_name(context, id, &name, NULL, NULL);
if (ret == 0)
ret = _krb5_xlock(context, fd, exclusive, name);
return ret;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
fcc_resolve(krb5_context context, krb5_ccache *id, const char *res) fcc_resolve(krb5_context context,
krb5_ccache *id,
const char *res,
const char *sub)
{ {
krb5_fcache *f; krb5_fcache *f;
if (sub && *sub) {
krb5_set_error_message(context, KRB5_CC_NOSUPP,
N_("FILE ccache type is not a collection "
"type", ""));
return KRB5_CC_NOSUPP;
}
f = calloc(1, sizeof(*f)); f = calloc(1, sizeof(*f));
if(f == NULL) { if(f == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM, krb5_set_error_message(context, KRB5_CC_NOMEM,
@@ -204,6 +230,7 @@ fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
f->version = 0; f->version = 0;
(*id)->data.data = f; (*id)->data.data = f;
(*id)->data.length = sizeof(*f); (*id)->data.length = sizeof(*f);
return 0; return 0;
} }
@@ -647,11 +674,8 @@ fcc_destroy(krb5_context context,
if (FCACHE(id) == NULL) if (FCACHE(id) == NULL)
return krb5_einval(context, 2); return krb5_einval(context, 2);
if (TMPFILENAME(id)) { if (TMPFILENAME(id))
(void) _krb5_erase_file(context, TMPFILENAME(id)); (void) _krb5_erase_file(context, TMPFILENAME(id));
free(TMPFILENAME(id));
TMPFILENAME(id) = NULL;
}
return _krb5_erase_file(context, FILENAME(id)); return _krb5_erase_file(context, FILENAME(id));
} }

View File

@@ -136,7 +136,9 @@ krb5_kcm_storage_request(krb5_context context,
} }
static krb5_error_code static krb5_error_code
kcm_alloc(krb5_context context, const char *name, krb5_ccache *id) kcm_alloc(krb5_context context,
const char *name,
krb5_ccache *id)
{ {
krb5_kcmcache *k; krb5_kcmcache *k;
@@ -229,17 +231,42 @@ kcm_free(krb5_context context, krb5_ccache *id)
} }
} }
static const char * static krb5_error_code KRB5_CALLCONV
kcm_get_name(krb5_context context, kcm_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id,
const char **name,
const char **col,
const char **sub)
{ {
return CACHENAME(id); /*
* TODO:
*
* - name should be <IPC-name>:<cache-name>
* - col should be <IPC-name>
* - sub should be <cache-name>
*/
if (name)
*name = CACHENAME(id);
if (col)
*col = NULL;
if (sub)
*sub = CACHENAME(id);
return 0;
} }
static krb5_error_code static krb5_error_code
kcm_resolve(krb5_context context, krb5_ccache *id, const char *res) kcm_resolve(krb5_context context,
krb5_ccache *id,
const char *res,
const char *sub)
{ {
return kcm_alloc(context, res, id); /*
* For now, for KCM the `res' is the `sub'.
*
* TODO: We should use `res' as the IPC name instead of the one currently
* hard-coded in `kcm_ipc_name'.
*/
return kcm_alloc(context, sub && *sub ? sub : res, id);
} }
/* /*

View File

@@ -491,13 +491,16 @@ typedef struct krb5_creds {
typedef struct krb5_cc_cache_cursor_data *krb5_cc_cache_cursor; typedef struct krb5_cc_cache_cursor_data *krb5_cc_cache_cursor;
#define KRB5_CC_OPS_VERSION 3 #define KRB5_CC_OPS_VERSION 4
typedef struct krb5_cc_ops { typedef struct krb5_cc_ops {
int version; int version;
const char *prefix; const char *prefix;
const char* (KRB5_CALLCONV * get_name)(krb5_context, krb5_ccache); krb5_error_code (KRB5_CALLCONV * get_name)(krb5_context, krb5_ccache,
krb5_error_code (KRB5_CALLCONV * resolve)(krb5_context, krb5_ccache *, const char *); const char **, const char **,
const char **);
krb5_error_code (KRB5_CALLCONV * resolve)(krb5_context, krb5_ccache *, const char *,
const char *);
krb5_error_code (KRB5_CALLCONV * gen_new)(krb5_context, krb5_ccache *); krb5_error_code (KRB5_CALLCONV * gen_new)(krb5_context, krb5_ccache *);
krb5_error_code (KRB5_CALLCONV * init)(krb5_context, krb5_ccache, krb5_principal); krb5_error_code (KRB5_CALLCONV * init)(krb5_context, krb5_ccache, krb5_principal);
krb5_error_code (KRB5_CALLCONV * destroy)(krb5_context, krb5_ccache); krb5_error_code (KRB5_CALLCONV * destroy)(krb5_context, krb5_ccache);

View File

@@ -207,6 +207,8 @@ typedef union _krb5_krcache_and_princ_id {
*/ */
typedef struct _krb5_krcache { typedef struct _krb5_krcache {
char *krc_name; /* Name for this credentials cache */ char *krc_name; /* Name for this credentials cache */
char *krc_collection;
char *krc_subsidiary;
krb5_timestamp krc_changetime; /* update time, does not decrease (mutable) */ krb5_timestamp krc_changetime; /* update time, does not decrease (mutable) */
krb5_krcache_and_princ_id krc_id; /* cache and principal IDs (mutable) */ krb5_krcache_and_princ_id krc_id; /* cache and principal IDs (mutable) */
#define krc_cache_and_principal_id krc_id.krcu_cache_and_princ_id #define krc_cache_and_principal_id krc_id.krcu_cache_and_princ_id
@@ -384,8 +386,6 @@ parse_residual(krb5_context context,
collection_name = strdup(residual); collection_name = strdup(residual);
if (collection_name == NULL) if (collection_name == NULL)
goto nomem; goto nomem;
subsidiary_name = NULL;
} else { } else {
collection_name = strndup(residual, sep - residual); collection_name = strndup(residual, sep - residual);
if (collection_name == NULL) if (collection_name == NULL)
@@ -918,14 +918,18 @@ initialize_internal(krb5_context context,
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ) krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
{ {
krb5_krcache *data = KRCACHE(id);
krb5_error_code ret; krb5_error_code ret;
if (data == NULL)
return krb5_einval(context, 2);
if (princ == NULL) if (princ == NULL)
return KRB5_CC_BADNAME; return KRB5_CC_BADNAME;
ret = initialize_internal(context, id, princ); ret = initialize_internal(context, id, princ);
if (ret == 0) if (ret == 0)
update_change_time(context, 0, KRCACHE(id)); update_change_time(context, 0, data);
return ret; return ret;
} }
@@ -939,6 +943,8 @@ krcc_close(krb5_context context, krb5_ccache id)
if (data == NULL) if (data == NULL)
return krb5_einval(context, 2); return krb5_einval(context, 2);
free(data->krc_subsidiary);
free(data->krc_collection);
free(data->krc_name); free(data->krc_name);
krb5_data_free(&id->data); krb5_data_free(&id->data);
@@ -1048,16 +1054,26 @@ make_cache(krb5_context context,
/* Create a keyring ccache handle for the given residual string. */ /* Create a keyring ccache handle for the given residual string. */
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual) krcc_resolve(krb5_context context,
krb5_ccache *id,
const char *residual,
const char *sub)
{ {
krb5_error_code ret; krb5_error_code ret;
key_serial_t collection_id, cache_id; key_serial_t collection_id, cache_id;
char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL; char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
ret = parse_residual(context, residual, &anchor_name, &collection_name, ret = parse_residual(context, residual, &anchor_name, &collection_name,
&subsidiary_name); &subsidiary_name);
if (ret) if (ret)
goto cleanup; goto cleanup;
if (sub) {
free(subsidiary_name);
if ((subsidiary_name = strdup(sub)) == NULL) {
ret = krb5_enomem(context);
goto cleanup;
}
}
ret = get_collection(context, anchor_name, collection_name, &collection_id); ret = get_collection(context, anchor_name, collection_name, &collection_id);
if (ret) if (ret)
@@ -1243,8 +1259,16 @@ alloc_cache(krb5_context context,
ret = make_subsidiary_residual(context, anchor_name, collection_name, ret = make_subsidiary_residual(context, anchor_name, collection_name,
subsidiary_name, &data->krc_name); subsidiary_name, &data->krc_name);
if (ret) { if (ret ||
(data->krc_collection = strdup(collection_name)) == NULL ||
(data->krc_subsidiary = strdup(subsidiary_name)) == NULL) {
if (data) {
free(data->krc_collection);
free(data->krc_name);
}
free(data); free(data);
if (ret == 0)
ret = krb5_enomem(context);
return ret; return ret;
} }
@@ -1321,10 +1345,25 @@ cleanup:
} }
/* Return an alias to the residual string of the cache. */ /* Return an alias to the residual string of the cache. */
static const char *KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
krcc_get_name(krb5_context context, krb5_ccache id) krcc_get_name(krb5_context context,
krb5_ccache id,
const char **name,
const char **collection_name,
const char **subsidiary_name)
{ {
return KRCACHE(id)->krc_name; krb5_krcache *data = KRCACHE(id);
if (data == NULL)
return krb5_einval(context, 2);
if (name)
*name = data->krc_name;
if (collection_name)
*collection_name = data->krc_collection;
if (subsidiary_name)
*subsidiary_name = data->krc_subsidiary;
return 0;
} }
/* Retrieve a copy of the default principal, if the cache is initialized. */ /* Retrieve a copy of the default principal, if the cache is initialized. */
@@ -1641,6 +1680,9 @@ krcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat offset)
key_serial_t cache_id; key_serial_t cache_id;
krb5_error_code ret; krb5_error_code ret;
if (data == NULL)
return krb5_einval(context, 2);
heim_base_exchange_32(&cache_id, data->krc_cache_id); heim_base_exchange_32(&cache_id, data->krc_cache_id);
ret = save_time_offsets(context, cache_id, (int32_t)offset, 0); ret = save_time_offsets(context, cache_id, (int32_t)offset, 0);

View File

@@ -87,6 +87,8 @@ EXPORTS
krb5_cc_copy_creds ;! krb5_cc_copy_creds ;!
krb5_cc_copy_match_f krb5_cc_copy_match_f
krb5_cc_default krb5_cc_default
krb5_cc_default_sub
krb5_cc_default_for
krb5_cc_default_name krb5_cc_default_name
krb5_cc_destroy krb5_cc_destroy
krb5_cc_end_seq_get krb5_cc_end_seq_get
@@ -111,6 +113,8 @@ EXPORTS
krb5_cc_register krb5_cc_register
krb5_cc_remove_cred krb5_cc_remove_cred
krb5_cc_resolve krb5_cc_resolve
krb5_cc_resolve_sub
krb5_cc_resolve_for
krb5_cc_retrieve_cred krb5_cc_retrieve_cred
krb5_cc_set_config krb5_cc_set_config
krb5_cc_set_default_name krb5_cc_set_default_name

View File

@@ -38,7 +38,8 @@
typedef struct krb5_mcache { typedef struct krb5_mcache {
char *name; char *name;
unsigned int refcnt; unsigned int refcnt;
int dead; unsigned int anonymous:1;
unsigned int dead:1;
krb5_principal primary_principal; krb5_principal primary_principal;
struct link { struct link {
krb5_creds cred; krb5_creds cred;
@@ -57,42 +58,89 @@ static struct krb5_mcache *mcc_head;
#define MISDEAD(X) ((X)->dead) #define MISDEAD(X) ((X)->dead)
static const char* KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
mcc_get_name(krb5_context context, mcc_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id,
const char **name,
const char **col,
const char **sub)
{ {
return MCACHE(id)->name; if (name)
*name = MCACHE(id)->name;
if (col)
*col = NULL;
if (sub)
*sub = MCACHE(id)->name;
return 0;
} }
static krb5_mcache * KRB5_CALLCONV static krb5_error_code
mcc_alloc(const char *name) mcc_alloc(krb5_context context, const char *name, krb5_mcache **out)
{ {
krb5_mcache *m, *m_c; krb5_mcache *m, *m_c;
size_t counter = 0;
int ret = 0; int ret = 0;
*out = NULL;
ALLOC(m, 1); ALLOC(m, 1);
if(m == NULL) if(m == NULL)
return NULL; return krb5_enomem(context);
again:
if (counter > 3) {
free(m->name);
free(m);
return EAGAIN; /* XXX */
}
if(name == NULL) if(name == NULL)
ret = asprintf(&m->name, "%p", m); ret = asprintf(&m->name, "u%p-%llu", m, (unsigned long long)counter);
else else
m->name = strdup(name); m->name = strdup(name);
if(ret < 0 || m->name == NULL) { if(ret < 0 || m->name == NULL) {
free(m); free(m);
return NULL; return krb5_enomem(context);
} }
if (strcmp(m->name, "anonymous") == 0) {
m->anonymous = 1;
m->dead = 0;
m->refcnt = 1;
m->primary_principal = NULL;
m->creds = NULL;
m->mtime = time(NULL);
m->kdc_offset = 0;
m->next = NULL;
*out = m;
return 0;
}
/* check for dups first */ /* check for dups first */
HEIMDAL_MUTEX_lock(&mcc_mutex); HEIMDAL_MUTEX_lock(&mcc_mutex);
for (m_c = mcc_head; m_c != NULL; m_c = m_c->next) for (m_c = mcc_head; m_c != NULL; m_c = m_c->next)
if (strcmp(m->name, m_c->name) == 0) if (strcmp(m->name, m_c->name) == 0)
break; break;
if (m_c) { if (m_c) {
free(m->name); free(m->name);
free(m); free(m);
HEIMDAL_MUTEX_unlock(&mcc_mutex); if (name) {
return NULL; /* We raced with another thread to create this cache */
m = m_c;
HEIMDAL_MUTEX_lock(&(m->mutex));
m->refcnt++;
HEIMDAL_MUTEX_unlock(&(m->mutex));
} else {
/* How likely are we to conflict on new_unique anyways?? */
counter++;
free(m->name);
m->name = NULL;
HEIMDAL_MUTEX_unlock(&mcc_mutex);
goto again;
}
HEIMDAL_MUTEX_unlock(&mcc_mutex);
*out = m;
return 0;
} }
m->anonymous = 0;
m->dead = 0; m->dead = 0;
m->refcnt = 1; m->refcnt = 1;
m->primary_principal = NULL; m->primary_principal = NULL;
@@ -103,35 +151,21 @@ mcc_alloc(const char *name)
HEIMDAL_MUTEX_init(&(m->mutex)); HEIMDAL_MUTEX_init(&(m->mutex));
mcc_head = m; mcc_head = m;
HEIMDAL_MUTEX_unlock(&mcc_mutex); HEIMDAL_MUTEX_unlock(&mcc_mutex);
return m; *out = m;
return 0;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
mcc_resolve(krb5_context context, krb5_ccache *id, const char *res) mcc_resolve(krb5_context context,
krb5_ccache *id,
const char *res,
const char *sub)
{ {
krb5_error_code ret;
krb5_mcache *m; krb5_mcache *m;
HEIMDAL_MUTEX_lock(&mcc_mutex); if ((ret = mcc_alloc(context, sub && *sub ? sub : res, &m)))
for (m = mcc_head; m != NULL; m = m->next) return ret;
if (strcmp(m->name, res) == 0)
break;
HEIMDAL_MUTEX_unlock(&mcc_mutex);
if (m != NULL) {
HEIMDAL_MUTEX_lock(&(m->mutex));
m->refcnt++;
HEIMDAL_MUTEX_unlock(&(m->mutex));
(*id)->data.data = m;
(*id)->data.length = sizeof(*m);
return 0;
}
m = mcc_alloc(res);
if (m == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
(*id)->data.data = m; (*id)->data.data = m;
(*id)->data.length = sizeof(*m); (*id)->data.length = sizeof(*m);
@@ -143,15 +177,11 @@ mcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
mcc_gen_new(krb5_context context, krb5_ccache *id) mcc_gen_new(krb5_context context, krb5_ccache *id)
{ {
krb5_error_code ret;
krb5_mcache *m; krb5_mcache *m;
m = mcc_alloc(NULL); if ((ret = mcc_alloc(context, NULL, &m)))
return ret;
if (m == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", ""));
return KRB5_CC_NOMEM;
}
(*id)->data.data = m; (*id)->data.data = m;
(*id)->data.length = sizeof(*m); (*id)->data.length = sizeof(*m);
@@ -221,7 +251,7 @@ mcc_close_internal(krb5_mcache *m)
return 0; return 0;
} }
if (MISDEAD(m)) { if (MISDEAD(m)) {
free (m->name); free(m->name);
HEIMDAL_MUTEX_unlock(&(m->mutex)); HEIMDAL_MUTEX_unlock(&(m->mutex));
return 1; return 1;
} }
@@ -248,6 +278,18 @@ mcc_destroy(krb5_context context,
{ {
krb5_mcache **n, *m = MCACHE(id); krb5_mcache **n, *m = MCACHE(id);
if (m->anonymous) {
HEIMDAL_MUTEX_lock(&(m->mutex));
if (m->refcnt == 0) {
HEIMDAL_MUTEX_unlock(&(m->mutex));
krb5_abortx(context, "mcc_destroy: refcnt already 0");
}
if (!MISDEAD(m))
mcc_destroy_internal(context, m);
HEIMDAL_MUTEX_unlock(&(m->mutex));
return 0;
}
HEIMDAL_MUTEX_lock(&mcc_mutex); HEIMDAL_MUTEX_lock(&mcc_mutex);
HEIMDAL_MUTEX_lock(&(m->mutex)); HEIMDAL_MUTEX_lock(&(m->mutex));
if (m->refcnt == 0) if (m->refcnt == 0)
@@ -290,12 +332,8 @@ mcc_store_cred(krb5_context context,
} }
l = malloc (sizeof(*l)); l = malloc (sizeof(*l));
if (l == NULL) { if (l == NULL)
krb5_set_error_message(context, KRB5_CC_NOMEM, return krb5_enomem(context);
N_("malloc: out of memory", ""));
HEIMDAL_MUTEX_unlock(&(m->mutex));
return KRB5_CC_NOMEM;
}
l->next = m->creds; l->next = m->creds;
m->creds = l; m->creds = l;
memset (&l->cred, 0, sizeof(l->cred)); memset (&l->cred, 0, sizeof(l->cred));

View File

@@ -40,6 +40,7 @@
typedef struct krb5_scache { typedef struct krb5_scache {
char *name; char *name;
char *file; char *file;
char *sub;
sqlite3 *db; sqlite3 *db;
sqlite_uint64 cid; sqlite_uint64 cid;
@@ -66,7 +67,7 @@ typedef struct krb5_scache {
#else #else
#define KRB5_SCACHE_DB "/tmp/krb5scc_%{uid}" #define KRB5_SCACHE_DB "/tmp/krb5scc_%{uid}"
#endif #endif
#define KRB5_SCACHE_NAME "SCC:" SCACHE_DEF_NAME ":" KRB5_SCACHE_DB #define KRB5_SCACHE_NAME "SCC:" KRB5_SCACHE_DB ":" SCACHE_DEF_NAME
#define SCACHE_INVALID_CID ((sqlite_uint64)-1) #define SCACHE_INVALID_CID ((sqlite_uint64)-1)
@@ -103,7 +104,8 @@ typedef struct krb5_scache {
#define SQL_UCACHE_PRINCIPAL "UPDATE caches SET principal=? WHERE OID=?" #define SQL_UCACHE_PRINCIPAL "UPDATE caches SET principal=? WHERE OID=?"
#define SQL_DCACHE "DELETE FROM caches WHERE OID=?" #define SQL_DCACHE "DELETE FROM caches WHERE OID=?"
#define SQL_SCACHE "SELECT principal,name FROM caches WHERE OID=?" #define SQL_SCACHE "SELECT principal,name FROM caches WHERE OID=?"
#define SQL_SCACHE_NAME "SELECT oid FROM caches WHERE NAME=?" #define SQL_SCACHE_NAME "SELECT oid FROM caches WHERE NAME=? OR " \
"(PRINCIPAL IS NOT NULL AND PRINCIPAL=?)"
#define SQL_CCREDS "" \ #define SQL_CCREDS "" \
"CREATE TABLE credentials (" \ "CREATE TABLE credentials (" \
@@ -153,8 +155,12 @@ free_krb5(void *str)
static void static void
scc_free(krb5_scache *s) scc_free(krb5_scache *s)
{ {
if (!s)
return;
if (s->file) if (s->file)
free(s->file); free(s->file);
if (s->sub)
free(s->sub);
if (s->name) if (s->name)
free(s->name); free(s->name);
@@ -225,22 +231,53 @@ exec_stmt(krb5_context context, sqlite3 *db, const char *str,
} }
static krb5_error_code static krb5_error_code
default_db(krb5_context context, sqlite3 **db) default_db(krb5_context context, const char *name, sqlite3 **db, char **file)
{ {
char *name; char *s = NULL;
char *f = NULL;
int ret; int ret;
ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &name); if (file)
if (ret) *file = NULL;
return ret;
ret = sqlite3_open_v2(name, db, SQLITE_OPEN_READWRITE, NULL); if (name == NULL) {
free(name); if ((name = krb5_cc_default_name(context))) {
if (strncmp(name, "SCC:", sizeof("SCC:") - 1) == 0)
name += sizeof("SCC:") - 1;
}
if (name == NULL) {
ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s);
if (ret)
return ret;
name = s;
}
}
if (strncmp(name, "SCC:", sizeof("SCC:") - 1) == 0)
name += sizeof("SCC:") - 1;
if ((f = strdup(name)) == NULL) {
free(s);
return krb5_enomem(context);
}
free(s);
if ((s = strrchr(f, ':')))
*s = '\0';
ret = sqlite3_open_v2(f, db, SQLITE_OPEN_READWRITE, NULL);
if (ret != SQLITE_OK) { if (ret != SQLITE_OK) {
sqlite3_close_v2(*db);
krb5_clear_error_message(context); krb5_clear_error_message(context);
free(f);
return ENOENT; return ENOENT;
} }
if (file)
*file = f;
else
free(f);
#ifdef TRACEME #ifdef TRACEME
sqlite3_trace(*db, trace, NULL); sqlite3_trace(*db, trace, NULL);
#endif #endif
@@ -249,14 +286,14 @@ default_db(krb5_context context, sqlite3 **db)
} }
static krb5_error_code static krb5_error_code
get_def_name(krb5_context context, char **str) get_def_name(krb5_context context, char *filein, char **str, char **file)
{ {
krb5_error_code ret; krb5_error_code ret;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
const char *name; const char *name;
sqlite3 *db; sqlite3 *db;
ret = default_db(context, &db); ret = default_db(context, filein, &db, file);
if (ret) if (ret)
return ret; return ret;
@@ -294,10 +331,15 @@ out:
static krb5_scache * KRB5_CALLCONV static krb5_scache * KRB5_CALLCONV
scc_alloc(krb5_context context, const char *name) scc_alloc(krb5_context context,
const char *name,
const char *sub,
int new_unique)
{ {
krb5_error_code ret; krb5_error_code ret = 0;
krb5_scache *s; krb5_scache *s;
char *freeme = NULL;
char *subsidiary;
ALLOC(s, 1); ALLOC(s, 1);
if(s == NULL) if(s == NULL)
@@ -305,41 +347,87 @@ scc_alloc(krb5_context context, const char *name)
s->cid = SCACHE_INVALID_CID; s->cid = SCACHE_INVALID_CID;
if (name) { if (name && *name && sub && *sub) {
char *file; if ((s->sub = strdup(sub)) == NULL ||
(s->file = strdup(name)) == NULL) {
if (*name == '\0') { free(s->file);
ret = get_def_name(context, &s->name); free(s);
if (ret) (void) krb5_enomem(context);
s->name = strdup(SCACHE_DEF_NAME); return NULL;
} else }
s->name = strdup(name);
file = strrchr(s->name, ':');
if (file) {
*file++ = '\0';
s->file = strdup(file);
ret = 0;
} else {
ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s->file);
}
} else { } else {
_krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s->file); s->sub = NULL;
ret = asprintf(&s->name, "unique-%p", s); s->file = NULL;
s->name = NULL;
if (name == NULL)
name = krb5_cc_default_name(context);
if (name == NULL) {
ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB,
&freeme);
if (ret) {
free(s);
return NULL;
}
name = freeme;
}
if (strncmp(name, "SCC:", sizeof("SCC:") - 1) == 0)
name += sizeof("SCC:") - 1;
if ((s->file = strdup(name)) == NULL) {
(void) krb5_enomem(context);
scc_free(s);
free(freeme);
return NULL;
}
if ((subsidiary = strrchr(s->file, ':'))) {
#ifdef WIN32
if (subsidiary == &s->file + 1)
subsidiary = NULL;
else
#endif
*(subsidiary++) = '\0';
}
if (new_unique) {
ret = asprintf(&s->sub, "unique-%p", s) < 0 || s->sub == NULL ?
krb5_enomem(context) : 0;
} else if (subsidiary == NULL || *subsidiary == '\0') {
ret = get_def_name(context, s->file, &s->sub, NULL);
if (ret) {
if ((s->sub = strdup(SCACHE_DEF_NAME)) == NULL)
ret = krb5_enomem(context);
else
ret = 0;
}
} else if ((s->sub = strdup(subsidiary)) == NULL) {
ret = krb5_enomem(context);
}
} }
if (ret < 0 || s->file == NULL || s->name == NULL) {
if (ret == 0 && s->file && s->sub &&
(asprintf(&s->name, "%s:%s", s->file, s->sub) < 0 || s->name == NULL))
ret = krb5_enomem(context);
if (ret || s->file == NULL || s->sub == NULL || s->name == NULL) {
scc_free(s); scc_free(s);
free(freeme);
return NULL; return NULL;
} }
return s; return s;
} }
static krb5_error_code static krb5_error_code
open_database(krb5_context context, krb5_scache *s, int flags) open_database(krb5_context context, krb5_scache *s, int flags)
{ {
struct stat st;
int ret; int ret;
if (!(flags & SQLITE_OPEN_CREATE) && stat(s->file, &st) == 0 &&
st.st_size == 0)
return ENOENT;
ret = sqlite3_open_v2(s->file, &s->db, SQLITE_OPEN_READWRITE|flags, NULL); ret = sqlite3_open_v2(s->file, &s->db, SQLITE_OPEN_READWRITE|flags, NULL);
if (ret) { if (ret) {
if (s->db) { if (s->db) {
@@ -361,7 +449,7 @@ create_cache(krb5_context context, krb5_scache *s)
{ {
int ret; int ret;
sqlite3_bind_text(s->icache, 1, s->name, -1, NULL); sqlite3_bind_text(s->icache, 1, s->sub, -1, NULL);
do { do {
ret = sqlite3_step(s->icache); ret = sqlite3_step(s->icache);
} while (ret == SQLITE_ROW); } while (ret == SQLITE_ROW);
@@ -477,20 +565,32 @@ bind_principal(krb5_context context,
* *
*/ */
static const char* KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
scc_get_name(krb5_context context, scc_get_name(krb5_context context,
krb5_ccache id) krb5_ccache id,
const char **name,
const char **file,
const char **sub)
{ {
return SCACHE(id)->name; if (name)
*name = SCACHE(id)->name;
if (file)
*file = SCACHE(id)->file;
if (sub)
*sub = SCACHE(id)->sub;
return 0;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
scc_resolve(krb5_context context, krb5_ccache *id, const char *res) scc_resolve(krb5_context context,
krb5_ccache *id,
const char *res,
const char *sub)
{ {
krb5_error_code ret;
krb5_scache *s; krb5_scache *s;
int ret;
s = scc_alloc(context, res); s = scc_alloc(context, res, sub, 0);
if (s == NULL) { if (s == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM, krb5_set_error_message(context, KRB5_CC_NOMEM,
N_("malloc: out of memory", "")); N_("malloc: out of memory", ""));
@@ -503,12 +603,12 @@ scc_resolve(krb5_context context, krb5_ccache *id, const char *res)
return ret; return ret;
} }
ret = sqlite3_bind_text(s->scache_name, 1, s->name, -1, NULL); ret = sqlite3_bind_text(s->scache_name, 1, s->sub, -1, NULL);
if (ret != SQLITE_OK) { if (ret != SQLITE_OK) {
krb5_set_error_message(context, ENOMEM, krb5_set_error_message(context, ENOMEM,
"bind name: %s", sqlite3_errmsg(s->db)); "bind principal: %s", sqlite3_errmsg(s->db));
scc_free(s); scc_free(s);
return ENOMEM; return ENOMEM;
} }
if (sqlite3_step(s->scache_name) == SQLITE_ROW) { if (sqlite3_step(s->scache_name) == SQLITE_ROW) {
@@ -540,7 +640,7 @@ scc_gen_new(krb5_context context, krb5_ccache *id)
{ {
krb5_scache *s; krb5_scache *s;
s = scc_alloc(context, NULL); s = scc_alloc(context, NULL, NULL, 1);
if (s == NULL) { if (s == NULL) {
krb5_set_error_message(context, KRB5_CC_NOMEM, krb5_set_error_message(context, KRB5_CC_NOMEM,
@@ -557,7 +657,7 @@ scc_gen_new(krb5_context context, krb5_ccache *id)
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
scc_initialize(krb5_context context, scc_initialize(krb5_context context,
krb5_ccache id, krb5_ccache id,
krb5_principal primary_principal) krb5_principal principal)
{ {
krb5_scache *s = SCACHE(id); krb5_scache *s = SCACHE(id);
krb5_error_code ret; krb5_error_code ret;
@@ -589,7 +689,7 @@ scc_initialize(krb5_context context,
} }
} }
ret = bind_principal(context, s->db, s->ucachep, 1, primary_principal); ret = bind_principal(context, s->db, s->ucachep, 1, principal);
if (ret) if (ret)
goto rollback; goto rollback;
sqlite3_bind_int(s->ucachep, 2, s->cid); sqlite3_bind_int(s->ucachep, 2, s->cid);
@@ -827,8 +927,8 @@ scc_get_principal(krb5_context context,
if (sqlite3_step(s->scache) != SQLITE_ROW) { if (sqlite3_step(s->scache) != SQLITE_ROW) {
sqlite3_reset(s->scache); sqlite3_reset(s->scache);
krb5_set_error_message(context, KRB5_CC_END, krb5_set_error_message(context, KRB5_CC_END,
N_("No principal for cache SCC:%s:%s", ""), N_("No principal for cache SCC:%s", ""),
s->name, s->file); s->name);
return KRB5_CC_END; return KRB5_CC_END;
} }
@@ -836,8 +936,8 @@ scc_get_principal(krb5_context context,
sqlite3_reset(s->scache); sqlite3_reset(s->scache);
krb5_set_error_message(context, KRB5_CC_END, krb5_set_error_message(context, KRB5_CC_END,
N_("Principal data of wrong type " N_("Principal data of wrong type "
"for SCC:%s:%s", ""), "for SCC:%s", ""),
s->name, s->file); s->name);
return KRB5_CC_END; return KRB5_CC_END;
} }
@@ -845,8 +945,8 @@ scc_get_principal(krb5_context context,
if (str == NULL) { if (str == NULL) {
sqlite3_reset(s->scache); sqlite3_reset(s->scache);
krb5_set_error_message(context, KRB5_CC_END, krb5_set_error_message(context, KRB5_CC_END,
N_("Principal not set for SCC:%s:%s", ""), N_("Principal not set for SCC:%s", ""),
s->name, s->file); s->name);
return KRB5_CC_END; return KRB5_CC_END;
} }
@@ -1001,8 +1101,8 @@ next:
if (sqlite3_column_type(ctx->credstmt, 0) != SQLITE_BLOB) { if (sqlite3_column_type(ctx->credstmt, 0) != SQLITE_BLOB) {
krb5_set_error_message(context, KRB5_CC_END, krb5_set_error_message(context, KRB5_CC_END,
N_("credential of wrong type for SCC:%s:%s", ""), N_("credential of wrong type for SCC:%s", ""),
s->name, s->file); s->name);
sqlite3_reset(ctx->credstmt); sqlite3_reset(ctx->credstmt);
return KRB5_CC_END; return KRB5_CC_END;
} }
@@ -1079,8 +1179,8 @@ scc_remove_cred(krb5_context context,
ret = KRB5_CC_END; ret = KRB5_CC_END;
krb5_set_error_message(context, ret, krb5_set_error_message(context, ret,
N_("Credential of wrong type " N_("Credential of wrong type "
"for SCC:%s:%s", ""), "for SCC:%s", ""),
s->name, s->file); s->name);
break; break;
} }
@@ -1134,6 +1234,7 @@ scc_set_flags(krb5_context context,
struct cache_iter { struct cache_iter {
char *drop; char *drop;
char *file;
sqlite3 *db; sqlite3 *db;
sqlite3_stmt *stmt; sqlite3_stmt *stmt;
}; };
@@ -1151,8 +1252,8 @@ scc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
if (ctx == NULL) if (ctx == NULL)
return krb5_enomem(context); return krb5_enomem(context);
ret = default_db(context, &ctx->db); ret = default_db(context, NULL, &ctx->db, &ctx->file);
if (ctx->db == NULL) { if (ret) {
free(ctx); free(ctx);
return ret; return ret;
} }
@@ -1160,48 +1261,48 @@ scc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
ret = asprintf(&name, "cacheIteration%pPid%d", ret = asprintf(&name, "cacheIteration%pPid%d",
ctx, (int)getpid()); ctx, (int)getpid());
if (ret < 0 || name == NULL) { if (ret < 0 || name == NULL) {
sqlite3_close(ctx->db); sqlite3_close(ctx->db);
free(ctx); free(ctx);
return krb5_enomem(context); return krb5_enomem(context);
} }
ret = asprintf(&ctx->drop, "DROP TABLE %s", name); ret = asprintf(&ctx->drop, "DROP TABLE %s", name);
if (ret < 0 || ctx->drop == NULL) { if (ret < 0 || ctx->drop == NULL) {
sqlite3_close(ctx->db); sqlite3_close(ctx->db);
free(name); free(name);
free(ctx); free(ctx);
return krb5_enomem(context); return krb5_enomem(context);
} }
ret = asprintf(&str, "CREATE TEMPORARY TABLE %s AS SELECT name FROM caches", ret = asprintf(&str, "CREATE TEMPORARY TABLE %s AS SELECT name FROM caches",
name); name);
if (ret < 0 || str == NULL) { if (ret < 0 || str == NULL) {
sqlite3_close(ctx->db); sqlite3_close(ctx->db);
free(name); free(name);
free(ctx->drop); free(ctx->drop);
free(ctx); free(ctx);
return krb5_enomem(context); return krb5_enomem(context);
} }
ret = exec_stmt(context, ctx->db, str, KRB5_CC_IO); ret = exec_stmt(context, ctx->db, str, KRB5_CC_IO);
free(str); free(str);
str = NULL; str = NULL;
if (ret) { if (ret) {
sqlite3_close(ctx->db); sqlite3_close(ctx->db);
free(name); free(name);
free(ctx->drop); free(ctx->drop);
free(ctx); free(ctx);
return ret; return ret;
} }
ret = asprintf(&str, "SELECT name FROM %s", name); ret = asprintf(&str, "SELECT name FROM %s", name);
if (ret < 0 || str == NULL) { if (ret < 0 || str == NULL) {
exec_stmt(context, ctx->db, ctx->drop, 0); exec_stmt(context, ctx->db, ctx->drop, 0);
sqlite3_close(ctx->db); sqlite3_close(ctx->db);
free(name); free(name);
free(ctx->drop); free(ctx->drop);
free(ctx); free(ctx);
return krb5_enomem(context); return krb5_enomem(context);
} }
free(name); free(name);
@@ -1249,10 +1350,13 @@ again:
goto again; goto again;
ret = _krb5_cc_allocate(context, &krb5_scc_ops, id); ret = _krb5_cc_allocate(context, &krb5_scc_ops, id);
if (ret) if (ret == 0)
return ret; ret = scc_resolve(context, id, ctx->file, name);
if (ret) {
return scc_resolve(context, id, name); free(*id);
*id = NULL;
}
return ret;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
@@ -1263,6 +1367,7 @@ scc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
exec_stmt(context, ctx->db, ctx->drop, 0); exec_stmt(context, ctx->db, ctx->drop, 0);
sqlite3_finalize(ctx->stmt); sqlite3_finalize(ctx->stmt);
sqlite3_close(ctx->db); sqlite3_close(ctx->db);
free(ctx->file);
free(ctx->drop); free(ctx->drop);
free(ctx); free(ctx);
return 0; return 0;
@@ -1304,7 +1409,7 @@ scc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
} }
} }
sqlite3_bind_text(sfrom->ucachen, 1, sto->name, -1, NULL); sqlite3_bind_text(sfrom->ucachen, 1, sto->sub, -1, NULL);
sqlite3_bind_int(sfrom->ucachen, 2, sfrom->cid); sqlite3_bind_int(sfrom->ucachen, 2, sfrom->cid);
do { do {
@@ -1334,20 +1439,8 @@ rollback:
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
scc_get_default_name(krb5_context context, char **str) scc_get_default_name(krb5_context context, char **str)
{ {
krb5_error_code ret;
char *name;
*str = NULL; *str = NULL;
return _krb5_expand_default_cc_name(context, KRB5_SCACHE_NAME, str);
ret = get_def_name(context, &name);
if (ret)
return _krb5_expand_default_cc_name(context, KRB5_SCACHE_NAME, str);
ret = asprintf(str, "SCC:%s", name);
free(name);
if (ret < 0 || *str == NULL)
return krb5_enomem(context);
return 0;
} }
static krb5_error_code KRB5_CALLCONV static krb5_error_code KRB5_CALLCONV
@@ -1364,7 +1457,7 @@ scc_set_default(krb5_context context, krb5_ccache id)
return KRB5_CC_IO; return KRB5_CC_IO;
} }
ret = sqlite3_bind_text(s->umaster, 1, s->name, -1, NULL); ret = sqlite3_bind_text(s->umaster, 1, s->sub, -1, NULL);
if (ret) { if (ret) {
sqlite3_reset(s->umaster); sqlite3_reset(s->umaster);
krb5_set_error_message(context, KRB5_CC_IO, krb5_set_error_message(context, KRB5_CC_IO,

View File

@@ -669,6 +669,176 @@ test_cc_config(krb5_context context, const char *cc_type,
krb5_free_principal(context, p); krb5_free_principal(context, p);
} }
static krb5_error_code
test_cccol(krb5_context context, const char *def_cccol, const char **what)
{
krb5_cc_cache_cursor cursor;
krb5_error_code ret;
krb5_principal p1, p2;
krb5_ccache id, id1, id2;
krb5_creds cred1, cred2;
size_t match1 = 0;
size_t match2 = 0;
memset(&cred1, 0, sizeof(cred1));
memset(&cred2, 0, sizeof(cred2));
*what = "krb5_parse_name";
ret = krb5_parse_name(context, "krbtgt/SU.SE@SU.SE", &cred1.server);
if (ret) return ret;
ret = krb5_parse_name(context, "lha@SU.SE", &cred1.client);
if (ret) return ret;
ret = krb5_parse_name(context, "krbtgt/H5L.SE@H5L.SE", &cred2.server);
if (ret) return ret;
ret = krb5_parse_name(context, "lha@H5L.SE", &cred2.client);
if (ret) return ret;
*what = "krb5_cc_set_default_name";
ret = krb5_cc_set_default_name(context, def_cccol);
if (ret) return ret;
*what = "krb5_cc_default";
ret = krb5_cc_default(context, &id1);
if (ret) return ret;
*what = "krb5_cc_initialize";
ret = krb5_cc_initialize(context, id1, cred1.client);
if (ret) return ret;
*what = "krb5_cc_store_cred";
ret = krb5_cc_store_cred(context, id1, &cred1);
if (ret) return ret;
*what = "krb5_cc_resolve";
ret = krb5_cc_resolve_for(context, NULL, def_cccol, cred2.client, &id2);
if (ret) return ret;
*what = "krb5_cc_initialize";
ret = krb5_cc_initialize(context, id2, cred2.client);
if (ret) return ret;
*what = "krb5_cc_store_cred";
ret = krb5_cc_store_cred(context, id2, &cred2);
if (ret) return ret;
krb5_cc_close(context, id1);
krb5_cc_close(context, id2);
id1 = id2 = NULL;
*what = "krb5_cc_default";
ret = krb5_cc_default(context, &id1);
if (ret) return ret;
*what = "krb5_cc_resolve";
ret = krb5_cc_resolve_for(context, NULL, def_cccol, cred2.client, &id2);
if (ret) return ret;
*what = "krb5_cc_get_principal";
ret = krb5_cc_get_principal(context, id1, &p1);
if (ret) return ret;
ret = krb5_cc_get_principal(context, id2, &p2);
if (ret) return ret;
if (!krb5_principal_compare(context, p1, cred1.client)) {
char *u1 = NULL;
char *u2 = NULL;
(void) krb5_unparse_name(context, p1, &u1);
(void) krb5_unparse_name(context, cred1.client, &u2);
warnx("Inconsistent principals for ccaches in %s: %s vs %s "
"(expected lha@SU.SE)", def_cccol, u1, u2);
return EINVAL;
}
if (!krb5_principal_compare(context, p2, cred2.client)) {
char *u1 = NULL;
char *u2 = NULL;
(void) krb5_unparse_name(context, p2, &u1);
(void) krb5_unparse_name(context, cred2.client, &u2);
warnx("Inconsistent principals for ccaches in %s: %s and %s "
"(expected lha@H5L.SE)", def_cccol, u1, u2);
return EINVAL;
}
krb5_free_principal(context, p1);
krb5_free_principal(context, p2);
*what = "krb5_cc_cache_get_first";
ret = krb5_cc_cache_get_first(context, NULL, &cursor);
if (ret) return ret;
*what = "krb5_cc_cache_next";
while (krb5_cc_cache_next(context, cursor, &id) == 0) {
krb5_principal p;
*what = "krb5_cc_get_principal";
ret = krb5_cc_get_principal(context, id, &p);
if (ret) return ret;
if (krb5_principal_compare(context, p, cred1.client))
match1++;
else if (krb5_principal_compare(context, p, cred2.client))
match2++;
krb5_free_principal(context, p);
krb5_cc_close(context, id);
}
(void) krb5_cc_cache_end_seq_get(context, cursor);
*what = "cccol iteration inconsistency";
if (match1 != 1 || match2 != 1) return EINVAL;
krb5_cc_close(context, id1);
krb5_cc_close(context, id2);
krb5_free_cred_contents(context, &cred1);
krb5_free_cred_contents(context, &cred2);
return 0;
}
static void
test_cccol_dcache(krb5_context context)
{
krb5_error_code ret;
char template[sizeof("DIR:dcache-XXXXXX")];
char *s;
const char *what;
memcpy(template, "DIR:dcache-XXXXXX", sizeof("DIR:dcache-XXXXXX"));
if (mkdtemp(template + sizeof("DIR:") - 1) == NULL)
krb5_err(context, 1, errno, "mkdtemp");
ret = test_cccol(context, template, &what);
if (asprintf(&s, "%s/primary", template + sizeof("DIR:") - 1) > 0) {
(void) unlink(s);
free(s);
}
if (asprintf(&s, "%s/tkt", template + sizeof("DIR:") - 1) > 0) {
(void) unlink(s);
free(s);
}
if (asprintf(&s, "%s/tkt.lha@H5L.SE", template + sizeof("DIR:") - 1) > 0) {
(void) unlink(s);
free(s);
}
if (asprintf(&s, "%s/tkt.lha@SU.SE", template + sizeof("DIR:") - 1) > 0) {
(void) unlink(s);
free(s);
}
(void) rmdir(template + sizeof("DIR:") - 1); /* XXX Check that this succeeds */
if (ret)
krb5_err(context, 1, errno, "%s", what);
}
static void
test_cccol_scache(krb5_context context)
{
krb5_error_code ret;
char template[sizeof("SCC:scache-XXXXXX")];
const char *what;
int fd;
memcpy(template, "SCC:scache-XXXXXX", sizeof("SCC:scache-XXXXXX"));
if ((fd = mkstemp(template + sizeof("SCC:") - 1)) == -1)
krb5_err(context, 1, errno, "mkstemp");
(void) close(fd);
ret = test_cccol(context, template, &what);
(void) unlink(template + sizeof("SCC:") - 1);
if (ret)
krb5_err(context, 1, ret, "%s", what);
}
static struct getargs args[] = { static struct getargs args[] = {
{"debug", 'd', arg_flag, &debug_flag, {"debug", 'd', arg_flag, &debug_flag,
@@ -725,6 +895,10 @@ main(int argc, char **argv)
test_default_name(context); test_default_name(context);
test_mcache(context); test_mcache(context);
/*
* XXX Make sure to set default ccache names for each cc type!
* Otherwise we clobber the user's ccaches.
*/
test_init_vs_destroy(context, krb5_cc_type_memory); test_init_vs_destroy(context, krb5_cc_type_memory);
test_init_vs_destroy(context, krb5_cc_type_file); test_init_vs_destroy(context, krb5_cc_type_file);
#if 0 #if 0
@@ -753,6 +927,14 @@ main(int argc, char **argv)
test_cache_find(context, "lha@SU.SE", 1); test_cache_find(context, "lha@SU.SE", 1);
test_cache_find(context, "hulabundulahotentot@SU.SE", 0); test_cache_find(context, "hulabundulahotentot@SU.SE", 0);
/*
* XXX We should compose and krb5_cc_set_default_name() a default ccache
* for each cc type that we test with test_cache_iter(), and we should do
* that inside test_cache_iter().
*
* Alternatively we should remove test_cache_iter() in favor of
* test_cccol(), which is a much more complete test.
*/
test_cache_iter(context, krb5_cc_type_memory, 0); test_cache_iter(context, krb5_cc_type_memory, 0);
test_cache_iter(context, krb5_cc_type_memory, 1); test_cache_iter(context, krb5_cc_type_memory, 1);
test_cache_iter(context, krb5_cc_type_memory, 0); test_cache_iter(context, krb5_cc_type_memory, 0);
@@ -860,6 +1042,22 @@ main(int argc, char **argv)
test_cc_config(context, "MEMORY", "bar", 1000); /* 1000 because fast */ test_cc_config(context, "MEMORY", "bar", 1000); /* 1000 because fast */
test_cc_config(context, "FILE", "/tmp/foocc", 30); /* 30 because slower */ test_cc_config(context, "FILE", "/tmp/foocc", 30); /* 30 because slower */
test_cccol_dcache(context);
test_cccol_scache(context);
#ifdef HAVE_KEYUTILS_H
{
const char *what;
ret = test_cccol(context, "KEYRING:legacy:fooccol", &what);
if (ret)
krb5_err(context, 1, ret, "%s", what);
ret = test_cccol(context, "MEMORY:fooccol", &what);
if (ret)
krb5_err(context, 1, ret, "%s", what);
}
#endif /* HAVE_KEYUTILS_H */
krb5_free_context(context); krb5_free_context(context);
#if 0 #if 0

View File

@@ -410,6 +410,7 @@ struct entry libdefaults_entries[] = {
{ "default_client_keytab_name", krb5_config_string, NULL, 0 }, { "default_client_keytab_name", krb5_config_string, NULL, 0 },
{ "default_cc_name", krb5_config_string, NULL, 0 }, { "default_cc_name", krb5_config_string, NULL, 0 },
{ "default_cc_type", krb5_config_string, NULL, 0 }, { "default_cc_type", krb5_config_string, NULL, 0 },
{ "default_cc_collection", krb5_config_string, NULL, 0 },
{ "default_etypes", krb5_config_string, NULL, 0 }, { "default_etypes", krb5_config_string, NULL, 0 },
{ "default_etypes_des", krb5_config_string, NULL, 0 }, { "default_etypes_des", krb5_config_string, NULL, 0 },
{ "default_keytab_modify_name", krb5_config_string, NULL, 0 }, { "default_keytab_modify_name", krb5_config_string, NULL, 0 },

View File

@@ -87,6 +87,8 @@ HEIMDAL_KRB5_2.0 {
krb5_cc_copy_cache; krb5_cc_copy_cache;
krb5_cc_copy_match_f; krb5_cc_copy_match_f;
krb5_cc_default; krb5_cc_default;
krb5_cc_default_for;
krb5_cc_default_sub;
krb5_cc_default_name; krb5_cc_default_name;
krb5_cc_destroy; krb5_cc_destroy;
krb5_cc_end_seq_get; krb5_cc_end_seq_get;
@@ -111,6 +113,8 @@ HEIMDAL_KRB5_2.0 {
krb5_cc_register; krb5_cc_register;
krb5_cc_remove_cred; krb5_cc_remove_cred;
krb5_cc_resolve; krb5_cc_resolve;
krb5_cc_resolve_for;
krb5_cc_resolve_sub;
krb5_cc_retrieve_cred; krb5_cc_retrieve_cred;
krb5_cc_set_config; krb5_cc_set_config;
krb5_cc_set_default_name; krb5_cc_set_default_name;

View File

@@ -105,7 +105,7 @@ echo "initial ticket"
${kinit} -c ${cache} --password-file=${objdir}/foopassword user@${R} || exitcode=1 ${kinit} -c ${cache} --password-file=${objdir}/foopassword user@${R} || exitcode=1
echo "copy ccache with gss_store_cred" echo "copy ccache with gss_store_cred"
${test_add_store_cred} ${cache} ${cache2} || exit 1 ${test_add_store_cred} --default --overwrite --env ${cache} ${cache2} || exit 1
${klist} -c ${cache2} || exit 1 ${klist} -c ${cache2} || exit 1
echo "keytab" echo "keytab"

View File

@@ -53,6 +53,7 @@ nokeytab="FILE:no-such-keytab"
cache="FILE:krb5ccfile" cache="FILE:krb5ccfile"
kinit="${TESTS_ENVIRONMENT} ../../kuser/kinit -c $cache ${afs_no_afslog}" kinit="${TESTS_ENVIRONMENT} ../../kuser/kinit -c $cache ${afs_no_afslog}"
kdestroy="${TESTS_ENVIRONMENT} ../../kuser/kdestroy -c $cache"
klist="${TESTS_ENVIRONMENT} ../../kuser/heimtools klist -c $cache" klist="${TESTS_ENVIRONMENT} ../../kuser/heimtools klist -c $cache"
kgetcred="${TESTS_ENVIRONMENT} ../../kuser/kgetcred -c $cache" kgetcred="${TESTS_ENVIRONMENT} ../../kuser/kgetcred -c $cache"
kadmin="${TESTS_ENVIRONMENT} ../../kadmin/kadmin -l -r $R" kadmin="${TESTS_ENVIRONMENT} ../../kadmin/kadmin -l -r $R"
@@ -121,17 +122,23 @@ trap "kill ${kdcpid}; echo signal killing kdc; exit 1;" EXIT
testfailed="echo test failed; cat messages.log; exit 1" testfailed="echo test failed; cat messages.log; exit 1"
echo "Test gss_acquire_cred_with_password" ; > messages.log echo "Test gss_acquire_cred_with_password" ; > messages.log
${kdestroy}
${context} --client-name=user1@${R} --client-password=u1 --mech-type=krb5 \ ${context} --client-name=user1@${R} --client-password=u1 --mech-type=krb5 \
host@lucid.test.h5l.se || { eval "$testfailed"; } host@lucid.test.h5l.se || { eval "$testfailed"; }
${klist} && { eval "$testfailed"; }
# These must fail (because wrong password) # These must fail (because wrong password)
${context} --client-name=user1@${R} --client-password=u2 --mech-type=krb5 \ ${context} --client-name=user1@${R} --client-password=u2 --mech-type=krb5 \
host@lucid.test.h5l.se && { eval "$testfailed"; } host@lucid.test.h5l.se && { eval "$testfailed"; }
${klist} && { eval "$testfailed"; }
${context} --client-name=user1@${R} --client-password=u2 --mech-type='' \ ${context} --client-name=user1@${R} --client-password=u2 --mech-type='' \
--mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; } --mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; }
${klist} && { eval "$testfailed"; }
${context} --client-name=user1@${R} --client-password=u2 --mech-type=krb5 \ ${context} --client-name=user1@${R} --client-password=u2 --mech-type=krb5 \
--mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; } --mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; }
${klist} && { eval "$testfailed"; }
${context} --client-name=user1@${R} --client-password=u2 --mech-type=all \ ${context} --client-name=user1@${R} --client-password=u2 --mech-type=all \
--mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; } --mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; }
${klist} && { eval "$testfailed"; }
${context} --client-name=user1@${R} --client-password=u2 \ ${context} --client-name=user1@${R} --client-password=u2 \
--mech-type=krb5,ntlm --mech-types=krb5 host@lucid.test.h5l.se \ --mech-type=krb5,ntlm --mech-types=krb5 host@lucid.test.h5l.se \
&& { eval "$testfailed"; } && { eval "$testfailed"; }

View File

@@ -4,6 +4,7 @@ noinst_DATA = \
an2ln-db.txt \ an2ln-db.txt \
kdc-tester4.json \ kdc-tester4.json \
krb5.conf \ krb5.conf \
krb5-cccol.conf \
krb5-authz.conf \ krb5-authz.conf \
krb5-authz2.conf \ krb5-authz2.conf \
krb5-canon.conf \ krb5-canon.conf \
@@ -183,6 +184,13 @@ krb5.conf: krb5.conf.in Makefile
-e 's,[@]kdc[@],,g' < $(srcdir)/krb5.conf.in > krb5.conf.tmp && \ -e 's,[@]kdc[@],,g' < $(srcdir)/krb5.conf.in > krb5.conf.tmp && \
mv krb5.conf.tmp krb5.conf mv krb5.conf.tmp krb5.conf
krb5-cccol.conf: krb5-cccol.conf.in Makefile
$(do_subst) \
-e 's,[@]WEAK[@],false,g' \
-e 's,[@]dk[@],,g' \
-e 's,[@]kdc[@],,g' < $(srcdir)/krb5-cccol.conf.in > krb5-cccol.conf.tmp && \
mv krb5-cccol.conf.tmp krb5-cccol.conf
krb5-authz.conf: krb5-authz.conf.in Makefile krb5-authz.conf: krb5-authz.conf.in Makefile
$(do_subst) < $(srcdir)/krb5-authz.conf.in > krb5-authz.conf.tmp && \ $(do_subst) < $(srcdir)/krb5-authz.conf.in > krb5-authz.conf.tmp && \
mv krb5-authz.conf.tmp krb5-authz.conf mv krb5-authz.conf.tmp krb5-authz.conf

View File

@@ -36,7 +36,7 @@ objdir="@objdir@"
. ${env_setup} . ${env_setup}
KRB5_CONFIG="${objdir}/krb5-cc.conf" KRB5_CONFIG="${objdir}/krb5.conf"
export KRB5_CONFIG export KRB5_CONFIG
unset KRB5CCNAME unset KRB5CCNAME
@@ -68,8 +68,6 @@ rm -f mkey.file*
> messages.log > messages.log
cp "${objdir}/krb5.conf" "${objdir}/krb5-cc.conf"
echo Creating database echo Creating database
${kadmin} \ ${kadmin} \
init \ init \
@@ -95,12 +93,7 @@ trap "kill -9 ${kdcpid}; echo signal killing kdc; exit 1;" EXIT
ec=0 ec=0
(cat ${objdir}/krb5.conf ; \ export KRB5CCNAME=SCC:${objdir}/sdb
echo '' ; \
echo '[libdefaults]' ; \
echo " default_cc_type = SCC" ; \
echo '' ) \
> ${objdir}/krb5-cc.conf
${kswitch} -p foo@${R} 2>/dev/null && ${kdestroy} ${kswitch} -p foo@${R} 2>/dev/null && ${kdestroy}
${kswitch} -p foo@${R} 2>/dev/null && ${kdestroy} ${kswitch} -p foo@${R} 2>/dev/null && ${kdestroy}
@@ -114,8 +107,8 @@ ${klist} -l | grep foo@ >/dev/null || { ec=1 ; eval "${testfailed}"; }
${kdestroy} ${kdestroy}
echo "getting both tickets"; > messages.log echo "getting both tickets"; > messages.log
${kinit} -c SCC:1 foo@${R} || { ec=1 ; eval "${testfailed}"; } ${kinit} -c ${KRB5CCNAME}:1 foo@${R} || { ec=1 ; eval "${testfailed}"; }
${kinit} -c SCC:2 bar@${R} || { ec=1 ; eval "${testfailed}"; } ${kinit} -c ${KRB5CCNAME}:2 bar@${R} || { ec=1 ; eval "${testfailed}"; }
echo "switch foo" echo "switch foo"
${kswitch} -p foo@${R} || { ec=1 ; eval "${testfailed}"; } ${kswitch} -p foo@${R} || { ec=1 ; eval "${testfailed}"; }
${klist} | head -2 | grep foo@ >/dev/null || { ec=1 ; eval "${testfailed}"; } ${klist} | head -2 | grep foo@ >/dev/null || { ec=1 ; eval "${testfailed}"; }
@@ -140,6 +133,22 @@ ${klist} -l | grep foo@ >/dev/null && { ec=1 ; eval "${testfailed}"; }
echo "check that bar is gone" echo "check that bar is gone"
${klist} -l | grep bar@ >/dev/null && { ec=1 ; eval "${testfailed}"; } ${klist} -l | grep bar@ >/dev/null && { ec=1 ; eval "${testfailed}"; }
echo "getting tickets (DIR)"; > messages.log
KRB5_CONFIG="${objdir}/krb5-cccol.conf"
export KRB5_CONFIG
unset KRB5CCNAME
rm -rf ${objdir}/kt ${objdir}/cc_dir
mkdir ${objdir}/cc_dir || { ec=1 ; eval "${testfailed}"; }
${kinit} foo@${R} || { ec=1 ; eval "${testfailed}"; }
${kinit} --no-change-default bar@${R} || { ec=1 ; eval "${testfailed}"; }
primary=`cat ${objdir}/cc_dir/primary`
[ "x$primary" = xtkt.foo@${R} ] || { ec=1 ; eval "${testfailed}"; }
${klist} -l |
grep "foo@TEST.H5L.SE.*FILE:${objdir}/cc_dir/tkt.foo@TEST.H5L.SE" > /dev/null ||
{ ec=1 ; eval "${testfailed}"; }
${klist} -l |
grep "bar@TEST.H5L.SE.*FILE:${objdir}/cc_dir/tkt.bar@TEST.H5L.SE" > /dev/null ||
{ ec=1 ; eval "${testfailed}"; }
echo "killing kdc (${kdcpid})" echo "killing kdc (${kdcpid})"
sh ${leaks_kill} kdc $kdcpid || exit 1 sh ${leaks_kill} kdc $kdcpid || exit 1

View File

@@ -0,0 +1,165 @@
[libdefaults]
default_realm = TEST.H5L.SE TEST2.H5L.SE
default_cc_collection = DIR:@objdir@/cc_dir/
no-addresses = TRUE
allow_weak_crypto = @WEAK@
dns_lookup_kdc = no
dns_lookup_realm = no
[appdefaults]
pkinit_anchors = FILE:@srcdir@/../../lib/hx509/data/ca.crt
reconnect-min = 2s
reconnect-backoff = 2s
reconnect-max = 10s
[realms]
TEST.H5L.SE = {
kdc = localhost:@port@
admin_server = localhost:@admport@
kpasswd_server = localhost:@pwport@
}
SUB.TEST.H5L.SE = {
kdc = localhost:@port@
}
TEST2.H5L.SE = {
kdc = localhost:@port@
kpasswd_server = localhost:@pwport@
}
TEST3.H5L.SE = {
kdc = localhost:@port@
}
TEST4.H5L.SE = {
kdc = localhost:@port@
}
SOME-REALM5.FR = {
kdc = localhost:@port@
}
SOME-REALM6.US = {
kdc = localhost:@port@
}
SOME-REALM7.UK = {
kdc = localhost:@port@
}
SOME-REALM8.UK = {
kdc = localhost:@port@
}
TEST-HTTP.H5L.SE = {
kdc = http/localhost:@port@
}
H1.TEST.H5L.SE = {
kdc = localhost:@port@
}
H2.TEST.H5L.SE = {
kdc = localhost:@port@
}
H3.H2.TEST.H5L.SE = {
kdc = localhost:@port@
}
H4.H2.TEST.H5L.SE = {
kdc = localhost:@port@
}
[domain_realm]
.test.h5l.se = TEST.H5L.SE
.sub.test.h5l.se = SUB.TEST.H5L.SE
.h1.test.h5l.se = H1.TEST.H5L.SE
.h2.test.h5l.se = H2.TEST.H5L.SE
.h3.h2.test.h5l.se = H3.H2.TEST.H5L.SE
.h4.h2.test.h5l.se = H4.H2.TEST.H5L.SE
.example.com = TEST2.H5L.SE
localhost = TEST.H5L.SE
.localdomain = TEST.H5L.SE
localdomain = TEST.H5L.SE
.localdomain6 = TEST.H5L.SE
localdomain6 = TEST.H5L.SE
[kdc]
enable-digest = true
allow-anonymous = true
digests_allowed = chap-md5,digest-md5,ntlm-v1,ntlm-v1-session,ntlm-v2,ms-chap-v2
strict-nametypes = true
enable-http = true
enable-pkinit = true
pkinit_identity = FILE:@srcdir@/../../lib/hx509/data/kdc.crt,@srcdir@/../../lib/hx509/data/kdc.key
pkinit_anchors = FILE:@srcdir@/../../lib/hx509/data/ca.crt
pkinit_pool = FILE:@srcdir@/../../lib/hx509/data/sub-ca.crt
# pkinit_revoke = CRL:@srcdir@/../../lib/hx509/data/crl1.crl
pkinit_mappings_file = @srcdir@/pki-mapping
pkinit_allow_proxy_certificate = true
database = {
label = {
dbname = @db_type@:@objdir@/current-db@kdc@
realm = TEST.H5L.SE
mkey_file = @objdir@/mkey.file
acl_file = @srcdir@/heimdal.acl
log_file = @objdir@/current@kdc@.log
}
label2 = {
dbname = @db_type@:@objdir@/current-db@kdc@
realm = TEST2.H5L.SE
mkey_file = @objdir@/mkey.file
acl_file = @srcdir@/heimdal.acl
log_file = @objdir@/current@kdc@.log
}
label3 = {
dbname = sqlite:@objdir@/current-db@kdc@.sqlite3
realm = SOME-REALM5.FR
mkey_file = @objdir@/mkey.file
acl_file = @srcdir@/heimdal.acl
log_file = @objdir@/current@kdc@.log
}
}
signal_socket = @objdir@/signal
iprop-stats = @objdir@/iprop-stats
iprop-acl = @srcdir@/iprop-acl
log-max-size = 40000
[hdb]
db-dir = @objdir@
[logging]
kdc = 0-/FILE:@objdir@/messages.log
krb5 = 0-/FILE:@objdir@/messages.log
default = 0-/FILE:@objdir@/messages.log
# If you are doing preformance measurements on OSX you want to change
# the kdc LOG line from = to - below to keep the FILE open and avoid
# open/write/close which is blocking (rdar:// ) on OSX.
# kdc = 0-/FILE=@objdir@/messages.log
[kadmin]
save-password = true
default_key_rules = {
*/des3-only@* = des3-cbc-sha1:pw-salt
*/aes-only@* = aes256-cts-hmac-sha1-96:pw-salt
}
@dk@
[capaths]
TEST.H5L.SE = {
TEST2.H5L.SE = .
SOME-REALM5.FR = 1
TEST3.H5L.SE = TEST2.H5L.SE
TEST4.H5L.SE = TEST2.H5L.SE
TEST4.H5L.SE = TEST3.H5L.SE
SOME-REALM6.US = SOME-REALM5.FR
SOME-REALM7.UK = SOME-REALM6.US
SOME-REALM7.UK = SOME-REALM5.FR
SOME-REALM8.UK = SOME-REALM6.US
}
H4.H2.TEST.H5L.SE = {
H1.TEST.H5L.SE = H3.H2.TEST.H5L.SE
H1.TEST.H5L.SE = H2.TEST.H5L.SE
H1.TEST.H5L.SE = TEST.H5L.SE
TEST.H5L.SE = H3.H2.TEST.H5L.SE
TEST.H5L.SE = H2.TEST.H5L.SE
H2.TEST.H5L.SE = H3.H2.TEST.H5L.SE
}