krb5: Fix kinit harder

The previous fixes for using `krb5_cc_default_for()` weren't quite
correct.
This commit is contained in:
Nicolas Williams
2020-03-13 21:36:00 -05:00
parent 4c736cbeec
commit a8874a62bb
4 changed files with 113 additions and 128 deletions

View File

@@ -39,6 +39,7 @@
.Nd acquire initial tickets .Nd acquire initial tickets
.Sh SYNOPSIS .Sh SYNOPSIS
.Nm kinit .Nm kinit
.Op Fl Fl no-change-default
.Op Fl Fl afslog .Op Fl Fl afslog
.Oo Fl c Ar cachename \*(Ba Xo .Oo Fl c Ar cachename \*(Ba Xo
.Fl Fl cache= Ns Ar cachename .Fl Fl cache= Ns Ar cachename
@@ -97,13 +98,22 @@ can later be used to obtain tickets for other services.
.Pp .Pp
Supported options: Supported options:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Fl c Ar cachename Fl Fl cache= Ns Ar cachename .It Fl c Ar cachename | Fl Fl cache= Ns Ar cachename
The credentials cache to put the acquired ticket in, if other than The credentials cache to put the acquired ticket in, if other than
default. default.
.It Fl Fl cache-default-for .It Fl Fl no-change-default
Use a cache in the default collection (for the default cache type) By default the principal's credentials will be stored in the default
named after the client principal. This is useful for users with credential cache. This option will cause them to instead be stored
multiple client principals. only in a cache whose name is derived from the principal's name. Note
that
.Xr klist 1
with the
.Fl l
option will list all the credential caches the user has, along with
the name of the principal whose credentials are stored therein. This
option is ignored if the
.Fl c Ar cachename | Fl Fl cache= Ns Ar cachename
option is given.
.It Fl f Fl Fl forwardable .It Fl f Fl Fl forwardable
Obtain a ticket than can be forwarded to another host. Obtain a ticket than can be forwarded to another host.
.It Fl F Fl Fl no-forwardable .It Fl F Fl Fl no-forwardable

View File

@@ -56,7 +56,6 @@ int validate_flag = 0;
int version_flag = 0; int version_flag = 0;
int help_flag = 0; int help_flag = 0;
int addrs_flag = -1; int addrs_flag = -1;
int default_for_flag = 0;
struct getarg_strings extra_addresses; struct getarg_strings extra_addresses;
int anonymous_flag = 0; int anonymous_flag = 0;
char *lifetime = NULL; char *lifetime = NULL;
@@ -109,9 +108,6 @@ static struct getargs args[] = {
{ "cache", 'c', arg_string, &cred_cache, { "cache", 'c', arg_string, &cred_cache,
NP_("credentials cache", ""), "cachename" }, NP_("credentials cache", ""), "cachename" },
{ "cache-default-for" , 0, arg_flag, &default_for_flag,
NP_("name cache after client principal", ""), NULL },
{ "forwardable", 'F', arg_negative_flag, &forwardable_flag, { "forwardable", 'F', arg_negative_flag, &forwardable_flag,
NP_("get tickets not forwardable", ""), NULL }, NP_("get tickets not forwardable", ""), NULL },
@@ -422,23 +418,51 @@ static krb5_error_code
renew_validate(krb5_context context, renew_validate(krb5_context context,
int renew, int renew,
int validate, int validate,
krb5_ccache cache, krb5_ccache *cachep,
krb5_const_principal principal,
krb5_boolean cache_is_default_for,
const char *server, const char *server,
krb5_deltat life) krb5_deltat life)
{ {
krb5_error_code ret; krb5_error_code ret;
krb5_ccache tempccache = NULL; krb5_ccache tempccache = NULL;
krb5_ccache cache = *cachep;
krb5_creds in, *out = NULL; krb5_creds in, *out = NULL;
krb5_kdc_flags flags; krb5_kdc_flags flags;
memset(&in, 0, sizeof(in)); memset(&in, 0, sizeof(in));
ret = krb5_cc_get_principal(context, cache, &in.client); ret = krb5_cc_get_principal(context, cache, &in.client);
if (ret && cache_is_default_for && principal) {
krb5_error_code ret2;
krb5_ccache def_ccache = NULL;
ret2 = krb5_cc_default(context, &def_ccache);
if (ret2 == 0)
ret2 = krb5_cc_get_principal(context, def_ccache, &in.client);
if (ret2 == 0 &&
krb5_principal_compare(context, principal, in.client)) {
krb5_cc_close(context, *cachep);
*cachep = def_ccache;
def_ccache = NULL;
ret = 0;
}
krb5_cc_close(context, def_ccache);
}
if (ret) { if (ret) {
krb5_warn(context, ret, "krb5_cc_get_principal"); krb5_warn(context, ret, "krb5_cc_get_principal");
return ret; return ret;
} }
if (principal && !krb5_principal_compare(context, principal, in.client)) {
char *ccname = NULL;
(void) krb5_cc_get_full_name(context, cache, &ccname);
krb5_errx(context, 1, "Credentials in cache %s do not match requested "
"principal", ccname ? ccname : "requested");
free(ccname);
}
if (server == NULL && if (server == NULL &&
krb5_principal_is_anonymous(context, in.client, krb5_principal_is_anonymous(context, in.client,
KRB5_ANON_MATCH_UNAUTHENTICATED)) KRB5_ANON_MATCH_UNAUTHENTICATED))
@@ -1082,8 +1106,8 @@ renew_func(void *ptr)
if (use_keytab || keytab_str) if (use_keytab || keytab_str)
expire += ctx->timeout; expire += ctx->timeout;
if (renew_expire > expire) { if (renew_expire > expire) {
ret = renew_validate(ctx->context, 1, validate_flag, ctx->ccache, ret = renew_validate(ctx->context, 1, validate_flag, &ctx->ccache,
server_str, ctx->ticket_life); NULL, FALSE, server_str, ctx->ticket_life);
} else { } else {
ret = get_new_tickets(ctx->context, ctx->principal, ctx->ccache, ret = get_new_tickets(ctx->context, ctx->principal, ctx->ccache,
ctx->ticket_life, 0, 0); ctx->ticket_life, 0, 0);
@@ -1198,24 +1222,30 @@ get_user_realm(krb5_context context)
} }
static void static void
get_princ(krb5_context context, krb5_principal *principal, const char *name) get_princ(krb5_context context,
krb5_principal *principal,
const char *ccname,
const char *name)
{ {
krb5_error_code ret; krb5_error_code ret = 0;
krb5_principal tmp; krb5_principal tmp;
int parseflags = 0; int parseflags = 0;
char *user_realm; char *user_realm;
if (name == NULL) { if (name == NULL) {
krb5_ccache ccache; krb5_ccache ccache = NULL;
/* If credential cache provides a client principal, use that. */ /* If credential cache provides a client principal, use that. */
if (krb5_cc_default(context, &ccache) == 0) { if (ccname)
ret = krb5_cc_resolve(context, ccname, &ccache);
else
ret = krb5_cc_default(context, &ccache);
if (ret == 0)
ret = krb5_cc_get_principal(context, ccache, principal); ret = krb5_cc_get_principal(context, ccache, principal);
krb5_cc_close(context, ccache); krb5_cc_close(context, ccache);
if (ret == 0) if (ret == 0)
return; return;
} }
}
user_realm = get_user_realm(context); user_realm = get_user_realm(context);
@@ -1320,59 +1350,6 @@ 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)
{ {
@@ -1387,6 +1364,7 @@ main(int argc, char **argv)
#endif #endif
krb5_boolean unique_ccache = FALSE; krb5_boolean unique_ccache = FALSE;
krb5_boolean historical_anon_pkinit = FALSE; krb5_boolean historical_anon_pkinit = FALSE;
krb5_boolean default_for = FALSE;
int anonymous_pkinit = FALSE; int anonymous_pkinit = FALSE;
setprogname(argv[0]); setprogname(argv[0]);
@@ -1463,7 +1441,7 @@ main(int argc, char **argv)
} else if (use_keytab || keytab_str) { } else if (use_keytab || keytab_str) {
get_princ_kt(context, &principal, argv[0]); get_princ_kt(context, &principal, argv[0]);
} else { } else {
get_princ(context, &principal, argv[0]); get_princ(context, &principal, cred_cache, argv[0]);
} }
if (fcache_version) if (fcache_version)
@@ -1479,28 +1457,34 @@ main(int argc, char **argv)
krb5_principal_get_realm(context, principal), krb5_principal_get_realm(context, principal),
"afslog", TRUE, &do_afslog); "afslog", TRUE, &do_afslog);
/*
* Cases:
*
* - use the given ccache
* - use a new unique ccache for running a command with (in this case we
* get to set KRB5CCNAME, so a new unique ccache makes sense)
* - use the default ccache for the given principal and maybe later switch
* the collection's default/primary to it
*
* The important thing is that, except for the case where we're running a
* command, we _can't set KRB5CCNAME_, and we can't expect the user to read
* our output and figure out to set it (we could have an output-for-shell-
* eval mode, like ssh-agent and such, but we don't). Therefore, in all
* cases where we can't set KRB5CCNAME we must do something that makes
* sense to the user, and that is to either initialize a given ccache, use
* the default, or use a subsidiary ccache named after the principal whose
* creds we're initializing.
*/
if (cred_cache) { if (cred_cache) {
/* Use the given ccache */
ret = krb5_cc_resolve(context, cred_cache, &ccache); ret = krb5_cc_resolve(context, cred_cache, &ccache);
} else if (default_for_flag) { } else if (argc > 1) {
char username[64];
char *user_realm;
if ((user_realm = get_user_realm(context)) == NULL)
user_realm = get_default_realm(context);
if (user_realm &&
krb5_principal_get_num_comp(context, principal) == 1 &&
strcmp(user_realm,
krb5_principal_get_realm(context, principal)) == 0 &&
roken_get_username(username, sizeof(username)) &&
strcmp(username,
krb5_principal_get_comp_string(context, principal, 0)) == 0)
ret = krb5_cc_default(context, &ccache);
else
ret = krb5_cc_default_for(context, principal, &ccache);
free(user_realm);
} else {
if (argc > 1) {
char s[1024]; char s[1024];
/*
* A command was given, so use a new unique ccache (and destroy it
* later).
*/
ret = krb5_cc_new_unique(context, NULL, NULL, &ccache); ret = krb5_cc_new_unique(context, NULL, NULL, &ccache);
if (ret) if (ret)
krb5_err(context, 1, ret, "creating cred cache"); krb5_err(context, 1, ret, "creating cred cache");
@@ -1510,30 +1494,10 @@ main(int argc, char **argv)
setenv("KRB5CCNAME", s, 1); setenv("KRB5CCNAME", s, 1);
unique_ccache = TRUE; unique_ccache = TRUE;
} else { } else {
ret = krb5_cc_cache_match(context, principal, &ccache); ret = krb5_cc_default_for(context, principal, &ccache);
if (ret) { default_for = TRUE;
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", ""));
@@ -1571,7 +1535,8 @@ main(int argc, char **argv)
if (renew_flag || validate_flag) { if (renew_flag || validate_flag) {
ret = renew_validate(context, renew_flag, validate_flag, ret = renew_validate(context, renew_flag, validate_flag,
ccache, server_str, ticket_life); &ccache, principal, default_for, server_str,
ticket_life);
#ifndef NO_AFS #ifndef NO_AFS
if (ret == 0 && server_str == NULL && do_afslog && k_hasafs()) if (ret == 0 && server_str == NULL && do_afslog && k_hasafs())

View File

@@ -538,7 +538,17 @@ acc_resolve(krb5_context context, krb5_ccache *id, const char *res, const char *
a = ACACHE(*id); a = ACACHE(*id);
if (sub) { if (sub) {
if (asprintf(&s, "%s:%s", res, sub) == -1 || s == NULL || /*
* For API there's no such thing as a collection name, there's only the
* default collection. Though we could perhaps put a CCAPI shared
* object path in the collection name.
*
* So we'll treat (res && !sub) and (!res && sub) as the same cases.
*
* See also the KCM ccache type, where we have similar considerations.
*/
if (asprintf(&s, "%s%s%s", res && *res ? res : "",
res && *res ? ":" : "", sub) == -1 || s == NULL ||
(a->cache_subsidiary = strdup(sub)) == NULL) { (a->cache_subsidiary = strdup(sub)) == NULL) {
acc_close(context, *id); acc_close(context, *id);
free(s); free(s);

View File

@@ -139,8 +139,8 @@ export KRB5_CONFIG
unset KRB5CCNAME unset KRB5CCNAME
rm -rf ${objdir}/kt ${objdir}/cc_dir rm -rf ${objdir}/kt ${objdir}/cc_dir
mkdir ${objdir}/cc_dir || { ec=1 ; eval "${testfailed}"; } mkdir ${objdir}/cc_dir || { ec=1 ; eval "${testfailed}"; }
${kinit} --cache-default-for foo@${R} || { ec=1 ; eval "${testfailed}"; } ${kinit} foo@${R} || { ec=1 ; eval "${testfailed}"; }
${kinit} --cache-default-for --no-change-default bar@${R} || { ec=1 ; eval "${testfailed}"; } ${kinit} --no-change-default bar@${R} || { ec=1 ; eval "${testfailed}"; }
primary=`cat ${objdir}/cc_dir/primary` primary=`cat ${objdir}/cc_dir/primary`
[ "x$primary" = xtkt.foo@${R} ] || { ec=1 ; eval "${testfailed}"; } [ "x$primary" = xtkt.foo@${R} ] || { ec=1 ; eval "${testfailed}"; }
${klist} -l | ${klist} -l |