summaryrefslogtreecommitdiffstats
path: root/src/login/pam_systemd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/login/pam_systemd.c')
-rw-r--r--src/login/pam_systemd.c184
1 files changed, 100 insertions, 84 deletions
diff --git a/src/login/pam_systemd.c b/src/login/pam_systemd.c
index bf45974..a711c89 100644
--- a/src/login/pam_systemd.c
+++ b/src/login/pam_systemd.c
@@ -10,6 +10,9 @@
#include <security/pam_modules.h>
#include <security/pam_modutil.h>
#include <sys/file.h>
+#if HAVE_PIDFD_OPEN
+#include <sys/pidfd.h>
+#endif
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
@@ -525,6 +528,26 @@ static const char* getenv_harder(pam_handle_t *handle, const char *key, const ch
return fallback;
}
+static bool getenv_harder_bool(pam_handle_t *handle, const char *key, bool fallback) {
+ const char *v;
+ int r;
+
+ assert(handle);
+ assert(key);
+
+ v = getenv_harder(handle, key, NULL);
+ if (isempty(v))
+ return fallback;
+
+ r = parse_boolean(v);
+ if (r < 0) {
+ pam_syslog(handle, LOG_ERR, "Boolean environment variable value of '%s' is not valid: %s", key, v);
+ return fallback;
+ }
+
+ return r;
+}
+
static int update_environment(pam_handle_t *handle, const char *key, const char *value) {
int r;
@@ -606,6 +629,7 @@ static int apply_user_record_settings(
bool debug,
uint64_t default_capability_bounding_set,
uint64_t default_capability_ambient_set) {
+ _cleanup_strv_free_ char **langs = NULL;
int r;
assert(handle);
@@ -617,48 +641,25 @@ static int apply_user_record_settings(
}
STRV_FOREACH(i, ur->environment) {
- _cleanup_free_ char *n = NULL;
- const char *e;
-
- assert_se(e = strchr(*i, '=')); /* environment was already validated while parsing JSON record, this thus must hold */
-
- n = strndup(*i, e - *i);
- if (!n)
- return pam_log_oom(handle);
-
- if (pam_getenv(handle, n)) {
- pam_debug_syslog(handle, debug,
- "PAM environment variable $%s already set, not changing based on record.", *i);
- continue;
- }
-
r = pam_putenv_and_log(handle, *i, debug);
if (r != PAM_SUCCESS)
return r;
}
if (ur->email_address) {
- if (pam_getenv(handle, "EMAIL"))
- pam_debug_syslog(handle, debug,
- "PAM environment variable $EMAIL already set, not changing based on user record.");
- else {
- _cleanup_free_ char *joined = NULL;
+ _cleanup_free_ char *joined = NULL;
- joined = strjoin("EMAIL=", ur->email_address);
- if (!joined)
- return pam_log_oom(handle);
+ joined = strjoin("EMAIL=", ur->email_address);
+ if (!joined)
+ return pam_log_oom(handle);
- r = pam_putenv_and_log(handle, joined, debug);
- if (r != PAM_SUCCESS)
- return r;
- }
+ r = pam_putenv_and_log(handle, joined, debug);
+ if (r != PAM_SUCCESS)
+ return r;
}
if (ur->time_zone) {
- if (pam_getenv(handle, "TZ"))
- pam_debug_syslog(handle, debug,
- "PAM environment variable $TZ already set, not changing based on user record.");
- else if (!timezone_is_valid(ur->time_zone, LOG_DEBUG))
+ if (!timezone_is_valid(ur->time_zone, LOG_DEBUG))
pam_debug_syslog(handle, debug,
"Time zone specified in user record is not valid locally, not setting $TZ.");
else {
@@ -674,21 +675,38 @@ static int apply_user_record_settings(
}
}
- if (ur->preferred_language) {
- if (pam_getenv(handle, "LANG"))
- pam_debug_syslog(handle, debug,
- "PAM environment variable $LANG already set, not changing based on user record.");
- else if (locale_is_installed(ur->preferred_language) <= 0)
- pam_debug_syslog(handle, debug,
- "Preferred language specified in user record is not valid or not installed, not setting $LANG.");
- else {
- _cleanup_free_ char *joined = NULL;
+ r = user_record_languages(ur, &langs);
+ if (r < 0)
+ pam_syslog_errno(handle, LOG_ERR, r,
+ "Failed to acquire user's language preferences, ignoring: %m");
+ else if (strv_isempty(langs))
+ ; /* User has no preference set so we do nothing */
+ else if (locale_is_installed(langs[0]) <= 0)
+ pam_debug_syslog(handle, debug,
+ "Preferred languages specified in user record are not installed locally, not setting $LANG or $LANGUAGE.");
+ else {
+ _cleanup_free_ char *lang = NULL;
+
+ lang = strjoin("LANG=", langs[0]);
+ if (!lang)
+ return pam_log_oom(handle);
+
+ r = pam_putenv_and_log(handle, lang, debug);
+ if (r != PAM_SUCCESS)
+ return r;
+
+ if (strv_length(langs) > 1) {
+ _cleanup_free_ char *joined = NULL, *language = NULL;
- joined = strjoin("LANG=", ur->preferred_language);
+ joined = strv_join(langs, ":");
if (!joined)
return pam_log_oom(handle);
- r = pam_putenv_and_log(handle, joined, debug);
+ language = strjoin("LANGUAGE=", joined);
+ if (!language)
+ return pam_log_oom(handle);
+
+ r = pam_putenv_and_log(handle, language, debug);
if (r != PAM_SUCCESS)
return r;
}
@@ -908,12 +926,14 @@ _public_ PAM_EXTERN int pam_sm_open_session(
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
_cleanup_(user_record_unrefp) UserRecord *ur = NULL;
int session_fd = -EBADF, existing, r;
- bool debug = false, remote;
+ bool debug = false, remote, incomplete;
uint32_t vtnr = 0;
uid_t original_uid;
assert(handle);
+ pam_log_setup();
+
if (parse_argv(handle,
argc, argv,
&class_pam,
@@ -934,57 +954,39 @@ _public_ PAM_EXTERN int pam_sm_open_session(
if (!logind_running())
goto success;
- /* Make sure we don't enter a loop by talking to
- * systemd-logind when it is actually waiting for the
- * background to finish start-up. If the service is
- * "systemd-user" we simply set XDG_RUNTIME_DIR and
- * leave. */
-
- r = pam_get_item(handle, PAM_SERVICE, (const void**) &service);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM service: @PAMERR@");
- if (streq_ptr(service, "systemd-user")) {
- char rt[STRLEN("/run/user/") + DECIMAL_STR_MAX(uid_t)];
-
- xsprintf(rt, "/run/user/"UID_FMT, ur->uid);
- r = configure_runtime_directory(handle, ur, rt);
- if (r != PAM_SUCCESS)
- return r;
-
- goto success;
- }
-
- /* Otherwise, we ask logind to create a session for us */
-
- r = pam_get_item(handle, PAM_XDISPLAY, (const void**) &display);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_XDISPLAY: @PAMERR@");
- r = pam_get_item(handle, PAM_TTY, (const void**) &tty);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_TTY: @PAMERR@");
- r = pam_get_item(handle, PAM_RUSER, (const void**) &remote_user);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RUSER: @PAMERR@");
- r = pam_get_item(handle, PAM_RHOST, (const void**) &remote_host);
- if (!IN_SET(r, PAM_BAD_ITEM, PAM_SUCCESS))
- return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM_RHOST: @PAMERR@");
+ r = pam_get_item_many(
+ handle,
+ PAM_SERVICE, &service,
+ PAM_XDISPLAY, &display,
+ PAM_TTY, &tty,
+ PAM_RUSER, &remote_user,
+ PAM_RHOST, &remote_host);
+ if (r != PAM_SUCCESS)
+ return pam_syslog_pam_error(handle, LOG_ERR, r, "Failed to get PAM items: @PAMERR@");
seat = getenv_harder(handle, "XDG_SEAT", NULL);
cvtnr = getenv_harder(handle, "XDG_VTNR", NULL);
type = getenv_harder(handle, "XDG_SESSION_TYPE", type_pam);
class = getenv_harder(handle, "XDG_SESSION_CLASS", class_pam);
desktop = getenv_harder(handle, "XDG_SESSION_DESKTOP", desktop_pam);
+ incomplete = getenv_harder_bool(handle, "XDG_SESSION_INCOMPLETE", false);
- tty = strempty(tty);
+ if (streq_ptr(service, "systemd-user")) {
+ /* If we detect that we are running in the "systemd-user" PAM stack, then let's patch the class to
+ * 'manager' if not set, simply for robustness reasons. */
+ type = "unspecified";
+ class = IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) ?
+ "manager-early" : "manager";
+ tty = NULL;
- if (strchr(tty, ':')) {
+ } else if (tty && strchr(tty, ':')) {
/* A tty with a colon is usually an X11 display, placed there to show up in utmp. We rearrange things
* and don't pretend that an X display was a tty. */
if (isempty(display))
display = tty;
tty = NULL;
- } else if (streq(tty, "cron")) {
+ } else if (streq_ptr(tty, "cron")) {
/* cron is setting PAM_TTY to "cron" for some reason (the commit carries no information why, but
* probably because it wants to set it to something as pam_time/pam_access/… require PAM_TTY to be set
* (as they otherwise even try to update it!) — but cron doesn't actually allocate a TTY for its forked
@@ -993,10 +995,10 @@ _public_ PAM_EXTERN int pam_sm_open_session(
class = "background";
tty = NULL;
- } else if (streq(tty, "ssh")) {
+ } else if (streq_ptr(tty, "ssh")) {
/* ssh has been setting PAM_TTY to "ssh" (for the same reason as cron does this, see above. For further
* details look for "PAM_TTY_KLUDGE" in the openssh sources). */
- type ="tty";
+ type = "tty";
class = "user";
tty = NULL; /* This one is particularly sad, as this means that ssh sessions — even though usually
* associated with a pty — won't be tracked by their tty in logind. This is because ssh
@@ -1004,7 +1006,7 @@ _public_ PAM_EXTERN int pam_sm_open_session(
* much later (this is because it doesn't know yet if it needs one at all, as whether to
* register a pty or not is negotiated much later in the protocol). */
- } else
+ } else if (tty)
/* Chop off leading /dev prefix that some clients specify, but others do not. */
tty = skip_dev_prefix(tty);
@@ -1029,7 +1031,16 @@ _public_ PAM_EXTERN int pam_sm_open_session(
!isempty(tty) ? "tty" : "unspecified";
if (isempty(class))
- class = streq(type, "unspecified") ? "background" : "user";
+ class = streq(type, "unspecified") ? "background" :
+ ((IN_SET(user_record_disposition(ur), USER_INTRINSIC, USER_SYSTEM, USER_DYNAMIC) &&
+ streq(type, "tty")) ? "user-early" : "user");
+
+ if (incomplete) {
+ if (streq(class, "user"))
+ class = "user-incomplete";
+ else
+ pam_syslog_pam_error(handle, LOG_WARNING, 0, "PAM session of class '%s' is incomplete, which is not supported, ignoring.", class);
+ }
remote = !isempty(remote_host) && !is_localhost(remote_host);
@@ -1144,6 +1155,9 @@ _public_ PAM_EXTERN int pam_sm_open_session(
"id=%s object_path=%s runtime_path=%s session_fd=%d seat=%s vtnr=%u original_uid=%u",
id, object_path, runtime_path, session_fd, seat, vtnr, original_uid);
+ /* Please update manager_default_environment() in core/manager.c accordingly if more session envvars
+ * shall be added. */
+
r = update_environment(handle, "XDG_SESSION_ID", id);
if (r != PAM_SUCCESS)
return r;
@@ -1221,6 +1235,8 @@ _public_ PAM_EXTERN int pam_sm_close_session(
assert(handle);
+ pam_log_setup();
+
if (parse_argv(handle,
argc, argv,
NULL,