170b76cdd1
On Linux, utmpx and utmp are identical. However, documentation (manual pages) covers utmp, and just says about utmpx that it's identical to utmp. It seems that it's preferred to use utmp, at least by reading the manual pages. Moreover, we were defaulting to utmp (utmpx had to be explicitly enabled at configuration time). So, it seems safer to just make it permanent, which should not affect default builds. Signed-off-by: Alejandro Colomar <alx@kernel.org>
267 lines
6.2 KiB
C
267 lines
6.2 KiB
C
/*
|
|
* SPDX-FileCopyrightText: 1991 - 1994, Julianne Frances Haugh
|
|
* SPDX-FileCopyrightText: 1996 - 2000, Marek Michałkiewicz
|
|
* SPDX-FileCopyrightText: 2000 - 2006, Tomasz Kłoczko
|
|
* SPDX-FileCopyrightText: 2007 - 2009, Nicolas François
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#ident "$Id: $"
|
|
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <sys/types.h>
|
|
#include <dirent.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include "defines.h"
|
|
#include "prototypes.h"
|
|
#ifdef ENABLE_SUBIDS
|
|
#include "subordinateio.h"
|
|
#endif /* ENABLE_SUBIDS */
|
|
#include "shadowlog.h"
|
|
|
|
#ifdef __linux__
|
|
static int check_status (const char *name, const char *sname, uid_t uid);
|
|
static int user_busy_processes (const char *name, 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 (name, 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)
|
|
{
|
|
struct utmp *utent;
|
|
|
|
setutent ();
|
|
while ((utent = getutent ()) != NULL)
|
|
{
|
|
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 (log_get_logfd(),
|
|
_("%s: user %s is currently logged in\n"),
|
|
log_get_progname(), name);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif /* !__linux__ */
|
|
|
|
#ifdef __linux__
|
|
#ifdef ENABLE_SUBIDS
|
|
#define in_parentuid_range(uid) ((uid) >= parentuid && (uid) < parentuid + range)
|
|
static int different_namespace (const char *sname)
|
|
{
|
|
/* 41: /proc/xxxxxxxxxx/task/xxxxxxxxxx/ns/user + \0 */
|
|
char path[41];
|
|
char buf[512], buf2[512];
|
|
ssize_t llen1, llen2;
|
|
|
|
snprintf (path, 41, "/proc/%s/ns/user", sname);
|
|
|
|
if ((llen1 = readlink (path, buf, sizeof(buf))) == -1)
|
|
return 0;
|
|
|
|
if ((llen2 = readlink ("/proc/self/ns/user", buf2, sizeof(buf2))) == -1)
|
|
return 0;
|
|
|
|
if (llen1 == llen2 && memcmp (buf, buf2, llen1) == 0)
|
|
return 0; /* same namespace */
|
|
|
|
return 1;
|
|
}
|
|
#endif /* ENABLE_SUBIDS */
|
|
|
|
|
|
static int check_status (const char *name, 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);
|
|
|
|
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);
|
|
(void) fclose (sfile);
|
|
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) ) {
|
|
return 1;
|
|
}
|
|
#ifdef ENABLE_SUBIDS
|
|
if ( different_namespace (sname)
|
|
&& ( have_sub_uids(name, ruid, 1)
|
|
|| have_sub_uids(name, euid, 1)
|
|
|| have_sub_uids(name, suid, 1))
|
|
) {
|
|
return 1;
|
|
}
|
|
#endif /* ENABLE_SUBIDS */
|
|
} else {
|
|
/* Ignore errors. This is just a best effort. */
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
(void) fclose (sfile);
|
|
return 0;
|
|
}
|
|
|
|
static int user_busy_processes (const char *name, 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;
|
|
|
|
#ifdef ENABLE_SUBIDS
|
|
sub_uid_open (O_RDONLY);
|
|
#endif /* ENABLE_SUBIDS */
|
|
|
|
proc = opendir ("/proc");
|
|
if (proc == NULL) {
|
|
perror ("opendir /proc");
|
|
#ifdef ENABLE_SUBIDS
|
|
sub_uid_close();
|
|
#endif
|
|
return 0;
|
|
}
|
|
if (stat ("/", &sbroot) != 0) {
|
|
perror ("stat (\"/\")");
|
|
(void) closedir (proc);
|
|
#ifdef ENABLE_SUBIDS
|
|
sub_uid_close();
|
|
#endif
|
|
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 (name, tmp_d_name, uid) != 0) {
|
|
(void) closedir (proc);
|
|
#ifdef ENABLE_SUBIDS
|
|
sub_uid_close();
|
|
#endif
|
|
fprintf (log_get_logfd(),
|
|
_("%s: user %s is currently used by process %d\n"),
|
|
log_get_progname(), name, pid);
|
|
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 (name, task_path+6, uid) != 0) {
|
|
(void) closedir (proc);
|
|
(void) closedir (task_dir);
|
|
#ifdef ENABLE_SUBIDS
|
|
sub_uid_close();
|
|
#endif
|
|
fprintf (log_get_logfd(),
|
|
_("%s: user %s is currently used by process %d\n"),
|
|
log_get_progname(), name, pid);
|
|
return 1;
|
|
}
|
|
}
|
|
(void) closedir (task_dir);
|
|
} else {
|
|
/* Ignore errors. This is just a best effort */
|
|
}
|
|
}
|
|
|
|
(void) closedir (proc);
|
|
#ifdef ENABLE_SUBIDS
|
|
sub_uid_close();
|
|
#endif /* ENABLE_SUBIDS */
|
|
return 0;
|
|
}
|
|
#endif /* __linux__ */
|
|
|