8 Commits
next ... master

Author SHA1 Message Date
5338dec85e utmp: update state for any user login
Previously, it was thought that only the user in question needed to be
taken into account for utmp, but this is not correct. Sometimes, a
USER_PROCESS would be recorded for one user in a tty then a short while
later is it followed by another USER_PROCESS for another user in the
same tty. No LOGIN_PROCESS with a null user is neccesarily recorded.
Without taking this into account, there was a bug in the logic and a
directory could mistakenly be deleted (or not deleted).
2021-09-20 13:19:45 -05:00
0f0e234980 use utmpx instead of utmp
The latter is deprecated actually.
2021-09-20 12:45:33 -05:00
25c77fc2a5 update README 2021-09-20 11:04:21 -05:00
58aa4d55a8 simplify and replace memmove/memcpy with snprintf
The old way used memmove/memcpy when altering paths since it was writing
to .$uid and $uid. We don't need to worry about that case so the path
handling can be made simplier by just using snprintf instead. It's
probably better practice in general for combining paths.
2021-09-20 11:04:21 -05:00
e076594359 cosmetic clean up
Alphabetize the headers and remove the extra indention in
pam_sm_open_session function.
2021-09-20 11:04:21 -05:00
b065d5f273 use utmp instead of writing counters to files
The original implementation worked by updating a counter in a file
(/run/user/.$uid) anytime a user logged in. If the counter goes to 0,
remove the directory. This works fine, but is a bit ugly and has some
complicated logic/pitfalls. Instead, we can just use utmp to keep track
of logins. By looping through the LOGIN_PROCESS and USER_PROCESS, we can
determine whether or not a user has completely logged out of all
sessions (and thus remove the /run/user/$uid directory) without needing
to write any of the .$uid files. This simplifies the code and makes it
cleaner.
2021-09-20 11:04:16 -05:00
70a11e4148 configure: default to /run/user
elogind (and presumably system-logind) uses /run/user. Copy them for
consistency.
2021-09-20 11:03:20 -05:00
d6cc5d7da7 fix group permissions of the user folders
When making a directory, the user was correctly given ownership but the
group was still root. In elogind (and likely system-logind), these
folders are owned by user:user. Fix this by also setting the egid to
the user's group.
2021-09-20 11:03:20 -05:00
3 changed files with 146 additions and 281 deletions

View File

@@ -8,6 +8,8 @@ The directory will be created on login (open session) and removed on logout
(close session), and its full path made available in an environment variable,
usually `$XDG_RUNTIME_DIR`.
This fork contains some changes of the original implementation for Artix Linux.
## Free Software
pam_rundir - Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>

2
configure vendored
View File

@@ -17,7 +17,7 @@ EOF
prefix=/
securedir=
parentdir=/run/users
parentdir=/run/user
varname=XDG_RUNTIME_DIR
cmdline="$0 $@"

View File

@@ -20,24 +20,39 @@
#include "config.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <sys/prctl.h>
#include <linux/securebits.h>
#include <string.h>
#include <pwd.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <errno.h>
#include <linux/securebits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <utmpx.h>
#define PAM_SM_SESSION
#include <security/pam_modules.h>
#include <security/pam_appl.h>
#define FLAG_NAME "pam_rundir_has_counted"
static int
array_pos (char *id, char *ttys_ids[], int len)
{
for (int i = 0; i < len; ++i)
{
if (strcmp (ttys_ids[i], "") == 0)
{
ttys_ids[i] = strdup(id);
return i;
}
if (strcmp (ttys_ids[i], id) == 0)
return i;
}
return -1;
}
static int
ensure_parent_dir (void)
{
@@ -51,6 +66,23 @@ ensure_parent_dir (void)
return r;
}
static int
get_number_of_ttys (void)
{
DIR *dir;
struct dirent *ent;
int count = 0;
if ((dir = opendir ("/dev")) != NULL)
{
while ((ent = readdir (dir)) != NULL)
{
if (strncmp ("tty", ent->d_name, 3) == 0)
++count;
}
}
return count;
}
static int
intlen (int n)
{
@@ -66,159 +98,6 @@ intlen (int n)
return l;
}
static void
print_int (char *s, int n, int l)
{
s += l;
for (;;)
{
const char digits[] = "0123456789";
*--s = digits[n % 10];
if (n < 10)
break;
n /= 10;
}
}
static int
open_and_lock (const char *file)
{
int fd;
do { fd = open (file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); }
while (fd < 0 && errno == EINTR);
if (fd < 0)
return fd;
if (flock (fd, LOCK_EX) < 0)
{
close (fd);
return -1;
}
return fd;
}
static inline void
print_filename (char *s, int uid, int l)
{
/* construct file name, e.g: "/run/users/.1000" */
memcpy (s, PARENT_DIR, sizeof (PARENT_DIR) - 1);
s[sizeof (PARENT_DIR) - 1] = '/';
s[sizeof (PARENT_DIR)] = '.';
print_int (s + sizeof (PARENT_DIR) + 1, uid, l);
s[sizeof (PARENT_DIR) + 1 + l] = '\0';
}
static int
read_counter (int fd)
{
int count = 0;
/* read counter in file, as ascii string */
for (;;)
{
char buf[4];
int p;
int r;
r = read (fd, buf, sizeof (buf));
if (r == 0)
break;
else if (r < 0)
{
if (errno == EINTR)
continue;
else
return -1;
}
else if (count == 0 && r == 1 && buf[0] == '-')
/* special case: dir not usable, but not a failure */
return -2;
for (p = 0; r > 0; --r, ++p)
{
if (buf[p] < '0' || buf[p] > '9')
return -1;
count *= 10;
count += buf[p] - '0';
}
}
return count;
}
/* basically, this is called when we tried to update the counter but failed,
* leaving the file in an invalid state (i.e. only partial write, or no
* truncate).
* So here, we try to make the file "properly invalid" so any further attempt to
* read it will lead to a no-op (because of invalid data). Obviously though, if
* we fail to e.g. seek or write, we can't do anything else...
* (Anyhow, this will likely never be called.)
*/
static void
emergency_invalidate_counter (int fd)
{
int r;
if (lseek (fd, 0, SEEK_SET) < 0)
return;
do { r = write (fd, "-", 1); }
while (r < 0 && errno == EINTR);
if (r == 1)
do { r = ftruncate (fd, 1); }
while (r < 0 && errno == EINTR);
}
static int
write_counter (int fd, int count)
{
int r;
int l;
r = lseek (fd, 0, SEEK_SET);
if (r < 0)
return r;
l = (count >= 0) ? intlen (count) : 1;
{
char buf[l];
if (count >= 0)
print_int (buf, count, l);
else
buf[0] = '-';
for (;;)
{
int w = 0;
r = write (fd, buf + w, l - w);
if (r < 0)
{
if (errno = EINTR)
continue;
if (w > 0)
emergency_invalidate_counter (fd);
return -1;
}
w += r;
if (w == l)
break;
}
do { r = ftruncate (fd, l); }
while (r < 0 && errno == EINTR);
if (r < 0)
emergency_invalidate_counter (fd);
}
return r;
}
static int
rmrf (const char *path)
{
@@ -243,10 +122,7 @@ rmrf (const char *path)
int l = lp + strlen (dp->d_name) + 2;
char name[l];
memcpy (name, path, lp);
name[lp] = '/';
memcpy (name + lp + 1, dp->d_name, l - lp - 1);
snprintf(name, l, "%s/%s", path, dp->d_name);
r += rmrf (name);
}
}
@@ -257,6 +133,69 @@ rmrf (const char *path)
return r;
}
static int
user_has_session (const char *user)
{
int ttys = get_number_of_ttys ();
char *ttys_ids[ttys];
int ttys_login[ttys];
for (int i = 0; i < ttys; ++i) {
ttys_ids[i] = "";
ttys_login[i] = 0;
}
/* Loop through all the utmp entries.
* Store the id in the char array.
* Update the login state at every iteration. */
setutxent();
struct utmpx *utmp_entry = getutxent();
while (utmp_entry)
{
if (utmp_entry->ut_type == LOGIN_PROCESS)
{
// This is a logout. The user is unknown.
int pos = array_pos (utmp_entry->ut_id, ttys_ids, ttys);
ttys_login[pos] = 0;
} else if (utmp_entry->ut_type == USER_PROCESS)
{
// This is a login.
int pos = array_pos (utmp_entry->ut_id, ttys_ids, ttys);
if (strcmp (utmp_entry->ut_user, user) == 0)
{
ttys_login[pos] = 1;
} else {
ttys_login[pos] = 0;
}
}
utmp_entry = getutxent();
}
for (int i = 0; i < ttys; ++i)
{
if (strcmp (ttys_ids[i], ""))
free(ttys_ids[i]);
}
int logins = 0;
for (int i = 0; i < ttys; ++i)
{
if (ttys_login[i] == 1)
++logins;
}
/* If there is only one login, then we know the user has
* logged out. If somehow it is under 1, just assume the
* user has no sessions. */
if (logins <= 1)
{
return 0;
} else {
return 1;
}
}
PAM_EXTERN int
pam_sm_close_session (pam_handle_t *pamh, int flags, int argc, const char **argv)
{
@@ -287,48 +226,12 @@ pam_sm_close_session (pam_handle_t *pamh, int flags, int argc, const char **argv
/* get length for uid as ascii string, i.e. in file/folder name */
l = intlen ((int) pw->pw_uid);
{
char file[sizeof (PARENT_DIR) + l + 2];
int fd;
int count = 0;
/* construct user dir, e.g: "/run/user/1000" */
char dir[sizeof (PARENT_DIR) + l + 2];
snprintf(dir, sizeof (PARENT_DIR) + l + 2, "%s/%d", PARENT_DIR, pw->pw_uid);
print_filename (file, (int) pw->pw_uid, l);
fd = open_and_lock (file);
if (fd < 0)
return PAM_SESSION_ERR;
count = read_counter (fd);
if (count < 0)
{
/* -2: dir not usable, but not a failure */
r = (count == -2) ? 0 : -1;
goto done;
}
/* make sure we don't go below zero, just in case */
if (count > 0)
--count;
if (count == 0)
{
/* construct runtime dir name, i.e. remove the dot before uid */
memmove (file + sizeof (PARENT_DIR), file + sizeof (PARENT_DIR) + 1, l + 1);
r = rmrf (file);
if (r < 0)
count = -1;
}
r = write_counter (fd, count);
if (r < 0)
goto done;
if (count == -1)
r = -1;
done:
close (fd); /* also unlocks */
}
if (!user_has_session (user))
r = rmrf (dir);
return (r == 0) ? PAM_SUCCESS : PAM_SESSION_ERR;
}
@@ -358,81 +261,41 @@ pam_sm_open_session (pam_handle_t *pamh, int flags, int argc, const char **argv)
/* get length for uid as ascii string, i.e. in file/folder name */
l = intlen ((int) pw->pw_uid);
/* construct user dir, e.g: "/run/user/1000" */
char dir[sizeof (PARENT_DIR) + l + 2];
snprintf(dir, sizeof (PARENT_DIR) + l + 2, "%s/%d", PARENT_DIR, pw->pw_uid);
/* flag for processing on close_session */
if (pam_set_data (pamh, FLAG_NAME, (void *) 1, NULL) != PAM_SUCCESS)
{
char file[sizeof (PARENT_DIR) + l + 2];
int fd;
int count = 0;
int secbits = -1;
/* well shit... try to revert, though we can't do nothing if it
* fails. A PAM_BUF_ERR (only possible error) should be pretty rare
* though (especially combined with a failure to re-write). */
goto done;
}
print_filename (file, (int) pw->pw_uid, l);
fd = open_and_lock (file);
if (fd < 0)
return PAM_SESSION_ERR;
count = read_counter (fd);
if (count < 0)
{
/* -2: dir not usable, but not a failure */
r = (count == -2) ? 0 : -1;
goto done;
}
/* construct runtime dir name, i.e. remove the dot before uid */
memmove (file + sizeof (PARENT_DIR), file + sizeof (PARENT_DIR) + 1, l + 1);
/* update counter now, so we don't have to undo folder creation if
* updating the counter fails, etc. Having a count with a failure to
* create the folder isn't that big a deal (plus rare enough) to be
* worthy of complicating things. */
r = write_counter (fd, ++count);
if (r < 0)
goto done;
/* flag for processing on close_session */
if (pam_set_data (pamh, FLAG_NAME, (void *) 1, NULL) != PAM_SUCCESS)
{
/* well shit... try to revert, though we can't do nothing if it
* fails. A PAM_BUF_ERR (only possible error) should be pretty rare
* though (especially combined with a failure to re-write). */
write_counter (fd, --count);
r = -1;
goto done;
}
/* to bypass permission checks for mkdir, in case it isn't group
* writable */
secbits = prctl (PR_GET_SECUREBITS);
if (secbits != -1)
prctl (PR_SET_SECUREBITS, (unsigned long) secbits | SECBIT_NO_SETUID_FIXUP);
/* set euid so if we do create the dir, it is own by the user */
if (seteuid (pw->pw_uid) < 0)
{
r = -1;
goto done;
}
if (mkdir (file, S_IRWXU) == 0 || errno == EEXIST)
{
l = strlen (file);
char buf[sizeof (VAR_NAME) + 1 + l];
memcpy (buf, VAR_NAME, sizeof (VAR_NAME) - 1);
buf[sizeof (VAR_NAME) - 1] = '=';
memcpy (buf + sizeof (VAR_NAME), file, l + 1);
pam_putenv (pamh, buf);
}
/* restore */
if (seteuid (0) < 0)
{
r = -1;
goto done;
}
/* to bypass permission checks for mkdir, in case it isn't group
* writable */
int secbits = -1;
secbits = prctl (PR_GET_SECUREBITS);
if (secbits != -1)
prctl (PR_SET_SECUREBITS, (unsigned long) secbits | SECBIT_NO_SETUID_FIXUP);
/* set euid and egid so if we do create the dir, it is owned by the user */
if (seteuid (pw->pw_uid) < 0 || setegid (pw->pw_gid) < 0)
goto done;
if (mkdir (dir, S_IRWXU) == 0 || errno == EEXIST)
{
char buf[sizeof (VAR_NAME) + strlen (dir) + 1];
snprintf(buf, sizeof (VAR_NAME) + strlen (dir) + 1, "%s=%s", VAR_NAME, dir);
pam_putenv (pamh, buf);
}
/* restore */
if (seteuid (0) < 0 || setegid (0) < 0)
goto done;
done:
if (secbits != -1)
prctl (PR_SET_SECUREBITS, (unsigned long) secbits);
close (fd); /* also unlocks */
}
if (secbits != -1)
prctl (PR_SET_SECUREBITS, (unsigned long) secbits);
return (r == 0) ? PAM_SUCCESS : PAM_SESSION_ERR;
}