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,