From 5843ad321944a028f6dee7e1fd4f9381c4953d07 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Thu, 18 Nov 2010 12:48:05 +0100 Subject: Add support for FAST in krb5 provider --- src/providers/krb5/krb5_auth.c | 12 +- src/providers/krb5/krb5_child.c | 313 ++++++++++++++++++++++++++++++++++++++- src/providers/krb5/krb5_common.c | 24 ++- src/providers/krb5/krb5_common.h | 6 +- src/providers/krb5/krb5_init.c | 2 +- 5 files changed, 344 insertions(+), 13 deletions(-) (limited to 'src/providers/krb5') diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c index 515c181b..e6b680ea 100644 --- a/src/providers/krb5/krb5_auth.c +++ b/src/providers/krb5/krb5_auth.c @@ -646,14 +646,16 @@ static void krb5_find_ccache_step(struct tevent_req *req) } /* We need to keep the root privileges to read the keytab file if - * validation is enabled, otherwise we can drop them and run krb5_child - * with user privileges. + * validation or FAST is enabled, otherwise we can drop them and run + * krb5_child with user privileges. * If we are offline we want to create an empty ccache file. In this * case we can drop the privileges, too. */ - if (!dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) || kr->is_offline) { - kr->run_as_user = true; - } else { + if ((dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) || + kr->krb5_ctx->use_fast) && + !kr->is_offline) { kr->run_as_user = false; + } else { + kr->run_as_user = true; } subreq = handle_child_send(state, state->ev, kr); diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c index 86115957..44853e76 100644 --- a/src/providers/krb5/krb5_child.c +++ b/src/providers/krb5/krb5_child.c @@ -90,6 +90,7 @@ struct krb5_req { char *ccname; char *keytab; bool validate; + char *fast_ccname; const char *upn; uid_t uid; @@ -491,6 +492,85 @@ static errno_t add_ticket_times_to_response(struct krb5_req *kr) return ret; } +static krb5_error_code find_principal_in_keytab(krb5_context ctx, + krb5_keytab keytab, + const char *realm, + krb5_principal *princ) +{ + krb5_error_code kerr; + krb5_error_code kt_err; + krb5_error_code kerr_d; + krb5_kt_cursor cursor; + krb5_keytab_entry entry; + bool principal_found = false; + + memset(&cursor, 0, sizeof(cursor)); + kerr = krb5_kt_start_seq_get(ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(1, ("krb5_kt_start_seq_get failed.\n")); + KRB5_DEBUG(1, kerr); + return kerr; + } + + /* We look for the first entry from our realm or take the last one */ + memset(&entry, 0, sizeof(entry)); + while ((kt_err = krb5_kt_next_entry(ctx, keytab, &entry, &cursor)) == 0) { + if (krb5_princ_realm(ctx, entry.principal)->length == strlen(realm) && + strncmp(krb5_princ_realm(ctx, entry.principal)->data, realm, + krb5_princ_realm(ctx, entry.principal)->length) == 0) { + DEBUG(9, ("Found keytab entry with the realm of the credential.\n")); + principal_found = true; + break; + } + + kerr = krb5_free_keytab_entry_contents(ctx, &entry); + if (kerr != 0) { + DEBUG(1, ("Failed to free keytab entry.\n")); + } + memset(&entry, 0, sizeof(entry)); + } + + /* Close the keytab here. Even though we're using cursors, the file + * handle is stored in the krb5_keytab structure, and it gets + * overwritten by other keytab calls, creating a leak. */ + kerr = krb5_kt_end_seq_get(ctx, keytab, &cursor); + if (kerr != 0) { + DEBUG(1, ("krb5_kt_end_seq_get failed.\n")); + goto done; + } + + if (!principal_found) { + kerr = KRB5_KT_NOTFOUND; + DEBUG(1, ("No principal from realm [%s] found in keytab.\n", realm)); + goto done; + } + + /* check if we got any errors from krb5_kt_next_entry */ + if (kt_err != 0 && kt_err != KRB5_KT_END) { + DEBUG(1, ("Error while reading keytab.\n")); + KRB5_DEBUG(1, kerr); + goto done; + } + + kerr = krb5_copy_principal(ctx, entry.principal, princ); + if (kerr != 0) { + DEBUG(1, ("krb5_copy_principal failed.\n")); + KRB5_DEBUG(1, kerr); + goto done; + } + + kerr = 0; + +done: + kerr_d = krb5_free_keytab_entry_contents(ctx, &entry); + if (kerr_d != 0) { + DEBUG(1, ("Failed to free keytab entry.\n")); + KRB5_DEBUG(1, kerr_d); + } + + return kerr; +} + static krb5_error_code validate_tgt(struct krb5_req *kr) { krb5_error_code kerr; @@ -584,6 +664,43 @@ done: } +static krb5_error_code get_and_save_tgt_with_keytab(krb5_context ctx, + krb5_principal princ, + krb5_keytab keytab, + char *ccname) +{ + krb5_error_code kerr = 0; + krb5_creds creds; + krb5_get_init_creds_opt options; + + memset(&creds, 0, sizeof(creds)); + memset(&options, 0, sizeof(options)); + + krb5_get_init_creds_opt_set_address_list(&options, NULL); + krb5_get_init_creds_opt_set_forwardable(&options, 0); + krb5_get_init_creds_opt_set_proxiable(&options, 0); + + kerr = krb5_get_init_creds_keytab(ctx, &creds, princ, keytab, 0, NULL, + &options); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + return kerr; + } + + kerr = create_ccache_file(ctx, princ, ccname, &creds); + if (kerr != 0) { + KRB5_DEBUG(1, kerr); + goto done; + } + kerr = 0; + +done: + krb5_free_cred_contents(ctx, &creds); + + return kerr; + +} + static krb5_error_code get_and_save_tgt(struct krb5_req *kr, char *password) { @@ -612,16 +729,19 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr, return kerr; } + } else { + DEBUG(9, ("TGT validation is disabled.\n")); + } + + if (kr->validate || kr->fast_ccname != NULL) { /* We drop root privileges which were needed to read the keytab file - * for the validation validation of the credentials here to run the + * for the validation of the credentials or for FAST here to run the * ccache I/O operations with user privileges. */ ret = become_user(kr->uid, kr->gid); if (ret != EOK) { DEBUG(1, ("become_user failed.\n")); return ret; } - } else { - DEBUG(9, ("TGT validation is disabled.\n")); } kerr = create_ccache_file(kr->ctx, kr->princ, kr->ccname, kr->creds); @@ -958,7 +1078,7 @@ static errno_t renew_tgt_child(int fd, struct krb5_req *kr) DEBUG(9, ("TGT validation is disabled.\n")); } - if (kr->validate) { + if (kr->validate || kr->fast_ccname != NULL) { /* We drop root privileges which were needed to read the keytab file * for the validation of the credentials or for FAST here to run the * ccache I/O operations with user privileges. */ @@ -1126,10 +1246,153 @@ static int krb5_cleanup(void *ptr) return EOK; } +static krb5_error_code get_tgt_times(krb5_context ctx, const char *ccname, + krb5_principal server_principal, + krb5_principal client_principal, + krb5_ticket_times *tgtt) +{ + krb5_error_code krberr; + krb5_ccache ccache = NULL; + krb5_creds mcred; + krb5_creds cred; + + krberr = krb5_cc_resolve(ctx, ccname, &ccache); + if (krberr != 0) { + DEBUG(1, ("krb5_cc_resolve failed.\n")); + goto done; + } + + memset(&mcred, 0, sizeof(mcred)); + memset(&cred, 0, sizeof(mcred)); + + mcred.server = server_principal; + mcred.client = client_principal; + + krberr = krb5_cc_retrieve_cred(ctx, ccache, 0, &mcred, &cred); + if (krberr != 0) { + DEBUG(1, ("krb5_cc_retrieve_cred failed.\n")); + krberr = 0; + goto done; + } + + tgtt->authtime = cred.times.authtime; + tgtt->starttime = cred.times.starttime; + tgtt->endtime = cred.times.endtime; + tgtt->renew_till = cred.times.renew_till; + + krb5_free_cred_contents(ctx, &cred); + + krberr = 0; + +done: + if (ccache != NULL) { + krb5_cc_close(ctx, ccache); + } + + return krberr; +} + +static krb5_error_code check_fast_ccache(krb5_context ctx, const char *realm, + const char *keytab_name, + TALLOC_CTX *mem_ctx, + char **fast_ccname) +{ + TALLOC_CTX *tmp_ctx = NULL; + krb5_error_code kerr; + char *ccname; + char *server_name; + krb5_ticket_times tgtt; + krb5_keytab keytab = NULL; + krb5_principal client_princ = NULL; + krb5_principal server_princ = NULL; + + tmp_ctx = talloc_new(NULL); + if (tmp_ctx == NULL) { + DEBUG(1, ("talloc_new failed.\n")); + kerr = ENOMEM; + goto done; + } + + ccname = talloc_asprintf(tmp_ctx, "FILE:%s/fast_ccache_%s", DB_PATH, realm); + if (ccname == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + kerr = ENOMEM; + goto done; + } + + if (keytab_name != NULL) { + kerr = krb5_kt_resolve(ctx, keytab_name, &keytab); + } else { + kerr = krb5_kt_default(ctx, &keytab); + } + if (kerr) { + DEBUG(0, ("Failed to read keytab file [%s].\n", + keytab_name != NULL ? keytab_name : "(default)")); + goto done; + } + + kerr = find_principal_in_keytab(ctx, keytab, realm, &client_princ); + if (kerr != 0) { + DEBUG(1, ("find_principal_in_keytab failed.\n")); + goto done; + } + + server_name = talloc_asprintf(tmp_ctx, "krbtgt/%s@%s", realm, realm); + if (server_name == NULL) { + DEBUG(1, ("talloc_asprintf failed.\n")); + kerr = ENOMEM; + goto done; + } + + kerr = krb5_parse_name(ctx, server_name, &server_princ); + if (kerr != 0) { + DEBUG(1, ("krb5_parse_name failed.\n")); + goto done; + } + + memset(&tgtt, 0, sizeof(tgtt)); + kerr = get_tgt_times(ctx, ccname, server_princ, client_princ, &tgtt); + if (kerr == 0) { + if (tgtt.endtime > time(NULL)) { + DEBUG(5, ("FAST TGT is still valid.\n")); + goto done; + } + } + + kerr = get_and_save_tgt_with_keytab(ctx, client_princ, keytab, ccname); + if (kerr != 0) { + DEBUG(1, ("get_and_save_tgt_with_keytab failed.\n")); + goto done; + } + + + kerr = 0; + +done: + if (client_princ != NULL) { + krb5_free_principal(ctx, client_princ); + } + if (server_princ != NULL) { + krb5_free_principal(ctx, server_princ); + } + + if (kerr == 0) { + *fast_ccname = talloc_steal(mem_ctx, ccname); + } + talloc_free(tmp_ctx); + + if (keytab != NULL) { + krb5_kt_close(ctx, keytab); + } + + return kerr; +} + static int krb5_child_setup(struct krb5_req *kr, uint32_t offline) { krb5_error_code kerr = 0; char *lifetime_str; + char *use_fast_str; krb5_deltat lifetime; kr->krb5_ctx = talloc_zero(kr, struct krb5_child_ctx); @@ -1239,6 +1502,48 @@ static int krb5_child_setup(struct krb5_req *kr, uint32_t offline) krb5_get_init_creds_opt_set_tkt_life(kr->options, lifetime); } + if (!offline) { + use_fast_str = getenv(SSSD_KRB5_USE_FAST); + if (use_fast_str == NULL || strcasecmp(use_fast_str, "never") == 0) { + DEBUG(9, ("Not using FAST.\n")); + } else if (strcasecmp(use_fast_str, "try") == 0 || + strcasecmp(use_fast_str, "demand") == 0) { + kerr = check_fast_ccache(kr->ctx, kr->krb5_ctx->realm, kr->keytab, + kr, &kr->fast_ccname); + if (kerr != 0) { + DEBUG(1, ("check_fast_ccache failed.\n")); + KRB5_DEBUG(1, kerr); + goto failed; + } + + kerr = sss_krb5_get_init_creds_opt_set_fast_ccache_name(kr->ctx, + kr->options, + kr->fast_ccname); + if (kerr != 0) { + DEBUG(1, ("sss_krb5_get_init_creds_opt_set_fast_ccache_name " + "failed.\n")); + KRB5_DEBUG(1, kerr); + goto failed; + } + + if (strcasecmp(use_fast_str, "demand") == 0) { + kerr = sss_krb5_get_init_creds_opt_set_fast_flags(kr->ctx, + kr->options, + KRB5_FAST_REQUIRED); + if (kerr != 0) { + DEBUG(1, ("sss_krb5_get_init_creds_opt_set_fast_flags " + "failed.\n")); + KRB5_DEBUG(1, kerr); + goto failed; + } + } + } else { + DEBUG(1, ("Unsupported value [%s] for krb5_use_fast.\n")); + kerr = EINVAL; + goto failed; + } + } + /* TODO: set options, e.g. * krb5_get_init_creds_opt_set_forwardable * krb5_get_init_creds_opt_set_proxiable diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c index 43535c1e..25188c5f 100644 --- a/src/providers/krb5/krb5_common.c +++ b/src/providers/krb5/krb5_common.c @@ -43,7 +43,8 @@ struct dp_option default_krb5_opts[] = { { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, { "krb5_renewable_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, { "krb5_lifetime", DP_OPT_STRING, NULL_STRING, NULL_STRING }, - { "krb5_renew_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER } + { "krb5_renew_interval", DP_OPT_NUMBER, NULL_NUMBER, NULL_NUMBER }, + { "krb5_use_fast", DP_OPT_STRING, NULL_STRING, NULL_STRING } }; errno_t check_and_export_lifetime(struct dp_option *opts, const int opt_id, @@ -100,11 +101,13 @@ done: errno_t check_and_export_options(struct dp_option *opts, - struct sss_domain_info *dom) + struct sss_domain_info *dom, + struct krb5_ctx *krb5_ctx) { int ret; const char *realm; const char *dummy; + char *use_fast_str; realm = dp_opt_get_cstring(opts, KRB5_REALM); if (realm == NULL) { @@ -138,6 +141,23 @@ errno_t check_and_export_options(struct dp_option *opts, return ret; } + + use_fast_str = dp_opt_get_string(opts, KRB5_USE_FAST); + if (use_fast_str != NULL) { + ret = check_fast(use_fast_str, &krb5_ctx->use_fast); + if (ret != EOK) { + DEBUG(1, ("check_fast failed.\n")); + return ret; + } + + if (krb5_ctx->use_fast) { + ret = setenv(SSSD_KRB5_USE_FAST, use_fast_str, 1); + if (ret != EOK) { + DEBUG(2, ("setenv [%s] failed.\n", SSSD_KRB5_USE_FAST)); + } + } + } + dummy = dp_opt_get_cstring(opts, KRB5_KDC); if (dummy == NULL) { DEBUG(1, ("No KDC explicitly configured, using defaults.\n")); diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h index c20c7b33..9ca01467 100644 --- a/src/providers/krb5/krb5_common.h +++ b/src/providers/krb5/krb5_common.h @@ -37,6 +37,7 @@ #define SSSD_KRB5_REALM "SSSD_KRB5_REALM" #define SSSD_KRB5_RENEWABLE_LIFETIME "SSSD_KRB5_RENEWABLE_LIFETIME" #define SSSD_KRB5_LIFETIME "SSSD_KRB5_LIFETIME" +#define SSSD_KRB5_USE_FAST "SSSD_KRB5_USE_FAST" #define KDCINFO_TMPL PUBCONF_PATH"/kdcinfo.%s" #define KPASSWDINFO_TMPL PUBCONF_PATH"/kpasswdinfo.%s" @@ -57,6 +58,7 @@ enum krb5_opts { KRB5_RENEWABLE_LIFETIME, KRB5_LIFETIME, KRB5_RENEW_INTERVAL, + KRB5_USE_FAST, KRB5_OPTS }; @@ -114,6 +116,7 @@ struct krb5_ctx { struct deferred_auth_ctx *deferred_auth_ctx; struct renew_tgt_ctx *renew_tgt_ctx; + bool use_fast; }; struct remove_info_files_ctx { @@ -124,7 +127,8 @@ struct remove_info_files_ctx { }; errno_t check_and_export_options(struct dp_option *opts, - struct sss_domain_info *dom); + struct sss_domain_info *dom, + struct krb5_ctx *krb5_ctx); errno_t krb5_try_kdcip(TALLOC_CTX *memctx, struct confdb_ctx *cdb, const char *conf_path, struct dp_option *opts); diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c index 0f1ed41f..525ba4df 100644 --- a/src/providers/krb5/krb5_init.c +++ b/src/providers/krb5/krb5_init.c @@ -138,7 +138,7 @@ int sssm_krb5_auth_init(struct be_ctx *bectx, } } - ret = check_and_export_options(ctx->opts, bectx->domain); + ret = check_and_export_options(ctx->opts, bectx->domain, ctx); if (ret != EOK) { DEBUG(1, ("check_and_export_options failed.\n")); goto fail; -- cgit