summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--contrib/sssd.spec.in2
-rw-r--r--src/Makefile.am4
-rw-r--r--src/config/SSSDConfig.py1
-rwxr-xr-xsrc/config/SSSDConfigTest.py3
-rw-r--r--src/config/etc/sssd.api.d/sssd-krb5.conf1
-rw-r--r--src/configure.ac1
-rw-r--r--src/db/sysdb.h1
-rw-r--r--src/db/sysdb_ops.c14
-rw-r--r--src/external/libkeyutils.m411
-rw-r--r--src/man/sssd-krb5.5.xml18
-rw-r--r--src/providers/data_provider.h11
-rw-r--r--src/providers/dp_pam_data_util.c88
-rw-r--r--src/providers/ipa/ipa_common.c3
-rw-r--r--src/providers/ipa/ipa_common.h2
-rw-r--r--src/providers/ipa/ipa_init.c9
-rw-r--r--src/providers/krb5/krb5_auth.c76
-rw-r--r--src/providers/krb5/krb5_auth.h10
-rw-r--r--src/providers/krb5/krb5_common.c3
-rw-r--r--src/providers/krb5/krb5_common.h1
-rw-r--r--src/providers/krb5/krb5_delayed_online_authentication.c354
-rw-r--r--src/providers/krb5/krb5_init.c8
-rw-r--r--src/responder/pam/pamsrv_cmd.c2
-rw-r--r--src/tests/sysdb-tests.c4
23 files changed, 593 insertions, 34 deletions
diff --git a/contrib/sssd.spec.in b/contrib/sssd.spec.in
index bbc172830..29f2c7db8 100644
--- a/contrib/sssd.spec.in
+++ b/contrib/sssd.spec.in
@@ -30,6 +30,7 @@ Requires: libdhash = %{dhash_version}-%{release}
Requires: libcollection = %{collection_version}-%{release}
Requires: libini_config = %{ini_config_version}-%{release}
Requires: cyrus-sasl-gssapi
+Requires: keyutils-libs
Requires(post): python
Requires(preun): initscripts chkconfig
Requires(postun): /sbin/service
@@ -75,6 +76,7 @@ BuildRequires: doxygen
BuildRequires: libselinux-devel
BuildRequires: libsemanage-devel
BuildRequires: bind-utils
+BuildRequires: keyutils-libs-devel
%description
Provides a set of daemons to manage access to remote directories and
diff --git a/src/Makefile.am b/src/Makefile.am
index 73cd408f9..d093089f2 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -773,6 +773,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 \
@@ -782,6 +783,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 \
@@ -813,6 +815,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 = \
@@ -823,6 +826,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 9e178f11e..6b759d83c 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -100,6 +100,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 32bb71239..61c2f9497 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -548,6 +548,7 @@ class SSSDConfigTestSSSDDomain(unittest.TestCase):
'krb5_ccname_template',
'krb5_keytab',
'krb5_validate',
+ 'krb5_store_password_if_offline',
'krb5_auth_timeout'])
options = domain.list_options()
@@ -719,6 +720,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,
@@ -865,6 +867,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 a9c8230b2..eeb8fe133 100644
--- a/src/config/etc/sssd.api.d/sssd-krb5.conf
+++ b/src/config/etc/sssd.api.d/sssd-krb5.conf
@@ -9,6 +9,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 4cc5d853f..811c2d9c4 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -95,6 +95,7 @@ m4_include([external/selinux.m4])
m4_include([external/crypto.m4])
m4_include([external/nscd.m4])
m4_include([external/nsupdate.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 5b6f21895..23560ecd9 100644
--- a/src/db/sysdb.h
+++ b/src/db/sysdb.h
@@ -471,6 +471,7 @@ int sysdb_cache_auth(TALLOC_CTX *mem_ctx,
const uint8_t *authtok,
size_t authtok_size,
struct confdb_ctx *cdb,
+ bool just_check,
time_t *_expire_date,
time_t *_delayed_until);
diff --git a/src/db/sysdb_ops.c b/src/db/sysdb_ops.c
index 00b74c6a4..7f454311e 100644
--- a/src/db/sysdb_ops.c
+++ b/src/db/sysdb_ops.c
@@ -1988,6 +1988,7 @@ int sysdb_cache_auth(TALLOC_CTX *mem_ctx,
const uint8_t *authtok,
size_t authtok_size,
struct confdb_ctx *cdb,
+ bool just_check,
time_t *_expire_date,
time_t *_delayed_until)
{
@@ -2120,6 +2121,11 @@ int sysdb_cache_auth(TALLOC_CTX *mem_ctx,
DEBUG(4, ("Hashes do match!\n"));
authentication_successful = true;
+ if (just_check) {
+ ret = EOK;
+ goto done;
+ }
+
ret = sysdb_attrs_add_time_t(update_attrs,
SYSDB_LAST_LOGIN, time(NULL));
if (ret != EOK) {
@@ -2168,8 +2174,12 @@ int sysdb_cache_auth(TALLOC_CTX *mem_ctx,
}
done:
- *_expire_date = expire_date;
- *_delayed_until = delayed_until;
+ if (_expire_date != NULL) {
+ *_expire_date = expire_date;
+ }
+ if (_delayed_until != NULL) {
+ *_delayed_until = delayed_until;
+ }
if (password) for (i = 0; password[i]; i++) password[i] = 0;
if (ret) {
ldb_transaction_cancel(sysdb->ldb);
diff --git a/src/external/libkeyutils.m4 b/src/external/libkeyutils.m4
new file mode 100644
index 000000000..5753d77aa
--- /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 01f212d2a..732f05bac 100644
--- a/src/man/sssd-krb5.5.xml
+++ b/src/man/sssd-krb5.5.xml
@@ -241,6 +241,24 @@
</listitem>
</varlistentry>
+ <varlistentry>
+ <term>krb5_store_password_if_offline (boolean)</term>
+ <listitem>
+ <para>
+ Store the password of the user if the provider is
+ offline and use it to request a TGT when the
+ provider gets online again.
+ </para>
+ <para>
+ Please note that this feature currently only
+ available on a Linux plattform.
+ </para>
+ <para>
+ Default: false
+ </para>
+ </listitem>
+ </varlistentry>
+
</variablelist>
</para>
</refsect1>
diff --git a/src/providers/data_provider.h b/src/providers/data_provider.h
index 747e6e89d..951b47ab1 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 <stdint.h>
#include <sys/un.h>
#include <errno.h>
#include <stdbool.h>
+#ifdef USE_KEYRING
+#include <sys/types.h>
+#include <keyutils.h>
+#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 02eb6e91a..d709447d1 100644
--- a/src/providers/dp_pam_data_util.c
+++ b/src/providers/dp_pam_data_util.c
@@ -25,6 +25,26 @@
#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);
+
static const char *pamcmd2str(int cmd) {
switch (cmd) {
case SSS_PAM_AUTHENTICATE:
@@ -46,6 +66,74 @@ static const char *pamcmd2str(int cmd) {
}
}
+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: %s\n", pamcmd2str(pd->cmd)));
diff --git a/src/providers/ipa/ipa_common.c b/src/providers/ipa/ipa_common.c
index 2ef674e74..a8b14d363 100644
--- a/src/providers/ipa/ipa_common.c
+++ b/src/providers/ipa/ipa_common.c
@@ -124,7 +124,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 f59a7d7bc..9678e0cea 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 2f0ccf0fa..0e72b1fab 100644
--- a/src/providers/ipa/ipa_init.c
+++ b/src/providers/ipa/ipa_init.c
@@ -272,6 +272,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 48173ba72..a7aebe529 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 e614d5c35..61b8071ef 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 17b6511e5..da7627ce3 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 12c487a90..dd7fdf2da 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 000000000..02f09919a
--- /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 0ad589268..d0c8be544 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"));
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index d5b4eff69..43da44bc4 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -381,7 +381,7 @@ static void pam_reply(struct pam_auth_req *preq)
ret = sysdb_cache_auth(preq, sysdb,
preq->domain, pd->user,
pd->authtok, pd->authtok_size,
- pctx->rctx->cdb,
+ pctx->rctx->cdb, false,
&exp_date, &delay_until);
pam_cache_auth_done(preq, ret, exp_date, delay_until);
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index 360609449..44cb6dc3e 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -1425,7 +1425,7 @@ static void cached_authentication_without_expiration(const char *username,
ret = sysdb_cache_auth(data, test_ctx->sysdb,
test_ctx->domain, data->username,
(const uint8_t *)password, strlen(password),
- test_ctx->confdb, &expire_date, &delayed_until);
+ test_ctx->confdb, false, &expire_date, &delayed_until);
fail_unless(ret == expected_result, "sysdb_cache_auth request does not "
"return expected result [%d].",
@@ -1487,7 +1487,7 @@ static void cached_authentication_with_expiration(const char *username,
ret = sysdb_cache_auth(data, test_ctx->sysdb,
test_ctx->domain, data->username,
(const uint8_t *) password, strlen(password),
- test_ctx->confdb, &expire_date, &delayed_until);
+ test_ctx->confdb, false, &expire_date, &delayed_until);
fail_unless(ret == expected_result,
"sysdb_cache_auth request does not return expected "