>From 331513efff86c377203c7ea9a86381c989233b8e Mon Sep 17 00:00:00 2001
From: Simo Sorce <simo@redhat.com>
Date: Wed, 28 Aug 2013 21:19:32 -0400
Subject: [PATCH 01/15] krb5: Add calls to change and restore credentials

In some cases we want to temporarily assume user credentials but allow the
process to regain back the original credentials (normally regaining uid 0).

Related:
https://fedorahosted.org/sssd/ticket/2061
---
 src/providers/krb5/krb5_become_user.c | 125 ++++++++++++++++++++++++++++++++++
 src/providers/krb5/krb5_utils.h       |   6 ++
 2 files changed, 131 insertions(+)

diff --git a/src/providers/krb5/krb5_become_user.c b/src/providers/krb5/krb5_become_user.c
index 70bc5630ece21505b58bd1a8795d7ab4b7864460..5539e114d33a7a2b0dfabb66f03144eddd230658 100644
--- a/src/providers/krb5/krb5_become_user.c
+++ b/src/providers/krb5/krb5_become_user.c
@@ -70,3 +70,128 @@ errno_t become_user(uid_t uid, gid_t gid)
     return EOK;
 }
 
+struct sss_creds {
+    uid_t uid;
+    gid_t gid;
+    int num_gids;
+    gid_t gids[];
+};
+
+errno_t restore_creds(struct sss_creds *saved_creds);
+
+/* This is a reversible version of become_user, and returns the saved
+ * credentials so that creds can be switched back calling restore_creds */
+errno_t switch_creds(TALLOC_CTX *mem_ctx,
+                     uid_t uid, gid_t gid,
+                     int num_gids, gid_t *gids,
+                     struct sss_creds **saved_creds)
+{
+    struct sss_creds *ssc = NULL;
+    int size;
+    int ret;
+
+    DEBUG(SSSDBG_FUNC_DATA, ("Switch user to [%d][%d].\n", uid, gid));
+
+    if (saved_creds) {
+        /* save current user credentials */
+        size = getgroups(0, NULL);
+        if (size == -1) {
+            ret = errno;
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Getgroups failed! (%d, %s)\n",
+                                        ret, strerror(ret)));
+            goto done;
+        }
+
+        ssc = talloc_size(mem_ctx,
+                          (sizeof(struct sss_creds) + size * sizeof(gid_t)));
+        if (!ssc) {
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Allocation failed!\n"));
+            ret = ENOMEM;
+            goto done;
+        }
+        ssc->num_gids = size;
+
+        size = getgroups(ssc->num_gids, ssc->gids);
+        if (size == -1) {
+            ret = errno;
+            DEBUG(SSSDBG_CRIT_FAILURE, ("Getgroups failed! (%d, %s)\n",
+                                        ret, strerror(ret)));
+            /* free ssc immediately otherwise the code will try to restore
+             * wrong creds */
+            talloc_zfree(ssc);
+            goto done;
+        }
+
+        /* we care only about effective ids */
+        ssc->uid = geteuid();
+        ssc->gid = getegid();
+    }
+
+    /* if we are regaining root set euid first so that we have CAP_SETUID back,
+     * ane the other calls work too, otherwise call it last so that we can
+     * change groups before we loose CAP_SETUID */
+    if (uid == 0) {
+        ret = setresuid(0, 0, 0);
+        if (ret == -1) {
+            ret = errno;
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("setresuid failed [%d][%s].\n", ret, strerror(ret)));
+            goto done;
+        }
+    }
+
+    /* TODO: use prctl to get/set capabilities too ? */
+
+    /* try to setgroups first should always work if CAP_SETUID is set,
+     * otherwise it will always fail, failure is not critical though as
+     * generally we only really care about uid and at mot primary gid */
+    ret = setgroups(num_gids, gids);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_TRACE_FUNC,
+              ("setgroups failed [%d][%s].\n", ret, strerror(ret)));
+    }
+
+    /* change gid now, (leaves saved gid to current, so we can restore) */
+    ret = setresgid(gid, -1, -1);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("setresgid failed [%d][%s].\n", ret, strerror(ret)));
+        goto done;
+    }
+
+    if (uid != 0) {
+        /* change uid, (leaves saved uid to current, so we can restore) */
+        ret = setresuid(uid, -1, -1);
+        if (ret == -1) {
+            ret = errno;
+            DEBUG(SSSDBG_CRIT_FAILURE,
+                  ("setresuid failed [%d][%s].\n", ret, strerror(ret)));
+            goto done;
+        }
+    }
+
+    ret = 0;
+
+done:
+    if (ret) {
+        if (ssc) {
+            /* attempt to restore creds first */
+            restore_creds(ssc);
+            talloc_free(ssc);
+        }
+    } else if (saved_creds) {
+        *saved_creds = ssc;
+    }
+    return ret;
+}
+
+errno_t restore_creds(struct sss_creds *saved_creds)
+{
+    return switch_creds(saved_creds,
+                        saved_creds->uid,
+                        saved_creds->gid,
+                        saved_creds->num_gids,
+                        saved_creds->gids, NULL);
+}
diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h
index cdc9f23641905cff300077f708087e98ba683e0d..aac3ec72ec7e1664ae96cc4e53d368e22448f5f1 100644
--- a/src/providers/krb5/krb5_utils.h
+++ b/src/providers/krb5/krb5_utils.h
@@ -80,6 +80,12 @@ char *expand_ccname_template(TALLOC_CTX *mem_ctx, struct krb5child_req *kr,
                              bool case_sensitive, bool *private_path);
 
 errno_t become_user(uid_t uid, gid_t gid);
+struct sss_creds;
+errno_t switch_creds(TALLOC_CTX *mem_ctx,
+                     uid_t uid, gid_t gid,
+                     int num_gids, gid_t *gids,
+                     struct sss_creds **saved_creds);
+errno_t restore_creds(struct sss_creds *saved_creds);
 
 errno_t get_ccache_file_data(const char *ccache_file, const char *client_name,
                              struct tgt_times *tgtt);
-- 
1.8.3.1

