/* 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"
#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"
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))
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);
#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"
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) {
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;
const char *cpu_weight;
const char *io_weight;
const char *runtime_max_sec;
+ const char *area;
bool incomplete;
} SessionContext;
}
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) {
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,
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)
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(
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);
/* class= */ NULL,
/* type= */ NULL,
/* desktop= */ NULL,
+ /* area= */ NULL,
&debug,
/* default_capability_bounding_set */ NULL,
/* default_capability_ambient_set= */ NULL) < 0)