[SSSD] [PATCH] Add support for delayed kinit if offline (sssd-1-2 only)

Sumit Bose sbose at redhat.com
Wed May 5 09:02:33 UTC 2010


On Mon, May 03, 2010 at 11:12:05AM +0200, Sumit Bose wrote:
> On Thu, Apr 29, 2010 at 06:21:20PM +0200, Sumit Bose wrote:
> > Hi,
> > 
> > this patch should fix #305 'The kerberos provider should automatically
> > kinit users when it goes online'.
> > 
> > This patch depends on:
> >  - 'Split pam_data utilities into a separate file'
> >  - 'Add callback when the ID provider switches from offline to online'
> > 
> > This version only applies to sssd-1-2, I will rebase/rewrite it for
> > master later.
> > 
> > bye,
> > Sumit
> 
> Sorry, I forgot to add a fix for the unit tests.
> 
> New version attached.
> 

rebased version which should work on top of 'Handle Krb5 password
expiration warning' is attached.

bye,
Sumit

> From a189b092198d3071682e5df4949cdefce4c3e66b Mon Sep 17 00:00:00 2001
> From: Sumit Bose <sbose at redhat.com>
> 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 +-
>  src/tests/sysdb-tests.c                            |    4 +-
>  22 files changed, 594 insertions(+), 25 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 5fb4062..5ae5b2e 100644
> --- a/src/Makefile.am
> +++ b/src/Makefile.am
> @@ -767,6 +767,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 \
> @@ -776,6 +777,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 \
> @@ -806,6 +808,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 = \
> @@ -816,6 +819,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 18df979..ead74c2 100644
> --- a/src/config/SSSDConfig.py
> +++ b/src/config/SSSDConfig.py
> @@ -98,6 +98,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 e889965..fbe287b 100755
> --- a/src/config/SSSDConfigTest.py
> +++ b/src/config/SSSDConfigTest.py
> @@ -547,6 +547,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,
> @@ -864,6 +866,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 @@
>                      </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>
> +                            Default: false
> +                        </para>
> +                    </listitem>
> +                </varlistentry>
> +
>              </variablelist>
>          </para>
>      </refsect1>
> 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 <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 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 <sbose at 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 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 */
> diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
> index c6661ae..8557be5 100644
> --- a/src/tests/sysdb-tests.c
> +++ b/src/tests/sysdb-tests.c
> @@ -2313,7 +2313,7 @@ static void cached_authentication_without_expiration(const char *username,
>      req = sysdb_cache_auth_send(data, test_ctx->ev, test_ctx->sysdb,
>                                  test_ctx->domain, data->username,
>                                  (const uint8_t *) password, strlen(password),
> -                                test_ctx->confdb);
> +                                test_ctx->confdb, false);
>      fail_unless(req != NULL, "sysdb_cache_password_send failed.");
>  
>      tevent_req_set_callback(req, test_search_done, data);
> @@ -2388,7 +2388,7 @@ static void cached_authentication_with_expiration(const char *username,
>      req = sysdb_cache_auth_send(data, test_ctx->ev, test_ctx->sysdb,
>                                  test_ctx->domain, data->username,
>                                  (const uint8_t *) password, strlen(password),
> -                                test_ctx->confdb);
> +                                test_ctx->confdb, false);
>      fail_unless(req != NULL, "sysdb_cache_password_send failed.");
>  
>      tevent_req_set_callback(req, test_search_done, data);
> -- 
> 1.6.6.1
> 

> _______________________________________________
> sssd-devel mailing list
> sssd-devel at lists.fedorahosted.org
> https://fedorahosted.org/mailman/listinfo/sssd-devel

-------------- next part --------------
From 2e4d7ca93a1d68d993ad06a44534489bf9eb7bf8 Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
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 +-
 src/tests/sysdb-tests.c                            |    4 +-
 22 files changed, 594 insertions(+), 25 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 5fb4062..5ae5b2e 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -767,6 +767,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 \
@@ -776,6 +777,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 \
@@ -806,6 +808,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 = \
@@ -816,6 +819,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 18df979..ead74c2 100644
--- a/src/config/SSSDConfig.py
+++ b/src/config/SSSDConfig.py
@@ -98,6 +98,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 e889965..fbe287b 100755
--- a/src/config/SSSDConfigTest.py
+++ b/src/config/SSSDConfigTest.py
@@ -547,6 +547,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,
@@ -864,6 +866,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 @@
                     </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>
+                            Default: false
+                        </para>
+                    </listitem>
+                </varlistentry>
+
             </variablelist>
         </para>
     </refsect1>
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 <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 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 a7c1946..8cd553c 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -599,6 +599,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;
@@ -968,7 +969,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"));
@@ -981,29 +982,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);
@@ -1294,6 +1307,24 @@ static void krb5_save_ccname_done(struct tevent_req *subreq)
     }
 
     if (kr->is_offline) {
+        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,
+                                           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;
@@ -1366,7 +1397,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);
 
@@ -1380,6 +1410,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 <sbose at 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 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 */
diff --git a/src/tests/sysdb-tests.c b/src/tests/sysdb-tests.c
index c6661ae..8557be5 100644
--- a/src/tests/sysdb-tests.c
+++ b/src/tests/sysdb-tests.c
@@ -2313,7 +2313,7 @@ static void cached_authentication_without_expiration(const char *username,
     req = sysdb_cache_auth_send(data, test_ctx->ev, test_ctx->sysdb,
                                 test_ctx->domain, data->username,
                                 (const uint8_t *) password, strlen(password),
-                                test_ctx->confdb);
+                                test_ctx->confdb, false);
     fail_unless(req != NULL, "sysdb_cache_password_send failed.");
 
     tevent_req_set_callback(req, test_search_done, data);
@@ -2388,7 +2388,7 @@ static void cached_authentication_with_expiration(const char *username,
     req = sysdb_cache_auth_send(data, test_ctx->ev, test_ctx->sysdb,
                                 test_ctx->domain, data->username,
                                 (const uint8_t *) password, strlen(password),
-                                test_ctx->confdb);
+                                test_ctx->confdb, false);
     fail_unless(req != NULL, "sysdb_cache_password_send failed.");
 
     tevent_req_set_callback(req, test_search_done, data);
-- 
1.6.6.1



More information about the sssd-devel mailing list