From 4a4549c49bb4fff8eb04f56cb056d8483af5e1df Mon Sep 17 00:00:00 2001 From: nekral-guest Date: Mon, 18 May 2009 18:32:17 +0000 Subject: [PATCH] * src/userdel.c, libmisc/user_busy.c, libmisc/Makefile.am, lib/prototypes.h: Move user_busy() to libmisc/user_busy.c. * NEWS, libmisc/user_busy.c: On Linux, do not check if an user is logged in with utmp, but check if the user is running some processes. If not on Linux, continue to search for an utmp record, but make sure the process recorded in the utmp entry is still running. --- ChangeLog | 12 ++- NEWS | 5 + lib/prototypes.h | 3 + libmisc/Makefile.am | 1 + libmisc/user_busy.c | 226 ++++++++++++++++++++++++++++++++++++++++++++ src/userdel.c | 71 ++++---------- 6 files changed, 263 insertions(+), 55 deletions(-) create mode 100644 libmisc/user_busy.c diff --git a/ChangeLog b/ChangeLog index acd6e067..7328c6aa 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,14 @@ -2009-05-17 Nicolas François +2009-05-18 Nicolas François + + * src/userdel.c, libmisc/user_busy.c, libmisc/Makefile.am, + lib/prototypes.h: Move user_busy() to libmisc/user_busy.c. + * NEWS, libmisc/user_busy.c: On Linux, do not check if an user is + logged in with utmp, but check if the user is running some + processes. If not on Linux, continue to search for an utmp record, + but make sure the process recorded in the utmp entry is still + running. + +2009-05-18 Nicolas François * man/usermod.8.xml: Document the -m/--move-home option. diff --git a/NEWS b/NEWS index dbd0eebd..ff110f96 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,11 @@ shadow-4.1.4 -> shadow-4.1.4.1 UNRELEASED - login * Fix failures with empty usernames on non PAM versions. * Fix CONSOLE (securetty) support on non PAM versions. +- userdel + * On Linux, do not check if an user is logged in with utmp, but check if + the user is running some processes. + * If not on Linux, continue to search for an utmp record, but make sure + the process recorded in the utmp entry is still running. shadow-4.1.3.1 -> shadow-4.1.4 2009-05-10 diff --git a/lib/prototypes.h b/lib/prototypes.h index ccbf1bce..c6180191 100644 --- a/lib/prototypes.h +++ b/lib/prototypes.h @@ -363,6 +363,9 @@ extern char *tz (const char *); /* ulimit.c */ extern int set_filesize_limit (int blocks); +/* user_busy.c */ +extern int user_busy (const char *name, uid_t uid); + /* utmp.c */ extern /*@null@*/struct utmp *get_current_utmp (void); extern struct utmp *prepare_utmp (const char *name, diff --git a/libmisc/Makefile.am b/libmisc/Makefile.am index 8b577b29..dd5a0848 100644 --- a/libmisc/Makefile.am +++ b/libmisc/Makefile.am @@ -56,6 +56,7 @@ libmisc_a_SOURCES = \ ttytype.c \ tz.c \ ulimit.c \ + user_busy.c \ utmp.c \ valid.c \ xgetpwnam.c \ diff --git a/libmisc/user_busy.c b/libmisc/user_busy.c new file mode 100644 index 00000000..2e22615e --- /dev/null +++ b/libmisc/user_busy.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 1991 - 1994, Julianne Frances Haugh + * Copyright (c) 1996 - 2000, Marek Michałkiewicz + * Copyright (c) 2000 - 2006, Tomasz Kłoczko + * Copyright (c) 2007 - 2009, Nicolas François + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the copyright holders or contributors may not be used to + * endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +#ident "$Id: $" + +#include +#include +#include +#include +#include "defines.h" +#include "prototypes.h" + +#ifdef __linux__ +static int check_status (const char *sname, uid_t uid); +static int user_busy_processes (uid_t uid); +#else /* !__linux__ */ +static int user_busy_utmp (const char *name); +#endif /* !__linux__ */ + +/* + * user_busy - check if an user if currently running processes + */ +int user_busy (const char *name, uid_t uid) +{ + /* There are no standard ways to get the list of processes. + * An option could be to run an external tool (ps). + */ +#ifdef __linux__ + /* On Linux, directly parse /proc */ + return user_busy_processes (uid); +#else /* !__linux__ */ + /* If we cannot rely on /proc, check is there is a record in utmp + * indicating that the user is still logged in */ + return user_busy_utmp (name); +#endif /* !__linux__ */ +} + +#ifndef __linux__ +static int user_busy_utmp (const char *name) +{ +#ifdef USE_UTMPX + struct utmpx *utent; + + setutxent (); + while ((utent = getutxent ()) != NULL) +#else /* !USE_UTMPX */ + struct utmp *utent; + + setutent (); + while ((utent = getutent ()) != NULL) +#endif /* !USE_UTMPX */ + { + if (utent->ut_type != USER_PROCESS) { + continue; + } + if (strncmp (utent->ut_user, name, sizeof utent->ut_user) != 0) { + continue; + } + if (kill (utent->ut_pid, 0) != 0) { + continue; + } + + return USER_BUSY; + } +} +#endif /* !__linux__ */ + +#ifdef __linux__ +static int check_status (const char *sname, uid_t uid) +{ + /* 40: /proc/xxxxxxxxxx/task/xxxxxxxxxx/status + \0 */ + char status[40]; + char line[1024]; + FILE *sfile; + + snprintf (status, 40, "/proc/%s/status", sname); + status[39] = '\0'; + + sfile = fopen (status, "r"); + if (NULL == sfile) { + return 0; + } + while (fgets (line, sizeof (line), sfile) == line) { + if (strncmp (line, "Uid:\t", 5) == 0) { + unsigned long ruid, euid, suid; + assert (uid == (unsigned long) uid); + if (sscanf (line, + "Uid:\t%lu\t%lu\t%lu\n", + &ruid, &euid, &suid) == 3) { + if ( (ruid == (unsigned long) uid) + || (euid == (unsigned long) uid) + || (suid == (unsigned long) uid)) { + (void) fclose (sfile); + return 1; + } + } else { + /* Ignore errors. This is just a best effort. */ + } + (void) fclose (sfile); + return 0; + } + } + (void) fclose (sfile); + return 0; +} + +static int user_busy_processes (uid_t uid) +{ + DIR *proc; + struct dirent *ent; + char *tmp_d_name; + pid_t pid; + DIR *task_dir; + /* 22: /proc/xxxxxxxxxx/task + \0 */ + char task_path[22]; + char root_path[22]; + struct stat sbroot; + struct stat sbroot_process; + + proc = opendir ("/proc"); + if (proc == NULL) { + perror ("opendir /proc"); + return 0; + } + if (stat ("/", &sbroot) != 0) { + perror ("stat (\"/\")"); + (void) closedir (proc); + return 0; + } + + while ((ent = readdir (proc)) != NULL) { + tmp_d_name = ent->d_name; + /* + * Ingo Molnar's patch introducing NPTL for 2.4 hides + * threads in the /proc directory by prepending a period. + * This patch is applied by default in some RedHat + * kernels. + */ + if ( (strcmp (tmp_d_name, ".") == 0) + || (strcmp (tmp_d_name, "..") == 0)) { + continue; + } + if (*tmp_d_name == '.') { + tmp_d_name++; + } + + /* Check if this is a valid PID */ + if (get_pid (tmp_d_name, &pid) == 0) { + continue; + } + + /* Check if the process is in our chroot */ + snprintf (root_path, 22, "/proc/%lu/root", (unsigned long) pid); + root_path[21] = '\0'; + if (stat (root_path, &sbroot_process) != 0) { + continue; + } + if ( (sbroot.st_dev != sbroot_process.st_dev) + || (sbroot.st_ino != sbroot_process.st_ino)) { + continue; + } + + if (check_status (tmp_d_name, uid) != 0) { + (void) closedir (proc); + return 1; + } + + snprintf (task_path, 22, "/proc/%lu/task", (unsigned long) pid); + task_path[21] = '\0'; + task_dir = opendir (task_path); + if (task_dir != NULL) { + while ((ent = readdir (task_dir)) != NULL) { + pid_t tid; + if (get_pid (ent->d_name, &tid) == 0) { + continue; + } + if (tid == pid) { + continue; + } + if (check_status (task_path+6, uid) != 0) { + (void) closedir (proc); + return 1; + } + } + (void) closedir (task_dir); + } else { + /* Ignore errors. This is just a best effort */ + } + } + + (void) closedir (proc); + return 0; +} +#endif /* __linux__ */ + diff --git a/src/userdel.c b/src/userdel.c index aca65243..7752fc74 100644 --- a/src/userdel.c +++ b/src/userdel.c @@ -100,7 +100,6 @@ static void close_files (void); static void fail_exit (int); static void open_files (void); static void update_user (void); -static void user_busy (const char *, uid_t); static void user_cancel (const char *); #ifdef EXTRA_CHECK_HOME_DIR @@ -576,57 +575,6 @@ static void update_user (void) SYSLOG ((LOG_INFO, "delete user '%s'\n", user_name)); } -/* - * user_busy - see if user is logged in. - * - * XXX - should probably check if there are any processes owned - * by this user. Also, I think this check should be in usermod - * as well (at least when changing username or UID). --marekm - */ -static void user_busy (const char *name, uid_t uid) -{ - -/* - * We see if the user is logged in by looking for the user name - * in the utmp file. - */ -#ifdef USE_UTMPX - struct utmpx *utent; - - setutxent (); - while ((utent = getutxent ()) != NULL) -#else /* !USE_UTMPX */ - struct utmp *utent; - - setutent (); - while ((utent = getutent ()) != NULL) -#endif /* !USE_UTMPX */ - { - if (utent->ut_type != USER_PROCESS) { - continue; - } - if (strncmp (utent->ut_user, name, sizeof utent->ut_user) != 0) { - continue; - } - if (kill (utent->ut_pid, 0) != 0) { - continue; - } - - fprintf (stderr, - _("%s: user %s is currently logged in\n"), - Prog, name); - if (!fflg) { -#ifdef WITH_AUDIT - audit_logger (AUDIT_DEL_USER, Prog, - "deleting user logged in", - name, AUDIT_NO_ID, - SHADOW_AUDIT_FAILURE); -#endif - exit (E_USER_BUSY); - } - } -} - /* * user_cancel - cancel cron and at jobs * @@ -648,7 +596,7 @@ static void user_cancel (const char *user) if (pid == 0) { execl (cmd, cmd, user, (char *) 0); perror (cmd); - _exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC); + exit (errno == ENOENT ? E_CMD_NOTFOUND : E_CMD_NOEXEC); } else if ((pid_t)-1 == pid) { perror ("fork"); return; @@ -893,8 +841,23 @@ int main (int argc, char **argv) #endif /* * Check to make certain the user isn't logged in. + * Note: This is a best effort basis. The user may log in between, + * a cron job may be started on her behalf, etc. */ - user_busy (user_name, user_id); + if (user_busy (user_name, user_id) != 0) { + fprintf (stderr, + _("%s: user %s is currently logged in\n"), + Prog, user_name); + if (!fflg) { +#ifdef WITH_AUDIT + audit_logger (AUDIT_DEL_USER, Prog, + "deleting user logged in", + user_name, AUDIT_NO_ID, + SHADOW_AUDIT_FAILURE); +#endif + exit (E_USER_BUSY); + } + } /* * Do the hard stuff - open the files, create the user entries,