diff options
author | Sumit Bose <sbose@redhat.com> | 2010-04-19 11:59:09 +0200 |
---|---|---|
committer | Stephen Gallagher <sgallagh@redhat.com> | 2010-05-26 15:14:40 -0400 |
commit | 02e38eae1b9cb5df2036a707dafd86f6047c17de (patch) | |
tree | 970b10c1df9bfe101a3d84ec1ff87dedd5364186 /src/providers/krb5 | |
parent | 06c03627c81a5252420931383a68eb67ba551667 (diff) | |
download | sssd-02e38eae1b9cb5df2036a707dafd86f6047c17de.tar.gz sssd-02e38eae1b9cb5df2036a707dafd86f6047c17de.tar.bz2 sssd-02e38eae1b9cb5df2036a707dafd86f6047c17de.zip |
Add support for delayed kinit if offline
If the configuration option krb5_store_password_if_offline is set to
true and the backend is offline the plain text user password is stored
and used to request a TGT if the backend becomes online. If available
the Linux kernel key retention service is used.
Diffstat (limited to 'src/providers/krb5')
-rw-r--r-- | src/providers/krb5/krb5_auth.c | 76 | ||||
-rw-r--r-- | src/providers/krb5/krb5_auth.h | 10 | ||||
-rw-r--r-- | src/providers/krb5/krb5_common.c | 3 | ||||
-rw-r--r-- | src/providers/krb5/krb5_common.h | 1 | ||||
-rw-r--r-- | src/providers/krb5/krb5_delayed_online_authentication.c | 354 | ||||
-rw-r--r-- | src/providers/krb5/krb5_init.c | 8 |
6 files changed, 425 insertions, 27 deletions
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c index 48173ba7..a7aebe52 100644 --- a/src/providers/krb5/krb5_auth.c +++ b/src/providers/krb5/krb5_auth.c @@ -371,10 +371,10 @@ static errno_t fork_child(struct tevent_req *req, struct tevent_context *ev, /* We need to keep the root privileges to read the keytab file if * validation is enabled, otherwise we can drop them here and run * krb5_child with user privileges. - * If authtok_size is zero we are offline and want to create an empty - * ccache file. In this case we can drop the privileges, too. */ + * If we are offline and 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->pd->authtok_size == 0) { + kr->is_offline) { ret = become_user(kr->uid, kr->gid); if (ret != EOK) { DEBUG(1, ("become_user failed.\n")); @@ -537,6 +537,7 @@ static void krb5_resolve_kpasswd_done(struct tevent_req *subreq); static void krb5_find_ccache_step(struct tevent_req *req); static void krb5_save_ccname_done(struct tevent_req *req); static void krb5_child_done(struct tevent_req *req); +static void krb5_pam_handler_cache_auth_step(struct tevent_req *req); struct krb5_auth_state { struct tevent_context *ev; @@ -897,40 +898,31 @@ static void krb5_find_ccache_step(struct tevent_req *req) DEBUG(9, ("Preparing for offline operation.\n")); kr->is_offline = true; - if (kr->valid_tgt_present) { - DEBUG(9, ("Valid TGT available, nothing to do.\n")); + if (kr->valid_tgt_present || kr->active_ccache_present) { + DEBUG(9, ("Valid TGT available or " + "ccache file is already in use.\n")); msg = talloc_asprintf(pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname); if (msg == NULL) { DEBUG(1, ("talloc_asprintf failed.\n")); - ret = ENOMEM; - goto done; + } else { + ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1, + (uint8_t *) msg); + if (ret != EOK) { + DEBUG(1, ("pam_add_response failed.\n")); + } } - ret = pam_add_response(pd, SSS_PAM_ENV_ITEM, strlen(msg) + 1, - (uint8_t *) msg); - if (ret != EOK) { - DEBUG(1, ("pam_add_response failed.\n")); + if (dp_opt_get_bool(kr->krb5_ctx->opts, + KRB5_STORE_PASSWORD_IF_OFFLINE)) { + krb5_pam_handler_cache_auth_step(req); + return; } state->pam_status = PAM_AUTHINFO_UNAVAIL; state->dp_err = DP_ERR_OFFLINE; ret = EOK; goto done; - } - memset(pd->authtok, 0, pd->authtok_size); - pd->authtok_size = 0; - if (kr->active_ccache_present) { - ret = krb5_save_ccname(state, state->be_ctx->sysdb, - state->be_ctx->domain, pd->user, - kr->ccname); - if (ret) { - DEBUG(1, ("krb5_save_ccname failed.\n")); - goto done; - } - - krb5_save_ccname_done(req); - return; } } @@ -1214,6 +1206,11 @@ static void krb5_save_ccname_done(struct tevent_req *req) char *password = NULL; if (kr->is_offline) { + if (dp_opt_get_bool(kr->krb5_ctx->opts,KRB5_STORE_PASSWORD_IF_OFFLINE)) { + krb5_pam_handler_cache_auth_step(req); + return; + } + DEBUG(4, ("Backend is marked offline, retry later!\n")); state->pam_status = PAM_AUTHINFO_UNAVAIL; state->dp_err = DP_ERR_OFFLINE; @@ -1277,6 +1274,34 @@ done: } +static void krb5_pam_handler_cache_auth_step(struct tevent_req *req) +{ + struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state); + struct pam_data *pd = state->pd; + struct krb5_ctx *krb5_ctx = state->kr->krb5_ctx; + int ret; + + ret = sysdb_cache_auth(state, state->be_ctx->sysdb, state->be_ctx->domain, + pd->user, pd->authtok, pd->authtok_size, + state->be_ctx->cdb, true, NULL, NULL); + if (ret != EOK) { + DEBUG(1, ("Offline authentication failed\n")); + state->pam_status = PAM_SYSTEM_ERR; + state->dp_err = DP_ERR_OK; + } else { + ret = add_user_to_delayed_online_authentication(krb5_ctx, pd, + state->kr->uid); + if (ret != EOK) { + /* This error is not fatal */ + DEBUG(1, ("add_user_to_delayed_online_authentication failed.\n")); + } + state->pam_status = PAM_AUTHINFO_UNAVAIL; + state->dp_err = DP_ERR_OFFLINE; + } + + tevent_req_done(req); +} + static void krb_reply(struct be_req *req, int dp_err, int result) { req->fn(req, dp_err, result, NULL); @@ -1335,4 +1360,3 @@ void krb5_auth_done(struct tevent_req *req) krb_reply(be_req, dp_err, pd->pam_status); } - diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h index e614d5c3..61b8071e 100644 --- a/src/providers/krb5/krb5_auth.h +++ b/src/providers/krb5/krb5_auth.h @@ -62,6 +62,7 @@ struct krb5child_req { }; struct fo_service; +struct deferred_auth_ctx; struct krb5_ctx { /* opts taken from kinit */ @@ -94,6 +95,8 @@ struct krb5_ctx { int child_debug_fd; pcre *illegal_path_re; + + struct deferred_auth_ctx *deferred_auth_ctx; }; void krb5_pam_handler(struct be_req *be_req); @@ -104,4 +107,11 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx, struct pam_data *pd, struct krb5_ctx *krb5_ctx); int krb5_auth_recv(struct tevent_req *req, int *pam_status, int *dp_err); + +errno_t add_user_to_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct pam_data *pd, + uid_t uid); +errno_t init_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct be_ctx *be_ctx, + struct tevent_context *ev); #endif /* __KRB5_AUTH_H__ */ diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c index 17b6511e..da7627ce 100644 --- a/src/providers/krb5/krb5_common.c +++ b/src/providers/krb5/krb5_common.c @@ -42,7 +42,8 @@ struct dp_option default_krb5_opts[] = { { "krb5_auth_timeout", DP_OPT_NUMBER, { .number = 15 }, NULL_NUMBER }, { "krb5_keytab", DP_OPT_STRING, { "/etc/krb5.keytab" }, NULL_STRING }, { "krb5_validate", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE }, - { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING } + { "krb5_kpasswd", DP_OPT_STRING, NULL_STRING, NULL_STRING }, + { "krb5_store_password_if_offline", DP_OPT_BOOL, BOOL_FALSE, BOOL_FALSE } }; errno_t check_and_export_options(struct dp_option *opts, diff --git a/src/providers/krb5/krb5_common.h b/src/providers/krb5/krb5_common.h index 12c487a9..dd7fdf2d 100644 --- a/src/providers/krb5/krb5_common.h +++ b/src/providers/krb5/krb5_common.h @@ -53,6 +53,7 @@ enum krb5_opts { KRB5_KEYTAB, KRB5_VALIDATE, KRB5_KPASSWD, + KRB5_STORE_PASSWORD_IF_OFFLINE, KRB5_OPTS }; diff --git a/src/providers/krb5/krb5_delayed_online_authentication.c b/src/providers/krb5/krb5_delayed_online_authentication.c new file mode 100644 index 00000000..02f09919 --- /dev/null +++ b/src/providers/krb5/krb5_delayed_online_authentication.c @@ -0,0 +1,354 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Request a TGT when the system gets online + + Authors: + Sumit Bose <sbose@redhat.com> + + Copyright (C) 2010 Red Hat + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <security/pam_modules.h> +#ifdef USE_KEYRING +#include <sys/types.h> +#include <keyutils.h> +#endif + +#include "providers/krb5/krb5_auth.h" +#include "dhash.h" +#include "util/util.h" +#include "util/find_uid.h" + +#define INITIAL_USER_TABLE_SIZE 10 + +struct deferred_auth_ctx { + hash_table_t *user_table; + struct be_ctx *be_ctx; + struct tevent_context *ev; + struct krb5_ctx *krb5_ctx; +}; + +struct auth_data { + struct be_ctx *be_ctx; + struct krb5_ctx *krb5_ctx; + struct pam_data *pd; +}; + +static void *hash_talloc(const size_t size, void *pvt) +{ + return talloc_size(pvt, size); +} + +static void hash_talloc_free(void *ptr, void *pvt) +{ + talloc_free(ptr); +} + +static void authenticate_user_done(struct tevent_req *req); +static void authenticate_user(struct tevent_context *ev, + struct tevent_timer *te, + struct timeval current_time, + void *private_data) +{ + struct auth_data *auth_data = talloc_get_type(private_data, + struct auth_data); + struct pam_data *pd = auth_data->pd; + struct tevent_req *req; + + DEBUG_PAM_DATA(9, pd); + + if (pd->authtok == NULL || pd->authtok_size == 0) { + DEBUG(1, ("Missing authtok for user [%s].\n", pd->user)); + return; + } + +#ifdef USE_KEYRING + long keysize; + long keyrevoke; + int ret; + keysize = keyctl_read(pd->key_serial, (char *) pd->authtok, + pd->authtok_size); + keyrevoke = keyctl_revoke(pd->key_serial); + if (keysize == -1) { + ret = errno; + DEBUG(1, ("keyctl_read failed [%d][%s].\n", ret, strerror(ret))); + return; + } else if (keysize != pd->authtok_size) { + DEBUG(1, ("keyctl_read returned key with wrong size, " + "expect [%d] got [%d].\n", pd->authtok_size, keysize)); + return; + } + if (keyrevoke == -1) { + ret = errno; + DEBUG(1, ("keyctl_revoke failed [%d][%s].\n", ret, strerror(ret))); + } +#endif + + req = krb5_auth_send(auth_data, ev, auth_data->be_ctx, auth_data->pd, + auth_data->krb5_ctx); + if (req == NULL) { + DEBUG(1, ("krb5_auth_send failed.\n")); + talloc_free(auth_data); + return; + } + + tevent_req_set_callback(req, authenticate_user_done, auth_data); +} + +static void authenticate_user_done(struct tevent_req *req) { + struct auth_data *auth_data = tevent_req_callback_data(req, + struct auth_data); + int ret; + int pam_status = PAM_SYSTEM_ERR; + int dp_err; + + ret = krb5_auth_recv(req, &pam_status, &dp_err); + talloc_free(req); + if (ret) { + DEBUG(1, ("krb5_auth request failed.\n")); + } else { + if (pam_status == PAM_SUCCESS) { + DEBUG(4, ("Successfully authenticated user [%s].\n", + auth_data->pd->user)); + } else { + DEBUG(1, ("Failed to authenticate user [%s].\n", + auth_data->pd->user)); + } + } + + talloc_free(auth_data); +} + +static errno_t authenticate_stored_users( + struct deferred_auth_ctx *deferred_auth_ctx) +{ + int ret; + hash_table_t *uid_table; + struct hash_iter_context_t *iter; + hash_entry_t *entry; + hash_key_t key; + hash_value_t value; + struct pam_data *pd; + struct auth_data *auth_data; + struct tevent_timer *te; + + ret = get_uid_table(deferred_auth_ctx, &uid_table); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("get_uid_table failed.\n")); + return ret; + } + + iter = new_hash_iter_context(deferred_auth_ctx->user_table); + if (iter == NULL) { + DEBUG(1, ("new_hash_iter_context failed.\n")); + return EINVAL; + } + + while ((entry = iter->next(iter)) != NULL) { + key.type = HASH_KEY_ULONG; + key.ul = entry->key.ul; + pd = talloc_get_type(entry->value.ptr, struct pam_data); + + ret = hash_lookup(uid_table, &key, &value); + + if (ret == HASH_SUCCESS) { + DEBUG(1, ("User [%s] is still logged in, " + "trying online authentication.\n", pd->user)); + + auth_data = talloc_zero(deferred_auth_ctx->be_ctx, + struct auth_data); + if (auth_data == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + } else { + auth_data->pd = talloc_steal(auth_data, pd); + auth_data->krb5_ctx = deferred_auth_ctx->krb5_ctx; + auth_data->be_ctx = deferred_auth_ctx->be_ctx; + + te = tevent_add_timer(deferred_auth_ctx->ev, + auth_data, tevent_timeval_current(), + authenticate_user, auth_data); + if (te == NULL) { + DEBUG(1, ("tevent_add_timer failed.\n")); + } + } + } else { + DEBUG(1, ("User [%s] is not logged in anymore, " + "discarding online authentication.\n", pd->user)); + talloc_free(pd); + } + + ret = hash_delete(deferred_auth_ctx->user_table, + &entry->key); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("hash_delete failed [%s].\n", + hash_error_string(ret))); + } + } + + talloc_free(iter); + + return EOK; +} + +static void delayed_online_authentication_callback(void *private_data) +{ + struct deferred_auth_ctx *deferred_auth_ctx = + talloc_get_type(private_data, struct deferred_auth_ctx); + int ret; + + if (deferred_auth_ctx->user_table == NULL) { + DEBUG(1, ("Delayed online authentication activated, " + "but user table does not exists.\n")); + return; + } + + DEBUG(5, ("Backend is online, starting delayed online authentication.\n")); + ret = authenticate_stored_users(deferred_auth_ctx); + if (ret != EOK) { + DEBUG(1, ("authenticate_stored_users failed.\n")); + } + + return; +} + +errno_t add_user_to_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct pam_data *pd, + uid_t uid) +{ + int ret; + hash_key_t key; + hash_value_t value; + struct pam_data *new_pd; + + if (krb5_ctx->deferred_auth_ctx == NULL) { + DEBUG(1, ("Missing context for delayed online authentication.\n")); + return EINVAL; + } + + if (krb5_ctx->deferred_auth_ctx->user_table == NULL) { + DEBUG(1, ("user_table not available.\n")); + return EINVAL; + } + + if (pd->authtok_size == 0 || pd->authtok == NULL) { + DEBUG(1, ("Missing authtok for user [%s].\n", pd->user)); + return EINVAL; + } + + ret = copy_pam_data(krb5_ctx->deferred_auth_ctx, pd, &new_pd); + if (ret != EOK) { + DEBUG(1, ("copy_pam_data failed\n")); + return ENOMEM; + } + + +#ifdef USE_KEYRING + new_pd->key_serial = add_key("user", new_pd->user, new_pd->authtok, + new_pd->authtok_size, KEY_SPEC_THREAD_KEYRING); + if (new_pd->key_serial == -1) { + ret = errno; + DEBUG(1, ("add_key fialed [%d][%s].\n", ret, strerror(ret))); + talloc_free(new_pd); + return ret; + } + DEBUG(9, ("Saved authtok of user [%s] with serial [%ld].\n", + new_pd->user, new_pd->key_serial)); + memset(new_pd->authtok, 0, new_pd->authtok_size); +#endif + + key.type = HASH_KEY_ULONG; + key.ul = uid; + value.type = HASH_VALUE_PTR; + value.ptr = new_pd; + + ret = hash_enter(krb5_ctx->deferred_auth_ctx->user_table, + &key, &value); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("Cannot add user [%s] to table [%s], " + "delayed online authentication not possible.\n", + pd->user, hash_error_string(ret))); + talloc_free(new_pd); + return ENOMEM; + } + + DEBUG(9, ("Added user [%s] successfully to " + "delayed online authentication.\n", pd->user)); + + return EOK; +} + +errno_t init_delayed_online_authentication(struct krb5_ctx *krb5_ctx, + struct be_ctx *be_ctx, + struct tevent_context *ev) +{ + int ret; + hash_table_t *tmp_table; + + ret = get_uid_table(krb5_ctx, &tmp_table); + if (ret != EOK) { + if (ret == ENOSYS) { + DEBUG(0, ("Delayed online auth was requested " + "on an unsupported system.\n")); + } else { + DEBUG(0, ("Delayed online auth was requested " + "but initialisation failed.\n")); + } + return ret; + } + ret = hash_destroy(tmp_table); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("hash_destroy failed [%s].\n", hash_error_string(ret))); + return EFAULT; + } + + krb5_ctx->deferred_auth_ctx = talloc_zero(krb5_ctx, + struct deferred_auth_ctx); + if (krb5_ctx->deferred_auth_ctx == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + return ENOMEM; + } + + ret = hash_create_ex(INITIAL_USER_TABLE_SIZE, + &krb5_ctx->deferred_auth_ctx->user_table, + 0, 0, 0, 0, hash_talloc, hash_talloc_free, + krb5_ctx->deferred_auth_ctx, + NULL, NULL); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("hash_create_ex failed [%s]\n", hash_error_string(ret))); + ret = ENOMEM; + goto fail; + } + + krb5_ctx->deferred_auth_ctx->be_ctx = be_ctx; + krb5_ctx->deferred_auth_ctx->krb5_ctx = krb5_ctx; + krb5_ctx->deferred_auth_ctx->ev = ev; + + ret = be_add_online_cb(krb5_ctx, be_ctx, + delayed_online_authentication_callback, + krb5_ctx->deferred_auth_ctx, NULL); + if (ret != EOK) { + DEBUG(1, ("be_add_online_cb failed.\n")); + goto fail; + } + + /* TODO: add destructor */ + + return EOK; +fail: + talloc_zfree(krb5_ctx->deferred_auth_ctx); + return ret; +} diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c index 0ad58926..d0c8be54 100644 --- a/src/providers/krb5/krb5_init.c +++ b/src/providers/krb5/krb5_init.c @@ -121,6 +121,14 @@ int sssm_krb5_auth_init(struct be_ctx *bectx, } } + if (dp_opt_get_bool(ctx->opts, KRB5_STORE_PASSWORD_IF_OFFLINE)) { + ret = init_delayed_online_authentication(ctx, bectx, bectx->ev); + if (ret != EOK) { + DEBUG(1, ("init_delayed_online_authentication failed.\n")); + goto fail; + } + } + ret = check_and_export_options(ctx->opts, bectx->domain); if (ret != EOK) { DEBUG(1, ("check_and_export_options failed.\n")); |