[SSSD] [PATCH] Improve handling of ccache files

Sumit Bose sbose at redhat.com
Thu Nov 19 13:31:36 UTC 2009


Hi,

this patch improves the handling of ccache files. It addresses two
issues already discussed on the list.

When randomized ccache file are used (or the client process id is used
in the name of the ccache file) each authentication of the user created
a new ccache file. This patch saves the name of the ccache in sysdb and
reuses the saved file name if the user has running processes on the
system. So a single user only has one active ccache file.

If the authentication happens when the system is offline the kerberos
related environment variables were not sent to the client. If a later
authentication happens online the old session still cannot see the
ccache file with the valid credentials. This patch send the environment
variables bach to the client even when offline.

bye,
Sumit
-------------- next part --------------
>From 00e25cf8af98cd0e19215fece8c5d5fd9192fb4f Mon Sep 17 00:00:00 2001
From: Sumit Bose <sbose at redhat.com>
Date: Mon, 9 Nov 2009 21:54:06 +0100
Subject: [PATCH] Improve handling of ccache files

- save current ccache file to sysdb
- use the saved ccache file if the user has running processes
- create an empty ccache if offline
- return enviroment variables if offline
---
 server/Makefile.am                 |   30 ++-
 server/db/sysdb.h                  |    1 +
 server/providers/krb5/krb5_auth.c  |  546 +++++++++++++++++++++++++++++-------
 server/providers/krb5/krb5_auth.h  |    3 +-
 server/providers/krb5/krb5_child.c |  250 ++++++++++-------
 server/tests/find_uid-tests.c      |  124 ++++++++
 server/util/find_uid.c             |  297 ++++++++++++++++++++
 server/util/find_uid.h             |   36 +++
 8 files changed, 1081 insertions(+), 206 deletions(-)
 create mode 100644 server/tests/find_uid-tests.c
 create mode 100644 server/util/find_uid.c
 create mode 100644 server/util/find_uid.h

diff --git a/server/Makefile.am b/server/Makefile.am
index 196486a..b792836 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -68,10 +68,11 @@ if HAVE_CHECK
         resolv-tests \
         krb5-utils-tests \
         check_and_open-tests \
-	ipa_timerules-tests \
+        ipa_timerules-tests \
         files-tests \
         refcount-tests \
-        fail_over-tests
+        fail_over-tests \
+        find_uid-tests
 endif
 
 check_PROGRAMS = \
@@ -132,7 +133,7 @@ INI_CFG_LIBS = \
 DHASH_CFLAGS = \
     -I$(srcdir)/../common/dhash
 DHASH_LIBS = \
-    -L$(builddir)/../common/dhash/.libs/ \
+    -L$(builddir)/../common/dhash/ \
     -ldhash
 
 AM_CPPFLAGS = -Wall \
@@ -266,6 +267,7 @@ dist_noinst_HEADERS = \
     util/sss_ldap.h \
     util/sss_krb5.h \
     util/refcount.h \
+    util/find_uid.h \
     config.h \
     monitor/monitor.h \
     monitor/monitor_interfaces.h \
@@ -518,6 +520,20 @@ ipa_timerules_tests_LDADD = \
     $(TALLOC_LIBS) \
     $(CHECK_LIBS)
 
+find_uid_tests_SOURCES = \
+    tests/find_uid-tests.c \
+    util/find_uid.c \
+    $(SSSD_DEBUG_OBJ)
+find_uid_tests_CFLAGS = \
+    $(AM_CFLAGS) \
+    $(TALLOC_CFLAGS) \
+    $(DHASH_CFLAGS) \
+    $(CHECK_CFLAGS)
+find_uid_tests_LDADD = \
+    $(TALLOC_LIBS) \
+    $(DHASH_LIBS) \
+    $(CHECK_LIBS)
+
 endif
 
 stress_tests_SOURCES = \
@@ -564,6 +580,7 @@ libsss_proxy_la_LDFLAGS = \
     -module
 
 libsss_krb5_la_SOURCES = \
+    util/find_uid.c \
     providers/krb5/krb5_utils.c \
     providers/krb5/krb5_become_user.c \
     providers/krb5/krb5_auth.c \
@@ -571,7 +588,9 @@ libsss_krb5_la_SOURCES = \
     providers/krb5/krb5_init.c
 libsss_krb5_la_CFLAGS = \
     $(AM_CFLAGS) \
-    $(KRB5_CFLAGS)
+    $(DHASH_CFLAGS)
+libsss_krb5_la_LIBADD = \
+    $(DHASH_LIBS)
 libsss_krb5_la_LDFLAGS = \
     -version-info 1:0:0 \
     -module
@@ -593,6 +612,7 @@ libsss_ipa_la_SOURCES = \
     providers/ldap/sdap.c \
     util/sss_ldap.c \
     util/sss_krb5.c \
+    util/find_uid.c \
     providers/krb5/krb5_utils.c \
     providers/krb5/krb5_become_user.c \
     providers/krb5/krb5_common.c \
@@ -600,9 +620,11 @@ libsss_ipa_la_SOURCES = \
 libsss_ipa_la_CFLAGS = \
     $(AM_CFLAGS) \
     $(LDAP_CFLAGS) \
+    $(DHASH_CFLAGS) \
     $(KRB5_CFLAGS)
 libsss_ipa_la_LIBADD = \
     $(OPENLDAP_LIBS) \
+    $(DHASH_LIBS) \
     $(KRB5_LIBS)
 libsss_ipa_la_LDFLAGS = \
     -version-info 1:0:0 \
diff --git a/server/db/sysdb.h b/server/db/sysdb.h
index a329985..f94b43f 100644
--- a/server/db/sysdb.h
+++ b/server/db/sysdb.h
@@ -72,6 +72,7 @@
 
 #define SYSDB_UUID "uniqueID"
 #define SYSDB_UPN "userPrincipalName"
+#define SYSDB_CCACHE_FILE "ccacheFile"
 
 #define SYSDB_ORIG_DN "originalDN"
 #define SYSDB_ORIG_MODSTAMP "originalModifyTimestamp"
diff --git a/server/providers/krb5/krb5_auth.c b/server/providers/krb5/krb5_auth.c
index d3e05e1..8068bce 100644
--- a/server/providers/krb5/krb5_auth.c
+++ b/server/providers/krb5/krb5_auth.c
@@ -29,10 +29,12 @@
 #include <sys/wait.h>
 #include <fcntl.h>
 #include <pwd.h>
+#include <sys/stat.h>
 
 #include <security/pam_modules.h>
 
 #include "util/util.h"
+#include "util/find_uid.h"
 #include "db/sysdb.h"
 #include "providers/krb5/krb5_auth.h"
 #include "providers/krb5/krb5_utils.h"
@@ -48,6 +50,262 @@ struct io_buffer {
     size_t size;
 };
 
+static errno_t add_krb5_env(struct dp_option *opts, const char *ccname,
+                            struct pam_data *pd)
+{
+    int ret;
+    const char *dummy;
+    char *env;
+    TALLOC_CTX *tmp_ctx = NULL;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        DEBUG(1, ("talloc_new failed.\n"));
+        return ENOMEM;
+    }
+
+    if (ccname != NULL) {
+        env = talloc_asprintf(tmp_ctx, "%s=%s",CCACHE_ENV_NAME, ccname);
+        if (env == NULL) {
+            DEBUG(1, ("talloc_asprintf failed.\n"));
+            ret = ENOMEM;
+            goto done;
+        }
+        ret = pam_add_response(pd, PAM_ENV_ITEM, strlen(env)+1,
+                               (uint8_t *) env);
+        if (ret != EOK) {
+            DEBUG(1, ("pam_add_response failed.\n"));
+            goto done;
+        }
+    }
+
+    dummy = dp_opt_get_cstring(opts, KRB5_REALM);
+    if (dummy != NULL) {
+        env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_REALM, dummy);
+        if (env == NULL) {
+            DEBUG(1, ("talloc_asprintf failed.\n"));
+            ret = ENOMEM;
+            goto done;
+        }
+        ret = pam_add_response(pd, PAM_ENV_ITEM, strlen(env)+1,
+                               (uint8_t *) env);
+        if (ret != EOK) {
+            DEBUG(1, ("pam_add_response failed.\n"));
+            goto done;
+        }
+    }
+
+    dummy = dp_opt_get_cstring(opts, KRB5_KDC);
+    if (dummy != NULL) {
+        env = talloc_asprintf(tmp_ctx, "%s=%s", SSSD_KRB5_KDC, dummy);
+        if (env == NULL) {
+            DEBUG(1, ("talloc_asprintf failed.\n"));
+            ret = ENOMEM;
+            goto done;
+        }
+        ret = pam_add_response(pd, PAM_ENV_ITEM, strlen(env)+1,
+                               (uint8_t *) env);
+        if (ret != EOK) {
+            DEBUG(1, ("pam_add_response failed.\n"));
+            goto done;
+        }
+    }
+
+    ret = EOK;
+
+done:
+    talloc_free(tmp_ctx);
+    return ret;
+}
+
+static errno_t check_if_ccache_file_is_used(uid_t uid, const char **ccname)
+{
+    int ret;
+    size_t offset = 0;
+    struct stat stat_buf;
+    const char *filename;
+    bool active;
+
+    if (ccname == NULL || *ccname == NULL) {
+        return EINVAL;
+    }
+
+    if (strncmp(*ccname, "FILE:", 5) == 0) {
+        offset = 5;
+    }
+
+    filename = *ccname + offset;
+
+    if (filename[0] != '/') {
+        DEBUG(1, ("Only absolute path names are allowed"));
+        return EINVAL;
+    }
+
+    ret = lstat(filename, &stat_buf);
+
+    if (ret == -1 && errno != ENOENT) {
+        DEBUG(1, ("stat failed [%d][%s].\n", errno, strerror(errno)));
+        return errno;
+    } else if (ret == -1 &&  errno == ENOENT) {
+        *ccname = NULL;
+        return EOK;
+    }
+
+    if (stat_buf.st_uid != uid) {
+        DEBUG(1, ("Cache file [%s] exists, but is owned by [%d] instead of "
+                  "[%d].\n", filename, stat_buf.st_uid, uid));
+        return EINVAL;
+    }
+
+    if (!S_ISREG(stat_buf.st_mode)) {
+        DEBUG(1, ("Cache file [%s] exists, but is not a regular file.\n",
+                  filename));
+        return EINVAL;
+    }
+
+    ret = check_if_uid_is_active(uid, &active);
+    if (ret != EOK) {
+        DEBUG(1, ("check_if_uid_is_active failed.\n"));
+        return ret;
+    }
+
+    if (!active) {
+        DEBUG(5, ("User [%d] is not active, deleting old ccache file [%s].\n",
+                  uid, filename));
+        ret = unlink(filename);
+        if (ret == -1) {
+            DEBUG(1, ("unlink failed [%d][%s].\n", errno, strerror(errno)));
+            return errno;
+        }
+        *ccname = NULL;
+    } else {
+        DEBUG(9, ("User [%d] is still active, reusing ccache file [%s].\n",
+                  uid, filename));
+    }
+    return EOK;
+}
+
+struct krb5_save_ccname_state {
+    struct tevent_context *ev;
+    struct sysdb_ctx *sysdb;
+    struct sysdb_handle *handle;
+    struct sss_domain_info *domain;
+    const char *name;
+    struct sysdb_attrs *attrs;
+};
+
+static void krb5_save_ccname_trans(struct tevent_req *subreq);
+static void krb5_set_user_attr_done(struct tevent_req *subreq);
+
+static struct tevent_req *krb5_save_ccname_send(TALLOC_CTX *mem_ctx,
+                                                struct tevent_context *ev,
+                                                struct sysdb_ctx *sysdb,
+                                                struct sss_domain_info *domain,
+                                                const char *name,
+                                                const char *ccname)
+{
+    struct tevent_req *req;
+    struct tevent_req *subreq;
+    struct krb5_save_ccname_state *state;
+    int ret;
+
+    if (name == NULL || ccname == NULL) {
+        DEBUG(1, ("Missing user or ccache name.\n"));
+        return NULL;
+    }
+
+    req = tevent_req_create(mem_ctx, &state, struct krb5_save_ccname_state);
+    if (req == NULL) {
+        DEBUG(1, ("tevent_req_create failed.\n"));
+        return NULL;
+    }
+
+    state->ev = ev;
+    state->sysdb = sysdb;
+    state->handle = NULL;
+    state->domain = domain;
+    state->name = name;
+
+    state->attrs = sysdb_new_attrs(state);
+    ret = sysdb_attrs_add_string(state->attrs, SYSDB_CCACHE_FILE, ccname);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_add_string failed.\n"));
+        goto failed;
+    }
+
+    subreq = sysdb_transaction_send(state, ev, sysdb);
+    if (subreq == NULL) {
+        goto failed;
+    }
+    tevent_req_set_callback(subreq, krb5_save_ccname_trans, req);
+
+    return req;
+
+failed:
+    talloc_free(req);
+    return NULL;
+}
+
+static void krb5_save_ccname_trans(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct krb5_save_ccname_state *state = tevent_req_data(req,
+                                                 struct krb5_save_ccname_state);
+    int ret;
+
+    ret = sysdb_transaction_recv(subreq, state, &state->handle);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sysdb_set_user_attr_send(state, state->ev, state->handle,
+                                      state->domain, state->name,
+                                      state->attrs, SYSDB_MOD_REP);
+    if (subreq == NULL) {
+        DEBUG(6, ("Error: Out of memory\n"));
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, krb5_set_user_attr_done, req);
+}
+
+static void krb5_set_user_attr_done(struct tevent_req *subreq)
+{
+    struct tevent_req *req = tevent_req_callback_data(subreq,
+                                                      struct tevent_req);
+    struct krb5_save_ccname_state *state = tevent_req_data(req,
+                                                 struct krb5_save_ccname_state);
+    int ret;
+
+    ret = sysdb_set_user_attr_recv(subreq);
+    talloc_zfree(subreq);
+    if (ret != EOK) {
+        DEBUG(6, ("Error: %d (%s)\n", ret, strerror(ret)));
+        tevent_req_error(req, ret);
+        return;
+    }
+
+    subreq = sysdb_transaction_commit_send(state, state->ev, state->handle);
+    if (subreq == NULL) {
+        DEBUG(6, ("Error: Out of memory\n"));
+        tevent_req_error(req, ENOMEM);
+        return;
+    }
+    tevent_req_set_callback(subreq, sysdb_transaction_complete, req);
+    return;
+}
+
+int krb5_save_ccname_recv(struct tevent_req *req)
+{
+    TEVENT_REQ_RETURN_ON_ERROR(req);
+
+    return EOK;
+}
+
 errno_t create_send_buffer(struct krb5child_req *kr, struct io_buffer **io_buf)
 {
     struct io_buffer *buf;
@@ -245,8 +503,7 @@ static int krb5_cleanup(void *ptr)
     return EOK;
 }
 
-static errno_t krb5_setup(struct be_req *req, struct krb5child_req **krb5_req,
-                          const char *homedir)
+static errno_t krb5_setup(struct be_req *req, struct krb5child_req **krb5_req)
 {
     struct krb5child_req *kr = NULL;
     struct krb5_ctx *krb5_ctx;
@@ -270,22 +527,12 @@ static errno_t krb5_setup(struct be_req *req, struct krb5child_req **krb5_req,
     }
     kr->read_from_child_fd = -1;
     kr->write_to_child_fd = -1;
+    kr->is_offline = false;
     talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
 
     kr->pd = pd;
     kr->req = req;
     kr->krb5_ctx = krb5_ctx;
-    kr->homedir = homedir;
-
-    kr->ccname = expand_ccname_template(kr, kr,
-                                        dp_opt_get_cstring(krb5_ctx->opts,
-                                                           KRB5_CCNAME_TMPL)
-                                        );
-    if (kr->ccname == NULL) {
-        DEBUG(1, ("expand_ccname_template failed.\n"));
-        err = EINVAL;
-        goto failed;
-    }
 
     *krb5_req = kr;
 
@@ -434,8 +681,11 @@ static errno_t fork_child(struct krb5child_req *kr)
 
         /* 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 (!dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE)) {
+         * 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 (!dp_opt_get_bool(kr->krb5_ctx->opts, KRB5_VALIDATE) ||
+            kr->pd->authtok_size == 0) {
             ret = become_user(kr->pd->pw_uid, kr->pd->gr_gid);
             if (ret != EOK) {
                 DEBUG(1, ("become_user failed.\n"));
@@ -678,8 +928,9 @@ static int handle_child_recv(struct tevent_req *req,
     return EOK;
 }
 
-static void get_user_upn_done(void *pvt, int err, struct ldb_result *res);
-static void krb5_pam_handler_done(struct tevent_req *req);
+static void get_user_attr_done(void *pvt, int err, struct ldb_result *res);
+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);
 
 void krb5_pam_handler(struct be_req *be_req)
@@ -692,13 +943,6 @@ void krb5_pam_handler(struct be_req *be_req)
 
     pd = talloc_get_type(be_req->req_data, struct pam_data);
 
-    if (be_is_offline(be_req->be_ctx)) {
-        DEBUG(4, ("Backend is marked offline, retry later!\n"));
-        pam_status = PAM_AUTHINFO_UNAVAIL;
-        dp_err = DP_ERR_OFFLINE;
-        goto done;
-    }
-
     if (pd->cmd != SSS_PAM_AUTHENTICATE && pd->cmd != SSS_PAM_CHAUTHTOK) {
         DEBUG(4, ("krb5 does not handles pam task %d.\n", pd->cmd));
         pam_status = PAM_SUCCESS;
@@ -706,18 +950,19 @@ void krb5_pam_handler(struct be_req *be_req)
         goto done;
     }
 
-    attrs = talloc_array(be_req, const char *, 3);
+    attrs = talloc_array(be_req, const char *, 4);
     if (attrs == NULL) {
         goto done;
     }
 
     attrs[0] = SYSDB_UPN;
     attrs[1] = SYSDB_HOMEDIR;
-    attrs[2] = NULL;
+    attrs[2] = SYSDB_CCACHE_FILE;
+    attrs[3] = NULL;
 
     ret = sysdb_get_user_attr(be_req, be_req->be_ctx->sysdb,
                               be_req->be_ctx->domain, pd->user, attrs,
-                              get_user_upn_done, be_req);
+                              get_user_attr_done, be_req);
 
     if (ret) {
         goto done;
@@ -731,7 +976,7 @@ done:
     krb_reply(be_req, dp_err, pd->pam_status);
 }
 
-static void get_user_upn_done(void *pvt, int err, struct ldb_result *res)
+static void get_user_attr_done(void *pvt, int err, struct ldb_result *res)
 {
     struct be_req *be_req = talloc_get_type(pvt, struct be_req);
     struct krb5_ctx *krb5_ctx;
@@ -740,17 +985,19 @@ static void get_user_upn_done(void *pvt, int err, struct ldb_result *res)
     int ret;
     struct pam_data *pd;
     int pam_status=PAM_SYSTEM_ERR;
-    const char *homedir = NULL;
+    int dp_err = DP_ERR_FATAL;
+    const char *ccache_file = NULL;
     const char *dummy;
+    bool active_ccache_present = false;
 
-    pd = talloc_get_type(be_req->req_data, struct pam_data);
-    krb5_ctx = get_krb5_ctx(be_req);
-    if (krb5_ctx == NULL) {
-        DEBUG(1, ("Kerberos context not available.\n"));
-        err = EINVAL;
+    ret = krb5_setup(be_req, &kr);
+    if (ret != EOK) {
+        DEBUG(1, ("krb5_setup failed.\n"));
         goto failed;
     }
 
+    pd = kr->pd;
+    krb5_ctx = kr->krb5_ctx;
 
     if (err != LDB_SUCCESS) {
         DEBUG(5, ("sysdb search for upn of user [%s] failed.\n", pd->user));
@@ -759,7 +1006,8 @@ static void get_user_upn_done(void *pvt, int err, struct ldb_result *res)
 
     switch (res->count) {
     case 0:
-        DEBUG(5, ("No upn for user [%s] found.\n", pd->user));
+        DEBUG(5, ("No attributes for user [%s] found.\n", pd->user));
+        goto failed;
         break;
 
     case 1:
@@ -776,68 +1024,110 @@ static void get_user_upn_done(void *pvt, int err, struct ldb_result *res)
             }
         }
 
-        homedir = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_HOMEDIR,
-                                              NULL);
-        if (homedir == NULL) {
+        if (pd->upn == NULL) {
+            DEBUG(1, ("Cannot set UPN.\n"));
+            goto failed;
+        }
+
+        kr->homedir = ldb_msg_find_attr_as_string(res->msgs[0], SYSDB_HOMEDIR,
+                                                  NULL);
+        if (kr->homedir == NULL) {
             DEBUG(4, ("Home directory for user [%s] not known.\n", pd->user));
         }
+
+        ccache_file = ldb_msg_find_attr_as_string(res->msgs[0],
+                                                  SYSDB_CCACHE_FILE,
+                                                  NULL);
+        if (ccache_file != NULL) {
+            ret = check_if_ccache_file_is_used(pd->pw_uid, &ccache_file);
+            if (ret != EOK) {
+                DEBUG(1, ("check_if_ccache_file_is_used failed.\n"));
+                goto failed;
+            }
+        }
+
+        if (ccache_file == NULL) {
+            active_ccache_present = false;
+            DEBUG(4, ("No active ccache file for user [%s] found.\n",
+                      pd->user));
+            ccache_file = expand_ccname_template(kr, kr,
+                                          dp_opt_get_cstring(kr->krb5_ctx->opts,
+                                                             KRB5_CCNAME_TMPL)
+                                    );
+            if (ccache_file == NULL) {
+                DEBUG(1, ("expand_ccname_template failed.\n"));
+                goto failed;
+            }
+        } else {
+            active_ccache_present = true;
+        }
+        DEBUG(9, ("Ccache_file is [%s] and %s.\n", ccache_file,
+                  active_ccache_present ? "will be kept/renewed" :
+                                          "will be generated"));
+        kr->ccname = ccache_file;
         break;
 
     default:
         DEBUG(1, ("A user search by name (%s) returned > 1 results!\n",
                   pd->user));
+        goto failed;
         break;
     }
 
-    if (pd->upn == NULL) {
-        DEBUG(1, ("Cannot set UPN.\n"));
-        goto failed;
-    }
 
-    ret = krb5_setup(be_req, &kr, homedir);
-    if (ret != EOK) {
-        DEBUG(1, ("krb5_setup failed.\n"));
-        goto failed;
+    if (be_is_offline(be_req->be_ctx)) {
+        DEBUG(9, ("Preparing for offline operation.\n"));
+        kr->is_offline = true;
+        memset(pd->authtok, 0, pd->authtok_size);
+        pd->authtok_size = 0;
+
+        if (active_ccache_present) {
+            req = krb5_save_ccname_send(kr, be_req->be_ctx->ev,
+                                        be_req->be_ctx->sysdb,
+                                        be_req->be_ctx->domain, pd->user,
+                                        kr->ccname);
+            if (req == NULL) {
+                DEBUG(1, ("krb5_save_ccname_send failed.\n"));
+                goto failed;
+            }
+
+            tevent_req_set_callback(req, krb5_save_ccname_done, kr);
+            return;
+        }
     }
 
-    req = handle_child_send(be_req, be_req->be_ctx->ev, kr);
+    req = handle_child_send(kr, be_req->be_ctx->ev, kr);
     if (req == NULL) {
         DEBUG(1, ("handle_child_send failed.\n"));
         goto failed;
     }
 
-    tevent_req_set_callback(req, krb5_pam_handler_done, kr);
+    tevent_req_set_callback(req, krb5_child_done, kr);
     return;
 
 failed:
     talloc_free(kr);
 
     pd->pam_status = pam_status;
-    krb_reply(be_req, DP_ERR_FATAL, pd->pam_status);
+    krb_reply(be_req, dp_err, pd->pam_status);
 }
 
-static void krb5_pam_handler_done(struct tevent_req *req)
+static void krb5_child_done(struct tevent_req *req)
 {
     struct krb5child_req *kr = tevent_req_callback_data(req,
                                                         struct krb5child_req);
     struct pam_data *pd = kr->pd;
     struct be_req *be_req = kr->req;
-    struct krb5_ctx *krb5_ctx = kr->krb5_ctx;
     int ret;
     uint8_t *buf;
     ssize_t len;
+    ssize_t pref_len;
     int p;
     int32_t *msg_status;
     int32_t *msg_type;
     int32_t *msg_len;
-    struct tevent_req *subreq = NULL;
-    char *password = NULL;
-    char *env = NULL;
+    int pam_status = PAM_SYSTEM_ERR;
     int dp_err = DP_ERR_FATAL;
-    const char *dummy;
-
-    pd->pam_status = PAM_SYSTEM_ERR;
-    talloc_free(kr);
 
     ret = handle_child_recv(req, pd, &buf, &len);
     talloc_zfree(req);
@@ -869,56 +1159,101 @@ static void krb5_pam_handler_done(struct tevent_req *req)
         goto done;
     }
 
-    ret = pam_add_response(pd, *msg_type, *msg_len, &buf[p]);
-    if (ret != EOK) {
-        DEBUG(1, ("pam_add_response failed.\n"));
+    if (*msg_status != PAM_SUCCESS && *msg_status != PAM_AUTHINFO_UNAVAIL) {
+        pam_status = *msg_status;
+        dp_err = DP_ERR_OK;
+
+        ret = pam_add_response(pd, *msg_type, *msg_len, &buf[p]);
+        if (ret != EOK) {
+            DEBUG(1, ("pam_add_response failed.\n"));
+        }
+
+        goto done;
+    } else {
+        pd->pam_status = *msg_status;
+    }
+
+    pref_len = strlen(CCACHE_ENV_NAME)+1;
+    if (*msg_len > pref_len &&
+        strncmp((const char *) &buf[p], CCACHE_ENV_NAME"=", pref_len) == 0) {
+        kr->ccname = talloc_strndup(kr, (char *) &buf[p+pref_len],
+                                   *msg_len-pref_len);
+        if (kr->ccname == NULL) {
+            DEBUG(1, ("talloc_strndup failed.\n"));
+            goto done;
+        }
+    } else {
+        DEBUG(1, ("Missing ccache name in child response [%.*s].\n", *msg_len,
+                                                                     &buf[p]));
         goto done;
     }
 
     if (*msg_status == PAM_AUTHINFO_UNAVAIL) {
         be_mark_offline(be_req->be_ctx);
-        pd->pam_status = *msg_status;
-        dp_err = DP_ERR_OFFLINE;
+        kr->is_offline = true;
+    }
+
+    struct sysdb_attrs *attrs;
+    attrs = sysdb_new_attrs(kr);
+    ret = sysdb_attrs_add_string(attrs, SYSDB_CCACHE_FILE, kr->ccname);
+    if (ret != EOK) {
+        DEBUG(1, ("sysdb_attrs_add_string failed.\n"));
         goto done;
     }
 
-    if (*msg_status == PAM_SUCCESS && pd->cmd == SSS_PAM_AUTHENTICATE) {
-        dummy = dp_opt_get_cstring(krb5_ctx->opts, KRB5_REALM);
-        if (dummy != NULL) {
-            env = talloc_asprintf(pd, "%s=%s", SSSD_KRB5_REALM, dummy);
-            if (env == NULL) {
-                DEBUG(1, ("talloc_asprintf failed.\n"));
-                goto done;
-            }
-            ret = pam_add_response(pd, PAM_ENV_ITEM, strlen(env)+1,
-                                   (uint8_t *) env);
-            if (ret != EOK) {
-                DEBUG(1, ("pam_add_response failed.\n"));
-                goto done;
-            }
-        }
+    req = krb5_save_ccname_send(kr, be_req->be_ctx->ev, be_req->be_ctx->sysdb,
+                                be_req->be_ctx->domain, pd->user, kr->ccname);
+    if (req == NULL) {
+        DEBUG(1, ("krb5_save_ccname_send failed.\n"));
+        goto done;
+    }
 
-        dummy = dp_opt_get_cstring(krb5_ctx->opts, KRB5_KDC);
-        if (dummy != NULL) {
-            env = talloc_asprintf(pd, "%s=%s", SSSD_KRB5_KDC, dummy);
-            if (env == NULL) {
-                DEBUG(1, ("talloc_asprintf failed.\n"));
-                goto done;
-            }
-            ret = pam_add_response(pd, PAM_ENV_ITEM, strlen(env)+1,
-                                   (uint8_t *) env);
-            if (ret != EOK) {
-                DEBUG(1, ("pam_add_response failed.\n"));
-                goto done;
-            }
+    tevent_req_set_callback(req, krb5_save_ccname_done, kr);
+    return;
+done:
+    talloc_free(kr);
+    pd->pam_status = pam_status;
+    krb_reply(be_req, dp_err, pd->pam_status);
+}
+
+static void krb5_save_ccname_done(struct tevent_req *req)
+{
+    struct krb5child_req *kr = tevent_req_callback_data(req,
+                                                        struct krb5child_req);
+    struct pam_data *pd = kr->pd;
+    struct be_req *be_req = kr->req;
+    struct krb5_ctx *krb5_ctx = kr->krb5_ctx;
+    int pam_status=PAM_SYSTEM_ERR;
+    int dp_err = DP_ERR_FATAL;
+    int ret;
+    char *password = NULL;
+
+    if (pd->cmd == SSS_PAM_AUTHENTICATE) {
+        ret = add_krb5_env(krb5_ctx->opts, kr->ccname, pd);
+        if (ret != EOK) {
+            DEBUG(1, ("add_krb5_env failed.\n"));
+            goto failed;
         }
     }
 
-    pd->pam_status = *msg_status;
-    dp_err = DP_ERR_OK;
+    ret = sysdb_set_user_attr_recv(req);
+    talloc_zfree(req);
+    if (ret != EOK) {
+        DEBUG(1, ("Saving ccache name failed.\n"));
+        goto failed;
+    }
+
+    if (kr->is_offline) {
+        DEBUG(4, ("Backend is marked offline, retry later!\n"));
+        pam_status = PAM_AUTHINFO_UNAVAIL;
+        dp_err = DP_ERR_OFFLINE;
+        goto failed;
+    }
+
+    if (be_req->be_ctx->domain->cache_credentials == TRUE) {
 
-    if (pd->pam_status == PAM_SUCCESS &&
-        be_req->be_ctx->domain->cache_credentials == TRUE) {
+        /* password caching failures are not fatal errors */
+        pd->pam_status = PAM_SUCCESS;
 
         switch(pd->cmd) {
             case SSS_PAM_AUTHENTICATE:
@@ -941,24 +1276,27 @@ static void krb5_pam_handler_done(struct tevent_req *req)
 
         if (password == NULL) {
             DEBUG(0, ("password not available, offline auth may not work.\n"));
-            goto done;
+            goto failed;
         }
 
         talloc_set_destructor((TALLOC_CTX *)password, password_destructor);
 
-        subreq = sysdb_cache_password_send(be_req, be_req->be_ctx->ev,
-                                           be_req->be_ctx->sysdb, NULL,
-                                           be_req->be_ctx->domain, pd->user,
-                                           password);
-        if (subreq == NULL) {
+        req = sysdb_cache_password_send(be_req, be_req->be_ctx->ev,
+                                        be_req->be_ctx->sysdb, NULL,
+                                        be_req->be_ctx->domain, pd->user,
+                                        password);
+        if (req == NULL) {
             DEBUG(2, ("cache_password_send failed, offline auth may not work.\n"));
-            goto done;
+            goto failed;
         }
-        tevent_req_set_callback(subreq, krb5_pam_handler_cache_done, be_req);
+        tevent_req_set_callback(req, krb5_pam_handler_cache_done, be_req);
         return;
     }
 
-done:
+failed:
+    talloc_free(kr);
+
+    pd->pam_status = pam_status;
     krb_reply(be_req, dp_err, pd->pam_status);
 }
 
diff --git a/server/providers/krb5/krb5_auth.h b/server/providers/krb5/krb5_auth.h
index 84eafec..54ce2b8 100644
--- a/server/providers/krb5/krb5_auth.h
+++ b/server/providers/krb5/krb5_auth.h
@@ -47,8 +47,9 @@ struct krb5child_req {
 
     struct tevent_timer *timeout_handler;
 
-    char *ccname;
+    const char *ccname;
     const char *homedir;
+    bool is_offline;
 };
 
 struct krb5_ctx {
diff --git a/server/providers/krb5/krb5_child.c b/server/providers/krb5/krb5_child.c
index 8d8a123..f7809d2 100644
--- a/server/providers/krb5/krb5_child.c
+++ b/server/providers/krb5/krb5_child.c
@@ -73,7 +73,6 @@ struct krb5_child_ctx {
 
 struct krb5_req {
     krb5_context ctx;
-    krb5_ccache cc;
     krb5_principal princ;
     char* name;
     krb5_creds *creds;
@@ -106,6 +105,98 @@ struct response {
     uint8_t *buf;
 };
 
+static krb5_error_code create_ccache_file(struct krb5_req *kr, krb5_creds *creds)
+{
+    krb5_error_code kerr;
+    krb5_ccache tmp_cc = NULL;
+    char *cc_file_name;
+    int fd = -1;
+    size_t ccname_len;
+    char *dummy;
+    char *tmp_ccname;
+
+    if (strncmp(kr->ccname, "FILE:", 5) == 0) {
+        cc_file_name = kr->ccname + 5;
+    } else {
+        cc_file_name = kr->ccname;
+    }
+
+    if (cc_file_name[0] != '/') {
+        DEBUG(1, ("Ccache filename is not an absolute path.\n"));
+        return EINVAL;
+    }
+
+    dummy = strrchr(cc_file_name, '/');
+    tmp_ccname = talloc_strndup(kr, cc_file_name, (size_t) (dummy-cc_file_name));
+    if (tmp_ccname == NULL) {
+        DEBUG(1, ("talloc_strdup failed.\n"));
+        return ENOMEM;
+    }
+    tmp_ccname = talloc_asprintf_append(tmp_ccname, "/.krb5cc_dummy_XXXXXX");
+
+    fd = mkstemp(tmp_ccname);
+    if (fd == -1) {
+        DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+        return errno;
+    }
+
+    kerr = krb5_cc_resolve(kr->ctx, tmp_ccname, &tmp_cc);
+    if (kerr != 0) {
+        KRB5_DEBUG(1, kerr);
+        goto done;
+    }
+
+    kerr = krb5_cc_initialize(kr->ctx, tmp_cc, kr->princ);
+    if (kerr != 0) {
+        KRB5_DEBUG(1, kerr);
+        goto done;
+    }
+    if (fd != -1) {
+        close(fd);
+        fd = -1;
+    }
+
+    if (creds != NULL) {
+        kerr = krb5_cc_store_cred(kr->ctx, tmp_cc, creds);
+        if (kerr != 0) {
+            KRB5_DEBUG(1, kerr);
+            goto done;
+        }
+    }
+
+    kerr = krb5_cc_close(kr->ctx, tmp_cc);
+    if (kerr != 0) {
+        KRB5_DEBUG(1, kerr);
+        goto done;
+    }
+    tmp_cc = NULL;
+
+    ccname_len = strlen(cc_file_name);
+    if (ccname_len >= 6 && strcmp(cc_file_name + (ccname_len-6), "XXXXXX")==0 ) {
+        fd = mkstemp(cc_file_name);
+        if (fd == -1) {
+            DEBUG(1, ("mkstemp failed [%d][%s].\n", errno, strerror(errno)));
+            kerr = errno;
+            goto done;
+        }
+    }
+
+    kerr = rename(tmp_ccname, cc_file_name);
+    if (kerr == -1) {
+        DEBUG(1, ("rename failed [%d][%s].\n", errno, strerror(errno)));
+    }
+
+done:
+    if (fd != -1) {
+        close(fd);
+        fd = -1;
+    }
+    if (kerr != 0 && tmp_cc != NULL) {
+        krb5_cc_destroy(kr->ctx, tmp_cc);
+    }
+    return kerr;
+}
+
 static struct response *init_response(TALLOC_CTX *mem_ctx) {
     struct response *r;
     r = talloc(mem_ctx, struct response);
@@ -163,7 +254,7 @@ static struct response *prepare_response_message(struct krb5_req *kr,
     }
 
     if (kerr == 0) {
-        if (kr->cc == NULL || kr->ccname == NULL) {
+        if (kr->ccname == NULL) {
             DEBUG(1, ("Error obtaining ccname.\n"));
             return NULL;
         }
@@ -195,6 +286,27 @@ static struct response *prepare_response_message(struct krb5_req *kr,
     return resp;
 }
 
+static errno_t sendresponse(int fd, krb5_error_code kerr, int pam_status,
+                            struct krb5_req *kr)
+{
+    int ret;
+    struct response *resp;
+
+    resp = prepare_response_message(kr, kerr, pam_status);
+    if (resp == NULL) {
+        DEBUG(1, ("prepare_response_message failed.\n"));
+        return ENOMEM;
+    }
+
+    ret = write(fd, resp->buf, resp->size);
+    if (ret == -1) {
+        DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
+        return errno;
+    }
+
+    return EOK;
+}
+
 static krb5_error_code validate_tgt(struct krb5_req *kr)
 {
     krb5_error_code kerr;
@@ -293,9 +405,6 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
 {
     krb5_error_code kerr = 0;
     int ret;
-    int fd = -1;
-    size_t ccname_len = 0;
-    size_t offset = 0;
 
     kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
                                         password, NULL, NULL, 0, NULL,
@@ -324,47 +433,12 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
         DEBUG(9, ("TGT validation is disabled.\n"));
     }
 
-    if (kr->ccname[0] == '/' || strncmp(kr->ccname, "FILE:", 5) == 0) {
-        offset = 0;
-        if (kr->ccname[0] == 'F') {
-            offset = 5;
-        }
-        ccname_len = strlen(kr->ccname);
-        if (ccname_len >= 6 &&
-            strcmp(kr->ccname + (ccname_len-6), "XXXXXX")==0 ) {
-            fd = mkstemp(kr->ccname + offset);
-            if (fd == -1) {
-                DEBUG(1, ("mkstemp failed [%d][%s].\n", errno,
-                          strerror(errno)));
-                kerr = KRB5KRB_ERR_GENERIC;
-                goto done;
-            }
-        }
-    }
-
-    kerr = krb5_cc_resolve(kr->ctx, kr->ccname, &kr->cc);
+    kerr = create_ccache_file(kr, kr->creds);
     if (kerr != 0) {
         KRB5_DEBUG(1, kerr);
         goto done;
     }
 
-    kerr = krb5_cc_initialize(kr->ctx, kr->cc, kr->princ);
-    if (fd != -1) {
-        close(fd);
-    }
-    if (kerr != 0) {
-        KRB5_DEBUG(1, kerr);
-        goto done;
-    }
-
-    kerr = krb5_cc_store_cred(kr->ctx, kr->cc, kr->creds);
-    if (kerr != 0) {
-        KRB5_DEBUG(1, kerr);
-        krb5_cc_destroy(kr->ctx, kr->cc);
-        kr->cc = NULL;
-        goto done;
-    }
-
     kerr = 0;
 
 done:
@@ -378,10 +452,8 @@ static errno_t changepw_child(int fd, struct krb5_req *kr)
 {
     int ret;
     krb5_error_code kerr = 0;
-    errno_t err;
     char *pass_str = NULL;
     char *newpass_str = NULL;
-    struct response *resp = NULL;
     int pam_status = PAM_SYSTEM_ERR;
     int result_code = -1;
     krb5_data result_code_string;
@@ -425,6 +497,8 @@ static errno_t changepw_child(int fd, struct krb5_req *kr)
     if (kerr != 0 || result_code != 0) {
         if (kerr != 0) {
             KRB5_DEBUG(1, kerr);
+        } else {
+            kerr = KRB5KRB_ERR_GENERIC;
         }
 
         if (result_code_string.length > 0) {
@@ -455,43 +529,20 @@ static errno_t changepw_child(int fd, struct krb5_req *kr)
     }
 
 sendresponse:
-    resp = prepare_response_message(kr, kerr, pam_status);
-    if (resp == NULL) {
-        DEBUG(1, ("prepare_response_message failed.\n"));
-        if (kr->cc != NULL) {
-            krb5_cc_destroy(kr->ctx, kr->cc);
-            kr->cc = NULL;
-        }
-        return ENOMEM;
-    }
-
-    ret = write(fd, resp->buf, resp->size);
-    if (ret == -1) {
-        err = errno;
-        DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
-        if (kr->cc != NULL) {
-            krb5_cc_destroy(kr->ctx, kr->cc);
-            kr->cc = NULL;
-        }
-        return err;
-    }
-
-    if (kr->cc != NULL) {
-        krb5_cc_close(kr->ctx, kr->cc);
-        kr->cc = NULL;
+    ret = sendresponse(fd, kerr, pam_status, kr);
+    if (ret != EOK) {
+        DEBUG(1, ("sendresponse failed.\n"));
     }
 
-    return EOK;
+    return ret;
 }
 
 static errno_t tgt_req_child(int fd, struct krb5_req *kr)
 {
     int ret;
     krb5_error_code kerr = 0;
-    errno_t err;
     char *pass_str = NULL;
     int pam_status = PAM_SYSTEM_ERR;
-    struct response *resp = NULL;
 
     pass_str = talloc_strndup(kr, (const char *) kr->pd->authtok,
                               kr->pd->authtok_size);
@@ -506,7 +557,7 @@ static errno_t tgt_req_child(int fd, struct krb5_req *kr)
     /* If the password is expired the KDC will always return
        KRB5KDC_ERR_KEY_EXP regardless if the supplied password is correct or
        not. In general the password can still be used to get a changepw ticket.
-       So we validate the password by trying to get a changepw ticket. */ 
+       So we validate the password by trying to get a changepw ticket. */
     if (kerr == KRB5KDC_ERR_KEY_EXP) {
         kerr = krb5_get_init_creds_password(kr->ctx, kr->creds, kr->princ,
                                             pass_str, NULL, NULL, 0,
@@ -540,36 +591,35 @@ static errno_t tgt_req_child(int fd, struct krb5_req *kr)
     }
 
 sendresponse:
-    resp = prepare_response_message(kr, kerr, pam_status);
-    if (resp == NULL) {
-        DEBUG(1, ("prepare_response_message failed.\n"));
-        if (kr->cc != NULL) {
-            krb5_cc_destroy(kr->ctx, kr->cc);
-            kr->cc = NULL;
-        }
-        return ENOMEM;
+    ret = sendresponse(fd, kerr, pam_status, kr);
+    if (ret != EOK) {
+        DEBUG(1, ("sendresponse failed.\n"));
     }
 
-    ret = write(fd, resp->buf, resp->size);
-    if (ret == -1) {
-        err = errno;
-        DEBUG(1, ("write failed [%d][%s].\n", errno, strerror(errno)));
-        if (kr->cc != NULL) {
-            krb5_cc_destroy(kr->ctx, kr->cc);
-            kr->cc = NULL;
-        }
-        return err;
+    return ret;
+}
+
+static errno_t create_empty_ccache(int fd, struct krb5_req *kr)
+{
+    int ret;
+    int pam_status = PAM_SUCCESS;
+
+    ret = create_ccache_file(kr, NULL);
+    if (ret != 0) {
+        KRB5_DEBUG(1, ret);
+        pam_status = PAM_SYSTEM_ERR;
     }
 
-    if (kr->cc != NULL) {
-        krb5_cc_close(kr->ctx, kr->cc);
-        kr->cc = NULL;
+    ret = sendresponse(fd, ret, pam_status, kr);
+    if (ret != EOK) {
+        DEBUG(1, ("sendresponse failed.\n"));
     }
 
-    return EOK;
+    return ret;
 }
 
-uint8_t *copy_buffer_and_add_zero(TALLOC_CTX *mem_ctx, const uint8_t *src, size_t len)
+uint8_t *copy_buffer_and_add_zero(TALLOC_CTX *mem_ctx, const uint8_t *src,
+                                  size_t len)
 {
     uint8_t *str;
 
@@ -683,11 +733,9 @@ static int krb5_cleanup(void *ptr)
         krb5_free_creds(kr->ctx, kr->creds);
     }
     if (kr->name != NULL)
-        krb5_free_unparsed_name(kr->ctx, kr->name);
+        sss_krb5_free_unparsed_name(kr->ctx, kr->name);
     if (kr->princ != NULL)
         krb5_free_principal(kr->ctx, kr->princ);
-    if (kr->cc != NULL)
-        krb5_cc_close(kr->ctx, kr->cc);
     if (kr->ctx != NULL)
         krb5_free_context(kr->ctx);
 
@@ -738,7 +786,13 @@ static int krb5_setup(struct pam_data *pd, const char *user_princ_str,
 
     switch(pd->cmd) {
         case SSS_PAM_AUTHENTICATE:
-            kr->child_req = tgt_req_child;
+            /* if authtok_size is 1 we got an empty password, this means we
+             * are offline and need to create an empty ccache file */
+            if (pd->authtok_size == 1) {
+                kr->child_req = create_empty_ccache;
+            } else {
+                kr->child_req = tgt_req_child;
+            }
             break;
         case SSS_PAM_CHAUTHTOK:
             kr->child_req = changepw_child;
@@ -901,6 +955,7 @@ int main(int argc, const char *argv[])
     ret = kr->child_req(STDOUT_FILENO, kr);
     if (ret != EOK) {
         DEBUG(1, ("Child request failed.\n"));
+        goto fail;
     }
 
     close(STDOUT_FILENO);
@@ -909,6 +964,7 @@ int main(int argc, const char *argv[])
     return 0;
 
 fail:
+    close(STDOUT_FILENO);
     talloc_free(pd);
     exit(-1);
 }
diff --git a/server/tests/find_uid-tests.c b/server/tests/find_uid-tests.c
new file mode 100644
index 0000000..bd213cd
--- /dev/null
+++ b/server/tests/find_uid-tests.c
@@ -0,0 +1,124 @@
+/*
+    SSSD
+
+    find_uid - Utilities tests
+
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2009 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 <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+
+#include <check.h>
+
+#include "util/find_uid.h"
+
+
+START_TEST(test_check_if_uid_is_active_success)
+{
+    uid_t uid;
+    bool result;
+    int ret;
+
+    uid = getuid();
+
+    ret = check_if_uid_is_active(uid, &result);
+    fail_unless(ret == EOK, "check_if_uid_is_active failed.");
+    fail_unless(result, "check_if_uid_is_active did not found my uid [%d]",
+                uid);
+}
+END_TEST
+
+START_TEST(test_check_if_uid_is_active_fail)
+{
+    uid_t uid;
+    bool result;
+    int ret;
+
+    uid = (uid_t) -4;
+
+    ret = check_if_uid_is_active(uid, &result);
+    fail_unless(ret == EOK, "check_if_uid_is_active failed.");
+    fail_unless(!result, "check_if_uid_is_active found (hopefully not active) "
+                         "uid [%d]", uid);
+}
+END_TEST
+
+START_TEST(test_get_uid_table)
+{
+    uid_t uid;
+    int ret;
+    TALLOC_CTX *tmp_ctx;
+    hash_table_t *table;
+    hash_key_t key;
+    hash_value_t value;
+
+    tmp_ctx = talloc_new(NULL);
+    fail_unless(tmp_ctx != NULL, "talloc_new failed.");
+
+    ret = get_uid_table(tmp_ctx, &table);
+    fail_unless(ret == EOK, "get_uid_table failed.");
+
+    uid = getuid();
+    key.type = HASH_KEY_ULONG;
+    key.ul = (unsigned long) uid;
+
+    ret = hash_lookup(table, &key, &value);
+
+    fail_unless(ret == HASH_SUCCESS, "Cannot find my uid [%d] in the table", uid);
+
+    uid = (uid_t) -4;
+    key.type = HASH_KEY_ULONG;
+    key.ul = (unsigned long) uid;
+
+    ret = hash_lookup(table, &key, &value);
+
+    fail_unless(ret == HASH_ERROR_KEY_NOT_FOUND, "Found (hopefully not active) "
+                                                 "uid [%d] in the table", uid);
+
+    talloc_free(tmp_ctx);
+}
+END_TEST
+
+Suite *find_uid_suite (void)
+{
+    Suite *s = suite_create ("find_uid");
+
+    TCase *tc_find_uid = tcase_create ("find_uid");
+
+    tcase_add_test (tc_find_uid, test_check_if_uid_is_active_success);
+    tcase_add_test (tc_find_uid, test_check_if_uid_is_active_fail);
+    tcase_add_test (tc_find_uid, test_get_uid_table);
+    suite_add_tcase (s, tc_find_uid);
+
+    return s;
+}
+
+int main(void)
+{
+    debug_level = 255;
+    int number_failed;
+    Suite *s = find_uid_suite ();
+    SRunner *sr = srunner_create (s);
+    srunner_run_all (sr, CK_NORMAL);
+    number_failed = srunner_ntests_failed (sr);
+    srunner_free (sr);
+    return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/server/util/find_uid.c b/server/util/find_uid.c
new file mode 100644
index 0000000..ddde7ce
--- /dev/null
+++ b/server/util/find_uid.c
@@ -0,0 +1,297 @@
+/*
+    SSSD
+
+    Create uid table
+
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2009 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 <stdio.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <talloc.h>
+#include <ctype.h>
+#include <sys/time.h>
+
+#include "common/dhash/dhash.h"
+#include "util/util.h"
+
+#define INITIAL_TABLE_SIZE 64
+#define PATHLEN (NAME_MAX + 14)
+#define BUFSIZE 4096
+
+TALLOC_CTX *hash_talloc_ctx(TALLOC_CTX *mem_ctx)
+{
+    static TALLOC_CTX * saved_ctx = NULL;
+
+    if (mem_ctx != NULL) {
+        saved_ctx = mem_ctx;
+    }
+
+    return saved_ctx;
+}
+
+void *hash_talloc(const size_t size)
+{
+    return talloc_size(hash_talloc_ctx(NULL), size);
+}
+
+void hash_talloc_free(void *ptr)
+{
+    talloc_free(ptr);
+}
+
+static errno_t get_uid_from_pid(const pid_t pid, uid_t *uid)
+{
+    int ret;
+    char path[PATHLEN];
+    struct stat stat_buf;
+    int fd;
+    char buf[BUFSIZE];
+    char *p;
+    char *e;
+    char *endptr;
+    long num=0;
+
+    ret = snprintf(path, PATHLEN, "/proc/%d/status", pid);
+    if (ret < 0) {
+        DEBUG(1, ("snprintf failed"));
+        return EINVAL;
+    } else if (ret >= PATHLEN) {
+        DEBUG(1, ("path too long?!?!\n"));
+        return EINVAL;
+    }
+
+    ret = lstat(path, &stat_buf);
+    if (ret == -1) {
+        DEBUG(1, ("lstat failed [%d][%s].\n", errno, strerror(errno)));
+        return errno;
+    }
+
+    if (!S_ISREG(stat_buf.st_mode)) {
+        DEBUG(1, ("not a regular file\n"));
+        return EINVAL;
+    }
+
+    fd = open(path, O_RDONLY);
+    if (fd == -1) {
+        DEBUG(1, ("open failed [%d][%s].\n", errno, strerror(errno)));
+        return errno;
+    }
+    ret = read(fd, buf, BUFSIZE);
+    if (ret == -1) {
+        DEBUG(1, ("read failed [%d][%s].\n", errno, strerror(errno)));
+        return errno;
+    }
+
+    ret = close(fd);
+    if (ret == -1) {
+        DEBUG(1, ("close failed [%d][%s].\n", errno, strerror(errno)));
+    }
+
+    p = strstr(buf, "\nUid:\t");
+    if (p != NULL) {
+        p += 6;
+        e = strchr(p,'\t');
+        if (e == NULL) {
+            DEBUG(1, ("missing delimiter.\n"));
+            return EINVAL;
+        } else {
+            *e = '\0';
+        }
+        num = strtol(p, &endptr, 10);
+        if(errno == ERANGE) {
+            DEBUG(1, ("strtol failed [%s].\n", strerror(errno)));
+            return errno;
+        }
+        if (*endptr != '\0') {
+            DEBUG(1, ("uid contains extra characters\n"));
+            return EINVAL;
+        }
+
+        if (num < 0 || num >= INT_MAX) {
+            DEBUG(1, ("uid out of range.\n"));
+            return ERANGE;
+        }
+
+    } else {
+        DEBUG(1, ("format error\n"));
+        return EINVAL;
+    }
+
+    *uid = num;
+
+    return EOK;
+}
+
+static errno_t name_to_pid(const char *name, pid_t *pid)
+{
+    long num;
+    char *endptr;
+
+    errno = 0;
+    num = strtol(name, &endptr, 10);
+    if(errno == ERANGE) {
+        perror("strtol");
+        return errno;
+    }
+
+    if (*endptr != '\0') {
+        DEBUG(1, ("pid string contains extra characters.\n"));
+        return EINVAL;
+    }
+
+    if (num <= 0 || num >= INT_MAX) {
+        DEBUG(1, ("pid out of range.\n"));
+        return ERANGE;
+    }
+
+    *pid = num;
+
+    return EOK;
+}
+
+static int only_numbers(char *p)
+{
+    while(*p!='\0' && isdigit(*p)) ++p;
+    return *p;
+}
+
+static errno_t get_active_uid_linux(hash_table_t *table, uid_t search_uid)
+{
+    DIR *proc_dir = NULL;
+    struct dirent *dirent;
+    int ret;
+    pid_t pid;
+    uid_t uid;
+
+    hash_key_t key;
+    hash_value_t value;
+
+    proc_dir = opendir("/proc");
+    if (proc_dir == NULL) {
+        DEBUG(1, ("Cannot open proc dir.\n"));
+        ret = errno;
+        goto done;
+    };
+
+    errno = 0;
+    while ((dirent = readdir(proc_dir)) != NULL) {
+        if (only_numbers(dirent->d_name) != 0) continue;
+        ret = name_to_pid(dirent->d_name, &pid);
+        if (ret != EOK) {
+            DEBUG(1, ("name_to_pid failed.\n"));
+            goto done;
+        }
+
+        ret = get_uid_from_pid(pid, &uid);
+        if (ret != EOK) {
+            DEBUG(1, ("get_uid_from_pid failed.\n"));
+            goto done;
+        }
+
+        if (table != NULL) {
+            key.type = HASH_KEY_ULONG;
+            key.ul = (unsigned long) uid;
+            value.type = HASH_VALUE_ULONG;
+            value.ul = (unsigned long) uid;
+
+            ret = hash_enter(table, &key, &value);
+            if (ret != HASH_SUCCESS) {
+                DEBUG(1, ("cannot add to table [%s]\n", hash_error_string(ret)));
+                ret = ENOMEM;
+                goto done;
+            }
+        } else {
+            if (uid == search_uid) {
+                ret = EOK;
+                goto done;
+            }
+        }
+
+
+        errno = 0;
+    }
+    if (errno != 0 && dirent == NULL) {
+        DEBUG(1 ,("readdir failed.\n"));
+        ret = errno;
+        goto done;
+    }
+
+    ret = closedir(proc_dir);
+    proc_dir = NULL;
+    if (ret == -1) {
+        DEBUG(1 ,("closedir failed, watch out.\n"));
+    }
+
+    if (table != NULL) {
+        ret = EOK;
+    } else {
+        ret = ENOENT;
+    }
+
+done:
+    if (proc_dir != NULL) closedir(proc_dir);
+    return ret;
+}
+
+errno_t get_uid_table(TALLOC_CTX *mem_ctx, hash_table_t **table)
+{
+#ifdef __linux__
+    int ret;
+
+    hash_talloc_ctx(mem_ctx);
+    ret = hash_create_ex(INITIAL_TABLE_SIZE, table, 0, 0, 0, 0,
+                         hash_talloc, hash_talloc_free, NULL);
+    if (ret != HASH_SUCCESS) {
+        DEBUG(1, ("hash_create_ex failed [%s]\n", hash_error_string(ret)));
+        return ENOMEM;
+    }
+
+    return get_active_uid_linux(*table, 0);
+#else
+    return ENOSYS;
+#endif
+}
+
+errno_t check_if_uid_is_active(uid_t uid, bool *result)
+{
+    int ret;
+
+    ret = get_active_uid_linux(NULL, uid);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(1, ("get_uid_table failed.\n"));
+        return ret;
+    }
+
+    if (ret == EOK) {
+        *result = true;
+    } else {
+        *result = false;
+    }
+
+    return EOK;
+}
diff --git a/server/util/find_uid.h b/server/util/find_uid.h
new file mode 100644
index 0000000..75665ef
--- /dev/null
+++ b/server/util/find_uid.h
@@ -0,0 +1,36 @@
+/*
+    SSSD
+
+    Create uid table
+
+    Authors:
+        Sumit Bose <sbose at redhat.com>
+
+    Copyright (C) 2009 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/>.
+*/
+#ifndef __FIND_UID_H__
+#define __FIND_UID_H__
+
+#include <talloc.h>
+#include <sys/types.h>
+
+#include "common/dhash/dhash.h"
+#include "util/util.h"
+
+errno_t get_uid_table(TALLOC_CTX *mem_ctx, hash_table_t **table);
+errno_t check_if_uid_is_active(uid_t uid, bool *result);
+
+#endif /* __FIND_UID_H__ */
-- 
1.6.2.5



More information about the sssd-devel mailing list