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, (close session), and its full path made available in an environment variable,
usually `$XDG_RUNTIME_DIR`. usually `$XDG_RUNTIME_DIR`.
This fork contains some changes of the original implementation for Artix Linux.
## Free Software ## Free Software
pam_rundir - Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com> pam_rundir - Copyright (C) 2015 Olivier Brunel <jjk@jjacky.com>

2
configure vendored
View File

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

View File

@@ -20,24 +20,39 @@
#include "config.h" #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 <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 #define PAM_SM_SESSION
#include <security/pam_modules.h> #include <security/pam_modules.h>
#include <security/pam_appl.h>
#define FLAG_NAME "pam_rundir_has_counted" #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 static int
ensure_parent_dir (void) ensure_parent_dir (void)
{ {
@@ -51,6 +66,23 @@ ensure_parent_dir (void)
return r; 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 static int
intlen (int n) intlen (int n)
{ {
@@ -66,159 +98,6 @@ intlen (int n)
return l; 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 static int
rmrf (const char *path) rmrf (const char *path)
{ {
@@ -243,10 +122,7 @@ rmrf (const char *path)
int l = lp + strlen (dp->d_name) + 2; int l = lp + strlen (dp->d_name) + 2;
char name[l]; char name[l];
memcpy (name, path, lp); snprintf(name, l, "%s/%s", path, dp->d_name);
name[lp] = '/';
memcpy (name + lp + 1, dp->d_name, l - lp - 1);
r += rmrf (name); r += rmrf (name);
} }
} }
@@ -257,6 +133,69 @@ rmrf (const char *path)
return r; 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_EXTERN int
pam_sm_close_session (pam_handle_t *pamh, int flags, int argc, const char **argv) 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 */ /* get length for uid as ascii string, i.e. in file/folder name */
l = intlen ((int) pw->pw_uid); l = intlen ((int) pw->pw_uid);
{ /* construct user dir, e.g: "/run/user/1000" */
char file[sizeof (PARENT_DIR) + l + 2]; char dir[sizeof (PARENT_DIR) + l + 2];
int fd; snprintf(dir, sizeof (PARENT_DIR) + l + 2, "%s/%d", PARENT_DIR, pw->pw_uid);
int count = 0;
print_filename (file, (int) pw->pw_uid, l); if (!user_has_session (user))
fd = open_and_lock (file); r = rmrf (dir);
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 */
}
return (r == 0) ? PAM_SUCCESS : PAM_SESSION_ERR; 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 */ /* get length for uid as ascii string, i.e. in file/folder name */
l = intlen ((int) pw->pw_uid); 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]; /* well shit... try to revert, though we can't do nothing if it
int fd; * fails. A PAM_BUF_ERR (only possible error) should be pretty rare
int count = 0; * though (especially combined with a failure to re-write). */
int secbits = -1; goto done;
}
print_filename (file, (int) pw->pw_uid, l); /* to bypass permission checks for mkdir, in case it isn't group
fd = open_and_lock (file); * writable */
if (fd < 0) int secbits = -1;
return PAM_SESSION_ERR; secbits = prctl (PR_GET_SECUREBITS);
if (secbits != -1)
count = read_counter (fd); prctl (PR_SET_SECUREBITS, (unsigned long) secbits | SECBIT_NO_SETUID_FIXUP);
if (count < 0) /* 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)
/* -2: dir not usable, but not a failure */ goto done;
r = (count == -2) ? 0 : -1; if (mkdir (dir, S_IRWXU) == 0 || errno == EEXIST)
goto done; {
} char buf[sizeof (VAR_NAME) + strlen (dir) + 1];
snprintf(buf, sizeof (VAR_NAME) + strlen (dir) + 1, "%s=%s", VAR_NAME, dir);
/* construct runtime dir name, i.e. remove the dot before uid */ pam_putenv (pamh, buf);
memmove (file + sizeof (PARENT_DIR), file + sizeof (PARENT_DIR) + 1, l + 1); }
/* restore */
/* update counter now, so we don't have to undo folder creation if if (seteuid (0) < 0 || setegid (0) < 0)
* updating the counter fails, etc. Having a count with a failure to goto done;
* 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;
}
done: done:
if (secbits != -1) if (secbits != -1)
prctl (PR_SET_SECUREBITS, (unsigned long) secbits); prctl (PR_SET_SECUREBITS, (unsigned long) secbits);
close (fd); /* also unlocks */
}
return (r == 0) ? PAM_SUCCESS : PAM_SESSION_ERR; return (r == 0) ? PAM_SUCCESS : PAM_SESSION_ERR;
} }