From 2bdf54dff9ab0de473fffffadedf6d7dd52c8e13 Mon Sep 17 00:00:00 2001 From: Sumit Bose Date: Mon, 19 Apr 2010 11:59:09 +0200 Subject: [PATCH] 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. --- src/Makefile.am | 4 + src/config/SSSDConfig.py | 1 + src/config/SSSDConfigTest.py | 3 + src/config/etc/sssd.api.d/sssd-krb5.conf | 1 + src/configure.ac | 1 + src/db/sysdb.h | 3 +- src/db/sysdb_ops.c | 10 +- src/external/libkeyutils.m4 | 11 + src/man/sssd-krb5.5.xml | 14 + src/providers/data_provider.h | 11 + src/providers/dp_pam_data_util.c | 88 +++++ src/providers/ipa/ipa_common.c | 3 +- src/providers/ipa/ipa_common.h | 2 +- src/providers/ipa/ipa_init.c | 9 + src/providers/krb5/krb5_auth.c | 94 +++++- src/providers/krb5/krb5_auth.h | 10 + src/providers/krb5/krb5_common.c | 3 +- src/providers/krb5/krb5_common.h | 1 + .../krb5/krb5_delayed_online_authentication.c | 336 ++++++++++++++++++++ src/providers/krb5/krb5_init.c | 8 + src/responder/pam/pamsrv_cmd.c | 2 +- 21 files changed, 592 insertions(+), 23 deletions(-) create mode 100644 src/external/libkeyutils.m4 create mode 100644 src/providers/krb5/krb5_delayed_online_authentication.c diff --git a/src/Makefile.am b/src/Makefile.am index a991a35..d2eaa93 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -764,6 +764,7 @@ libsss_krb5_la_SOURCES = \ providers/child_common.c \ providers/krb5/krb5_utils.c \ providers/krb5/krb5_become_user.c \ + providers/krb5/krb5_delayed_online_authentication.c \ providers/krb5/krb5_auth.c \ providers/krb5/krb5_common.c \ providers/krb5/krb5_init.c \ @@ -773,6 +774,7 @@ libsss_krb5_la_CFLAGS = \ $(DHASH_CFLAGS) libsss_krb5_la_LIBADD = \ $(DHASH_LIBS) \ + $(KEYUITLS_LIB) \ $(KRB5_LIBS) libsss_krb5_la_LDFLAGS = \ -version-info 1:0:0 \ @@ -803,6 +805,7 @@ libsss_ipa_la_SOURCES = \ util/find_uid.c \ providers/krb5/krb5_utils.c \ providers/krb5/krb5_become_user.c \ + providers/krb5/krb5_delayed_online_authentication.c \ providers/krb5/krb5_common.c \ providers/krb5/krb5_auth.c libsss_ipa_la_CFLAGS = \ @@ -813,6 +816,7 @@ libsss_ipa_la_CFLAGS = \ libsss_ipa_la_LIBADD = \ $(OPENLDAP_LIBS) \ $(DHASH_LIBS) \ + $(KEYUTILS_LIBS) \ $(KRB5_LIBS) libsss_ipa_la_LDFLAGS = \ -version-info 1:0:0 \ diff --git a/src/config/SSSDConfig.py b/src/config/SSSDConfig.py index 6dd8cac..d9f2856 100644 --- a/src/config/SSSDConfig.py +++ b/src/config/SSSDConfig.py @@ -97,6 +97,7 @@ option_strings = { 'krb5_ccname_template' : _("Location of the user's credential cache"), 'krb5_keytab' : _("Location of the keytab to validate credentials"), 'krb5_validate' : _("Enable credential validation"), + 'krb5_store_password_if_offline' : _("Store password if offline for later online authentication"), # [provider/krb5/chpass] 'krb5_changepw_principal' : _('The principal of the change password service'), diff --git a/src/config/SSSDConfigTest.py b/src/config/SSSDConfigTest.py index 127ad22..0824fb8 100755 --- a/src/config/SSSDConfigTest.py +++ b/src/config/SSSDConfigTest.py @@ -546,6 +546,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'krb5_ccname_template', 'krb5_keytab', 'krb5_validate', + 'krb5_store_password_if_offline', 'krb5_auth_timeout']) options = domain.list_options() @@ -718,6 +719,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'krb5_ccname_template', 'krb5_keytab', 'krb5_validate', + 'krb5_store_password_if_offline', 'krb5_auth_timeout'] self.assertTrue(type(options) == dict, @@ -862,6 +864,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase): 'krb5_ccname_template', 'krb5_keytab', 'krb5_validate', + 'krb5_store_password_if_offline', 'krb5_auth_timeout']) options = domain.list_options() diff --git a/src/config/etc/sssd.api.d/sssd-krb5.conf b/src/config/etc/sssd.api.d/sssd-krb5.conf index 7b12e08..1f8bead 100644 --- a/src/config/etc/sssd.api.d/sssd-krb5.conf +++ b/src/config/etc/sssd.api.d/sssd-krb5.conf @@ -8,6 +8,7 @@ krb5_ccachedir = str, None, false krb5_ccname_template = str, None, false krb5_keytab = str, None, false krb5_validate = bool, None, false +krb5_store_password_if_offline = bool, None, false [provider/krb5/chpass] krb5_changepw_principal = str, None, false diff --git a/src/configure.ac b/src/configure.ac index 93debb6..5542ee4 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -94,6 +94,7 @@ m4_include([external/python.m4]) m4_include([external/selinux.m4]) m4_include([external/crypto.m4]) m4_include([external/nscd.m4]) +m4_include([external/libkeyutils.m4]) m4_include([util/signal.m4]) PKG_CHECK_MODULES([DBUS],[dbus-1]) diff --git a/src/db/sysdb.h b/src/db/sysdb.h index cf97ed6..d6780c3 100644 --- a/src/db/sysdb.h +++ b/src/db/sysdb.h @@ -557,7 +557,8 @@ struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx, const char *name, const uint8_t *authtok, size_t authtok_size, - struct confdb_ctx *cdb); + struct confdb_ctx *cdb, + bool just_check); int sysdb_cache_auth_recv(struct tevent_req *req, time_t *expire_date, time_t *delayed_until); diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c index 618b810..6fcc95b 100644 --- a/src/db/sysdb_ops.c +++ b/src/db/sysdb_ops.c @@ -4645,6 +4645,7 @@ struct sysdb_cache_auth_state { struct sss_domain_info *domain; struct sysdb_ctx *sysdb; struct confdb_ctx *cdb; + bool just_check; struct sysdb_attrs *update_attrs; bool authentication_successful; struct sysdb_handle *handle; @@ -4724,7 +4725,8 @@ struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx, const char *name, const uint8_t *authtok, size_t authtok_size, - struct confdb_ctx *cdb) + struct confdb_ctx *cdb, + bool just_check) { struct tevent_req *req; struct tevent_req *subreq; @@ -4774,6 +4776,7 @@ struct tevent_req *sysdb_cache_auth_send(TALLOC_CTX *mem_ctx, state->domain = domain; state->sysdb = sysdb; state->cdb = cdb; + state->just_check = just_check; state->update_attrs = NULL; state->authentication_successful = false; state->handle = NULL; @@ -4888,6 +4891,11 @@ static void sysdb_cache_auth_get_attrs_done(struct tevent_req *subreq) DEBUG(4, ("Hashes do match!\n")); state->authentication_successful = true; + if (state->just_check) { + ret = EOK; + goto done; + } + ret = sysdb_attrs_add_time_t(state->update_attrs, SYSDB_LAST_LOGIN, time(NULL)); if (ret != EOK) { diff --git a/src/external/libkeyutils.m4 b/src/external/libkeyutils.m4 new file mode 100644 index 0000000..5753d77 --- /dev/null +++ b/src/external/libkeyutils.m4 @@ -0,0 +1,11 @@ +AC_SUBST(KEYUTILS_LIBS) + +AC_CHECK_HEADERS([keyutils.h], + [AC_CHECK_LIB([keyutils], [add_key], + [AC_DEFINE(USE_KEYRING, 1, [Define if the keyring should be used]) + KEYUTILS_LIBS="-lkeyutils" + ], + [AC_MSG_WARN([No usable keyutils library found])] + )], + [AC_MSG_WARN([keyutils header files are not available])] +) diff --git a/src/man/sssd-krb5.5.xml b/src/man/sssd-krb5.5.xml index c291eca..54200f6 100644 --- a/src/man/sssd-krb5.5.xml +++ b/src/man/sssd-krb5.5.xml @@ -238,6 +238,20 @@ + + krb5_store_password_if_offline (boolean) + + + Store the password of the user if the provider is + offline and use it to request a TGT when the + provider gets online again. + + + Default: false + + + + diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h index 747e6e8..951b47a 100644 --- a/src/providers/data_provider.h +++ b/src/providers/data_provider.h @@ -22,10 +22,16 @@ #ifndef __DATA_PROVIDER_H__ #define __DATA_PROVIDER_H__ +#include "config.h" + #include #include #include #include +#ifdef USE_KEYRING +#include +#include +#endif #include "talloc.h" #include "tevent.h" #include "ldb.h" @@ -178,9 +184,14 @@ struct pam_data { bool offline_auth; bool last_auth_saved; int priv; +#ifdef USE_KEYRING + key_serial_t key_serial; +#endif }; /* from dp_auth_util.c */ +errno_t copy_pam_data(TALLOC_CTX *mem_ctx, struct pam_data *old_pd, + struct pam_data **new_pd); void pam_print_data(int l, struct pam_data *pd); int pam_add_response(struct pam_data *pd, enum response_type type, diff --git a/src/providers/dp_pam_data_util.c b/src/providers/dp_pam_data_util.c index 308bd7c..93db7f6 100644 --- a/src/providers/dp_pam_data_util.c +++ b/src/providers/dp_pam_data_util.c @@ -24,6 +24,94 @@ #include "providers/data_provider.h" +#define PD_STR_COPY(el) do { \ + if (old_pd->el != NULL) { \ + pd->el = talloc_strdup(pd, old_pd->el); \ + if (pd->el == NULL) { \ + DEBUG(1, ("talloc_strdup failed.\n")); \ + goto failed; \ + } \ + } \ +} while(0); + +#define PD_MEM_COPY(el, size) do { \ + if (old_pd->el != NULL) { \ + pd->el = talloc_memdup(pd, old_pd->el, (size)); \ + if (pd->el == NULL) { \ + DEBUG(1, ("talloc_memdup failed.\n")); \ + goto failed; \ + } \ + } \ +} while(0); + +int pam_data_destructor(void *ptr) +{ + struct pam_data *pd = talloc_get_type(ptr, struct pam_data); + + if (pd->authtok_size != 0 && pd->authtok != NULL) { + memset(pd->authtok, 0, pd->authtok_size); + pd->authtok_size = 0; + } + + if (pd->newauthtok_size != 0 && pd->newauthtok != NULL) { + memset(pd->newauthtok, 0, pd->newauthtok_size); + pd->newauthtok_size = 0; + } + + return EOK; +} + +struct pam_data *create_pam_data(TALLOC_CTX *mem_ctx) +{ + struct pam_data *pd; + + pd = talloc_zero(mem_ctx, struct pam_data); + if (pd == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + return NULL; + } + + talloc_set_destructor((TALLOC_CTX *) pd, pam_data_destructor); + + return pd; +} + +errno_t copy_pam_data(TALLOC_CTX *mem_ctx, struct pam_data *old_pd, + struct pam_data **new_pd) +{ + struct pam_data *pd = NULL; + + pd = create_pam_data(mem_ctx); + if (pd == NULL) { + DEBUG(1, ("create_pam_data failed.\n")); + return ENOMEM; + } + + pd->cmd = old_pd->cmd; + pd->authtok_type = old_pd->authtok_type; + pd->authtok_size = old_pd->authtok_size; + pd->newauthtok_type = old_pd->newauthtok_type; + pd->newauthtok_size = old_pd->newauthtok_size; + + PD_STR_COPY(domain); + PD_STR_COPY(user); + PD_STR_COPY(service); + PD_STR_COPY(tty); + PD_STR_COPY(ruser); + PD_STR_COPY(rhost); + PD_MEM_COPY(authtok, old_pd->authtok_size); + PD_MEM_COPY(newauthtok, old_pd->newauthtok_size); + pd->cli_pid = old_pd->cli_pid; + + *new_pd = pd; + + return EOK; + +failed: + talloc_free(pd); + return ENOMEM; +} + void pam_print_data(int l, struct pam_data *pd) { DEBUG(l, ("command: %d\n", pd->cmd)); diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c index bed0666..48ac56a 100644 --- a/src/providers/ipa/ipa_common.c +++ b/src/providers/ipa/ipa_common.c @@ -120,7 +120,8 @@ struct dp_option ipa_def_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_TRUE, BOOL_TRUE }, - { "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 } }; int ipa_get_options(TALLOC_CTX *memctx, diff --git a/src/providers/ipa/ipa_common.h b/src/providers/ipa/ipa_common.h index 03a082e..2b93659 100644 --- a/src/providers/ipa/ipa_common.h +++ b/src/providers/ipa/ipa_common.h @@ -40,7 +40,7 @@ struct ipa_service { /* the following define is used to keep track of the options in the krb5 * module, so that if they change and ipa is not updated correspondingly * this will trigger a runtime abort error */ -#define IPA_KRB5_OPTS_TEST 9 +#define IPA_KRB5_OPTS_TEST 10 enum ipa_basic_opt { IPA_DOMAIN = 0, diff --git a/src/providers/ipa/ipa_init.c b/src/providers/ipa/ipa_init.c index 6e0688f..598c616 100644 --- a/src/providers/ipa/ipa_init.c +++ b/src/providers/ipa/ipa_init.c @@ -233,6 +233,15 @@ int sssm_ipa_auth_init(struct be_ctx *bectx, goto done; } + if (dp_opt_get_bool(krb5_auth_ctx->opts, KRB5_STORE_PASSWORD_IF_OFFLINE)) { + ret = init_delayed_online_authentication(krb5_auth_ctx, bectx, + bectx->ev); + if (ret != EOK) { + DEBUG(1, ("init_delayed_online_authentication failed.\n")); + goto done; + } + } + ret = check_and_export_options(krb5_auth_ctx->opts, bectx->domain); if (ret != EOK) { DEBUG(1, ("check_and_export_opts failed.\n")); diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c index 0c08fe1..822be65 100644 --- a/src/providers/krb5/krb5_auth.c +++ b/src/providers/krb5/krb5_auth.c @@ -667,6 +667,7 @@ 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_done(struct tevent_req *treq); +static void krb5_pam_handler_cache_auth_done(struct tevent_req *subreq); struct krb5_auth_state { struct tevent_context *ev; @@ -1037,7 +1038,7 @@ static void krb5_find_ccache_step(struct tevent_req *req) kr->is_offline = true; if (kr->valid_tgt_present) { - DEBUG(9, ("Valid TGT available, nothing to do.\n")); + DEBUG(9, ("Valid TGT available.\n")); msg = talloc_asprintf(pd, "%s=%s", CCACHE_ENV_NAME, kr->ccname); if (msg == NULL) { DEBUG(1, ("talloc_asprintf failed.\n")); @@ -1050,29 +1051,41 @@ static void krb5_find_ccache_step(struct tevent_req *req) if (ret != EOK) { DEBUG(1, ("pam_add_response failed.\n")); } - - 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) { - subreq = krb5_save_ccname_send(state, state->ev, + if (dp_opt_get_bool(kr->krb5_ctx->opts,KRB5_STORE_PASSWORD_IF_OFFLINE)) { + subreq = sysdb_cache_auth_send(state, state->ev, state->be_ctx->sysdb, state->be_ctx->domain, pd->user, - kr->ccname); + pd->authtok, pd->authtok_size, + state->be_ctx->cdb, true); if (subreq == NULL) { - DEBUG(1, ("krb5_save_ccname_send failed.\n")); - ret = ENOMEM; - goto done; + DEBUG(2, ("sysdb_cache_auth_send failed, " + "delayed online authentication not possible.\n")); + /* This is not a fatal error, we continue with standard + * offline authentication. */ + } else { + tevent_req_set_callback(subreq, + krb5_pam_handler_cache_auth_done, req); + return; } + } - tevent_req_set_callback(subreq, krb5_save_ccname_done, req); - return; + memset(pd->authtok, 0, pd->authtok_size); + pd->authtok_size = 0; + + subreq = krb5_save_ccname_send(state, state->ev, + state->be_ctx->sysdb, + state->be_ctx->domain, pd->user, + kr->ccname); + if (subreq == NULL) { + DEBUG(1, ("krb5_save_ccname_send failed.\n")); + ret = ENOMEM; + goto done; } + + tevent_req_set_callback(subreq, krb5_save_ccname_done, req); + return; } subreq = handle_child_send(state, state->ev, kr); @@ -1259,6 +1272,24 @@ static void krb5_save_ccname_done(struct tevent_req *subreq) } if (kr->is_offline) { + if (dp_opt_get_bool(krb5_ctx->opts,KRB5_STORE_PASSWORD_IF_OFFLINE)) { + subreq = sysdb_cache_auth_send(state, state->ev, + state->be_ctx->sysdb, + state->be_ctx->domain, pd->user, + pd->authtok, pd->authtok_size, + state->be_ctx->cdb, true); + if (subreq == NULL) { + DEBUG(2, ("sysdb_cache_auth_send failed, " + "delayed online authentication not possible.\n")); + /* This is not a fatal error, we continue with standard + * offline authentication. */ + } else { + tevent_req_set_callback(subreq, + krb5_pam_handler_cache_auth_done, req); + return; + } + } + DEBUG(4, ("Backend is marked offline, retry later!\n")); state->pam_status = PAM_AUTHINFO_UNAVAIL; state->dp_err = DP_ERR_OFFLINE; @@ -1331,7 +1362,6 @@ static void krb5_pam_handler_cache_done(struct tevent_req *subreq) struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req); int ret; - /* password caching failures are not fatal errors */ ret = sysdb_cache_password_recv(subreq); talloc_zfree(subreq); @@ -1345,6 +1375,36 @@ static void krb5_pam_handler_cache_done(struct tevent_req *subreq) tevent_req_done(req); } +static void krb5_pam_handler_cache_auth_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_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; + time_t expire_date; + time_t delayed_until; + + ret = sysdb_cache_auth_recv(subreq, &expire_date, &delayed_until); + talloc_zfree(subreq); + + if (ret) { + DEBUG(2, ("Offline authentication failed.\n")); + } 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); diff --git a/src/providers/krb5/krb5_auth.h b/src/providers/krb5/krb5_auth.h index e614d5c..aa2247f 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 delayed_online_authentication; struct krb5_ctx { /* opts taken from kinit */ @@ -94,6 +95,8 @@ struct krb5_ctx { int child_debug_fd; pcre *illegal_path_re; + + struct delayed_online_authentication *delayed_online_authentication; }; 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 52cbe16..12cdb30 100644 --- a/src/providers/krb5/krb5_common.c +++ b/src/providers/krb5/krb5_common.c @@ -40,7 +40,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 0b0da31..3d01ec9 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 0000000..b6f5a8f --- /dev/null +++ b/src/providers/krb5/krb5_delayed_online_authentication.c @@ -0,0 +1,336 @@ +/* + SSSD + + Kerberos 5 Backend Module -- Request a TGT when the system gets online + + Authors: + Sumit Bose + + 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 . +*/ + +#include +#ifdef USE_KEYRING +#include +#include +#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 delayed_online_authentication { + 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 delayed_online_authentication *delayed_online_authentication) +{ + 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(delayed_online_authentication, &uid_table); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("get_uid_table failed.\n")); + return ret; + } + + iter = new_hash_iter_context(delayed_online_authentication->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(delayed_online_authentication->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 = delayed_online_authentication->krb5_ctx; + auth_data->be_ctx = delayed_online_authentication->be_ctx; + + te = tevent_add_timer(delayed_online_authentication->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(delayed_online_authentication->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 delayed_online_authentication *delayed_online_authentication = + talloc_get_type(private_data, struct delayed_online_authentication); + int ret; + + if (delayed_online_authentication->user_table == NULL) { + DEBUG(1, ("Delayed online authentication activated, " + "but user table does not exists.\n")); + return; + } + + DEBUG(9, ("Backend is online, starting delayed online authentication.\n")); + ret = authenticate_stored_users(delayed_online_authentication); + 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->delayed_online_authentication == NULL) { + DEBUG(1, ("Missing context for delayed online authentication.\n")); + return EINVAL; + } + + if (krb5_ctx->delayed_online_authentication->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->delayed_online_authentication, 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->delayed_online_authentication->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; + + krb5_ctx->delayed_online_authentication = talloc_zero(krb5_ctx, + struct delayed_online_authentication); + if (krb5_ctx->delayed_online_authentication == NULL) { + DEBUG(1, ("talloc_zero failed.\n")); + return ENOMEM; + } + + ret = hash_create_ex(INITIAL_USER_TABLE_SIZE, + &krb5_ctx->delayed_online_authentication->user_table, + 0, 0, 0, 0, hash_talloc, hash_talloc_free, + krb5_ctx->delayed_online_authentication, + NULL, NULL); + if (ret != HASH_SUCCESS) { + DEBUG(1, ("hash_create_ex failed [%s]\n", hash_error_string(ret))); + ret = ENOMEM; + goto fail; + } + + krb5_ctx->delayed_online_authentication->be_ctx = be_ctx; + krb5_ctx->delayed_online_authentication->krb5_ctx = krb5_ctx; + krb5_ctx->delayed_online_authentication->ev = ev; + + ret = be_add_online_cb(krb5_ctx, be_ctx, + delayed_online_authentication_callback, + krb5_ctx->delayed_online_authentication, NULL); + if (ret != EOK) { + DEBUG(1, ("be_add_online_cb failed.\n")); + goto fail; + } + + /* TODO: add destructor */ + + return EOK; +fail: + talloc_zfree(krb5_ctx->delayed_online_authentication); + return ret; +} diff --git a/src/providers/krb5/krb5_init.c b/src/providers/krb5/krb5_init.c index 83129d9..fb5560d 100644 --- a/src/providers/krb5/krb5_init.c +++ b/src/providers/krb5/krb5_init.c @@ -120,6 +120,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")); diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c index 8d67da1..182e849 100644 --- a/src/responder/pam/pamsrv_cmd.c +++ b/src/responder/pam/pamsrv_cmd.c @@ -520,7 +520,7 @@ static void pam_reply(struct pam_auth_req *preq) req = sysdb_cache_auth_send(preq, preq->cctx->ev, sysdb, preq->domain, pd->user, pd->authtok, pd->authtok_size, - pctx->rctx->cdb); + pctx->rctx->cdb, false); if (req == NULL) { DEBUG(1, ("Failed to setup offline auth.\n")); /* this error is not fatal, continue */ -- 1.6.6.1