[SSSD] [PATCH] Improve handling of ccache files

Sumit Bose sbose at redhat.com
Fri Nov 20 13:41:28 UTC 2009


On Thu, Nov 19, 2009 at 02:31:36PM +0100, Sumit Bose wrote:
> 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

Stephen found a compilation issue. The attached version should fix it.

bye,
Sumit
-------------- next part --------------
>From d3e45ae1df2bea9b63e5c94900bc3b82affe01f9 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..2fead50
--- /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 "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..154a5f9
--- /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 "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