From 98ebc9942e98b789048d284b71370499f2a099eb Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Wed, 17 Mar 2010 11:56:47 +0100
Subject: [PATCH] Make sss_userdel check for logged in users

sss_userdel now warns if the deleted user was logged in at the time of
deletion.

Also adds a new parameter --kick to userdel that kills all user
processes before actually deleting ther user.

Fixes: #229
---
 src/Makefile.am           |    1 +
 src/man/sss_userdel.8.xml |   11 ++++
 src/tools/sss_userdel.c   |  135 ++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 144 insertions(+), 3 deletions(-)

diff --git a/src/Makefile.am b/src/Makefile.am
index 3a156cb..c3b1fe7 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -411,6 +411,7 @@ sss_useradd_LDADD = \
 
 sss_userdel_SOURCES = \
     tools/sss_userdel.c \
+    util/find_uid.c \
     $(SSSD_UTIL_OBJ) \
     $(SSSD_TOOLS_OBJ)
 sss_userdel_LDADD = \
diff --git a/src/man/sss_userdel.8.xml b/src/man/sss_userdel.8.xml
index 0c49529..b37718c 100644
--- a/src/man/sss_userdel.8.xml
+++ b/src/man/sss_userdel.8.xml
@@ -75,6 +75,17 @@
                     </para>
                 </listitem>
             </varlistentry>
+            <varlistentry>
+                <term>
+                    <option>-k</option>,<option>--kick</option>
+                </term>
+                <listitem>
+                    <para>
+                        Before actually deleting the user, terminate all
+                        his processes.
+                    </para>
+                </listitem>
+            </varlistentry>
         </variablelist>
     </refsect1>
 
diff --git a/src/tools/sss_userdel.c b/src/tools/sss_userdel.c
index 7f17b1f..7620ff4 100644
--- a/src/tools/sss_userdel.c
+++ b/src/tools/sss_userdel.c
@@ -23,12 +23,98 @@
 #include <stdlib.h>
 #include <talloc.h>
 #include <popt.h>
+#include <sys/types.h>
+#include <sys/wait.h>
 
 #include "db/sysdb.h"
 #include "util/util.h"
+#include "util/find_uid.h"
 #include "tools/tools_util.h"
 #include "tools/sss_sync_ops.h"
 
+#ifndef KILL_CMD
+#define KILL_CMD "killall"
+#endif
+
+#ifndef KILL_CMD_USER_FLAG
+#define KILL_CMD_USER_FLAG "-u"
+#endif
+
+static int is_logged_in(TALLOC_CTX *mem_ctx, uid_t uid)
+{
+    int ret;
+    hash_key_t key;
+    hash_value_t value;
+    hash_table_t *uid_table;
+
+    ret = get_uid_table(mem_ctx, &uid_table);
+    if (ret == ENOSYS) return ret;
+    if (ret != EOK) {
+        DEBUG(1, ("Cannot initialize hash table.\n"));
+        return ret;
+    }
+
+    key.type = HASH_KEY_ULONG;
+    key.ul   = (unsigned long) uid;
+
+    ret = hash_lookup(uid_table, &key, &value);
+    talloc_zfree(uid_table);
+    return ret == HASH_SUCCESS ? EOK : ENOENT;
+}
+
+static int kick_user(struct tools_ctx *tctx)
+{
+    int ret;
+    int status;
+    pid_t pid, child_pid;
+
+    tctx->octx->lock = 1;
+
+    start_transaction(tctx);
+    if (tctx->error != EOK) {
+        return tctx->error;
+    }
+
+    ret = usermod(tctx, tctx->ev, tctx->sysdb, tctx->handle, tctx->octx);
+    if (ret != EOK) {
+        talloc_zfree(tctx->handle);
+        return ret;
+    }
+
+    end_transaction(tctx);
+    if (tctx->error != EOK) {
+        return tctx->error;
+    }
+
+    errno = 0;
+    pid = fork();
+    if (pid == 0) {
+        /* child */
+        execlp(KILL_CMD, KILL_CMD, KILL_CMD_USER_FLAG,
+               tctx->octx->name, (char *) NULL);
+        exit(errno);
+    } else {
+        /* parent */
+        if (pid == -1) {
+            DEBUG(1, ("fork failed [%d]: %s\n"));
+            return errno;
+        }
+
+        while((child_pid = waitpid(pid, &status, 0)) > 0) {
+            if (child_pid == -1) {
+                DEBUG(1, ("waitpid failed\n"));
+                return errno;
+            }
+
+            if (WIFEXITED(status)) {
+                break;
+            }
+        }
+    }
+
+    return EOK;
+}
+
 int main(int argc, const char **argv)
 {
     int ret = EXIT_SUCCESS;
@@ -38,14 +124,20 @@ int main(int argc, const char **argv)
     int pc_debug = 0;
     int pc_remove = 0;
     int pc_force = 0;
+    int pc_kick = 0;
     poptContext pc = NULL;
     struct poptOption long_options[] = {
         POPT_AUTOHELP
         { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug,
                     0, _("The debug level to run with"), NULL },
-        { "remove", 'r', POPT_ARG_NONE, NULL, 'r', _("Remove home directory and mail spool"), NULL },
-        { "no-remove", 'R', POPT_ARG_NONE, NULL, 'R', _("Do not remove home directory and mail spool"), NULL },
-        { "force", 'f', POPT_ARG_NONE, NULL, 'f', _("Force removal of files not owned by the user"), NULL },
+        { "remove", 'r', POPT_ARG_NONE, NULL, 'r',
+                    _("Remove home directory and mail spool"), NULL },
+        { "no-remove", 'R', POPT_ARG_NONE, NULL, 'R',
+                    _("Do not remove home directory and mail spool"), NULL },
+        { "force", 'f', POPT_ARG_NONE, NULL, 'f',
+                    _("Force removal of files not owned by the user"), NULL },
+        { "kick", 'k', POPT_ARG_NONE, NULL, 'k',
+                    _("Kill users' processes before removing him"), NULL },
         POPT_TABLEEND
     };
 
@@ -75,6 +167,10 @@ int main(int argc, const char **argv)
             case 'f':
                 pc_force = DO_FORCE_REMOVAL;
                 break;
+
+            case 'k':
+                pc_kick = 1;
+                break;
         }
     }
 
@@ -144,6 +240,17 @@ int main(int argc, const char **argv)
         goto fini;
     }
 
+    if (pc_kick) {
+        ret = kick_user(tctx);
+        if (ret != EOK) {
+            tctx->error = ret;
+
+            /* cancel transaction */
+            talloc_zfree(tctx->handle);
+            goto done;
+        }
+    }
+
     start_transaction(tctx);
     if (tctx->error != EOK) {
         goto done;
@@ -161,6 +268,28 @@ int main(int argc, const char **argv)
 
     end_transaction(tctx);
 
+    if (!pc_kick) {
+        ret = is_logged_in(tctx, tctx->octx->uid);
+        switch(ret) {
+            case ENOENT:
+                break;
+
+            case EOK:
+                ERROR("WARNING: The user (uid %lu) was still logged in when "
+                      "deleted.\n", (unsigned long) tctx->octx->uid);
+                break;
+
+            case ENOSYS:
+                ERROR("Cannot determine if the user was logged in on this "
+                      "platform");
+                break;
+
+            default:
+                ERROR("Error while checking if the user was logged in\n");
+                break;
+        }
+    }
+
     ret = run_userdel_cmd(tctx);
     if (ret != EOK) {
         ERROR("The post-delete command failed: %s\n", strerror(ret));
-- 
1.6.6.1

