mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 00:47:10 +09:00
pam: introduce multiple per-user "areas", i.e. "sub-home-directories" of sorts
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <security/pam_ext.h>
|
||||
#include <security/pam_misc.h>
|
||||
#include <security/pam_modules.h>
|
||||
|
||||
#include "sd-bus.h"
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "memory-util.h"
|
||||
#include "pam-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "strv.h"
|
||||
#include "user-record-util.h"
|
||||
#include "user-record.h"
|
||||
@@ -114,6 +116,20 @@ static int acquire_user_record(
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, PAM_SERVICE_ERR, "User name not set.");
|
||||
}
|
||||
|
||||
/* Possibly split out the area name */
|
||||
_cleanup_free_ char *username_without_area = NULL, *area = NULL;
|
||||
const char *carea = strrchr(username, '%');
|
||||
if (carea && (filename_is_valid(carea + 1) || isempty(carea + 1))) {
|
||||
username_without_area = strndup(username, carea - username);
|
||||
if (!username_without_area)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
username = username_without_area;
|
||||
area = strdup(carea + 1);
|
||||
if (!area)
|
||||
return pam_log_oom(handle);
|
||||
}
|
||||
|
||||
/* Let's bypass all IPC complexity for the two user names we know for sure we don't manage, and for
|
||||
* user names we don't consider valid. */
|
||||
if (STR_IN_SET(username, "root", NOBODY_USER_NAME) || !valid_user_group_name(username, 0))
|
||||
@@ -242,6 +258,14 @@ static int acquire_user_record(
|
||||
TAKE_PTR(json_copy);
|
||||
}
|
||||
|
||||
/* Let's store the area we parsed out of the name in an env var, so that pam_systemd later can honour it. */
|
||||
if (area) {
|
||||
r = pam_misc_setenv(handle, "XDG_AREA", area, /* readonly= */ 0);
|
||||
if (r != PAM_SUCCESS)
|
||||
return pam_syslog_pam_error(handle, LOG_ERR, r,
|
||||
"Failed to set environment variable $XDG_AREA to '%s': @PAMERR@", area);
|
||||
}
|
||||
|
||||
if (ret_record)
|
||||
*ret_record = TAKE_PTR(ur);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "cap-list.h"
|
||||
#include "capability-util.h"
|
||||
#include "cgroup-setup.h"
|
||||
#include "chase.h"
|
||||
#include "creds-util.h"
|
||||
#include "devnum-util.h"
|
||||
#include "errno-util.h"
|
||||
@@ -120,6 +121,7 @@ static int parse_argv(
|
||||
const char **class,
|
||||
const char **type,
|
||||
const char **desktop,
|
||||
const char **area,
|
||||
bool *debug,
|
||||
uint64_t *default_capability_bounding_set,
|
||||
uint64_t *default_capability_ambient_set) {
|
||||
@@ -145,6 +147,13 @@ static int parse_argv(
|
||||
if (desktop)
|
||||
*desktop = p;
|
||||
|
||||
} else if ((p = startswith(argv[i], "area="))) {
|
||||
|
||||
if (!isempty(p) && !filename_is_valid(p))
|
||||
pam_syslog(handle, LOG_WARNING, "Area name specified among PAM module parameters is not valid, ignoring: %m");
|
||||
else if (area)
|
||||
*area = p;
|
||||
|
||||
} else if (streq(argv[i], "debug")) {
|
||||
if (debug)
|
||||
*debug = true;
|
||||
@@ -854,6 +863,7 @@ typedef struct SessionContext {
|
||||
const char *cpu_weight;
|
||||
const char *io_weight;
|
||||
const char *runtime_max_sec;
|
||||
const char *area;
|
||||
bool incomplete;
|
||||
} SessionContext;
|
||||
|
||||
@@ -1043,6 +1053,14 @@ static void session_context_mangle(
|
||||
}
|
||||
|
||||
c->remote = !isempty(c->remote_host) && !is_localhost(c->remote_host);
|
||||
|
||||
if (!c->area)
|
||||
c->area = ur->default_area;
|
||||
|
||||
if (!isempty(c->area) && !filename_is_valid(c->area)) {
|
||||
pam_syslog_pam_error(handle, LOG_WARNING, 0, "Specified area '%s' is not a valid filename, ignoring area request.", c->area);
|
||||
c->area = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool can_use_varlink(const SessionContext *c) {
|
||||
@@ -1374,6 +1392,73 @@ static int import_shell_credentials(pam_handle_t *handle) {
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
static int update_home_env(
|
||||
pam_handle_t *handle,
|
||||
UserRecord *ur,
|
||||
const char *area,
|
||||
bool debug) {
|
||||
|
||||
int r;
|
||||
|
||||
assert(handle);
|
||||
assert(ur);
|
||||
|
||||
const char *h = ASSERT_PTR(user_record_home_directory(ur));
|
||||
|
||||
/* If an empty area string is specified, this means an explicit: do not use the area logic, normalize this here */
|
||||
area = empty_to_null(area);
|
||||
|
||||
_cleanup_free_ char *ha = NULL;
|
||||
if (area) {
|
||||
_cleanup_free_ char *j = path_join(h, "Areas", area);
|
||||
if (!j)
|
||||
return pam_log_oom(handle);
|
||||
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
r = chase(j, /* root= */ NULL, CHASE_MUST_BE_DIRECTORY, &ha, &fd);
|
||||
if (r < 0) {
|
||||
/* Log the precise error */
|
||||
pam_syslog_errno(handle, LOG_WARNING, r, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory: %m", j, area);
|
||||
|
||||
/* Also tell the user directly at login, but a bit more vague */
|
||||
pam_info(handle, "Path '%s' of requested user area '%s' is not accessible, reverting to regular home directory.", j, area);
|
||||
area = NULL;
|
||||
} else {
|
||||
/* Validate that the target is definitely owned by user */
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) < 0)
|
||||
return pam_syslog_errno(handle, LOG_ERR, errno, "Unable to fstat() target area directory '%s': %m", ha);
|
||||
|
||||
if (st.st_uid != ur->uid) {
|
||||
pam_syslog(handle, LOG_ERR, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area);
|
||||
|
||||
/* Also tell the user directly at login. */
|
||||
pam_info(handle, "Path '%s' of requested user area '%s' is not owned by user, reverting to regular home directory.", ha, area);
|
||||
area = NULL;
|
||||
} else {
|
||||
pam_debug_syslog(handle, debug, "Area '%s' selected, setting $HOME to '%s': %m", area, ha);
|
||||
h = ha;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (area) {
|
||||
r = update_environment(handle, "XDG_AREA", area);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
} else if (pam_getenv(handle, "XDG_AREA")) {
|
||||
/* Unset the $XDG_AREA variable if set. Note that pam_putenv() would log nastily behind our
|
||||
* back if we call it without $XDG_AREA actually being set. Hence we check explicitly if it's
|
||||
* set before. */
|
||||
r = pam_putenv(handle, "XDG_AREA");
|
||||
if (!IN_SET(r, PAM_SUCCESS, PAM_BAD_ITEM))
|
||||
pam_syslog_pam_error(handle, LOG_WARNING, r,
|
||||
"Failed to unset XDG_AREA environment variable, ignoring: @PAMERR@");
|
||||
}
|
||||
|
||||
return update_environment(handle, "HOME", h);
|
||||
}
|
||||
|
||||
_public_ PAM_EXTERN int pam_sm_open_session(
|
||||
pam_handle_t *handle,
|
||||
int flags,
|
||||
@@ -1386,13 +1471,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
pam_log_setup();
|
||||
|
||||
uint64_t default_capability_bounding_set = UINT64_MAX, default_capability_ambient_set = UINT64_MAX;
|
||||
const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL;
|
||||
const char *class_pam = NULL, *type_pam = NULL, *desktop_pam = NULL, *area_pam = NULL;
|
||||
bool debug = false;
|
||||
if (parse_argv(handle,
|
||||
argc, argv,
|
||||
&class_pam,
|
||||
&type_pam,
|
||||
&desktop_pam,
|
||||
&area_pam,
|
||||
&debug,
|
||||
&default_capability_bounding_set,
|
||||
&default_capability_ambient_set) < 0)
|
||||
@@ -1421,6 +1507,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
c.type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
|
||||
c.class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
|
||||
c.desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
|
||||
c.area = getenv_harder(handle, "XDG_AREA", area_pam);
|
||||
c.incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false);
|
||||
|
||||
r = pam_get_data_many(
|
||||
@@ -1444,6 +1531,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
r = update_home_env(handle, ur, c.area, debug);
|
||||
if (r != PAM_SUCCESS)
|
||||
return r;
|
||||
|
||||
if (default_capability_ambient_set == UINT64_MAX)
|
||||
default_capability_ambient_set = pick_default_capability_ambient_set(ur, c.service, c.seat);
|
||||
|
||||
@@ -1469,6 +1560,7 @@ _public_ PAM_EXTERN int pam_sm_close_session(
|
||||
/* class= */ NULL,
|
||||
/* type= */ NULL,
|
||||
/* desktop= */ NULL,
|
||||
/* area= */ NULL,
|
||||
&debug,
|
||||
/* default_capability_bounding_set */ NULL,
|
||||
/* default_capability_ambient_set= */ NULL) < 0)
|
||||
|
||||
Reference in New Issue
Block a user