diff options
Diffstat (limited to 'kuser/kinit.c')
| -rw-r--r-- | kuser/kinit.c | 680 |
1 files changed, 575 insertions, 105 deletions
diff --git a/kuser/kinit.c b/kuser/kinit.c index 61f962bbe7ed..a5e00c943392 100644 --- a/kuser/kinit.c +++ b/kuser/kinit.c @@ -34,8 +34,10 @@ */ #include "kuser_locl.h" +#undef HC_DEPRECATED_CRYPTO +#include <krb5_locl.h> -#ifdef __APPLE__ +#ifdef HAVE_FRAMEWORK_SECURITY #include <Security/Security.h> #endif @@ -61,9 +63,11 @@ int anonymous_flag = 0; char *lifetime = NULL; char *renew_life = NULL; char *server_str = NULL; +static krb5_principal tgs_service; char *cred_cache = NULL; char *start_str = NULL; -static int switch_cache_flags = 1; +static int default_for = 0; +static int switch_cache_flags = -1; struct getarg_strings etype_str; int use_keytab = 0; char *keytab_str = NULL; @@ -76,6 +80,10 @@ int pk_enterprise_flag = 0; struct hx509_certs_data *ent_user_id = NULL; char *pk_x509_anchors = NULL; int pk_use_enckey = 0; +int pk_anon_fast_armor = -1; +char *gss_preauth_mech = NULL; +char *gss_preauth_name = NULL; +char *kdc_hostname = NULL; static int canonicalize_flag = 0; static int enterprise_flag = 0; static int ok_as_delegate_flag = 0; @@ -181,7 +189,20 @@ static struct getargs args[] = { { "pk-use-enckey", 0, arg_flag, &pk_use_enckey, NP_("Use RSA encrypted reply (instead of DH)", ""), NULL }, + + { "pk-anon-fast-armor", 0, arg_flag, &pk_anon_fast_armor, + NP_("use unauthenticated anonymous PKINIT as FAST armor", ""), NULL }, #endif + + { "gss-mech", 0, arg_string, &gss_preauth_mech, + NP_("use GSS mechanism for pre-authentication", ""), NULL }, + + { "gss-name", 0, arg_string, &gss_preauth_name, + NP_("use distinct GSS identity for pre-authentication", ""), NULL }, + + { "kdc-hostname", 0, arg_string, &kdc_hostname, + NP_("KDC host name", ""), "hostname" }, + #ifndef NO_NTLM { "ntlm-domain", 0, arg_string, &ntlm_domain, NP_("NTLM domain", ""), "domain" }, @@ -190,6 +211,9 @@ static struct getargs args[] = { { "change-default", 0, arg_negative_flag, &switch_cache_flags, NP_("switch the default cache to the new credentials cache", ""), NULL }, + { "default-for-principal", 0, arg_flag, &default_for, + NP_("Use a default cache appropriate for the client principal", ""), NULL }, + { "ok-as-delegate", 0, arg_flag, &ok_as_delegate_flag, NP_("honor ok-as-delegate on tickets", ""), NULL }, @@ -218,18 +242,124 @@ usage(int ret) } static krb5_error_code +tgs_principal(krb5_context context, + krb5_ccache cache, + krb5_principal client, + krb5_const_realm tgs_realm, + krb5_principal *out_princ) +{ + krb5_error_code ret; + krb5_principal tgs_princ; + krb5_creds creds; + krb5_creds *tick; + krb5_flags options; + + ret = krb5_make_principal(context, &tgs_princ, tgs_realm, + KRB5_TGS_NAME, tgs_realm, NULL); + if (ret) + return ret; + + /* + * Don't fail-over to a different realm just because a TGT expired + */ + options = KRB5_GC_CACHED | KRB5_GC_EXPIRED_OK; + + memset(&creds, 0, sizeof(creds)); + creds.client = client; + creds.server = tgs_princ; + ret = krb5_get_credentials(context, options, cache, &creds, &tick); + if (ret == 0) { + krb5_free_creds(context, tick); + *out_princ = tgs_princ; + } else { + krb5_free_principal(context, tgs_princ); + } + + return ret; +} + + +/* + * Try TGS specified with '-S', + * then TGS of client realm, + * then if fallback is FALSE: fail, + * otherwise try TGS of default realm, + * and finally first TGT in ccache. + */ +static krb5_error_code get_server(krb5_context context, + krb5_ccache cache, krb5_principal client, const char *server, + krb5_boolean fallback, krb5_principal *princ) { + krb5_error_code ret = 0; krb5_const_realm realm; - if (server) - return krb5_parse_name(context, server, princ); + krb5_realm def_realm; + krb5_cc_cursor cursor; + krb5_creds creds; + const char *pcomp; + + if (tgs_service) + goto done; + if (server) { + ret = krb5_parse_name(context, server, &tgs_service); + goto done; + } + + /* Try the client realm first */ realm = krb5_principal_get_realm(context, client); - return krb5_make_principal(context, princ, realm, - KRB5_TGS_NAME, realm, NULL); + ret = tgs_principal(context, cache, client, realm, &tgs_service); + if (ret == 0 || ret != KRB5_CC_NOTFOUND) + goto done; + + if (!fallback) + return ret; + + /* Next try the default realm */ + ret = krb5_get_default_realm(context, &def_realm); + if (ret) + return ret; + ret = tgs_principal(context, cache, client, def_realm, &tgs_service); + free(def_realm); + if (ret == 0 || ret != KRB5_CC_NOTFOUND) + goto done; + + /* Finally try the first TGT with instance == realm in the cache */ + ret = krb5_cc_start_seq_get(context, cache, &cursor); + if (ret) + return ret; + + for (/**/; ret == 0; krb5_free_cred_contents (context, &creds)) { + + ret = krb5_cc_next_cred(context, cache, &cursor, &creds); + if (ret) + break; + if (creds.server->name.name_string.len != 2) + continue; + pcomp = krb5_principal_get_comp_string(context, creds.server, 0); + if (strcmp(pcomp, KRB5_TGS_NAME) != 0) + continue; + realm = krb5_principal_get_realm(context, creds.server); + pcomp = krb5_principal_get_comp_string(context, creds.server, 1); + if (strcmp(realm, pcomp) != 0) + continue; + ret = krb5_copy_principal(context, creds.server, &tgs_service); + break; + } + if (ret == KRB5_CC_END) { + ret = KRB5_CC_NOTFOUND; + krb5_set_error_message(context, ret, + N_("Credential cache contains no TGTs", "")); + } + krb5_cc_end_seq_get(context, cache, &cursor); + +done: + if (!ret) + ret = krb5_copy_principal(context, tgs_service, princ); + return ret; } static krb5_error_code @@ -311,29 +441,57 @@ static krb5_error_code renew_validate(krb5_context context, int renew, int validate, - krb5_ccache cache, + krb5_ccache *cachep, + krb5_const_principal principal, + krb5_boolean cache_is_default_for, const char *server, krb5_deltat life) { krb5_error_code ret; krb5_ccache tempccache = NULL; + krb5_ccache cache = *cachep; krb5_creds in, *out = NULL; krb5_kdc_flags flags; memset(&in, 0, sizeof(in)); 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); + cache = *cachep = def_ccache; + def_ccache = NULL; + ret = 0; + } + krb5_cc_close(context, def_ccache); + } if (ret) { krb5_warn(context, ret, "krb5_cc_get_principal"); 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 && krb5_principal_is_anonymous(context, in.client, KRB5_ANON_MATCH_UNAUTHENTICATED)) ret = get_anon_pkinit_tgs_name(context, cache, &in.server); else - ret = get_server(context, in.client, server, &in.server); + ret = get_server(context, cache, in.client, server, TRUE, &in.server); if (ret) { krb5_warn(context, ret, "get_server"); goto out; @@ -344,7 +502,7 @@ renew_validate(krb5_context context, * no need to check the error here, it's only to be * friendly to the user */ - krb5_get_credentials(context, KRB5_GC_CACHED, cache, &in, &out); + (void) krb5_get_credentials(context, KRB5_GC_CACHED, cache, &in, &out); } flags.i = 0; @@ -427,6 +585,106 @@ out: return ret; } +static krb5_error_code +make_wellknown_name(krb5_context context, + krb5_const_realm realm, + const char *instance, + krb5_principal *principal) +{ + krb5_error_code ret; + + ret = krb5_make_principal(context, principal, realm, + KRB5_WELLKNOWN_NAME, instance, NULL); + if (ret == 0) + krb5_principal_set_type(context, *principal, KRB5_NT_WELLKNOWN); + + return ret; +} + +static krb5_error_code +acquire_gss_cred(krb5_context context, + krb5_const_principal client, + krb5_deltat life, + const char *passwd, + gss_cred_id_t *cred, + gss_OID *mech) +{ + krb5_error_code ret; + OM_uint32 major, minor; + gss_name_t name = GSS_C_NO_NAME; + gss_key_value_element_desc cred_element; + gss_key_value_set_desc cred_store; + gss_OID_set_desc mechs; + + *cred = GSS_C_NO_CREDENTIAL; + *mech = GSS_C_NO_OID; + + if (gss_preauth_mech) { + *mech = gss_name_to_oid(gss_preauth_mech); + if (*mech == GSS_C_NO_OID) + return EINVAL; + } + + if (gss_preauth_name) { + gss_buffer_desc buf; + + buf.value = gss_preauth_name; + buf.length = strlen(gss_preauth_name); + + major = gss_import_name(&minor, &buf, GSS_C_NT_USER_NAME, &name); + ret = _krb5_gss_map_error(major, minor); + } else if (!krb5_principal_is_federated(context, client)) { + ret = _krb5_gss_pa_unparse_name(context, client, &name); + } else { + /* + * WELLKNOWN/FEDERATED is used a placeholder where the user + * did not specify either a Kerberos credential or a GSS-API + * initiator name. It avoids the expense of acquiring a default + * credential purely to interrogate the credential name. + */ + name = GSS_C_NO_NAME; + ret = 0; + } + if (ret) + goto out; + + cred_store.count = 1; + cred_store.elements = &cred_element; + + if (passwd && passwd[0]) { + cred_element.key = "password"; + cred_element.value = passwd; + } else if (keytab_str) { + cred_element.key = "client_keytab", + cred_element.value = keytab_str; + } else { + cred_store.count = 0; + } + + if (*mech) { + mechs.count = 1; + mechs.elements = (gss_OID)*mech; + } + + major = gss_acquire_cred_from(&minor, + name, + life ? life : GSS_C_INDEFINITE, + *mech ? &mechs : GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &cred_store, + cred, + NULL, + NULL); + if (major != GSS_S_COMPLETE) { + ret = _krb5_gss_map_error(major, minor); + goto out; + } + +out: + gss_release_name(&minor, &name); + return ret; +} + #ifndef NO_NTLM static krb5_error_code @@ -481,6 +739,10 @@ get_new_tickets(krb5_context context, krb5_init_creds_context ctx = NULL; krb5_get_init_creds_opt *opt = NULL; krb5_prompter_fct prompter = krb5_prompter_posix; + gss_cred_id_t gss_cred = GSS_C_NO_CREDENTIAL; + gss_OID gss_mech = GSS_C_NO_OID; + krb5_principal federated_name = NULL; + #ifndef NO_NTLM struct ntlm_buf ntlmkey; memset(&ntlmkey, 0, sizeof(ntlmkey)); @@ -498,7 +760,7 @@ get_new_tickets(krb5_context context, else f = fopen(password_file, "r"); if (f == NULL) { - krb5_warnx(context, "Failed to open the password file %s", + krb5_warnx(context, N_("Failed to open the password file %s", ""), password_file); return errno; } @@ -506,7 +768,8 @@ get_new_tickets(krb5_context context, if (fgets(passwd, sizeof(passwd), f) == NULL) { krb5_warnx(context, N_("Failed to read password from file %s", ""), password_file); - fclose(f); + if (f != stdin) + fclose(f); return EINVAL; /* XXX Need a better error */ } if (f != stdin) @@ -514,31 +777,83 @@ get_new_tickets(krb5_context context, passwd[strcspn(passwd, "\n")] = '\0'; } -#ifdef __APPLE__ +#ifdef HAVE_FRAMEWORK_SECURITY if (passwd[0] == '\0') { + enum querykey { + qk_class, qk_matchlimit, qk_service, qk_account, qk_secreturndata, + }; + const void *querykeys[] = { + [qk_class] = kSecClass, + [qk_matchlimit] = kSecMatchLimit, + [qk_service] = kSecAttrService, + [qk_account] = kSecAttrAccount, + [qk_secreturndata] = kSecReturnData, + }; + const void *queryargs[] = { + [qk_class] = kSecClassGenericPassword, + [qk_matchlimit] = kSecMatchLimitOne, + [qk_service] = NULL, /* filled in later */ + [qk_account] = NULL, /* filled in later */ + [qk_secreturndata] = kCFBooleanTrue, + }; + CFStringRef service_ref = NULL; + CFStringRef account_ref = NULL; + CFDictionaryRef query_ref = NULL; const char *realm; OSStatus osret; - UInt32 length; - void *buffer; - char *name; + char *name = NULL; + CFTypeRef item_ref = NULL; + CFDataRef item; + CFIndex length; realm = krb5_principal_get_realm(context, principal); ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_NO_REALM, &name); if (ret) - goto nopassword; - - osret = SecKeychainFindGenericPassword(NULL, strlen(realm), realm, - strlen(name), name, - &length, &buffer, NULL); + goto fail; + + service_ref = CFStringCreateWithCString(kCFAllocatorDefault, realm, + kCFStringEncodingUTF8); + if (service_ref == NULL) + goto fail; + + account_ref = CFStringCreateWithCString(kCFAllocatorDefault, name, + kCFStringEncodingUTF8); + if (account_ref == NULL) + goto fail; + + queryargs[qk_service] = service_ref; + queryargs[qk_account] = account_ref; + query_ref = CFDictionaryCreate(kCFAllocatorDefault, + querykeys, queryargs, + /*numValues*/sizeof(querykeys)/sizeof(querykeys[0]), + /*keyCallbacks*/NULL, /*valueCallbacks*/NULL); + if (query_ref == NULL) + goto fail; + + osret = SecItemCopyMatching(query_ref, &item_ref); + if (osret != noErr) + goto fail; + + item = item_ref; + length = CFDataGetLength(item); + if (length >= sizeof(passwd) - 1) + goto fail; + + CFDataGetBytes(item, CFRangeMake(0, length), (UInt8 *)passwd); + passwd[length] = '\0'; + + fail: + if (item_ref) + CFRelease(item_ref); + if (query_ref) + CFRelease(query_ref); + if (account_ref) + CFRelease(account_ref); + if (service_ref) + CFRelease(service_ref); free(name); - if (osret == noErr && length < sizeof(passwd) - 1) { - memcpy(passwd, buffer, length); - passwd[length] = '\0'; - } - nopassword: - do { } while(0); } #endif @@ -567,6 +882,8 @@ get_new_tickets(krb5_context context, if (pk_enterprise_flag || enterprise_flag || canonicalize_flag || windows_flag) krb5_get_init_creds_opt_set_win2k(context, opt, TRUE); if (pk_user_id || ent_user_id || anonymous_pkinit) { + if (pk_anon_fast_armor == -1) + pk_anon_fast_armor = 0; ret = krb5_get_init_creds_opt_set_pkinit(context, opt, principal, pk_user_id, @@ -630,6 +947,29 @@ get_new_tickets(krb5_context context, etype_str.num_strings); } + if (gss_preauth_mech || gss_preauth_name) { + ret = acquire_gss_cred(context, principal, ticket_life, + passwd, &gss_cred, &gss_mech); + if (ret) + goto out; + + /* + * The principal specified on the command line is used as the GSS-API + * initiator name, unless the --gss-name option was present, in which + * case the initiator name is specified independently. + */ + if (gss_preauth_name == NULL) { + krb5_const_realm realm = krb5_principal_get_realm(context, principal); + + ret = make_wellknown_name(context, realm, + KRB5_FEDERATED_NAME, &federated_name); + if (ret) + goto out; + + principal = federated_name; + } + } + ret = krb5_init_creds_init(context, principal, prompter, NULL, start_time, opt, &ctx); if (ret) { krb5_warn(context, ret, "krb5_init_creds_init"); @@ -644,23 +984,63 @@ get_new_tickets(krb5_context context, } } + if (kdc_hostname) { + ret = krb5_init_creds_set_kdc_hostname(context, ctx, kdc_hostname); + if (ret) { + krb5_warn(context, ret, "krb5_init_creds_set_kdc_hostname"); + goto out; + } + } + + if (anonymous_flag && pk_anon_fast_armor == -1) + pk_anon_fast_armor = 0; + if (!gss_preauth_mech && anonymous_flag && pk_anon_fast_armor) { + krb5_warnx(context, N_("Ignoring --pk-anon-fast-armor because " + "--anonymous given", "")); + pk_anon_fast_armor = 0; + } + if (fast_armor_cache_string) { - krb5_ccache fastid; - + krb5_ccache fastid = NULL; + + if (pk_anon_fast_armor > 0) + krb5_errx(context, 1, + N_("cannot specify FAST armor cache with FAST " + "anonymous PKINIT option", "")); + pk_anon_fast_armor = 0; + ret = krb5_cc_resolve(context, fast_armor_cache_string, &fastid); if (ret) { krb5_warn(context, ret, "krb5_cc_resolve(FAST cache)"); goto out; } - + ret = krb5_init_creds_set_fast_ccache(context, ctx, fastid); if (ret) { krb5_warn(context, ret, "krb5_init_creds_set_fast_ccache"); goto out; } + } else if (pk_anon_fast_armor == -1) { + ret = _krb5_init_creds_set_fast_anon_pkinit_optimistic(context, ctx); + if (ret) { + krb5_warn(context, ret, "_krb5_init_creds_set_fast_anon_pkinit_optimistic"); + goto out; + } + } else if (pk_anon_fast_armor) { + ret = krb5_init_creds_set_fast_anon_pkinit(context, ctx); + if (ret) { + krb5_warn(context, ret, "krb5_init_creds_set_fast_anon_pkinit"); + goto out; + } } - if (use_keytab || keytab_str) { + if (gss_mech != GSS_C_NO_OID) { + ret = krb5_gss_set_init_creds(context, ctx, gss_cred, gss_mech); + if (ret) { + krb5_warn(context, ret, "krb5_gss_set_init_creds"); + goto out; + } + } else if (use_keytab || keytab_str) { ret = krb5_init_creds_set_keytab(context, ctx, kt); if (ret) { krb5_warn(context, ret, "krb5_init_creds_set_keytab"); @@ -780,6 +1160,18 @@ get_new_tickets(krb5_context context, goto out; } + ret = krb5_init_creds_store_config(context, ctx, tempccache); + if (ret) { + krb5_warn(context, ret, "krb5_init_creds_store_config"); + goto out; + } + + ret = krb5_init_creds_warn_user(context, ctx); + if (ret) { + krb5_warn(context, ret, "krb5_init_creds_warn_user"); + goto out; + } + krb5_init_creds_free(context, ctx); ctx = NULL; @@ -823,12 +1215,16 @@ get_new_tickets(krb5_context context, } out: + { + OM_uint32 minor; + gss_release_cred(&minor, &gss_cred); + } + krb5_free_principal(context, federated_name); krb5_get_init_creds_opt_free(context, opt); if (ctx) krb5_init_creds_free(context, ctx); if (tempccache) krb5_cc_destroy(context, tempccache); - if (enctype) free(enctype); @@ -854,7 +1250,10 @@ ticket_lifetime(krb5_context context, krb5_ccache cache, krb5_principal client, krb5_warn(context, ret, "krb5_cc_get_principal"); return 0; } - ret = get_server(context, in_cred.client, server, &in_cred.server); + + /* Determine TGS principal without fallback */ + ret = get_server(context, cache, in_cred.client, server, FALSE, + &in_cred.server); if (ret) { krb5_free_principal(context, in_cred.client); krb5_warn(context, ret, "get_server"); @@ -916,16 +1315,18 @@ update_siginfo_msg(time_t exp, const char *srv) #ifdef HAVE_SIGACTION static void -handle_siginfo(int sig) +handler(int sig) { - struct iovec iov[2]; + if (sig == SIGINFO) { + struct iovec iov[2]; - iov[0].iov_base = rk_UNCONST(siginfo_msg); - iov[0].iov_len = strlen(siginfo_msg); - iov[1].iov_base = "\n"; - iov[1].iov_len = 1; + iov[0].iov_base = rk_UNCONST(siginfo_msg); + iov[0].iov_len = strlen(siginfo_msg); + iov[1].iov_base = "\n"; + iov[1].iov_len = 1; - writev(STDERR_FILENO, iov, sizeof(iov)/sizeof(iov[0])); + writev(STDERR_FILENO, iov, sizeof(iov)/sizeof(iov[0])); + } /* else ignore interrupts; our progeny will not ignore them */ } #endif @@ -935,6 +1336,7 @@ struct renew_ctx { krb5_principal principal; krb5_deltat ticket_life; krb5_deltat timeout; + int anonymous_pkinit; }; static time_t @@ -967,19 +1369,21 @@ renew_func(void *ptr) if (use_keytab || keytab_str) expire += ctx->timeout; if (renew_expire > expire) { - ret = renew_validate(ctx->context, 1, validate_flag, ctx->ccache, - server_str, ctx->ticket_life); + ret = renew_validate(ctx->context, 1, validate_flag, &ctx->ccache, + NULL, FALSE, server_str, ctx->ticket_life); } else { ret = get_new_tickets(ctx->context, ctx->principal, ctx->ccache, - ctx->ticket_life, 0, 0); + ctx->ticket_life, 0, ctx->anonymous_pkinit); } - expire = ticket_lifetime(ctx->context, ctx->ccache, ctx->principal, - server_str, &renew_expire); + if (ret == 0) { + expire = ticket_lifetime(ctx->context, ctx->ccache, ctx->principal, + server_str, &renew_expire); #ifndef NO_AFS - if (ret == 0 && server_str == NULL && do_afslog && k_hasafs()) - krb5_afslog(ctx->context, ctx->ccache, NULL, NULL); + if (server_str == NULL && do_afslog && k_hasafs()) + krb5_afslog(ctx->context, ctx->ccache, NULL, NULL); #endif + } update_siginfo_msg(expire, server_str); @@ -1083,29 +1487,35 @@ get_user_realm(krb5_context context) } 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; int parseflags = 0; char *user_realm; if (name == NULL) { - krb5_ccache ccache; + krb5_ccache ccache = NULL; /* 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); - krb5_cc_close(context, ccache); - if (ret == 0) - return; - } + krb5_cc_close(context, ccache); + if (ret == 0) + return; } user_realm = get_user_realm(context); if (name) { - if (canonicalize_flag || enterprise_flag) + if (enterprise_flag) parseflags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE; parse_name_realm(context, name, parseflags, user_realm, &tmp); @@ -1282,9 +1692,13 @@ main(int argc, char **argv) ret = krb5_init_context(&context); if (ret == KRB5_CONFIG_BADFORMAT) - errx(1, "krb5_init_context failed to parse configuration file"); + krb5_err(context, 1, ret, "Failed to parse configuration file"); + else if (ret == EISDIR) + /* We use EISDIR to mean "not a regular file" */ + krb5_errx(context, 1, + "Failed to read configuration file: not a regular file"); else if (ret) - errx(1, "krb5_init_context failed: %d", ret); + krb5_err(context, 1, ret, "Failed to read configuration file"); if (getarg(args, sizeof(args) / sizeof(args[0]), argc, argv, &optidx)) usage(1); @@ -1324,17 +1738,29 @@ main(int argc, char **argv) krb5_err(context, 1, ret, "krb5_pk_enterprise_certs"); pk_user_id = NULL; + if (pk_anon_fast_armor > 0) + krb5_warnx(context, N_("Ignoring --pk-anon-fast-armor " + "because --pk-user given", "")); + pk_anon_fast_armor = 0; + } else if (argc && argv[0][0] == '@' && + (gss_preauth_mech || anonymous_flag)) { + const char *instance; + + if (gss_preauth_mech) { + instance = KRB5_FEDERATED_NAME; + } else if (anonymous_flag) { + instance = KRB5_ANON_NAME; + anonymous_pkinit = TRUE; + } - } else if (anonymous_flag && argc && argv[0][0] == '@') { - /* If principal argument as @REALM, try anonymous PKINIT */ - - ret = krb5_make_principal(context, &principal, &argv[0][1], - KRB5_WELLKNOWN_NAME, KRB5_ANON_NAME, - NULL); + ret = make_wellknown_name(context, &argv[0][1], instance, &principal); if (ret) - krb5_err(context, 1, ret, "krb5_make_principal"); - krb5_principal_set_type(context, principal, KRB5_NT_WELLKNOWN); - anonymous_pkinit = TRUE; + krb5_err(context, 1, ret, "make_wellknown_name"); + if (!gss_preauth_mech && pk_anon_fast_armor > 1) { + krb5_warnx(context, N_("Ignoring --pk-anon-fast-armor " + "because --anonymous given", "")); + pk_anon_fast_armor = 0; + } } else if (anonymous_flag && historical_anon_pkinit) { char *realm = argc == 0 ? get_default_realm(context) : argv[0][0] == '@' ? &argv[0][1] : argv[0]; @@ -1347,8 +1773,17 @@ main(int argc, char **argv) anonymous_pkinit = TRUE; } else if (use_keytab || keytab_str) { get_princ_kt(context, &principal, argv[0]); + } else if (gss_preauth_mech && argc == 0 && gss_preauth_name == NULL) { + /* + * Use the federated name as a placeholder if we have neither a Kerberos + * nor a GSS-API client name, and we are performing GSS-API preauth. + */ + ret = make_wellknown_name(context, get_default_realm(context), + KRB5_FEDERATED_NAME, &principal); + if (ret) + krb5_err(context, 1, ret, "make_wellknown_name"); } else { - get_princ(context, &principal, argv[0]); + get_princ(context, &principal, cred_cache, argv[0]); } if (fcache_version) @@ -1364,43 +1799,73 @@ main(int argc, char **argv) krb5_principal_get_realm(context, principal), "afslog", TRUE, &do_afslog); - if (cred_cache) + /* + * 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 as requested and do + * not later switch the collection's default/primary to it + * - use the default cache, possibly a new unique one that later gets + * switched 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) { + /* Use the given ccache */ ret = krb5_cc_resolve(context, cred_cache, &ccache); - else { - if (argc > 1) { - 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; - } - } - } + } else if (argc > 1) { + 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); + 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; + switch_cache_flags = 0; + } else if (default_for) { + ret = krb5_cc_default_for(context, principal, &ccache); + if (switch_cache_flags == -1) + switch_cache_flags = 0; + } 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) && + strcmp(type, "FILE")) { + krb5_cc_close(context, ccache); + ret = get_switched_ccache(context, type, principal, + &ccache); + if (ret == 0) + unique_ccache = TRUE; + } + } } if (ret) krb5_err(context, 1, ret, N_("resolving credentials cache", "")); @@ -1439,7 +1904,9 @@ main(int argc, char **argv) if (renew_flag || validate_flag) { ret = renew_validate(context, renew_flag, validate_flag, - ccache, server_str, ticket_life); + &ccache, principal, + default_for ? TRUE : FALSE, server_str, + ticket_life); #ifndef NO_AFS if (ret == 0 && server_str == NULL && do_afslog && k_hasafs()) @@ -1476,13 +1943,16 @@ main(int argc, char **argv) ctx.principal = principal; ctx.ticket_life = ticket_life; ctx.timeout = timeout; + ctx.anonymous_pkinit = anonymous_pkinit; #ifdef HAVE_SIGACTION memset(&sa, 0, sizeof(sa)); sigemptyset(&sa.sa_mask); - sa.sa_handler = handle_siginfo; + sa.sa_handler = handler; sigaction(SIGINFO, &sa, NULL); + sigaction(SIGINT, &sa, NULL); + sigaction(SIGQUIT, &sa, NULL); #endif ret = simple_execvp_timed(argv[1], argv+1, |
