Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
5338dec85e | |||
0f0e234980 | |||
25c77fc2a5 | |||
58aa4d55a8 | |||
e076594359 | |||
b065d5f273 | |||
70a11e4148 | |||
d6cc5d7da7 |
@@ -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
2
configure
vendored
@@ -17,7 +17,7 @@ EOF
|
||||
|
||||
prefix=/
|
||||
securedir=
|
||||
parentdir=/run/users
|
||||
parentdir=/run/user
|
||||
varname=XDG_RUNTIME_DIR
|
||||
|
||||
cmdline="$0 $@"
|
||||
|
423
pam_rundir.c
423
pam_rundir.c
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user